Merge m3a: brokerd's decision path (M3a)

Tasks 01 to 22, the review, and task 23 (the review fixes, with a second,
independent review of them). The one conflict, docs/implementer-lessons.md,
had the same T18 and T19 on both sides; m3a's T20 to T22 follow them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 21:55:11 -07:00
co-authored by Claude Opus 5.5
212 changed files with 18352 additions and 378 deletions
+31
View File
@@ -0,0 +1,31 @@
# Task 23: the M3a review fixes
Done by the design model directly (2026-09-22): Ornith was under heavy contention, and the fixes are
small. The findings are in `docs/implementer-log.md`, "M3a, tasks 01 to 22". Most fixes land with a
test that fails without it; the rest are checked by reading, as the last column says.
| Finding | Fix | Test |
|---|---|---|
| 1 | `Writer::open`: a log with no complete record chains from the last line of the file before it, or from zero; no indexing | `brokerd/tests/audit_edges.rs`: one zero-length log file; one with a torn record only |
| 2 | One definition of a log file name, `proto::is_audit_log_name`, used by `brokerd` and `bxctl` | `proto/tests/log_names.rs`; `brokerd/tests/audit_edges.rs`: `2026-0x-19.jsonl` is neither verified nor written |
| 3 | `brokerd`'s config, directory and socket failures end with `#brokerd-start-failed`; a lost listener or a refused thread with `#brokerd-listener-lost`; both entries are new | `brokerd/tests/serve_pointers.rs` (the listener path by reading) |
| 4 | `Writer` no longer unlinks `audit/.lock` | `brokerd/tests/audit_edges.rs` |
| 5 | No `unwrap` in `audit.rs`, no `as` in `grants.rs` | gate |
| 6 | `serve` starts threads with `thread::Builder`; a refused per-connection thread closes that connection and prints a line; running out of file descriptors or memory pauses the listener (second round) | by reading |
| 7 | Every role's `main` reads `args_os`. `brokerd` and `loopd` keep the config path as a path, so one that is not UTF-8 works; `bxctl` and `inferproxy` take text and answer such an argument with their usage | `brokerd/tests/serve_pointers.rs`, `loopd/tests/args_os.rs`, `bxctl/tests/args_os.rs`, `inferproxy/tests/args_os.rs` |
| 8 | `BrokerPort` waits at most 24 hours after a pending frame, whatever `expires` says | `loopd/tests/broker_port_cap.rs` |
| 9 | `bxctl`'s admin requests time out after 30 s | `bxctl/tests/admin_timeout.rs` |
| 10 | `bxctl` escapes `retrying` errors and every `error` detail | `bxctl/tests/escape_details.rs` |
| 11 | The `the requester went away` result is recorded at the time it happens | by reading |
| 12 | `bxctl`'s usage says what `audit verify` does | `bxctl/tests/cli.rs` (unchanged) |
| 13 | `AdminError::Io`, and a failed write is returned at once | by reading |
| 14 | Not in this task: `ToolArgs::HttpFetch`'s `url` and `host` become one sealed type in M3b's first task, where the runtime starts reading `url` | — |
| 15 | `verify.rs` shares the name rule (2) and names the log-like files it did not check; `MAX_PATH`'s doc; the unused push in `grants.rs`. `find_tool` and `additionalProperties` change the baseline and wait for the next epoch change | — |
Also, because the server changed: `crates/loopd/tests/device.rs` keeps the expected server in one
constant, `EXPECT`, now four slots over one 262,144-token pool, and `tools/check-m3a-device.sh`
matches it. They stay recorded expectations rather than values read from `/props`, which would
make the self-test's own check pass by definition.
Result: six code commits (`eed0a22` to `f6841f1`), then a second round after an independent review of those (`ba369f8` to `fc8befa`; see the log). Most new tests failed before their fix; two are regression guards that passed before it; `make gate`
ok; `make verify-device` 6 of 6 and `tools/check-m3a-device.sh` ok on straylight.
@@ -53,11 +53,13 @@ pub fn open(
client
}
/// The next frame, waiting at most ten seconds.
/// The next frame, waiting at most ten seconds. Once the handler has closed its end, macOS
/// refuses the timeout with EINVAL (22); the frame is buffered by then and the read cannot block.
pub fn next(stream: &mut UnixStream) -> Envelope {
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.unwrap();
if let Err(e) = stream.set_read_timeout(Some(Duration::from_secs(10))) {
let peer_closed = cfg!(target_vendor = "apple") && e.raw_os_error() == Some(22);
assert!(peer_closed, "set_read_timeout: {e}");
}
proto::read_frame(stream).unwrap()
}