Files
boxmaker/docs/plans/M5c1/06-memoryd-honcho-config.md
kyleandClaude Opus 5.5 0e1e070fc3 M5c-1 plan: task 06, memoryd checks [honcho] and its keys at start
memoryd.toml gains an optional [honcho] section, [[honcho.read]] and the
Honcho keys in [secrets.<name>], checked at start into
memoryd::honcho::config::HonchoConfig (13 reasons, runbook entry
honcho-config-invalid given as a fragment), and [sockets] state for
brokerd's state.sock. Keys are checked, never read; no [honcho] leaves
memoryd as it was. memoryd enables bxcore's feature secrets.

Checks: a reference at ref 05 + this task passes make gate with 1188
tests (1171 + 17); /tmp unchanged around the gate; the given tests fail
before as the task says (two targets do not compile, 4 start tests
fail); 49 one-line mutations of the reference, all caught (one gap, a
URL with a user and no password, found and closed); the new tests 40
times under CPU load, no failure; a replay from the task text on a fresh
ref-05 worktree with its own target is byte-identical and passes the
gate, and every before block was found once; given files pass fmt and
the 500-line limit; no chat-template tag in any file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 02:48:16 -07:00

26 KiB

M5c-1 task 06: memoryd checks its [honcho] section and its keys at start

Branch: m5c1 (run git switch m5c1; git status --short must be empty, otherwise stop) Commit subject: memoryd: [honcho] and its keys checked at start, and brokerd's state socket Log row: the Task cell is M5c1/06-memoryd-honcho-config.

Goal

From M5c on, memoryd sends finished turns and memory writes to Honcho, the memory the agents of the fleet share (M5c spec, sections 3 and 10). This task adds the configuration for it, read and checked when memoryd starts. Nothing talks to Honcho or to brokerd yet: later tasks use what this task checks.

  • [honcho], optional, in memoryd.toml:

    [honcho]
    url = "http://rift.scylla-hammerhead.ts.net:8000"   # http(s)://, a host, optional port
    agent = "boxmaker0"                                  # this agent's peer
    owner = "kyle"                                       # the owner's peer, the same in the fleet
    trusted = "boxmaker0-trusted"                        # this agent's three workspaces
    untrusted = "boxmaker0-untrusted"
    fleet = "fleet"
    
    [[honcho.read]]                                      # zero or more: other agents' workspaces
    agent = "boxmaker1"
    workspace = "boxmaker1-trusted"
    trust = "untrusted"                                  # "trusted" or "untrusted"; the default
    class = "private"                                    # "public", "private" or "secret"; default
    
    [secrets.honcho_trusted]                             # optional: a key per workspace
    env = "HONCHO_KEY"
    

    The keys' names are honcho_trusted, honcho_untrusted, honcho_fleet, and honcho_read_<agent> for an agent in [[honcho.read]]. A key's [secrets.<name>] table is the one every role uses (credential, env or file, M5b). Here a key is only checked (its table names one well-formed source); it is not read: a later task reads it when it makes the Honcho client. No key is ever printed.

  • No [honcho] section (and no key): Honcho is off, and memoryd behaves exactly as before. Every existing memoryd test passes unchanged.

  • An invalid section stops memoryd at start, before it takes its lock or touches its home, with one line and the pointer:

    memoryd: /etc/boxmaker/memoryd.toml: [honcho] agent and owner are both "kyle": the agent's peer must not be the owner's
    see docs/runbook.md#honcho-config-invalid
    

    The reasons are in the table "The reasons" below. This task appends the runbook entry, given.

  • [sockets] state: the path of brokerd's state.sock (task 05), which memoryd will connect to, to ask for a session's state. Like the other three [sockets] keys, empty or missing means the default under home: <home>/run/memory-broker/state.sock, the path brokerd serves by default.

Interfaces

Later tasks use these exactly as written. They are in the skeleton you copy (step 3); only the bodies of seven private or small functions are yours to fill.

In crates/memoryd/src/honcho/config.rs (the module memoryd::honcho::config):

pub const RUNBOOK: &str = "see docs/runbook.md#honcho-config-invalid";
pub const MAX_NAME: usize = 512;
pub const TRUSTED_KEY: &str = "honcho_trusted";
pub const UNTRUSTED_KEY: &str = "honcho_untrusted";
pub const FLEET_KEY: &str = "honcho_fleet";
pub const READ_KEY: &str = "honcho_read_";

