Skip to content

feat(server): contextmd serving module serves stored rules through harness hooks - #220

Merged
lukemaj merged 7 commits into
mainfrom
feat/217-contextmd-serving
Oct 12, 2026
Merged

lukemaj merged 7 commits into
mainfrom
feat/217-contextmd-serving

Conversation

@lukemaj

@lukemaj lukemaj commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What changed: A fork-owned contextmd module in the server serves ContextMD's stored rules to agents in Chromeria threads: thin harness hooks post each moment to POST /contextmd/v1/moment, and the module matches it, resolves the running run, decides what to serve or deny, and logs it.
Why: Agents act without rules that already exist; serving state has to live where the turn is known, and the probe showed a Chromeria endpoint is faster than a file-based hook and joins every moment to its thread and run.
So what: Independent review, then merge under tonight's authority; then the planner installs the hooks and runs a live check. Closes #217.

How it works

  • Route (ContextmdRoute.ts): authenticates with Chromeria's own EnvironmentAuth (an ordinary bearer session with orchestration:read, issued by t3 auth session issue at install) and calls the service.
  • Selection (Select.ts, Matcher.ts, Store.ts, Reads.ts, Harnesses.ts): a TypeScript port of ContextMD PR feat(web): configure Prism role preferences and view capacity #23's matcher. Entries stay Markdown data files in ContextMD's stores, named by ~/.config/contextmd/serving.json (or CONTEXTMD_SERVING_CONFIG) and read on every moment, so knowledge edits need no release. Harness profiles hold the canary-proven seams per harness.
  • State (ServingState.ts): its own <stateDir>/contextmd.sqlite, synchronous short transactions, busy_timeout 100. Per-run 8,000 and 2,000-byte budgets; reserve, then settle; unsettled reservations release after 2 s as delivery_unverified; repeated delivery ids replay. Must-know denies: pointer rules lift on an observed successful read of the target, inline rules on a proven whole delivery, cap of 3, re-armed by compaction. Grok carries. One log row per moment and per candidate, no prompt, command or result text.
  • Deny evidence (obedience finding 1): on Claude Code and Grok the projected turn item of a denied call holds exactly what the model saw. A deny whose rule text is not there (another hook's reason won) is logged hidden, stays armed and does not count toward the cap.
  • Footprint: two lines in server.ts; one partial index on the run-attempts table, created at runtime after activation; a startup self-check disables the module (unmapped) if the attempt columns or nativeThreadId disappear.

Proof (head of this PR)

  • vp test run apps/server/src/contextmd/: 64 passed at head 4ec3128b8b (state core: budgets, deny composition, lifts, cap, parallel waves, killed-hook release, lock holder, epochs, carries, evidence; route: run join across a steering restart, unjoined/ambiguous, auth, oversize, replay, bad event/config, store reload, self-check; matcher: PR feat(web): configure Prism role preferences and view capacity #23's segment and store cases, per-harness selection on captured event shapes, printed exits, reads).
  • tsc --noEmit (no errors in the module), vp lint, vp fmt --check, knip --exports for apps/server: clean for the changed scope. Repo-wide checks are CI's.
  • ContextMD's contract suite (toolboxmd/contextmd PR, tests/test_contract.py, PR feat(web): configure Prism role preferences and view capacity #23's hook-seam tests ported): 17/17 through the real hook against a dev server built from this head.
  • Live canary in a dev instance (own base dir and port; the running app and ~/.t3 untouched), every served token quoted before the next action:
Harness Pack Tool result Failure (cmd; echo "exit=$?") Pointer deny, lifted by a read Inline deny Two hooks deny one call
Claude Code (haiku-4-5) yes yes yes yes yes other hook won, rule logged hidden, re-denied, then shown
Codex (gpt-6-astra) no seam yes yes yes (cat) yes n/a
Grok Build no seam yes (carry) yes (carry) yes yes n/a
OpenCode (muse-spark-1.3) no seam yes yes yes yes n/a
  • OpenCode obedience rerun (finding 3): the commit scenario 6/6 complied with rule text in the user message, against 4/6 in the obedience test; no distrust language in any transcript (small n; Chromeria instead of headless CLI).

  • Latency: 3 ms server time per moment sequentially; under a 48-call wave 110 ms p95 server, 188 ms p95 hook wall time (hook deadline 1 s; Grok fails open at 5 s).

  • Independent review: six rounds by Codex gpt-6.1-sol (findings and verdicts in the comments below); round 6 PASS with two non-blocking notes (readers called by path such as /bin/cat; an abandoned OpenCode reply holds budget for up to 120 s), tracked as follow-ups.

  • Review-driven changes since the first head: serving disables itself instead of failing startup; denies count toward the cap only once shown; deny evidence reads the projected item's result field, by exact native item id, only once finished; overdue evidence ages out as unverified; the body is capped while read; concurrent same-id deliveries share one reply; late settles are refused; Codex reads count only after the projected command exited 0.

Limits

  • The Cue (router) client, its health flag and holdout suppression wait for Serving: router lane live with holdout, outage handling and rollback contextmd#13; the holdout bit is logged now.
  • Codex leaves no projected item for a denied call, so its deny delivery rests on settlement.
  • On Claude Code, cat RULE.md; echo "exit=$?" with a failing cat counts as a read, because the harness reports the call as successful.
  • Session-start packs are served only on Claude Code (no proven seam elsewhere).

Elon record

  • Requirements and who asked: serving state in Chromeria, no serving outside it, pointer lifts on read, inline lifts on whole delivery, cap 3, parallel calls denied, compaction re-arms, matcher in TypeScript in the module, own SQLite file, auth through Chromeria's session system, fail-open when the server is down (user, 2026-10-12); the three obedience fixes (user-approved); PR feat(web): configure Prism role preferences and view capacity #23's budgets and rules.
  • Deleted: the Python selection worker and its process supervision, a custom endpoint token file, host renderers in the fork, the failure spool, the outside-session turn key, the projection compaction item dependency (harness hook events cover all four), the router client until test(brand): expect the Chromeria web icons in production builds #13.
  • Bottleneck: live delivery through each harness's seams, now proven per harness.
  • Checked myself: live canaries on all four harnesses with the final code, the forced two-hook collision, the 48-call wave, the contract suite against this head, the projected deny items in the dev database.

Built by Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

lukemaj and others added 2 commits October 12, 2026 23:28
…rness hooks

Thin Claude Code, Codex, Grok Build and OpenCode hooks post each moment to
POST /contextmd/v1/moment. The module matches it against ContextMD's stores
(a TypeScript port of PR #23's matcher), resolves the running Chromeria run,
decides what to serve or deny within the per-run 8,000 and 2,000-byte
budgets, and reserves it until the hook settles. Must-know denies lift on an
observed read (pointer rules) or a proven whole delivery (inline rules), cap
at 3, and re-arm after compaction; a deny another hook's reason replaced
stays armed. State and the serving log live in <stateDir>/contextmd.sqlite.
Hooks authenticate as an ordinary Chromeria session with orchestration:read.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 4.9 KiB −23 B (−0.5%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB −23 B (−1.9%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.9 KiB 20.8 KiB −41 B (−0.2%) 29.3 KiB ✅
Codex Live turn messages 2 1 −1 (−50.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB −24 B (−0.5%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB −24 B (−2.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.2 KiB −41 B (−0.2%) 29.3 KiB ✅
Claude Live turn messages 2 1 −1 (−50.0%) 8 ✅

Baseline: 8aec64b · PR result: 4ec3128 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@lukemaj

lukemaj commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Independent review (Codex gpt-6.1-sol, round 1) of a1ea6e2ae17161efcf3159ac2cbf558eafc76caa: FAIL. Relayed unchanged by the implementing agent (the reviewer ran in plan mode and could not post).

All 50 scoped tests passed, but the following blocking defects remain.

  1. P1, blocking: serving initialization can prevent Chromeria from starting. apps/server/src/contextmd/ContextmdServing.ts:84–86 Directory creation, database opening, and schema initialization are unconditional dependencies of the application's route layer. An unwritable, corrupt, or incompatible contextmd.sqlite therefore fails Chromeria startup rather than disabling serving. Catch acquisition failures through an explicit disabled-service path, close partially acquired resources, and return 204. Add startup-failure coverage.
  2. P1, blocking: undelivered denies consume the cap and bypass the gate. apps/server/src/contextmd/ServingState.ts:964–977 The counter increments on reservation; release deletes claims and refunds bytes but leaves that counter unchanged. An in-memory reproduction produced [200, 200, 200, 204] for four unsettled parallel calls, with not_read_capped still lifted after release. Count only settled, proven-shown denies; outstanding reservations must keep parallel calls denied. Test a wave larger than three and three successive dropped deliveries.
  3. P1, blocking: tool input can falsely prove that a deny was shown. apps/server/src/contextmd/ServingState.ts:417 Evidence checks search the entire serialized projection for at most 60 escaped characters. A denied command containing the rule text passes this check even when its output contains only another hook's reason. Reproduced. Decode the projected item, inspect the actual completed output, and verify the whole delivered rule. Test rule text appearing only in input and rules sharing a prefix.
  4. P2, blocking: chunked request bodies are unbounded while being read. apps/server/src/contextmd/ContextmdRoute.ts:49 Without Content-Length, request.text buffers the entire body before the service checks its byte length. Apply the 1 MB limit during consumption and test a chunked oversized request.
  5. P2, blocking: concurrent delivery replays return different replies. apps/server/src/contextmd/ContextmdServing.ts:174–175 Two requests with the same delivery ID can both pass the replay check before asynchronous selection finishes; the second hits UNIQUE constraint failed: deliveries.delivery and becomes 204. Recheck and return the stored reply inside the decision transaction. Add concurrent same-ID HTTP coverage.
  6. P2, blocking: concurrent evidence checks refund a hidden deny twice. apps/server/src/contextmd/ServingState.ts:405–435 applyChecks never verifies that the claim remains unchecked, so both results decrement the counter. Make evidence adjudication an atomic, conditional transition.
  7. P2, blocking: settlement accepts expired reservations. apps/server/src/contextmd/ServingState.ts:321 Settlement checks stage but not age; reproduced successful settlement at ten seconds without a sweep. Enforce expiry transactionally in settle, refund expired reservations, and test late settlement without another moment or heartbeat.

- A state file that cannot be opened disables serving instead of failing
  Chromeria startup.
- A deny counts toward the cap only once shown (on settle, or on projection
  evidence); outstanding and dropped deliveries never spend it.
- Projection evidence reads only the denied call's output and needs the whole
  delivered rule; each deny is adjudicated once.
- The request body is capped while it is read, chunked bodies included.
- A delivery id decided while another request was selecting gets the same
  reply.
- A settle after the harness's deadline releases the reservation; OpenCode,
  which settles when the model reads the text, gets 120 s.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Independent review (Codex gpt-6.1-sol, round 2) of 90f760ea3680bb1db20e40644b8b0a1da641ac93: FAIL. Relayed unchanged by the implementing agent (the reviewer ran in plan mode).

All 58 scoped tests passed. The startup, bounded-body, concurrent-replay, atomic-adjudication, and late-settlement fixes address their original failure mechanisms. Epoch-specific claim updates prevent late evidence from lifting a deny in a newer compaction epoch.

  1. P1, blocking: a failed Codex read lifts the pointer gate. ServingState.ts:618 records reads when failed is null. Codex leaves it null unless output matches the printed-exit regex. Reproduced with cat <existing-target> returning Permission denied: the pointer became lifted=1, lift_outcome='read'. Fix: establish successful completion before recording a read. Add failed-read coverage using ordinary failure output.
  2. P1, blocking: Claude edit denies cannot prove delivery. ContextmdServing.ts:94 reads only output, but Claude's adapter stores Edit/Write results in diffStr (ClaudeAdapterV2.ts:3949). A must-know inline rule matching an edited path is adjudicated hidden even when its denial reached the model; it never lifts or counts toward the cap. Fix: decode the actual result field by projected item type, excluding input. Test an inline path rule against a denied Edit/Write call.
  3. P2, blocking: unfinished projections receive permanent verdicts. ContextmdServing.ts:197 queries without checking completion status. An empty output of a running item becomes permanently hidden. Fix: treat nonterminal items as pending; add a running-to-completed projection test.
  4. P2, blocking: missing evidence can evade the safety cap indefinitely. ServingState.ts:355 repeatedly selects only the newest eight unchecked claims; reproduced 80 claims unchecked after 395 s. Claims without tool_use_id never expire. Fix: age out overdue claims independently of the bounded lookup, including claims without lookup ids.
  5. P2, blocking: unverified denies remain logged as injected. ServingState.ts:384 leaves the candidate outcome injected. Fix: keep a pending-evidence outcome until adjudication, then log shown, hidden or delivery-unverified.
  6. P3, non-blocking: dead OpenCode reservations retain budget for 120 seconds. Harnesses.ts:177. Optional: cancel reservations when the plugin drops queued replies.

- Deny evidence reads each projected item type's result field (output for
  commands and tools, diffStr for file edits) and only once the item is
  finished; an unfinished item leaves the deny pending.
- Settled denies awaiting evidence age out after 60 s on their own, including
  ones without a tool-use id, as `unverified` (counted toward the cap); the
  log shows `awaiting_evidence` until a verdict instead of `injected`.
- Without a failure signal (Codex) a read counts only when the output shows
  the file's first non-empty line.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Independent review (Codex gpt-6.1-sol, round 3) of 0f6c25a8034522fa25fe42bd9660a325b3d7d2f4: FAIL. Relayed unchanged by the implementing agent (the reviewer ran in plan mode).

All 60 scoped tests pass. Fixes 2–5 address their reported mechanisms; fix 1 remains incomplete and introduces a regression.

  1. P1, blocking: a failed Codex read still lifts the pointer gate. apps/server/src/contextmd/Select.ts:148 accepts a shared first line from anywhere in the combined output. Reproduced with cat AGENTS.md README.md: output reports AGENTS.md: Permission denied and contains README's text. Both files start with # T3 Code, so selection records both as read and the AGENTS pointer becomes lifted=1, lift_outcome='read'. Fix: establish success for each target individually, reject target-specific read errors, and avoid treating a common heading as proof. Add coverage for failed and successful files sharing their first line.
  2. P2, blocking: successful partial Codex reads no longer lift pointer rules. apps/server/src/contextmd/Select.ts:221 requires the first non-empty line even for supported partial readers. Reproduced with successful sed -n '3,5p' AGENTS.md: reads=[], the pointer remains armed. Fix: recognize successful returned ranges or search results without requiring the file's first line. Add partial-read coverage.
  3. P3, non-blocking, unchanged from round 2: dropped OpenCode reservations retain budget for up to 120 seconds. Harnesses.ts:177. Optional: cancel the reservation when the plugin discards a queued reply.

A Codex read now counts per file: no output line may report an error for that
file, and the output must hold one of the file's own lines (at least 12
characters, not shared with the other files the command named). Shared
headings and other files' text no longer prove a read; partial reads
(`sed -n`, `head`, `grep -n`) qualify through the lines they print.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Independent review (Codex gpt-6.1-sol, round 4) of 63abe2be1634d19e5987fc5b255f8359117e5128: FAIL. Relayed unchanged by the implementing agent (the reviewer ran in plan mode).

All 60 scoped tests pass. The reported mixed-file, sed range and grep -n cases are fixed, but successful-read regressions remain.

  1. P2, blocking: valid Codex reads still fail the evidence filter. Select.ts:149, :176, :181. Reproduced successful reads of a short file (# Rules\nRun tests.\n), two identical files, and nl -ba /RULE.md. Each returns reads=[]; the pointer stays armed and the retry is denied. Fix: recognize complete single-file output regardless of line length, use file-qualified evidence for ambiguous multi-file output, and normalize numbered-reader output; keep failed-file rejection.
  2. P2, blocking: ordinary file content is mistaken for a read error. Select.ts:179. Reading a file containing If RULE.md is not found, stop and ask the user. rejects the read. Fix: identify reader diagnostics tied to the target path instead of matching arbitrary content.
  3. P3, non-blocking, unchanged: dropped OpenCode reservations retain budget for up to 120 seconds. Harnesses.ts:177.

No additional blocking findings in the rest of the module.

…exit code

Replaces the output-matching heuristic from rounds 2 and 3 (review round 4).
Codex tells its hook nothing about failure, but the projection records each
command's status and exit code. A read without a failure signal is now held
pending and counts, lifting its pointer rules, only once the projected
command item finished with exit code 0; a failed call discards it, and an
unproven one is dropped after 60 s. Selection only names the files.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Independent review (Codex gpt-6.1-sol, round 5) of 62450daa309a3c360d61742a6ffff18a70565cdb: FAIL. Relayed unchanged by the implementing agent (the reviewer ran in plan mode).

All 62 scoped tests pass. Read-only checks confirmed multi-file confirmation, original-epoch isolation after compaction, expiry of unproven reads after 60 seconds, and immediate lifting for successful Claude, Grok and OpenCode reads.

  1. P2, blocking: the projection lookup can confirm the wrong command. ContextmdServing.ts:247 The suffix LIKE match is not an exact tool-use identity and treats % and _ as wildcards. Reproduced with a successful prior-exec-1 item preceding a failed exec-1. Fix: match the exact item identity; apply the same correction to deny evidence at line 221; add collision coverage.
  2. P2, blocking: a missing tool-use id turns unknown success into a proven read. ServingState.ts:699 With failed === null and no tool-use id, pending becomes null, which also means confirmed. Fix: discard or retain uncheckable reads without letting them count; add missing-id coverage.
  3. P2, blocking: an absent projected exit code is accepted as zero. ContextmdServing.ts:252 Fix: require an explicit numeric zero; test completed items with null and missing exit codes.
  4. P2, blocking: ordinary file content still rejects successful reads. Select.ts:188 The retained EXIT_PRINTED heuristic marks any output containing exit=1 as failure, so a successful cat of a file mentioning exit=1 never counts, on every harness. Fix: separate output-based failure routing from read-success evidence; add cross-host coverage.
  5. P3, non-blocking, unchanged: dropped OpenCode reservations retain budget for up to 120 seconds. Harnesses.ts:177.

- Projected items are found by their exact native item id
  (`nativeItemRef.nativeId`, equal to the hook's tool-use id on every
  harness) for both deny and read evidence, never by a suffix pattern.
- A read is proven only by a finished command item with an explicit exit
  code 0; a missing exit code proves nothing.
- A read without a failure signal and without a tool-use id can never be
  checked and does not count.
- A printed non-zero exit routes error keys and the failure seam, but no
  longer decides whether files were read: that stays the harness's report.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Independent review (Codex gpt-6.1-sol, round 6) of 4ec3128b8b6503ccf93d47c749d60c532131635b: PASS (no blocking findings). Relayed unchanged by the implementing agent (the reviewer ran in plan mode).

All 64 scoped tests pass. The four round-5 blocking findings are cleared; an additional cross-host check confirmed that printed exit=1 preserves harness read evidence and leaves Codex reads pending.

  1. P3, non-blocking: readers invoked by executable path are missed. apps/server/src/contextmd/Reads.ts:35 cat <target> records a read, while /bin/cat <target> and /usr/bin/head -n 5 <target> return reads=[], leaving the pointer gate armed. Fix: recognize reader executable basenames with path prefixes.
  2. P3, non-blocking, unchanged: dropped OpenCode reservations retain budget for up to 120 seconds. apps/server/src/contextmd/Harnesses.ts:177 Optional fix: cancel the reservation when the plugin drops the reply.

The stated Claude masked-exit trade-off remains a limitation: successful completion of the whole shell call does not prove every reader inside it succeeded. Under the accepted call-level read heuristic, not treated as a new blocker.

@lukemaj
lukemaj merged commit 75ab2f5 into main Oct 12, 2026
42 of 45 checks passed
@lukemaj
lukemaj deleted the feat/217-contextmd-serving branch October 12, 2026 22:50
lukemaj added a commit that referenced this pull request Oct 14, 2026
…rness hooks (#220)

* feat(server): contextmd serving module serves stored rules through harness hooks

Thin Claude Code, Codex, Grok Build and OpenCode hooks post each moment to
POST /contextmd/v1/moment. The module matches it against ContextMD's stores
(a TypeScript port of PR #23's matcher), resolves the running Chromeria run,
decides what to serve or deny within the per-run 8,000 and 2,000-byte
budgets, and reserves it until the hook settles. Must-know denies lift on an
observed read (pointer rules) or a proven whole delivery (inline rules), cap
at 3, and re-arm after compaction; a deny another hook's reason replaced
stays armed. State and the serving log live in <stateDir>/contextmd.sqlite.
Hooks authenticate as an ordinary Chromeria session with orchestration:read.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(fork): register the contextmd serving feature

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): contextmd serving review round 1 fixes

- A state file that cannot be opened disables serving instead of failing
  Chromeria startup.
- A deny counts toward the cap only once shown (on settle, or on projection
  evidence); outstanding and dropped deliveries never spend it.
- Projection evidence reads only the denied call's output and needs the whole
  delivered rule; each deny is adjudicated once.
- The request body is capped while it is read, chunked bodies included.
- A delivery id decided while another request was selecting gets the same
  reply.
- A settle after the harness's deadline releases the reservation; OpenCode,
  which settles when the model reads the text, gets 120 s.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): contextmd serving review round 2 fixes

- Deny evidence reads each projected item type's result field (output for
  commands and tools, diffStr for file edits) and only once the item is
  finished; an unfinished item leaves the deny pending.
- Settled denies awaiting evidence age out after 60 s on their own, including
  ones without a tool-use id, as `unverified` (counted toward the cap); the
  log shows `awaiting_evidence` until a verdict instead of `injected`.
- Without a failure signal (Codex) a read counts only when the output shows
  the file's first non-empty line.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): contextmd serving review round 3 fixes

A Codex read now counts per file: no output line may report an error for that
file, and the output must hold one of the file's own lines (at least 12
characters, not shared with the other files the command named). Shared
headings and other files' text no longer prove a read; partial reads
(`sed -n`, `head`, `grep -n`) qualify through the lines they print.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): contextmd serving confirms Codex reads by the projected exit code

Replaces the output-matching heuristic from rounds 2 and 3 (review round 4).
Codex tells its hook nothing about failure, but the projection records each
command's status and exit code. A read without a failure signal is now held
pending and counts, lifting its pointer rules, only once the projected
command item finished with exit code 0; a failed call discards it, and an
unproven one is dropped after 60 s. Selection only names the files.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): contextmd serving review round 5 fixes

- Projected items are found by their exact native item id
  (`nativeItemRef.nativeId`, equal to the hook's tool-use id on every
  harness) for both deny and read evidence, never by a suffix pattern.
- A read is proven only by a finished command item with an explicit exit
  code 0; a missing exit code proves nothing.
- A read without a failure signal and without a tool-use id can never be
  checked and does not count.
- A printed non-zero exit routes error keys and the failure seam, but no
  longer decides whether files were read: that stays the harness's report.

Refs #217

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

contextmd serving module: serving state, settlement and deny lifts in the Chromeria server

1 participant