//! 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, 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, 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 { // 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 { // 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 { // 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 { // 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!() }