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>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
//! 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!()
|
||||
}
|
||||
Reference in New Issue
Block a user