From b4db588dd5b92d647b661c2dc17b40925af92348 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 21 Sep 2026 14:23:27 -0300 Subject: 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 / 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. --- 9harness/build.zig | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 9harness/build.zig (limited to '9harness/build.zig') diff --git a/9harness/build.zig b/9harness/build.zig new file mode 100644 index 0000000..4cf84d6 --- /dev/null +++ b/9harness/build.zig @@ -0,0 +1,54 @@ +//! Build fragment for 9harness: the harness fs daemon (binary `9harness`), +//! a read-only fresh-from-disk 9P2000 view of every harness's state, +//! posted by default under the name `harness`. It is `@import`ed by the +//! root build.zig and called with the root builder, so every `b.path(...)` +//! here is relative to the cloud9 root (hence the `9harness/` prefix), +//! every option is defined by the root and every step it registers lands +//! in the root's step list under the `9harness` prefix. +//! +//! Steps: 9harness, 9harness-test, 9harness-itest. +const std = @import("std"); + +pub const Context = struct { + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + cloud9: *std.Build.Module, + /// The 9ns binary, for the --mntgen money shot in the integration + /// suite; null when 9ns is disabled, in which case the mntgen + /// section of the suite is skipped. + ns: ?*std.Build.Step.Compile, +}; + +pub const Artifacts = struct { + exe: *std.Build.Step.Compile, + /// `9harness-test`: the tree's unit tests (exclusions, path safety, + /// node ids, vanishing files — all against a fake HOME). + test_step: *std.Build.Step, + /// `9harness-itest`: test/e2e.sh (fixture roots + scratch registry, + /// plus the real-registry end-to-end when the `harness` name is free). + itest_step: *std.Build.Step, +}; + +pub fn add(b: *std.Build, ctx: Context) Artifacts { + const mod = b.createModule(.{ + .root_source_file = b.path("9harness/src/main.zig"), + .target = ctx.target, + .optimize = ctx.optimize, + .imports = &.{.{ .name = "cloud9", .module = ctx.cloud9 }}, + }); + const exe = b.addExecutable(.{ .name = "9harness", .root_module = mod }); + const install = b.addInstallArtifact(exe, .{}); + b.getInstallStep().dependOn(&install.step); + b.step("9harness", "Build and install only the harness fs daemon").dependOn(&install.step); + + const test_step = b.step("9harness-test", "Run the 9harness tree's unit tests (fake HOME; never the live roots)"); + test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = mod })).step); + + const itest_step = b.step("9harness-itest", "Run 9harness/test/e2e.sh (posted name, reads, cmp of a transcript, mntgen mount, exclusions, live appends)"); + const run = b.addSystemCommand(&.{"bash"}); + run.addFileArg(b.path("9harness/test/e2e.sh")); + run.addArtifactArg(exe); + if (ctx.ns) |ns| run.addArtifactArg(ns); + itest_step.dependOn(&run.step); + return .{ .exe = exe, .test_step = test_step, .itest_step = itest_step }; +} -- cgit v1.3