Files
boxmaker/docs/plans/M5a/21-session-follows-result.md
T
kyleandClaude Opus 5.5 f389a9a43a M5a plan: tasks 21 and 22, from the device check
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>
2026-09-25 17:55:41 -07:00

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, from docs/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_untrusted and a_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. Then cargo 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 gate prints gate: ok with 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.