/// `[honcho]` as written (serde; unknown keys are errors).
pub struct HonchoSection {
    pub url: String,
    pub agent: String,
    pub owner: String,
    pub trusted: String,
    pub untrusted: String,
    pub fleet: String,
    pub read: Vec<ReadSection>, // #[serde(default)]
}
/// One `[[honcho.read]]` as written (serde; unknown keys are errors).
pub struct ReadSection {
    pub agent: String,
    pub workspace: String,
    pub trust: Option<String>, // #[serde(default)]
    pub class: Option<String>, // #[serde(default)]
}
/// `[honcho]` and its keys, checked.
pub struct HonchoConfig {
    pub url: String,
    pub agent: String,
    pub owner: String,
    pub trusted: String,
    pub untrusted: String,
    pub fleet: String,
    pub read: Vec<ReadWorkspace>,
    pub keys: BTreeMap<String, SecretSource>, // by the name in [secrets.<name>]
}
pub struct ReadWorkspace {
    pub agent: String,
    pub workspace: String,
    pub trust: Trust,
    pub class: DataClass,
}
pub enum Trust {
    Trusted,
    Untrusted,
}

pub fn read_key(agent: &str) -> String; // "honcho_read_<agent>"
pub fn is_name(name: &str) -> bool; // Honcho's rule: 1 to 512 of a-z, A-Z, 0-9, _ and -
pub fn check(
    section: Option<&HonchoSection>,
    secrets: &BTreeMap<String, SecretSpec>,
) -> Result<Option<HonchoConfig>, String>;

The derives: HonchoSection and ReadSection Debug, Clone, PartialEq, Eq, Deserialize; HonchoConfig and ReadWorkspace Debug, Clone, PartialEq, Eq; Trust also Copy.

In crates/memoryd/src/config.rs (edits C1 to C8): Config gains pub honcho: Option<HonchoSection> and pub secrets: BTreeMap<String, SecretSpec>; Sockets gains pub state: PathBuf; ConfigError gains Honcho(PathBuf, String); Config gains pub fn honcho_config(&self) -> Result<Option<HonchoConfig>, String> and pub fn state_socket(&self) -> PathBuf.

The reasons

check returns the first reason it finds. The order is the given check's: R4 (no section), R1, R2 for the five names of [honcho], R3, R5, then each [[honcho.read]] in turn (R2, R6, R7, R8, R9, R10), then the keys (R11, R12, R13). {x:?} is Rust's Debug format: the text in double quotes, with " and control characters escaped. R1 to R4 are in the given code already; R5 to R13 are yours.

# When The reason (the text in Err)
R1 url is not http:// or https://, a host and an optional port [honcho] url must be http:// or https://, a host and an optional port, and nothing else (the constant URL_REASON; it never quotes the URL, which could hold a password)
R2 a name is not a Honcho name {table} {key} {name:?} is not a Honcho name: 1 to 512 of a-z, A-Z, 0-9, _ and -, table being [honcho] or [[honcho.read]] (the given check_name)
R3 agent and owner are equal [honcho] agent and owner are both {agent:?}: the agent's peer must not be the owner's
R4 a [secrets.<name>] with no [honcho] [secrets.{name}] is set, but there is no [honcho] section (the first name)
R5 two of trusted, untrusted, fleet are equal [honcho] workspace {name:?} is named twice: trusted, untrusted and fleet must be three different workspaces
R6 a read agent is [honcho] agent [[honcho.read]] agent {agent:?} is this agent ([honcho] agent)
R7 a read agent is in an earlier entry [[honcho.read]] agent {agent:?} is listed twice
R8 a read workspace is one of the three [[honcho.read]] workspace {workspace:?} is one of this agent's own
R9 trust is another text [[honcho.read]] trust {trust:?} of agent {agent:?} is not "trusted" or "untrusted"
R10 class is another text [[honcho.read]] class {class:?} of agent {agent:?} is not "public", "private" or "secret"
R11 honcho_read_<agent> for an agent not in [[honcho.read]] [secrets.{name}] is the key of agent {agent:?}, which is not in [[honcho.read]]
R12 a [secrets.<name>] with any other name [secrets.{name}] is not a Honcho key: memoryd has only honcho_trusted, honcho_untrusted, honcho_fleet and honcho_read_<agent>
R13 SecretSpec::source refuses the table [secrets.{name}] {why}, why being source's own text

