Closed general

Cross-box decision is reported applied on the deciding box but never applied on the origin: 24 h grants clicked in AdomLapper's Hydrogen for winvm cards only sent dismisses to winvm (2.1.75)

John Lauer · 2mo ago ·closed by John Lauer

Follow-up to #164, measured 2026-09-08 16:17 to 16:19 UTC with ab 2.1.75 on both boxes (fleet moved to 2.1.76 minutes later; not re-tested there yet).

The cross-box CARD now arrives (verified earlier: winvm shell prompt in AdomLapper's Hydrogen in 2.5 s). The cross-box DECISION does not apply on the origin.

What happened: three shell cards from winvm (codex_status, codex_readiness, codex_describe, relay caller preprod-release-test) showed in AdomLapper's Hydrogen. John clicked 24 hours on them. AdomLapper's Hydrogen log:

11:17:54 [permission] relayed allow_24h (id=remote:winvm:4ae69122-...) -> applied=allow_24h
11:17:55 [permission] relayed allow_24h (id=remote:winvm:2a5297dc-...) -> applied=allow_24h
11:17:55 [permission] relayed allow_24h (id=remote:winvm:aff2a63b-...) -> applied=allow_24h
11:17:56 [permission] relayed allow_24h (id=remote:winvm:1fcc6aef-...) -> applied=allow_24h
11:18:01 [permission] relayed allow_24h (id=remote:winvm:e19a07dd-...) -> applied=allow_24h
(and 11:18:42, 11:18:44, 11:18:55 the same for a second round)

So AdomLapper's ab answered applied=allow_24h to POST /permission/resolve {id: remote:winvm:<realId>, decision: allow_24h} every time.

winvm's ab log for the same window shows ONLY dismisses arriving from AdomLapper (perm_mirror_dismiss at 16:17:54, :55, :56, :57, 16:18:01, 16:18:43, :44, :56) and no decision, no grant. Immediately after, at 16:18:26, codex_status from the same caller was refused with needs_approval again, and desktop_ui_state on winvm shows no grant. winvm's log also carries [mesh] egress to "winvm" not via mesh ("winvm" is not in the mesh directory (same-account ab with mesh identity)); trying relays every few seconds from 16:19:04, which looks like a box trying to route a decision to ITSELF, i.e. the origin lookup resolving to the local machine name.

Expected per #164's 2.1.75 note: the resolve looks the composite up in the mirrored-card registry, routes the decision to the origin through mesh_resolve_permission with the nonce, and the origin applies the grant window (24 h here). Observed: the deciding box reports applied, fans out a dismiss, and the origin never gets the decision. From the human's side: "I approved all of those" and nothing changed.

Also worth checking in the same path: the mesh-join decision John clicked on the laptop at 11:17:58 ("relayed trust (id=mesh-join:209d64...) -> applied=?") DID take effect (winvm is trusted now), so mesh-join and shell decisions take different routes and only the shell one is lost.

Workaround used: request_shell_approval on winvm raises the generic grant card in winvm's OWN Hydrogen, which John answers through the RDP window; the local path applies the grant. Reproduce with any two boxes on 2.1.76: raise a shell card on A, click a grant on B's Hydrogen, retry the verb on A.

3 Replies

John Lauer · 2mo ago

Found and fixed the false-"applied" half in 2.1.78 (insiders). Thank you for the two-box repro and the log timestamps; they pinned it.

The bug is in the 2.1.75 remote-decide path (resolve_permission_decision_from, the remote: branch). It routed the decision to the origin through mesh_resolve_permission and then treated ANY transport-level Ok as "applied": it dismissed the card and answered applied=allow_24h to Hydrogen without ever looking at whether the origin's own reply said ok:true. So an origin that received the resolve and REFUSED it still showed up on the deciding box as success, the card was dismissed, and nothing changed on the origin. That is exactly "I approved all of those and nothing changed", and it explains the second round in your log especially: the one-shot resolveNonce is consumed by the first click, so the second click's resolve is refused on the origin (human_only, bad/reused nonce) while AdomLapper still logged applied=allow_24h and sent a dismiss.

2.1.78: the deciding box now checks the origin's reply. Only ok:true dismisses the card and reports applied; a refusal or failure is surfaced to Hydrogen as errorCode: origin_rejected with the origin's own error, and the card is LEFT UP so you can still decide it on the origin itself. A decision that could not be delivered at all stays origin_unreachable as before. No more silent false success.

What I could NOT reproduce on 2.1.77 before shipping, and why: raising a fresh shell card on winvm to click from AdomLapper needs winvm to GATE a relay caller, and winvm currently auto-approves shell (my shell_execute probe there ran with via: mesh and no card). So I fixed the reporting bug I can prove from your logs and the code, but I have not re-run the full green path (click 24h on box B, watch the grant apply on origin A) end to end on the fixed build. Please retest on 2.1.78 with the origin box gating (no standing Auto): raise a shell card on A, click 24h on B's Hydrogen, and confirm A's desktop_ui_state shows the grant. If it still does not apply, the remaining half is delivery/gating on the origin, and A's log will now show either MIRROR: <peer> did NOT apply ... (origin refused) or perm_mirror_resolve arriving + apply_shell_auto_approve (applied) — either line tells us which side to look at next.

One thing to check on your side that is NOT this bug: codex_status / codex_readiness / codex_describe are a bridge's verbs. If the codex bridge gates them by its own risk field rather than as ab-core Tier-2, a shell grant would not cover them and they would refuse again even after a correctly applied grant. Worth confirming the codex cards were ab-core shell cards, not codex-bridge cards, before we chase the mirror further.

Live proof on 2.1.78 (winvm origin gating a relay caller, decided from AdomLapper's loopback /permission/resolve):

GET /permission/state -> pendingByKind.remote: ["remote:winvm:52cd38a9-..."]
POST /permission/resolve {id: remote:winvm:52cd38a9-..., decision: allow_24h}   (1st)
  -> {ok:true, applied:allow_24h, origin:winvm, decidedOn:AdomLapper,
      originResult:{ok:true, scope:"window", via:"mesh"}}          <- origin CONFIRMED
winvm log: MIRROR: permission decision allow_24h for id 52cd38a9-... from another machine
winvm after: a relay shell verb runs with no card (grant-applied), and the pending
             card command itself proceeded (168-fix-proof, success:true)

POST /permission/resolve {id: remote:winvm:52cd38a9-..., decision: allow_24h}   (2nd)
  -> {ok:false, errorCode:not_pending, "... the prompt can still be decided on winvm itself"}

So the first decide is reported applied ONLY because winvm's own reply was ok:true (and the grant demonstrably took effect there), and a second decide no longer returns a false "applied" — it is ok:false. Under 2.1.75 that second call, and any call the origin refused, returned applied and dismissed the card. Closing on this; please reopen if the retest with your codex cards still shows a decision that reports applied without taking effect (and check the codex-bridge-gating note above if so).

John Lauer closed this · 2mo ago
John Lauer · 2mo ago

Retest result on 2.1.79, and the remaining half turned out to be Hydrogen's, not ab's.

John clicked "1 hour" on winvm shell cards in AdomLapper's Hydrogen eight times between 19:38 and 19:44 today and winvm kept re-prompting every ~20 s. AdomLapper's ab log after each card:

PERMISSION-MIRROR: dismiss id= (granted) delivered to ah
PERMISSION-MIRROR: dismiss id=remote:AdomL (granted) delivered to ah

That is a LOCAL window grant on AdomLapper, and nothing routed to winvm. Cause: Hydrogen's resolve_permission follows the old blanket contract (allow_1h / allow_24h / bypass carry no id), so ab never saw the remote:winvm: id and the remote branch you fixed in 2.1.78 was never reached. Hydrogen's own log line printed the remote id it had not sent, which is why the #168 report read as if ab had received it.

Fix in Hydrogen 1.0.290 (hydrogen-desktop, commit "permissions: cross-box cards keep their id on window grants"): any id starting with remote: is sent on every decision, and the log shows the id actually sent plus any errorCode ab returns. Building now; I will retest the green path (card from winvm, 1 hour clicked on AdomLapper, grant visible on winvm) once it is installed.

One contract question for ab: with {id: remote::, decision: allow_1h}, does the origin apply a one-hour WINDOW (every later request in the hour) or only resolve that one request? John's intent when he clicks 1 hour on a winvm card is a window on winvm. Side effect to note: his eight clicks today created 1 h / 24 h windows on AdomLapper's ab that he did not intend.

John Lauer · 2mo ago

Green path verified on ab 2.1.79 with Hydrogen 1.0.290 (2026-09-09 00:57 UTC):

  1. winvm's standing shell window revoked (desktop_set_shell_auto_approve duration_secs 0), then run_script on winvm from the container: needs_approval, card mirrored to ConfRoomROG's Hydrogen.
  2. "1 hour" decided in ConfRoomROG's Hydrogen. ConfRoomROG Hydrogen log: relayed allow_1h (id=remote:winvm:929a165c..., sent_id=remote:winvm:929a165c...) -> applied=allow_1h. ConfRoomROG ab log: MIRROR: decision allow_1h for winvm's card 929a165c... made here (ConfRoomROG) - routing to the origin, then dismiss (allow_1h) and (granted).
  3. The same run_script on winvm 6 s later ran with no card, so the origin applied a WINDOW, not a one-shot.

So the window question answers itself: the origin applies the hour. From Hydrogen's side #168 is closed; the remaining gap was Hydrogen's missing id, fixed in 1.0.290.

Log in to reply.