memoryd: note answers name notes.md and the file that was not changed

This commit is contained in:
2026-09-25 20:32:29 -07:00
parent 7b95937c88
commit e963ef72bb
4 changed files with 30 additions and 14 deletions
+19 -7
View File
@@ -57,9 +57,21 @@ pub fn untrusted_refusal(file: MemoryFile) -> String {
) )
} }
/// The answer when an untrusted session's `add` became a note. /// The answer when an untrusted session's `add` became a note. It names the file it did not change:
pub const SAVED_AS_NOTE: &str = /// on the device the model read a shorter answer as success.
"saved as a note (untrusted): this session has read untrusted content"; pub fn saved_as_note(file: MemoryFile) -> String {
format!(
"not added to {}: this session has read untrusted content, so it was saved in notes.md as an untrusted note",
file.name()
)
}
/// The answer to a `note` from a session that has not read untrusted content.
pub const NOTED: &str = "saved in notes.md";
/// The answer to a `note` from a session that has read untrusted content.
pub const NOTED_UNTRUSTED: &str =
"saved in notes.md, marked untrusted: this session has read untrusted content";
fn stale(file: MemoryFile) -> String { fn stale(file: MemoryFile) -> String {
format!( format!(
@@ -97,13 +109,13 @@ pub fn apply(store: &Store, args: &MemoryArgs, ctx: &Context) -> Result<Outcome,
| MemoryArgs::Consolidate { file, .. } => *file, | MemoryArgs::Consolidate { file, .. } => *file,
}; };
// 3. When `ctx.untrusted`: an Add becomes `noted(store, ctx, text, "add")?` with its answer // 3. When `ctx.untrusted`: an Add becomes `noted(store, ctx, text, "add")?` with its answer
// replaced by SAVED_AS_NOTE; any other action -> Err(untrusted_refusal(file)). // replaced by saved_as_note(file); any other action -> Err(untrusted_refusal(file)).
if ctx.untrusted { if ctx.untrusted {
match args { match args {
MemoryArgs::Add { text, .. } => { MemoryArgs::Add { text, .. } => {
let outcome = noted(store, ctx, text, "add")?; let outcome = noted(store, ctx, text, "add")?;
return Ok(Outcome { return Ok(Outcome {
answer: SAVED_AS_NOTE.to_string(), answer: saved_as_note(file),
..outcome ..outcome
}); });
} }
@@ -222,9 +234,9 @@ fn noted(
Ok(Outcome { Ok(Outcome {
action, action,
answer: if ctx.untrusted { answer: if ctx.untrusted {
"noted (untrusted)" NOTED_UNTRUSTED
} else { } else {
"noted" NOTED
} }
.to_string(), .to_string(),
change: Change { change: Change {
+7 -4
View File
@@ -4,7 +4,7 @@
#[path = "support/tmp.rs"] #[path = "support/tmp.rs"]
mod tmp; mod tmp;
use memoryd::edit::{Context, SAVED_AS_NOTE, apply}; use memoryd::edit::{Context, apply};
use memoryd::files::Store; use memoryd::files::Store;
use memoryd::notes::{self, Kind}; use memoryd::notes::{self, Kind};
use proto::{DataClass, MemoryArgs, MemoryFile, Timestamp}; use proto::{DataClass, MemoryArgs, MemoryFile, Timestamp};
@@ -94,14 +94,17 @@ fn a_note_is_labelled_by_the_session() {
let f = Fixture::new("edit-note"); let f = Fixture::new("edit-note");
assert_eq!( assert_eq!(
f.call(r#"{"action":"note","text":"The launch moved."}"#, false), f.call(r#"{"action":"note","text":"The launch moved."}"#, false),
Ok("noted".to_string()) Ok("saved in notes.md".to_string())
); );
assert_eq!( assert_eq!(
f.call( f.call(
r#"{"action":"note","text":"example.org says March."}"#, r#"{"action":"note","text":"example.org says March."}"#,
true true
), ),
Ok("noted (untrusted)".to_string()) Ok(
"saved in notes.md, marked untrusted: this session has read untrusted content"
.to_string()
)
); );
assert_eq!( assert_eq!(
f.file("notes.md"), f.file("notes.md"),
@@ -127,7 +130,7 @@ fn an_untrusted_session_can_take_notes_but_not_change_identity() {
r#"{"action":"add","file":"user","text":"Owner lives in Oslo."}"#, r#"{"action":"add","file":"user","text":"Owner lives in Oslo."}"#,
true true
), ),
Ok(SAVED_AS_NOTE.to_string()) Ok("not added to user.md: this session has read untrusted content, so it was saved in notes.md as an untrusted note".to_string())
); );
assert_eq!(f.file("user.md"), ""); assert_eq!(f.file("user.md"), "");
assert!( assert!(
+3 -3
View File
@@ -175,7 +175,7 @@ fn an_untrusted_session_writes_notes_not_identity() {
)); ));
assert_eq!( assert_eq!(
answer, answer,
"saved as a note (untrusted): this session has read untrusted content" "not added to memory.md: this session has read untrusted content, so it was saved in notes.md as an untrusted note"
); );
let memory = std::fs::read_to_string(r.dir.path().join("memory/memory.md")).unwrap(); let memory = std::fs::read_to_string(r.dir.path().join("memory/memory.md")).unwrap();
assert_eq!(memory, ""); assert_eq!(memory, "");
@@ -232,7 +232,7 @@ fn review_holds_identity_writes_but_not_notes() {
r#"{"action":"note","text":"Not held."}"#, r#"{"action":"note","text":"Not held."}"#,
false, false,
)); ));
assert_eq!(noted, "noted"); assert_eq!(noted, "saved in notes.md");
let (untrusted, _) = content(call( let (untrusted, _) = content(call(
&r, &r,
"mm-b", "mm-b",
@@ -242,7 +242,7 @@ fn review_holds_identity_writes_but_not_notes() {
)); ));
assert_eq!( assert_eq!(
untrusted, untrusted,
"saved as a note (untrusted): this session has read untrusted content" "not added to memory.md: this session has read untrusted content, so it was saved in notes.md as an untrusted note"
); );
let admin = |command, arg: &str| match ask( let admin = |command, arg: &str| match ask(
&r.config.admin_socket(), &r.config.admin_socket(),
+1
View File
@@ -6,6 +6,7 @@ reviewer adds findings under "Reviews" once per milestone.
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model | | Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|---|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|
| M5a/22-plain-note-answers | 2026-09-25 | done | 1 | pass | none | The edits are all in `crates/memoryd/src/edit.rs` and match the task exactly. Replaced the `SAVED_AS_NOTE` const with a `saved_as_note(file)` fn ("not added to {file.name()}: this session has read untrusted content, so it was saved in notes.md as an untrusted note") plus the `NOTED` const "saved in notes.md" and `NOTED_UNTRUSTED` const "saved in notes.md, marked untrusted: this session has read untrusted content"; the `format!` string stays one line past 100 chars and `cargo fmt` left it so. Updated the comment in `apply` ("replaced by saved_as_note(file)") and the `answer:` line (`saved_as_note(file)`). In `noted` replaced the two string literals with `NOTED_UNTRUSTED`/`NOTED`. Copied the new `crates/memoryd/tests/edit.rs` and `serve.rs` from `docs/plans/M5a/files/` (not the `-task06`/`-task10` versions). Step 2 showed exactly the four named tests failing (`a_note_is_labelled_by_the_session` and `an_untrusted_session_can_take_notes_but_not_change_identity` in edit, `an_untrusted_session_writes_notes_not_identity` and `review_holds_identity_writes_but_not_notes` in serve); after the edits the step-2 command and `cargo test -p memoryd` every suite pass; `make gate` printed `gate: ok` with 944 tests, unchanged; `grep -rn SAVED_AS_NOTE crates/` prints nothing. No new dependency, so `Cargo.lock` is unchanged. | ? |
| M5a/21-session-follows-result | 2026-09-25 | done (by the design model) | many | fail | The session added and then deleted diagnostic test files (`crates/bxctl/tests/zz_diag.rs`, `zz_diag2.rs`) and ran about 80 minutes of experiments instead of stopping when a test outside the task failed, as the task says. | The session's edits match the task file exactly and the copied `ledger_label.rs` is identical to the plan's; step 2 showed the two named tests failing, and after the edits `ledger_label`, `cargo test -p brokerd` and `-p bxctl` passed. The gate then failed in `bxctl`'s `args_os` with `NotFound`. The cause was the design model's: its reference worktree for tasks 21 and 22 shared this `target/` through a symlink, so test binaries built there had the worktree's absolute paths compiled in (`CARGO_BIN_EXE_*`, `CARGO_MANIFEST_DIR`), and Cargo, whose freshness check does not include the workspace's absolute path, kept them after the worktree was deleted. After `cargo clean`, `make gate` printed `gate: ok`, 944 tests. The owner stopped the run. | Ornith-1.5-35B-A3B (the session); design model (the commit) | | M5a/21-session-follows-result | 2026-09-25 | done (by the design model) | many | fail | The session added and then deleted diagnostic test files (`crates/bxctl/tests/zz_diag.rs`, `zz_diag2.rs`) and ran about 80 minutes of experiments instead of stopping when a test outside the task failed, as the task says. | The session's edits match the task file exactly and the copied `ledger_label.rs` is identical to the plan's; step 2 showed the two named tests failing, and after the edits `ledger_label`, `cargo test -p brokerd` and `-p bxctl` passed. The gate then failed in `bxctl`'s `args_os` with `NotFound`. The cause was the design model's: its reference worktree for tasks 21 and 22 shared this `target/` through a symlink, so test binaries built there had the worktree's absolute paths compiled in (`CARGO_BIN_EXE_*`, `CARGO_MANIFEST_DIR`), and Cargo, whose freshness check does not include the workspace's absolute path, kept them after the worktree was deleted. After `cargo clean`, `make gate` printed `gate: ok`, 944 tests. The owner stopped the run. | Ornith-1.5-35B-A3B (the session); design model (the commit) |
| M5a/20-review-ids | 2026-09-25 | done | 1 | pass | none | The two edits exactly as the task gives, both in `submit` in `crates/memoryd/src/review.rs`: the comment block "how many Waiting lines have an id starting format!(\"p-{day}-\"). The id is format!(\"p-{day}-{:04}\", taken + 1)." became "how many Waiting lines of `lines(store)?`, done or not, have an id starting format!(\"p-{day}-\"): an id is never used twice. The id is format!(\"p-{day}-{:04}\", taken + 1)."; and `let taken = waiting(store)?` became `let taken = lines(store)?`, the `.iter()`/`.filter(..)`/`.count()` after it unchanged. `waiting` untouched, since `list`/`accept`/`reject` still use it. Copied `crates/memoryd/tests/review_ids.rs` from `docs/plans/M5a/files/`. `cargo fmt --all`; step 2 showed exactly the two named tests failing (`a_write_staged_after_an_accepted_one_gets_a_new_id_and_can_be_accepted` expected `p-2026-09-24-0002` got `p-2026-09-24-0001`; `ids_count_every_write_staged_that_day_waiting_or_done` expected `p-2026-09-24-0004` got `p-2026-09-24-0002`); after the edits both pass and `cargo test -p memoryd` every suite passes; `make gate` printed `gate: ok`. `cargo test --workspace` reports 940 passed, plus the gate's separate loopd `end_to_end` ignored test = 941 total. No new dependency, so `Cargo.lock` is unchanged. | ? | | M5a/20-review-ids | 2026-09-25 | done | 1 | pass | none | The two edits exactly as the task gives, both in `submit` in `crates/memoryd/src/review.rs`: the comment block "how many Waiting lines have an id starting format!(\"p-{day}-\"). The id is format!(\"p-{day}-{:04}\", taken + 1)." became "how many Waiting lines of `lines(store)?`, done or not, have an id starting format!(\"p-{day}-\"): an id is never used twice. The id is format!(\"p-{day}-{:04}\", taken + 1)."; and `let taken = waiting(store)?` became `let taken = lines(store)?`, the `.iter()`/`.filter(..)`/`.count()` after it unchanged. `waiting` untouched, since `list`/`accept`/`reject` still use it. Copied `crates/memoryd/tests/review_ids.rs` from `docs/plans/M5a/files/`. `cargo fmt --all`; step 2 showed exactly the two named tests failing (`a_write_staged_after_an_accepted_one_gets_a_new_id_and_can_be_accepted` expected `p-2026-09-24-0002` got `p-2026-09-24-0001`; `ids_count_every_write_staged_that_day_waiting_or_done` expected `p-2026-09-24-0004` got `p-2026-09-24-0002`); after the edits both pass and `cargo test -p memoryd` every suite passes; `make gate` printed `gate: ok`. `cargo test --workspace` reports 940 passed, plus the gate's separate loopd `end_to_end` ignored test = 941 total. No new dependency, so `Cargo.lock` is unchanged. | ? |
| M5a/19-no-forged-headers | 2026-09-25 | done | 2 | timed out (shell 2 min) | none | Two edits exactly as the task gives. `crates/proto/src/memory.rs`: the doc comment above `pub fn entry_problem` (the two lines "holding a line that is only the separator") became the three lines explaining a line starting with the separator, spaces aside, would read as a header in notes.md and a header says where an entry came from; and in the body, `if line.trim() == SEPARATOR { return Some(format!("an entry cannot hold a line that is only {SEPARATOR}")); }` became `if line.trim_start().starts_with(SEPARATOR) { return Some(format!("an entry cannot hold a line that starts with {SEPARATOR}")); }`, the for loop and closing braces unchanged. `crates/memoryd/src/notes.rs` `parse_header`: the `untrusted` match `Some(s) => *s == "untrusted"` became `Some(&"untrusted") => true, Some(_) => return None`, so a `noted` header whose last part is anything other than `untrusted` is not a header. Copied three test files from `docs/plans/M5a/files/`: `crates/proto/tests/memory_args.rs` and `crates/memoryd/tests/notes.rs` (new versions) and the new `crates/memoryd/tests/forgery.rs` (not the `-task02`/`-task06` files). `cargo fmt --all`. Step 2 showed exactly the three named tests failing (`entries_are_not_empty_not_too_long_and_hold_no_separator_line`, `a_noted_header_with_an_unknown_last_part_is_not_a_header`, `a_note_holding_a_header_line_is_refused_and_nothing_is_written`); the fourth, `recall_finds_no_identity_in_what_an_untrusted_session_wrote`, already passed. After the edits the step-2 command passed and `cargo test -p proto -p memoryd -p brokerd` every suite passed. `make gate` printed `gate: ok`; `cargo test --workspace` reported 938 passed and the gate's separate loopd `end_to_end` ignored test added 1, for 939 total. First `make gate` run hit the shell's 120 s timeout before finishing (the loopd daemon test needs more than 2 min); the second run completed with `gate: ok`. No new dependency, so `Cargo.lock` is unchanged. | Ornith-1.5-35B-A3B | | M5a/19-no-forged-headers | 2026-09-25 | done | 2 | timed out (shell 2 min) | none | Two edits exactly as the task gives. `crates/proto/src/memory.rs`: the doc comment above `pub fn entry_problem` (the two lines "holding a line that is only the separator") became the three lines explaining a line starting with the separator, spaces aside, would read as a header in notes.md and a header says where an entry came from; and in the body, `if line.trim() == SEPARATOR { return Some(format!("an entry cannot hold a line that is only {SEPARATOR}")); }` became `if line.trim_start().starts_with(SEPARATOR) { return Some(format!("an entry cannot hold a line that starts with {SEPARATOR}")); }`, the for loop and closing braces unchanged. `crates/memoryd/src/notes.rs` `parse_header`: the `untrusted` match `Some(s) => *s == "untrusted"` became `Some(&"untrusted") => true, Some(_) => return None`, so a `noted` header whose last part is anything other than `untrusted` is not a header. Copied three test files from `docs/plans/M5a/files/`: `crates/proto/tests/memory_args.rs` and `crates/memoryd/tests/notes.rs` (new versions) and the new `crates/memoryd/tests/forgery.rs` (not the `-task02`/`-task06` files). `cargo fmt --all`. Step 2 showed exactly the three named tests failing (`entries_are_not_empty_not_too_long_and_hold_no_separator_line`, `a_noted_header_with_an_unknown_last_part_is_not_a_header`, `a_note_holding_a_header_line_is_refused_and_nothing_is_written`); the fourth, `recall_finds_no_identity_in_what_an_untrusted_session_wrote`, already passed. After the edits the step-2 command passed and `cargo test -p proto -p memoryd -p brokerd` every suite passed. `make gate` printed `gate: ok`; `cargo test --workspace` reported 938 passed and the gate's separate loopd `end_to_end` ignored test added 1, for 939 total. First `make gate` run hit the shell's 120 s timeout before finishing (the loopd daemon test needs more than 2 min); the second run completed with `gate: ok`. No new dependency, so `Cargo.lock` is unchanged. | Ornith-1.5-35B-A3B |