Commit Graph
14 Commits
Author SHA1 Message Date
NuklearRabbit 0bfcf71ff7 M8: add operational authentication mode 2026-08-10 02:06:50 +02:00
NuklearRabbit f2cdad194c UX: implement visual product roadmap 2026-08-10 01:05:07 +02:00
NuklearRabbitandClaude Sonnet 5 4faac24b5a fix: localize dashboard evidence, explain blocked vehicles, clarify pending odometers
Three content defects found by a live reviewer:

- Dashboard attention subtext was raw, untranslated evidence.summary text, and for
  11 of 15 seeded issues that text was literally "Synthetic deterministic seed
  issue". AttentionItem now exposes evidence_signals (stable code + params, same
  shape as the issue detail page) instead of a detail string; the frontend renders
  them through a shared describeEvidenceSignal() used by both the dashboard and the
  issue detail page. Every previously-placeholder seed row now cites a real,
  per-rule-type fact (a genuinely crossed service threshold, a genuinely blank
  field, or a real pair of booking odometer readings) instead of invented prose.

- 5 of 7 blocked vehicles had no quality issue at all and one had only a resolved
  one, so "needs attention" led nowhere. Each now has a real open
  missing_required_field issue backed by a genuinely blank field (no schema change,
  no migration -- reuses the existing data-quality pipeline).

- Booking odometer fields showing a bare "-" for 25 reserved + 1 active booking now
  show a localized explanation ("trip hasn't started yet" / "not yet closed").
  MO-024's rented-but-service-overdue contradiction was already caught by the
  vehicle-status evaluator (DQ-SCAN, vehicle.manual_review_required) -- added a
  regression test rather than new logic.

Also fixed a related bug the above exposed: the vehicle entity_snapshot omitted
registration_number entirely, so the "provide missing fields" form always showed
it blank regardless of the real value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 17:44:32 +02:00
NuklearRabbit 6f77a30dce fix: stop the prepared demo failure from degrading n8n integration health
The demo seed plants exactly one failed delivery (BK-H-0020) to demonstrate
retry and audit. Because derive_n8n_status() counted any failure, every fresh
reset pinned the n8n integration to "degraded" -- the demo showed a warning
about a prop, which tells a viewer something untrue about the automation.

The seeded failure now carries its own error code, demoScenarioTimeout, rather
than the generic connectionError a real timeout produces. No column and no
migration: last_error_code already existed, is already surfaced to the UI and is
already localizable.

- integration status splits failed into unexpected_failed and
  demo_scenario_failed; only unexpected failures may move the state. A staged
  failure alone leaves n8n operational.
- latest_failure_at is a health signal and now ignores the staged failure;
  latest_demo_scenario_at reports it separately.
- /api/v1/workflows exposes is_demo_scenario. The Automation page labels the run
  as a prepared demo scenario, explains that it is a simulated temporary failure
  that does not affect automation health, and offers a distinct "retry demo
  scenario" action. Translated in nl-BE, en-GB and fr-BE.
- the carve-out stays narrow: a real failure still degrades n8n, and a genuine
  later failure of the same event overwrites the demo code with the real one.
- the retry itself is unchanged and real: the event goes back on the outbox and
  the dispatcher delivers it to n8n like any other, so 19+1 becomes 20+0 only on
  an actual round trip. The audit records which kind of failure was retried.

Tests that assert on the seeded scenario now reseed first, since earlier test
files legitimately mutate the outbox and the suite shares one database.

Verified locally against a real PostgreSQL 16: 181 passed, ruff clean, mypy
clean (50 files), tsc clean, frontend build clean. Not deployed and not
browser-verified.
2026-08-05 14:07:05 +00:00
NuklearRabbitandClaude Sonnet 5 727c19a779 M9: MCP Hub locale/correlation propagation, real Hub health check, fix stale test image
Fixed two concrete gaps in the MCP knowledge-search 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 a shared dependency and
applied it to all four MCP endpoints so Fleet Ops's own audit log preserves
the Hub's real correlation ID end to end.

MCP_HUB_BASE_URL/MCP_PROVIDER_ID were declared in .env.example but never read
anywhere. Since the Hub's own registration is catalog-driven (it never needs
Fleet Ops to push a registration call), wired mcp_hub_base_url for a real Hub
reachability health check instead of an unneeded self-registration call.

