Skip to content

Commit 370b129

Browse files
committed
test(core): filesystem native-parity guard (fs_probe vs native Linux)
Build fs_probe for wasm and native, run the wasm one inside an AgentOs VM via openShell, and assert its stdout matches native-Linux glibc line-for-line (stat mode bits, chmod round-trip, access rwx, truncate size, append/read integrity, and a heap-growth write loop). Guards the filesystem native-parity fixes in secure-exec.
1 parent 36e6630 commit 370b129

3 files changed

Lines changed: 226 additions & 22 deletions

File tree

Cargo.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ repository = "https://github.com/rivet-dev/agent-os"
2323
# normal crates.io dependencies so CI/publish builds do not need a sibling
2424
# checkout.
2525
[workspace.dependencies]
26-
agentos-bridge = { package = "secure-exec-bridge", path = "../secure-exec/crates/bridge", version = "0.3.0-rc.1" }
26+
agentos-bridge = { package = "secure-exec-bridge", path = "../secure-exec/crates/bridge", version = "0.3.4-rc.1" }
2727
agentos-protocol = { path = "crates/agentos-protocol", version = "0.2.0-rc.3" }
2828
agentos-sidecar-core = { path = "crates/agentos-sidecar-core", version = "0.2.0-rc.3" }
2929
agentos-sidecar = { path = "crates/agentos-sidecar", version = "0.2.0-rc.3" }
3030
agentos-sidecar-browser = { path = "crates/agentos-sidecar-browser", version = "0.2.0-rc.3" }
31-
agentos-kernel = { package = "secure-exec-kernel", path = "../secure-exec/crates/kernel", version = "0.3.0-rc.1" }
32-
agentos-execution = { package = "secure-exec-execution", path = "../secure-exec/crates/execution", version = "0.3.0-rc.1" }
33-
agentos-v8-runtime = { package = "secure-exec-v8-runtime", path = "../secure-exec/crates/v8-runtime", version = "0.3.0-rc.1" }
34-
secure-exec-client = { path = "../secure-exec/crates/secure-exec-client", version = "0.3.0-rc.1" }
35-
secure-exec-bridge = { path = "../secure-exec/crates/bridge", version = "0.3.0-rc.1" }
36-
secure-exec-sidecar = { path = "../secure-exec/crates/sidecar", version = "0.3.0-rc.1" }
31+
agentos-kernel = { package = "secure-exec-kernel", path = "../secure-exec/crates/kernel", version = "0.3.4-rc.1" }
32+
agentos-execution = { package = "secure-exec-execution", path = "../secure-exec/crates/execution", version = "0.3.4-rc.1" }
33+
agentos-v8-runtime = { package = "secure-exec-v8-runtime", path = "../secure-exec/crates/v8-runtime", version = "0.3.4-rc.1" }
34+
secure-exec-client = { path = "../secure-exec/crates/secure-exec-client", version = "0.3.4-rc.1" }
35+
secure-exec-bridge = { path = "../secure-exec/crates/bridge", version = "0.3.4-rc.1" }
36+
secure-exec-sidecar = { path = "../secure-exec/crates/sidecar", version = "0.3.4-rc.1" }
3737
# LOCAL-DEV: unpublished converged browser crate (web/wasm work).
38-
secure-exec-sidecar-browser = { path = "../secure-exec/crates/sidecar-browser", version = "0.3.0-rc.1" }
39-
secure-exec-vm-config = { path = "../secure-exec/crates/vm-config", version = "0.3.0-rc.1" }
38+
secure-exec-sidecar-browser = { path = "../secure-exec/crates/sidecar-browser", version = "0.3.4-rc.1" }
39+
secure-exec-vm-config = { path = "../secure-exec/crates/vm-config", version = "0.3.4-rc.1" }
4040
vbare = "0.0.4"
4141
vbare-compiler = { package = "rivet-vbare-compiler", version = "0.0.5" }
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { spawnSync } from "node:child_process";
2+
import {
3+
copyFileSync,
4+
existsSync,
5+
mkdirSync,
6+
mkdtempSync,
7+
rmSync,
8+
writeFileSync,
9+
} from "node:fs";
10+
import { tmpdir } from "node:os";
11+
import { dirname, join, resolve } from "node:path";
12+
import { fileURLToPath } from "node:url";
13+
import { afterEach, describe, expect, test } from "vitest";
14+
import type { AgentOs } from "../src/index.js";
15+
16+
const __dirname = dirname(fileURLToPath(import.meta.url));
17+
const REPO_ROOT = resolve(__dirname, "../../..");
18+
const SECURE_EXEC_C_ROOT = resolve(
19+
REPO_ROOT,
20+
"../secure-exec/registry/native/c",
21+
);
22+
const WASM_PROBE_BINARY = resolve(SECURE_EXEC_C_ROOT, "build/fs_probe");
23+
const NATIVE_PROBE_BINARY = resolve(
24+
SECURE_EXEC_C_ROOT,
25+
"build/native/fs_probe",
26+
);
27+
const PATCHED_LIBC = resolve(
28+
SECURE_EXEC_C_ROOT,
29+
"sysroot/lib/wasm32-wasi/libc.a",
30+
);
31+
const SIDECAR_BINARY = resolve(REPO_ROOT, "target/debug/agentos-sidecar");
32+
33+
function hasCommand(command: string): boolean {
34+
try {
35+
return (
36+
spawnSync(command, ["--version"], { encoding: "utf8" }).status === 0
37+
);
38+
} catch {
39+
return false;
40+
}
41+
}
42+
43+
// This test builds the fs_probe fixtures (native + wasm) and the workspace
44+
// sidecar on the fly, so it can only run with the secure-exec sibling checkout
45+
// present (absent on release pins, where prepare-build performs no clone) plus
46+
// make + cargo. The wasm C toolchain is the WASI SDK clang invoked by the
47+
// sibling Makefile (not a system `clang` on PATH), so it is not probed here.
48+
// Skip cleanly otherwise instead of hard-failing the run, matching
49+
// vim-native-parity.test.ts.
50+
const CAN_RUN =
51+
existsSync(join(SECURE_EXEC_C_ROOT, "Makefile")) &&
52+
hasCommand("make") &&
53+
hasCommand("cargo");
54+
55+
function runChecked(
56+
command: string,
57+
args: string[],
58+
options: { cwd: string; label: string },
59+
): void {
60+
const result = spawnSync(command, args, {
61+
cwd: options.cwd,
62+
encoding: "utf8",
63+
env: { ...process.env, AGENTOS_WASM_SNAPSHOT_RUNNER: "off" },
64+
});
65+
if (result.status !== 0) {
66+
throw new Error(
67+
[
68+
`${options.label} failed`,
69+
`cwd=${options.cwd}`,
70+
`command=${command} ${args.join(" ")}`,
71+
`status=${result.status}`,
72+
result.stdout,
73+
result.stderr,
74+
]
75+
.filter(Boolean)
76+
.join("\n"),
77+
);
78+
}
79+
}
80+
81+
function ensureFsProbeBuilt(): void {
82+
if (!existsSync(PATCHED_LIBC)) {
83+
runChecked("make", ["sysroot"], {
84+
cwd: SECURE_EXEC_C_ROOT,
85+
label: "failed to build patched wasi-libc sysroot",
86+
});
87+
}
88+
runChecked("make", ["build/native/fs_probe", "build/fs_probe"], {
89+
cwd: SECURE_EXEC_C_ROOT,
90+
label: "failed to build fs_probe parity fixtures",
91+
});
92+
}
93+
94+
function ensureWorkspaceSidecarBuilt(): void {
95+
runChecked("node", ["scripts/secure-exec-dep.mjs", "prepare-build"], {
96+
cwd: REPO_ROOT,
97+
label: "failed to prepare secure-exec workspace dependency",
98+
});
99+
runChecked("cargo", ["build", "-q", "-p", "agentos-sidecar"], {
100+
cwd: REPO_ROOT,
101+
label: "failed to build workspace agentos-sidecar",
102+
});
103+
process.env.AGENTOS_SIDECAR_BIN = SIDECAR_BINARY;
104+
process.env.AGENTOS_WASM_SNAPSHOT_RUNNER = "off";
105+
}
106+
107+
function materializeFsProbePackage(): string {
108+
const pkgDir = mkdtempSync(join(tmpdir(), "agentos-fs-probe-pkg-"));
109+
mkdirSync(join(pkgDir, "bin"));
110+
copyFileSync(WASM_PROBE_BINARY, join(pkgDir, "bin", "fs_probe"));
111+
writeFileSync(
112+
join(pkgDir, "package.json"),
113+
JSON.stringify({ name: "fs-probe-fixture", version: "0.0.0" }),
114+
);
115+
writeFileSync(
116+
join(pkgDir, "agentos-package.json"),
117+
JSON.stringify({ name: "fs-probe-fixture" }),
118+
);
119+
return pkgDir;
120+
}
121+
122+
function normalizeProbeOutput(output: string): string {
123+
return output
124+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
125+
.replace(/\r\n/g, "\n")
126+
.replace(/\r/g, "\n")
127+
.trimEnd();
128+
}
129+
130+
function runNativeProbe(): string {
131+
const scratchDir = mkdtempSync(join(tmpdir(), "fs-probe-native-"));
132+
try {
133+
const result = spawnSync(NATIVE_PROBE_BINARY, [scratchDir], {
134+
encoding: "utf8",
135+
});
136+
expect(result.status, result.stderr || result.stdout).toBe(0);
137+
return normalizeProbeOutput(result.stdout);
138+
} finally {
139+
rmSync(scratchDir, { force: true, recursive: true });
140+
}
141+
}
142+
143+
describe.skipIf(!CAN_RUN)("filesystem native parity", () => {
144+
let vm: AgentOs | undefined;
145+
let shellId: string | undefined;
146+
let unsubscribeShellData: (() => void) | undefined;
147+
let probePkgDir: string | undefined;
148+
149+
afterEach(async () => {
150+
if (unsubscribeShellData) {
151+
unsubscribeShellData();
152+
unsubscribeShellData = undefined;
153+
}
154+
if (probePkgDir) {
155+
rmSync(probePkgDir, { force: true, recursive: true });
156+
probePkgDir = undefined;
157+
}
158+
if (vm && shellId) {
159+
try {
160+
vm.closeShell(shellId);
161+
} catch {
162+
// The probe may already have exited.
163+
}
164+
}
165+
shellId = undefined;
166+
if (vm) {
167+
await vm.dispose();
168+
vm = undefined;
169+
}
170+
});
171+
172+
test("fs_probe output matches native Linux through openShell", async () => {
173+
ensureWorkspaceSidecarBuilt();
174+
ensureFsProbeBuilt();
175+
const expected = runNativeProbe();
176+
const { AgentOs } = await import("../src/index.js");
177+
178+
probePkgDir = materializeFsProbePackage();
179+
vm = await AgentOs.create({
180+
defaultSoftware: false,
181+
software: [probePkgDir],
182+
});
183+
184+
let rawOutput = "";
185+
({ shellId } = vm.openShell({
186+
command: "fs_probe",
187+
args: ["/tmp/fs-probe-vm"],
188+
cols: 120,
189+
rows: 40,
190+
}));
191+
unsubscribeShellData = vm.onShellData(shellId, (data) => {
192+
rawOutput +=
193+
typeof data === "string" ? data : Buffer.from(data).toString("utf8");
194+
});
195+
196+
const status = await vm.waitShell(shellId);
197+
const actual = normalizeProbeOutput(rawOutput);
198+
expect(status, actual).toBe(0);
199+
expect(actual).toBe(expected);
200+
// Generous timeout: the first run may build the wasi-libc sysroot and the
201+
// debug sidecar from cold, which can exceed a 2-minute budget. Warm runs
202+
// are near-instant (make/cargo no-ops).
203+
}, 600_000);
204+
});

0 commit comments

Comments
 (0)