Repository navigation
acpmux: a peer counts an enrolled Mac's signed group Allow once (peer-route approvals 3/5) - #26198
Conversation
…-route approvals 3/5)
The card answers tool asks as a group (_acpmux/permission_group_respond),
which a peer refused for every caller that is not its person, and the Mac's
daemon marked every forwarded group read allowRefused, so a --host chat drew
no Allow at all.
- B, the gate (hub/person.rs): allow_once from a non-person counts when
_meta.acpmux.approverDecisions holds, for EVERY pending member of the group
at the request's revision, {decisionNonce, approverDecision}: the member's
single-prompt Mac signature over its one-time allow option. A missing
member, an entry for anything else, a wrong nonce, a stale revision or one
signature that does not count refuses it all; allow_chat stays the
person's.
- B, the answer (respond_permission_group, new `person` parameter): under the
prompt state's lock, before any member is answered, every signature is
verified, then every nonce spent (approver_spends; same lock order as a
single answer).
- B, the read: a non-person reader gets macAllowOnce: true beside
allowRefused (protocol IR, spec and web types).
- A, the forward: the person here whose app gave this Mac's key reads a peer
group that says macAllowOnce with no allowRefused and no allow_chat; a
refused group answer names the peer (data.peer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Unclear: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 2457dc6 to compare; do not merge until green; see lane-rules Gallery before/after for
States
Pick other states with a |
|
Owned by an agent session · session |
|
Bead |
|
Found 184 test failures on Blacksmith runners: Failures
...and 174 more test failures. View all on Blacksmith: run 40518031710, run 40518031828
|
… every nonce at once Review of #26198: the group path used the generic approver check, which also took a phone's signature (no tool call in its message, no may_allow), and it spent members' nonces one at a time, so a revocation between two could burn earlier nonces with nothing answered. - PersonGate::macs_prove / macs_spend: only an enrolled Mac's signature counts for a group member; every member verified and every nonce spent together under one hold of the Mac set's lock (MacApprovers:: all_verify_then, PersonGate::spend_decisions), so a reload lands before or after the whole answer and a refusal spends nothing. - protocol: AnswerMeta declares approverDecision and approverDecisions (ApproverDecision, MemberApproverDecision); spec and web types regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Peer-route approvals, part 3 of 5: a peer counts an enrolled Mac's signed Allow once on a group of tool prompts. Parts 1 (#26131) and 2 (#26133) have landed; this PR is on
feat-cmux-next.The card answers tool prompts (execute, edit, read, …) in batches through
_acpmux/permission_group_respond, not throughpermission_respond. Part 1 only covered the single-prompt answer. A peer refused every group allow that did not come from its own person. This Mac's daemon also marked every forwarded group readallowRefused, so a peer-route chat's card showed only Deny ("Only the cmux app can approve this"). A live capture on a fleet-built app confirmed it.hub/person.rs):allow_oncegroup answer passes only with_meta.acpmux.approverDecisions. That must hold, for every pending member of the group at the request'srevision,{decisionNonce, approverDecision}: the member's own part-1 signature over its single allow_once option, with no answers.PersonGate::macs_prove/macs_spend). A Mac's message binds the member's tool-call id, and itsmay_allowholds every member to a plain tool ask.hub/permission_groups.rsrespond_permission_group): under the session's permission lock and one hold of the Mac set's lock (MacApprovers::all_verify_then), it verifies every member's proof and spends every nonce together (PersonGate::spend_decisions, all or none) before any member is answered. Anything else getspermission.person_required, nothing is answered and no nonce is spent._acpmux/permission_groupsread saysmacAllowOnce: truebesideallowRefused.AnswerMetadeclaresapproverDecisionandapproverDecisions(ApproverDecision,MemberApproverDecision), andPermissionGroupListdeclaresmacAllowOnce. The IR, spec and generated web types are regenerated.server/peer_forward.rs):allowRefusedandallow_chatonly when all three hold: the reader is A's own person, A holds this Mac's approver key (thisMac, set by the app), and the peer saysmacAllowOnce. The card then draws Allow once and Deny, never Allow for this chat.allowRefused.data.peer, part 2), so the app can say where to enroll.The five conditions
allow_oncecounts.allow_chatfrom a non-person is still refused, and A never draws it for a peer-route chat. Every member needs a single allow_once option, and part 1'smay_allowrefuses plans, mode switches, folder trust and questions. Groups only ever hold plain tool asks (eligible).person_required, and Deny from any client passes as before.host approvals rm/unenrollreload takes that lock, so it lands before the whole answer (nothing spent, nothing answered, every member still answerable) or after it, never between two members. The integration test removes the Mac and shows the next allow refused.Tests (they fail before the fix and pass after)
They ran remotely through
cmux-ci test.Before (the gate arm, the forward condition and the proof match neutralised):
Before the review fix (a phone's proof accepted per member; members verified and spent one at a time):
After (head of this PR):
The integration test uses a 3-member group on two hubs. Each of these is refused with
permission.person_requiredanddata.peer, and the group stays pending at its revision: unsigned, a missing member, a wrong nonce, a stale revision, an expired signature, the other option, a Mac that is not enrolled, an extra entry, allow_chat, and a Mac unenrolled by a reload. A fully signed allow resolves all 3 members, and a replay is refused.The webviews
methods.tsdrift (_acpmux/file_put_status/file_put_datamissing) is onfeat-cmux-nextalready; this PR regenerates onlytypes.tsand changes no method.No capture: daemon only. Part 5 carries the card's before and after captures from a fleet-built app of the whole stack.
Overlap (files only, not the same fix): #22030, #24797, #26127, #26093 and #25697 touch
requests.rs/peer_forward.rs.Agent session: agent session
🤖 Generated with Claude Code