21: the ledger raises a session's state by the stricter of the grant's label and the result's own, so a clean session that recalls a memory-tainted note becomes untrusted and cannot write it into memory.md (found on straylight, 2026-09-25). 22: the answers to a note name notes.md, and for an untrusted add the file that was not changed. The new given tests are ledger_label.rs and new versions of memoryd's edit.rs and serve.rs; the old copies are kept as -task06 and -task10. On a scratch tree at the end of task 20: the named tests fail and then pass, 5 and 4 mutations are caught, and the gate passes with 944 tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
3.9 KiB
M5a task 21: a session's state follows the result's own label
Branch: m5a (run git switch m5a; git status --short must be empty, otherwise stop)
Commit subject: brokerd: the ledger raises a session's state by the stricter label
Goal
A fix for a hole the M5a device check found (2026-09-25). After every tool call, the ledger raises
the session's state (its taint and whether it has read untrusted content). It used the label of
the grant the call ran under, never the label on the result. Before M5a they were always the
same. Now memoryd can make a result stricter: a recall that finds a memory-tainted note
answers untrusted. The runner already combines the two labels, and the audit log records the
combined one, but the session stayed trusted. So a clean session could recall a note written after
a fetched page, then add it to memory.md as identity. On the device, that is what happened.
The fix: in Ledger::finish, raise the state by the stricter of the grant's label and the
result's own: the higher class, and untrusted if either is (M5a spec, section 8, as changed in
0e5b1d2).
Files
- Copy: the new test
crates/brokerd/tests/ledger_label.rs, fromdocs/plans/M5a/files/(the same path under it) - Modify:
crates/brokerd/src/ledger/answer.rs,docs/implementer-log.md
The edits, exactly
Both are in crates/brokerd/src/ledger/answer.rs. Nothing else in the file changes.
1. The line
use crate::policy::{Ask, Denial, SessionState};
becomes
use crate::policy::{Ask, Denial, Label, SessionState};
2. In finish, the lines
// 3. A result raises the state, then is recorded.
ToolResponse::Result { .. } => {
let raised = match self.state_read(&guard, &call.session) {
Ok(state) => match guard.state.raise(&call.session, state, call.label) {
become
// 3. A result raises the state, then is recorded. The state follows the stricter of
// the grant's label and the result's own: `memoryd` can make a result stricter.
ToolResponse::Result {
class, untrusted, ..
} => {
let label = Label {
class: call.label.class.max(*class),
untrusted: call.label.untrusted || *untrusted,
};
let raised = match self.state_read(&guard, &call.session) {
Ok(state) => match guard.state.raise(&call.session, state, label) {
The lines after them (Ok(raised) => raised, and on) stay as they are.
Steps
- 1. Copy the test:
cp docs/plans/M5a/files/crates/brokerd/tests/ledger_label.rs crates/brokerd/tests/ - 2. See it fail.
cargo test -p brokerd --test ledger_label. Expected: two tests fail,a_recall_of_a_tainted_note_makes_a_clean_session_untrustedanda_result_of_a_higher_class_than_its_grant_raises_the_taint. The third,a_result_laxer_than_its_grant_still_raises_the_state_by_the_grant, already passes; it guards what must not change. - 3. Make the two edits. Run
cargo fmt --all. - 4. See it pass.
cargo test -p brokerd --test ledger_label: all three pass. Thencargo test -p brokerd: every suite passes. - 5. Run the gate.
make gate, with a shell timeout of at least 600000 ms: on titan, while it serves Ornith, the gate takes longer than 2 minutes. Expected last line:gate: ok, with 944 tests (941 before this task, plus the three new ones). - 6. Log and commit.
git add crates/brokerd docs/implementer-log.md && git commit
Done when
make gateprintsgate: okwith 944 tests.
Stop and report if
- Either quoted block is not in the file exactly as shown.
- Step 2 shows a different set of failing tests than the two named.
- A test other than these three fails after the edits. Do not change a test.