Files
boxmaker/docs/plans/M5a/files/crates/memoryd/src/review.rs
T
kyleandClaude Opus 5.5 de1032157b Plan M5a: local memory in 15 tasks
memoryd (files, the check, edits, journal, index, recall, serving),
the memory tools and built-in grants in brokerd, the memory block in
loopd, bxctl reindex and memory, and the device checks with memoryd.

Each task was built as a reference and gated. Each was replayed on its
own at its end state (854 to 936 tests) and mutation-checked. Each
skeleton compiles and fails its tests. Every todo comment was read
beside its signature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 15:32:54 -07:00

105 lines
4.0 KiB
Rust

//! Review (off by default, `[memory] review = true`): identity writes wait in
//! `memory/pending.jsonl` for the owner's `accept` or `reject` instead of being applied (M5a spec,
//! section 5). Notes are never staged.
use serde::{Deserialize, Serialize};
use proto::escape::escape_model_text;
use proto::{DataClass, MemoryArgs, Timestamp};
use crate::edit::{Context, apply};
use crate::files::Store;
use crate::journal::{self, Record};
/// One line of `pending.jsonl`: a write waiting, or what became of one.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum Line {
Waiting {
id: String,
time: String,
session: String,
call: u64,
taint: DataClass,
/// The call's arguments, as `brokerd` sent them.
arguments: String,
},
Accepted {
id: String,
time: String,
edit: String,
},
Rejected {
id: String,
time: String,
},
}
fn lines(store: &Store) -> Result<Vec<Line>, String> {
// Read `store.path("pending.jsonl")`: NotFound -> Ok(empty); another error -> Err(format!("{}:
// {e}", path.display())). Parse every line as a Line with serde_json; a bad one ->
// Err(format!("{}: line {}: {e}", path.display(), n + 1)).
todo!()
}
fn add(store: &Store, line: &Line) -> Result<(), String> {
// `serde_json::to_string(line)` (its error as a String), then `store.append("pending.jsonl",
// &format!("{text}\n"))`.
todo!()
}
/// The writes still waiting: `(id, session, arguments, taint, call)`.
fn waiting(store: &Store) -> Result<Vec<Line>, String> {
// The ids that have an Accepted or a Rejected line are done. Return a clone of every Waiting
// line whose id is not done, in file order.
todo!()
}
/// Stage an identity write. Its shape is checked now; its content when it is accepted.
pub fn submit(
store: &Store,
args: &MemoryArgs,
arguments: &str,
ctx: &Context,
) -> Result<String, String> {
// 1. `args.check()?`.
// 2. `day` is the first 10 characters of `ctx.now.to_rfc3339()`. `taken` = how many Waiting
// lines have an id starting format!("p-{day}-"). The id is format!("p-{day}-{:04}", taken +
// 1).
// 3. `add` a Waiting line: id, time `ctx.now.to_rfc3339()`, session, call and taint from ctx,
// arguments as given.
// 4. Ok(format!("waiting for the owner's review ({id}); nothing is changed until it is
// accepted")).
todo!()
}
/// The writes waiting, one per line, arguments escaped.
pub fn list(store: &Store) -> Result<String, String> {
// One line per `waiting` entry: format!("{id} {session} {}", escape_model_text(arguments)),
// joined by newlines; with none, "nothing is waiting for review".
todo!()
}
/// Apply a waiting write, with the same checks, against the file as it is now, and journal it.
pub fn accept(store: &Store, id: &str, now: Timestamp) -> Result<String, String> {
// 1. The Waiting line with this id among `waiting(store)?`, else Err(format!("nothing waiting
// as {id}")).
// 2. Parse its arguments as MemoryArgs (the error as a String).
// 3. A Context from its session, call and taint, untrusted false, now.
// 4. `apply(store, &args, &ctx)?`, then `journal::record(store, &ctx, &outcome)?` gives the
// edit id.
// 5. `add` an Accepted line { id, time now, edit }.
// 6. Ok(format!("accepted: {id} as {edit}: {}", outcome.answer)).
todo!()
}
/// Discard a waiting write; the journal records it.
pub fn reject(store: &Store, id: &str, now: Timestamp) -> Result<String, String> {
// 1. The Waiting line with this id, else Err(format!("nothing waiting as {id}")).
// 2. `add` a Rejected line { id, time now }.
// 3. `journal::append` a Record: its session and call, action "reject", file None, to empty, no
// entries, no notes, untrusted false, undoes Some(id).
// 4. Ok(format!("rejected: {id}")).
todo!()
}