Files
boxmaker/docs/plans/M5a/files/crates/bxctl/src/memory.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

77 lines
3.4 KiB
Rust

//! The owner's memory commands over `memoryd`'s owner socket: `reindex`, and `memory log`,
//! `undo`, `pending`, `accept`, `reject` (M5a spec, section 10).
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::time::Duration;
use proto::{
Empty, Envelope, MemoryAdmin, MemoryCommand, Message, PROTOCOL_VERSION, read_frame, write_frame,
};
use crate::cli::{Command, UsageError};
use crate::escape::escape_json_text;
/// How long `bxctl` waits for `memoryd`: a reindex reads every session log.
pub const MEMORY_TIMEOUT: Duration = Duration::from_secs(300);
pub fn default_socket(home: &Path) -> PathBuf {
home.join("run").join("owner-memory").join("admin.sock")
}
/// What to ask `memoryd`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ask {
Reindex,
Admin(MemoryCommand, String),
}
/// `bxctl reindex [--memory-socket PATH]`.
pub fn parse_reindex(args: &[String], home: &Path) -> Result<Command, UsageError> {
// `socket_flag(args, home)?`; any argument left -> Err(UsageError). Ok(Command::Memory {
// socket, ask: Ask::Reindex }).
todo!()
}
/// `bxctl memory log [N] | undo <id> | pending | accept <id> | reject <id>`, each with an optional
/// `--memory-socket PATH`.
pub fn parse_memory(args: &[String], home: &Path) -> Result<Command, UsageError> {
// `socket_flag(args, home)?`, then match what is left: ["log"] -> Admin(Log, ""); ["log", n]
// where n parses as usize -> Admin(Log, n); ["pending"] -> Admin(Pending, ""); ["undo", id] ->
// Admin(Undo, id); ["accept", id] -> Admin(Accept, id); ["reject", id] -> Admin(Reject, id);
// anything else -> Err(UsageError). Ok(Command::Memory { socket, ask }).
todo!()
}
/// Take `--memory-socket PATH` out of `args`, once at most; the rest in order.
fn socket_flag(args: &[String], home: &Path) -> Result<(PathBuf, Vec<String>), UsageError> {
// Walk `args`: "--memory-socket" takes the next argument as the socket (none ->
// Err(UsageError); a second --memory-socket -> Err(UsageError)); every other argument is kept
// in order. The socket defaults to `default_socket(home)`. Ok((socket, rest)).
todo!()
}
/// `n` with commas between thousands.
fn grouped(n: u64) -> String {
// As memoryd's `grouped`: the digits of n with a comma before each char at index i > 0 where
// `(digits.len() - i).is_multiple_of(3)`.
todo!()
}
/// Ask `memoryd` and print its answer. `Err` is the line for stderr.
pub fn run(socket: &Path, ask: &Ask, timeout: Duration, out: &mut dyn Write) -> Result<(), String> {
// 1. Connect to `socket` and set read and write timeouts of `timeout`; each error ->
// format!("cannot reach memoryd at {}: {e}", socket.display()).
// 2. The message: Reindex -> Message::Reindex(Empty {}); Admin(command, arg) ->
// Message::MemoryAdmin(MemoryAdmin { command, arg }). `write_frame` it in an Envelope { v:
// PROTOCOL_VERSION, id: 1, final: true } (an error: the same "cannot reach" sentence).
// 3. `read_frame` (an error -> format!("memoryd did not answer: {e}")). Reindexed(r) ->
// format!("reindexed: {} logs, {} records, {} notes") with each count `grouped`;
// MemoryAdminReply(reply) -> reply.text; Error(e) -> Err(format!("memoryd: {}",
// escape_json_text(&e.detail))); anything else -> Err("memoryd answered with the wrong
// message").
// 4. `writeln!(out, "{text}")`, its error as a String.
todo!()
}