brokerd: the ledger raises a session's state by the stricter label

M5a task 21. The work is Ornith's, from the stopped run; the design
model checked it against the task file and committed it after finding
that the gate's failure came from its own reference worktree's stale
test binaries in the shared target/ (fixed with cargo clean; 944 tests).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 20:20:48 -07:00
co-authored by Claude Opus 5.5
parent f389a9a43a
commit 7b95937c88
3 changed files with 135 additions and 4 deletions
+11 -4
View File
@@ -6,7 +6,7 @@ use proto::{ApprovalAnswer, AuditEvent, DataClass, DenyReason, Timestamp, ToolRe
use super::{Answer, Answered, Call, Grants, Inner, Ledger};
use crate::approvals::Verdict;
use crate::policy::{Ask, Denial, SessionState};
use crate::policy::{Ask, Denial, Label, SessionState};
impl Ledger {
/// Re-decide an approval and record it.
@@ -159,10 +159,17 @@ impl Ledger {
};
match &response {
// 3. A result raises the state, then is recorded.
ToolResponse::Result { .. } => {
// 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, call.label) {
Ok(state) => match guard.state.raise(&call.session, state, label) {
Ok(raised) => raised,
Err(e) => {
(self.log)(&format!("brokerd: {e}"));
+123
View File
@@ -0,0 +1,123 @@
//! The ledger raises a session's state by the stricter of the grant's label and the result's own
//! (M5a spec, section 8). `memoryd` can make a `recall` result untrusted; if the session did not
//! follow, a clean session could recall a tainted note and then write it into `memory.md`. Do not
//! edit.
#[path = "support/build.rs"]
mod build;
#[path = "support/rig.rs"]
mod rig;
#[path = "support/sink.rs"]
mod sink;
#[path = "support/tmp.rs"]
mod tmp;
use brokerd::ledger::Call;
use brokerd::policy::{Label, SessionState};
use build::now;
use proto::{AuditEvent, CallId, DataClass, SessionId, ToolResponse};
use rig::Rig;
fn label(class: DataClass, untrusted: bool) -> Label {
Label { class, untrusted }
}
fn call(label: Label) -> Call {
Call {
session: SessionId::new("s1").unwrap(),
call: CallId(1),
decision: 0,
label,
}
}
fn result(label: Label) -> ToolResponse {
ToolResponse::Result {
content: "[1] memory-tainted · note".to_string(),
class: label.class,
untrusted: label.untrusted,
truncated: false,
}
}
fn state(rig: &Rig) -> SessionState {
rig.state().read(&SessionId::new("s1").unwrap()).unwrap()
}
/// The one result record's (class, untrusted, taint_after).
fn recorded(rig: &Rig) -> (DataClass, bool, DataClass) {
match rig.events().as_slice() {
[
AuditEvent::Result {
class,
untrusted,
taint_after,
..
},
] => (*class, *untrusted, *taint_after),
other => panic!("{other:?}"),
}
}
#[test]
fn a_recall_of_a_tainted_note_makes_a_clean_session_untrusted() {
let rig = Rig::new("label-recall");
let ledger = rig.ledger();
// `recall`'s built-in grant labels its results private and trusted; `memoryd` found a
// `memory-tainted` hit and said untrusted.
let found = label(DataClass::Private, true);
let answer = ledger.finish(
&call(label(DataClass::Private, false)),
result(found),
now(),
);
assert_eq!(answer, result(found), "passed on unchanged");
assert_eq!(
state(&rig),
SessionState {
taint: DataClass::Private,
untrusted: true
}
);
assert_eq!(
recorded(&rig),
(DataClass::Private, true, DataClass::Private)
);
}
#[test]
fn a_result_of_a_higher_class_than_its_grant_raises_the_taint() {
let rig = Rig::new("label-class");
let ledger = rig.ledger();
let found = label(DataClass::Secret, false);
ledger.finish(&call(label(DataClass::Public, false)), result(found), now());
assert_eq!(
state(&rig),
SessionState {
taint: DataClass::Secret,
untrusted: false
}
);
assert_eq!(
recorded(&rig),
(DataClass::Secret, false, DataClass::Secret)
);
}
#[test]
fn a_result_laxer_than_its_grant_still_raises_the_state_by_the_grant() {
let rig = Rig::new("label-laxer");
let ledger = rig.ledger();
ledger.finish(
&call(label(DataClass::Secret, true)),
result(label(DataClass::Public, false)),
now(),
);
assert_eq!(
state(&rig),
SessionState {
taint: DataClass::Secret,
untrusted: true
}
);
}