Agent pane: keep answers to reused permission IDs in separate sessions
Why (HQ #6236, brief 21)
The merged #32113 correctly gives different permission IDs separate outstanding gesture tickets, but AgentPaneGestureIntent.slot currently keys an answer ticket by permissionId without sessionId. AgentPaneTransport can show more than one session on a single connection (AcpmuxPaneSessions) and AgentPaneUserGestures.reserve revokes an older ticket with the same slot.
Deterministic adversarial case: reserve the user's answer for session A / permission p1, then before its frame reaches the relay reserve session B / permission p1 on the same pane connection. The second reservation revokes the first. Redemption verifies sessionId and still fails closed, but the first real click is lost and the user gets a refused answer. This is collision resilience, not a claim of an observed production ID collision.
What changed
- Scope the permission-answer slot to both session ID and permission ID. The session has a UTF-8 length prefix so delimiter-bearing IDs cannot alias.
- Add
AgentPaneRelayedAllowTests.answerTicketsDistinguishSessionsWithTheSamePermissionIDcovering two simultaneous sessions, fail-closed cross-session redemption, same-ask replacement, and an adversarial delimiter collision. - Leave existing exact-pick validation, single-use redemption, the per-connection ticket cap, and all non-answer gesture methods unchanged.
Evidence / verification
- Read the source at
feat-cmux-next6c3fe3648ba4b44e7bf38c4982a665ec2d5d5712; branched from8d660c2e834a48939092c97a5d0b837c6b838643. - Local isolated Swift 6.2.1 model (using the current slot rule and reserve/drop rule): reproduced the old ticket collision; passed the amended slot, delimiter and replacement checks. This is a model check, not an app or daemon integration test.
- The new repository Swift Testing case has been added; macOS suite and Cloud runtime have not been executed from this Linux environment. CI/review needed before landing.
- Reviewed context: merged #32113 owns the original two-ask fix; #31915 (Lawrence) is a separate live Rust gesture-pick parity PR; #31919 documents the separate before-prompt fork-ready crash window. This change avoids their branches and files.
No production auth, remote machine, account or paid runtime use. Requesting review of the exact head and the new regression.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes agent pane answer tickets being keyed by permission ID alone, which let a reused permission ID in a second session revoke the first session's pending answer and lose the click. Slots are now scoped to both session ID and permission ID, using a UTF-8 length prefix so delimiter-bearing IDs cannot alias. Adds a regression test covering cross-session reuse, fail-closed misrouting, same-ask replacement, and delimiter collisions.