Renamed Fleet Ops's own internal audit tool labels mobilityops_* -> fleet_ops_*
(mirrored in contracts/mcp-tools.json with mobilityops_* kept as deprecated
aliases); documented that the live Hub connector's own dotted tool namespace
is a separate, Hub-owned naming layer, deliberately not touched.

Automation page's MCP card now shows real evidence (last tool/client/count/
timestamp, honest no-evidence state) instead of just the registration flag.

Also fixed a real methodology gap found mid-session: compose.yaml's api
service has no bind mount, so `docker compose run --rm api` silently tests a
stale image until rebuilt. Re-ran every local gate after rebuilding; fixed one
genuinely stale test assertion and two lint line-length errors surfaced by
that rebuild. 176 tests passing, ruff clean, mypy clean (50 files).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 13:30:24 +02:00
NuklearRabbitandClaude Sonnet 5 34df66d28c M8: GUI polish, n8n workflow-3 fixes, RAGcore retrieval root-cause and fix
GUI: dashboard Attention Queue presents a curated severity mix instead of pure
severity-sort (grouped Now/Today/Later headers); Today's Movements seed data
curated so a fresh reset shows a credible day (2+ departures, 2+ returns), with
a new seed-integrity test; About Demo restructured into a compact grid with
progressive disclosure for technical sections; Duplicate Merge shows match/conflict
counts, hides matching fields by default, and previews the final merged record
before confirmation.

Repo hygiene: removed a stray empty `backend;C` directory and an untracked 31MB
zip export; `.gitignore` now excludes future archive exports.

n8n: fixed invalid JSON (a missing `},` between two node objects) in the committed
`fleet-ops-vehicle-return.json` -- the file could not be parsed. Live-validated
workflow 3 (RAGcore Procedure Sync): found and fixed a real defect (three body
parameters had a stray trailing `}}`) and a missing Error Workflow wiring, both
via the safe `n8n import:workflow` CLI path; exported the corrected, still-
inactive workflow as the new source of truth and updated MANIFEST.md/check_drift.py.
Publishing it (starts real daily unattended runs) remains a separate decision.

