# M4a task 11: which posts become turns, and the queue per session **Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop) **Commit subject:** `gatewayd: sessions, routing posts to sessions, commands and the queue` ## Goal The decisions, as pure code: no network, no files. For each new post, **in this order** (spec section 7): 1. Ignore it if it is the bot's own, or a system message (`kind` not empty). 2. Ignore it, **silently**, unless the author is in `allow.users`. 3. Is it for this Boxmaker? A direct message (`"D"`): always. A channel or group message (`"O"`, `"P"`, `"G"`) whose id is in `allow.channels`: if it **names this bot**, or if it is a reply in a thread this Boxmaker already has a session for **and names nobody else**. Anything else: ignore. `@channel`, `@here` and `@all` name nobody: every agent would answer them. 4. The session: its root is `root_id`, or the post's own id when that is empty; the session id is `mm-`. A top-level post is a new session (`resume: false`); a reply resumes it. 5. Commands: a message starting with `!`. `!!…` is not a command: one `!` is removed and the rest goes on as a message. `!approve …` and `!deny …` are answered with `M4B_COMMAND`; any other `!` with `UNKNOWN_COMMAND`. A command never reaches `loopd`. 6. Otherwise the message joins its session's queue. "Names" means `@` in the message, case-insensitive, where the name is the longest run of `a-z`, `0-9`, `.`, `-` and `_` after the `@`, without trailing dots. The spec's examples, with this bot called `boxmaker-straylight` and another agent called Hermes in the channel (the first test checks every row): | Post | For Boxmaker? | |---|---| | `@boxmaker-straylight summarise the audit log` (top level) | yes: a new session rooted here | | a reply in that thread: `and the older files?` | yes: its thread, nobody else named | | a reply in that thread: `@hermes what do you think?` | no | | `@boxmaker-straylight @hermes compare notes` | yes (and Hermes answers too) | | `@boxmaker-straylightx hello` | no: a different name | | `@channel standup in five` | no | **The queue.** One turn at a time per session. A message for a session with no turn running starts one at once. Messages that arrive while it runs wait; when it ends, **all** waiting messages go together as the next turn, joined with a blank line (`"\n\n"`), in the order they came. A session with `limit` messages already waiting drops the next one, and the caller answers `BUSY`. ## Files - Copy: `crates/gatewayd/tests/sessions.rs`, and the skeleton `crates/gatewayd/src/sessions.rs` - Modify: `crates/gatewayd/src/lib.rs` (`pub mod sessions;`), `docs/implementer-log.md` ## The skeleton Written: the three answer texts (`M4B_COMMAND`, `UNKNOWN_COMMAND`, `BUSY`), `EVERYONE`, `Ignored` (`Own`, `System`, `NotAllowed`, `NotForUs`), `Thread { channel, root }`, `Message { session, thread, resume, text, joins_thread }`, `Route` (`Ignore`, `Reply { thread, text }`, `Queue`), `Router`, `Batch { session, thread, resume, text }`, `Pushed` (`Start(Batch)`, `Waiting`, `Full(Thread)`), `Pending`, `Queues`. To fill: `named`, `Router::new`, `Router::for_us`, `Router::route`, `Queues::new`, `push`, `finish`, `running`, `threads`. `route` is a straight line of early returns in the order above; the `known` function it is given answers "does this Boxmaker have a session for this thread root?" (task 14 answers it from the state file). `SessionId` has no `Ord`, so `Queues` keys a `HashMap`. ## Steps - [ ] **1. Copy.** `git switch m4a`, then `cp docs/plans/M4a/files/crates/gatewayd/tests/sessions.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/src/sessions.rs crates/gatewayd/src/`. Add `pub mod sessions;` to `lib.rs`. - [ ] **2. See it fail.** `cargo test -p gatewayd --test sessions`. Expected: it compiles and 8 tests fail. - [ ] **3. Fill `named`, then the `Router`, then the `Queues`**, `cargo check -p gatewayd` after each function. - [ ] **4. See it pass.** `cargo test -p gatewayd --test sessions`. Expected: 8 passed. - [ ] **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 sessions` passes; `make gate` prints `gate: ok`. ## Stop and report if - A row of the table above seems to need a different order of the steps.