Closed general

Queryable decision history for permission prompts: a box that was away cannot learn whether a mirrored prompt was allowed or denied, or when

John Lauer · 1mo ago ·closed by John Lauer

Seen on AdomLapper 2026-09-12 (John): two mirrored approval cards from arav-rog show "closed on another surface / closed while this window was away". ah never received a decision for them, so its bell cannot say allowed or denied, by whom, or at what time. ab does send {id, decision, decidedOn} on POST /permission/dismiss when it decides (adom-bridge#175), which works when the window is up; when ah's window is away, restarting, or the event is lost, nothing lets it catch up afterwards.

Ask: a read-only history of decisions, reachable two ways:

  1. A verb, e.g. permission_history {sinceSeq | since, limit}, returning [{id, key, command/reason summary, requestedAt, decidedAt, decision (allow_once | allow_1h | allow_24h | auto | denied | expired | closed), decidedOn (box), decidedBy (surface: ab window, toast, hydrogen, mirrored box)}]. Same cursor style as notify_events.
  2. The same fields on POST /permission/dismiss (decidedAt is the missing one today), so a live window shows the time without a second call.

With that, ah fills in every "closed on another surface" row on the next open (decision + time + box) and can show it in the bell. Related: #185 (grants have no event), #188 (a grant should resolve all pending prompts).

1 Reply

John Lauer · 1mo ago

Shipped in insiders 2.1.135, both asks.

1. permission_history {sinceSeq?, since?, limit?, includeRequested?} (ungated read; also POST /command {"command":"permission_history"} on the direct API). Every prompt raised on a box and every decision is written to ~/.adom/permission-history.jsonl (append-only, 512 KB cap keeping the newer half) at the two chokepoints all prompts already pass: permission_mirror::request (raised) and permission_mirror::dismiss_from (decided). The verb returns the DECIDED rows merged with their request, cursor-style like notify_events:

{ entries: [ { seq, event: "decided", id,
               type,            // shell-approval | web-consent | mesh-join | notify-decision ...
               verb, summary,   // the command / title, <= 160 chars
               reason, server, aiThread,
               requestedAt,     // RFC3339 UTC
               decision,        // allow_once | allow_1h | allow_24h | bypass_while_running | approved | denied | expired | granted | timeout | dismissed
               decidedOn,       // box name
               decidedBy,       // "this box" | "another box"
               decidedAt } ],   // RFC3339 UTC, the origin's stamp
  count, total, nextSeq,
  pending: [ {id, type, verb} ],   // prompts still live on this box
  file }

since takes an RFC3339 UTC time; includeRequested:true adds the raw requested rows; nextSeq < sinceSeq means the file was trimmed (reset to 0). The request half survives an ab restart between the prompt and the decision (the file is read back when memory has no row).

2. POST /permission/dismiss now carries decidedAt and requestedAt next to id, decision, decidedOn (the same two fields ride perm_mirror_dismiss over the relay and perm_dismiss over the mesh). So a live window shows the time with no second call, and on the next open the bell fills every "closed on another surface" row from permission_history (decision + time + box).

Proof (arav-rog on 2.1.135, a buttoned prompt nobody answered):

notify_user {title:"#191 proof", buttons:[Yes,No], wait:true, timeoutSec:12}  -> timedOut:true after 12 s

permission_history {includeRequested:true}:
{"seq":1,"event":"requested","id":"c900825c6b2a4671","type":"notify-decision","verb":"notify_user",
 "summary":"#191 proof / do NOT click: this prompt must time out on its own","reason":"#191 proof: ...",
 "server":"<relay>","machine":"arav-rog","requestedAt":"2026-09-19T19:35:26.550Z"}
{"seq":2,"event":"decided","id":"c900825c6b2a4671","type":"notify-decision","verb":"notify_user",
 "summary":"#191 proof / do NOT click: ...","reason":"#191 proof: ...","server":"<relay>",
 "requestedAt":"2026-09-19T19:35:26.550Z","decision":"timeout","decidedOn":"arav-rog","decidedBy":"this box",
 "decidedAt":"2026-09-19T19:35:38.590Z"}
nextSeq: 3; permission_history {sinceSeq:3} -> count 0 (cursor holds); pending: []
file: C:/Users/arav/.adom/permission-history.jsonl

Note on decidedBy: it is "this box" / "another box" relative to the box that holds the history; the surface (ab window vs toast vs Hydrogen vs mirrored card) is not carried on the decision path today, so it is not claimed. decidedOn is exact.

John Lauer closed this · 1mo ago

Log in to reply.