mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(acp): address Thufir pass-4 review findings
- Every ask terminal routes through finish_permission(); applied path stops on false (poison) immediately instead of looping back. Cancel path uses None idle sentinel instead of dummy instant. - Deadline equality: process expired entries first at entry.deadline == hard_deadline, then return HardTimeout — fail-closed response is always written before exit. - Wire-truth tests: production-path, cancel, and nine-request tests now capture child stdin NDJSON and assert parsed exact lines/ids. Temporal tests rebuilt around one continuously running loop per scenario. - Desktop nonce-present = nonce-only: unknown nonce drops the frame without falling back to the id map. Legacy fallback keyed by compound (channel:session:turn:id), never bare id. Both indexes cleaned on every terminal (acp_write, permission_terminal) and backstop (turn_completed, turn_error). New tests: FOREIGN-nonce drop + cleanup assertions on both indexes for all four terminal paths. - NIP-AO: permission_terminal in frame-kind table; synchronous policy outcomes (rejected/allowed/allow_failed_closed) in reason table with explanatory note distinguishing ask vs. synchronous paths. - Desktop: permission_terminal handler uses pinned uncertain copy; tests for live replay and lifecycle-only archive replay. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
+19
-3
@@ -124,12 +124,22 @@ below). It is omitted on all other frame kinds.
|
||||
| `turn_completed` | Terminal lifecycle — emitted when a turn ends (success, cancel, or timeout) |
|
||||
| `turn_error` | Terminal lifecycle — emitted when a turn ends with an error or process death |
|
||||
| `control_result` | Acknowledgement telemetry emitted after processing a control frame |
|
||||
| `permission_terminal` | Observer-only terminal for uncertain permission outcomes (process poison or cancel-during-write). No ACP wire response was confirmed. Carries an `authorization` envelope with `reason = "uncertain"`. Desktop uses this to retire the card without a JSON-RPC response. |
|
||||
|
||||
Permission `acp_read` frames (carrying `session/request_permission` calls) always
|
||||
include an `authorization` envelope. The corresponding `acp_write` (the harness
|
||||
response) also includes an `authorization` envelope correlated by the same nonce —
|
||||
this pairs the challenge and answer in the observer log.
|
||||
|
||||
Synchronous policy outcomes (`reject`, `allow`, preflight denial) also produce
|
||||
`acp_write` frames with `authorization` envelopes. Their `reason` values are:
|
||||
|
||||
| Policy path | `reason` |
|
||||
|-------------|----------|
|
||||
| `reject` policy, preflight denial, ask-unavailable downgrade | `"rejected"` |
|
||||
| `allow` policy (auto-approval succeeded) | `"allowed"` |
|
||||
| `allow` policy (fail-closed, no unique allow_once option) | `"allow_failed_closed"` |
|
||||
|
||||
**One-write / one-observe contract.** Each pending permission entry produces at most
|
||||
one ACP wire write and at most one authorized `acp_write` observer event. The write
|
||||
and the observer event are always emitted together; if the write fails the observer
|
||||
@@ -165,10 +175,16 @@ call, the `ObserverEvent` carries an `authorization` field:
|
||||
| `"applied"` | Owner decision was received and written to the agent pipe. |
|
||||
| `"timed_out"` | No decision arrived before the 300-second per-request deadline; request failed closed (denial). |
|
||||
| `"cancelled"` | The turn was cancelled while the request was pending; request failed closed (denial). |
|
||||
| `"rejected"` | `reject` policy, preflight denial, or ask-unavailable downgrade; request denied synchronously without an actionable card. |
|
||||
| `"allowed"` | `allow` policy auto-approval succeeded; request granted synchronously. |
|
||||
| `"allow_failed_closed"` | `allow` policy but no unique `allow_once` option available; request denied synchronously. |
|
||||
|
||||
The `uncertain` terminal (cancel arriving while the write is in flight) does NOT
|
||||
produce an `acp_write` observer event — instead the harness emits a
|
||||
`permission_terminal` observer event with `authorization.reason = "uncertain"` so
|
||||
`"rejected"`, `"allowed"`, and `"allow_failed_closed"` are emitted on `acp_write` frames
|
||||
for synchronous policy paths (see [Synchronous policy outcomes](#synchronous-policy-outcomes)).
|
||||
They are NOT emitted for `ask`-policy pending-map entries.
|
||||
|
||||
The `uncertain` outcome does NOT produce an `acp_write` observer event — instead the
|
||||
harness emits a `permission_terminal` observer event with `authorization.reason = "uncertain"` so
|
||||
Desktop clients can retire the card without an ACP wire response. The process is
|
||||
irrecoverably poisoned and will be respawned by the pool. Desktop clients MUST NOT
|
||||
expect an `acp_write` for every `acp_read` they receive; the corresponding
|
||||
|
||||
Reference in New Issue
Block a user