CB-609: docs/MCP-Contract.md is a pre-build design doc that CLAUDE.md still points every session at #114

Closed
opened 2026-08-17 14:25:09 +02:00 by ltms · 1 comment
Owner

Found in a pre-tag audit on 2026-08-17. Partially mitigated already — see Done now at the end.

The finding

docs/MCP-Contract.md opens with:

Status: 🟡 Design (2026-07-14). Greenfield — no MCP code exists yet; the pom carries only Javalin/Jackson.

That was true when it was written. The MCP server then shipped, and this page never caught up. Measured against mcp/BridgeMcp.java and BridgedApp.build():

Tools it names that do not exist bridge_read, bridge_cancel
Shipped tools it omits bridge_poll, bridge_ack, bridge_profiles, bridge_whoami
Parameter names wrong nearly everywhere — message/target/timeout_seconds/block where the code takes content/sessionId/timeoutMs/wait; text for bridge_reply's content; target for bridge_stop's paneId; bridge_status.target optional where the code requires sessionId
REST paths POST /workers, DELETE /workers/{paneId} — the daemon serves POST /members, DELETE /members/{paneId}

bridge_cancel is labelled "future" in the doc itself, so that one is honest. The rest is drift.

Why it is not just a stale file

CLAUDE.md — the instruction surface every session in the fleet loads — sends readers here:

Flows and the error model … are diagrammed in docs/MCP-Contract.md §6

So an agent that follows the pointer lands on a page whose tool table is wrong, headed by a banner claiming the code does not exist. CLAUDE.md says of itself that the prompt is part of the product; this is the one place it delegates, and the delegate is wrong.

The genuinely useful part

§6 — the flows and error model (rendezvous, bridge_ask, detached delivery, turn-done fallback). Those shapes are what shipped. Only the names around them drifted. Any rewrite should keep §6 and treat the rest as history.

Done now (2026-08-17), so nobody is misled while this waits

  • The status banner is rewritten to 🔴 HISTORICAL DESIGN — do NOT use as the tool reference, listing the specific drift above, and naming the live MCP schema plus CLAUDE.md's intent→tool table as the authority.
  • CLAUDE.md's pointer now says §6 only and warns about the rest.
  • Separately fixed: CLAUDE.md step 5 said bridge_ack{ticket, msgId}; the code requires target and msgId and errors otherwise. Corrected in CLAUDE.md and in the byte-identical wiki template.

What is left for this ticket

  1. Either rewrite the page against the code, or cut it down to §6 and delete the stale tool catalogue outright. Deleting is an acceptable answer — the live MCP schema documents each tool at its point of use, and a second copy is what drifted in the first place.
  2. If a written tool reference is kept, it needs something that fails when it drifts. A doc with no check becomes this page again in six months — see CB-611 for why a hand-maintained second copy is the recurring defect here.
  3. Decide whether the REST surface is documented anywhere at all. Today 12 of 14 routes appear in no document: /healthz, /metrics, /sessions, /members, /profiles, POST /members, POST /sessions/{id}/message, POST /sessions/{id}/reply, GET /sessions/{id}/replies, POST /sessions/{id}/ask, GET /sessions/{id}/status, GET /tasks/{ticket}. CLAUDE.md documents no REST at all, deliberately. If REST is a supported operator surface it belongs in wiki/11-Features.md; if it is internal, say so once and stop treating its absence as debt.

Milestone

2.0. The misleading part is fixed; what remains is a rewrite, and the shipped instruction surface (CLAUDE.md + the live schema) was audited and is accurate.

Related

