Skip to content

gate: the asker's exact re-issue observes its grant (#366) - #1363

Merged
dp-web4 merged 2 commits into
mainfrom
cbp/reissue-observes-grant
Oct 22, 2026
Merged

dp-web4 merged 2 commits into
mainfrom
cbp/reissue-observes-grant

Conversation

@dp-web4

@dp-web4 dp-web4 commented Oct 21, 2026

Copy link
Copy Markdown
Owner

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 a claimable call 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:

  • its byte-identical re-issue finds nothing to claim;
  • it opens a duplicate petition for the operator to rule on again.

Live on 2026-10-21:

UTC
19:24:25 cf54d46850bcdfe0 opened (git apply of GT metadata)
19:43:08 operator approves ("k")
19:53:08 horizon passes, unobserved
20:14:09 identical re-issue → d9b167e12a7283b8 minted
20:14:30 operator approves again; claimed 22 s later

#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.
    • It goes through mark_observed's own conjuncts: approved, bar met, unspent, first observation.
    • Re-issuing one act never starts the fuse of another.
    • decided_horizon keeps its outer cap of expires_at + 600.
  • tool_gate_escalation_claim calls it right before claim_bound. The session it passes is the derived proven_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

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 past expires_at + 600.
  • the_askers_exact_reissue_claims_a_grant_ruled_while_it_was_idle (real tool_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.
  • Sabotage check: both new tests fail with observe_reissue disabled. The handler test fails by coalescing into a fresh pending petition, which is exactly the duplicate.
  • Full lib suite: 1255 passed, 0 failed, 2 ignored.

Accountability self-audit

surface: tool_gate_escalation_claim (re-issue path)   act: spend a one-time governance-write approval
S: med/reversible-per-act [construct: one-time grant, act-digest bound (#539)]
R: n/a   W: pass [construct: proven_host_session_id derived from the resolved session; observe_reissue matches plugin+marker+act digest+host session]
O: pass [construct: observe_reissue then claim_bound, both before any side effect; a non-matching call mutates nothing]
A: pass [construct: claim witness unchanged; observed_at is store-only state as before (#850)]
V: present [construct: decided_horizon cap at expires_at + 600; deny/withdraw unchanged]
verdict: PASS

— cbp-claude

🤖 Generated with Claude Code

https://claude.ai/code/session_014LYVQN8U1xqy6U6pbyUDvH

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

dp-web4 commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

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
@dp-web4

dp-web4 commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

Response to the advisory review. Each point is now covered by a negative test at 60ff8f88.

  1. Observation only after the authenticated session binding. In tool_gate_escalation_claim, observe_reissue runs only with proven_host_session_id. That value is derived from the session that resolve_attributed_caller resolved. It sits after the asker/plugin mismatch refusal and the stated-vs-proven host-session refusal, both of which return Err before the call. New arms in the_askers_exact_reissue_claims_a_grant_ruled_while_it_was_idle:

    • An unattributed caller (no session_id) naming the asker's member and exact act claims nothing.
    • The asker's own session asserting another host session is refused (Err).
    • After both, the grant has observed_at: None and consumed_at: None. Only then does the asker's real re-issue claim it.

    Caveat on what (b) proves: it pins the ordering (refuse before observe). It is not an independent check of the binding itself, which is the existing precedence block.

  2. No revival past the absolute expiry cap. a_reissue_observes_only_its_own_matching_grant, last arm: a re-issue at expires_at + 600 + 1 is observed (mark_observed doesn't read the clock), but is_claimable is false. decided_horizon() is min(observed_at + 600, expires_at + 600), unchanged by this PR.

  3. Cannot mark another ask as observed. Same store test:

    • These observe nothing: another session, another act digest, another marker, another member, an unnamed act.
    • The asker's re-issue observes exactly the matching row.
    • A second approved grant of the same session with a different act stays observed_at: None.

    The handler test adds the sibling-session case through the real claim tool.

Both tests fail with observe_reissue disabled. The full lib suite passed at the previous head (1255/0); the two touched tests pass at 60ff8f88.

— cbp-claude

dp-web4 commented Oct 22, 2026

Copy link
Copy Markdown
Owner Author

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.

@dp-web4
dp-web4 merged commit bebb3e1 into main Oct 22, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant