agent-pane-policy: the gesture pick is the Swift host's: hand-off prompt ids, answer digests, Double numbers
A gesture ticket is bound to a pick: the method and the params it was taken for. The Swift agent pane host builds that pick three ways that the crate does not, so a host that runs the crate would never redeem these tickets:
- Hand-off prompts: a session/prompt picks its own id without a hand-off turn's
handoff-<turn>-label (AgentPaneHandoffPrompt.ownID, 07efcedc1bb). The crate used the raw id, so the ticket of a Cloud hand-off prompt never redeemed. - Question answers: a permission answer's
answersandannotationsare picked by the sha256 of their canonical JSON (AcpmuxSecretKey.canonicalDigest, 8c3e8297749). The crate picked nothing for a frame with answers, so the ticket of a question answer never redeemed. - Numbers: a number param is held as a Double, so
1and1.0give the same pick.
What changes in the crate:
src/canonical.rsportscanonicalJSON/canonicalDigest. These are the same bytes as acpmux'sapprovers::canonical_json: keys in UTF-8 byte order, no whitespace, only",\and control characters escaped, and an integral float below 1e15 written as an integer. A value with no canonical form has no digest, and its frame picks nothing.GesturePick::new(src/check.rs) follows SwiftAgentPaneGesturePick:own_prompt_id, the digests, a null answers is no key, andas_double.- The test is
tests/gesture_pick.rs, with 24 cases intests/cases/picks.json. The expected values come from a line-for-line Python copy of the Swift code. That copy asserts Swift's own canonical JSON vectors (AgentPaneRelayedAllowTests) and acpmux's fixed digest (the_wire_payload_is_fixed,44ba30ad…), so the digests match Swift byte for byte. - One
tests/cases/check.jsoncase now expects the answers digest, as Swift gives it, not "no pick". I renamed it to say so. sha2is the workspace's 0.10.9.cmux-tasks-corealready brings it into the app FFI lock, so no new package goes into the app.cmux-tui/Cargo.lockgains one dependency line.
This is the last of the Swift-only drifts that would break features if the app called the crate (rm-pane-policy, layer-ownership V-23). The app does not call the crate yet, and cmux-app-ffi does not link it, so nothing changes in the app and this PR is no FFI input.
Security
A ticket binds a question answer to exactly those answers and annotations, by digest, as in the running Swift host. Before, Rust picked nothing for such a frame, so the ticket could not redeem it at all. No method or param is newly allowed, and the default stays deny.
Changelog
none
Observed / Not run
Observed (fleet, Linux, scripts/ci/cmux-tui-rust-check.sh):
- red 491bc30e448a (the cases, no fix),
gesture_picks: job 4a1391d28e534741c122f80c, FAIL. - green 5a877ccee96c:
gesture_picks9d7d7a9962d481ba6d74a97e PASS;full_check_orderba855a870a310f1410dc80cd PASS. - head f0ff04db8d8b (the test moved to tests/gesture_pick.rs, same body):
gesture_picks22bc82b833861f1df34047de PASS,full_check_order36bfbb518b62f980b218a08f PASS,panic6565cf92c3ddee37fb2eb093 green. - clippy at the scratch ref 87e158b74000 (this head plus #31907, the base clippy fix; never merged): 3ae3fde409656c44d0833217 green.
Known differences, all fail closed (a ticket does not redeem; none reachable through check_frame today, because the answers rule refuses numbers in answers): -0 in answers digests as -0 here and 0 in Swift; acpmux's canonical_json writes 2.0 where Swift and this crate write 2; a hand-off id handoff-12- followed by a combining mark loses its label here and not in Swift (Character vs scalar split).
Not run: no Swift compile (no Swift change).