a73x

daa3bf69

refactor: the daemon owns its command surface, shell integration, upgrade and QUIC arm as child files

a73x   2026-08-30 12:01

Commit message
refactor: the daemon owns its command surface, shell integration, upgrade and QUIC arm as child files

build.zig
Old New
@@ -159,10 +159,6 @@ const mod_table = [_]ModSpec{
159 // side-channel events. The borrowed clipboard slice stays tied to the 159 // side-channel events. The borrowed clipboard slice stays tied to the
160 // frame payload, so native and wasm consumers share one contract. 160 // frame payload, so native and wasm consumers share one contract.
161 .{ .name = "client_core", .path = "src/client/client_core.zig", .layer = 1, .wasm = true, .imports = &.{"protocol"} }, 161 .{ .name = "client_core", .path = "src/client/client_core.zig", .layer = 1, .wasm = true, .imports = &.{"protocol"} },
162 // The QUIC listener: the vocabulary plus a UDP socket and a connection
163 // table, and deliberately no protocol import — it carries opaque bytes,
164 // exactly as proxy.zig does.
165 .{ .name = "quic_server", .path = "src/server/quic_server.zig", .layer = 1, .link_libc = true, .imports = &.{"quic"}, .quic_tests = true },
166 // This image, as a path something can exec. Under src/cli/ because it 162 // This image, as a path something can exec. Under src/cli/ because it
167 // asks the OS about the process it is in — a question no headless 163 // asks the OS about the process it is in — a question no headless
168 // client may spell. 164 // client may spell.
@@ -182,28 +178,12 @@ const mod_table = [_]ModSpec{
182 // protocol and nothing else — no daemon, no clients — so the tracker's 178 // protocol and nothing else — no daemon, no clients — so the tracker's
183 // own tests drive it with an engine and no socket in sight. 179 // own tests drive it with an engine and no socket in sight.
184 .{ .name = "delta", .path = "src/engine/delta.zig", .layer = 1, .imports = &.{ "engine", "protocol" } }, 180 .{ .name = "delta", .path = "src/engine/delta.zig", .layer = 1, .imports = &.{ "engine", "protocol" } },
185 // The session's command state machine: MarkEvents in, transitions out.
186 // Engine plus protocol and nothing else, same shape as delta — pure,
187 // socket-free, and its own tests drive it with no daemon in sight.
188 .{ .name = "cmd", .path = "src/server/cmd.zig", .layer = 1, .imports = &.{ "engine", "protocol" } },
189 // The wall: the host spelling grammar, the daemon list, and the state 181 // The wall: the host spelling grammar, the daemon list, and the state
190 // file behind them, plus the one atomic writer every state file uses. 182 // file behind them, plus the one atomic writer every state file uses.
191 // One owner for a grammar the hub, the file and the CLI all have to 183 // One owner for a grammar the hub, the file and the CLI all have to
192 // agree on — xdg for where that file lives, so its tests need no hub, 184 // agree on — xdg for where that file lives, so its tests need no hub,
193 // no daemon and no socket. 185 // no daemon and no socket.
194 .{ .name = "hosts", .path = "src/client/hosts.zig", .layer = 1, .imports = &.{"xdg"}, .test_imports = &.{"testtmp"} }, 186 .{ .name = "hosts", .path = "src/client/hosts.zig", .layer = 1, .imports = &.{"xdg"}, .test_imports = &.{"testtmp"} },
195 // The upgrade vocabulary: the version skew rule and the manifest an
196 // exec-ing daemon leaves for its replacement. Protocol only — the
197 // manifest is a stranger's format (length-prefixed sections, unknown
198 // tags skipped), so it cannot depend on server types.
199 .{ .name = "upgrade", .path = "src/server/upgrade.zig", .layer = 1, .imports = &.{"protocol"} },
200 // Shell integration: the OSC 133 mark scripts and what a spawn must add
201 // to hand them to a shell. Near-leaf on purpose — it writes files and
202 // reads the environment, and knows nothing of ptys, servers or the
203 // protocol, so its tests need no daemon and no socket. The one import
204 // is xdg, for the private-directory policy the shim directory shares
205 // with the key file's parent; xdg is itself a leaf, so no cycle.
206 .{ .name = "shellint", .path = "src/server/shellint.zig", .layer = 1, .imports = &.{"xdg"} },
207 // The replay core: snapshot/delta application and the resume 187 // The replay core: snapshot/delta application and the resume
208 // coordinates, shared by the CLI client, the wasm core, and the 188 // coordinates, shared by the CLI client, the wasm core, and the
209 // server's test fixtures. Engine plus protocol and nothing else, and 189 // server's test fixtures. Engine plus protocol and nothing else, and
@@ -223,11 +203,18 @@ const mod_table = [_]ModSpec{
223 // under it. 203 // under it.
224 .{ .name = "ptyclient", .path = "test/ptyclient.zig", .layer = 1, .link_libc = true, .imports = &.{ "pty", "script" } }, 204 .{ .name = "ptyclient", .path = "test/ptyclient.zig", .layer = 1, .link_libc = true, .imports = &.{ "pty", "script" } },
225 // ---- layer 2 ---- 205 // ---- layer 2 ----
226 // quic and quic_server both: the listener it owns, and the vocabulary 206 // The command state machine, shell integration, the upgrade vocabulary
227 // it names directly (the key it loads, the idle default it falls back 207 // and the QUIC listener are CHILD FILES of this root rather than modules
228 // to). xdg is for endpoint_req's lazy bind — the default key path, 208 // of their own, so nothing outside src/server/ can name one: a second
229 // resolved by the daemon itself when nobody handed it a --key. 209 // module claiming any of those files is a file-in-multiple-modules
230 .{ .name = "server", .path = "src/server/server.zig", .layer = 2, .link_libc = true, .imports = &.{ "engine", "pty", "protocol", "delta", "cmd", "shellint", "sockpath", "quic", "quic_server", "xdg", "upgrade", "proxy" }, .test_imports = &.{ "replica", "testtmp" }, .quic_tests = true }, 210 // compile error. `quic` is the listener's and the daemon's both — the
211 // socket that child opens, and the vocabulary this root names directly
212 // (the key it loads, the idle default it falls back to). xdg is for
213 // endpoint_req's lazy bind — the default key path, resolved by the
214 // daemon itself when nobody handed it a --key — and for the shim
215 // directory shell integration writes under the same 0700 policy.
216 // `pty` stays a row of its own: the ptyclient fixture consumes it.
217 .{ .name = "daemon", .path = "src/server/server.zig", .layer = 2, .link_libc = true, .imports = &.{ "engine", "pty", "protocol", "delta", "sockpath", "quic", "xdg", "proxy" }, .test_imports = &.{ "replica", "testtmp" }, .quic_tests = true },
231 // The agent-facing client. It speaks frames and owns no terminal, which 218 // The agent-facing client. It speaks frames and owns no terminal, which
232 // is the whole point — it attaches at 0x0 and never claims the grid. 219 // is the whole point — it attaches at 0x0 and never claims the grid.
233 // The transport modules are the CLI client's, minus everything that 220 // The transport modules are the CLI client's, minus everything that
@@ -247,13 +234,14 @@ const mod_table = [_]ModSpec{
247 // wall and the browser hub resolve a host line the same way. Nothing 234 // wall and the browser hub resolve a host line the same way. Nothing
248 // here WRITES that file — `wall_host.recordHost` and `hub_main` do. 235 // here WRITES that file — `wall_host.recordHost` and `hub_main` do.
249 .{ .name = "client", .path = "src/client/client.zig", .layer = 3, .link_libc = true, .imports = &.{ "protocol", "replica", "keymap", "quic", "handoff", "hosts", "xdg", "sockpath", "askpass" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 236 .{ .name = "client", .path = "src/client/client.zig", .layer = 3, .link_libc = true, .imports = &.{ "protocol", "replica", "keymap", "quic", "handoff", "hosts", "xdg", "sockpath", "askpass" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
250 // The daemon entrypoint loads the key and constructs the listener, so 237 // The daemon entrypoint loads the key and constructs the listener, so it
251 // it needs quic/quic_server directly rather than through the server. 238 // needs `quic` directly; the listener type and the upgrade vocabulary it
252 // `muxd endpoint` prints the announce line handoff spells; sockpath is 239 // reaches through the daemon root's re-exports, being that root's own
253 // the sun_path bound, checked before any verb acts on the path; and the 240 // child files now. `muxd endpoint` prints the announce line handoff
254 // keygen round-trip test needs a directory to generate into, which the 241 // spells; sockpath is the sun_path bound, checked before any verb acts
255 // daemon itself never touches. 242 // on the path; and the keygen round-trip test needs a directory to
256 .{ .name = "daemon_main", .path = "src/cli/main.zig", .layer = 3, .link_libc = true, .imports = &.{ "server", "protocol", "proxy", "quic", "quic_server", "xdg", "spawn", "handoff", "sockpath", "upgrade", "cliflags" }, .test_imports = &.{"testtmp"}, .quic_tests = true }, 243 // generate into, which the daemon itself never touches.
244 .{ .name = "daemon_main", .path = "src/cli/main.zig", .layer = 3, .link_libc = true, .imports = &.{ "daemon", "protocol", "proxy", "quic", "xdg", "spawn", "handoff", "sockpath", "cliflags" }, .test_imports = &.{"testtmp"}, .quic_tests = true },
257 // ---- layer 4 ---- 245 // ---- layer 4 ----
258 // The muxweb hub's HTTP/WebSocket decisions: Origin gate, route 246 // The muxweb hub's HTTP/WebSocket decisions: Origin gate, route
259 // table, WS endpoint naming. Assets are injected (the exe root 247 // table, WS endpoint naming. Assets are injected (the exe root
@@ -820,24 +808,24 @@ fn docGate(b: *std.Build, target: std.Build.ResolvedTarget, check_step: *std.Bui
820 } 808 }
821 809
822 /// Test registration order. Doctrine-laden and deliberately NOT derived 810 /// Test registration order. Doctrine-laden and deliberately NOT derived
823 /// from the layers: delta, cmd, shellint and sockpath run BEFORE server 811 /// from the layers: delta and sockpath run BEFORE daemon because their
824 /// because their tests are seconds-long and socket-free, while a 812 /// tests are seconds-long and socket-free, while a regression in either
825 /// regression in any of them can wedge a server test that waits on a 813 /// can wedge a daemon test that waits on a client forever — and a wedged
826 /// client forever — and a wedged step prints nothing at all. Failing 814 /// step prints nothing at all. Failing first is what makes the catch
827 /// first is what makes the catch legible. script leads for the same 815 /// legible; the daemon's own child files obey the same rule inside its
828 /// reason: instant, allocation-only, and both fixtures inherit its 816 /// binary, where their imports lead the root's test block. script leads
829 /// escape pins. The mains are roots of nothing but the one binary, and 817 /// for the same reason: instant, allocation-only, and both fixtures
830 /// they carry the argument parsers — a test that is never built is not a 818 /// inherit its escape pins. The mains are roots of nothing but the one
831 /// test (decisions.md). 819 /// binary, and they carry the argument parsers — a test that is never
820 /// built is not a test (decisions.md).
832 const test_order = [_][]const u8{ 821 const test_order = [_][]const u8{
833 "script", "protocol", "client_core", "engine", "pty", 822 "script", "protocol", "client_core", "engine", "pty",
834 "delta", "cmd", "hosts", "askpass", "upgrade", 823 "delta", "hosts", "askpass", "replica", "keymap",
835 "shellint", "replica", "keymap", "webhub", "wall", 824 "webhub", "wall", "sockpath", "agent_main", "daemon",
836 "sockpath", "agent_main", "server", "client", "proxy", 825 "client", "proxy", "client_main", "quic", "daemon_main",
837 "client_main", "quic", "quic_server", "daemon_main", "testtmp", 826 "testtmp", "rawmode", "delaypipe", "xdg", "spawn",
838 "rawmode", "delaypipe", "xdg", "spawn", "handoff", 827 "handoff", "layout", "render", "ptyclient", "hub_main",
839 "layout", "render", "ptyclient", "hub_main", "wsclient", 828 "wsclient", "cliflags", "mux",
840 "cliflags", "mux",
841 }; 829 };
842 830
843 comptime { 831 comptime {
src/cli/main.zig
Old New
@@ -5,17 +5,17 @@
5 // shell, and `/bin/sh` is the name it falls back to when $SHELL says nothing. 5 // shell, and `/bin/sh` is the name it falls back to when $SHELL says nothing.
6 6
7 const std = @import("std"); 7 const std = @import("std");
8 const Server = @import("server").Server; 8 const Server = @import("daemon").Server;
9 const proto = @import("protocol"); 9 const proto = @import("protocol");
10 const proxy = @import("proxy"); 10 const proxy = @import("proxy");
11 const quic = @import("quic"); 11 const quic = @import("quic");
12 const quic_server = @import("quic_server"); 12 const quic_server = @import("daemon").quic_server;
13 const build_options = @import("build_options"); 13 const build_options = @import("build_options");
14 const xdg = @import("xdg"); 14 const xdg = @import("xdg");
15 const spawn = @import("spawn"); 15 const spawn = @import("spawn");
16 const handoff = @import("handoff"); 16 const handoff = @import("handoff");
17 const sockpath = @import("sockpath"); 17 const sockpath = @import("sockpath");
18 const upgrade = @import("upgrade"); 18 const upgrade = @import("daemon").upgrade;
19 const cliflags = @import("cliflags"); 19 const cliflags = @import("cliflags");
20 20
21 const usage = 21 const usage =
@@ -527,7 +527,7 @@ fn resumeRun(alloc: std.mem.Allocator, o: Opts, resume_fd: std.posix.fd_t) !u8 {
527 // The marker's job ended the moment this image started serving. 527 // The marker's job ended the moment this image started serving.
528 _ = unsetenv(rollback_marker); 528 _ = unsetenv(rollback_marker);
529 529
530 @import("server").installSignalHandlers(); 530 @import("daemon").installSignalHandlers();
531 return try srv.run(); 531 return try srv.run();
532 } 532 }
533 533
@@ -678,7 +678,7 @@ fn run(alloc: std.mem.Allocator, o: Opts, sock_path: []const u8) !u8 {
678 srv.attachQuic(l); 678 srv.attachQuic(l);
679 } 679 }
680 680
681 @import("server").installSignalHandlers(); 681 @import("daemon").installSignalHandlers();
682 return try srv.run(); 682 return try srv.run();
683 } 683 }
684 684
src/server/server.zig
Old New
@@ -13,16 +13,18 @@ const Engine = @import("engine").Engine;
13 const Pty = @import("pty").Pty; 13 const Pty = @import("pty").Pty;
14 const proto = @import("protocol"); 14 const proto = @import("protocol");
15 const DeltaTracker = @import("delta").DeltaTracker; 15 const DeltaTracker = @import("delta").DeltaTracker;
16 const cmdmod = @import("cmd"); 16 const cmdmod = @import("cmd.zig");
17 const shellint = @import("shellint"); 17 const shellint = @import("shellint.zig");
18 // Test-only consumer (applyFrame): the tests replay daemon frames through 18 // Test-only consumer (applyFrame): the tests replay daemon frames through
19 // the production client's replay core rather than a hand-rolled twin. 19 // the production client's replay core rather than a hand-rolled twin.
20 const replica_mod = @import("replica"); 20 const replica_mod = @import("replica");
21 const sockpath = @import("sockpath"); 21 const sockpath = @import("sockpath");
22 const quic = @import("quic"); 22 const quic = @import("quic");
23 const quic_server = @import("quic_server");
24 const xdg = @import("xdg"); 23 const xdg = @import("xdg");
25 const upgrade = @import("upgrade"); 24 // Re-exported for the daemon's own main (src/cli/main.zig) — the only
25 // consumer outside this folder; nobody else may know these exist.
26 pub const quic_server = @import("quic_server.zig");
27 pub const upgrade = @import("upgrade.zig");
26 const proxy = @import("proxy"); 28 const proxy = @import("proxy");
27 const TmpDir = @import("testtmp").TmpDir; 29 const TmpDir = @import("testtmp").TmpDir;
28 // The agent relay is a sub-file of this module, not a row of its own: it is 30 // The agent relay is a sub-file of this module, not a row of its own: it is
@@ -3600,6 +3602,15 @@ pub const Server = struct {
3600 test { 3602 test {
3601 std.testing.refAllDeclsRecursive(@This()); 3603 std.testing.refAllDeclsRecursive(@This());
3602 3604
3605 // The child files' own suites first: seconds-long and socket-free, while
3606 // a regression in one can wedge a daemon test that waits on a client
3607 // forever — and a wedged step prints nothing at all, so failing before
3608 // the sockets open is what makes the catch legible.
3609 _ = @import("cmd.zig");
3610 _ = @import("shellint.zig");
3611 _ = @import("upgrade.zig");
3612 _ = @import("quic_server.zig");
3613
3603 // Reaching a file is what registers its tests; build.zig gates the list. 3614 // Reaching a file is what registers its tests; build.zig gates the list.
3604 _ = @import("server_test_agent.zig"); 3615 _ = @import("server_test_agent.zig");
3605 _ = @import("server_test_attach.zig"); 3616 _ = @import("server_test_attach.zig");
src/server/server_test_agent.zig
Old New
@@ -1,7 +1,7 @@
1 const std = @import("std"); 1 const std = @import("std");
2 const proto = @import("protocol"); 2 const proto = @import("protocol");
3 const quic = @import("quic"); 3 const quic = @import("quic");
4 const quic_server = @import("quic_server"); 4 const quic_server = @import("quic_server.zig");
5 const TmpDir = @import("testtmp").TmpDir; 5 const TmpDir = @import("testtmp").TmpDir;
6 const h = @import("server_test_harness.zig"); 6 const h = @import("server_test_harness.zig");
7 const srv_mod = @import("server.zig"); 7 const srv_mod = @import("server.zig");
src/server/server_test_await.zig
Old New
@@ -1,6 +1,6 @@
1 const std = @import("std"); 1 const std = @import("std");
2 const proto = @import("protocol"); 2 const proto = @import("protocol");
3 const shellint = @import("shellint"); 3 const shellint = @import("shellint.zig");
4 const TmpDir = @import("testtmp").TmpDir; 4 const TmpDir = @import("testtmp").TmpDir;
5 const h = @import("server_test_harness.zig"); 5 const h = @import("server_test_harness.zig");
6 const srv_mod = @import("server.zig"); 6 const srv_mod = @import("server.zig");
src/server/server_test_harness.zig
Old New
@@ -3,7 +3,7 @@ const Engine = @import("engine").Engine;
3 const proto = @import("protocol"); 3 const proto = @import("protocol");
4 const replica_mod = @import("replica"); 4 const replica_mod = @import("replica");
5 const quic = @import("quic"); 5 const quic = @import("quic");
6 const quic_server = @import("quic_server"); 6 const quic_server = @import("quic_server.zig");
7 const TmpDir = @import("testtmp").TmpDir; 7 const TmpDir = @import("testtmp").TmpDir;
8 const srv_mod = @import("server.zig"); 8 const srv_mod = @import("server.zig");
9 const Server = srv_mod.Server; 9 const Server = srv_mod.Server;
src/server/server_test_quic.zig
Old New
@@ -2,7 +2,7 @@ const std = @import("std");
2 const Engine = @import("engine").Engine; 2 const Engine = @import("engine").Engine;
3 const proto = @import("protocol"); 3 const proto = @import("protocol");
4 const quic = @import("quic"); 4 const quic = @import("quic");
5 const quic_server = @import("quic_server"); 5 const quic_server = @import("quic_server.zig");
6 const TmpDir = @import("testtmp").TmpDir; 6 const TmpDir = @import("testtmp").TmpDir;
7 const h = @import("server_test_harness.zig"); 7 const h = @import("server_test_harness.zig");
8 const srv_mod = @import("server.zig"); 8 const srv_mod = @import("server.zig");
src/server/server_test_session.zig
Old New
@@ -2,7 +2,7 @@ const std = @import("std");
2 const Engine = @import("engine").Engine; 2 const Engine = @import("engine").Engine;
3 const proto = @import("protocol"); 3 const proto = @import("protocol");
4 const quic = @import("quic"); 4 const quic = @import("quic");
5 const quic_server = @import("quic_server"); 5 const quic_server = @import("quic_server.zig");
6 const xdg = @import("xdg"); 6 const xdg = @import("xdg");
7 const TmpDir = @import("testtmp").TmpDir; 7 const TmpDir = @import("testtmp").TmpDir;
8 const h = @import("server_test_harness.zig"); 8 const h = @import("server_test_harness.zig");
src/server/server_test_upgrade.zig
Old New
@@ -1,6 +1,6 @@
1 const std = @import("std"); 1 const std = @import("std");
2 const proto = @import("protocol"); 2 const proto = @import("protocol");
3 const upgrade = @import("upgrade"); 3 const upgrade = @import("upgrade.zig");
4 const TmpDir = @import("testtmp").TmpDir; 4 const TmpDir = @import("testtmp").TmpDir;
5 const h = @import("server_test_harness.zig"); 5 const h = @import("server_test_harness.zig");
6 const srv_mod = @import("server.zig"); 6 const srv_mod = @import("server.zig");