In Rust, R9 and R10 contain \" for each double quote shown: for R9, format!("[[honcho.read]] trust {other:?} of agent {:?} is not \"trusted\" or \"untrusted\"", entry.agent), where other is the text written. In R11, agent is what follows honcho_read_ in the name. name in R4, R11, R12 and R13 is shown as it is, without :?.

Every place that changes

This is the complete list.

Place What happens there
crates/memoryd/Cargo.toml bxcore with the feature secrets (edit K1)
crates/memoryd/src/lib.rs pub mod honcho; (edit L1)
crates/memoryd/src/honcho/mod.rs new: step 3 gives it whole
crates/memoryd/src/honcho/config.rs new: a skeleton you copy, then fill 7 bodies (step 4)
crates/memoryd/src/config.rs module comment, imports, Sockets.state, Config.honcho and .secrets, ConfigError::Honcho, load, honcho_config, state_socket (edits C1 to C8)
crates/memoryd/src/main.rs the config error's pointer by its kind (edits M1, M2)
docs/runbook.md the entry honcho-config-invalid, appended (step 6)
docs/dependencies.md the zeroize row names memoryd (edit D1)
everything else in memoryd (serve.rs, socket.rs, …) no change

Files

  • Copy, from docs/plans/M5c1/files/ (the same path under it):
    • new tests: crates/memoryd/tests/honcho_config.rs, crates/memoryd/tests/honcho_reasons.rs, crates/memoryd/tests/honcho_start.rs
    • the skeleton crates/memoryd/src/honcho/config.rs (you fill 7 bodies in it)
  • Append, from docs/plans/M5c1/files/, to the end of docs/runbook.md, with one blank line before it: runbook-honcho-config-invalid.md (step 6 gives the command).
  • Create: crates/memoryd/src/honcho/mod.rs (step 3).
  • Modify: crates/memoryd/Cargo.toml, crates/memoryd/src/lib.rs, crates/memoryd/src/config.rs, crates/memoryd/src/main.rs, docs/dependencies.md, docs/implementer-log.md.
  • Do not touch: any other source file, any file of another crate, any other Cargo.toml, any other test (crates/memoryd/tests/config.rs in particular: it must pass as it is), the test support files, and in the skeleton anything but the 7 todo!() bodies.

Facts you need

  • The feature secrets. SecretSpec and SecretSource are in bxcore::secrets, which is compiled only when a crate enables bxcore's feature secrets (M5b). gatewayd does; loopd never does, as it holds no credentials. memoryd holds Honcho's keys, so edit K1 enables it. make gate builds the whole workspace at once, and Cargo then turns the feature on for every crate because gatewayd asks for it; so without K1 the gate could pass while cargo build -p memoryd alone fails. Cargo.lock does not change: zeroize, the feature's one dependency, is already bxcore's.
  • Two stages. Config::parse (serde) reads the file: a missing key in [honcho] or [[honcho.read]], an unknown key, or a value of the wrong type is a parse error, as in every other table, and keeps its pointer memoryd-start-failed. Then Config::load calls honcho_config, which checks the values (honcho::config::check): an Err is ConfigError::Honcho, and main prints the pointer honcho-config-invalid for it. parse itself does not check (the serve tests build their Config with it); honcho_config is pure, and later tasks call it again to get the HonchoConfig.
  • trust and class stay text (Option<String>) in ReadSection, so that a wrong value is one of the reasons (R9, R10) with this runbook entry, not a serde error. Both are lower case only: "Trusted" and "Private" are refused.
  • Honcho names (M5c spec, section 6): Honcho accepts ^[a-zA-Z0-9_-]+$, at most 512 characters, for a workspace, a peer or a session. Checked on bytes: a non-ASCII letter such as é fails, and so does any Unicode digit that is not 0 to 9.
  • The URL. http:// or https:// (lower case), a host of ASCII letters, digits, . and -, and optionally : and a port from 1 to 65535 written in digits only (u16::from_str alone would take a leading +). Nothing may follow: not even /, since roncho builds each request URL from /v3/... and would drop a path silently. A user or password (user:pw@host) fails, because the host would contain @ or the port would not be digits. The reason never quotes the URL.
  • Keys are checked, not read. SecretSpec::source checks the table (exactly one of credential, env, file; a well-formed name; an absolute path) without reading anything. The given tests name environment variables that are not set and files that do not exist, and expect Ok. Do not call bxcore::secrets::load or read_checked.
  • Where the check runs. main loads the config first, before the instance lock and before anything under the home is made (as it already does); a given test checks that <home>/run and <home>/memory do not exist after an invalid section.
  • The given tests.
    • honcho_config.rs (8 tests): what a valid section gives, the defaults, the keys, the key names and the pointer, parse errors, state_socket, and load.
    • honcho_reasons.rs (5 tests): every reason of the table, exactly, one problem per section; names and URLs at their edges; the first problem is the one reported.
    • honcho_start.rs (4 tests): starts memoryd serve as a process (every wait has a 10-second deadline; a guard kills and waits for the process before the home is removed). An invalid section: exit 1 and exactly the two lines; an unknown key: still memoryd-start-failed; a valid section with keys: memoryd serves as before. The two key values it passes (an environment variable, a 0600 file) must appear in nothing memoryd prints.
  • The calls the bodies and edits use:
    Call Signature, and what it gives
    str::strip_prefix url.strip_prefix("https://") -> Option<&str>
    str::split_once rest.split_once(':') -> Option<(&str, &str)>: split at the first :
    str::bytes .bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-')
    u8::is_ascii_alphanumeric, u8::is_ascii_digit b.is_ascii_digit() -> bool
    str::parse port.parse::<u16>() -> Result<u16, ParseIntError>
    Option::as_deref entry.trust.as_deref() -> Option<&str>, to match on Some("trusted")
    slice::iter().any earlier.iter().any(|r| r.agent == entry.agent) -> bool
    SecretSpec::source pub fn source(&self) -> Result<SecretSource, String> (bxcore::secrets)
    BTreeMap::insert keys.insert(name.clone(), source)
    check_name (given) fn check_name(table: &str, key: &str, name: &str) -> Result<(), String>: Err is R2
    honcho::check (given) see Interfaces; honcho_config calls it (C7)

