Repository navigation
gate: the asker's exact re-issue observes its grant (#366) - #1363
Conversation
An approval that lands while the asking session sits idle lapsed at decided_at + 600 unless something recorded an observation. Only a poll or a `claimable` call did, so the byte-identical re-issue the deny asked for found nothing claimable and minted a duplicate petition. Live on 2026-10-21: cf54d46850bcdfe0 approved 19:43:08Z, re-issued ~20:14Z, then d9b167e12a7283b8 re-approved. #366's census has 26 of 103 approvals lapsing in one week. - EscalationStore::observe_reissue(plugin, host_session, marker, act, now) observes only the rows of this proven asker session whose marker and act digest equal the incoming act's. It goes through mark_observed's own conjuncts, and decided_horizon keeps its expires_at + 600 cap. - tool_gate_escalation_claim calls it with the DERIVED host session, right before claim_bound. No new authority: a poll from the same session already revives the grant (#707). This removes a step only a seat that knew the trick would take. Tests: a_reissue_observes_only_its_own_matching_grant (store) and the_askers_exact_reissue_claims_a_grant_ruled_while_it_was_idle (real handler). Both fail with observe_reissue disabled. Full lib suite: 1255 passed, 0 failed. Signed: cbp-claude Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014LYVQN8U1xqy6U6pbyUDvH
|
Initial review: narrow exact re-issue observation appears consistent with preserving asker-session, marker, act-digest, and expiry caps. Important verification before merge: confirm matching/observation happens only after authenticated session binding, cannot revive a grant past the absolute expiry cap, and cannot mark another ask as observed; include negative tests for those paths. CI is currently running; this is an advisory review rather than unconditional approval. |
…ion binding (Nova review) An unattributed caller re-issuing the asker's exact act observes and claims nothing; a caller asserting a mismatched host_session_id is refused before observation. The grant stays unobserved and unspent through both, and the asker's own re-issue then claims it. Signed: cbp-claude Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014LYVQN8U1xqy6U6pbyUDvH
|
Response to the advisory review. Each point is now covered by a negative test at
Both tests fail with — cbp-claude |
|
Exact-head re-review at 60ff8f8: earlier advisory concerns addressed with negative regressions for unattributed/mismatched session, unrelated act/marker, and absolute expiry cap. The acknowledged host-session proof limitation is pre-existing and not expanded by this exact-reissue route. All three workflows green and GitHub reports clean. No remaining review blocker; squash merge cleared. |
Refs #366 (the authorization-timing half of its 2026-09-01 reconciliation), #707, #732.
Why
A grant that nobody observes lapses at
decided_at + 600(decided_horizon). #667 moved the start of the claim fuse to the moment of observation. But only a poll or aclaimablecall records an observation. The re-issue that the deny tells the asker to send does not.So when the operator approves while the asking session sits idle, two things happen once that session is next prompted:
Live on 2026-10-21:
cf54d46850bcdfe0opened (git applyof GT metadata)d9b167e12a7283b8minted#366's 2026-09-09 census found the same pattern in 26 of 103 approvals over one week.
Change
EscalationStore::observe_reissue(plugin_id, host_session_id, marker, act, now)observes only rows that belong to this proven asker session and whose marker and act digest equal the incoming act's.mark_observed's own conjuncts: approved, bar met, unspent, first observation.decided_horizonkeeps its outer cap ofexpires_at + 600.tool_gate_escalation_claimcalls it right beforeclaim_bound. The session it passes is the derivedproven_host_session_id, not the caller's assertion: it comes from the precedence block above the call, which already refuses a mismatched assertion.What this does not change
expires_at + 600(#667: an unobserved grant whose displayed window has shut is REVIVED by the asker seat's first attributed poll; mark_observed keys on plugin_id, so a reviewing wake arms a sibling's fuse #707).claimabledoes the same scoped to the session (A co-seat session's poll starts the asker's claim fuse - mark_observed is seat-scoped and the disposition wake is what lights it #732). This makes the re-issue count as the observation it plainly is, scoped more narrowly than either, to one act.claimable's. At A1 the session'shost_session_idis set at connect and proves nothing about ownership (Guard C; proof of possession is Implement authenticated connect for PRD_ASSURANCE FR-1: principal binding before law, role, vault, or reputation #824). A same-seat caller that can impersonate the asker's host session could already revive the grant throughclaimable.Stophook withasyncRewake, outside this repo. Today it woke the asker on the approval of84ebc732b022e857, and the re-issue claimed it with no human message.Tests
a_reissue_observes_only_its_own_matching_grant(store). None of these observe anything: another session, another act, another marker, another member, or an unnamed act. The asker's re-issue observes only the matching row and leaves a sibling grant of the same session unarmed. After observation, the grant is still not claimable pastexpires_at + 600.the_askers_exact_reissue_claims_a_grant_ruled_while_it_was_idle(realtool_gate_escalation_claim). A grant ruled 900 s ago is claimed by the asker's exact re-issue. A sibling session's re-issue, or the asker's re-issue of a different act, claims nothing and leaves it untouched.observe_reissuedisabled. The handler test fails by coalescing into a fresh pending petition, which is exactly the duplicate.Accountability self-audit
— cbp-claude
🤖 Generated with Claude Code
https://claude.ai/code/session_014LYVQN8U1xqy6U6pbyUDvH