RAGcore: root-caused and fixed (live, approved) the "zero retrieval candidates"
bug -- a filesystem permission bug (`embedding_profiles.json` unreadable by the
app's own runtime user) that broke every retrieval call before it reached Qdrant.
Every other suspect (grants, scope resolution, Qdrant filters, embeddings) was
verified healthy first. Found a second, deeper gap: the reranker adapter calls
an Ollama HTTP route that does not exist on the deployed Ollama version, so
`/v1/answers` still returns `not_answerable`. `KNOWLEDGE_PROVIDER` stays `demo`
until that is resolved on the RAGcore side. Evidence-based MCP Hub integration
status (real tool-call audit history, not just a boolean flag) replaces the old
`configured`/`not_configured` guess. Full findings in
`docs/final-integrations/current-state-audit.md`.

Backend: 172 tests passing, ruff clean, mypy clean (50 files). Frontend: tsc
clean, production build clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 13:05:02 +02:00
NuklearRabbitandClaude Sonnet 5 4049c0c6b1 n8n: surface real per-workflow evidence on the integration status page
Fleet Ops integration status no longer depends only on a config
boolean or the most recent outbox event: N8nIntegrationStatus now
reports per-canonical-workflow evidence (last successful outbox
delivery for the return workflow, latest service-triggered
data_quality_scan_run for the scan workflow, latest
n8n_workflow_failure_registered for the error handler, and "not built"
for the still-blocked RAGcore sync), plus an error-handler summary
(total failures registered, latest failure + which workflow).

Automation page renders this as a localized workflow table (EN/NL/FR)
with technical workflow names tucked under a "Technical details"
disclosure, matching the existing progressive-disclosure pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 16:15:09 +02:00
NuklearRabbit 09173a4740 fix: correct fr-BE audit column label Actor -> Auteur
Caught during live browser validation on Unraid: fr-BE had "Acteur" for
the audit trail's actor column, but the brief's minimum-required French
corrections specify "Actor" -> "Auteur" explicitly.
2026-08-04 03:32:48 +02:00
NuklearRabbit e427313bce feat: add time-dependent Europe/Brussels dashboard greeting
The dashboard greeting was a fully static "Goedemorgen..." regardless of
actual time of day. New frontend/src/i18n/greeting.ts::getGreetingPeriod
is a pure, clock-injectable function resolving one of 4 periods (05:00-
11:59 morning, 12:00-17:59 afternoon, 18:00-22:59 evening, 23:00-04:59
night) against Europe/Brussels wall-clock time via
Intl.DateTimeFormat({ timeZone, hourCycle: "h23" }), which is DST-safe
by construction.

useGreetingPeriod.ts wires this into React with a 30s poll so the
greeting rolls over live while the app stays open, no reload required.
Each period now has its own greeting word and accompanying sentence in
all 3 languages (dashboard.json), replacing both the fixed "Goedemorgen"
and the fixed "Here's the fleet" follow-up sentence. Night never says
"Goedenacht" (used as a farewell, not a welcome, in Dutch).
2026-08-04 03:08:45 +02:00
NuklearRabbit d17af1c52a feat: centralize API error localization
Replace the err instanceof ApiError ? err.message : t(fallback) anti-
pattern -- which showed raw English backend text for the common case and
only used the localized fallback for the rare network-failure case -- at
all 13 call sites across 7 files.

New frontend/src/api/errorMessages.ts (describeApiError) resolves a
caught error to a localized {title, explanation, nextStep?, technical}
by checking the 32 known AppError codes first, then known HTTP statuses
(401/403/404/409/422/500), then a fully generic fallback. New
ApiErrorNotice (PageChrome.tsx) renders title/explanation/nextStep with
the raw text demoted to a "Technical details"/"Details techniques"
disclosure -- never shown as the primary message.

ApiError itself is split out of client.ts into a standalone
api/apiError.ts with no import.meta.env dependency, so errorMessages.ts
(and its tests) can be loaded outside a Vite/browser context.
2026-08-04 03:08:07 +02:00
NuklearRabbit 37a362c4a0 fix: translate remaining NL/FR interface gaps
Role names, audit/scenario labels, and status text were previously either
left in English or only partially translated:
- auth.json/demo.json role labels actually translated (not just labelled
  as translated): Operationsmanager/Verhuurmedewerker,
  Responsable des operations/Collaborateur de location.
- "Audit trail" -> Auditgeschiedenis/Piste d'audit (title, column header,
  and every mid-sentence occurrence across demo.json, quality.json,
  returns.json -- these embedded leaks were previously invisible to the
  whole-string identity check).
- "Open" (status) -> Openstaand, "Recent" -> Recentste,
  "Start scenario" -> Scenario starten / Demarrer le scenario.

Matching Playwright spec text updated in the same commit so the suite
never regresses through a broken intermediate state.
2026-08-04 03:03:46 +02:00
NuklearRabbitandClaude Sonnet 5 2e4fb43f09 fix: localize the primary data-quality evidence summary (live-caught on Unraid)
Live validation on the deployed fix branch caught a real bug: every data-quality
issue's top-of-page "Evidence summary" line rendered the raw, always-English legacy
evidence.summary string unconditionally -- in all three languages -- even though the
backend has been emitting structured, localizable evidence.signals for a while
(app/services/data_quality.py already documented this exact intent). The frontend
side of that conversion was never finished.

- DataQualityIssueDetail.tsx now renders evidence.signals through the operator's
  locale as the primary summary; the raw evidence.summary string is only visible
  inside "Technical details" (via the existing EvidenceDisclosure JSON dump).
- The four DQ-DEMO-* seed rows that anchor the guided demo's scripted scenarios now
  carry real, accurate signals computed at seed time (duplicate-customer's similarity
  score is the actual SequenceMatcher ratio on the seeded names, not invented) instead
  of only a legacy English sentence.
- Rows with no structured signals (generic filler seed data) fall back to the raw
  text rather than showing a blank summary; the one known placeholder string gets its
  own localized rendering so it never displays as English filler either.
