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>
This commit is contained in:
NuklearRabbit
2026-08-05 13:30:24 +02:00
co-authored by Claude Sonnet 5
parent 2ae2044e3a
commit 727c19a779
17 changed files with 269 additions and 45 deletions
+53 -4
View File
@@ -57,6 +57,7 @@ M7 — complete. All milestones (M0M7) done, plus a full post-M7 final-accept
- Inspection `public_ref` is assigned as `INSP-{count+1:04d}` from a live count query (not gap-safe, fine for a PoC single-writer demo, would need a sequence for real concurrency-safe numbering).
- Found and fixed during browser verification (not caught by pytest, since it's a UI-only defect): `ReturnForm` originally held its own `result` state and was conditionally rendered only when `booking.status === "active"`; once the return succeeded the booking flipped to `returned` and React unmounted the form before the user ever saw the result panel. Fixed by lifting the result into `BookingDetail` (`ReturnResultPanel` is now a sibling, not nested in `ReturnForm`). Also found: `OutboxEvent.event_id`'s Python-side `default=uuid.uuid4` on the mapped_column only applies at flush/commit time, so reading `event.event_id` before `db.commit()` returned `None` (rendered as the literal string "None" in the result panel); fixed by assigning `event_id=uuid.uuid4()` explicitly at construction. Lesson: SQLAlchemy column `default=` callables are not available on the in-memory Python object until flush — never rely on the generated value for a same-transaction response body without an explicit `db.flush()` or an explicit Python-side assignment.
- Operational note for this environment: `docker compose run --rm api ...` (used for tests/lint) only starts a throwaway one-off container — it does **not** update the long-running `api`/`web` service containers. After any code change meant to be verified live (browser, curl), `docker compose up -d --build <service>` is required, not just `docker compose build`.
- **Sharper version of the note above, found the hard way (2026-08-05)**: `compose.yaml`'s `api` service has **no bind mount** for `./backend` — application code is baked into the image at build time only. `docker compose run --rm api pytest/ruff/mypy` reuses whatever image was last built; it does **not** pick up host file edits at all, not even for a throwaway container. Editing code and immediately running `docker compose run --rm api pytest` without an intervening `docker compose build api` silently tests/lints the *old* code and can report a false "all green." Always `docker compose build api` before the first local gate run after a code change in a session (subsequent runs against the same build are fine). Caught this only because a new test file's test count didn't match after several rounds of edits; re-ran the full local gate suite after rebuilding and found one genuinely stale test assertion (below) — nothing else was actually broken, but this was luck, not verification, until the rebuild.
## Completed evidence
@@ -1755,7 +1756,55 @@ deployed line — `master` is stale, 19 commits behind). Full audit at
clean; `mypy app` — clean (50 files); `cd frontend && npm run build` — clean.
- Committed `34df66d`, pushed to `origin/feat/fleet-ops-final-integrations`. Not yet
deployed to the live Fleet Ops instance at this point in the session.
- **Exact next action**: deploy this revision to `http://192.168.10.150:1236`; Batch 4
(MCP Hub — confirm Fleet Ops-side config wiring, tool naming, locale param, correlation
ID propagation); Batch 5 (AI Operations Brief using the demo knowledge provider since
RAGcore stays off; GUI activity showcase; final regression; evidence write-up).
- Deployed to `http://192.168.10.150:1236` (`git archive` + scp + `docker compose up
--build -d db api web`, following the established deployment convention). Browser-
verified live: Attention Queue tier grouping, Today's Movements curated seed mix (2
returns + 2 departures after a real demo reset), About Demo progressive disclosure,
Duplicate Merge match/conflict summary + preview all render correctly.
- **Found and fixed a real methodology gap mid-session**: `compose.yaml`'s `api` service
has no bind mount, so `docker compose run --rm api pytest/ruff/mypy` was silently
testing a stale image for an unknown portion of this session (only caught because a new
test's collected-count didn't match). Rebuilt and re-ran every local gate from that
point on; found exactly one genuinely stale test assertion (`ragcore_sync["built"]` —
correctly `True` now, not `False`, since workflow 3 really is built) and two line-length
lint errors in the new seed test, both fixed. See the sharper operational note added
next to the original `docker compose run` warning above.
## Batch 4 — MCP Hub (2026-08-05)
- `MCP_HUB_BASE_URL`/`MCP_PROVIDER_ID` were declared in `.env.example` but never read by
`Settings` anywhere — dead config. Since the Hub's own registration is catalog-driven
(it reconciles its catalog into the gateway; Fleet Ops never pushes a registration
call — confirmed via the Hub-side investigation), wiring them for self-registration
would have built an unneeded feature. Wired `mcp_hub_base_url` for something Fleet Ops
actually needs instead: a real, bounded (1.5s timeout) Hub reachability health check,
surfaced as `hub_reachable` on `/api/v1/integrations/status` and the Automation page.
- Fixed two real, concrete gaps in `mcp_integrations.py`'s `search-knowledge` endpoint
matching the task brief's own description almost verbatim: no `locale` field existed at
all (now `nl-BE`/`en-GB`/`fr-BE`, wired straight through to the knowledge provider,
which already supported a `language` param — just never received one), and the
correlation ID was **always** freshly minted (`uuid.uuid4()`), ignoring any inbound
`X-Correlation-Id` header. Added a shared `get_correlation_id` dependency (valid inbound
UUID preserved end-to-end into Fleet Ops's own audit log; fresh UUID only when absent/
invalid) and applied it to all four MCP endpoints, not just search-knowledge.
- Fleet Ops's own internal audit tool labels renamed `mobilityops_*` → `fleet_ops_*`
(`fleet_ops_get_operations_summary`, `.list_attention_vehicles`, `.get_vehicle_details`,
`.search_knowledge`) and mirrored in `contracts/mcp-tools.json` (with `mobilityops_*`
kept as `deprecated_aliases`, per the task's own "don't break existing clients"
instruction). Note: the **live** ITWorx MCP Hub connector publishes these under its own
dotted namespace (`mobilityops.operations.summary` etc.) — that naming is Hub-owned and
was deliberately not touched (separate repo, active concurrent session there, and
already verified live per the Hub-side investigation); `docs/10-mcp-hub-integration.md`
now documents both naming layers explicitly so this isn't read as a contradiction.
- Automation page's MCP card now shows real evidence (last tool/client/call count/
timestamp, honest "registered but no calls yet" state) instead of only the
registration-enabled boolean, plus the new Hub-reachability badge.
- New tests: `test_search_knowledge_respects_requested_locale`,
`test_search_knowledge_preserves_inbound_correlation_id`,
`test_operations_summary_mints_correlation_id_when_none_supplied`.
- Evidence: `docker compose run --rm api pytest -q` — **176 passed** (against a verified
fresh rebuild); `ruff check .` — clean; `mypy app` — clean (50 files); `cd frontend &&
npm run build` — clean.
- **Exact next action**: commit and deploy Batch 4; Batch 5 (AI Operations Brief using the
demo knowledge provider since RAGcore stays off; GUI activity showcase optional-after-
demo-complete; final regression across all gates; `artifacts/final-integrations/final-summary.md`).