The edits, exactly

Nothing else in these files changes. Each quoted "before" block occurs exactly once in its file.

crates/memoryd/Cargo.toml

K1. The line

bxcore.workspace = true

becomes

bxcore = { workspace = true, features = ["secrets"] }

crates/memoryd/src/lib.rs

L1. The line

pub mod files;

becomes

pub mod files;
pub mod honcho;

crates/memoryd/src/config.rs

C1. The line

//! `memoryd` configuration: read `memoryd.toml` into a typed `Config` (M5a spec, section 11).

becomes

//! `memoryd` configuration: read `memoryd.toml` into a typed `Config` (M5a spec, section 11), and
//! check its `[honcho]` section at start (M5c spec, section 10).

C2. The lines

use std::path::{Path, PathBuf};

use bxcore::DataClass;
use serde::Deserialize;

become

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use bxcore::DataClass;
use bxcore::secrets::SecretSpec;
use serde::Deserialize;

use crate::honcho::config::{self as honcho, HonchoConfig, HonchoSection};

C3. In Sockets, the lines

    pub broker: PathBuf,
    pub admin: PathBuf,
}

become

    pub broker: PathBuf,
    pub admin: PathBuf,
    /// `brokerd`'s `state.sock`, which `memoryd` connects to, to ask for a session's state (M5c
    /// spec, section 7). `brokerd.toml` names the same path as its `[sockets] state`.
    pub state: PathBuf,
}

C4. In Config, the lines

    pub sockets: Sockets,
}

become

    pub sockets: Sockets,
    /// `[honcho]` as written; `honcho_config` checks it. Without it, Honcho is off.
    pub honcho: Option<HonchoSection>,
    /// `[secrets.<name>]`: the Honcho keys, checked by `honcho_config`, never read here.
    pub secrets: BTreeMap<String, SecretSpec>,
}

C5. The lines

    Parse(PathBuf, toml::de::Error),
}

become

    Parse(PathBuf, toml::de::Error),
    /// `[honcho]` or a key for it is not allowed: the path, and `honcho_config`'s reason.
    Honcho(PathBuf, String),
}

C6. In impl std::fmt::Display for ConfigError, the line

            ConfigError::Parse(path, err) => write!(f, "{}: {err}", path.display()),

becomes

            ConfigError::Parse(path, err) => write!(f, "{}: {err}", path.display()),
            ConfigError::Honcho(path, why) => write!(f, "{}: {why}", path.display()),

C7. The lines

    /// Read and parse `path`; a relative `soul_starter` is taken to be next to it.
    pub fn load(path: &Path) -> Result<Config, ConfigError> {
        let text =
            std::fs::read_to_string(path).map_err(|e| ConfigError::Read(path.to_path_buf(), e))?;
        let mut config =
            Config::parse(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))?;

