diff --git a/README.md b/README.md index 6bfcc9e..c03e484 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,21 @@ All people, companies, vehicles, bookings and documents are synthetic. The workf The PoC implements: -- operations dashboard; -- vehicle and booking views; -- one complete vehicle-return workflow; -- five deterministic data-quality checks; +- operations dashboard with a truthful aggregate n8n/MCP integration-status card; +- vehicle and booking views with working search, filters and pagination; +- server-backed session lifecycle (refresh-safe, central 401 handling); +- a role matrix enforced server-side and mirrored in the UI (see + `docs/12-security-and-audit.md`); +- vehicle return capture → authoritative server-evaluated review → commit → result; +- five deterministic data-quality checks, each with a bounded resolution flow, plus a + manual scan action; - human review and customer merge; -- audit trail; +- audit trail with human-readable before/after evidence and safe entity links; +- role-aware global search across vehicles, bookings and (Operations Manager) issues; +- safe, confirmed demo reset; - RAGcore-backed knowledge assistant with citations; -- one n8n return-processing workflow; +- two n8n workflows: return processing, and a scheduled data-quality scan with + crash-recoverable outbox delivery leases; - four read-only MCP tools through ITWorx MCP Hub; - deterministic demo reset and five-minute showcase. @@ -33,18 +40,26 @@ It is not an ERP, CRM, accounting package, public booking site, payment system o - **n8n**: fully implemented and verified against a real n8n instance, including degraded mode (n8n stopped mid-flow → return still commits, event stays `pending` - with backoff, self-heals once n8n returns) and the failed-delivery manual-retry path. + with backoff, self-heals once n8n returns), the failed-delivery manual-retry path, + stale-delivery-lease recovery after a simulated crash, and a second (scheduled + quality-scan) workflow live-verified end to end against a real n8n instance. + `GET /api/v1/integrations/status` reports a truthful aggregate state from outbox + delivery counts, not just the most recent event. - **RAGcore**: the demo `KnowledgeProvider` (deterministic TF-IDF extractive retrieval over the local procedure documents) is what satisfies the knowledge-assistant acceptance criteria and is fully verified. A `RAGcoreKnowledgeProvider` HTTP adapter is implemented and unit-tested, including its unavailable-degradation path, but was never exercised against a live RAGcore instance in this environment. - **ITWorx MCP Hub**: the four read-only provider endpoints are implemented, tested, and - directly `curl`-verified with correct auth enforcement and audit logging. No live Hub - instance was reachable in this environment to verify an actual Hub round trip. + directly `curl`-verified with correct auth enforcement and audit logging. + `MCP_HUB_REGISTRATION_ENABLED` is now actually wired into `Settings` (it was previously + declared in `.env.example` but silently dropped) and reported honestly by the + integration-status endpoint. No live Hub instance was reachable in this environment to + verify an actual Hub round trip. -See `artifacts/final-acceptance/summary.md` for full verification evidence and exact -commands. +See `artifacts/functional-completion/final-summary.md` for the functional-completion +audit evidence (supersedes the design-validation summary below for integration status), +and `artifacts/final-acceptance/summary.md` for the original M0–M7 acceptance evidence. ## Repository map @@ -60,6 +75,8 @@ commands. - `frontend/` — React/TypeScript/Vite web app, including the Playwright end-to-end suite (`frontend/e2e/`). - `artifacts/evidence/` — final acceptance evidence (screenshots, architecture, `final-summary.md`). - `artifacts/design-validation/` — baseline audit, Stitch direction references and implemented responsive captures. +- `docs/functional-completion/` — the functional-completion audit and pre-work server baseline. +- `artifacts/functional-completion/` — functional-completion acceptance evidence. ## Quickstart @@ -83,9 +100,9 @@ All defaults are configurable via `.env` (see `.env.example`). ## Quality gates ```bash -make test # backend: pytest (66 tests) +make test # backend: pytest (117 tests) make lint # backend: ruff + mypy (strict, zero errors) -make e2e # frontend: Playwright end-to-end (18 tests, live stack required) +make e2e # frontend: Playwright end-to-end (37 tests, live stack required) ``` Frontend build/typecheck: `cd frontend && npm run build` (`tsc -b && vite build`). diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml index 09cad2e..e30e681 100644 --- a/contracts/openapi.yaml +++ b/contracts/openapi.yaml @@ -66,6 +66,26 @@ paths: responses: '200': description: Booking detail + /api/v1/bookings/{public_ref}/return-preview: + post: + operationId: previewVehicleReturn + description: >- + Non-mutating evaluation of what committing this return would do. Shares its + domain evaluation with the commit endpoint below so the two can never drift. + No writes, no audit event, no outbox event. + parameters: + - $ref: '#/components/parameters/PublicRef' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterReturnRequest' + responses: + '200': + description: Authoritative evaluation of the resulting fleet state + '409': + description: Booking is not active /api/v1/bookings/{public_ref}/return: post: operationId: registerVehicleReturn @@ -94,9 +114,17 @@ paths: /api/v1/data-quality/issues: get: operationId: listDataQualityIssues + description: Operations Manager only. responses: '200': description: Quality issues + /api/v1/data-quality/scan: + post: + operationId: runDataQualityScan + description: Manual trigger for the deterministic five-rule scan. Operations Manager only. + responses: + '200': + description: Counts of newly created issues per rule type /api/v1/data-quality/issues/{public_ref}/merge-customers: post: operationId: mergeDuplicateCustomers @@ -107,6 +135,106 @@ paths: description: Merge completed '409': description: Issue no longer mergeable + /api/v1/data-quality/issues/{public_ref}/provide-fields: + post: + operationId: provideMissingFields + description: >- + missing_required_field only. Resolves once nothing required remains missing; + otherwise leaves the issue open with updated evidence. + parameters: + - $ref: '#/components/parameters/PublicRef' + responses: + '200': + description: Issue after the update (may still be open) + '409': + description: Wrong rule type or issue not open + '422': + description: Disallowed field or empty value + /api/v1/data-quality/issues/{public_ref}/resolve-odometer-regression: + post: + operationId: resolveOdometerRegression + description: >- + odometer_regression only. Either retains the canonical odometer, or corrects a + related booking's reading -- a correction below the current canonical value is + rejected, since it would not resolve the regression. + parameters: + - $ref: '#/components/parameters/PublicRef' + responses: + '200': + description: Issue resolved + '409': + description: Wrong rule type or issue not open + '422': + description: Invalid booking reference or a correction below canonical + /api/v1/data-quality/issues/{public_ref}/resolve-overlap: + post: + operationId: resolveBookingOverlap + description: >- + booking_overlap only. Blocks one of the two overlapping bookings and + re-verifies no overlap remains before resolving. + parameters: + - $ref: '#/components/parameters/PublicRef' + responses: + '200': + description: Issue resolved + '409': + description: Wrong rule type, issue not open, or overlap still present + '422': + description: booking_ref not one of the overlapping bookings + /api/v1/data-quality/issues/{public_ref}/apply-recommended-status: + post: + operationId: applyRecommendedVehicleStatus + description: >- + vehicle_status_conflict only. Applies the one authoritative recommendation + function's output and re-validates before resolving. + parameters: + - $ref: '#/components/parameters/PublicRef' + responses: + '200': + description: Applied status, reason and the resolved issue + '409': + description: Wrong rule type, issue not open, or no conflict detected + /api/v1/search: + get: + operationId: search + description: >- + Bounded typed results (vehicle, booking, data_quality_issue, section). + Data-quality and manager-only sections are filtered server-side by role. + Customers are never returned -- no customer detail route exists. + parameters: + - in: query + name: q + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + responses: + '200': + description: Search results + /api/v1/integrations/status: + get: + operationId: getIntegrationStatus + description: >- + Truthful aggregate n8n state derived from outbox delivery counts (not just the + most recent event), plus the actual MCP Hub registration_enabled setting. + Operations Manager only. + responses: + '200': + description: n8n and MCP Hub integration status + /api/v1/integrations/n8n/scheduled-scan: + post: + operationId: n8nScheduledScan + description: >- + Triggered by the scheduled n8n quality-scan workflow. Runs the same run_scan() + the manual UI action uses; idempotent by construction. + security: + - serviceToken: [] + responses: + '200': + description: Counts of newly created issues per rule type + '401': + description: Invalid service token /api/v1/knowledge/questions: post: operationId: askKnowledgeQuestion diff --git a/docs/05-api-contract.md b/docs/05-api-contract.md index 5b851ba..7f2d194 100644 --- a/docs/05-api-contract.md +++ b/docs/05-api-contract.md @@ -11,9 +11,10 @@ The demo may use signed server-issued sessions or short-lived JWTs. Demo-role bu ### System and demo - `GET /health` -- `GET /api/v1/system/status` - `POST /api/v1/demo/login` -- `POST /api/v1/demo/reset` — Operations Manager only +- `GET /api/v1/demo/session` — confirms the current session; `Cache-Control: no-store` +- `POST /api/v1/demo/logout` — safe to call without a session +- `POST /api/v1/demo/reset` — Operations Manager only; invalidates the caller's own session ### Dashboard @@ -28,17 +29,37 @@ The demo may use signed server-issued sessions or short-lived JWTs. Demo-role bu - `GET /api/v1/bookings` - `GET /api/v1/bookings/{public_ref}` +- `POST /api/v1/bookings/{public_ref}/return-preview` — non-mutating; shares its domain + evaluation with the commit endpoint below so the two cannot drift apart - `POST /api/v1/bookings/{public_ref}/return` -Return commands require an `Idempotency-Key` header and optimistic version where relevant. +Return commands require an `Idempotency-Key` header. Concurrency safety is row-lock based +(`SELECT ... FOR UPDATE` on the booking and vehicle); no optimistic-version field is +accepted or needed on top of that. ### Data quality +All Operations Manager only. + - `GET /api/v1/data-quality/issues` - `GET /api/v1/data-quality/issues/{public_ref}` +- `POST /api/v1/data-quality/scan` — manual trigger for the deterministic five-rule scan - `POST /api/v1/data-quality/issues/{public_ref}/defer` - `POST /api/v1/data-quality/issues/{public_ref}/reject` -- `POST /api/v1/data-quality/issues/{public_ref}/merge-customers` +- `POST /api/v1/data-quality/issues/{public_ref}/merge-customers` — possible_duplicate_customer +- `POST /api/v1/data-quality/issues/{public_ref}/provide-fields` — missing_required_field +- `POST /api/v1/data-quality/issues/{public_ref}/resolve-odometer-regression` — odometer_regression +- `POST /api/v1/data-quality/issues/{public_ref}/resolve-overlap` — booking_overlap +- `POST /api/v1/data-quality/issues/{public_ref}/apply-recommended-status` — vehicle_status_conflict + +Each of the five rule types has exactly one bounded resolution path above (plus +defer/reject, which apply to any open issue). + +### Search + +- `GET /api/v1/search?q=...` — bounded typed results (vehicle, booking, + data_quality_issue, section); role-filtered server-side; customers are never returned + (no customer detail route exists in this PoC) ### Knowledge @@ -47,9 +68,21 @@ Return commands require an `Idempotency-Key` header and optimistic version where ### Automation and audit -- `GET /api/v1/workflows` -- `POST /api/v1/workflows/{event_id}/retry` -- `GET /api/v1/audit` +- `GET /api/v1/workflows` — Operations Manager only +- `POST /api/v1/workflows/{event_id}/retry` — Operations Manager only +- `GET /api/v1/audit` — Operations Manager only; each event includes `before`/`after` + plus a resolved `entity_ref`/`entity_link` where the entity type supports one +- `GET /api/v1/integrations/status` — Operations Manager only; truthful aggregate n8n + state from outbox delivery counts (not just the most recent event), and the actual + MCP Hub `registration_enabled` setting + +### n8n-service endpoints + +Service-token protected (`X-Service-Token`, same shared secret as the return callback): + +- `POST /api/v1/integrations/n8n/return-callback` +- `POST /api/v1/integrations/n8n/scheduled-scan` — triggered by the scheduled + quality-scan workflow; runs the same domain scan the manual UI action uses ### MCP-provider endpoints diff --git a/docs/07-data-quality.md b/docs/07-data-quality.md index b6af10c..182c24e 100644 --- a/docs/07-data-quality.md +++ b/docs/07-data-quality.md @@ -25,14 +25,29 @@ Merge rewires booking references, preserves the loser as a tombstone and audits Required for active customers: first name, last name and at least one of email or phone. Required for active vehicles: registration number, make, model and location. +Resolution: `POST /provide-fields` accepts only the fields the entity type actually +requires (rejects anything else), applies them, and re-runs the same missing-field check. +The issue resolves only once nothing required remains missing; a partial submission +updates the record and its evidence but leaves the issue open. + ## DQ-03 Odometer regression Flag an inspection or maintenance reading below the canonical odometer. Never lower the canonical value automatically. +Resolution: `POST /resolve-odometer-regression` offers exactly two bounded decisions — +`retain_canonical` (the submitted reading is treated as erroneous; canonical is +untouched) or `correct_reading` (updates a named related booking's reading and the +vehicle's canonical odometer together). A `correct_reading` value below the current +canonical is rejected, since it would not resolve the regression, not silently applied. + ## DQ-04 Booking overlap Flag overlapping `reserved` or `active` bookings for one vehicle. Normal write APIs reject new overlaps; the seed/import path may create one controlled legacy conflict. +Resolution: `POST /resolve-overlap` blocks one of the two named overlapping bookings +(minimal safe resolution, not a scheduling calendar) and re-verifies no +reserved/active overlap remains among the issue's related bookings before resolving. + ## DQ-05 Vehicle status conflict Examples: @@ -42,6 +57,16 @@ Examples: - status `available` while critical open quality issue exists; - status `maintenance` with an active booking. +Resolution: `POST /apply-recommended-status` computes a recommendation from one +authoritative function mirroring the conditions above, applies it, and re-runs the same +function to confirm the conflict is actually gone before resolving. + ## Lifecycle -Detection is idempotent by `(rule_type, entity_type, entity_id, evidence fingerprint)` while open. Resolved issues remain historical. Reintroduced evidence creates a new issue linked to the prior issue where useful. +Detection is idempotent by `(rule_type, entity_type, entity_id)` while open — the CSV +seed rows don't carry a stable evidence fingerprint, so the literal +`(..., evidence fingerprint)` scheme from an earlier draft of this rule was dropped as +unworkable for seeded data; re-implementing it would need to reconcile with that. Resolved +issues remain historical. Reintroduced evidence creates a new issue whose evidence carries +`reopened_from` (the prior issue's reference) and `previous_decision` (its resolved +status), so a repeat problem is never presented as if no decision was ever made. diff --git a/docs/08-return-workflow.md b/docs/08-return-workflow.md index 525cd06..67f44a2 100644 --- a/docs/08-return-workflow.md +++ b/docs/08-return-workflow.md @@ -1,5 +1,16 @@ # Vehicle-return workflow +## Preview + +`POST /api/v1/bookings/{public_ref}/return-preview` takes the same request body as the +commit endpoint below and runs the identical evaluation (`evaluate_return()`) with no +writes, no audit event and no outbox event — it exists so the UI's review step shows the +server's actual answer instead of guessing the outcome client-side. It returns the +canonical and submitted odometer readings, whether the submission is a regression, the +resulting vehicle status with a human-readable reason, whether a quality issue would be +created, and next-booking risk. `register_vehicle_return` (below) calls the same +`evaluate_return()` function, so preview and commit cannot drift apart. + ## Input - booking public reference; diff --git a/docs/12-security-and-audit.md b/docs/12-security-and-audit.md index a330a44..0781efe 100644 --- a/docs/12-security-and-audit.md +++ b/docs/12-security-and-audit.md @@ -4,6 +4,30 @@ Role buttons may create a session for a seeded demo identity. All API routes still enforce authorization. Demo reset and customer merge require Operations Manager. +The browser never treats its own cached copy of the logged-in user as authoritative: +`AuthContext` re-verifies against `GET /api/v1/demo/session` on every app load (that +response is `Cache-Control: no-store`, so a stale cached "authenticated" response can't +survive a logout), and a central 401 listener on the API client clears local auth state +from any endpoint, not just the session check. `POST /api/v1/demo/logout` and +`POST /api/v1/demo/reset` both invalidate the session cookie server-side. + +## Role matrix + +| Capability | Rental Employee | Operations Manager | +|---|---|---| +| Dashboard, fleet, vehicle detail, bookings, booking detail | yes | yes | +| Register a vehicle return | yes | yes | +| Knowledge assistant | yes | yes | +| Data-quality workbench (view, scan, all resolutions) | no | yes | +| Integrations / automation status and retry | no | yes | +| Audit trail | no | yes | +| Demo reset | no | yes | + +Enforced server-side (every listed manager-only action returns `403` for Rental +Employee, verified by direct API tests, not just a hidden button) and mirrored in the +frontend nav (manager-only items are not rendered, not merely disabled) and route guards +(direct URL access shows a restricted message rather than partial data). + ## Service authentication Use separate scoped credentials for: @@ -42,6 +66,13 @@ Required actions: Audit is append-only through the application. Provide filters by actor, action, entity and correlation ID. +`GET /api/v1/audit` (Operations Manager only) returns `before`/`after` for every event +(the columns already existed but were not serialized until this pass) plus a resolved +`entity_ref`/`entity_link` for vehicle, booking and data-quality-issue entities (no +customer link exists — no customer detail route). The UI shows a human-readable +before/after summary per row by default, with the raw before/after/metadata JSON behind +a `
` disclosure rather than shown unconditionally. + ## Confirmation No write-capable MCP actions exist in this PoC. Destructive UI actions such as demo reset and customer merge require explicit confirmation.