Repository navigation
Build 4/7 — Delivery receipts and settlement: inbox receipt, Codex verified, UNKNOWN never resends #77
Description
Part of #67 · Build group 4 of 7 from the signed port table (amended 2026-08-25: P14, P15 added to this group). Revised after the Codex consistency review.
Rows covered
P2 transitions (simplify) · P7 Claude Answer receipt (adapt: route and receipt both change) · P8 Codex Answer receipt (port already present; verify) · P9 settlement and duplicate safety (port the rule only; one policy edit) · P14 approval outcome wording (adapt to #61 R5) · P15 Companion reply outcome (port the rule, simplify storage; #61 C2).
Legacy citations (verbatim from the table)
- P2:
legacy@1d32845:bridge/store.py:1644-1666,1738-1755,2638-2671,2798-2831. - P7:
legacy@1d32845:bridge/claude.py:1-21,329-393— evidence for the superseded channel route, not an implementation to retain. - P8:
legacy@1d32845:bridge/codex.py:1264-1317,1529-1575— exact-IDclientUserMessageIdreadback; v1 already has it (adapters/agent/codex/adapter.py:340-393,426-461,696-735). - P9:
legacy@1d32845:bridge/delivery.py:28-75;bridge/coordinator.py:1075-1109;bridge/store.py:964-1035,3394-3555,3653-3874— prove the invariant only. - P14:
legacy@1d32845:bridge/transcript.py:1633-1713;bridge/daemon.py:1901-2052— legacy had no approval transport, so it never claimed a verdict arrived. - P15:
legacy@1d32845:bridge/channel.py:11-13,75-86;bridge/store.py:1517-1614;bridge/daemon.py:830-879.
Shape (binding constraint: P9 is tens of lines in core/relays.py, no new module)
- P2 transitions (
core/sessions.py, ≤ 30 lines):reply_windowis derived from the inspection (Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74) — this ticket adds the transitions: aSessionStoppedwithwaiting_for.kind ∈ {question, permission}→OPENifreach.relay == attached; a new turn observed (state=running) →CLOSED;SessionEnded→CLOSEDand pending Relays for that target are settledFAILEDwith "Session ended". No stored window, no supersession ledger. - P9 (
core/relays.py:115-181, ≤ 40 lines):DELIVEREDcompletes; provenFAILEDmay retry under the existing current-Engine policy (ten-minute bound, Parity audit: legacy as the behaviour spec — the gap table that scopes the repair #61 R2);UNKNOWNis never auto-resent — retained as duplicate-risk information, surfaced with the warning, a second attempt needs explicit user authority. No coordinator, no state machine, no persistence. - P14 (
core/approvals.py:167-208, ≤ 20 lines): only aDELIVEREDreceipt may announce "approved/denied";FAILED/UNKNOWNsays "not confirmed" and points the user to the original dialog. - P15 (
core/bridge.py:671-680, ≤ 20 lines): the direct Companion reply reports the Telegram adapter's receipt grade and reason; no resend, no persistence; token and full message never logged. - P7 — Claude route (
adapters/agent/claude/adapter.py, ≤ 200 lines net after the channel stack is removed):answer_relaytargets the inbox socket Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's registration recorded for the exact target; what earnsDELIVEREDis Prototype the Claude lane: official roster, built-in inbox socket and user-scope hooks, with no wrapper #71's ruling, copied verbatim into this section before code (socket write accepted / inbound delivered / turn started / correlated observation); everything weaker isUNKNOWN. Inboxhold/refuseand the peer-authority limit are their own honest receipts (FAILEDwith reason, orUNKNOWN), never mapped onto the oldacknowledged_by_claude. - P8 — Codex route: unchanged; add the verification test on the daemon route, delete channel-era tests.
- Removed in the same branch:
adapters/agent/claude/channel*(ADR 0006 server, plugin rendering,--channelsselection) and their tests; ADR 0006 / 0007 marked Superseded (Prototype the Claude lane: official roster, built-in inbox socket and user-scope hooks, with no wrapper #71 has proved the replacement by then).
Left behind
P2: both Stop ledgers, their recovery transitions, old Reply Windows; an unattached row never gets an open window. P7: queued_for_claude / acknowledged_by_claude, the MCP Channel adapter, the legacy synchronous client, durable acknowledgement queries. P8: the legacy per-TUI lease ledger and synchronous app-server client. P9: delivery-attempt history, confirmed-request history, crash enquiry, cross-restart replay, any new delivery/coordinator module. P14: any wording that reports a verdict on a non-DELIVERED receipt. P15: the durable channel ledger, auto-resend.
Red first
Acceptance steps relay, approval, companion inbound red before; green after, both lanes.
Done when
Steps green on Simon's machine; pytest + ruff green; the core/relays.py diff is a policy edit (reviewer checks: no new class, no new file).
Blocked by
#74 (locked types); #75 (typed waiting_for drives the P2 transitions); #71 (receipt definition).
Activity
Simon-AI-coding commentedon Aug 26, 2026
Advisor note from #83 (2026-08-26)
#83's Done-when bullet 4 has two halves. The installation-side half (missing managed binary, unwritable
~/Library/LaunchAgents,launchctl bootstraprefused → explicitOutcomereason throughbridge-install status/reconcile) is #83's and ships there. The pre-wireFAILEDhalf — a Relay or Approval against a Codex Session with no shared daemon returnsFAILEDwith a reason before anything is sent — is delivery behaviour and lands here, on the shared-daemon route this ticket builds. #74 supplies the discovery side of the same fact (LaneDiscovery.degradedwhen the daemon is absent, rows still listed, ruled 2026-08-26). Nothing in #83 builds a second copy of the daemon route.Connect the registration, so a Session the hook reported is reachable (…
Discover Sessions, lock the one inspection type, and make a registere…
Simon-AI-coding commentedon Aug 26, 2026
Advisor note from #75 (2026-08-26)
Two things #75 settles that P14's wording and P2's transitions build on:
approval.summary_ofno longer returns the rawcommand; it delegates to the pure extractor instop_analysis.py— first non-blank ofdescription/file_path/path/notebook_path, ≤ 200 chars, never command text, content or edit strings (legacy@1d32845:bridge/transcript.py:1779-1811,126-143). ABashpermission is announced as "permission to use Bash — ", or the tool name alone. P14 words on top of that; it does not reintroduce the command.stop noticeacceptance step is split. Content (the notice names what the Session stopped on,SessionStopped.waiting_forfilled) is Build 2/7 — Stop analysis: one pure Claude module for question, permission and tool summary #75's. Timing — whether the transition fires inside the step's window — is this ticket's P2: Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 measured a 292 ms observation window against a 5 s discovery cadence, with the next Stop for the same Session arriving 35 s after its turn ended. Build 2/7 — Stop analysis: one pure Claude module for question, permission and tool summary #75 adds no new producer ofSessionStopped; when and whether it fires is decided here.Simon-AI-coding commentedon Aug 26, 2026
Advisor note from #75 (2026-08-26) — signature change
#75's pure module is
analyse(records: Sequence[Mapping]) -> WaitingFor; thenotification: str | Noneparameter #75's body locked is dropped. v2 installs noNotificationhook, so nothing could fill it. Legacy's first-turn invariant (tool name known before the transcript flushes,legacy@1d32845:bridge/daemon.py:143-145,213-223,2049-2051) is adapted onto thePermissionRequesthook: the Claude adapter overlaysApprovalListener.pending()for the exact target afteranalyse, with fixed precedence — a readable question always wins; a pending request fillsapproval_id/tool_nameon a permission, or turnsUNKNOWN/NONEintoPERMISSIONwithcaught_upleft as the parser reported it. Cite this signature, not the ticket body's.Simon-AI-coding commentedon Aug 26, 2026
Advisor: two items added to this ticket's scope from #75's review (2026-08-26)
Both found and verified by #75's worker, both fenced out of #75 by ruling because they are transitions and Bridge Core policy. They are this ticket's, explicitly.
1. A Session entering
waitingraises noSessionStopped— a question reaches no outletadapters/agent/claude/window.py:293emitsSessionStoppedonly onwas_active and status == "idle", andSTATUSES_MEANING_TURN_ACTIVE = {"busy", "waiting"}(:118; pinned bytests/test_claude_reply_window.py:292). So when a Session raises a question or a permission it is still "turn active"; when the user answers and it goesidle, the transcript carries thetool_result,analysecorrectly saysNONE, and the notice that finally fires says nothing about what it stopped on.The permission half already reaches the user by another road (
approval.py:489→AwaitingApproval→core/bridge.py:461→approvals.opened()). The question half has no announcement route at all:AskUserQuestionraises noPermissionRequesthook (unmeasured on 2.1.246 whether it raises a permission dialog of its own — measure it here). After #75 the question is on the roster row, with prompt, options and recommendation, for as long as the Session waits — visible, not announced.CONTEXT.md's Stop Notice names "the question with its options" as its content, so this is a v1.0 gap, not polish.The fix is one edge in
window.py(busy → waitingalso raisesSessionStopped,waiting_foralready available from #75's read) plus the policy it opens: a permission would then announce twice, once asAwaitingApprovaland once asSessionStopped. Bridge Core decides which announces and which is suppressed; the adapter does not.2. Codex
WaitingForprojectionadapters/agent/codex/adapter.py:647emitsAwaitingApprovalfrom the app-server request and never fillsWaitingFor(kind=permission, …), so a Codex roster row and a CodexSessionStoppedcannot say what the Session stopped on. #75 is Claude-only by its Shape and by the port table ("Codex's existing app-server request path stays outside the module"). The projection from the app-server request into the lockedWaitingForis one row here, beside P8 — no transcript parser for Codex, ever.Simon-AI-coding commentedon Aug 26, 2026
Advisor note (2026-08-26) — the shared-daemon client now comes from #76
Supersedes the sentence above, "the shared-daemon route this ticket builds". #76 builds the read-side client (
adapters/agent/codex/shared_daemon.py,_shared_daemon()returning a kept, reconnectingattach()connection orNone), because its ownprogressstep on the Codex lane is red by construction without it. #77 consumes that connection for Relay, Approval, the CodexWaitingForprojection and the pre-wireFAILED— it does not open a second one, and it does not move the daemon's lifecycle (join-only, ADR 0012). Ruling and bounds on #76.Simon-AI-coding commentedon Aug 26, 2026
Advisor rulings (2026-08-26) — the worker's three questions
Q1 — the "question/permission → OPEN" transition is dropped.
derive_reply_window(seams/agent.py) stays OPEN iffidleand main, and gains no condition in #77. The Shape's sentence predates #74's removal ofreach/attachedand #71's measurement: the inbox route carries words, never authority, so a question is never answered over it. P2 lands as tests for the two transitions already true in code (running → CLOSEDfrom the derivation;SessionEnded → CLOSED+ pending RelaysFAILEDviacore/bridge.py:_session_ended). Still #77's measurement: whetherAskUserQuestionraises aPermissionRequesthook on 2.1.246 — the only fact that could give the question half a route (through the approval channel). A throwawayclaudeprobe on this machine is Simon's per-run authorisation in the worker's session; if not run, "unmeasured on 2.1.246" is written here. Probe (i) — a question consuming an inbox message — is not run; #71 makes it moot.Q2 — (a):
answer_relay/approval_relaymove ontoSharedDaemon.client()._threadsis populated only byregister_session, which nothing insrc/calls, so both verbs are dead in the assembled engine. The shared daemon is Codex reach on this map; "unchanged; verify" was written before the client existed and now means: the receipt protocol (exact-IDclientUserMessageIdreadback) keeps its rule and moves wire. Pre-wireFAILED=_shared_daemon() is Nonewith the reason.core/relays.pyremains a policy edit.self._ownstays only for its remaining production callers (Live Calluse_app_server,adapters/call/realtime/adapter.py:174;codex/adapter.py:480), listed in the resolution; not deleted here; no second daemon connection.Q3 — confirmed. In
core/bridge.py:_session_stopped, aSessionStoppedwithkind is PERMISSIONandas_approval_request(target) is not Noneis suppressed (logged): theAwaitingApprovalnotice carries the budget, fallback and closing, and already reachesEVERY_OUTLET. APERMISSIONwith no approval id is announced, and the notice says the user answers it at the terminal.QUESTIONis always announced.P7 receipt: #71's ruling verbatim. Base:
mainatda89dbd(#98 merged;stopped_onnow takes the dialog's tool name, adapters forget a Session at their ownSessionEnded).Simon-AI-coding commentedon Aug 26, 2026
Advisor ruling (2026-08-26) — the question half, after probe (ii) measured true
AskUserQuestionraises aPermissionRequesthook on 2.1.246 with the whole question intool_input.questions[]and aprompt_id; adenywith a message is consumed by the Session as the answer (worker's measurement; evidence pasted below by the worker). B in #77: the Claude adapter's existing_overlayfillsWaitingFor(kind=QUESTION, prompt, options, approval_id=prompt_id)from that payload — the hook's question (the thing itself) wins over the transcript's reconstruction;Option.recommendedhas no source here and staysNone;as_approval_requeststill returnsNonefor a QUESTION, so nothing carries a verdict into it and the notice says the question is answered at the terminal; a multi-question payload projects the first and states the count.derive_reply_windowgains nothing — this route is not a Relay. C — answering the question from the bridge — is filed as its own ticket, a candidate on #67 pending Simon's call on v1.0. Also to land in this diff: a comment besideapproval.py:100-115recording that thepermissionDecisionoutput shape has no effect on 2.1.246 and thatapproval_ackover our socket, not the hook's exit, is the proof.5 remaining items
Simon-AI-coding commentedon Aug 26, 2026
Advisor (2026-08-26), after the codex-lane review: (1) the
procStartfinding — the reply key's time is UTC, not a twelve-hour clock as #71 recorded; measured on eleven live Sessions — is posted on #71 as a correction. (2)core/relays.pybudget is measured on implementation lines (ast; docstrings/comments excluded), per the standing rule; the number goes in the resolution; the named check (no new class, no new file) holds and is binding; prose is not cut to hit a count. (3) Legacy classifications for the three uncited behaviours go in the resolution: busy→waiting Stop edge adapted from legacy stop detection; permission suppression — legacy has no such behaviour (no approval transport); shared-daemon routing — legacy's per-TUI lease ledger and synchronous client dropped, because the shared daemon owns the threads (ADR 0012). (4) Feature Envy atcodex/adapter.py:504andshared: boolatthreads.py:63are judgement items, left. One re-review follows.Simon-AI-coding commentedon Aug 26, 2026
Advisor (2026-08-26), item B, final: the
(recommended)marker lives inside the option label (stop_analysis.RECOMMENDED_MARKER), so the hook payload carries it whenever the model wrote it. The earlier clause "recommendedFalse on the hook route" is withdrawn; the shared parser is unchanged andrecommended=Trueappears on either route exactly when the marker was written — a fact read, not an inference; order is never consulted; the label is the spoken words with the marker stripped, as #75 landed. Pinned by a hook-route test.core/relays.pybudget: 44 implementation lines added by the defined count (reviewer's method: 62), no new class or file — in scope, nothing cut. Acceptance on Simon's machine:relay,approval,companion inboundgreen on the Claude lane ("delivered via deliver (delivered); relay.txt contains BRAVO"); Codex lane blocked earlier in the journey, mechanism to be stated in the resolution.Simon-AI-coding commentedon Aug 26, 2026
Resolution — Build 4/7, all six rows landed
Branch
delivery-receipts-77, eight commits ahead ofmain@da89dbd.pytest(395) andruffgreen; the commit messages carry the reasoning and this comment carries what is not in any one of them.The rows
3a56966— zero lines of source. The ticket'squestion/permission → OPENtransition was dropped by ruling (Q1 = A):reachwent with #68 and #71 proved the inbox can never answer a question. The other two transitions were already true in landed code and are now covered by tests.bcdf75e+43ff6a5— the Answer Relay moved off ADR 0006's channel onto the Session's own inbox socket. Net −249 lines of source.67762ad— the exact-IDclientUserMessageIdreadback kept its rule and moved wire, onto #76's shared-daemon connection.add2b34— a policy edit; see the budget note below.3a56966— only aDELIVEREDreceipt may announce a verdict.3a56966— the Companion reply is attempted once, graded, never re-sent.c69d562+04c14af— from the advisor's ruling after probe (ii).WaitingForprojection67762ad(roster row) +55995dc(the Stop).FAILED(from #83)67762adAcceptance — the three steps this ticket owns
Run twice on Simon's machine against the real far side:
claude2.1.246,codex-cli0.149.1, the real Telegram bot and his own Telegram account, a bundle byte-identical toe553c61.Claude lane — all three green (run
20260826T105036Z):relay—delivered via deliver (delivered); relay.txt contains BRAVO. The receipt and the effect, on a Session started by hand. This is the inbox route proving itself end to end: no channel, no wrapper, no launch.approval—approval 3483c34d…: announced as chat message 3598 ('a session is waiting for your permission to use Write — …/relay.txt'); approve answered 'approved by voice'.companion inbound—message 3600 → @workspace-claude-56 → inbound.txt contains CHARLIE, with theengine.logline An inbound Companion Channel message leaves no trace in the engine log, so an exercised path is indistinguishable from one that never ran #48 requires.Also red in that run and not this ticket's:
child(#79, which its own evidence names) and the0b realtime contract probe(0 audio frames in 30 s).Codex lane — blocked before those steps, and here is the mechanism. The shared daemon answers both
thread/readandthread/resumefor the acceptance's freshly-started TUI thread withlist_turns is not supported yet:So
stable name(#78) andprogress(#76) fail, no turn is driven, andrelay/approval/companion inboundare skipped rather than red — unobservable on this lane, not failing.The mechanism, with the confidence it deserves: in the same run a different Codex thread on the same daemon —
01a02110-…, one of Simon's own, which had taken turns — was described successfully and delivered a permission request all the way toAwaitingApproval. The acceptance's thread had taken none ("no turn ran"). Hypothesis, consistent with every observation in this run but measured only here:list_turnsfails on a thread with no turns. That makes the Codex lane's acceptance a chicken-and-egg — the harness drives a turn only afterstable nameandprogresspass. Reported rather than worked around, as ruled. It rides #78's exit item.An incident, and the harness fix it forced
The first run (
20260826T103838Z) granted a permission belonging to a Session that was not the harness's own._answer_pending_approvaltookpending_approvals[0]with no filter — safe when it was written, because no Codex approval could ever be pending, and unsafe as of this branch, where the Codex lane reaches every thread on the shared daemon. It ran:The command was itself a probe (
create /tmp/vcprobe/approval-probe.txt), so nothing was harmed — and the mechanism would not have known the difference. The lane's own Write then waited four and a half minutes for the next pass, which is why that run reportedrelayas "a receipt without an effect" andcompanion inboundas an announcement nobody was left to read: two red steps and one silent grant, from one missing filter. Fixed in0f03fe2— the address is the filter, as it is everywhere else in that file. Worth knowing beyond this ticket: an engine now sees, announces and can answer the dialogs of every Codex Session on the machine. That is the product working as designed, and it is a new fact for anything that readspending_approvals.Legacy classifications the commit messages did not state
Per
CLAUDE.md, one line each, at the advisor's direction:adapters/agent/claude/window.py:298— thebusy → waitingStop edge: adapted from legacy's stop detection (legacy@1d32845:bridge/daemon.py:143-145,213-223) onto the roster's own state transition, because v2 installs noNotificationhook.core/bridge.py:592— suppressing a Stop whose dialog the Approval Relay announces: legacy has no such behaviour. It had no approval transport, so one stop never had two announcers.adapters/agent/codex/adapter.py:419,shared_daemon.py:217,threads.py:63— the shared-daemon routing, subscription and connection ownership: legacy's per-TUI lease ledger and synchronous client are dropped, because the shared daemon owns the threads (ADR 0012, and this ticket's own "left behind" list). The exact-ID readback beside them is the ported part, cited in67762ad.The reviewer check, and the P9 budget
The named check holds. The
core/relays.pydiff adds no class and no file: three module-level functions (may_be_retried,confirmation_for,_report_for) and nothing else.The budget, stated honestly because two counts disagree. Raw: 139 added, 16 removed. Implementation lines only — ast-visible tokens, docstrings and comments excluded, which is the standing rule: 44 added / 6 removed, net +38 by my count, 62 added by the reviewer's. The gap is method, not diff: mine collapses identical lines (repeated
),else:), the reviewer's does not. Ruled in scope by the advisor, with nothing cut — prose is not trimmed to hit a number.Facts worth carrying out of this ticket
procStartis UTC, and Prototype the Claude lane: official roster, built-in inbox socket and user-scope hooks, with no wrapper #71's record of it was wrong — posted there as a correction. It was recorded as "a twelve-hour clock with no AM/PM"; this machine is UTC+12 and that was the offset. The two readings are the same string for a local afternoon and a whole day apart for a local morning, so the ported formula was wrong on the hour and the date at once, for every Relay before local noon, silently. Measured against all eleven live Sessions' key files: eleven matches, no mismatches.permissionDecisionoutput shape has no effect on 2.1.246 — the evidence comment above, and a note besideapproval.py:100-115in this diff. Drift in that shape is indistinguishable from a hook answeringask, which is why the route's proof isapproval_ackover our own socket.AskUserQuestion's recommendation mark lives in the option's label, by the tool's own instructions — so it is carried on the hook route as it is on the transcript route, one parser, one reading. The earlier "alwaysFalsehere" was written believing the payload could not carry it and is withdrawn.engine/composition.py:418→adapters/call/realtime/adapter.py:174) andadapters/agent/codex/adapter.py's ownverify.ADRs
0006 Superseded by ADR 0013, which is new here and records the decision Simon took on #71 — the socket route, on private surface, knowingly, with no version pin and the honest-receipt rule that replaces one. 0007 was already Superseded by ADR 0011. The two are one map: the inbox for words, the hooks for authority.
What is deliberately not here
C— answering a question from the bridge — is #103, in v1.0 as Build 8/7 after #80. It leans on two things this diff keeps exactly:approval_id = prompt_idon the projected QUESTION, and the option labels as spoken words. One thing for it to decide first: a flattened multi-question call has oneprompt_idfor several answers, so #103 has to settle how an answer addresses a group before it sends one.Carry the user's words over a Session's own inbox, and claim only wha…
Simon-AI-coding commentedon Aug 26, 2026
Closed — merged as
e2090aa(#104)Squashed onto
main;delivery-receipts-77deleted, local and remote. The resolution comment above holds the row-by-row account, the acceptance evidence and the legacy classifications; this is the closing state and what leaves this ticket for other people to pick up.Done-when, item by item
relay,approval,companion inboundgreen on Simon's machineclaude2.1.246 and the real Telegram bot, on a Session started by hand with no wrapper.relay:delivered via deliver (delivered); relay.txt contains BRAVO— the receipt and the effect. Codex lane blocked before those steps; mechanism below.pytest+ruffgreenruff check,ruff format --checkandswift format lintgreen on the mergedmain.core/relays.pydiff is a policy edit — no new class, no new filemay_be_retried,confirmation_for,_report_for) and nothing else. Budget stated honestly in the resolution: 139 raw added lines; 44 added / 6 removed by one implementation-line count, 62 added by the reviewer's — the gap is method, not diff. Ruled in scope, nothing cut.What leaves this ticket
#78 absorbs the Codex lane's blocker. The shared daemon answers both
thread/readandthread/resumewithlist_turns is not supported yetfor the acceptance's freshly-started TUI thread, sostable nameandprogressfail, no turn is driven, and this ticket's three steps are skipped rather than red on that lane — unobservable, not failing. In the same run a different thread on the same daemon, one that had taken turns, was described and delivered a permission all the way toAwaitingApproval. Hypothesis, consistent with every observation here and measured only here:list_turnsfails on a thread with no turns, which makes the Codex acceptance a chicken-and-egg, since the harness drives a turn only after those two steps pass.#103 (answering a question from the bridge, Build 8/7 after #80) leans on two things this diff keeps exactly:
approval_id = prompt_idon the projected QUESTION, and the option labels as the spoken words. One thing for it to settle before it sends anything: a flattened multi-question call carries oneprompt_idfor several answers.A new fact for anything that reads
pending_approvals: an engine now sees, announces and can answer the dialogs of every Codex Session on the machine, because the Codex lane reaches every thread on the shared daemon. That is the product working as designed. It is also what made the acceptance harness grant a permission that was not its own on the first run —pending_approvals[0]with no filter, fixed here, and the reason to check any other consumer that assumes the list is its own.#54 takes the ADR 0006 channel server's remnants outside
src/, per Simon's note.#55 still owns the acceptance's one secret. It has no durable home, so it lives in a gitignored
.envthis checkout reads and nowhere else.#71 carries a correction, posted there:
procStartis UTC, not "a twelve-hour clock with no AM/PM" — that machine is UTC+12 and the probe saw the offset. Wrong on the hour and the date at once for every Relay before local noon, and silently. Eleven live Sessions' key files reconstructed fromps: eleven matches, no mismatches.Cleared
The probe rig (
/tmp/gptvc-77-probe/), its six transcripts under~/.claude-b/projects/, and the gitignored research file are deleted at Simon's word. Everything they proved is in the evidence comment above.~/.claude-b/settings.jsonis byte-for-byte as #71 left it — sha2561f0117cb63d854d01abf5e3524dd00a8e625a213593cd1f9f7bb82cf05ae1706, verified before and after.Give a Session one name, composed once and never changed again (#78) (#…
Simon-AI-coding commentedon Aug 26, 2026
Cross-note: the Codex approval route this ticket built is proven on the real machine by acceptance run
20260826T213402Z— a realitem/fileChange/requestApproval, escalated to the Companion Channel (message 3655) before anything answered it, answered throughbridgectl approve, with the effect landing outside the sandbox where Codex could not have written it alone. It took #105 to relay an instruction Codex would actually ask about; see #105 and PR #108 for the measurement.Require the pin itself, not merely the absence of the worst value (#105)
Relay the Codex lane an instruction its own sandbox will not let it t…
Briefing is the one source of words about Session state
A relay that finally fails reaches the user through Briefing
A Codex row holding a dialog reads waiting on every pass
An answer carried into a held hook is framed `Ruling:` (#328)
Record what a framed denial costs the surfaces that read it (#328)