chore: remove generated deployment and audit evidence

This commit is contained in:
ChatGPT MCP
2026-08-31 05:49:16 +00:00
parent 51c3c90fa1
commit 7d935c5a7d
17 changed files with 0 additions and 2928 deletions
-195
View File
@@ -1,195 +0,0 @@
# Demo-productization final summary
## Branches and commits
- **Gitea repository**: `ssh://git@192.168.10.150:222/Jens/MobilityOps.git` (browsable at
`http://192.168.10.150:3000/Jens/MobilityOps`)
- **Branch**: `feat/mobilityops-functional-completion` (no new branch created; no merge
to `main`; no rebase/reset/squash/force-push; full git history preserved, as required)
- **Start commit** (functional-completion baseline, already accepted):
`e0c7ed60112510687627d20a957af91c8b9db7f8`
- **Final commit**: `4a268c73515dc4f1d56c1aa2f231714654bffbb8` — verified via
`git rev-parse HEAD` on `feat/mobilityops-functional-completion` and confirmed to match
`/mnt/user/appdata/mobilityops/.deploy/source-revision` on the Unraid server exactly.
(This corrects a self-reference gap in the immediately preceding pair of commits, which
necessarily could not know their own hash at the time they were written; this is now
the single, unambiguous, verified reference. The repository's primary branch is
`master`, not `main` — no branch named `main` exists in this repository.)
- **Live URL**: `http://192.168.10.150:1236`
## Demo organisation and context
**Northstar Mobility** — a fictitious Belgian camper/van rental company (~50 vehicles,
one main location, rental team, an Operations Manager, a small workshop). This name was
already a locked internal decision (`ragcore_tenant: northstar-mobility-demo`,
`PROJECT_STATE.md`'s "Locked decisions") before this work — this pass surfaces it in the
UI rather than inventing it. Full concept: `docs/demo-release/demo-concept.md`.
## Roles
- **Operations Manager** — full access: data-quality resolution, workflow retries, audit
trail, demo reset, the Demo Guide.
- **Rental Employee** — scoped access: bookings, returns, fleet, knowledge assistant.
Both are reachable from the login screen with no password.
## Demo Guide
An 8-step, sessionStorage-persisted guided tour (Operations-Manager-only, since every
step requires that role). Full design: `docs/demo-release/demo-guide.md`. Steps: (1)
understand operational state, (2) open the booking needing attention, (3) process the
odometer-anomaly return, (4) handle the created data-quality issue, (5) merge the
duplicate customer, (6) ask the knowledge assistant, (7) check automation + audit, (8)
review real vs. synthetic vs. not-connected.
## Scenarios (all 5, full detail in `docs/demo-release/demo-scenarios.md`)
| # | Scenario | Fixed records | Role |
|---|---|---|---|
| 1 | Odometer regression on return | `BK-DEMO-RETURN` / `MO-024` | Either |
| 2 | Possible duplicate customer | `CUS-0012` / `CUS-0178` / `DQ-DEMO-DUPLICATE` | OM |
| 3 | Overlapping bookings | `MO-016` / `BK-DEMO-OVERLAP-A/B` / `DQ-DEMO-OVERLAP` | OM |
| 4 | Failed automation, retried | outbox event `...020` / `BK-H-0020` | OM |
| 5 | Grounded procedure question | (no fixed record; suggested questions) | Either |
`GET /api/v1/demo/manifest`'s `scenarios` array derives `ready`/`blocked_reason` from the
live underlying records, never hardcoded — confirmed via `backend/tests/
test_demo_manifest.py` (`test_demo_manifest_scenarios_ready_after_fresh_reset`) and
live-checked after every reset throughout this work.
## Seed strategy and date-anchoring
`seed/generate_seed.py --anchor 2026-08-01 --seed 20260801` produces deterministic CSVs
with absolute timestamps authored against a fixed anchor. `backend/app/seed_loader.py`
shifts every seeded datetime by `(real today authored anchor)` on every seed/reset, so
"today"/"near-future"/"currently overlapping" scenarios stay true to the actual reset
moment instead of decaying. This fixed a real, confirmed bug (`BK-DEMO-RETURN` was found
sitting 2 days in the past before this fix). Full detail: `docs/demo-release/demo-data.md`.
## Reset strategy
`POST /api/v1/demo/reset` (Operations Manager only, gated by `DEMO_ALLOW_RESET`) clears
MobilityOps's own tables, reseeds with a fresh date anchor, re-runs the data-quality scan,
and runs a server-side scenario-integrity check (`scenario_integrity_report()`) recorded
in both the response and the `demo_reset` audit event. Reachable from the sidebar, the
Demo Guide, and the About page. Never touches shared n8n/RAGcore/MCP data, other
containers, or volumes.
## Real vs. synthetic vs. not-connected
See `docs/demo-release/demo-concept.md` for the full breakdown. In short: auth/roles,
vehicle/booking management, return preview/commit, the 5 data-quality rules and their
resolutions, the audit trail, n8n orchestration, Docker deployment, and the automated
test suite are all really implemented. The organisation, all people, vehicles, bookings,
procedures, and the 5 named scenarios are synthetic. RAGcore and the ITWorx MCP Hub are
not live-connected (honestly labelled "Demomodus"/"Niet gekoppeld" everywhere, never a
fabricated success).
## Test results
### Backend (clean checkout, isolated stack)
- `pytest`: **127 passed**
- `ruff check .`: clean
- `mypy app`: clean (48 source files)
### Frontend (clean checkout, isolated stack)
- `npm ci`: clean (pre-existing esbuild-moderate/react-router-RSC-high advisories,
unchanged from before this work — not introduced by it)
- `tsc -b`: clean
- `npm run build`: clean
- Full Playwright suite: **56 passed** (against the isolated clean-checkout stack)
### Guided-demo test
`frontend/e2e/guided-demo-full.spec.ts` — one comprehensive test walking a fresh
Operations Manager session through all 8 Demo Guide steps performing the real action at
each step (processes the actual odometer-anomaly return, resolves the resulting
data-quality issue, merges the duplicate customer, asks a suggested knowledge question,
checks automation + audit, reviews the About page), then resets the demo data again to
restore the environment. **Passed**, confirmed stable across repeated runs both locally
and against the live Unraid deployment.
### Clean-checkout drill
Fresh `git clone` of this branch/commit into an isolated scratch directory, `.env` from
`.env.example`, isolated Compose project name (`mobilityops-cleandrill`) and remapped
host ports (`compose.override.yaml` with `!override` merge tags — no shared state with
any other stack), `docker compose up --build -d` from empty volumes → migrations ran
automatically (`e7b08389f47f (head)`) → seeded → full backend gate (127 passed, ruff/
mypy clean) → `npm ci`/`tsc -b`/`vite build` clean → full Playwright suite (56 passed)
→ reseeded and confirmed all 5 scenarios `ready: true` via the manifest → torn down
(`docker compose down -v` on the isolated project only; the working dev stack was never
touched).
### Server deployment
Deployed incrementally after every batch (10 deploy cycles across this work); final
state: both `api` and `web` rebuilt and healthy at the final commit, `db` untouched
across all of them (no destructive migrations on this branch). Migrations at
`e7b08389f47f (head)` throughout. `.deploy/source-revision` on the server matches the
final commit exactly.
### Container health
`docker compose ps` on the server: `api`, `db`, `web` all `healthy`, no restart loops.
### Browser console / network
No unexpected console errors on login, dashboard, scenarios, About, or with the Demo
Guide open (verified via `demo-accessibility.spec.ts`; the one benign 401 from the app's
own session-probe on first load is expected and explicitly accounted for, not silenced
blindly). No unresolved server errors in `docker logs` for `api`/`web` at the time of
this evidence capture.
### Responsive / accessibility
- Demo Guide renders as a correctly-anchored bottom sheet at 390px with no horizontal
overflow (`demo-accessibility.spec.ts`).
- **Real bug found and fixed**: the Demo Guide's fixed desktop side panel overlapped
main content with no reflow, making the return form's "Review return" button
unclickable while the guide was open at ordinary desktop widths — this surfaced while
writing the full guided-demo test. Fixed via a `guide-open` layout class that reserves
space for the panel; regression-tested.
- Demo badge and Demo Guide triggers are keyboard-focusable and operable (Enter to open,
explicit close controls).
- Existing responsive-overflow checks (390/768/1280/1440px) remain green throughout.
## Known limitations
- RAGcore and the ITWorx MCP Hub are not live-connected in this environment (by design
— see scope). The knowledge assistant uses a local, English-only demo knowledge base;
a Dutch question against it returns "insufficient evidence" (verified empirically), so
suggested questions and the Demo Guide's step 6 instructions deliberately stay in
English rather than silently breaking the demo's centerpiece grounded-answer feature.
- Existing operational screens (Dashboard, Vehicles, Bookings, Data Quality workbench,
Audit, Automation internals) remain in English; only new demo-productization surfaces
(login, Demo Guide, scenario overview, About page, demo badge, plain-language
integration labels) are in Dutch — a deliberate, documented scope decision, not an
oversight (`docs/demo-release/current-demo-gap-audit.md`, gap #11).
- Scenario S3 ("missing inspection before next booking", `MO-031`) is seeded and visible
in the attention queue but isn't one of the 5 scenarios surfaced on `/scenarios`,
matching the brief's request for exactly 5.
## 5-minute and 10-minute demo flows
See `docs/demo-release/demo-runbook.md` for the exact click-through scripts.
## Redeploy commands and rollback procedure
See `docs/demo-release/demo-runbook.md``git archive``scp` → extract → rebuild
`api`/`web` → confirm migrations → reseed. Rollback: extract an earlier
`.deploy/source-<short-sha>.tar.gz` and update `.deploy/source-revision` to match.
## Evidence screenshots
All captured live against `http://192.168.10.150:1236` (`artifacts/demo-release/screenshots/`):
1. `01-demo-entry-desktop.png` / `02-demo-entry-mobile.png` — demo entry, both sizes
2. `03-dashboard-with-scenarios.png` — dashboard with the scenario teaser panel
3. `04-demo-guide.png` — the Demo Guide panel open
4. `05-return-preview.png` / `06-return-result.png` — the return flow
5. `07-data-quality-resolution.png` — a data-quality issue with its plain-language explainer
6. `08-duplicate-customer-merge.png` — the duplicate-customer comparison/merge UI
7. `09-knowledge-assistant.png` — a grounded answer with cited sources
8. `10-integration-status.png` — plain-language integration status on Automation
9. `11-automation-retry-before.png` / `11-automation-retry-after.png` — a workflow retry
10. `12-audit-trail.png` / `13-audit-related-events.png` — audit trail + correlation drill-down
11. `14-about-demo.png` — the About page
12. `15-demo-badge-popover.png` — the permanent synthetic-demo badge popover
13. `16-reset-confirm.png` — the reset confirmation flow
No secrets appear in any screenshot or in this document.
-145
View File
@@ -1,145 +0,0 @@
# MobilityOps Unraid deployment evidence
## Outcome
- Deployment: **PASS**
- Gitea publication: **PASS**
- Gitea URL: `https://gitea.itworx.tech/Jens/MobilityOps`
- Visibility: private (verified in the Gitea web UI)
- Branch: `master`
- Verified baseline commit: `4bf9afbeff44088864e0844769d4dd0e4089d85b`
- Deployment implementation commit: `1e13943cffb2da8a328b5b1ea5e9b1fe73fdd774`
- Server: `192.168.10.150`
- Server directory: `/mnt/user/appdata/mobilityops`
- Compose project: `mobilityops`
- Application URL: `http://192.168.10.150:1236`
- Port mapping: LAN `0.0.0.0:1236` / `[::]:1236` to `web:80`
## Services and health
| Service | Runtime state | Health | Host exposure |
|---|---|---|---|
| `db` | running, 0 restarts | healthy | none (`5432/tcp` internal) |
| `api` | running, 0 restarts | healthy | none (`8000/tcp` internal) |
| `web` | running, 0 restarts | healthy | `1236:80` on LAN |
| shared host `n8n` | running | healthy | `5678:5678` on LAN; outside MobilityOps Compose |
The final review topology reuses the n8n container that was already running on the host.
Its empty public-host/editor URL settings were corrected in the persistent Unraid template
so workflow execution URLs are valid. The temporary Compose-owned n8n container was
removed without deleting its retained volume. Port `1236` was confirmed unused before the
original deployment; the application directory was created specifically for MobilityOps.
## Deployment commands
The existing SSH aliases resolve to the requested hosts and keys (`gitea.itworx.tech`
for Gitea SSH and `unraid` for root access). No key was created, copied, or replaced.
The committed source was transferred from the workstation; Unraid has no Gitea key.
Repository publication used the SSH clone URL supplied by Gitea:
```bash
git remote add origin ssh://git@192.168.10.150:222/Jens/MobilityOps.git
git push -u origin master
git push origin --tags
```
Git and the Gitea web UI both verified `master` as the default branch, the full commit
history, baseline commit `4bf9afbeff44088864e0844769d4dd0e4089d85b`, and zero tags.
The remote tree contains no `.env`, local database, `node_modules`, virtual environment,
test cache, build cache, Playwright output, or browser binaries.
```bash
git archive --format=tar.gz --output=<temporary-archive> <commit>
scp <temporary-archive> unraid:/mnt/user/appdata/mobilityops/.deploy/source.tar.gz
ssh unraid
cd /mnt/user/appdata/mobilityops
tar -xzf .deploy/source.tar.gz
./deploy/unraid/configure-env.sh http://192.168.10.150:1236
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d db api web
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api \
python -m app.cli seed --reset
./deploy/unraid/setup-existing-n8n.sh \
n8n \
http://192.168.10.150:1236/api/v1/integrations/n8n/return-callback
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up -d db api web
```
The server `.env` was created from `.env.example`, is mode `0600`, and contains generated
runtime secrets. Secret values and n8n owner credentials remain server-only and are not
included here or in Git.
## Validation evidence
- Migration: `e7b08389f47f (head)`.
- Deterministic seed: users 2, customers 180, vehicles 50, bookings 246, inspections 75,
maintenance 40, data-quality issues 26, workflow runs 20.
- HTTP: `GET /` returned 200; `GET /health` returned
`{"status":"ok","service":"mobilityops-api"}` through the web proxy.
- Backend gates in an isolated local Compose project: 66 tests passed, Ruff clean, mypy
clean across 44 files.
- Frontend: `npm ci && npm run build` completed (`tsc -b && vite build`).
- Logs: no traceback, fatal, uncaught, or unresolved startup error in the deployment log
scan. Browser console had no warnings or errors during the smoke test.
- Browser smoke test in Chrome: Operations Manager demo login, Dashboard, Vehicles,
Bookings, Data Quality, Knowledge, Automation, and Audit all loaded from the LAN URL.
- Dashboard showed persisted seed metrics (21 available, 11 rented, 6 cleaning,
5 maintenance, 7 blocked, 22 open issues, 1 pending/failed workflow).
- Return workflow: `BK-DEMO-RETURN` accepted 54,700 km, created `INSP-0076` and
`DQ-RET-0076`, preserved the 54,820 km canonical odometer, and changed the booking to
returned.
- Shared-n8n round trip: final post-deploy event `98eb06dc-0bcc-4e3d-96ec-c23b2d266293`
reached `succeeded` on attempt 1 with no last error; the deterministic reset afterwards
restored `BK-DEMO-RETURN` to `active`.
- Data quality: `DQ-RET-0076` displayed the persisted regression evidence and related
booking/inspection references.
- Knowledge: UI truthfully showed `Provider: demo · available · 10 procedures indexed`;
the damage question returned grounded excerpts and citations from the local procedures.
## Integration status
- RAGcore: disabled for this deployment; `KNOWLEDGE_PROVIDER=demo`. No claim of a live
RAGcore connection is shown. Operational functionality is unaffected.
- ITWorx MCP Hub: registration disabled with `MCP_HUB_REGISTRATION_ENABLED=false`; the
independently authenticated provider endpoints remain available internally to the web
proxy/API boundary, but no live Hub connection is claimed.
- n8n: the existing server instance at `http://192.168.10.150:5678` is healthy; the
MobilityOps workflow is imported/published there and a real return delivery succeeded.
The bundled MobilityOps service is disabled by default in the Unraid overlay.
## Known limitations
- RAGcore and ITWorx MCP Hub are intentionally not connected yet.
- Demo authentication remains the accepted HMAC-cookie PoC mechanism.
- The dependency advisories already documented in final acceptance remain unchanged.
## Redeploy
From the workstation, create an archive of the desired committed revision and transfer it
to `.deploy/source.tar.gz`. On Unraid, preserve `.env` and the named volumes, then run:
```bash
cd /mnt/user/appdata/mobilityops
tar -xzf .deploy/source.tar.gz
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d db api web
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api alembic current
curl -fsS http://127.0.0.1:1236/health
```
## Logs
```bash
cd /mnt/user/appdata/mobilityops
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml ps
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml logs --tail=200
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml logs -f api web
docker logs -f n8n
```
## Safe rollback
Choose a known-good commit on the workstation, archive and transfer it as above, then on
Unraid extract it over the identifiable MobilityOps source directory and run the same
`up --build -d` command. Preserve `.env` and both named volumes; do not use `down -v`,
remove volumes, prune Docker, or modify unrelated containers. Check the target commit's
Alembic compatibility before rolling application code behind the current database schema.
@@ -1,89 +0,0 @@
# MobilityOps premium UI evidence summary
Date: 2026-08-02
Branch: `design/mobilityops-premium-ui`
Baseline revision: `dfabb41582e302f45a3de826f85f531bf23dfc8b`
Final design implementation commit: `1f292e14bb6a2e8ded5dc675b1b3360307d8a9ae`
Review URL: `http://192.168.10.150:1236`
## Outcome
The working PoC was transformed into the Control Rail operational interface without
changing backend contracts or adding scope. All existing journeys remain functional;
return registration gained an evidence-based review boundary before commit.
## Evidence index
- Baseline audit: `docs/design/current-ux-audit.md`
- Three directions and decision: `docs/design/design-directions.md`
- Design tokens and component rules: `docs/design/design-system.md`
- Stitch resource IDs: `docs/design/stitch-manifest.md`
- Implemented visual validation: `docs/design/implementation-validation.md`
- Baseline captures: `artifacts/design-validation/current/`
- Stitch captures: `artifacts/design-validation/stitch/`
- Final responsive captures: `artifacts/design-validation/implementation/`
## Major implementation changes
- Responsive Control Rail shell with compact top bar, desktop rail, off-canvas menu and
labelled mobile bottom navigation.
- Live readiness band, filterable Attention queue, movement timeline, honest integration
pulse and persisted activity on the operations dashboard.
- Searchable fleet and booking registries; booking client pagination limits the DOM to 25
operational rows; responsive tables retain field labels.
- Capture → review → result return workflow with calculated consequence preview and no
write request before confirmation.
- Match/conflict duplicate comparison, evidence-first knowledge, system-health cards and
expandable audit metadata.
- Inline SVG product mark, Feather-like line icon set, CSS control-centre illustration,
timeline/status motion and reduced-motion fallback; no image or motion dependency.
## Validation
| Gate | Result |
|---|---|
| Backend tests | 66 passed |
| Backend lint | ruff passed |
| Backend types | mypy: 0 issues in 44 files |
| Frontend types/build | passed; 59 modules; 240.24 kB JS and 36.63 kB CSS before gzip |
| Browser journeys | 19 passed locally |
| Horizontal overflow | none at 390/768/1280/1440 px |
| Accessibility | named landmarks, skip link, visible focus, text-plus-shape status, labelled mobile rows, reduced-motion support |
| Deployed browser smoke | passed on all 10 authenticated routes plus login at desktop and mobile sizes |
| Console/network | 0 browser warnings/errors; 7 authenticated API paths returned HTTP 200 |
| Deployed global search | Ctrl+K plus `MO-024` navigation passed against the review URL |
All displayed operational counts remain derived from the existing persisted API data.
Synthetic-data labelling is persistent on login and authenticated surfaces.
Deployed evidence is stored in `artifacts/design-validation/implementation/deployed/`.
The review stack reports healthy PostgreSQL/API state, HTTP 200 from the web application,
and healthy state from the server's existing n8n at port 5678. A synthetic return reached
`succeeded` on attempt 1 through that shared n8n and its MobilityOps callback; the demo was
then reset to its deterministic state.
## Performance observations
No runtime font, image or animation dependency was added. The application uses inline SVG
and CSS visuals, and the production bundle remains appropriate for this internal PoC.
## Known limitations
- Global search resolves Control Rail sections and `MO-*`, `BK-*`, `DQ-*` public
references. It intentionally does not offer customer lookup because the locked PoC has
no customer detail route or cross-entity search API.
- The repository retains a bundled n8n service for standalone local clean-checkout demos.
The Unraid overlay keeps it behind the opt-in `bundled-n8n` profile; the live review
deployment uses the server's existing shared n8n instead.
- The MCP Hub state is correctly shown as not configured in the current PoC rather than
simulated as healthy.
- Live RAGcore and MCP Hub round trips remain subject to the existing environment limits
documented in `PROJECT_STATE.md`; their degradation behavior is unchanged.
## Rollback
The accepted baseline remains reachable at commit
`dfabb41582e302f45a3de826f85f531bf23dfc8b`. To roll back the review deployment without
rewriting git history, archive that revision, extract it over the application source on
Unraid while preserving `.env` and Docker volumes, and run
`docker compose -p mobilityops up -d --build`. Verify `/health` and port 1236 afterwards.
-129
View File
@@ -1,129 +0,0 @@
# Fleet Ops — final acceptance evidence
## Accepted implementation
- Deployment source marker: current `master`; the application image was built from runtime commit `cb7edb0` and the following commits contain acceptance/evidence only.
- Live demo: `http://192.168.10.150:1236` — public passwordless synthetic demo.
- Deployment: Unraid `/mnt/user/appdata/mobilityops`, Compose project `mobilityops`.
- Database migration: `b913a72e8c14 (head)`.
- Product name: Fleet Ops; MobilityOps remains the technical repository/deployment ID.
## Clean acceptance — 2026-08-10
```text
Backend, isolated PostgreSQL: 241 passed, zero warnings
Playwright, fresh local build: 152 passed (5.4 min)
Playwright, live production: 152 passed (4.7 min)
Ruff: clean
Mypy: clean across 58 source files
Frontend lint/build: passed
npm audit, full and prod: 0 vulnerabilities
Alembic: b913a72e8c14 (head)
```
The browser suite covers both roles, protected routes, booking/return/maintenance/user/privacy workflows, five data-quality resolution types, correlated audit, retryable orchestration, grounded knowledge in three languages, the complete guided demo, the recruiter highlights, keyboard behaviour and responsive layouts from 360 to 1440 px.
The provider matrix explicitly covers both valid title contracts: the deterministic provider uses Markdown frontmatter titles, while production RAGcore returns curated localized presentation titles. Both must include the language-specific source fragment. The focused live matrix passed 3/3, followed by the complete 152/152 green production run.
## Production hand-off state
The final reset completed immediately before the verified Hub call at `2026-08-10T19:16Z` and restored:
```text
users 2 · customers 180 · vehicles 50 · bookings 254 · inspections 75
maintenance 40 · data-quality issues 33 · workflow runs 20
scenario_integrity.all_ready = true (5/5 scenarios)
```
API readiness is `ready`, PostgreSQL is `up`, and API, web, database, scheduled backup, Prometheus and Grafana are running healthy/current. No traceback or critical error appears in post-deployment API/backup logs.
A pre-deployment custom-format backup was created and independently verified by SHA-256 plus `pg_restore --list`:
`/mnt/user/appdata/mobilityops/backups/postgres/mobilityops-20260810T185833Z.dump`
## External integration evidence
- **Existing central n8n:** configured, dispatch enabled and operational. All four expected workflows have recent healthy heartbeats. The reset state contains 19 successful runs, zero pending, zero unexpected failures and one explicitly synthetic failed-retry scenario.
- **RAGcore:** reachable and ready for tenant `northstar-mobility-demo`, workspace `mobilityops`, collection `internal-procedures`. Exact identity lookups independently confirm 11/11 active published documents in each of NL/EN/FR; the latest n8n report separately records 33 synchronized documents and zero failures. The canonical parsed-artifact hash is not misrepresented as a raw-source hash.
- **ITWorx MCP Hub:** registration is enabled, the Hub is reachable and Fleet Ops is **operational**. Three real `mobilityops.operations.summary` verification calls are audited under the Hub's tenant-bound client identity; the final short-lived bearer produced HTTP 200/JSON and HTTP 401 after immediate revocation. All exposed Fleet Ops tools remain read-only.
## Recruiter and visual acceptance
The public entry now offers a direct **90-second Highlights** route. It links three product actions to their engineering proof, while the **Engineering** workspace explains domain ownership, commit-before-orchestration, citation-bound AI, security and explicit scope.
Interactive live inspection confirmed:
- no horizontal overflow on Highlights or Engineering at desktop; automated coverage confirms 360/390 px mobile layouts;
- clear hierarchy, consistent primary actions and readable proof cards;
- compact remaining-attention action (count badge, two-line label, small directional icon) without the former stretched empty panel;
- truthful live n8n/RAGcore/MCP labels after reset, including 11 verified RAG documents and the real Hub client/tool evidence;
- no dead links, placeholder numbers or unexplained raw data-quality references;
- complete synthetic-data disclosure on login and in the persistent shell.
Current evidence images are in `artifacts/evidence/screenshots/`:
- `1-login.png`
- `2-highlights.png`
- `3-engineering-story.png`
- `4-dashboard.png`
- `5-knowledge-evidence.png`
- `6-highlights-mobile.png`
## Deliberate boundary
This is a completed, production-shaped public demo—not claimed customer adoption and not a general ERP. Accounting, payments, public reservations, CRM, inventory, HR, a second RAG stack, a separate MCP server and autonomous write agents remain intentionally excluded. A real personal-data rollout would additionally require the adopting organisation's identity provider, retention approvals, secrets lifecycle, alert ownership and disaster-recovery governance.
## Local M54 upgrade candidate — 2026-08-23 (not released)
This evidence supplements, but does not replace, the production hand-off above. The local
working tree contains an intentionally uncommitted recruiter UX and resilience upgrade;
production, its database and the production screenshots were not changed.
Implemented evidence includes the interactive five-step system flow, hardened booking/return/
data-quality invariants, atomic cross-replica demo reset, exact n8n callback and dispatcher
leases, citation-bound RAGcore answers, deterministic seed regeneration and abortable,
race-safe frontend data loading. Keyboard, forced-colours, reduced-motion and responsive
behaviour remain part of the established Fleet Ops design system.
Exact local validation commands and results:
```text
docker compose -p mobilityops-m54-final -f compose.yaml -f compose.test.yaml run --build --rm api pytest
331 passed in 94.19s
focused DQ-03 edge/concurrency suite
44 passed
combined RAGcore, dispatcher and data-quality suite
121 passed
docker compose -p mobilityops-lint -f compose.yaml -f compose.test.yaml run --build --rm --no-deps api ruff check app tests
All checks passed
docker compose -p mobilityops-lint -f compose.yaml -f compose.test.yaml run --build --rm --no-deps api mypy app
Success: no issues found in 62 source files
cd frontend && npm run lint
passed
cd frontend && npm run build && npm run budget
passed
python scripts/check-source-budgets.py
passed; data-quality service 799/900 lines; main CSS 76342/78000 bytes;
architecture CSS 8057/9000 bytes; data-quality CSS 6525/8000 bytes
cd frontend && npx playwright test
171 passed (6.9m) in one uninterrupted run against the rebuilt release container
global-search shortcut regression, repeated against the release container
10 passed in 10 consecutive repetitions
docker compose -p mobilityops-contracts -f compose.yaml -f compose.test.yaml run --build --rm --no-deps --volume "C:\Projects\MobilityOps:/repo" --workdir /repo/backend api python ../scripts/check-contracts.py
OpenAPI, event, MCP and n8n contracts are synchronized
cd frontend && npm audit --omit=dev
0 vulnerabilities
```
The final local reset restored `2 users / 180 customers / 50 vehicles / 254 bookings / 75
inspections / 40 maintenance records / 33 data-quality issues / 20 workflow runs`, with all
five synthetic scenarios ready. API readiness is `ready`, PostgreSQL is `up`, the rebuilt
API/web stack remains available, and the final 20-minute log scan contains no traceback,
critical, unhandled, panic, fatal or emergency hit. `git diff --check` is clean.
In-app browser inspection covered the login, Dashboard and Engineering surfaces at desktop
and 390×844. It confirmed zero horizontal overflow, no alert or console errors, the intended
one-column mobile flow, readable mobile cards and consistent selected-step feedback. The local
Compose candidate is healthy at `http://localhost:1228`. A release commit/tag, production
backup and deployment remain deferred until explicit authorization; production and its data
were not touched by M54.
@@ -1,161 +0,0 @@
# Fleet Ops final integrations — evidence summary
Session date: 2026-08-05. Branch `feat/fleet-ops-final-integrations`.
## Repository state
| Repo | Start | End | Branch | Notes |
|---|---|---|---|---|
| Fleet Ops (MobilityOps) | `3ebca9e` (from `feat/live-n8n-ragcore-integration`) | `727c19a` (+ e2e test fixes, uncommitted at write time) | `feat/fleet-ops-final-integrations`, pushed to `origin` | 3 commits: `34df66d`, `2ae2044`, `727c19a` |
| RAGcore | `64a908a` | `64a908a` (+1 isolated commit `ce0ad56`) | `main` | Only a backlog handoff entry committed; no code changes (36-file concurrent-session collision — see below) |
| ITWorx MCP Hub | not modified this session | — | `feature/wp240-final-acceptance` | Connector already live in production before this session started; not touched |
## Deployed revisions
- Fleet Ops: `http://192.168.10.150:1236`, redeployed twice this session (after Batches
1-3 and after Batch 4), `docker compose -p mobilityops -f compose.yaml -f
compose.unraid.yaml up --build -d db api web`, `.deploy/source-revision` = `727c19a...`.
- RAGcore: `http://192.168.10.150:1237`, `ragcore-app-1`. No image redeploy — the two live
fixes (filesystem permissions, reranker model pull) were applied directly to the
running container/Ollama instance, not via a code deploy.
- ITWorx MCP Hub: `http://192.168.10.150:1100` (Tower), unchanged, already live before
this session at commit `c4a0f6d` per the Hub's own state.
## GUI polish (Batch 1)
- Dashboard Attention Queue: curated severity mix (grouped "Handle now / Follow up
today / Review later"), replacing pure severity-sort that let `high` crowd out
everything else.
- Today's Movements: seed data curated (`seed/bookings.csv`) so a fresh reset shows ≥2
departures and ≥2 returns; new `test_seed_today_movements_are_a_credible_mix` test.
Live-verified after a real demo reset: 2 returns + 2 departures shown.
- About Demo: restructured into a compact grid with `<details>` progressive disclosure
for architecture/security/testing sections.
- Duplicate Customer Merge: match/conflict counts shown, matching fields hidden by
default (toggle to reveal), compact preview of the merged record before confirmation.
- Repo hygiene: removed a stray empty `backend;C` dir and an untracked 31MB zip export;
`.gitignore` now excludes future archive exports.
- All four live-verified via browser against the deployed instance (see screenshots
taken during the session — not separately saved to disk).
## n8n (Batch 2)
- 4 canonical workflows confirmed live: Vehicle Return Orchestration, Scheduled Data
Quality Scan, RAGcore Procedure Sync, Workflow Error Handler.
- Fixed genuinely invalid JSON in the committed `fleet-ops-vehicle-return.json` (a
missing `},` between two node objects — the file could not be parsed).
- Workflow 3 (RAGcore Procedure Sync): confirmed 6 real nodes built and saved. Found and
fixed two real defects via the safe `n8n import:workflow` CLI path (not the REST API,
which caused a documented wipe incident in an earlier session): three body-parameter
expressions had a stray trailing `}}`, and `settings.errorWorkflow` was unset. Exported
the corrected definition to `n8n/workflows/fleet-ops-ragcore-procedure-sync.json`,
added to `MANIFEST.md` and `check_drift.py`.
- **Not published** — the Schedule Trigger runs daily at midnight; activating it starts
real unattended production runs, deliberately left as a separate go-live decision.
- No no-op/sync/error-handler live-execution smoke test was run this session beyond the
structural CLI-export verification above (workflow remains unpublished).
## RAGcore (Batch 3)
- **Root cause found and fixed, live, user-approved**: the "zero retrieval candidates"
bug was a filesystem permission bug (`/workspace/.state/models/embedding_profiles.json`
was `root:root` mode `600` on the host bind mount, unreadable by the app's actual
runtime uid 10001) — not authorization, not Qdrant, not embeddings, all independently
verified healthy first. Fixed via `chown`/`chmod`; re-verified in-process (5 real hits,
up from 0).
- **Second, deeper gap found, not fixed**: the reranker adapter calls
`{ollama}/api/rerank`, a route this Ollama version (`0.32.5`) does not serve (404).
Pulled a working model (`xitao/bge-reranker-v2-m3:latest`, 1.2GB, approved) — did not
fix it, since the problem is the HTTP route, not the model. `/v1/answers` still returns
`not_answerable`/0 citations for real questions against real matching content.
- User decision: leave `KNOWLEDGE_PROVIDER=demo`; hand the reranker fix off to RAGcore's
own backlog (`docs/ai/BACKLOG.yaml`, task `M8-01`, committed in that repo as `ce0ad56`
— the only commit made in RAGcore this session) rather than editing RAGcore code amid
its own 36-file concurrent-session collision.
- Side effect: minting the live-verification credential rotated the existing "Fleet Ops
Knowledge Assistant (production)" service account's credential (2-active-credential cap
reached). A fresh credential must be issued before actually flipping the provider live.
## MCP Hub (Batch 4)
- Confirmed the Fleet Ops connector is already live in production on the Hub side
(Tower, commit `c4a0f6d`), with a real contract fix already applied there
(`vehicle.get`'s wire parameter normalized to camelCase `vehicleRef`).
- Fixed two concrete gaps in Fleet Ops's own `search-knowledge` endpoint: no `locale`
field existed at all (now `nl-BE`/`en-GB`/`fr-BE`, wired to the knowledge provider's
existing `language` param), and the correlation ID was always freshly minted, ignoring
any inbound `X-Correlation-Id` header. Added `get_correlation_id`, applied to all four
MCP endpoints.
- `MCP_HUB_BASE_URL` was dead config (declared, never read); wired it for a real,
bounded Hub-reachability health check instead of an unneeded self-registration push
(the Hub's own registration is catalog-driven).
- Renamed Fleet Ops's own internal audit tool labels `mobilityops_*``fleet_ops_*`
(mirrored in `contracts/mcp-tools.json`, `mobilityops_*` kept as deprecated aliases).
The live Hub connector's own dotted tool namespace (`mobilityops.operations.summary`
etc.) is a separate, Hub-owned naming layer, deliberately not touched.
- Automation page's MCP card now shows real evidence (last tool/client/count/timestamp)
instead of only the registration-enabled boolean.
## AI Operations Brief (Batch 5)
Real MCP-client-shaped run via the live ITWorx MCP Hub connector's own
`MobilityOpsClient` class against production Fleet Ops. Full runbook and live output in
`docs/final-integrations/ai-operations-brief-runbook.md`. Summary:
- Real operations summary (21 available / 11 rented / 6 cleaning / 5 maintenance /
7 blocked; 23 open quality issues).
- Real most-pressing vehicle identified (`MO-031`, missing operational inspection).
- Real vehicle detail lookup.
- Real grounded knowledge answer (English damage-handling question): 2 real citations,
`evidence_state: grounded`.
- Dutch/French variants of the same question honestly returned `insufficient` (no
fabrication) — root cause: the live Hub connector doesn't yet send the new `locale`
field, a Hub-side follow-up, not silently worked around.
- Correlation IDs verified end-to-end in Fleet Ops's own audit log
(`GET /api/v1/audit?action=mcp_tool_request`), matching the response payloads exactly.
- No write actions performed at any point.
## Testing per batch
- Backend: **176 passed**, `ruff check .` clean, `mypy app` clean (50 source files) —
verified against a freshly rebuilt image after discovering mid-session that
`docker compose run --rm api` (no bind mount on the `api` service) silently tests a
stale image otherwise. One genuinely stale test assertion found and fixed as a result.
- Frontend: `tsc -b && vite build` clean.
- E2e (Playwright, against the live deployed instance,
`MOBILITYOPS_PUBLIC_URL=http://192.168.10.150:1236`): every spec file run this
session passed — `demo.spec.ts`, `interactive-elements.spec.ts` (26),
`responsive-i18n.spec.ts` + `demo-accessibility.spec.ts` + `guided-demo-full.spec.ts`
(28), `i18n-coverage.spec.ts` + `error-messages.spec.ts` + `clickable-rows.spec.ts` +
`demo-guide.spec.ts` + `demo-entry.spec.ts` + `demo-legibility.spec.ts` +
`fleet-ops-correction.spec.ts` + `ui-redesign.spec.ts` + `greeting.spec.ts` +
`greeting-live.spec.ts` (28, after fixing 2 pre-existing fragile locators unrelated to
this session's feature work — a `.data-table` ambiguity now that Automation has two
tables, and a `Technische details` toggle ambiguity for the same reason; plus one
pre-existing untranslated-loanword false positive in `i18n-coverage.spec.ts`).
## Known limitations, stated plainly
- `KNOWLEDGE_PROVIDER` is still `demo`, not `ragcore` — blocked on RAGcore's own
reranker gap (handed off, not fixed this session).
- n8n workflow 3 is built and correct but not published (deliberate, separate decision).
- The live MCP Hub connector doesn't yet send the new `locale` field, so
locale-aware knowledge search only works when called directly against Fleet Ops (as
proven by the backend tests), not yet through the live Hub connector as deployed.
- No public-demo-readiness checklist, About Demo Guide "completed" end-state polish
(section 4E), or dashboard MCP "activity showcase after Demo Complete" gating were
built this session — the MCP evidence display exists on the Automation page
unconditionally rather than gated behind guided-demo completion.
- No security-review pass was run separately this session (existing gates: ruff, mypy,
the repo's own auth/audit test coverage).
## Rollback
- Fleet Ops: prior working revision `0571a40` remains in `.deploy/` as
`source-0571a40.tar.gz` on the Unraid host; redeploy by re-extracting and re-running
the same `docker compose up --build -d` sequence with that archive.
- RAGcore: `chown`/`chmod` change is trivially reversible (`chown 0:0` +
`chmod 600` on the same path) if needed, though there is no reason to revert a
permission fix. Ollama model pull (`xitao/bge-reranker-v2-m3:latest`) can be removed
with `ollama rm` if unwanted; it is inert until RAGcore's own code is changed to use it.
- MCP Hub: not modified this session.
@@ -1,284 +0,0 @@
# Fleet Ops correction and release — final evidence
**Result: PASS**
## Commits
- Source branch / commit (verified pre-correction baseline): `master` @ `18344bc8b7a75a2f868bf15bf498fc030ac6c34c`
- Fix branch: `fix/fleet-ops-i18n-status-flow`
- Final fix-branch commit: `284b3c7` (merged content identical to `2e4fb43`, which carries the evidence-summary localization fix)
- Main-before-merge: `18344bc8b7a75a2f868bf15bf498fc030ac6c34c` (confirmed unchanged via `git fetch` + `git rev-parse origin/master` immediately before merging — no unexpected commits landed on master while this branch was in progress)
- Merge commit: `de0bdea84fea01b4501deb7099107bc753c2e6d7` (`git merge --no-ff fix/fleet-ops-i18n-status-flow -m "merge: complete Fleet Ops localization and status resolution"`, zero conflicts)
- Final main commit: `de0bdea84fea01b4501deb7099107bc753c2e6d7`
- Deployed commit: `de0bdea84fea01b4501deb7099107bc753c2e6d7` (`.deploy/source-revision` on Unraid)
- Gitea main branch: `master` (confirmed via `git fetch origin && git rev-parse origin/master` matching local `master` after push)
- Live URL: `http://192.168.10.150:1236`
Fix-branch commit history: `6deb955`, `e6539d1`, `ac4b163`, `1fdd2b3`, `1e40775`, `a7ac5ed`, `7851e80`, `cda2c32`, `2e4fb43`, `284b3c7`.
## What this correction fixed
1. **Status-recommendation flow redesigned** (sections 8A8F). The old single opaque
"calculate and apply recommended status" action is replaced by a single shared, pure
evaluator (`backend/app/services/vehicle_status.py::evaluate_vehicle_status`,
documented in `docs/fleet-ops-correction/vehicle-status-decision-table.md`) used
identically by the scanner, a non-mutating preview endpoint
(`POST /api/v1/data-quality/issues/{ref}/status-recommendation`), and a
transactional apply endpoint (`POST .../apply-recommended-status`) that locks the
row, recomputes facts, rejects a stale `recommendation_token`, refuses unsafe/manual-
review recommendations, and re-validates post-write before resolving the issue.
- Forbidden shortcuts eliminated: "maintenance + active booking" no longer
auto-recommends "rented" (being in maintenance is itself now a blocking fact);
"maintenance with nothing else wrong" no longer auto-clears to "available" (no
fact proves maintenance is actually finished — release stays a manual decision).
- Frontend: "Review recommendation" → a localized decision panel (current/
recommended status, why, evidence, consequences) → an exact "Change status to
&lt;status&gt;" confirm action → result, or a distinct "Manual review required"
state offering no generic apply button.
2. **MO-016 order independence** (section 9). Order independence does not mean "same
final status regardless of order" — resolving the booking overlap first genuinely
removes the conflict, correctly leaving nothing to apply. What holds either way: the
recommendation always reflects real current facts (never a stale proxy), and nothing
unsafe is ever applied (never "rented"). Proven by a backend test explicitly scoped
to MO-016/DQ-DEMO-STATUS (the original version wasn't — `_first_open()` returned
whichever of ~14 open `vehicle_status_conflict` issues was most recent, not
necessarily MO-016's) and a browser-level Playwright test covering both orders.
3. **"Fleet Ops" is a non-localizable brand constant** (`frontend/src/product.ts`,
backend `PRODUCT_NAME`), wired via `{{productName}}` interpolation everywhere the
brand appeared in locale prose. A permanent test fails the build if any locale file
ever defines the brand name or an `appName` key again.
4. **Dynamic backend prose converted to message codes + params** (sections 5/6/10):
return status reasons, audit field/actor-type labels, automation `last_error` (new
`last_error_code` column, migration `799d8800e241`), search results (sections/
vehicles/bookings/issues), and — found live on Unraid — the data-quality evidence
summary. Raw technical text is demoted to a "Technical details" disclosure
everywhere.
5. **Knowledge-base fixes**: the demo provider's tokenizer silently dropped accented
characters (`[a-z0-9]+` split "véhicule" into "v"+"hicule"), breaking French
retrieval broadly — fixed to include the Latin-1 accented range. Reweighted section
scoring so a body match (real substance) outranks a heading/title match (a shallow
structural hint) — the old weighting misranked the damage procedure behind an
unrelated document for the brief's exact validation question in all 3 languages.
Removed leftover "MobilityOps"/"PoC" mentions from 9 procedure documents.
6. **Search, audit, automation, maintenance/inspections localized** (section 10):
backend returns stable codes + params only; the frontend localizes section labels,
vehicle summaries, booking/issue statuses, audit action/field/actor labels,
automation error explanations, and maintenance/inspection type labels.
7. **i18n test suite strengthened** (section 11): key parity, brand invariant,
translation-quality (cross-locale identical-value detection), a hardcoded-JSX-text
static scan (had to anchor on backreferenced closing-tag names — a naive `>text<`
regex misread TypeScript generics as JSX), and a 3-language route matrix (every main
route, no console errors, correct `html[lang]`, real page headings).
## Live-caught bug (the deployment validation earning its keep)
Live validation on the freshly-deployed fix branch directly caught a real defect: every
data-quality issue's top-of-page evidence summary was unconditionally showing raw,
always-English text (e.g. *"vehicle marked available while reserved bookings
conflict"*) in **all three languages**, because the frontend never finished the
`evidence.signals` localization the backend had already been emitting (the backend code
even had a comment describing the intended design that the frontend didn't implement).
Fixed in commit `2e4fb43`:
- `DataQualityIssueDetail.tsx` now renders `evidence.signals` through the operator's
locale as the primary evidence text.
- The four `DQ-DEMO-*` seed rows that anchor the guided demo's scripted scenarios now
carry real, accurate signals computed at seed time (the duplicate-customer similarity
score is the actual `SequenceMatcher` ratio on the seeded names, not invented).
- Rows with no structured signals fall back to raw text rather than showing a blank
summary; the one known filler placeholder gets its own localized rendering.
- A regression test locks this in: the vehicle-status-conflict evidence summary must
show localized text and must never contain the specific raw English sentence that was
live-visible before the fix, in all 3 languages.
Also found and fixed along the way: a frontend logic bug conflating "no conflict" with
"manual review required" (both carry `safe_to_apply: false`), which showed a false
"manual review required" panel for MO-016 after its booking overlap was resolved
instead of the correct "no change needed" state (fixed in `1fdd2b3`).
## Translation coverage
- All three locale files (`nl-BE`, `en-GB`, `fr-BE`) define exactly the same key set
for every namespace (`i18n-coverage.spec.ts`, structural guarantee).
- No locale file contains an empty string value.
- No locale file defines the brand name or an `appName` key (brand-invariant test).
- Cross-locale translation-quality check: for every string ≥8 characters of real prose,
nl-BE ≠ en-GB, fr-BE ≠ en-GB, fr-BE ≠ nl-BE, with a precise, audited allowlist for
genuine proper nouns/cognates (23 entries, each with a documented reason).
- Hardcoded-JSX-text static scan: zero findings against the current codebase (verified
against both false positives — TypeScript generics — and a deliberately-injected-
then-reverted false negative).
- 3-language route matrix: every main route (dashboard, vehicles, vehicle detail,
bookings, booking detail, data quality, issue detail, automation, knowledge, audit,
scenarios, about) opens cleanly in all 3 languages with no console errors, correct
`html[lang]`, and a real page heading.
- **Remaining visible wrong-language text**: none found. The one gap that existed (the
data-quality evidence summary) was found live and fixed before merge.
## Branding
- Visible product name: **Fleet Ops**, exactly, in all 3 languages, everywhere (login,
topbar, footer "Fleet Ops Demo", document title, About page, Demo Guide, knowledge
base). Verified structurally (brand-invariant test) and live (branding test across
dashboard/vehicles/data-quality/audit/automation/knowledge pages in all 3 languages;
visual screenshots of the login screen in nl-BE and fr-BE).
- Technical identifier retained (by design, per the brief): repository name, local
directory, package/module names, Compose project, deployment directory, database
name, and the `/health` endpoint's `service: "mobilityops-api"` field remain
"mobilityops" — none of these are visible UI text.
- No visible "MobilityOps" or "PoC" anywhere in the UI or the demo knowledge base
(9 procedure documents cleaned up; regression test in `test_knowledge.py` scans every
procedure file for both strings).
## Status-preview / apply / manual-review / MO-016 ordering
- **Preview**: verified non-mutating — the issue's `status` stays `"open"` after
calling the preview endpoint and re-fetching it via a fresh request.
- **Apply**: the confirm button names the exact target status ("Change status to
Blocked" / "Status wijzigen naar Geblokkeerd" / "Changer le statut vers Bloqué");
applying resolves the issue and updates the vehicle atomically.
- **Manual review**: MO-024 (active rental + service-threshold reached, a genuine fact
contradiction) shows "Manual review required" with no generic apply button rendered
at all.
- **Stale token**: simulated by resolving the underlying booking overlap after the
preview was fetched but before applying — the apply call is correctly rejected
(`RECOMMENDATION_STALE`), the UI shows the "situation has changed" message, and the
user must review again before a new apply is possible.
- **MO-016 ordering**: both orders tested. Resolving the overlap first correctly leaves
nothing to apply (vehicle stays "available", genuinely correct). Resolving the status
conflict first safely blocks the vehicle; resolving the now-redundant overlap
afterwards does not disturb it. Neither order ever produces "rented".
## Knowledge (per language)
The brief's exact validation question, in each language, grounds on the damage
procedure as the **primary** (not just top-3) source:
- nl-BE: *"Wat moet ik doen wanneer een voertuig beschadigd terugkomt?"* → damage
procedure, Dutch source, Dutch excerpt.
- en-GB: *"What should I do when a vehicle returns with damage?"* → damage procedure,
English source, English excerpt.
- fr-BE: *"Que dois-je faire lorsqu'un véhicule revient endommagé ?"* → damage
procedure, French source, French excerpt.
This required two real fixes: a tokenizer bug that silently dropped accented
characters (breaking French retrieval broadly) and a scoring-weight rebalance (body
matches now outrank heading/title matches).
## Audit / automation
- Audit: action labels localized (`workflow_retry` → "automatisering opnieuw
geprobeerd" / "automation retried" / "automatisation relancée", etc.), field names
localized (`operational_status` → "Operationele status" / "Operational status" /
"Statut opérationnel"), actor types localized, raw technical codes only inside
"Technical details". Verified live and via a dedicated Playwright test.
- Automation: the seeded synthetic failure shows a localized primary explanation
("De workflowdienst was tijdelijk niet bereikbaar…") with the raw technical message
("Synthetic connection timeout to n8n") only under "Technical details". Verified live
and via a dedicated Playwright test.
## Backend tests / lint / types
- `pytest`: **151 passed**, 0 failed (clean checkout, local dev, and post-merge master
— run four times across this correction, always 151/151).
- `ruff check .`: all checks passed, every run.
- `mypy app` (strict): no issues found in 49 source files, every run.
- Alembic: `alembic upgrade head` from empty database lands on `799d8800e241`
(the new `outbox_events.last_error_code` column); `downgrade -1` / `upgrade head`
round-trip verified.
## Frontend build / Playwright
- `npm ci`, `tsc -b`, `vite build`: clean, every run.
- Full Playwright suite: **116 tests**, run repeatedly against the local dev stack, an
isolated clean-checkout stack, the live fix-branch deployment, and the live
post-merge master deployment — **116/116 passed** on the final master-deployment run
and on the final local run. A handful of transient, sequential-run-only flakes
occurred at various points across ~10 full-suite runs today (different test each
time, e.g. a pre-existing logout-timing race in `AuthContext.logout()` unrelated to
this branch); every single one was confirmed to pass cleanly in isolation.
- Guided demo covered indirectly via `guided-demo-full.spec.ts`,
`demo-guide.spec.ts`, and the route matrix across all 3 languages — no dedicated
"run the guided tour end-to-end in French" script exists beyond what those specs plus
the branding/route-matrix tests already exercise, since the guided tour's steps route
through the same pages already covered per-language.
## Clean-checkout drill
Fresh `git clone --branch fix/fleet-ops-i18n-status-flow` of only committed files into
an isolated Compose project (`cleancheckfleetops`, ports 8129/1229/5679 to avoid
colliding with the working dev stack). From empty volumes: build → up → `alembic
upgrade head``reset_and_seed` (50 vehicles / 180 customers / 246 bookings / 27
data-quality issues / 20 workflow runs) → 151 backend tests + Ruff + mypy green →
frontend build green → full Playwright suite green → final reset →
`scenario_integrity.all_ready: true`. Isolated stack, containers, volumes, and images
torn down afterward; working dev environment confirmed untouched.
## Unraid deployment
Deployed via `git archive``scp` → extract into `/mnt/user/appdata/mobilityops`
(preserving `.env` and persistent volumes) → `.deploy/source-revision` → rebuild
`api`+`web``alembic upgrade head` → reset/reseed. Done twice: once for the fix
branch (caught the evidence-summary bug), once for the final merged master. Both times:
containers healthy, no errors in `api`/`web` container logs, full Playwright suite
green against the live server, `scenario_integrity.all_ready: true` after final reset.
RAGcore and MCP Hub were not activated (the demo `KnowledgeProvider` — deterministic
local retrieval — remains what's live, per the brief's constraint against activating
unvalidated live integrations).
## Responsive / accessibility
- Breakpoint matrix (1440×1000, 1280×800, 1024×768, 768×1024, 430×932, 390×844,
360×800) × 3 languages: no horizontal overflow, localized headings visible
(`responsive-i18n.spec.ts`).
- Status-recommendation panel: keyboard-only activation of "Review recommendation" and
"Change status to X" verified via focus assertions (not just click); reduced-motion
emulated during the flow; status never conveyed by colour alone (the badge always
carries its own localized text); `aria-live="polite"` added so the applied
confirmation is announced to screen readers.
## Known limitations
- A pre-existing, narrow timing race in `AuthContext.logout()` (clears local state and
redirects before awaiting the server-side cookie-clearing POST) occasionally flakes
one specific Playwright test only under heavy sequential load; not introduced by this
branch, not fixed (out of this branch's scope), always passes in isolation.
- The 11 generic `DQ-0xxx` filler seed rows (not tied to a named demo scenario) show a
localized generic placeholder rather than rich structured evidence, since they carry
no real underlying data gap to describe accurately (the CSV's placeholder text
doesn't correspond to an actually-missing field on the referenced vehicles).
- No dedicated "full guided demo in French, screenshot every step" script exists as a
single artifact; coverage is composed from the route matrix, branding, and existing
guided-demo specs, each run across all 3 languages.
## Screenshots
`artifacts/fleet-ops-correction/screenshots/`, all captured live against
`http://192.168.10.150:1236`:
- `login-nl-BE.jpg` — login screen, Dutch (default), "Fleet Ops" brand + "Bedieningscentrum" subtitle.
- `login-fr-BE.jpg` — login screen switched to French, "Fleet Ops" brand + "Centre de contrôle" subtitle, "Organisation de démo : Northstar Mobility (fictive)".
- `dq-demo-status-fr-BE-collapsed.jpg` — DQ-DEMO-STATUS in French: the localized evidence summary ("Ce véhicule a deux réservations qui se chevauchent…") replacing the raw English sentence, in its collapsed pre-review state.
- `dq-demo-status-fr-BE-clean-reload.jpg` — the same page after a clean reload, confirming the fix is stable across navigation.
One capture attempt mid-session showed the brand rendered as "Vlootoperaties" instead
of "Fleet Ops" — investigated immediately via `document.documentElement` inspection and
confirmed to be **Chrome's own built-in page-translate feature** auto-triggering on the
automation browser profile (`class="translated-ltr"`, `lang` rewritten to bare `"nl"`
by Google Translate, not the app), re-triggering specifically on React DOM mutations
from clicking through the panel. Not an application defect: a clean reload immediately
after showed the correct "Fleet Ops" brand and correctly localized French content
again, and none of the 116 Playwright tests (which run in a clean automated browser
context without this extension behaviour) ever observed it.
## Rollback procedure
1. `ssh unraid`, `cd /mnt/user/appdata/mobilityops`.
2. `git archive --format=tar 18344bc -o` (from a local clone) → `scp` → extract, or
restore from the previous `.deploy/source-revision` (`18344bc8b7a75a2f868bf15bf498fc030ac6c34c`).
3. `echo 18344bc8b7a75a2f868bf15bf498fc030ac6c34c > .deploy/source-revision`.
4. `docker compose -f compose.yaml -f compose.unraid.yaml build api web && ... up -d api web`.
5. `alembic downgrade e7b08389f47f` if the `last_error_code` column must also be
rolled back (not required for a same-schema rollback within this correction's own
history, only if reverting past the whole correction).
6. Re-seed and re-verify `scenario_integrity.all_ready: true`.
The fix branch `fix/fleet-ops-i18n-status-flow` was not deleted.
@@ -1,291 +0,0 @@
# Fleet Ops final localization — final summary
Small, targeted correction round on top of the already-merged, functionally-validated
Fleet Ops correction milestone. Scope: remaining NL/FR translation gaps, centralized
API-error localization, a time-dependent Europe/Brussels dashboard greeting, i18n
test hardening, and documentation consistency — explicitly no redesign, no business-logic
changes, no new functionality. Audit and rationale: `docs/fleet-ops-final-localization/audit.md`.
## Commits
| Stage | Commit | Message |
|---|---|---|
| Start commit (branch base = prior `origin/master` head) | `f7805579f7c73bd3085d73a725fa985b4a4892ed` | `docs(release): final Fleet Ops correction evidence and screenshots` |
| Final fix-branch commit | `09173a4740ddb282fe5412c5305284e9776d397c` | `fix: correct fr-BE audit column label Actor -> Auteur` |
| Merge commit | `5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0` | `merge: finalize Fleet Ops localization` |
| Final master commit | `5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0` | (same as merge commit — merge commit is the branch tip) |
| Deployed commit | `5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0` | matches `.deploy/source-revision` on Unraid exactly |
Branch used: `fix/fleet-ops-final-i18n-ux` (the brief named `fix/fleet-ops-final-localization`;
this branch was verified freshly and cleanly branched from `origin/master` with a clean
working tree, so it was used as-is rather than renamed — see the audit doc's naming note).
`origin/master` was re-fetched and confirmed unchanged (`f780557`) immediately before the
merge, per the mandatory pre-merge safety check.
Full commit sequence (oldest to newest):
```
1fbb20b docs: audit remaining Fleet Ops localization gaps
37a362c fix: translate remaining NL/FR interface gaps
94cfb7b test: tighten i18n allowlist, add substring and brand-leak guards
d17af1c feat: centralize API error localization
e427313 feat: add time-dependent Europe/Brussels dashboard greeting
77208b8 fix: prevent topbar overflow from an unbreakable Dutch role-name translation
f0d6411 fix: serve the missing Fleet Ops favicon
9468cc3 docs: update PROJECT_STATE and README for the final localization round
09173a4 fix: correct fr-BE audit column label Actor -> Auteur
5f0eaa5 merge: finalize Fleet Ops localization
```
## Product name and supported languages
- Visible product name: **Fleet Ops**, everywhere, never translated (`frontend/src/product.ts`
constant, interpolated as `{{productName}}`). "MobilityOps" remains the internal repo /
Compose project / deployment-directory identifier only.
- Supported UI languages: **nl-BE** (default), **en-GB**, **fr-BE**.
- No visible "MobilityOps" or the word "PoC" anywhere in the UI (enforced by a dedicated
automated test, see below).
## Corrected translations
- Role names actually translated (not just labelled as translated): `auth.json` /
`demo.json` role keys — **Operationsmanager** / **Verhuurmedewerker** (nl-BE),
**Responsable des opérations** / **Collaborateur de location** (fr-BE).
- `audit.title`**Auditgeschiedenis** / **Piste d'audit**; `columns.actor`**Uitvoerder**
(nl-BE) / **Auteur** (fr-BE, corrected during live browser validation — see Known
limitations).
- `list.statusOpen`**Openstaand**; `ledger.filterRecent`**Recentste**;
`scenarios.startScenario`**Scenario starten** / **Démarrer le scénario**.
- 8 previously-missed mid-sentence "Audit trail" leaks fixed across `demo.json`,
`quality.json`, `returns.json` (nl-BE) — found by the new embedded-substring test, not
the pre-existing whole-string-identity test, which structurally cannot catch this class
of bug.
- No unintended English text remains in nl-BE or fr-BE (see translation-coverage evidence
below).
## Removed allowlist exceptions
Removed 7 now-stale `IDENTICAL_VALUE_ALLOWLIST` entries in `i18n-coverage.spec.ts`:
`audit.title`, `auth.roleOperationsManager`, `auth.roleRentalEmployee`,
`demo.scenarios.startScenario`, `demo.scenarios.roles.operations_manager`,
`demo.scenarios.roles.rental_employee`, `navigation.items.audit` — all now genuinely
translated; their old comments describing them as "deliberately untranslated" were no
longer true. Two new tests added: embedded-English/Dutch-substring leak guard, and a
no-"MobilityOps"/no-"PoC" guard.
## Hardcoded-text result
The pre-existing static JSX scanner (`i18n-coverage.spec.ts`, section 11D) found **zero**
hardcoded user-facing strings outside the approved technical-token allowlist (Fleet Ops,
Northstar Mobility, ITWorx MCP Hub) across `pages/` and `components/`. Result: **PASS**.
## API-error-localization result
New `frontend/src/api/errorMessages.ts` (`describeApiError`) replaces the
`err instanceof ApiError ? err.message : t(fallback)` anti-pattern (which showed raw
English backend text for the common case) at all 13 call sites across 7 files
(`Automation.tsx`, `ReturnForm.tsx`, `DataQuality.tsx`, `DemoGuide.tsx`, `Layout.tsx`,
`DataQualityIssueDetail.tsx` ×7 sites, `Knowledge.tsx`). Resolution order: known `AppError`
code (32 codes) → known HTTP status (401/403/404/409/422/500) → fully generic fallback.
New `ApiErrorNotice` component (`PageChrome.tsx`) always renders a localized title +
explanation + optional next step; raw backend text is demoted to a "Technical
details"/"Détails techniques" disclosure, never the primary message.
Evidence: `frontend/e2e/error-messages.spec.ts` (10 tests, all passing) —
every known code/status has non-empty copy in all 3 locales; a known code never surfaces
raw text as the primary message; unknown-code and unknown-status fallback chains behave
correctly; a drift guard greps the actual backend `AppError("CODE", ...)` call sites and
confirms `KNOWN_CODES` exactly matches (32 codes, zero drift). Live-verified on Unraid: the
seeded failed automation run renders a fully localized French error with a "DÉTAILS
TECHNIQUES" disclosure below it.
## Greeting logic and edge cases
New `frontend/src/i18n/greeting.ts` (`getGreetingPeriod`, clock-injectable, pure) resolves
one of 4 periods against **Europe/Brussels** wall-clock time via
`Intl.DateTimeFormat({ timeZone: "Europe/Brussels", hourCycle: "h23" })` (DST-safe by
construction — no manual UTC-offset math):
| Period | Window | nl-BE | en-GB | fr-BE |
|---|---|---|---|---|
| morning | 05:0011:59 | Goedemorgen | Good morning | Bonjour |
| afternoon | 12:0017:59 | Goedemiddag | Good afternoon | Bonjour |
| evening | 18:0022:59 | Goedenavond | Good evening | Bonsoir |
| night | 23:0004:59 | Welkom terug | Welcome back | Bon retour |
Never "Goedenacht" (a farewell in Dutch, not a welcome). Each period also has its own
accompanying sentence per language (`dashboard.json` `greetingBody`), replacing the old
fixed "Here's the fleet." `useGreetingPeriod.ts` polls every 30s so the greeting rolls
over live while the app stays open, no reload required; initial render uses a synchronous
`useState(() => getGreetingPeriod())` so there is never a flash of the wrong period.
Edge-case evidence:
- `frontend/e2e/greeting.spec.ts` (4 tests): exact boundary checks at 04:59/05:00/11:59/
12:00/17:59/18:00/22:59/23:00 in both CET (winter) and CEST (summer), plus a dedicated
spring-forward/fall-back DST-transition test (2026-03-29 and 2026-10-25).
- `frontend/e2e/greeting-live.spec.ts` (6 tests, real browser via Playwright's `page.clock`):
all 8 boundary times rendered correctly in **all 3 languages** against the actual app;
live period rollover with no `page.reload()` call anywhere in that test; language-switch
behaviour without changing the time period; the "never Goedenacht" guard.
- Live-verified on Unraid at actual current server time (2026-08-04, ~03:2x CEST, i.e. the
night period): dashboard showed "Welkom terug. Hier is het laatste overzicht van je
wagenpark." (nl-BE), "Welcome back. Here's the latest overview of your fleet." (en-GB),
"Bon retour. Voici le dernier aperçu de votre flotte." (fr-BE).
## README / PROJECT_STATE corrections
- `PROJECT_STATE.md`: fixed the stale "Product name: MobilityOps." / "PoC only"
locked-decisions lines (predated the Fleet Ops rebrand); fixed the "Fleet Ops
correction" section header, which still read "IN PROGRESS .../Not yet merged to
master" despite already being merged (`de0bdea` / `f780557`); appended a new dated
entry for this correction round (not a rewrite of prior entries, per the brief's
explicit instruction not to hide earlier history).
- `README.md`: linked `docs/fleet-ops-final-localization/` alongside the existing
correction-round doc link; refreshed the stale Playwright test count (113 → 138 → 139
after the favicon regression test was added).
## Backend tests, Ruff, mypy
Run on the final master commit (`5f0eaa5`), local dev stack, rebuilt from source:
- `pytest`: **151 passed**, 0 failed.
- `ruff check .`: **All checks passed!**
- `mypy app` (the project's canonical invocation, matching all prior milestone gates —
no `[tool.mypy]` strict config exists in `pyproject.toml`): **Success: no issues found
in 49 source files.**
No backend Python was touched this round; these numbers are unchanged from the prior
correction milestone's final gate, confirmed green again on the current tree.
## Frontend build, Playwright
- `npx tsc --noEmit`: clean, 0 errors.
- `npm run build` (`tsc -b && vite build`): clean production build.
- Full Playwright suite (`npx playwright test`), master build, local dev stack:
**139 passed**, 0 failed (confirmed on a clean run after two transient
`0xC0000005` Chromium worker crashes caused by this specific machine running 43+
concurrent Chrome processes at the time — see Known limitations; a targeted 48-test
re-run of every new/changed suite also passed cleanly in between).
## Clean-checkout drill
Isolated Compose project `mobilityops-clean` (ports 8129/1229/5679, no shared volumes/
network with the working dev stack), fresh `git clone --branch
fix/fleet-ops-final-i18n-ux` of only committed files:
1. `docker compose build` + `up -d` from empty volumes — all 4 containers healthy.
2. `alembic upgrade head``799d8800e241 (head)`.
3. `seed --reset` → 2 users / 180 customers / 50 vehicles / 246 bookings / 75 inspections /
40 maintenance / 27 data-quality issues / 20 workflow runs — matches the documented
deterministic count exactly.
4. Backend gates: `pytest` 151 passed, `ruff check .` clean, `mypy app` clean (49 files).
5. Frontend: `npm ci` clean, `tsc --noEmit` clean, `vite build` clean.
6. Full Playwright suite against the isolated stack (`MOBILITYOPS_PUBLIC_URL=http://localhost:1229`):
**139 passed**, 0 failed — this run covers the Dutch/English/French language checks,
greeting boundaries, API error paths, and the guided demo, all in one pass.
7. Final reset + `scenario_integrity`: all 5 scenarios `ready: true`.
8. Isolated stack, containers, volumes and images torn down; original dev environment
confirmed untouched (`mobilityops-*` containers unaffected throughout).
**PASS.**
## Guided demo per language
Verified live on the Unraid deployment (`http://192.168.10.150:1236`) in all 3 languages
via direct browser interaction: login screen role buttons, dashboard (greeting, readiness
band, attention queue, integration pulse, recent activity), audit trail, automation retry
flow with localized error + technical-details disclosure, and demo reset — all rendering
correctly in nl-BE, en-GB and fr-BE. The full guided-demo Playwright spec
(`guided-demo-full.spec.ts`) passed as part of the 139-test suite on both the local dev
stack and the isolated clean-checkout stack.
## Server deployment, container health
Deployed to `http://192.168.10.150:1236` (Compose project `mobilityops`,
`/mnt/user/appdata/mobilityops`), preserving the server's existing `.env`, the Postgres
and n8n named volumes, the exposed port, and the deployment directory — only `api` and
`web` were rebuilt/recreated; `db` was never touched beyond `alembic upgrade head`; no
second n8n instance was started (shared existing n8n at `:5678` used throughout).
Procedure (matching `docs/demo-release/demo-runbook.md` exactly): `git archive``scp`
extract over the existing deployment dir → update `.deploy/source-revision`
`docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web`
→ confirm `alembic current``seed --reset`.
Final container status:
```
mobilityops-api-1 Up (healthy)
mobilityops-db-1 Up (healthy)
mobilityops-web-1 Up (healthy)
```
Deployed twice this round: once for the fix-branch tip (`09173a4`, with full live
3-language validation), once for the final master merge commit (`5f0eaa5`) after the
merge — both deployments passed migrations, reseed, and a live smoke test.
## Repository / runtime hash comparison
```
git rev-parse HEAD (local, master) = 5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0
/mnt/user/appdata/mobilityops/.deploy/source-revision = 5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0
```
**Exact match.**
## Browser console and network
No console errors on any checked route in any of the 3 languages (dashboard, audit,
automation, login) on the live Unraid deployment. All observed `/api/` network requests
returned `200`. `api` and `web` container logs show no errors/tracebacks/exceptions after
the final deployment.
## Known limitations
- **Transient `document.documentElement.lang` DOM-attribute anomaly during interactive
manual browser testing** on the live server: on 2 occasions, right after a client-side
action (an automation retry click; a demo-reset confirm click), `document.documentElement.lang`
briefly showed `"nl"` while the actually-rendered page content, `localStorage`, and a
controlled repeat of the exact same click sequence (fresh login, single deliberate
click, immediate inspection) all remained correctly `"fr-BE"`. Root-caused as far as
possible: the codebase has exactly one `i18n.changeLanguage()` call site
(`LanguageSwitcher.tsx`), which was not invoked in the clean repro, and `t()` /
`i18n.language` are structurally coupled through a single i18next singleton with no
code path capable of producing this split state. Not reproduced even once across 139
automated Playwright tests run 3 times total (local pre-merge, isolated clean-checkout,
local post-merge on master) in a clean, extension-free browser context. Most likely
explanation: a third-party browser extension active in the specific interactive testing
session (which also had ~10 unrelated pre-existing tabs open on the same origin, and
showed independent signs of instability — repeated CDP screenshot timeouts) rewriting
the `lang` attribute based on its own content heuristics, independent of the React app.
Logged here for transparency rather than silently dismissed; does not affect any
automated PASS result above.
- **Two transient Chromium worker crashes** (`0xC0000005` / access violation) during the
master-build Playwright re-run, on a machine that had accumulated 43+ concurrent Chrome
processes from the interactive testing session above. A clean run immediately
afterward (fewer processes) passed all 139 tests; a 48-test targeted re-run of every
new/changed suite also passed cleanly in between. Treated as machine resource
contention, not a code defect — consistent with the prior correction milestone's own
documented experience of "sequential-run-only flakes reproduced from resource
contention of running two full Docker stacks at once," per `PROJECT_STATE.md`.
- One translation gap (fr-BE `audit.columns.actor`: "Acteur" instead of the brief's
specified "Auteur") was missed in the initial pass and only caught during live browser
validation on Unraid; fixed in commit `09173a4` and redeployed before the master merge.
- The Fleet Ops brand mark (`BrandMark` in `Icons.tsx`) was flagged by the user as
potentially due for a visual refresh; per explicit user decision mid-session, this is
out of scope for this correction round and deferred to a separate follow-up task.
- No RAGcore/MCP Hub implementation changes were made or claimed; both remain in the same
demo/not-connected state documented by the prior correction milestone.
## Rollback procedure
`.deploy/source-revision` on the server records exactly which commit is live. To roll
back: `ssh unraid`, extract an earlier `source-<short-sha>.tar.gz` from
`/mnt/user/appdata/mobilityops/.deploy/` (prior tarballs remain in place, including
`source-9468cc3e.tar.gz`, `source-09173a4.tar.gz` from this round and earlier ones from
the prior correction milestone), update `.deploy/source-revision` to match, and re-run
`docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web`
followed by `alembic upgrade head` (migrations are additive only — no destructive
migration exists on this branch, so no database rollback is needed). No secrets were
printed or read at any point in this process (`.env` was preserved byte-for-byte
throughout, verified via unchanged file timestamp after each extraction).
@@ -1,155 +0,0 @@
# Fleet Ops release — final-product-polish evidence
## Result: PASS
## Commits
- Original feature-branch baseline before this task: `257a4cf` (`docs(polish): audit finale demo-afwerking`)
- Feature-branch commits added this task, on `feat/mobilityops-functional-completion`:
- `337f871` — polish: rebrand to Fleet Ops, add trilingual i18n, adaptive demo guide, and UX overhaul
- `845db14` — fix: mobile topbar overflow at 421-440px and add trilingual responsive coverage
- Feature branch final commit: `845db14e172539b1d10e40f6a3249a72122deb41`
- `master` before merge (verified against the previously recorded baseline): `e0c7ed60112510687627d20a957af91c8b9db7f8` — unchanged, no unexpected commits, no conflicts (confirmed via `git merge-tree` dry run before merging)
- Merge commit on `master`: `18a765d62345ea9a6660d04fb868f218cf4d0b6e` (`merge: release Fleet Ops multilingual demo`, `--no-ff`)
- Final `master` commit (pushed and deployed): `18a765d62345ea9a6660d04fb868f218cf4d0b6e`
- Deployed commit on Unraid (`.deploy/source-revision`): `18a765d62345ea9a6660d04fb868f218cf4d0b6e`
- Feature branch was **not** deleted, per instruction.
## URL
- Live review deployment: `http://192.168.10.150:1236`
## Visible branding
- Product name "Fleet Ops" (with a space) visible in: sidebar brand lockup, browser tab title, login screen, footer product line, About page heading ("What Fleet Ops is and isn't" / "Wat Fleet Ops wel en niet is" / "Ce que Fleet Ops est et n'est pas"), demo badge popover, dashboard copy, all 3 languages.
- No visible "MobilityOps" or "PoC"/"proof of concept" wording remains in user-facing copy (verified by full-page inspection of all main routes in all 3 languages plus a targeted source grep for stray hardcoded strings). The repository, Docker image names, and internal git history retain "MobilityOps" (out of scope; not user-visible).
- Retained technical identifiers (unchanged, as instructed): API paths (`/api/v1/...`), Docker Compose project name (`mobilityops`), internal vehicle/customer reference prefixes (`MO-`, `CUS-`), Gitea repository name.
## Supported locales
- `nl-BE` (default for a fresh session, unauthenticated visitor)
- `en-GB`
- `fr-BE`
- Persisted via `localStorage` key `fleetops.language`; survives refresh, logout/login, and demo reset. No flags used — accessible `<select>` language picker (visible name/code) in the topbar (desktop/tablet) and inside the mobile navigation drawer (≤960px, to avoid topbar overflow). `document.documentElement.lang` kept in sync. All dates/numbers rendered via `Intl.DateTimeFormat`/`Intl.NumberFormat` (`Europe/Brussels` timezone).
## Translation coverage
- `frontend/e2e/i18n-coverage.spec.ts`: recursively compares every key path across all 3 locale files for all 14 namespaces (`common, auth, navigation, dashboard, fleet, bookings, returns, quality, knowledge, integrations, audit, demo, errors, accessibility`) and fails the build on any missing key or empty string value. **2/2 passed** in every gate run this task (local, clean-checkout, and live-deployment runs).
- Command: `npx playwright test e2e/i18n-coverage.spec.ts --project=chromium`
## Knowledge-base locales
- `knowledge/procedures/{nl-BE,en-GB,fr-BE}/` — 11 procedure documents per language (same `document_id`s across languages so citations stay stable): vehicle checkout, vehicle return, damage handling, odometer anomalies, cleaning checklist, maintenance escalation, customer documents, privacy, booking conflicts, roles/escalation, and a new **vehicle availability** procedure (added this task to cover the "vehicle-available-again" guided-demo step explicitly).
- `DemoKnowledgeProvider` now retrieves per-language (only searches the UI-selected language's corpus), with localized "no match"/"low confidence" boilerplate text per language; the frontend passes the active UI language on every `/api/v1/knowledge/questions` and `/api/v1/knowledge/status` call.
- Verified live in all 3 languages this task (see Browser evidence below): NL/EN/FR suggested questions each return grounded, correctly-cited, same-language answers.
- Backend unit tests: `test_demo_provider_grounds_damage_question_in_dutch`, `test_demo_provider_grounds_damage_question_in_french`, `test_demo_provider_health_reports_document_count_per_language`, `test_demo_provider_insufficient_evidence_message_is_localized` — all passing.
## Demo Guide — adaptive per breakpoint
- **Extra-wide desktop (≥1440px)**: docked rail (`.demo-guide-panel.is-wide`), fixed 420px minimum width, no drop shadow (reads as part of the layout), never auto-collapses. Verified: `demo-guide.spec.ts` → "wide desktop viewport docks the guide as a rail that never collapses to a chip".
- **Standard desktop/tablet (7011439px)**: floating non-modal panel that auto-collapses to a persistent, closable progress chip ("Demo-gids · stap X van Y") the instant "Ga naar deze stap" is used; chip has its own expand action and a separate close (×) control; reopens on one click; content reflow padding shrinks to 0 while collapsed so nothing is permanently blocked. Verified: 3 dedicated tests in `demo-guide.spec.ts`.
- **Mobile (≤700px)**: bottom sheet with collapsed / half / full states, a drag-handle button that cycles states, no horizontal overflow, primary actions (Volgende/Ga naar deze stap) reachable in the half state. Verified: `demo-guide.spec.ts` → "mobile viewport shows a bottom sheet with collapsed/half/full states and no horizontal overflow", plus `demo-accessibility.spec.ts` → "demo guide is usable as a mobile bottom sheet".
- **Cross-cutting (4D)**: "Ga naar deze stap" scrolls the on-page target into view, moves programmatic focus to it (`tabindex=-1` + `.focus()`), and applies a 2.2s outline pulse (`.demo-guide-highlight`, disabled under `prefers-reduced-motion`); Escape collapses the standard-tier panel first, then closes it on a second press; progress (`currentIndex`/`completed`) persists in `sessionStorage` across navigation and reload. Verified: `demo-guide.spec.ts` → "Escape collapses the standard-tier panel, then closes it" and "going to a step scrolls, focuses and highlights the on-page target".
- Fixed along the way: two dangling `aria-labelledby` references (`SectionHeading` never actually set the referenced `id`) on Dashboard and Data Quality Issue Detail panels.
## Data Quality Workbench improvements
- Replaced plain radio rows with accessible `.choice-card` selectable tiles (title, consequence detail, `:has(input:checked)`/`.is-selected` state, visible focus ring, hover state) across the duplicate-customer survivor choice, odometer-regression decision, and booking-overlap block choice.
- Clear action hierarchy: primary resolve/apply/merge action uses `.button-primary`; defer uses a de-emphasized `.button-tertiary`; reject uses `.button-tertiary-destructive` (muted, turns critical-red only on hover) — no longer visually competing with the recommended resolution.
- Technical evidence (`evidence_json`) collapsed by default behind a localized "Technical details" `<details>` disclosure.
- Contrast/opacity audited: no unintended overlays, disabled-looking text, or weak borders found beyond the (fixed) dangling-aria-labelledby issue.
## Terminology mapping
- Achieved via the i18next namespace architecture itself rather than a separate module: technical codes (rule types, statuses, action codes, integration states) resolve through dedicated JSON keys (`quality:ruleTypes.*`, `quality:list.status*`, `audit:actions.*`, `integrations:statusLabels.*`, `fleet:statuses.*`, `bookings:statuses.*`) with a human label in all 3 languages; raw technical values (correlation IDs, full UUIDs, raw evidence JSON) are confined to "Technical details" disclosures. Example mappings implemented: `possible_duplicate_customer` → "Possible duplicate customer"/"Mogelijke dubbele klant"/"Client peut-être en double"; `demo_login` → "Logged in"/"Ingelogd"/"Connecté"; n8n `degraded` → "Retry available"/"Opnieuw proberen mogelijk"/"Nouvelle tentative possible"; `not_configured`/`disabled` → "Not connected"/"Niet gekoppeld"/"Non connecté".
## Automation / audit improvements
- Automation ledger: succeeded events group and collapse when >3 in view ("Show N succeeded jobs"/"Hide individual jobs"), filter chips (needs-attention/recent/succeeded/all), meaningful short refs (`AUT-RET-####` derived from the aggregate ref, full UUID behind a `<details>`), localized event types and statuses.
- Audit trail: events grouped by `correlation_id` into one card with a human action-label heading (`audit:actions.*`), related-event count and an expandable technical list; readable before/after diff (`ChangeDiff` component: humanized field names, `set to`/`was`/`X → Y` phrasing) instead of raw JSON by default; short reference (`AUD-XXXXXXXX`) with full UUID and correlation ID behind "Technical details".
## Attention Queue / clickable rows
- Full "stretched link" pattern applied to: Attention Queue, Today's movements, Vehicles table, Bookings table, Data Quality table. Entire row is one activation target (pointer cursor, hover state, keyboard-focusable, Enter/Space activates), secondary in-row links (e.g. the vehicle reference inside a booking row) remain independently clickable via `.cell-link { z-index: 2 }` layered above the row overlay.
- Dedicated tests in `frontend/e2e/clickable-rows.spec.ts` (8 tests): click on empty row space, keyboard focus + Enter, mobile-viewport click, secondary-link independence, correct routing for each of the 5 surfaces, pointer-cursor/focus-ring check.
## Test results (all commands re-run against this exact final state)
### Backend (local dev stack, clean-checkout instance, and live Unraid deployment — all three, all green)
```
docker compose exec api pytest -q → 131 passed
docker compose exec api ruff check . → All checks passed!
docker compose exec api mypy app → Success: no issues found in 48 source files
```
### Frontend
```
cd frontend && npm run build → tsc -b && vite build: success
```
### Playwright (92 tests; run against local dev stack, the isolated clean-checkout stack, and the live Unraid deployment — 92/92 passed in all three runs)
```
npx playwright test --project=chromium
```
Suites: `demo-accessibility`, `demo-entry`, `demo-guide` (including the 3 new adaptive-breakpoint tests, chip close-control test, Escape test, scroll/focus/highlight test), `demo-legibility`, `demo`, `guided-demo-full`, `i18n-coverage`, `interactive-elements`, `responsive-i18n` (7 breakpoints × 3 languages = 21 tests), `ui-redesign`, `clickable-rows` (new, 8 tests).
## Clean-checkout drill (evidence)
Performed in an isolated environment (separate Compose project `mobilityops-clean`, separate host ports 8129/1229, no shared volumes or n8n) so the user's existing long-running dev/n8n environment was never touched:
1. `git clone` of the local repository at commit `845db14` (feature branch, pre-merge) into a scratch directory.
2. `cp .env.example .env` (project name and ports overridden for isolation only).
3. `docker compose up --build -d db api web` — migrations ran automatically on API startup.
4. `docker compose exec api python -m app.cli seed --reset` — deterministic seed loaded (users:2, customers:180, vehicles:50, bookings:246, inspections:75, maintenance:40, data_quality_issues:26, workflow_runs:20).
5. `docker compose exec api pytest -q` → 131 passed. `ruff check .` → clean. `mypy app` → clean.
6. `npm ci && npm run build` → clean build.
7. `npx playwright test --project=chromium` (pointed at the isolated stack via `MOBILITYOPS_PUBLIC_URL`) → 92 passed.
8. Live browser verification in English and French (Dutch already covered as the automated-suite default): guided-demo dashboard, knowledge-assistant grounded answers in both languages with correct same-language citations.
9. `POST /api/v1/demo/reset``scenario_integrity: {"all_ready": true, "not_ready": []}`.
10. Isolated stack torn down (`docker compose down -v`) — original dev environment (containers, n8n owner account/workflows) confirmed untouched and healthy throughout.
No PASS was claimed from pre-existing containers at any point — every gate above ran against a stack built from empty volumes.
## Server deployment evidence
- Deployed via the established safe method: `git archive` from the exact commit → `scp` to `.deploy/source-<sha>.tar.gz` on Unraid → extract → update `.deploy/source-revision``docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web` (db never rebuilt; server `.env` and named volumes — Postgres, n8n — preserved throughout).
- Deployed twice this task: once for the feature branch (`845db14`) for pre-merge live validation, once for the merged `master` (`18a765d`) for the final release.
- Post-deploy, both times: migrations confirmed at head (`e7b08389f47f`), reseed run, `pytest`/`ruff`/`mypy` re-run in the container (all green), full 92-test Playwright suite re-run against the live URL (all green), console/network inspected via live browser (no errors, all `/api/*` calls 200), demo reset performed, `scenario_integrity.all_ready: true` confirmed both times.
- Real shared n8n instance (`http://192.168.10.150:5678`) integration confirmed live: the seeded failed-demo automation event correctly shows "Retry available"/"Opnieuw proberen mogelijk" (not the raw `degraded` string) on the Integration pulse card.
## Responsive / accessibility
- No-horizontal-overflow verified across the full 7-breakpoint matrix (1440×1000, 1280×800, 1024×768, 768×1024, 430×932, 390×844, 360×800) in all 3 languages (`responsive-i18n.spec.ts`, 21 tests) plus the original 4-breakpoint English suite (`ui-redesign.spec.ts`).
- Real bug found and fixed during this pass: the new topbar language switcher pushed the 421440px range into horizontal overflow (the existing "compact topbar" breakpoint stopped at 420px). Fixed by widening that breakpoint to 440px; re-verified clean at exactly 430px in all 3 languages.
- Focus-visible outlines, `prefers-reduced-motion` handling (demo-guide highlight pulse, bottom-sheet height transitions), and keyboard reachability verified via `demo-accessibility.spec.ts` and the new adaptive-guide/clickable-row tests.
## Screenshots
`artifacts/fleet-ops-release/screenshots/`:
- `01-login-nl.jpg` — login screen, Dutch default, language selector visible
- `02-dashboard-nl-desktop.jpg` — dashboard, Dutch, Attention Queue + Integration status
- `03-data-quality-choice-cards.jpg` — Data Quality Workbench choice-card redesign (duplicate-customer merge)
- `04-integrations-nl.jpg` — Integrations page, grouped/filterable automation ledger
- `05-audit-trail-nl.jpg` — Audit trail, correlation-grouped human action labels
- `06-dashboard-en-desktop.jpg` — dashboard, English
- `07-dashboard-fr-desktop.jpg` — dashboard, French
- `08-about-fr.jpg` — About page, French, confirming full rebrand + translated content
- `09-mobile-guide-bottom-sheet.png` — mobile bottom sheet, half state (390×844)
- `10-mobile-guide-full.png` — mobile bottom sheet, full state (390×844)
## Known limitations
- Data-quality evidence "summary" strings (the free-text detail line under each Attention Queue/Data Quality row, e.g. "exact email; exact phone; similar name") remain English-only — these are generated deep in the deterministic rule engine as diagnostic strings, not yet converted to message codes. The rule-type label, status, and all surrounding UI are fully localized; only this one diagnostic fragment is not. Documented as a follow-up, not blocking.
- RAGcore and ITWorx MCP Hub remain honestly labelled as not live-connected (unchanged from prior milestones) — the demo knowledge base is the multilingual, fully-verified stand-in.
- Vehicle/customer internal reference prefixes (`MO-`, `CUS-`) were left unchanged; they are generic internal codes, not user-visible "MobilityOps" branding, and changing them was out of scope for this task.
- Automated live-browser evidence for the guided demo was captured in Dutch (via the automated Playwright suite, which defaults to the app's own nl-BE default) and manually spot-checked live in English and French (knowledge assistant, dashboard, About page); a full manual click-through of all 8 guided-demo steps was not repeated live in all 3 languages beyond the automated `guided-demo-full.spec.ts` (Dutch) and the targeted EN/FR checks documented above, given the exhaustive automated coverage already exercising the same code paths per language via `responsive-i18n.spec.ts` and `i18n-coverage.spec.ts`.
## Rollback procedure
- `.deploy/source-revision` on Unraid records the exact deployed commit (`18a765d62345ea9a6660d04fb868f218cf4d0b6e`).
- Prior tarballs remain in `.deploy/` on the server, including `.deploy/source-845db14.tar.gz` (feature branch, pre-merge) and `.deploy/source-4a268c7.tar.gz` (previous release, pre-polish).
- To roll back: extract the desired `source-<short-sha>.tar.gz`, update `.deploy/source-revision` to match, and re-run `docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web`. Database migrations on this branch are additive only; no destructive migration was introduced.
@@ -1,246 +0,0 @@
# MobilityOps functional-completion — final summary
## Outcome: PASS
All achievable functional-completion requirements were audited, implemented, tested
locally (including a genuine clean-checkout drill), committed, pushed, deployed to
Unraid and re-verified against the live server after every batch.
## Revisions
- Starting branch: `design/mobilityops-premium-ui`
- Starting/observed commit: `54dc952915a4874fcdf14781e1c37feb0e253851`
- Completion branch: `feat/mobilityops-functional-completion`
- Final commit: `5b2827eb7e84e40d97c4d025debb2af1246908e0` (this is the parent commit
the deploy below targets; the commit that actually records this string is
necessarily one commit later — `git log -1` on this branch is the authoritative
source of the true HEAD)
- Gitea branch URL: `https://gitea.itworx.tech/Jens/MobilityOps` (SSH remote
`ssh://git@192.168.10.150:222/Jens/MobilityOps.git`), branch
`feat/mobilityops-functional-completion`
- Deployed URL: `http://192.168.10.150:1236`
- Server deployment directory: `/mnt/user/appdata/mobilityops`
- Server Compose project: `mobilityops`
## Exact commands executed (representative — run after every batch)
```bash
# Local backend gate
docker compose exec -T api python -m app.cli seed --reset
docker compose exec -T api pytest -q
docker compose run --rm api ruff check .
docker compose run --rm api mypy app
# Local frontend gate
cd frontend && npx tsc -b --noEmit && npm run build
# Local e2e (against the local dev stack)
npx playwright test
# Deploy the exact committed revision
COMMIT=$(git rev-parse HEAD)
git archive --format=tar.gz --output=/tmp/source.tar.gz "$COMMIT"
scp -P 22 /tmp/source.tar.gz unraid:/mnt/user/appdata/mobilityops/.deploy/source.tar.gz
ssh unraid "cd /mnt/user/appdata/mobilityops && tar -xzf .deploy/source.tar.gz && echo $COMMIT > .deploy/source-revision"
ssh unraid "cd /mnt/user/appdata/mobilityops && docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d db api web"
ssh unraid "cd /mnt/user/appdata/mobilityops && docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api alembic current"
ssh unraid "cd /mnt/user/appdata/mobilityops && docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api python -m app.cli seed --reset"
# e2e against the live server
MOBILITYOPS_PUBLIC_URL=http://192.168.10.150:1236 npx playwright test
```
Clean-checkout drill (once, section 14):
```bash
git clone --branch feat/mobilityops-functional-completion \
<repo> /tmp/mobilityops-clean-checkout
cd /tmp/mobilityops-clean-checkout
cp .env.example .env
docker compose -p mobilityops-clean up --build -d # isolated project name/ports
docker compose -p mobilityops-clean exec -T api alembic current
docker compose -p mobilityops-clean exec -T api python -m app.cli seed --reset
docker compose -p mobilityops-clean exec -T api pytest -q
docker compose -p mobilityops-clean run --rm api ruff check .
docker compose -p mobilityops-clean run --rm api mypy app
cd frontend && npm ci && npx tsc -b --noEmit && npm run build
MOBILITYOPS_PUBLIC_URL=http://localhost:11228 npx playwright test
docker compose -p mobilityops-clean down -v # isolated project only
```
## Test and validation results
| Gate | Local dev stack | Clean-checkout (isolated) | Live Unraid |
|---|---|---|---|
| `pytest` | 117 passed | 117 passed | — (not applicable; no test runner on the review host) |
| `ruff check .` | clean | clean | — |
| `mypy app` | 0 issues / 46 files | 0 issues / 46 files | — |
| `npx tsc -b` | clean | clean | — |
| `npm run build` | clean | clean | — |
| `npx playwright test` | 37 passed | 37 passed | **37 passed** (against `http://192.168.10.150:1236`) |
| `npm audit` | 4 known advisories (unchanged — see Known limitations) | same | — |
Every batch (1 through 5) was deployed and re-verified with the full 37-test Playwright
suite against the live server before moving to the next batch, not only at the end.
## Application URLs and ports
| Service | URL / port | Notes |
|---|---|---|
| Web (Unraid) | `http://192.168.10.150:1236` | only MobilityOps-owned service exposed on the LAN |
| API (Unraid) | Compose-network only | reached through the web nginx `/api/` proxy |
| PostgreSQL (Unraid) | Compose-network only | never exposed |
| Shared n8n (Unraid) | `http://192.168.10.150:5678` | pre-existing host infrastructure, outside the MobilityOps Compose project |
| Web (local dev) | `http://localhost:1228` | |
| API (local dev) | `http://localhost:8128` | |
| n8n (local dev) | `http://localhost:5678` | bundled, `bundled-n8n` profile |
## Demo users and access method
Two fixed seeded identities, selected via the login screen's role buttons (no
password): **Amelie De Ridder** (`USR-OPS`, Operations Manager) and **Karim
Boujaddaine** (`USR-EMP`, Rental Employee). `POST /api/v1/demo/login` issues an
HttpOnly, `SameSite=Lax` signed session cookie; `GET /api/v1/demo/session` (marked
`Cache-Control: no-store`) is what the browser actually trusts on every load, not a
locally cached copy.
## Implemented functionality (this pass, on top of the already-accepted M0M7/design baseline)
- Fixed two confirmed defects: Vehicles and Bookings both computed a filtered/paginated
result but rendered the raw array.
- Server-backed session lifecycle (`GET /demo/session`, `POST /demo/logout`), central
401 handling, no more `sessionStorage`-as-authority.
- A role matrix enforced server-side (403 on every manager-only action for Rental
Employee, not just a hidden button) and mirrored in the nav/route guards.
- Authoritative, non-mutating return preview (`POST /bookings/{ref}/return-preview`)
sharing its evaluation function with commit — fixed a real bug where the frontend's
guessed preview text was wrong (damage → described as "maintenance", actual rule
"blocked"; the no-contradiction case → described as "available", actual rule always
"cleaning" first).
- Audit API/UI now expose `before`/`after` (the columns existed but were never
serialized) plus a resolved safe entity link.
- Typed related-entity snapshots (booking_overlap's related refs are bookings, not
vehicles — previously silently unresolved) and a bounded resolution flow for every
one of the five data-quality rule types, plus an audited manual scan trigger and
documented recurrence linking (`reopened_from`/`previous_decision`).
- Role-aware backend search (`GET /api/v1/search`) replacing a blind client-side regex
guesser, with a real results panel, keyboard navigation and debouncing.
- A safe, confirmed demo-reset UI trigger (the endpoint already existed and was
already gated).
- Truthful aggregate n8n integration status (`GET /api/v1/integrations/status`) from
outbox delivery counts, replacing a single-most-recent-event read; fixed
`MCP_HUB_REGISTRATION_ENABLED` being declared in `.env.example` but never wired into
`Settings`.
- Bounded outbox delivery-lease recovery for a process crash between claim and outcome.
- A second n8n workflow (scheduled quality scan), independent of RAGcore/MCP Hub.
## RAGcore integration status
Unchanged from the prior baseline and honestly reported throughout: the demo
`KnowledgeProvider` (deterministic TF-IDF extractive retrieval over local procedure
documents) 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 — no live RAGcore instance exists to test against.
`KNOWLEDGE_PROVIDER=demo` on the Unraid deployment; no simulated live connection is ever
shown.
## MCP Hub integration status
The four read-only provider endpoints are implemented, tested, and directly
curl-verified with correct service-token auth enforcement and audit logging.
`MCP_HUB_REGISTRATION_ENABLED` — previously declared in `.env.example` but silently
dropped by `extra="ignore"` since it had no `Settings` field — is now actually wired in
and honestly reported (`GET /api/v1/integrations/status`'s `mcp_hub.state`). It is
`false` on the Unraid deployment (`state: "not_configured"`). No live Hub instance was
reachable in this environment to verify an actual Hub round trip.
## n8n integration status
Fully implemented and live-verified. The original return-processing workflow: verified
against both the local bundled instance and the shared Unraid instance, including a real
degraded-mode drill in an earlier session (n8n stopped mid-flow → return still committed
locally, event stayed `pending` with backoff, self-healed once n8n returned) and the
manual-retry path. This pass adds:
- **Truthful status**: `GET /api/v1/integrations/status` derives n8n health from
aggregate outbox counts (pending/delivering/succeeded/failed), not the single most
recent event.
- **Stale-delivery-lease recovery**: a claimed-but-never-resolved `delivering` row (the
process crashing between claim and outcome) is now recoverable; unit-tested including
a simulated crash, and confirmed a still-alive worker's unexpired lease is never
touched.
- **Second workflow**: `mobilityops-scheduled-quality-scan` (hourly + manual-test
trigger, ships `"active": false"`), calling `POST
/api/v1/integrations/n8n/scheduled-scan`. Live-verified two ways: (1) executed
end-to-end via the Manual test trigger against the **local** n8n instance — full
green execution in the n8n editor, confirmed by the resulting
`data_quality_scan_run` audit event (`actor_type=service`); (2) published to the
**shared Unraid n8n** via `deploy/unraid/setup-scheduled-scan.sh` and the resulting
endpoint directly curl-verified against the live deployed API, also confirmed via the
audit trail. The shared instance's own UI could not be browser-tested directly — it
runs `N8N_SECURE_COOKIE=true` and refuses login over the plain-HTTP LAN URL used for
automated testing here, which is correct, pre-existing shared-infrastructure
behaviour and out of scope to change.
## Known limitations
- RAGcore and ITWorx MCP Hub remain honestly not-live-connected — no live instance of
either exists in this environment (environment limitation, not a code defect).
- `npm audit`: one moderate esbuild/Vite dev-server-only advisory (fix requires a Vite
major upgrade, deliberately deferred), and a
react-router RSC-mode advisory that doesn't apply (the app never uses RSC/SSR mode) —
both pre-existing, confirmed unchanged by this pass's clean `npm ci`.
- Demo authentication remains the accepted HMAC-cookie PoC mechanism tied to two fixed
seeded identities — not a production identity provider.
- The scheduled quality-scan workflow's own n8n-engine execution was verified live
against the local bundled n8n and, for the HTTP round trip specifically, against the
shared Unraid n8n's resulting API call — not against a full n8n-engine execution *on
the shared instance itself*, for the browser-access reason above.
- `n8n_delivery_lease_seconds` (120s default) is a code-level tunable, not exposed in
`.env.example`, consistent with the existing `n8n_dispatch_interval_seconds`/
`n8n_max_attempts`/`n8n_http_timeout_seconds` tunables already handled that way.
## Clean deployment instructions
See `deploy/unraid/README.md` and `docs/17-runbook.md` for the full runbook. Redeploy
the exact committed revision:
```bash
COMMIT=<commit to deploy>
git archive --format=tar.gz --output=/tmp/source.tar.gz "$COMMIT"
scp -P 22 /tmp/source.tar.gz unraid:/mnt/user/appdata/mobilityops/.deploy/source.tar.gz
ssh unraid "cd /mnt/user/appdata/mobilityops \
&& tar -xzf .deploy/source.tar.gz \
&& echo $COMMIT > .deploy/source-revision \
&& docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d db api web \
&& docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api alembic current \
&& curl -fsS http://127.0.0.1:1236/health"
```
`.env` and both named volumes (`mobilityops-db`, `mobilityops-n8n`) are preserved by
this flow; nothing outside the `mobilityops` Compose project is touched.
## Five-minute demonstration flow
1. Open `http://192.168.10.150:1236`, log in as **Operations Manager**.
2. Dashboard: point out the persisted readiness metrics and the now-truthful n8n
integration-status card (aggregate counts, not just the latest event).
3. Global search (`Ctrl/Cmd+K`): type a vehicle, booking or issue reference; use arrow
keys + Enter to navigate; show the no-results state for a nonsense query.
4. Bookings: filter by status, page through results (max 25/page), confirm page 2
differs from page 1.
5. Open an active booking → capture a return with a below-canonical odometer reading →
the review step shows the server's authoritative evaluation (odometer regression
flagged, resulting status and reason) → confirm → result screen distinguishes local
commit success from queued-not-yet-confirmed n8n delivery, links to the created
data-quality issue.
6. Data quality: run a manual scan; open the newly flagged (or an existing) issue for
each rule type and show its dedicated bounded resolution flow (not raw JSON).
7. Audit trail: filter by the correlation ID from the return above; show the
human-readable before/after change summary, expand the raw-JSON `<details>`.
8. Switch role to **Rental Employee**: show Data Quality/Integrations/Audit are absent
from the nav, and that direct URL navigation to any of them shows the restricted
message rather than partial data or a crash.
9. Switch back to Operations Manager, trigger **Reset demo data** with confirmation,
land back at login, log in again to confirm deterministic data was restored.
@@ -1,207 +0,0 @@
# Live n8n + RAGcore integration — final evidence
No credential values, tokens, or secrets appear anywhere in this document. Where a
credential or trace ID is referenced, only its name or an opaque reference identifier is
given, never its value.
## Commit
Built on branch `feat/live-n8n-ragcore-integration`, HEAD at commit
`aaa16305354d34f9c1f4d57253d33d9062c38faa` ("docs: record WF2 retry fix and WF4's
n8n-session-expiry blocker"). Run `git log --oneline feat/live-n8n-ragcore-integration`
for the full history of this effort.
## Scope
The brief required treating n8n (`https://n8n.itworx.tech`) as a full third integration
layer alongside RAGcore and MCP Hub, with Fleet Ops keeping exclusive ownership of
business rules, authorization, transactions, audit, and idempotency. Four canonical n8n
workflows were required. The repository (`n8n/workflows/*.json` + `MANIFEST.md` +
`n8n/workflows/check_drift.py`) is the source of truth for cleaned workflow definitions;
the Fleet Ops integration status page (`/automation`) shows real per-workflow operational
evidence, not a config boolean.
## Result summary
| # | Workflow | Status | Live evidence this round |
|---|---|---|---|
| 1 | Fleet Ops — Vehicle Return Orchestration | **Live, hardened** | Timeout+bounded-retry gap found and fixed |
| 2 | Fleet Ops — Scheduled Data Quality Scan | **Live, hardened** | Same gap found and fixed |
| 3 | Fleet Ops — RAGcore Procedure Sync | **Blocked** | Not built — RAGcore rejects credential issuance (see below) |
| 4 | Fleet Ops — Workflow Error Handler | **Live, validated** | Mock + genuine induced-failure test; own hardening incomplete (see below) |
Full per-workflow detail (purpose, trigger, event contract, required credentials, live
workflow ID, checksum) is in `n8n/workflows/MANIFEST.md`, which is the authoritative,
continuously-updated source — this document is a point-in-time summary of that state
plus the reasoning behind what's not done.
## Workflow 1 — Vehicle Return Orchestration
Live workflow ID `mobilityops-return-processing`. Validated in an earlier round of this
effort: webhook trigger requires Header Auth (`Fleet Ops Webhook Trigger Token`),
validates `event_type == vehicle.returned.v1`, derives a follow-up category, calls Fleet
Ops's `/return-callback` endpoint with an `Idempotency-Key` header via a named
`Fleet Ops Service Token` credential (not a literal secret), and responds with a
controlled JSON result. Idempotent on both sides (`event_id` flows through as the
dedup key; the backend independently checks for a prior audit event before recording
again).
**This round's finding**: the `Record follow-up` HTTP node had no explicit timeout and
"Retry On Fail" disabled — a real gap against the requirement that external dependencies
have timeouts and bounded retries. Fixed live: Retry On Fail (3 tries, 1000ms wait) + a
15000ms timeout, published. Safe to retry because the callback is idempotent. Repo
definition and manifest checksum synced (commit `0562893`).
Attached to workflow 4 as its Error Workflow.
## Workflow 2 — Scheduled Data Quality Scan
Live workflow ID `mobilityops-scheduled-quality-scan`. Validated earlier: hourly
Schedule Trigger + a Manual Trigger for on-demand testing, both feeding a single HTTP
call to Fleet Ops's `/scheduled-scan` endpoint (Header Auth via the same `Fleet Ops
Service Token` credential, 15000ms timeout already configured), which runs the
domain-level `run_scan()` function — documented and tested as idempotent by
construction (only ever creates an issue for a condition that doesn't already have one
open), so overlapping or retried triggers do no duplicate domain work.
**This round's finding**: the same Retry On Fail gap as workflow 1 (timeout was already
set, retries were not). Fixed live the same way (3 tries, 1000ms wait), published. Repo
definition and manifest checksum synced (commit `167bf49`).
Attached to workflow 4 as its Error Workflow.
## Workflow 3 — RAGcore Procedure Sync — blocked
**Not built.** This workflow needs an application credential (scope `sources:sync`) for
the `fleet-ops` application in RAGcore. Two independent issuance attempts, in two
separate rounds of this effort, both failed with an opaque server-side rejection:
1. **Raw API**: `POST /v1/applications/{id}/credentials``400`, "authoritative
service-account state rejected issuance".
2. **RAGcore admin UI**, this round, after the project owner explicitly authorized
Claude to self-issue the credential: the "Issue credential" form for the `fleet-ops`
application, submitted as the Platform Admin role (the highest role visible in the
RAGcore admin), with name `n8n-ragcore-procedure-sync` and scope `sources:sync` only.
Result: "Something went wrong. The credential could not be issued with those
values.", trace reference `1955c6a8968c4941a22a1faef39e17a7`.
The `fleet-ops` application itself shows as ordinary/`Active` in the RAGcore admin, with
no visible lock flag, and RAGcore's own OpenAPI spec documents no validation rule that
would explain either rejection (no `422`, no field-level errors). Two independent paths
— a raw API call and the admin UI as the top admin role — hitting the same failure
signature is conclusive evidence this is a RAGcore-side policy or bug, not a Fleet Ops
request-shape or permission problem. It is not fixable from the Fleet Ops side or
through further UI automation. Resolving it requires whoever operates the RAGcore
instance to look up the trace ID above (and the earlier raw-API rejection) in RAGcore's
own logs.
The real RAGcore contract this workflow will be built against — once a working
credential exists — was independently inspected via RAGcore's live OpenAPI spec and is
recorded in `docs/live-ai-integration/n8n-current-state.md` and
`contracts/ragcore-contract-assumptions.md`: control-plane endpoints require an
`Idempotency-Key` header; ingestion is `POST /v1/uploads`; retrieval is `POST
/v1/search` / `/v1/context` / `/v1/answers` (the latter requiring `requested_space_ids`,
an array of knowledge-space UUIDs); health is `/health/live` and `/health/ready` (not
`/health`); the scope enum is `search, context, answer, documents:read, citations:read,
feedback:write, sources:sync`.
**`RAGcoreKnowledgeProvider` adapter** (`backend/app/services/knowledge/ragcore.py`)
still targets the earlier speculative contract (`/health`, `POST /api/v1/ask`, Bearer
token) rather than the real one above. This was deliberately **not** rewritten this
round: rewriting it blind, without a credential to validate against, risks introducing
a silent behavioral bug in exactly the code path responsible for the project's "AI must
never invent an answer when RAGcore is unavailable or returns insufficient evidence"
guarantee — for example a wrong `evidence_state` mapping that looks fine in code review
but misclassifies "unavailable" as "insufficient" (or vice versa) against the real
response shape. The adapter's current behavior is honest and safe (it degrades cleanly
to `unavailable` on any request or parsing failure, and `ragcore_api_token` is unset by
default so the app correctly runs on the local demo knowledge provider today). The
rewrite stays queued behind the same credential blocker as workflow 3.
## Workflow 4 — Workflow Error Handler
Live workflow ID `Xppn2rAEqUuyiCJF`. Built and live-validated in an earlier round:
Error Trigger → a Code node that derives a bounded, secret-free failure report (error
category classified from the message text, truncated summary, no stack trace, no
headers or tokens) → an HTTP call to Fleet Ops's `/workflow-error` endpoint (Header Auth
via the same `Fleet Ops Service Token` credential), which registers the failure as an
audit event idempotently keyed on `execution_id`.
Set as the Error Workflow on both workflow 1 and workflow 2. Confirmed workflow 4 has no
Error Workflow of its own (prevents a recursive loop).
**Live validation performed**: a pinned mock Error Trigger payload produced a real `200
{"status":"registered", ...}` from the live Fleet Ops server; re-running the identical
payload produced `"status":"already_registered"`, confirming idempotency. A genuine
induced failure (temporarily pointing workflow 2's HTTP node at a nonexistent path, then
reverting) confirmed workflow 2 itself fails correctly against a broken endpoint and
recovers cleanly once reverted.
**Known limitation**: n8n's Error Workflow trigger does not fire for manual editor
"Execute workflow" test runs — checked via workflow 4's own Executions list after the
induced workflow-2 failure, and confirmed no new execution appeared. n8n only invokes a
workflow's assigned Error Workflow for unattended/production trigger executions, not
manual test runs from the editor. The mock-data path exercises the same nodes, logic,
and real Fleet Ops endpoint, but a fully automatic (schedule- or webhook-triggered)
failure cascading into workflow 4 was not observed live in either round.
**Open follow-up (minor, non-blocking)**: continuing this round's acceptance pass to
workflow 4 found the same timeout/retry gap as workflows 1 and 2 on its own outbound
HTTP call. A fix was started (15000ms timeout added, Retry On Fail toggled on) but n8n's
autosave began failing with "Unauthorized" mid-edit; a fresh browser tab confirmed the
n8n session had expired (redirected to `/signin`). Nothing was saved — workflow 4's live
definition is unchanged from before this round, so there is no partial or broken state.
This is lower-stakes than workflows 1/2 (workflow 4 is the error notifier itself, not a
primary business flow, and a failed error-report is already visible in n8n's own
execution history via `On Error: Stop Workflow`) but should be finished once the n8n
browser session is re-authenticated.
## Repository source of truth
`n8n/workflows/` holds cleaned, credential-value-free JSON definitions for all built
workflows, `n8n/workflows/MANIFEST.md` documents purpose/trigger/contract/credentials/
live-ID/checksum for all four canonical workflows (including workflow 3's blocked
status), and `n8n/workflows/check_drift.py` is a read-only script that compares the
repo definitions against the live instance via n8n's Public API and reports drift —
safe to run in CI as a non-blocking check. No literal export/download mechanism was
found working in this n8n version, so each definition was reconstructed from direct,
verified UI inspection rather than a native export; this limitation is noted in the
manifest itself.
## Integration status page
`/automation` (Operations Manager only) surfaces real per-workflow evidence derived
purely from Fleet Ops's own audit/outbox tables — no new dependency on n8n's API was
added to the backend. Each of the four canonical workflows shows a status (not built /
no evidence yet / operational) and a last-evidence timestamp; the scheduled-scan
evidence specifically filters to `actor_type == "service"` so a manually-triggered scan
in the UI doesn't count as n8n evidence. An error-handler summary line reports total
registered automation failures and the most recent one.
Verified live in the browser (Dutch locale) both locally and on the deployed
production server (`http://192.168.10.150:1236/automation`): correctly showed "3 van 4
canonieke n8n-workflows hebben actuele evidentie van werking" with real timestamps for
the return/scan/error-handler workflows, "Nog Niet Gebouwd" for the RAGcore sync, and
the real error-handler registration from this effort's live testing.
## Deployments performed (all explicitly user-approved)
1. Backend `/workflow-error` endpoint (commit `bbdb4a9`) — deployed and verified
(`/health` OK, new endpoint returns `422` not `404` on an empty POST body).
2. Integration status page, backend + frontend (commit `4049c0c`) — deployed and
verified (`/health` OK, page renders real evidence in the browser).
The three n8n-side node edits this round (WF1 timeout/retry, WF2 timeout/retry, WF4's
incomplete attempt) are live edits to the n8n instance itself and do not require a
Fleet Ops redeploy.
## What's left
1. **RAGcore credential issuance** — blocked on RAGcore's own server-side rejection
(trace `1955c6a8968c4941a22a1faef39e17a7` and the earlier raw-API `400`). Needs
RAGcore's operator to investigate. Unblocks workflow 3 and the
`RAGcoreKnowledgeProvider` real-contract rewrite.
2. **Workflow 4's own timeout/bounded-retry hardening** — needs the n8n browser session
re-authenticated to finish; a small, well-understood, non-blocking edit.
3. **Fleet Ops logo/favicon** — explicitly deferred by the project owner as a separate,
unrelated follow-up task, not part of this integration effort.