Do not advertise capture tools a runtime cannot serve - #641
Conversation
CommitLore — record lintTrailers: clean — 4 commits in Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
Holding this — the fix does not cover the case that produced the evidenceAn adversarial review raised it and I reproduced it live.
const captureAssets = preflightCaptureAssets(); // src/mcp/server.ts:508
const captureReady = captureAssets.ready;
const advertisedTools = captureReady ? TOOLS : TOOLS.filter(…); // computed once
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [...advertisedTools] }));
…
if (!captureReady && [PREPARE, VERIFY, STAGE].includes(request.params.name)) throw …
MeasuredServer started with assets present, Three capture tools advertised before the deletion, three after. Why this matters more than a general edge caseThis is exactly the state the finding was written from. Measured on one machine: Those processes started while their installation existed and kept serving after it was deleted underneath them. A startup snapshot cannot see that, so the runtimes that produced #633 and #635 would still advertise capture and still fail on the call. Also raised, still to check
What is not in questionThe startup gating itself is right and the RED was real: on unmodified |
The gap is closed — measured the same way it was foundThe blind review's finding was that readiness was a startup snapshot, so a server kept advertising capture after its assets were deleted underneath it. Reproduced then, reproduced now against the fix, same script:
Three capture tools advertised before the deletion and three after; now three and none. That is the state the finding came from. On one machine, four runtimes were answering as MCP servers and two of them — Completion condition, stronger than the review document'sThe document asks for a startup preflight. That alone would have left the measurement above at
Also addressed from the same review
Verification
Runtime identity remains F-001's (#644); this PR names the runtime from its own entrypoint and package root and should read from the shared identity once that lands. |
Advertising a capture tool promises that the running installation has the assets needed to execute it, not merely the handler code. Limit: MCP capture advertisement requires package manifest, SPEC, and schema to be available in the active runtime Ruled-out: code-only tool advertisement | a stale runtime can expose capture after its SPEC is gone Blast: module Undo: easy Certainty: firm Record-Id: r-preflight002 Provenance: authored Verified: npx tsc --noEmit; npx vitest run test/mcp-preflight.test.ts; npx vitest run test/mcp.test.ts test/mcp-capture.test.ts test/doctor.test.ts; docker linux/amd64 MCP suites (106 tests); docker linux/amd64 npm ci && npm run build && npm run artifact:manifest; npm run artifact:verify; find dist -type f | wc -l = 274 CommitLore-Version: 2.0.0
A readiness answer has a shelf life: the runtimes behind this finding outlived their installations. Recheck the required assets at the MCP request boundary so capture is withdrawn and calls receive a repair before they start work. Limit: capture readiness can change after MCP server startup Ruled-out: a daemon, watcher process, hosted service, or new database | a filesystem check at the boundary is enough Blast: module Undo: easy Certainty: firm Record-Id: r-recheck002 Provenance: authored Verified: npx tsc --noEmit; npx vitest run test/mcp-preflight.test.ts test/mcp.test.ts test/mcp-capture.test.ts; Node 24 linux MCP suites with linked-worktree Git metadata mounted; docker linux/amd64 npm ci && npm run build && npm run artifact:manifest; npm ci; npm run artifact:verify; find dist -type f | wc -l = 274; live stdio spec removal [3,0]; negative control [3,3] CommitLore-Version: 2.0.0
Provenance: authored Record-Id: r-canonf002b Verified: docker linux/amd64 build and artifact:manifest; artifact:verify reports fe3613c6; 274 files in dist
Rebasing onto main put this branch's own deferral to the test. It said F-001 owns runtime convergence and that reading the package version here was only what an operator could act on until that work landed. It has landed, so the version comes from runtimeIdentity() and the local reader is gone. The message stays this branch's: a failure reports the fallback without the former absolute path of a deleted installation, which the identity error text would have carried. Convergence is F-001's; not leaking a dead path is F-002's. Blast: module Undo: easy Certainty: firm Ruled-out: keep the local packageVersion reader | it is a second answer to a question F-001 now owns, which is the divergence this pair of findings exists to remove Provenance: authored Record-Id: r-f002onf001 Verified: tsc --noEmit; 115 cases across mcp-preflight, mcp, mcp-capture and runtime-identity; live tools/list before and after deleting spec/ reports [3, 0]; docker linux/amd64 build and artifact:manifest; artifact:verify 33a26f61; 280 files in dist
eaf0466 to
b51c6b4
Compare
Summary
Closes F-002 (P0). Addresses the asset-readiness half of #633 and #635.
The MCP server advertised capture tools in
tools/listwhile the assets those tools need were noton disk. A user saw the capability, called it, and the call failed. Startup now checks the package
manifest,
SPEC, and the schema, and a server that cannot serve capture does not offer capture.Measured evidence this closes
Four commitlore runtimes were serving MCP on one machine at the same moment:
Two of those roots no longer exist:
Those processes keep answering after their installation was deleted underneath them. Reproduce with
ps -axo args= | grep -i "commitlore.*mcp".RED, verified independently against unmodified main
The three regressions were run on
f474cf43with only the new test file added:A correction to the finding's wording
The review describes the failure as a raw
ENOENT. On currentmainit is no longer literallythat: the base leaks the missing asset's absolute path with a generic reinstall line. The
defect is the same — the message names a path instead of the runtime and what to do about it — but
the finding's wording is one release out of date and this PR does not repeat it.
What is out of scope, deliberately
Runtime identity convergence is F-001's, and a parallel change owns it. Rather than add a second
identity mechanism, this uses the current entrypoint path and package root directly. When F-001
lands, these messages should read from the shared identity instead.
Test plan
mcp-preflight,mcpandmcp-capturelinux/amd64containernpx tsc --noEmitguard fails the actionable-message case; removing the startup warning fails the degraded
diagnostic case — each restored and green
npm run artifact:verify