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>
80 lines
3.2 KiB
Markdown
80 lines
3.2 KiB
Markdown
# M5a task 10: serving
|
|
|
|
**Branch:** `m5a` (run `git switch m5a`; `git status --short` must be empty, otherwise stop)
|
|
**Commit subject:** `memoryd: serve, three sockets and one lock over the files and the index`
|
|
|
|
## Goal
|
|
|
|
`memoryd serve --config <path>` (M5a spec, section 7): at start it creates what is missing under
|
|
`memory/`, opens the index and catches it up, then listens on three sockets, one request per
|
|
connection, each answered by one frame:
|
|
|
|
| Socket | Peer | Takes |
|
|
|---|---|---|
|
|
| `run/loop-memory/memory.sock` | `loopd` | `memory_block`, `session_updated` |
|
|
| `run/broker-memory/memory.sock` | `brokerd` | `memory_call` (`recall` or `memory`) |
|
|
| `run/owner-memory/admin.sock` | `bxctl` | `reindex`, `memory_admin` |
|
|
|
|
A message on the wrong socket is refused (`BadMessage`). One lock covers the files and the index,
|
|
so writes never interleave.
|
|
|
|
## Files
|
|
|
|
- Copy (skeleton): `crates/memoryd/src/serve.rs`
|
|
- Copy (test): `crates/memoryd/tests/serve.rs`
|
|
- Replace: `crates/memoryd/src/main.rs` with `docs/plans/M5a/files/crates/memoryd/src/main.rs-task10`
|
|
(task 03 gave it whole, and nothing has changed it since; this version starts the server)
|
|
- Modify: `crates/memoryd/src/lib.rs` (`pub mod serve;` after `pub mod review;`),
|
|
`crates/memoryd/src/index.rs`, `docs/implementer-log.md`
|
|
|
|
## `index.rs`, exactly
|
|
|
|
Add this function just before the doc comment `/// Every session log under `sessions`: `(session,
|
|
epoch, path)`, sorted.`:
|
|
|
|
```rust
|
|
/// The logs of one session: `(epoch, path)`, sorted. A session with no directory has none.
|
|
pub fn session_logs(sessions: &Path, session: &str) -> Result<Vec<(i64, PathBuf)>, String> {
|
|
let dir = sessions.join(session);
|
|
let files = match std::fs::read_dir(&dir) {
|
|
Ok(files) => files,
|
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
|
|
Err(e) => return Err(format!("{}: {e}", dir.display())),
|
|
};
|
|
let mut found = Vec::new();
|
|
for file in files.filter_map(|f| f.ok()) {
|
|
let name = file.file_name().to_string_lossy().into_owned();
|
|
if let Some(Ok(epoch)) = name.strip_suffix(".jsonl").map(str::parse::<i64>) {
|
|
found.push((epoch, file.path()));
|
|
}
|
|
}
|
|
found.sort();
|
|
Ok(found)
|
|
}
|
|
```
|
|
|
|
## The skeleton
|
|
|
|
Written: `READ_WAIT`, `Log`, `State`, `Memoryd`, `Running`, `Which`, `error`, `bad`,
|
|
`Memoryd::lock`, and **`Memoryd::handle`**, the table of which socket takes which message: read
|
|
it first. Fill: `start`, `Running::run`, `connection`, `block`, `session_updated`, `call`,
|
|
`recall`, `memory`, `index_notes`, `reindex`, `admin`.
|
|
|
|
## Steps
|
|
|
|
- [ ] **1. Copy** the two files, replace `main.rs`; edit `lib.rs` and `index.rs`.
|
|
- [ ] **2. See it fail.** `cargo test -p memoryd --test serve`: 8 failed.
|
|
- [ ] **3. Fill** the functions. `cargo fmt --all`.
|
|
- [ ] **4. See it pass.** The same command, three times: 8 passed each time, in about a second.
|
|
- [ ] **5. Run the gate.** `make gate`: `gate: ok`, 8 more tests than before (about 920).
|
|
- [ ] **6. Log and commit.** `git add -A crates/memoryd docs/implementer-log.md Cargo.lock && git commit`
|
|
|
|
## Done when
|
|
|
|
- `make gate` prints `gate: ok`.
|
|
|
|
## Stop and report if
|
|
|
|
- A test fails after the functions follow their comments exactly. Do not edit a test.
|
|
- A test waits more than a few seconds, or passes only sometimes.
|