become

    /// Read, parse and check `path`; a relative `soul_starter` is taken to be next to it.
    pub fn load(path: &Path) -> Result<Config, ConfigError> {
        let text =
            std::fs::read_to_string(path).map_err(|e| ConfigError::Read(path.to_path_buf(), e))?;
        let mut config =
            Config::parse(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))?;
        if let Err(why) = config.honcho_config() {
            return Err(ConfigError::Honcho(path.to_path_buf(), why));
        }

and the line

    fn socket(&self, set: &Path, default: &str) -> PathBuf {

becomes

    /// `[honcho]` and its keys, checked: `Ok(None)` when there is no `[honcho]` and no key.
    pub fn honcho_config(&self) -> Result<Option<HonchoConfig>, String> {
        honcho::check(self.honcho.as_ref(), &self.secrets)
    }

    fn socket(&self, set: &Path, default: &str) -> PathBuf {

C8. The lines

        self.socket(&self.sockets.admin, "run/owner-memory/admin.sock")
    }

become

        self.socket(&self.sockets.admin, "run/owner-memory/admin.sock")
    }
    pub fn state_socket(&self) -> PathBuf {
        self.socket(&self.sockets.state, "run/memory-broker/state.sock")
    }

crates/memoryd/src/main.rs

M1. The line

use memoryd::config::Config;

becomes

use memoryd::config::{Config, ConfigError};
use memoryd::honcho::config::RUNBOOK as HONCHO_INVALID;

