Skeletons for new files only; existing files change by exact edits, so no task overwrites an earlier one's work. Each task's tests were run against a reference built in order from master, with the gate at each step and mutations checked; each skeleton compiles and fails its tests, and every todo!() comment was read beside its signature. The reference is kept off this machine. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.2 KiB
4.2 KiB
M4b task 07: [approvals] in gatewayd.toml
Branch: m4b (run git switch m4b; git status --short must be empty, otherwise stop)
Commit subject: gatewayd: config, [approvals] users and socket
Goal
Who may answer approvals, and where brokerd brings them (M4b spec, section 3):
[approvals]
users = [] # Mattermost user ids who may answer; empty: everyone in [allow] users
socket = "" # empty: <home>/run/gateway/gateway.sock
An approver must also be in [allow] users: one who is not heard could never answer, so that is a
configuration error. The first approver (approvals.users[0], or allow.users[0] when the
list is empty) gets, in direct messages, the approvals of sessions that are not in Mattermost.
Files
- Copy:
crates/gatewayd/tests/config_approvals.rs - Modify:
crates/gatewayd/src/config.rs,docs/runbook.md,docs/implementer-log.md
The changes, exactly
In crates/gatewayd/src/config.rs:
- In
struct Config, afterpub limits: Limits,:
#[serde(default)]
pub approvals: ApprovalsConfig,
and after struct Config:
/// `[approvals]`: who may answer approvals, and where `brokerd` brings them.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
#[serde(deny_unknown_fields, default)]
pub struct ApprovalsConfig {
/// Mattermost user ids; empty means everyone in `[allow] users`.
pub users: Vec<String>,
/// Empty: `<home>/run/gateway/gateway.sock`.
pub socket: PathBuf,
}
- In
problem, right before the[limits]checks (if self.limits.queue == 0):
for id in &self.approvals.users {
if !self.allow.users.contains(id) {
return Some(format!(
"[approvals] {id:?} is not in [allow] users: an approver who is not heard could never answer"
));
}
}
- In
impl Config, beforestate_path, three methods. Write them from these descriptions:pub fn approvers(&self) -> &[String]:&self.approvals.users, or&self.allow.userswhen the first is empty.pub fn first_approver(&self) -> Option<&str>: the first ofapprovers()(.first().map(String::as_str)).pub fn gateway_socket(&self) -> PathBuf:self.approvals.socket, orself.paths.home.join("run/gateway/gateway.sock")when it is empty (as_os_str().is_empty()).
In docs/runbook.md, entry gatewayd-start-failed, replace the Why and Confirm
paragraphs with:
**Why.** `gatewayd` could not read or parse `gatewayd.toml`, or a value in it is not allowed: a
`url` that is not `http://` or `https://` with a host and an optional port, an id that is not 26
characters of `a-z0-9`, an empty `[allow] users`, an id in `[approvals] users` that is not also in
`[allow] users`, or a secret with not exactly one of `credential`, `env` and `file`. Or it could
not create `<home>/gateway/`, or could not listen on `gateway.sock` (its directory is made 0700
and the socket 0600, as `brokerd`'s are; a path of 108 bytes or more cannot be a socket). It will
not guess at a configuration that decides who it answers and who approves.
**Confirm.** The line says which: `<config path>: …` for the file (unknown keys are errors; check
it against `docs/specs/2026-09-23-m4a-gateway.md`, section 3, and
`docs/specs/2026-09-24-m4b-approvals.md`, section 3), `cannot prepare <dir>: …` for a directory
(`ls -ld <dir> "$(dirname <dir>)"`), or `cannot listen on <socket>: …` for `gateway.sock`
(`Address already in use` means another `gatewayd` runs: `pgrep -x gatewayd`).
Steps
- 1. Copy.
git switch m4b, thencp docs/plans/M4b/files/crates/gatewayd/tests/config_approvals.rs crates/gatewayd/tests/ - 2. See it fail.
cargo test -p gatewayd --test config_approvals. Expected: it does not compile. - 3. Make the changes. Run
cargo fmt --all. - 4. See it pass.
cargo test -p gatewayd --test config_approvals --test config. Expected: 4 and 7 passed. - 5. Run the gate.
make gate. Expected last line:gate: ok. - 6. Log and commit.
git add crates/gatewayd docs/runbook.md docs/implementer-log.md Cargo.lock && git commit
Done when
- Both suites pass;
make gateprintsgate: ok.