diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-21 14:23:27 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-21 15:20:40 -0300 |
| commit | b4db588dd5b92d647b661c2dc17b40925af92348 (patch) | |
| tree | 7a036e500251d7f3f958854dcf3a8ead375bbc63 /build.zig | |
| parent | 0d7e295efee1fca0935cf4a8bee9629c007dd2b6 (diff) | |
| download | cloud9-b4db588dd5b92d647b661c2dc17b40925af92348.tar.gz cloud9-b4db588dd5b92d647b661c2dc17b40925af92348.zip | |
9harness: the harness fs daemon
The last item of the 9P plan, replacing zmxify's introspection half: a
read-only, fresh-from-disk 9P view of every agent harness's state on
this machine, posted as `harness` like any other service, so a shell
inside a 9ns --mntgen mount reads it at /mnt/9p/harness with no setup.
/pid /uptime /claude/{projects,history,skills}
/codex/{sessions,session-index,history}
/omp /hermes /dsh the mirrors
/skills/{claude,codex,omp}
Nothing is cached: a lookup, getattr or readdir walks the real
filesystem, so a transcript grows as its harness writes it and a new
session appears as soon as its file lands. Writes answer EPERM, and no
name that looks like a credential, key, token or auth store is ever
answered at any depth.
Three findings from the adversarial pass, each with its regression:
- The read path composed <base>/<rel> and opened it in one call, which
follows symlinks. A name swapped for a link between the walk and the
read served bytes from outside every pinned root (proved against
/etc/passwd). Every stat, read and readdir now resolves through
openIn, which walks from the base one component at a time with
O_NOFOLLOW, and O_PATH for the intermediates, so no component can
redirect the walk. O_PATH also keeps a fifo in a root from parking the
daemon in open(); a read refuses anything but a regular file.
- Joining a child onto an empty relative path returned an uncopied
scratch slice, so every file at the top of a mirror root (/hermes/x,
/dsh/x) listed but read back uninitialized stack bytes.
- A directory past the comptime caps was served short, and a short
listing cannot be told from a small directory. The caps answer NFILE
now. Staging also stops at the first record that does not fit instead
of packing a shorter one behind it, which dropped that entry from the
listing across the read boundary.
Suites: 13/13 unit (fake HOME, never the live roots), 36/0 end-to-end
including the mntgen money shot and the live ~/.claude/.credentials.json
proved unreachable, 131/131 programs-test.
Diffstat (limited to 'build.zig')
| -rw-r--r-- | build.zig | 20 |
1 files changed, 20 insertions, 0 deletions
@@ -156,6 +156,7 @@ pub fn build(b: *std.Build) void { const is_linux = target.result.os.tag == .linux; const want_9proc = b.option(bool, "9proc", "Build the 9proc library module and demo (default: target is Linux)") orelse is_linux; const want_9ns = b.option(bool, "9ns", "Build the 9ns FUSE mount CLI (default: target is Linux; Linux only)") orelse is_linux; + const want_9harness = b.option(bool, "9harness", "Build the harness fs daemon (default: target is Linux; Linux only)") orelse is_linux; if (want_9ns and !is_linux) { std.debug.print("error: -D9ns=true needs a Linux target (got {s})\n", .{@tagName(target.result.os.tag)}); std.process.exit(1); @@ -206,6 +207,25 @@ pub fn build(b: *std.Build) void { programs_itest.dependOn(p.itest_step); } + // The harness fs daemon: a read-only 9P view of every harness's + // state, posted by default as `harness` (Linux only: it posts into + // $XDG_RUNTIME_DIR/9p through cloud9.post). + if (want_9harness and !is_linux) { + std.debug.print("error: -D9harness=true needs a Linux target (got {s})\n", .{@tagName(target.result.os.tag)}); + std.process.exit(1); + } + const harness_build = @import("9harness/build.zig"); + const harness: ?harness_build.Artifacts = if (want_9harness) harness_build.add(b, .{ + .target = target, + .optimize = optimize, + .cloud9 = module, + .ns = if (ns) |p| p.exe else null, + }) else null; + if (harness) |p| { + programs_test.dependOn(p.test_step); + programs_itest.dependOn(p.itest_step); + } + // 9proc's end-to-end suites drive the demo through a 9ns mount, // so they are wired once both fragments have run. if (proc) |i| { |
