review: memory writes from a secret session are not written to Honcho

The journal now records each call's class (absent on the owner's undo and on
older lines, which still read). The mirror turns everything a secret write
would conclude or send to the fleet into one Skip, after its deletions, which
still go; a note whose own header says secret is skipped the same way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:04:42 -07:00
co-authored by Claude Opus 5.5
parent 5f4e0d9997
commit b392af9ca4
6 changed files with 237 additions and 4 deletions
+35 -3
View File
@@ -4,7 +4,7 @@
use std::sync::{Arc, Mutex};
use bxcore::{SessionId, Timestamp};
use bxcore::{DataClass, SessionId, Timestamp};
use crate::files::Store;
use crate::honcho::ledger::{self, Action, Item, Ledger, Record, Workspace};
@@ -18,6 +18,9 @@ pub const OWNER: &str = "owner";
/// The part of an edit that has no Boxmaker session to stamp its conclusion with.
pub const NO_SESSION: &str = "the edit has no Boxmaker session";
/// Why a write from a `secret` session sends nothing but its deletions (M5c spec, section 3).
pub const SECRET_WRITE: &str = "the write is from a secret session";
/// `before` and `after` as multisets of entry text: what `before` has more of (dropped, in
/// `before`'s order), and what `after` has more of (added, in `after`'s order).
pub fn diff(before: &[String], after: &[String]) -> (Vec<String>, Vec<String>) {
@@ -138,8 +141,25 @@ fn note_actions(
.collect()
}
/// Whether `record` came from a `secret` session: by the class the journal gives the call, or, for
/// a line from before it did, by the class any of its notes was written with.
fn is_secret(record: &journal::Record, notes: &[notes::Note]) -> bool {
record.class.as_deref() == Some(notes::class_name(DataClass::Secret))
|| notes.iter().any(|n| {
matches!(
n.kind,
Kind::Noted {
class: DataClass::Secret,
..
}
)
})
}
/// What `record` asks of Honcho, in the order to send it: an identity edit's, or a write saved as
/// notes. Anything else (a rejection) asks nothing.
/// notes. Anything else (a rejection) asks nothing. From a `secret` session only the deletions
/// go, in their order, and everything else the write asked is one `Skip` after them: deleting
/// leaks nothing and keeps Honcho consistent with the files.
pub fn actions(
record: &journal::Record,
session: Option<&SessionId>,
@@ -147,10 +167,22 @@ pub fn actions(
owner: &str,
) -> Vec<Action> {
let notes = notes_of(record);
match &record.file {
let all = match &record.file {
Some(file) => identity_actions(record, file, &notes, session, agent, owner),
None => note_actions(record, &notes, session, agent),
};
if !is_secret(record, &notes) {
return all;
}
let (mut kept, held): (Vec<Action>, Vec<Action>) = all
.into_iter()
.partition(|a| matches!(a, Action::Unconclude { .. }));
if !held.is_empty() {
kept.push(Action::Skip {
why: SECRET_WRITE.to_string(),
});
}
kept
}
/// With `[honcho]`: decides each journalled edit into the ledger.