Skip to content

agent-pane-policy: the gesture pick is the Swift host's: hand-off prompt ids, answer digests, Double numbers - #31915

Open
lawrencecchen wants to merge 12 commits into
feat-cmux-nextfrom
rust-migration/pane-policy-gesture-pick
Open

lawrencecchen wants to merge 12 commits into
feat-cmux-nextfrom
rust-migration/pane-policy-gesture-pick

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

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, 07efced). The crate used the raw id, so the ticket of a Cloud hand-off prompt never redeemed.
  • Question answers: a permission answer's answers and annotations are picked by the sha256 of their canonical JSON (AcpmuxSecretKey.canonicalDigest, 8c3e829). 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 1 and 1.0 give the same pick.

What changes in the crate:

  • src/canonical.rs ports canonicalJSON/canonicalDigest. These are the same bytes as acpmux's approvers::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 Swift AgentPaneGesturePick: own_prompt_id, the digests, a null answers is no key, and as_double.
  • The test is tests/gesture_pick.rs, with 24 cases in tests/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.json case now expects the answers digest, as Swift gives it, not "no pick". I renamed it to say so.
  • sha2 is the workspace's 0.10.9. cmux-tasks-core already brings it into the app FFI lock, so no new package goes into the app. cmux-tui/Cargo.lock gains 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):

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).