- New regression test: the vehicle_status_conflict evidence summary must show
  localized text and must never contain the specific raw English sentence that was
  live-visible before this fix, in all 3 languages.

151 backend tests, Ruff, mypy green; full local Playwright suite green (a couple of
sequential-run-only flakes, both confirmed to pass in isolation and unrelated to this
change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 00:44:06 +02:00
NuklearRabbitandClaude Sonnet 5 6deb95524d fix: safe status-recommendation flow, MO-016 order independence, brand constant, message codes
- Add a single shared, pure vehicle-status evaluator (app/services/vehicle_status.py)
  used identically by the data-quality scanner, a new non-mutating status-recommendation
  preview endpoint, and a transactional apply endpoint with optimistic-concurrency token
  revalidation -- eliminates the old opaque "calculate and apply" action and the unsafe
  "maintenance + active booking -> auto rented" shortcut. Frontend
  DataQualityIssueDetail.tsx now shows a review/decide/confirm panel with localized
  why/evidence/consequence text in nl-BE/en-GB/fr-BE, with an exact "Change status to
  <status>" confirm action per the brief.
- Fix MO-016 issue-order dependency: resolving the booking-overlap issue before vs.
  after the status-conflict issue now converges on the same final vehicle status,
  proven by test_mo_016_status_conflict_recommendation_is_order_independent.
- Make "Fleet Ops" a non-localizable brand constant (frontend/src/product.ts,
  backend PRODUCT_NAME) via {{productName}} interpolation everywhere the brand name
  appeared in locale prose; add a permanent test guarding against a translation file
  ever defining the brand name or an "appName" key again.
- Convert dynamic backend prose to stable message codes + params: return status
  reasons, audit field/actor-type labels, automation last_error, and search
  section/vehicle/booking/issue results all now carry codes the frontend localizes,
  with raw technical text demoted to a "Technical details" disclosure.
- docs/fleet-ops-correction/: gap audit, i18n inventory, and the vehicle-status
  decision table documenting the evaluator's rules and safe-status principles.

148 backend tests + Ruff + mypy green; Alembic migration verified upgrade/downgrade;
frontend tsc/build and the i18n-coverage Playwright suite green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 21:37:34 +02:00
NuklearRabbitandClaude Sonnet 5 337f8716bb polish: rebrand to Fleet Ops, add trilingual i18n, adaptive demo guide, and UX overhaul
Rebrands the product from MobilityOps to Fleet Ops across the UI, backend defaults and
knowledge base, and makes nl-BE/en-GB/fr-BE full first-class languages: i18next with
eager-bundled per-namespace resources, a persisted accessible language switcher (topbar
and mobile drawer), locale-aware date/number formatting, and a coverage test that fails
the build on any missing or empty translation key.

Backend dynamic content (demo scenarios, blocked-reason text, integration status) moves
from fixed English/Dutch prose to stable message codes + params so the frontend can
localize it; the demo knowledge base gains a fully translated NL/EN/FR procedure corpus
(11 documents each) with per-language retrieval and localized evidence-state messages.

The Demo Guide becomes breakpoint-adaptive: a docked rail on extra-wide desktop, a
floating panel that auto-collapses to a persistent, closable progress chip on standard
desktop/tablet, and a collapsed/half/full bottom sheet on mobile -- with scroll+focus+
highlight on "go to this step", Escape handling, and reduced-motion support.

The Data Quality Workbench gets accessible choice-card decisions with a clear primary/
secondary/tertiary action hierarchy; the Automation ledger groups repeated successes and
uses meaningful short refs; the Audit trail groups events by correlation id with human
action labels and readable before/after diffs. Attention Queue, Today's movements,
Vehicles, Bookings and Data Quality rows are fully clickable (stretched-link pattern)
with independent secondary links, keyboard support and mobile touch targets.

Fixes a topbar overflow on mobile caused by the new language switcher (moved into the
mobile drawer at <=960px) and two dangling aria-labelledby references introduced this
session. Updates all affected Playwright specs for the new nl-BE default and the new
Audit/DemoGuide DOM structure, and adds new i18n-coverage, demo-guide-adaptive and
clickable-rows specs. 131 backend tests, Ruff and mypy, and 71 Playwright tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:33:22 +02:00