Each task's tests were run against a reference at its end state; the end states were replayed from master in order with the gate at each step (650 to 762 tests); each skeleton compiles against its tests and fails them. The reference is kept off this machine. Lessons T27 (every wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
64 lines
3.0 KiB
Markdown
64 lines
3.0 KiB
Markdown
# M4a task 12: the state file
|
|
|
|
**Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop)
|
|
**Commit subject:** `gatewayd: state, what was handled, our threads and turns in flight`
|
|
|
|
## Goal
|
|
|
|
`<home>/gateway/state.json` lets `gatewayd` pick up where it left off (spec section 9):
|
|
|
|
```json
|
|
{"channels": {"<channel id>": 1758650000000}, "recent": ["<post id>", …],
|
|
"threads": ["<root post id>", …],
|
|
"in_flight": [{"session": "mm-…", "channel": "<id>", "root": "<id>"}]}
|
|
```
|
|
|
|
- `channels`: the `create_at` of the last post handled in each channel; catching up starts there.
|
|
A mark never moves back.
|
|
- `recent`: the ids of the last 500 posts handled (`RECENT_KEPT`), so a post seen twice (live and
|
|
in a catch-up) is handled once.
|
|
- `threads`: the roots of the threads this Boxmaker takes part in, in channels; the newest 5,000
|
|
(`THREADS_KEPT`).
|
|
- `in_flight`: turns sent to `loopd` and not yet answered; after a restart each gets an
|
|
"interrupted" reply.
|
|
|
|
It is **our** format: unknown fields are errors, and every id must be a valid Mattermost id. It is
|
|
written atomically after every change, in the same six steps as `brokerd/src/state.rs`'s
|
|
`persist` (read it). A file that exists but cannot be read, parsed or written stops `gatewayd`
|
|
with `see docs/runbook.md#gateway-state-damaged`: guessing would answer posts twice. **Only a
|
|
missing file** is a first start.
|
|
|
|
## Files
|
|
|
|
- Copy: `crates/gatewayd/tests/state.rs`, and the skeleton `crates/gatewayd/src/state.rs`
|
|
- Modify: `crates/gatewayd/src/lib.rs` (`pub mod state;`), `docs/implementer-log.md`
|
|
|
|
## The skeleton
|
|
|
|
Written: `RECENT_KEPT`, `THREADS_KEPT`, `RUNBOOK`, `StateError` (`Read(PathBuf, String)`,
|
|
`Write(PathBuf, io::Error)`) and its `Display` (the path, the problem, and the pointer on its own
|
|
line), `InFlight`, the private `StateFile`, `State { path, file }`.
|
|
|
|
To fill: `StateFile::problem`, `State::load`, `save`, `persist`, and the methods `seen`, `handled`,
|
|
`since`, `channels`, `mark`, `knows_thread`, `join_thread`, `start_turn`, `end_turn`,
|
|
`take_in_flight`. Every method that changes the state saves it before it returns, and returns the
|
|
save's error.
|
|
|
|
## Steps
|
|
|
|
- [ ] **1. Copy.** `git switch m4a`, then
|
|
`cp docs/plans/M4a/files/crates/gatewayd/tests/state.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/src/state.rs crates/gatewayd/src/`.
|
|
Add `pub mod state;` to `lib.rs`.
|
|
- [ ] **2. See it fail.** `cargo test -p gatewayd --test state`. Expected: it compiles and 4 tests
|
|
fail.
|
|
- [ ] **3. Fill `problem`, `load`, `persist`, `save`, then the methods**, `cargo check -p gatewayd`
|
|
after each.
|
|
- [ ] **4. See it pass.** `cargo test -p gatewayd --test state`. Expected: 4 passed, in well under a
|
|
second.
|
|
- [ ] **5. Run the gate.** `cargo fmt --all`, then `make gate`. Expected last line: `gate: ok`.
|
|
- [ ] **6. Log and commit.** `git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit`
|
|
|
|
## Done when
|
|
|
|
- `cargo test -p gatewayd --test state` passes; `make gate` prints `gate: ok`.
|