M3a spec: a ledger module and a two-process end-to-end test
Both came out of planning the tasks. The three locked steps need a home that is neither the connection handler nor the pending table. The one-process end-to-end test would have made loopd dev-depend on brokerd, which the crate-dependency gate forbids. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ in `docs/specs/2026-09-17-pre-m1-design.md`; section 6 of this document replaces
|
|||||||
| Any edit, deletion, reordering or gap in the audit log is found, with its file and line | Tampering suite, through `bxctl audit verify` and `brokerd`'s startup |
|
| Any edit, deletion, reordering or gap in the audit log is found, with its file and line | Tampering suite, through `bxctl audit verify` and `brokerd`'s startup |
|
||||||
| `loopd` cannot approve: the tool socket refuses every admin message | Socket tests |
|
| `loopd` cannot approve: the tool socket refuses every admin message | Socket tests |
|
||||||
| An approval only lets through a call that is still `ask` when approved | Approval tests |
|
| An approval only lets through a call that is still `ask` when approved | Approval tests |
|
||||||
| A denial reaches the model as a plain tool result and the turn goes on | End-to-end test in one process; on straylight with Ornith |
|
| A denial reaches the model as a plain tool result and the turn goes on | End-to-end test with the real `brokerd` binary; on straylight with Ornith |
|
||||||
| Every fail-closed state's message names a runbook entry, and every entry named exists | A test per state for the pointer; the gate script for the entry |
|
| Every fail-closed state's message names a runbook entry, and every entry named exists | A test per state for the pointer; the gate script for the entry |
|
||||||
|
|
||||||
Out of scope: running tools, containers, the egress proxy, tool timeouts (all M3b); secrets
|
Out of scope: running tools, containers, the egress proxy, tool timeouts (all M3b); secrets
|
||||||
@@ -49,6 +49,7 @@ Each is one file under 500 lines with one purpose. Only `serve` starts threads.
|
|||||||
| `state` | Each session's taint and untrusted flag, read and written as files. |
|
| `state` | Each session's taint and untrusted flag, read and written as files. |
|
||||||
| `audit` | The audit writer: lock, chain, sync, rollover, startup check, recovery, accepted breaks. |
|
| `audit` | The audit writer: lock, chain, sync, rollover, startup check, recovery, accepted breaks. |
|
||||||
| `approvals` | The table of pending approvals and their expiry. In memory only. |
|
| `approvals` | The table of pending approvals and their expiry. In memory only. |
|
||||||
|
| `ledger` | The audit writer and the session state behind one lock, and the three steps that hold it (section 2, "Threads and locks"). |
|
||||||
| `runner` | `Runtime`, `RunSpec`, and `run(decision, runtime)`. The only place a `RunSpec` is built. |
|
| `runner` | `Runtime`, `RunSpec`, and `run(decision, runtime)`. The only place a `RunSpec` is built. |
|
||||||
| `broker` | Handles one `broker.sock` connection: one tool request from decision to answer. |
|
| `broker` | Handles one `broker.sock` connection: one tool request from decision to answer. |
|
||||||
| `admin` | Handles one `admin.sock` connection: one admin request. |
|
| `admin` | Handles one `admin.sock` connection: one admin request. |
|
||||||
@@ -821,10 +822,16 @@ implementation, the oracle, or a compiling skeleton) is in section 15.
|
|||||||
`y` refuses and the id approves; a line already waiting in stdin refuses; an id missing from the
|
`y` refuses and the id approves; a line already waiting in stdin refuses; an id missing from the
|
||||||
list prints "no longer pending" and asks nothing; model content holding `ESC[8m` comes out
|
list prints "no longer pending" and asks nothing; model content holding `ESC[8m` comes out
|
||||||
escaped.
|
escaped.
|
||||||
- **End to end, in one process.** The fake llama server, `loopd`'s turn loop, and the `brokerd`
|
- **End to end, in two processes.** A test in `loopd` starts the real `brokerd` binary (its path
|
||||||
library with the recording runtime. The scripted model calls `call_tool` for `read_file` with no
|
comes from `BOXMAKER_BROKERD`; the test is `#[ignore]`d without it, and `make gate` builds the
|
||||||
grant; its next request contains "Denied: no grant allows this call."; the audit log holds one
|
workspace and then runs it with the variable set) on a temporary home, and runs `loopd`'s turn
|
||||||
`Decision` record with `no_grant`.
|
loop against the fake llama server with a `BrokerPort` on that `brokerd`'s socket. The scripted
|
||||||
|
model calls `call_tool` for `read_file` with no grant; its next request contains "Denied: no
|
||||||
|
grant allows this call."; the audit directory, read with `proto::ChainVerifier`, verifies and
|
||||||
|
holds one record, a `Decision` with `no_grant`. It cannot be one process: that would make `loopd` depend on
|
||||||
|
`brokerd`, even as a dev-dependency, and no crate may depend on another role's crate
|
||||||
|
(`scripts/check-crate-deps.sh` checks dev-dependencies too). Two processes over the real socket
|
||||||
|
is also the better test.
|
||||||
- **The runbook script**, with its self-test.
|
- **The runbook script**, with its self-test.
|
||||||
|
|
||||||
**On straylight** (a scripted check, not part of `verify-device`): the script first reads
|
**On straylight** (a scripted check, not part of `verify-device`): the script first reads
|
||||||
|
|||||||
Reference in New Issue
Block a user