Full acceptance evidence for the Fleet Ops correction milestone: commits, branding, translation coverage, status-preview/apply/manual-review/MO-016-ordering results, knowledge grounding per language, audit/automation localization, backend/frontend test results, clean-checkout drill, Unraid deployment (both fix-branch and post-merge master), responsive/accessibility results, known limitations, and rollback procedure. Includes live screenshots (nl-BE and fr-BE login, and the localized data-quality evidence summary that live validation caught and fixed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
18 KiB
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 to2e4fb43, which carries the evidence-summary localization fix) - Main-before-merge:
18344bc8b7a75a2f868bf15bf498fc030ac6c34c(confirmed unchanged viagit fetch+git rev-parse origin/masterimmediately 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-revisionon Unraid) - Gitea main branch:
master(confirmed viagit fetch origin && git rev-parse origin/mastermatching localmasterafter 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
- Status-recommendation flow redesigned (sections 8A–8F). 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 indocs/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 stalerecommendation_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 <status>" confirm action → result, or a distinct "Manual review required" state offering no generic apply button.
- 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 openvehicle_status_conflictissues was most recent, not necessarily MO-016's) and a browser-level Playwright test covering both orders. - "Fleet Ops" is a non-localizable brand constant (
frontend/src/product.ts, backendPRODUCT_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 anappNamekey again. - Dynamic backend prose converted to message codes + params (sections 5/6/10):
return status reasons, audit field/actor-type labels, automation
last_error(newlast_error_codecolumn, migration799d8800e241), 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. - 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. - 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.
- 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, correcthtml[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.tsxnow rendersevidence.signalsthrough 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 actualSequenceMatcherratio 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
appNamekey (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
/healthendpoint'sservice: "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.pyscans every procedure file for both strings).
Status-preview / apply / manual-review / MO-016 ordering
- Preview: verified non-mutating — the issue's
statusstays"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 headfrom empty database lands on799d8800e241(the newoutbox_events.last_error_codecolumn);downgrade -1/upgrade headround-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-0xxxfiller 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
ssh unraid,cd /mnt/user/appdata/mobilityops.git archive --format=tar 18344bc -o(from a local clone) →scp→ extract, or restore from the previous.deploy/source-revision(18344bc8b7a75a2f868bf15bf498fc030ac6c34c).echo 18344bc8b7a75a2f868bf15bf498fc030ac6c34c > .deploy/source-revision.docker compose -f compose.yaml -f compose.unraid.yaml build api web && ... up -d api web.alembic downgrade e7b08389f47fif thelast_error_codecolumn 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).- Re-seed and re-verify
scenario_integrity.all_ready: true.
The fix branch fix/fleet-ops-i18n-status-flow was not deleted.