Repository navigation
feat: record D1–D5 and the D1 live-check findings in the E01 contract - #417
Conversation
D1 conditional yes for claude-code/2.1.263 + codecarto MCP, registry empty until the live check; operator attests no Elicitation hooks and the adapter cannot verify it (receipt disclosure). D2 deferred. D3 boundary accepted (E5/E6 configuration), continuity not provided, SessionStart marker an open E02 investigation. D4 accepted with the full E5/E6 requirement list, re-run wherever the real entry lives. D5 no. Consequence stated: observed proofs, cooperative acceptances; E06 surfaces the classification rather than refusing. Three forward notes: record parsing scoped to the inbox, retention for hook writes, per-host O_EXCL proof. No code change; registry empty; #399 stays open. Refs #399 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nly, timeout outcome The maintainer ran the D1 live interactive check on claude-code 2.1.277 (host auto-updated from 2.1.263 three minutes earlier); it passed with three findings, each now in the contract with tests and fixtures: - Version drift. VerifiedAcceptanceIntegration.client_version is exact; acceptanceChannelSupported refuses a live clientInfo.version that differs (or is absent) and names both versions; classifyAcceptance re-derives against the exact version; receipt.client.version is required. Test: registered 2.1.263, live 2.1.277 → unsupported. - Timeout is a third outcome. ElicitationOutcome adds timed-out (distinct from rejected/declined/cancelled); AcceptanceRequestOutcome carries declined and timed-out, neither minting a record; client_request_timeout_ms on the registry entry and acceptanceTtlWithin require the adapter's TTL to stay below it. - Action and decision can disagree — observed live (action accept, content.decision reject). elicitationDecision derives the outcome only from content.decision; action alone never authorizes; missing, empty, or unrecognized decisions are invalid. Ten fixtures under valid/elicitation/, the first three verbatim from the log; a mutation that trusts `action` fails the suite. D1 is recorded as YES for claude-code/2.1.277 + the codecarto MCP server, conditional on those three items plus the operator attestation on Elicitation hooks; the registry stays empty until the entry is added by amendment. E5 and E6b were re-run on 2.1.277 with identical results and the spike report says which binary each observation is from. Refs #399 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Revision: D1 live-check findings folded in (
|
| Finding | Contract change | Test / fixture |
|---|---|---|
| Version drift — a version-pinned registry entry goes stale silently | VerifiedAcceptanceIntegration.client_version (exact); acceptanceChannelSupported refuses a live clientInfo.version that differs or is absent, naming both; classifyAcceptance re-derives on the exact version; receipt.client.version required |
registered 2.1.263 vs live 2.1.277 → unsupported; stale-version receipt → cooperative |
Timeout is a third outcome — -32001 after ~2.5 min is not a decline |
ElicitationOutcome.timed-out; AcceptanceRequestOutcome.outcome gains declined and timed-out (neither mints); client_request_timeout_ms + acceptanceTtlWithin require the adapter's TTL below the client's observed timeout |
TTL 120 s within 150 s; 150 s and unmeasured → not within |
Action and decision disagree — observed (action: accept, content.decision: reject) |
elicitationDecision derives the outcome only from content.decision; action never authorizes; missing/empty/unrecognized decision → invalid |
10 fixtures under valid/elicitation/ (the three live shapes verbatim); mutation that trusts action fails the suite |
D1 is recorded as YES for claude-code/2.1.277 + the codecarto MCP server, conditional on those three items plus the operator attestation that no Elicitation/ElicitationResult hooks are configured (adapter cannot verify; receipt disclosure). The registry stays empty; the entry is added by amendment with the live-check evidence, now that the three items are implemented and tested. D2–D5 unchanged.
Also: E5 and E6b re-run on 2.1.277 with identical results; the spike report states which binary each observation came from.
npm run build exit 0; npm test 972/972; git diff --check clean; no private paths.
Two trust rules in the E01 contract were expressed as helpers an adapter
was trusted to call, so neither bound anything:
- acceptanceTtlWithin was never called from core/. The shipped fixture
paired a 3600 s window with the 150 s client timeout it is supposed to
fit inside and still classified verified. checkAcceptanceRequestTtl is
now the enforcement form and classifyAcceptance calls it, against the
registry entry the receipt resolves to; an unmeasured
client_request_timeout_ms fails closed, because an unmeasured client
bounds nothing.
- buildAcceptanceRequest composes an honest presentation, but the digest
binding only proves the person saw *a* presentation. A request minted by
any other path could show an empty limitations list over a caller-reported
proof or an unprotected namespace, validate byte-for-byte, bind a receipt,
and read verified. checkPresentationDisclosure re-derives the required
disclosure from the records the acceptance binds to; classifyAcceptance
refuses verified when a line was withheld or the presentation named a
different policy than the acceptance is read under.
Also: a thrown elicitation response is timed-out only on the structured
JSON-RPC code, not on error prose that merely contains the words —
{ threw: "accept: request timed out" } is not evidence of a timeout.
Fixtures: the valid acceptance request is now a 120 s window answered at
60 s, so the suite's own approval path satisfies the rule it asserts; new
negative fixture acceptance-request-ttl-outlives-client-timeout covers a
window inside the 24 h cap but longer than the client's timeout.
Mutation checks: disabling the TTL enforcement turns 1 test red; disabling
the disclosure enforcement turns 2 red.
Verified: 25/25 targeted, 973/973 full suite, build exit 0, diff --check
clean.
An independent review of the previous commit found that the disclosure fix had replaced "the adapter is trusted to call the helper" with "the reader is trusted to pass the records" — the same defect one level down, and this one failed open: omitting context.proofs/reviews defaulted them to [], so no proof line was ever required and the exact attack the commit claimed to close returned verified with no reasons. - proofs and reviews are now required for a verified reading. A reader that omits them gets cooperative with an explicit reason, because a degradation must lower trust, never raise it. Pass [] to assert there are none. - A host/client/version/channel tuple registered more than once let array order pick the timeout: [generous, tight] read a 60-minute window as verified while [tight] alone read cooperative, and [tight, unmeasured] vs [unmeasured, tight] disagreed on identical registry content. Duplicates are now named as a reason and the TTL binds against the strictest matching entry, so an unmeasured entry cannot be sidestepped by a measured sibling. - client_request_timeout_ms was only checked !== undefined, so "999999999", Infinity, and [999999999] each coerced past the comparison and read verified. It must now be a finite positive number. Also documented, not fixed: the disclosure check compares lines by exact equality, so a presentation carrying every required line plus contradicting text passes. The validator cannot tell clarification from contradiction; reviewing presentation copy is an operator responsibility. Mutation checks, each turning the suite red: defaulting proofs/reviews to [] (1 failure), taking matches[0] instead of the strictest entry (1), reducing the timeout guard to an undefined check (1). Verified: 25/25 targeted, 973/973 full suite, build exit 0, diff --check clean.
Records the maintainer's 2026-09-18 decisions D1–D5 in
docs/engineering/record-contract.md§ Decision record, after the Claude Code 2.1.263 spike (#416). Documentation only; no code change;VERIFIED_ACCEPTANCE_INTEGRATIONSstays empty;cooperativestays unauthorized; #399 stays open pending the D1 live check; E02/E03 blocked; E09 on hold.claude-code/2.1.263+ the codecarto MCP server, session-attested; registry empty until the maintainer's live check; new requirement that the operator attests noElicitation/ElicitationResulthooks, which the adapter cannot verify and the receipt must disclose.SessionStartmarker recorded as an open E02 investigation.bypassPermissions+ the full E5/E6 list; re-run E5/E6 wherever the real ingestion entry is placed.cooperative; E06 surfaces the classification rather than refusing..err/.status); E05 needs retention for hook writes; theset -Cproof is per-host (bashhere,dashon Debian-family) and must be re-proved on the pilot host.npm test971/971;git diff --checkclean.Refs #399, #398
🤖 Generated with Claude Code