Ran a dedicated post-M7 release-readiness audit. Found and fixed the one real gap: mypy was a declared dev dependency but had never been run in any milestone's validation loop. Fixed all 43 pre-existing type errors it surfaced, including two genuine defensive- programming gaps (unguarded Optional vehicle/customer lookups that could have crashed with unhandled 500s instead of clean 404/401 responses) rather than suppressing them. make lint now runs ruff + mypy; mypy reports zero errors across 44 source files. Re-verified end to end against a genuinely wiped-volumes clean checkout: automatic migrations, deterministic seed, 66/66 backend tests, and the full user-journey matrix (login, dashboard, vehicle/booking detail, return workflow, invalid-mileage rejection, data-quality review, duplicate-customer merge, audit trail, Knowledge Assistant, n8n, MCP Hub) via curl and Playwright. Live-verified both external-dependency degraded modes, not just unit tests: stopped n8n mid-flow and confirmed a return still commits with the outbox event staying pending and retrying with backoff, then self-healing to succeeded with zero manual intervention once n8n came back; verified RAGcore's unavailable-degradation path against an unreachable host. Added frontend/e2e/interactive-elements.spec.ts (11 tests covering every nav item, filter, tab, and role boundary) alongside the existing demo script test — 12/12 e2e tests passing. Verified no secrets are committed (.env never tracked, clean git history scan) and .env.example covers every operator-configurable setting. Confirmed no placeholders, TODOs, fake responses, hardcoded metrics, or dead routes anywhere in the codebase. Updated README.md with an honest integration-status section and PROJECT_STATE.md with the full audit findings. Added artifacts/final-acceptance/summary.md as the authoritative final evidence document (commands, results, URLs, demo access, integration status per external dependency, known limitations, deployment instructions, five-minute demo flow).
16 KiB
MobilityOps — final acceptance audit summary
This audit was run after M0–M7 had already been implemented and committed, specifically
to independently re-verify the finished system end to end rather than trust the
milestone-by-milestone build log. It found and fixed one real category of defect
(mypy had never been run across the whole build) and confirmed everything else — every
user journey, every button/filter/form, both external-dependency degraded modes, secret
hygiene, and the clean-checkout path — works as documented.
Final commit
This audit's fixes are committed as the commit immediately following
108b5d04fc6f7c5ff9c47009032d6469df29cf3c ("M7: portfolio polish and final acceptance").
Run git log -1 --format="%H %s" for the exact hash.
Exact commands executed
Clean-checkout drill (run twice during this audit, most recently against fully wiped Docker volumes):
git status # working tree clean before starting
docker compose down -v # wipe all volumes — genuinely clean state
cp .env.example .env
docker compose up --build -d # migrations run automatically (backend/entrypoint.sh)
docker compose exec api python -m app.cli seed --reset
docker compose run --rm api pytest -q
docker compose run --rm api ruff check .
docker compose run --rm api mypy app
cd frontend && npm run build
cd frontend && npx playwright test
n8n one-time setup (owner account via browser at http://localhost:5678/setup, then):
docker compose exec n8n n8n import:workflow --input=//imports/mobilityops-return-processing.json
docker compose exec n8n n8n publish:workflow --id=mobilityops-return-processing
docker compose restart n8n
Degraded-mode drills:
docker compose stop n8n # then register a return via the API — commits, event stays pending
docker compose start n8n # dispatcher self-heals, no manual intervention
docker compose run --rm -e KNOWLEDGE_PROVIDER=ragcore -e RAGCORE_BASE_URL=http://ragcore-not-reachable:9999 \
api python -c "from app.services.knowledge import get_knowledge_provider; ..."
Test and validation results
| Check | Command | Result |
|---|---|---|
| Backend unit/integration tests | docker compose run --rm api pytest -q |
66 passed, 0 failed, 0 skipped |
| Backend lint | docker compose run --rm api ruff check . |
All checks passed |
| Backend type check | docker compose run --rm api mypy app |
Success: no issues found in 44 source files (found and fixed 43 pre-existing errors this audit — see below) |
| Frontend build + typecheck | cd frontend && npm run build |
Clean (tsc -b && vite build, zero errors) |
| End-to-end (Playwright) | cd frontend && npx playwright test |
12 passed (demo.spec.ts — full 9-step demo script; interactive-elements.spec.ts — 11 tests covering every nav item, filter, tab, and role boundary) |
| Clean-checkout migrations | docker compose down -v && docker compose up --build -d |
11 tables created automatically, alembic current → e7b08389f47f (head), zero manual step |
| Deterministic seed | docker compose exec api python -m app.cli seed --reset |
users:2 customers:180 vehicles:50 bookings:246 inspections:75 maintenance:40 data_quality_issues:26 workflow_runs:20 — identical across every reseed this session |
| Secret scan | git ls-files | grep -x .env; git log --all -p -- '*.env'; history grep for AWS/private-key/sk- patterns |
No .env ever committed; no secrets found in history |
mypy defects found and fixed (the one real gap this audit uncovered)
mypy is a declared dev dependency (backend/pyproject.toml) but was never added to any
milestone's validation loop — only ruff was run throughout M0–M7. Running it cold
surfaced 43 errors across 10 files. All were triaged and fixed (not suppressed):
- Two genuine defensive-programming gaps, not just type-annotation issues:
app/services/returns.py: the vehicle lookup after acquiring the row lock had noNoneguard; a dangling FK would have crashed with an unhandled 500 instead of a clean404 VEHICLE_NOT_FOUND. Fixed.app/api/routers/bookings.py: same pattern inget_bookingfor the customer/vehicle lookups — now returns a clean500with a message instead of anAttributeError.
app/api/deps.py/app/api/routers/demo.py:CurrentUser.roleis validated by Pydantic at runtime already, butget_current_usernow explicitly checks role membership before construction, turning a would-be unhandledValidationErrorinto a clean401for a corrupted/tampered session cookie.- Two instances of reusing one variable name for both a
Vehicleand aCustomeracross branches (dashboard.py,data_quality.py) — renamed for clarity, not just to satisfy mypy. Booking.__table__.update()/Customer.__table__.update()switched to the idiomaticsqlalchemy.update(Model)construct (also fixes the type error).- Remainder: deprecated
conint()→Annotated[int, Field(...)], aSequencevslist.sort()call, anassert-guarded None-narrow after aWHERE ... IS NOT NULLfilter mypy can't see through, and a couple of narrowly-scoped# type: ignore[...]comments for known SQLAlchemy stub gaps (Result.rowcount).
make lint now runs ruff check . and mypy app.
Application URLs and ports
| Service | URL | Notes |
|---|---|---|
| Web (React SPA) | http://localhost:1228 |
nginx-served static build |
| API | http://localhost:8128 |
FastAPI, /health for liveness |
| API docs | http://localhost:8128/docs |
auto-generated OpenAPI/Swagger UI |
| n8n | http://localhost:5678 |
requires one-time owner setup, see below |
| PostgreSQL | localhost:5432 (container-internal only, no host port published) |
Demo users and access method
No passwords. Two demo-role buttons on http://localhost:1228/login:
- Open as Operations Manager →
USR-OPS, "Amelie De Ridder". Full access: dashboard, data-quality resolution/merge, automation retry, demo reset, MCP/service-token routes are separate (not user-facing). - Open as Rental Employee →
USR-EMP, "Karim Boujaddaine". Can register returns and browse vehicles/bookings/knowledge; Automation page is visible but shows a role-restricted message instead of the delivery table (enforced both in the UI and by the backend'srequire_operations_managerdependency — verified bytest_retry_requires_operations_managerand the e2e role-restriction test).
Session is an HMAC-signed, HttpOnly cookie (app/core/security.py) — a demo mechanism,
not a real identity provider (documented as a known limitation).
Implemented functionality
- Operations dashboard with 100% database-backed metrics, attention items linking to the underlying data-quality issue, "today" departures/returns, and recent automation runs.
- Vehicle and booking list/detail pages with working filters (status, attention-only) and a tabbed vehicle detail view (overview/bookings/inspections/maintenance/quality).
- Full transactional vehicle-return workflow: row-locked, idempotent by
Idempotency-Key, canonical-odometer regression handling (never silently lowers the canonical value), vehicle status derivation, two audit events, and a schema-compliant outbox event — verified against real concurrent submissions (1×201 + 2×409). - Invalid-mileage rejection: negative values and non-numeric input both correctly
rejected with
422and a precise Pydantic validation message. - Data Quality Workbench: five deterministic rules (duplicate customer via TF-IDF-style weighted signal scoring, missing required field, odometer regression, booking overlap, vehicle status conflict), issue list/detail/defer/reject, and a two-column duplicate-customer compare-and-merge UI with an inline (non-native-dialog) confirmation step, transactional booking rewiring, and audit logging.
- Full audit trail: every significant action (login, return, vehicle status change, data-quality issue lifecycle, customer merge, workflow retry, demo reset, n8n callback, MCP tool request, knowledge question) is recorded with actor, correlation ID, and before/after state; filterable by action.
- Knowledge Assistant: deterministic TF-IDF-weighted extractive retrieval over the 10
procedure documents — never generative, always cites real excerpts, and honestly
reports
insufficient/unavailablestates rather than fabricating an answer. - n8n automation: background outbox dispatcher (
FOR UPDATE SKIP LOCKEDclaim, exponential backoff, no DB transaction held during the HTTP call), a live-verified round trip through an actual n8n workflow, manual retry for failed deliveries, and an Automation page (Operations Manager only) showing all runs with filtering. - Four read-only, service-token-authenticated MCP Hub provider endpoints, each recording its own service-request audit event, with zero write/mutation endpoints anywhere in that namespace.
- Responsive UI verified down to 360px width (nav wraps, tables become cards, metric tiles reflow to a 2-column grid, no horizontal overflow) — both by an automated Playwright viewport/overflow assertion and by a captured screenshot.
RAGcore integration status: implemented, not live-verified
The active KnowledgeProvider in this environment is DemoKnowledgeProvider — fully
implemented, fully tested, fully live-verified, and what actually satisfies the
knowledge-assistant acceptance criteria. A RAGcoreKnowledgeProvider HTTP adapter also
exists (app/services/knowledge/ragcore.py), targeting a best-effort contract inferred
from contracts/ragcore-contract-assumptions.md (no live RAGcore API spec was available).
Its unavailable-degradation path is live-verified this audit: pointed at an
unreachable host, it returns {"evidence_state": "unavailable", "answer": "", "sources": []} with no fabrication, exactly as required — but an actual successful round trip
against a real RAGcore instance has never been performed, because no such instance was
reachable in this environment.
MCP Hub integration status: implemented, not live-verified
All four contracted read-only tools (mobilityops_get_operations_summary,
mobilityops_list_attention_vehicles, mobilityops_get_vehicle_details,
mobilityops_search_knowledge) are implemented as service-token-protected endpoints under
/api/v1/integrations/mcp/, directly curl-verified this audit (auth enforcement,
correct data shape, no write methods, service-request audit logging). No live ITWorx MCP
Hub instance was reachable in this environment, so an actual Hub-mediated tool call was
never performed — only direct calls to MobilityOps's own provider API.
n8n integration status: implemented and fully live-verified
The only external integration with a real, running counterpart service available in this environment. Fully verified this audit, including both success and degraded paths:
- Success: a real return registered via the API was delivered by the background
dispatcher to an actual n8n instance (owner account + imported/activated workflow),
which called back into MobilityOps and was recorded
succeeded. - Degraded mode:
docker compose stop n8n, then a return was registered — it committed successfully (201, bookingstatus: returnedpersisted) exactly as required by the architecture's reliability boundary ("a return command and its outbox event commit in one transaction" and "n8n failure leaves events pending with bounded retries"). The outbox event stayedpendingwith two realConnectErrors logged and exponential backoff. - Self-healing: restarting n8n required no manual intervention — the background
dispatcher picked the pending event back up on its next poll cycle and delivered it to
succeeded(5 total attempts across the outage). - Manual retry (S5 scenario): a seeded
faileddelivery, retried from the Automation page, moved topendingand was delivered tosucceededby the live dispatcher within one poll cycle.
Known limitations
- RAGcore and the ITWorx MCP Hub were never reachable in this build/audit environment; both integrations are implemented and tested against inferred/documented contracts but not verified against real instances of those systems (see above).
- n8n requires a one-time, per-fresh-environment manual owner-account setup through its
own web UI (
http://localhost:5678/setup) — a property of the n8n 2.x image itself (N8N_BASIC_AUTH_ACTIVEno longer gates the UI), not something MobilityOps can bypass. The workflow import/activation itself is scripted (make n8n-setup). - Demo authentication is an HMAC-signed session cookie tied to two fixed seeded users — appropriate for a PoC, not a production identity provider.
- Inspection public references are assigned via a simple
count + 1sequence, not gap-safe under true concurrent writers (acceptable for this single-tenant demo). - The five data-quality rules use a simplified idempotency key
(
rule_type, entity_type, entity_idwhile open) rather than the spec's literal evidence-fingerprint scheme — documented rationale inPROJECT_STATE.md's M3 notes. npm auditreports one residual moderateesbuild/Vite-8 dev-server-only advisory (fixable only by a Vite major version bump) and one highreact-routerRSC-mode advisory that does not apply to this app (it never uses React Router's RSC/SSR mode).
Clean deployment instructions
git clone <repo> && cd MobilityOps
cp .env.example .env
make demo # build, start, migrate (automatic), seed
One-time n8n setup (only needed for the automation demo path; everything else works without it):
# open http://localhost:5678/setup in a browser, create any owner account
# (8+ chars, 1 number, 1 capital letter — no email verification required)
make n8n-setup
Verify:
curl http://localhost:8128/health # {"status":"ok",...}
curl -o /dev/null -w "%{http_code}\n" http://localhost:1228/ # 200
make test # 66 backend tests
make lint # ruff + mypy, zero errors
make e2e # 12 Playwright tests (stack must be running)
Full detail, recovery expectations, and required operational checks: docs/17-runbook.md.
Five-minute demonstration flow
- Open
http://localhost:1228→ Open as Operations Manager. - Dashboard: point out the metrics are live counts (available/rented/cleaning/ maintenance/blocked vehicles, open quality issues, pending/failed workflows), and the Attention Required list linking straight to the underlying issues.
- Vehicles → MO-024 → open the active booking
BK-DEMO-RETURN, register a return with an odometer reading below MO-024's canonical value → the result panel shows the inspection, the derived vehicle status, the automatically-created data-quality issue, and the queued automation event — canonical odometer is confirmed unchanged. - Data Quality → DQ-DEMO-DUPLICATE: the two-column CUS-0012/CUS-0178 comparison,
merge with the inline confirmation step, issue flips to
resolved. - Knowledge: ask "What must I do when a vehicle returns with damage?" → grounded answer citing both the return and damage-handling procedures with real excerpts.
- Automation: filter to
failed, retry the seeded delivery, watch it succeed within a few seconds via the live n8n instance. - Audit: filter by
return_registeredorcustomer_mergedto show every action from this walkthrough is recorded with actor, timestamp, and correlation ID. - Resize the browser to 360px width to show the responsive layout (nav wraps, tables
become cards) — or run
make e2eand point at the passing responsive assertion.