Review 582 (REQUEST_CHANGES at 95178349) found a residual fail-open of the
same class the PR set out to close. Acknowledgement coverage was decided by
comparing a count against a count:
covers_live_sessions = live_count_known and acked_count >= sessions_live_other
Nothing bound an acknowledgement to the identity of a session that actually
owed one, so acknowledgements supplied for the requesting session and for a
session that does not exist satisfied the obligations of two live sessions
that never answered - minting a clean, correctly signed proof and an allow
verdict from the restart gate.
Coverage is now derived from authoritative impact-report evidence:
- New `_required_ack_sessions()` derives the required session ids from the
report itself, via `ack_state` keys and/or `affected_sessions` filtered on
`live and not is_requester`. The requester is excluded only on explicit
`is_requester` evidence, never inferred.
- When both views are present they must name the same set, and the result is
reconciled against `counts.sessions_live_other`. Missing, malformed,
duplicated, contradictory, or unreconcilable identity evidence fails closed
and outranks every permitting path, including the timeout policy.
- Coverage requires every required id to carry an explicit acknowledgement
token keyed by that id. Acknowledgements for the requester, for unknown
ids, or for fabricated ids never increase coverage.
- Caller-supplied acknowledgement cardinality is no longer proof of anything.
Failure propagates unchanged through `acks_or_timeout` -> `proof.clean` ->
`failed_checks` -> `gate_apply_restart` verdict `deny` / `allow=False`.
The earlier missing-acknowledgement remediation is preserved in full: absent,
None, non-mapping, empty, partial, stale, and unparseable acks still fail
closed, `ack_timeout_policy_applied` stays strict `value is True`, and the
legitimate zero-live-sessions and explicit-timeout paths still pass.
Reviewer's reproduction, before and after this commit:
sessions_live_other = 2
report ack_state = {'other-0': 'pending', 'other-1': 'pending'}
supplied acks = {'req': 'ack', 'totally-bogus-session': 'ack'}
before: acks_or_timeout = True | proof.clean = True | gate allow
after: acks_or_timeout = False | proof.clean = False | gate deny
Tests: 22 new cases in `AcknowledgementIdentityBindingTests` covering the
reviewer's exact exploit, wrong-ids-with-sufficient-count, partial identity
match, requester-only acks, fabricated ids, unproven per-session states,
missing/malformed/contradictory identity evidence, count mismatch, and the
preserved success paths.
Verification:
- `pytest tests/test_drain_proof.py` -> 61 passed, 56 subtests
(baseline at 95178349: 39 passed, 26 subtests)
- Restart surface (6 modules) -> 154 passed, 68 subtests, exit 0
(baseline at 95178349: 132 passed, 38 subtests)
- Full `pytest tests/` -> 5177 passed vs baseline 5155 passed; the 23
failures are identical in both runs and pre-exist at 95178349.
Scope: drain_proof.py, tests/test_drain_proof.py.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VRUZAf3Fr5n3kqhhiayN6C
(cherry picked from commit 4193b63f415b066ee292386c2c89bc3d2651a0cc)