lawrencecchen and others added 4 commits November 2, 2026 05:50
The Swift host's AgentPaneGesturePick binds a ticket to a hand-off
prompt's own id (AgentPaneHandoffPrompt.ownID, 07efced), to a
question's answers and annotations by the sha256 of their canonical JSON
(AcpmuxSecretKey.canonicalDigest, 8c3e829; the same bytes as acpmux
approvers::canonical_json), skips a null answers, and compares numbers
as Double. The crate uses the raw prompt id and picks nothing for a
frame with answers, so a ticket for a question answer or a hand-off
prompt never redeems. tests/cases/picks.json (24 cases; the expected
values from a line-for-line Python copy of the Swift code, which also
asserts Swift's AgentPaneRelayedAllowTests canonical JSON vectors and
acpmux's the_wire_payload_is_fixed digest) and parity.rs gesture_picks;
check.json's ticketed question answer now expects its answers digest.

Co-Authored-By: Claude <noreply@anthropic.com>
…at it now expects

Co-Authored-By: Claude <noreply@anthropic.com>
…f prompt ids, answer digests, Double numbers

GesturePick now matches Swift AgentPaneGesturePick:
- a session/prompt picks its own id (own_prompt_id, a port of
  AgentPaneHandoffPrompt.ownID: one handoff-<turn>- label stripped);
- a permission answer's answers and annotations are picked by the
  sha256 of their canonical JSON (src/canonical.rs, a port of
  AcpmuxSecretKey.canonicalJSON/canonicalDigest, the same bytes as
  acpmux approvers::canonical_json); a null one is no key; one that is
  not an object or has no canonical form picks nothing;
- a number param is held as a Double, so 1 and 1.0 pick the same.
sha2 is the workspace's 0.10.9, already in the app FFI lock
(cmux-tasks-core): no new package.

Co-Authored-By: Claude <noreply@anthropic.com>
…esture_pick.rs

So this branch adds nothing at the end of tests/parity.rs, where #31846
and #31856 each add one test (lead, conflict risk). Same test body.

Co-Authored-By: Claude <noreply@anthropic.com>
@lawrencecchen lawrencecchen added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review f0ff04d: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr31915 -f ref=rust-migration/pane-policy-gesture-pick -f platform=macos -f backend_mode=local

web and size checks: green

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (subagent, correctness first): SHIP at f0ff04d
reviewer-session: ac97a827-55a9-4e0a-877b-a42049e11b5c
author-session: rm-pane-policy
R9: pass
perf claim: none
Compared src/canonical.rs, GesturePick::new, as_double and own_prompt_id line by line with Swift AcpmuxSecretKey.canonicalJSON/canonicalDigest, AgentPaneGesturePick, AgentPaneJSONScalar and AgentPaneHandoffPrompt.ownID, and canonical.rs with acpmux approvers::canonical_json. Key order, escapes (lower-case \u00xx), raw non-ASCII, null/non-object/no-digest answers, extra params, missing ids, 1 vs 1.0 vs true, null and nested params: all equal. I recomputed every digest in picks.json and the changed check.json case with my own script: all match (44ba30ad... is acpmux's fixed vector).
Security: a pick holds the sha256 of the exact answers/annotations, so a ticket for one answer never redeems another; no digest or a non-object picks nothing; no method or param is newly allowed. sha2 is the workspace 0.10.9; the lock adds one dependency line and no package; cmux-app-ffi does not link the crate.
Non-blocking, all fail closed and not reachable through check_one (the answers rule refuses numbers in answers/annotations before the pick): (a) -0 or -0.0 in answers gives "-0" in Rust and "0" in Swift (Rust prints the sign of negative zero); (b) an integral float literal that serde_json's parser rounds differently from Foundation could give no digest in Rust; (c) own_prompt_id splits at the first '-' code point, Swift at the first '-' Character, so "handoff-12-" followed by a combining mark is stripped in Rust and not in Swift (picks.py copies the Rust behavior here). The crate stays the same prompt per acpmux named_turn, so no ticket redeems another prompt.
Jobs: red 4a1391d28e53 FAIL gesture_picks at 491bc30; green 9d7d7a9962d4 gesture_picks and ba855a870a31 full_check_order at 5a877cc (src equal to the head); head: gesture_picks 22bc82b83386 PASS, panic 6565cf92c3dd green (ratchet 81 crates), full_check_order 36bfbb518b62 still running at review time; clippy 3ae3fde40965 green at scratch ref 87e158b.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Evidence for ready-to-land at f0ff04d: SHIP receipt #31915 (comment); red at 491bc30 (4a1391d28e534741c122f80c: gesture_picks fails); green at this head: gesture_picks 22bc82b833861f1df34047de 1/1, full_check_order 36bfbb518b62f980b218a08f 1/1, panic 6565cf92c3ddee37fb2eb093 green; clippy green at the scratch ref 87e158b = this head + #31907 (3ae3fde409656c44d0833217), since feat's clippy is red until #31907 lands. No FFI input; no Swift change, so no Mac Swift suite applies.

@lawrencecchen lawrencecchen added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Nov 2, 2026
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (independent review subagent): SHIP at 9f5bc0f

Re-review after the cmux-bot merge of feat-cmux-next (previous SHIP at f0ff04d). Read-only; I ran no build or test.

  1. The PR diff did not change. f0ff04db8d8b over its merge base 14a5e1b15367 and 9f5bc0faa51b over its merge base 6bf4b1675f8b give the same patch on all 8 PR files (index lines ignored). The whole head diff over the base touches only those 8 files.
  2. The merge is clean. git merge-tree f0ff04db8d8b 6bf4b1675f8b gives a tree equal to the head's tree, so nobody resolved a conflict by hand. git merge-tree origin/feat-cmux-next HEAD is clean (rc 0) at feat 18dadc3e4db6, and no newer feat commit touches the crate. The merge brought in base red: the pane policy lists the guest-queue methods the Swift host allows (#31144) #31794 (queue_approve/decline/get in policy.json), agent-pane-policy: the first frame drops the page's personKey, as the Swift host does (cx-fcaq) #31857 (personKey dropped in frame.rs) and agent-pane-policy: Claude Code's own AskUserQuestion gives its answer keys, as the Swift host reads them (#28129) #31856 (AskUserQuestion keys in gesture.rs, question_keys.json, parity.rs). These files are different from the PR's files (check.rs, canonical.rs, lib.rs, check.json, picks.json, gesture_pick.rs), and the feat delta is in the head exactly as it is on feat. frames.json and question_keys.json have no pick, and no check.json case uses the queue_* methods. The one check.json case that this PR changed (a ticketed question answer that picks by digest 2790eafc…) is unchanged. Only picks.json has a numeric pick param (the Double case), so the as_double change breaks no feat case. _acpmux/queue_approve ("always"; scalars sessionId/promptId/turnId/revision) goes through the generic scalar pick. That is the same path as Swift (the hand-off label is removed only for session/prompt).
  3. No new package. The only lock change is + "sha2 0.10.9" in the cmux-agent-pane-policy entry of cmux-tui/Cargo.lock. That version was already in the lock, and the dependency is sha2.workspace = true. cmux-tui/crates/cmux-app-ffi/Cargo.lock did not change, and it already has sha2 0.10.9.
  4. Security: default deny is kept. The PR does not change policy.json. The merge added no method, field or answer key through this PR; the queue_* and AskUserQuestion keys come from feat's own reviewed PRs. The pick rules stay tight. A prompt sheds at most one valid handoff-<1..15 digits, no leading 0>- label, and an id with two labels stays as it is. Answers and annotations are bound by the sha256 of exactly the canonical JSON that was sent. A non-object or a non-canonical number picks nothing, so the ticket does not redeem it (fails closed).
  5. Red/green proof. The PR's own crate files are byte-equal to f0ff04d, and the proof there is still good for them. The crate tree as a whole is different, but only by the feat files listed above, which these tests do not exercise. On this exact head, CI clippy (changed cmux-tui packages) (cargo clippy --all-targets, which compiles the crate and its tests), rustfmt and rust workspaces are green. Not run: the crate's tests (gesture_picks, full_check_order, question_answer_keys) were not run again on 9f5bc0f.

@lawrencecchen lawrencecchen removed the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Nov 2, 2026
@blacksmith-sh

This comment has been minimized.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Review (codex gpt-6.1-sol xhigh, next-review): SHIP at 6e6efc5

SHIP at 6e6efc5
Visual verdict: no images; Leo tries the build

Compared the changed paths with Swift and independently verified all 24 pick fixtures and existing full-check picks. Rust tests could not run because the box has no Rust toolchain.

Follow-ups: none

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Review (codex gpt-6-astra high, next-review): SHIP at 8b479af

SHIP at 8b479af

Visual verdict: unchanged by this diff

Reviewed the Swift parity paths and canonicalization. No correctness findings.

Follow-ups: none

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 2, 2026 18:10 Inactive
@blacksmith-sh

blacksmith-sh Bot commented Nov 2, 2026

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
the short nightly decide gate must use the pooled Linux route with a Blacksmith fallbac
k/
the short nightly decide gate must use the pooled Linux route with a Blacksmith fallbac
k
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Review (codex gpt-6.1-sol xhigh, next-review): SHIP at 0a313a3

SHIP at 0a313a3
Visual verdict: no images; Leo tries the build

Compared against Swift and independently verified all 24 pick fixtures and canonical digest vectors. Cargo tests were not run because the box has no Rust toolchain.

Follow-ups: none

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Review (codex gpt-6-astra high, next-review): SHIP at 8a6baed

SHIP at 8a6baed

Visual verdict: unchanged by this diff

Swift behavior, canonicalization, and added vectors match; supplied hosted checks are green.

Follow-ups: none

This branch was previously deployed

3 inactive deployments
ci-routing-agent — 8a6baedf Deployed Nov 2, 2026 by github-actions[bot] via cmux-next macOS placement #63691
artifacts — 8a6baedf Deployed Nov 2, 2026 by cmux-bot[bot] via publish tree-addressed cmux-tui #102459
sentry-dsyms — 8a6baedf Deployed Nov 2, 2026 by cmux-bot[bot] via upload cmux-tui dSYMs to Sentry #102459
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants