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

4.7 KiB

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.