a73x

93710915

build: the product is shell-free by rule, not by folder

a73x   2026-08-29 01:12

Commit message
build: the product is shell-free by rule, not by folder

Folder rule 5 covered src/client and src/tui only, so the two folders that
actually spawn things were the two nothing checked. The three production
sites that legitimately name a shell take the per-file exemption.

build.zig
Old New
@@ -472,11 +472,12 @@ const source_bans = [_]SourceBan{
472 }, 472 },
473 .{ 473 .{
474 .rule = "5", 474 .rule = "5",
475 .folders = &.{ "src/client", "src/tui" }, 475 .folders = &.{ "src/client", "src/tui", "src/server", "src/cli" },
476 .needles = &.{ "\"/bin/sh\"", "\"-c\"" }, 476 .needles = &.{ "\"/bin/sh\"", "\"-c\"" },
477 .why = "the only program the client runs is one the user named — " ++ 477 .why = "the only program mux runs is one the user named — the " ++
478 "`ssh` from the handoff recipe, or `--via`'s own words — and it " ++ 478 "session shell, `ssh` from the handoff recipe, or `--via`'s own " ++
479 "is exec'd as argv, so no shell of ours ever parses it", 479 "words — and every one is exec'd as argv, so no shell of ours " ++
480 "ever parses a line we built",
480 }, 481 },
481 }; 482 };
482 483
src/cli/flags.zig
Old New
@@ -5,6 +5,10 @@
5 //! everything after it payload. What a flag MEANS stays with the caller, in 5 //! everything after it payload. What a flag MEANS stays with the caller, in
6 //! post-checks over the parsed struct, or in a field TYPE that declares 6 //! post-checks over the parsed struct, or in a field TYPE that declares
7 //! `parseCLI` — a rule one type owns is a rule no new caller can forget. 7 //! `parseCLI` — a rule one type owns is a rule no new caller can forget.
8 // folder rule 5 exemption: `/bin/sh` here is a shell NAME the daemon execs
9 // as argv[0] when nothing else names one, not a `-c` line for a shell to
10 // parse.
11
8 const std = @import("std"); 12 const std = @import("std");
9 13
10 pub const Outcome = union(enum) { 14 pub const Outcome = union(enum) {
src/cli/main.zig
Old New
@@ -1,6 +1,9 @@
1 //! `mux d` — the daemon mode. `run` hosts the session; `dump` prints the 1 //! `mux d` — the daemon mode. `run` hosts the session; `dump` prints the
2 //! authoritative grid over the protocol (debug aid, also used by e2e); 2 //! authoritative grid over the protocol (debug aid, also used by e2e);
3 //! `proxy` exposes the session socket over stdio for `mux --via`. 3 //! `proxy` exposes the session socket over stdio for `mux --via`.
4 // folder rule 5 exemption: the daemon's whole job is to spawn the user's
5 // shell, and `/bin/sh` is the name it falls back to when $SHELL says nothing.
6
4 const std = @import("std"); 7 const std = @import("std");
5 const Server = @import("server").Server; 8 const Server = @import("server").Server;
6 const proto = @import("protocol"); 9 const proto = @import("protocol");
src/server/server_test_session.zig
Old New
@@ -19,6 +19,9 @@ const connectedPair = h.connectedPair;
19 const firstStateFrame = h.firstStateFrame; 19 const firstStateFrame = h.firstStateFrame;
20 const serverThread = h.serverThread; 20 const serverThread = h.serverThread;
21 21
22 // folder rule 5 exemption: a test's daemon needs a shell to spawn, and this
23 // file's fixture helpers sit outside the `test` blocks the rule skips.
24
22 /// The teardown on the failing branch is not tidiness: a Server that got built 25 /// The teardown on the failing branch is not tidiness: a Server that got built
23 /// owns a live shell on a pty, and discarding it leaves that shell holding the 26 /// owns a live shell on a pty, and discarding it leaves that shell holding the
24 /// test runner's stdout — the build never sees EOF and hangs instead of 27 /// test runner's stdout — the build never sees EOF and hangs instead of