Files
boxmaker/docs/plans/M4a/14-gatewayd-serve.md
T
kyleandClaude Opus 5.5 0339dc13b2 Plan M4a: gatewayd in 15 tasks, with skeletons and given tests
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>
2026-09-23 19:05:44 -07:00

80 lines
4.7 KiB
Markdown

# M4a task 14: the serve loop
**Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `gatewayd: serve, the event loop, typing, catch-up and reconnecting`
## Goal
The loop that ties tasks 03 to 13 together (spec sections 8 and 9):
- **Connecting**, at start and after every loss: `GET /users/me`, open the WebSocket, wait for
`hello`, then log `gatewayd: connected to <url> as <username>`. A connection that fails is tried
again after 1, 2, 5, 10, then every 30 seconds, with one log line per attempt:
`gatewayd: cannot reach <url>: <error>; trying again in <n> s`, then a newline and
`see docs/runbook.md#mattermost-unreachable`. A refused token (401 or 403) is never tried again:
`run` returns `Stop::Auth`.
- **After a restart** (the first connection only): every turn left in flight gets
`interrupted: gatewayd restarted before the answer arrived; ask again` in its thread. A later
reconnect must not do this: those turns are still running.
- **Catching up**: the direct channel with each allowed user, and each allowed channel, from its
mark. A channel without a mark is marked "now" and not caught up: history is not answered.
- **Each post**, live or caught up: posts in channels we do not track are not recorded at all. A
tracked post is recorded as handled **before** it is acted on, so after a crash it is not
answered twice (the in-flight record reports it instead). Then it is routed (task 11): a stranger
is logged by user id and post id only, **never with the text**.
- **Turns** run on their own threads (task 13), recorded in flight while they run. When one ends,
its session's waiting messages start the next.
- **Typing**: every `typing_every_ms`, `user_typing` for every thread with a turn running.
## Files
- Copy: `crates/gatewayd/tests/serve.rs`, `crates/gatewayd/tests/serve_restart.rs`,
`crates/gatewayd/tests/support/fake_mm.rs`, `crates/gatewayd/tests/support/gateway.rs`, and the
skeletons `crates/gatewayd/src/serve/mod.rs` and `crates/gatewayd/src/serve/handle.rs`
- Modify: `crates/gatewayd/src/lib.rs` (`pub mod serve;`), `docs/implementer-log.md`
## The skeletons
`serve/mod.rs`, written: the pointers, `INTERRUPTED`, `Log`, `Stop` (`Auth`, `State`, `Start`,
`Asked`) and its `Display`, `Tuning` and its default, the `Gateway` struct, and the two functions
that are glue: **`run`** (the connect-or-back-off loop) and **`Gateway::event_loop`** (finish turns,
send typing, wait for an event, handle it). Read both first: they call everything you write.
To fill: `From<StateError> for Stop`, `backoff`, `sleep_unless`, `connect`, `Gateway::new`,
`Gateway::connected`.
`serve/handle.rs`, all to fill: `now_ms`, `post`, `tracked`, `handle_post`, `start`, `finished`,
`typing`, `catch_up`.
Each `todo!()` has its steps above it, with the exact log lines. `run` takes the token already
loaded and a `stop` flag, so the tests need no secrets and can end it; task 15's `main` passes a
flag that is never set.
## Steps
- [ ] **1. Copy.** `git switch m4a`, then
`mkdir -p crates/gatewayd/src/serve && cp docs/plans/M4a/files/crates/gatewayd/src/serve/mod.rs docs/plans/M4a/files/crates/gatewayd/src/serve/handle.rs crates/gatewayd/src/serve/`,
`cp docs/plans/M4a/files/crates/gatewayd/tests/serve.rs docs/plans/M4a/files/crates/gatewayd/tests/serve_restart.rs crates/gatewayd/tests/`
and
`cp docs/plans/M4a/files/crates/gatewayd/tests/support/fake_mm.rs docs/plans/M4a/files/crates/gatewayd/tests/support/gateway.rs crates/gatewayd/tests/support/`.
Add `pub mod serve;` to `lib.rs`.
- [ ] **2. See it fail.** `cargo test -p gatewayd --no-fail-fast --test serve --test serve_restart`.
Expected: it compiles; `serve` 6 fail; `serve_restart` 6 fail and 1 passes (a damaged state file
stops `run` before anything you write is called).
- [ ] **3. Fill `mod.rs` first** (`from`, `backoff`, `sleep_unless`, `Gateway::new`, `connect`,
`connected`), **then `handle.rs`** (`now_ms`, `post`, `tracked`, `finished`, `typing`, `start`,
`handle_post`, `catch_up`). `cargo check -p gatewayd` after each function.
- [ ] **4. See it pass.** `cargo test -p gatewayd --test serve --test serve_restart`, five times.
Expected: 6 and 7 passed each time, in about 2 s.
- [ ] **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
- Both suites pass five times running; `make gate` prints `gate: ok`.
## Stop and report if
- A test passes only sometimes, or a test takes 5 s or more (that is a wait that timed out, not a
pass).
- `run` or `event_loop` seems to need a change. They are given; report instead.