CB-611 (#113) — the pattern of second copies drifting.

Found in a pre-tag audit on 2026-08-17. Partially mitigated already — see *Done now* at the end. ## The finding `docs/MCP-Contract.md` opens with: > **Status:** 🟡 Design (2026-07-14). Greenfield — **no MCP code exists yet**; the pom carries only Javalin/Jackson. That was true when it was written. The MCP server then shipped, and this page never caught up. Measured against `mcp/BridgeMcp.java` and `BridgedApp.build()`: | | | |---|---| | Tools it names that do not exist | `bridge_read`, `bridge_cancel` | | Shipped tools it omits | `bridge_poll`, `bridge_ack`, `bridge_profiles`, `bridge_whoami` | | Parameter names | wrong nearly everywhere — `message`/`target`/`timeout_seconds`/`block` where the code takes `content`/`sessionId`/`timeoutMs`/`wait`; `text` for `bridge_reply`'s `content`; `target` for `bridge_stop`'s `paneId`; `bridge_status.target` optional where the code requires `sessionId` | | REST paths | `POST /workers`, `DELETE /workers/{paneId}` — the daemon serves `POST /members`, `DELETE /members/{paneId}` | `bridge_cancel` is labelled "future" in the doc itself, so that one is honest. The rest is drift. ## Why it is not just a stale file `CLAUDE.md` — the instruction surface every session in the fleet loads — **sends readers here**: > Flows and the error model … are diagrammed in `docs/MCP-Contract.md` §6 So an agent that follows the pointer lands on a page whose tool table is wrong, headed by a banner claiming the code does not exist. `CLAUDE.md` says of itself that the prompt is part of the product; this is the one place it delegates, and the delegate is wrong. ## The genuinely useful part **§6 — the flows and error model** (rendezvous, `bridge_ask`, detached delivery, turn-done fallback). Those shapes *are* what shipped. Only the names around them drifted. Any rewrite should keep §6 and treat the rest as history. ## Done now (2026-08-17), so nobody is misled while this waits - The status banner is rewritten to **🔴 HISTORICAL DESIGN — do NOT use as the tool reference**, listing the specific drift above, and naming the live MCP schema plus `CLAUDE.md`'s intent→tool table as the authority. - `CLAUDE.md`'s pointer now says **§6 only** and warns about the rest. - Separately fixed: `CLAUDE.md` step 5 said `bridge_ack{ticket, msgId}`; the code requires `target` and `msgId` and errors otherwise. Corrected in `CLAUDE.md` and in the byte-identical wiki template. ## What is left for this ticket 1. Either rewrite the page against the code, or cut it down to §6 and delete the stale tool catalogue outright. **Deleting is an acceptable answer** — the live MCP schema documents each tool at its point of use, and a second copy is what drifted in the first place. 2. If a written tool reference is kept, it needs something that fails when it drifts. A doc with no check becomes this page again in six months — see **CB-611** for why a hand-maintained second copy is the recurring defect here. 3. Decide whether the REST surface is documented anywhere at all. Today **12 of 14 routes appear in no document**: `/healthz`, `/metrics`, `/sessions`, `/members`, `/profiles`, `POST /members`, `POST /sessions/{id}/message`, `POST /sessions/{id}/reply`, `GET /sessions/{id}/replies`, `POST /sessions/{id}/ask`, `GET /sessions/{id}/status`, `GET /tasks/{ticket}`. `CLAUDE.md` documents no REST at all, deliberately. If REST is a supported operator surface it belongs in `wiki/11-Features.md`; if it is internal, say so once and stop treating its absence as debt. ## Milestone **2.0.** The misleading part is fixed; what remains is a rewrite, and the shipped instruction surface (`CLAUDE.md` + the live schema) was audited and is accurate. ## Related CB-611 (#113) — the pattern of second copies drifting.
ltms added this to the 2.0 — one operation centre, many hosts milestone 2026-08-17 14:25:09 +02:00
Author
Owner

Closing — all three remaining items are resolved.

1. Rewrite or cut down. Cut down, e897e52. The page went 389 → 188 lines: the flows and the error model stayed, the tool catalogue and the pre-build planning sections were deleted. Deleting was the right answer for the reason the ticket gave — the live schema documents each tool where it is used, and the second copy is what drifted.

2. Something that fails when it drifts. McpContractDocTest, three tests: the tool names the page mentions must exist in FleetMcp, the page must carry its "not a tool reference" disclaimer, and — the part that matters — a denominator guard, because an empty set is a subset of everything and a name-check that finds no names passes silently.

That guard earned itself immediately. My first mutation run came back green and I nearly believed it; grep -c showed the mutation had never been applied. Re-applied properly, it killed.

3. The REST surface. Split out as #252 — it is a genuinely separate decision (is REST supported or internal?) and does not belong to this page. 14 routes, 0 entries.

Since this was filed, CLAUDE.md's pointer has been rewritten again to name the guard test rather than "§6 only", so the instruction surface now points at something that cannot silently rot. The canonical block was re-checked as byte-identical with the wiki template afterwards — the edit was in the project addendum, outside the block.

The pattern ticket this belonged to, #113, is now closed too: all three of its instances are fixed.

Closing — all three remaining items are resolved. **1. Rewrite or cut down.** Cut down, `e897e52`. The page went 389 → 188 lines: the flows and the error model stayed, the tool catalogue and the pre-build planning sections were deleted. Deleting was the right answer for the reason the ticket gave — the live schema documents each tool where it is used, and the second copy is what drifted. **2. Something that fails when it drifts.** `McpContractDocTest`, three tests: the tool names the page mentions must exist in `FleetMcp`, the page must carry its "not a tool reference" disclaimer, and — the part that matters — a **denominator guard**, because an empty set is a subset of everything and a name-check that finds no names passes silently. That guard earned itself immediately. My first mutation run came back green and I nearly believed it; `grep -c` showed the mutation had never been applied. Re-applied properly, it killed. **3. The REST surface.** Split out as **#252** — it is a genuinely separate decision (is REST supported or internal?) and does not belong to this page. 14 routes, 0 entries. Since this was filed, `CLAUDE.md`'s pointer has been rewritten again to name the guard test rather than "§6 only", so the instruction surface now points at something that cannot silently rot. The canonical block was re-checked as byte-identical with the wiki template afterwards — the edit was in the project addendum, outside the block. The pattern ticket this belonged to, #113, is now closed too: all three of its instances are fixed.
ltms closed this issue 2026-09-03 08:39:39 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#114