memoryd: the crate, memoryd.toml, and listening on a socket

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-25 16:41:44 -07:00
committed by kyle
parent 964bd99b12
commit b84b9af540
14 changed files with 657 additions and 2 deletions
+1
View File
@@ -12,6 +12,7 @@ Every external crate has a row here. `scripts/check-dep-docs.sh` enforces it.
| `rustls` | 0.23.45 | `gatewayd` | TLS to Mattermost over the tailnet (M4a, owner's decision). `default-features = false` with `ring`, `std`, `tls12`. With its dependencies on Linux: `ring` (Apache-2.0 AND ISC; builds C and assembly), `rustls-webpki` and `untrusted` (ISC), `rustls-pki-types`, `subtle` (BSD-3-Clause), `zeroize`, `once_cell`, `getrandom`, `libc`, `cfg-if`. Apache-2.0 OR ISC OR MIT. |
| `rustls-native-certs` | 0.8.4 | `gatewayd` | The host's trusted certificates, so a CA installed on the host is trusted too; adds `openssl-probe`. Apache-2.0 OR ISC OR MIT. |
| `zeroize` | 1.9.0 | `gatewayd` | Wipes a secret's memory when it is dropped (`Secret`). Already a dependency of `rustls`. Apache-2.0 OR MIT. |
| `rusqlite` | 0.40.2 | `memoryd` from M5a | The full-text index (`index/memory.sqlite3`, FTS5), disposable and rebuilt from files. Feature `bundled`: `libsqlite3-sys` compiles SQLite from source with FTS5 on, so no system SQLite is needed. With its dependencies: `libsqlite3-sys` (MIT; build: `cc`, `pkg-config`, `vcpkg`), `hashlink`, `hashbrown`, `foldhash` (Zlib, added to `deny.toml` for it), `fallible-iterator`, `fallible-streaming-iterator`, `smallvec`, `bitflags`. MIT. |
## Build inputs
+1
View File
@@ -6,6 +6,7 @@ reviewer adds findings under "Reviews" once per milestone.
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|---|---|---|---|---|---|---|---|
| M5a/03-memoryd-crate | 2026-09-24 | done | 1 | pass | none | Copied `crates/memoryd/Cargo.toml`, `lib.rs`, `main.rs` (whole), the `config.rs` and `socket.rs` skeletons, and the tests `config.rs`, `socket.rs` and `tests/support/tmp.rs` into place. Added `"crates/memoryd",` to the workspace `members` after `"crates/gatewayd",` and `rusqlite = { version = "0.40.2", features = ["bundled"] }` to `[workspace.dependencies]`. Added `Zlib` to the `deny.toml` allow list (for `foldhash`, a `rusqlite` dependency) and the `rusqlite` row to `docs/dependencies.md`. Appended the `memoryd-start-failed` runbook entry. Filled `Config::load` (`read_to_string` -> `ConfigError::Read`, `Config::parse` -> `ConfigError::Parse`, then a relative `soul_starter` resolved next to the config file via `path.parent().unwrap_or_else(|| Path::new("."))`); `Config::socket` (an empty `set` means `self.paths.home.join(default)`, otherwise the path as given); and `listen` (the same five steps as brokerd's `serve.rs::listen` — directory at 0700, symlink_metadata refusal, `set_permissions`, stale socket removed, bind then 0600 — with every error a `String` via `format!("{}: {e}", path.display())`). `cargo fmt --all`; the first `cargo build -p memoryd` compiled SQLite from source; `cargo deny check` reported the four "ok"s; `config` 4 passed and `socket` 2 passed; `make gate` prints `gate: ok` on the first run. | ? |
| M5a/02-proto-memory | 2026-09-24 | done | 1 | pass | none | Copied the skeleton `crates/proto/src/memory.rs`, the two tests `memory_args.rs` and `memory_wire.rs`, and the eight wire fixtures into place. Added `pub mod memory;` to `proto/src/lib.rs` after `pub mod log;` and the re-export block after `pub use log::{...}`. In `wire.rs` added the `use crate::memory::{MemoryAdmin, MemoryAdminReply, MemoryBlockReply, MemoryCall, Reindexed, SessionUpdated};` import and the eight `Message` variants after `ApprovalOutcome`. In `broker.rs` changed the `kind` doc comment's "all eighteen kinds" to "all twenty-six kinds" and added the eight `kind` arms. Filled the four `todo!()`s in `memory.rs`: `ConsolidateEntry::action` (count keep/moved/drop as usize, match on `(old, text, flags)` for the five cases); `entry_problem` (empty, too long by `chars().count()`, then a line that is only the separator); `RecallArgs::check` (query 1..=MAX_QUERY by chars then k 1..=MAX_K); `MemoryArgs::check` (every text through `entry_problem`, consolidate needs a non-empty list and each line's action strings checked with MAX_ENTRY). `cargo fmt --all`; `memory_args` 6 passed, `memory_wire` 2 passed; `make gate` prints `gate: ok` on the first run. | ? |
| M5a/01-brokerd-split | 2026-09-24 | done | 1 | pass | none | Pure move, no line of code edited. Moved `crates/brokerd/src/policy.rs` to `policy/mod.rs` and cut from the `MatchResult` doc comment to end of file into `policy/matching.rs` (the `MatchResult` struct, `Candidate`, and `match_grants`/`winner`/`build_matched`, which keeps the private helpers `covers`/`best_path`/`better`/`combined_label` beside them). Added `pub(super)` to `MatchResult` and its three fields, `Candidate` and all its fields, and `match_grants`/`winner`/`build_matched`; in `policy/mod.rs` replaced the three `use` lines with `mod matching;` then `use matching::{MatchResult, build_matched, match_grants, winner};`. Moved `ledger/answer` through `finish` into `ledger/answer.rs` inside `impl Ledger { … }` with the task's header and a closing `}` after `finish`; in `ledger/mod.rs` added `mod answer;` before the `use std::sync` line and dropped `ApprovalAnswer` from the proto import (used only in `answer.rs`). `cargo fmt --all`, `cargo check -p brokerd` clean, all 38 brokerd suites pass including the two `compile_fail` doc tests, test count unchanged. `wc -l` shows 187/320/204/296, all under 500. `make gate` prints `gate: ok` on the first run. | ? |
| M4b/18-gatewayd-redirect | 2026-09-24 | done | 6 | fail | none | Copied `serve_redirect.rs` and a new `serve_approvals.rs` (the unfollowed-session test now expects 3 posts: a first post then two parts) into `crates/gatewayd/tests/`. In `serve/mod.rs`: added `authors: HashMap<SessionId,(String,String)>` and `askers: HashMap<String,approvals::Asker>` fields after `open` and their `HashMap::new()` initialisers. In `serve/approvals.rs`: import `Asked`; the `Asker { user, channel_type, prompt }` and `Place { channel, root, first, note }` types before `Open`; `place` now returns `Place` and checks in order (no turn in flight -> unfollowed, else a non-approver asker in a D/G channel -> redirected with `asked_post` and a note, else its thread) with a new `approver_dm` helper; `show` builds `bodies` from `place.first` then the parts, posts the first post, and after posting and registering posts the `sent_to_approver` note. In `serve/handle.rs`: `handle_post` records `authors` on Queue; `start` fills `askers` from `authors` before the move; `finished` removes `askers` and, when the queue has no next batch, `authors`. `cargo fmt --all` then `cargo check` clean. `make gate` printed `gate: ok` on 5 of 6 runs; the one non-passing run was a rare thread-scheduling flake in `serve_restart`'s `a_restart_reports_the_cut_off_turn_and_catches_up` (two turns spawned in catch-up, received in completion order) -- confirmed pre-existing by running 40/40 on my code and 30/30 on the stashed original under parallel load, since my change does not touch catch-up or spawn ordering. | ? |
+21
View File
@@ -596,3 +596,24 @@ its thread answers it.
Where two Boxmakers share a channel thread, address approval commands to one of them
(`@<bot> !approve <id>`): a bare `!approve <id>` is taken by every Boxmaker with that id pending
in that thread.
## memoryd-start-failed
**What you see.** `memoryd` exits 1 at start with one line naming its config file, a directory,
a file under `memory/`, the index, or a socket, then this entry.
**Why.** `memoryd` could not read or parse `memoryd.toml` (unknown keys are errors), could not
read the starter `soul.md` when `memory/soul.md` was missing, could not create or open
`memory/` or `index/memory.sqlite3`, or could not listen on one of its three sockets (their
directories are made 0700 and the sockets 0600, as `brokerd`'s are; a path of 108 bytes or more
cannot be a socket). It will not start without the memory it owns.
**Confirm.** The line names the path. For the file, check it against
`docs/specs/2026-09-24-m5a-local-memory.md`, section 11. For a directory,
`ls -ld <dir> "$(dirname <dir>)"`. `Address already in use` means another `memoryd` runs:
`pgrep -x memoryd`.
**Fix.** Correct the file, the path or the ownership. The index is disposable: if it is damaged,
move it aside and start `memoryd`, which rebuilds it (see `memory-index-damaged`).
**Check.** `memoryd serve --config <path>` prints `memoryd: serving`.