Files
boxmaker/docs/plans/M5a/16-macos-gate.md
T
kyleandClaude Opus 5.5 64190c3701 M5a plan: task 16, the gate passes on macOS; the review's tasks become 17 to 20
The work moved to titan (macOS), where the gate failed for reasons older
than M5a: sockets under macOS's long TMPDIR pass its 104-byte limit,
gatewayd reads /proc/self for its uid, and /tmp is /private/tmp. Task 16
sets TMPDIR=/tmp for every process Cargo runs (.cargo/config.toml [env]),
has gatewayd take its uid from the owner of a pipe it makes (no dependency,
no unsafe), and hands over a toolkit shell.rs that compares resolved paths.
On a scratch tree the gate passes on titan and on talos with 936 tests, and
a wrong uid fails 7 gatewayd tests. Tasks 16 to 19 are renumbered 17 to 20.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 16:41:44 -07:00

113 lines
4.4 KiB
Markdown

# M5a task 16: the gate passes on macOS as well as on Linux
**Branch:** `m5a` (run `git switch m5a`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `gate: pass on macOS: tests use /tmp, gatewayd finds its uid without /proc`
## Goal
Work moved to titan, a Mac, on 2026-09-24. There `make gate` fails although the code is right,
because three places assume Linux. The harness itself still runs on straylight (Linux); this
task only makes the gate pass on both. No behaviour change on Linux, and no new test.
1. **Unix socket paths are too long.** Tests put sockets under `std::env::temp_dir()`. On macOS
that is `/var/folders/…/T/`, about 50 bytes, and a socket's whole path may be at most 104
bytes there, so some tests' daemons cannot bind (for example brokerd's
`serve_runner.rs` and gatewayd's `serve_restart.rs`). A new `.cargo/config.toml` sets
`TMPDIR=/tmp` for every process Cargo runs, tests included.
2. **`gatewayd` reads `/proc/self`** for the user id it runs as, when it checks a secret file's
owner. macOS has no `/proc`. `std` has no call for the user id, and `unsafe` (for `libc`) is
not allowed, so a new function `user_id` takes the owner of a pipe the process has just made,
which is the user the process runs as on Linux and on macOS (checked on both: 1000 on talos,
501 on titan, the same as `id -u`).
3. **`toolkit`'s `shell.rs` test** expects `pwd` to print `/tmp`; on macOS `/tmp` is a link to
`/private/tmp`, and `pwd` prints the resolved path. A new version of that given test compares
with the resolved path.
## Files
- Create: `.cargo/config.toml` (at the repository root, next to `Cargo.toml`)
- Copy: a **new version** of the given `crates/toolkit/tests/shell.rs`, from
`docs/plans/M5a/files/crates/toolkit/tests/shell.rs`
- Modify: `crates/gatewayd/src/secrets.rs`, `docs/implementer-log.md`
## The changes, exactly
**1. Create `.cargo/config.toml`** with exactly this content:
```toml
# Tests put Unix sockets under `std::env::temp_dir()`. macOS's is long (/var/folders/…/T/), and a
# socket's path there can pass the 104 bytes macOS allows, so every process Cargo runs, tests
# included, uses /tmp.
[env]
TMPDIR = { value = "/tmp", force = true }
```
**2. `crates/gatewayd/src/secrets.rs`**, in `read_checked`. Nothing else in the file changes.
**a.** In the comment, the line
```rust
// on `named`): the uid of "/proc/self"; "<path> is not owned by the user gatewayd runs as";
```
becomes
```rust
// on `named`): the uid of `user_id()`; "<path> is not owned by the user gatewayd runs as";
```
**b.** The three lines
```rust
let owner = std::fs::metadata("/proc/self")
.map_err(|e| format!("cannot read /proc/self: {}", e))?
.uid();
```
become the one line
```rust
let owner = user_id()?;
```
**c.** Insert this function immediately above the line
`/// The text without one trailing newline; not empty; UTF-8.` (the doc comment of `value`), with
one blank line between them:
```rust
/// The user id this process runs as: the owner of a pipe it has just made. `std` has no call for
/// it, and `/proc/self` is Linux only.
fn user_id() -> Result<u32, String> {
let (reader, _writer) = std::io::pipe().map_err(|e| format!("cannot make a pipe: {e}"))?;
let pipe = std::fs::File::from(std::os::fd::OwnedFd::from(reader));
let meta = pipe
.metadata()
.map_err(|e| format!("cannot read a pipe: {e}"))?;
Ok(meta.uid())
}
```
No `use` line changes: `MetadataExt` (for `.uid()`) is already imported, and the rest is named in
full.
## Steps
- [ ] **1. Create `.cargo/config.toml`**, then copy the test:
`cp docs/plans/M5a/files/crates/toolkit/tests/shell.rs crates/toolkit/tests/`
- [ ] **2. Make the three edits to `secrets.rs`.** Run `cargo fmt --all`.
- [ ] **3. Run the tests.** `cargo test -p gatewayd -p toolkit -p brokerd`: every suite passes.
- [ ] **4. Run the gate.** `make gate`. Expected last line: `gate: ok`, with 936 tests, the same
as before this task. On macOS it failed before this task, with 8 tests failing; now it passes.
- [ ] **5. Log and commit.**
`git add .cargo/config.toml crates/gatewayd crates/toolkit docs/implementer-log.md && git commit`
## Done when
- `make gate` prints `gate: ok` with 936 tests.
## Stop and report if
- Any line quoted above is not in the file exactly as shown.
- `cargo-deny` is missing (`error: no such command: deny`): report it, do not install anything.
- Any test fails after the changes. Do not change a test.