M2. The lines

    let config = match Config::load(&path) {
        Ok(config) => config,
        Err(e) => {
            eprintln!("memoryd: {e}\n{START_FAILED}");
            return ExitCode::from(1);
        }

become

    let config = match Config::load(&path) {
        Ok(config) => config,
        Err(e) => {
            let pointer = match &e {
                ConfigError::Read(..) | ConfigError::Parse(..) => START_FAILED,
                ConfigError::Honcho(..) => HONCHO_INVALID,
            };
            eprintln!("memoryd: {e}\n{pointer}");
            return ExitCode::from(1);
        }

docs/dependencies.md

D1. The line (the zeroize row)

| `zeroize` | 1.9.0 | `bxcore`, feature `secrets` (enabled by `gatewayd`; never by `loopd`) | Wipes a secret's memory when it is dropped (`Secret`). Already a dependency of `rustls`. Apache-2.0 OR MIT. |

becomes

| `zeroize` | 1.9.0 | `bxcore`, feature `secrets` (enabled by `gatewayd` and `memoryd`; never by `loopd`) | Wipes a secret's memory when it is dropped (`Secret`). Already a dependency of `rustls`. Apache-2.0 OR MIT. |

Steps

  • 1. Copy the given tests. From the repository root, three commands:

    cp docs/plans/M5c1/files/crates/memoryd/tests/honcho_config.rs crates/memoryd/tests/
    cp docs/plans/M5c1/files/crates/memoryd/tests/honcho_reasons.rs crates/memoryd/tests/
    cp docs/plans/M5c1/files/crates/memoryd/tests/honcho_start.rs crates/memoryd/tests/
    
  • 2. See them fail. Two commands, each with a shell timeout of 600000 ms:

    • cargo build --tests --keep-going -p memoryd: two test targets do not compile. honcho_config, with 8 errors: one E0433 (cannot find `honcho` in `memoryd` ), one E0432 (unresolved import `bxcore::secrets` : the feature is not on yet), two E0599 for honcho_config, three E0599 for state_socket (no method named … found for struct `Config` ) and one E0599 for the variant Honcho of ConfigError. honcho_reasons, with 2: the same E0433, and one E0599 for honcho_config. honcho_start compiles.
    • cargo test -p memoryd --test honcho_start: it compiles, and its 4 tests fail (about 10 s: one waits out its deadline). Each memoryd stopped with unknown field `honcho`, expected one of `paths`, `memory`, `index`, `sockets` and the pointer memoryd-start-failed:
      Test Before this task
      an_invalid_section_stops_memoryd_with_its_reason_and_the_pointer assertion left == right failed: the parse error where the reason was wanted
      a_key_for_an_agent_not_read_stops_memoryd the same
      an_unknown_key_in_the_section_is_still_a_start_failure the stderr, printed as the message: it names honcho, not fleat
      a_valid_section_with_keys_starts_memoryd_as_before memoryd never answered; stderr: … after 10 s
  • 3. The module. Make edits K1 and L1. Create crates/memoryd/src/honcho/mod.rs with exactly this content:

    //! Honcho, the memory this agent shares with the fleet, reached through `roncho` (M5c spec).
    
    pub mod config;
    

    Then copy the skeleton:

    cp docs/plans/M5c1/files/crates/memoryd/src/honcho/config.rs crates/memoryd/src/honcho/
    

    cargo check -p memoryd: no error, and 13 warnings, all from the unfilled bodies: ten unused variable (name, url, section twice, entry three times, earlier, secrets, read), constant `URL_REASON` is never used, and function `trust_of` is never used and the same for class_of. They go as you fill the bodies.

  • 4. Fill the seven bodies of honcho/config.rs, one at a time, in this order, each from its comment and the table "The reasons", and run cargo check -p memoryd after each: is_name, check_url, check_own_workspaces, trust_of, class_of, read_workspace, check_keys. When a body is filled, delete its instruction comment (the // lines inside the body) and keep the function's doc comment (///) as it is. Change nothing else in the file. After the seventh, cargo check -p memoryd prints no warning.

  • 5. The configuration. Make edits C1 to C8 in config.rs, then M1 and M2 in main.rs. cargo check -p memoryd: no error and no warning. Then this command, and every test passes (8, 5, 4 and 4):

    cargo test -p memoryd --test honcho_config --test honcho_reasons --test honcho_start \
      --test config
    
  • 6. The runbook and the dependency row. From the repository root:

    { echo; cat docs/plans/M5c1/files/runbook-honcho-config-invalid.md; } >> docs/runbook.md
    

    Then grep -c '^## honcho-config-invalid$' docs/runbook.md prints 1. Make edit D1.

  • 7. Check. Four commands:

    • cargo build -p memoryd: it builds (it needs K1: see "The feature secrets").
    • grep -rn 'runbook.md#honcho-config-invalid' crates/memoryd/src prints exactly one line, the RUNBOOK constant in honcho/config.rs.
    • grep -n 'secrets::load\|read_checked' crates/memoryd/src -r prints nothing: no key is read.
    • git diff --stat -- crates/bxcore crates/loopd crates/brokerd crates/gatewayd crates/bxctl prints nothing.
  • 8. Run the gate. cargo fmt --all, then make gate, with a shell timeout of 600000 ms. Expected last line: gate: ok, with 1188 tests: 1171 before this task, plus 17 new ones (8 in honcho_config.rs, 5 in honcho_reasons.rs, 4 in honcho_start.rs).

  • 9. Log and commit. Add your row to docs/implementer-log.md; its Task cell is M5c1/06-memoryd-honcho-config. git status --short lists crates/memoryd/Cargo.toml, crates/memoryd/src/lib.rs, crates/memoryd/src/config.rs, crates/memoryd/src/main.rs, docs/runbook.md, docs/dependencies.md and your log as modified, and crates/memoryd/src/honcho/ and the three test files as new. Stage with git add crates/memoryd docs/runbook.md docs/dependencies.md docs/implementer-log.md Cargo.lock, then commit as AGENTS.md says. Cargo.lock does not change in this task; the git add is harmless.

Done when

  • make gate prints gate: ok with 1188 tests.
  • git diff HEAD~1 --stat lists only the four modified memoryd files, honcho/mod.rs, honcho/config.rs, the three new test files, docs/runbook.md, docs/dependencies.md and docs/implementer-log.md.

Stop and report if

  • A "before" block of K1, L1, C1 to C8, M1, M2 or D1 is not in its file exactly as shown, or is there more than once.
  • Step 2 or 3 shows a different result: other test targets, errors of another kind, other failing tests, or other warnings.
  • cargo check reports an error after a body you filled or an edit you made, and the comment or the edit does not tell you how to fix it. Quote the error, and stop.
  • A comment in the skeleton does not fit the code under it, or a reason in the table does not fit the comment that names it. Quote both and stop.
  • After step 4 or 5 a test fails although each body follows its comment and each edit is as shown. Do not change a test or the skeleton's given code: stop, and give the failing tests' names and their messages.
  • The fix seems to need a change anywhere else: serve.rs, another crate, the test support files, reading a key, or a check the table does not list. It does not; stop and say why you think so.
  • A test outside these three files fails in the gate: check once whether it fails without your change, then stop and report both results.
  • The gate passes with a count other than 1188.