Skip to content

Commit ece7ce6

Browse files
committed
chore: kernel hardening, Node.js compat, and test suite coverage (#1451)
1 parent b1f023a commit ece7ce6

429 files changed

Lines changed: 62484 additions & 17990 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agent/notes/http_direct_probe.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
const script = "const https=require('https'); https.get('https://registry.npmjs.org/', (res)=>{ console.log('STATUS', res.statusCode); res.resume(); res.on('end', ()=>process.exit(0)); }).on('error', (e)=>{ console.error('ERR', e && e.code, e && e.message); process.exit(1); });";
7+
const result = await kernel.exec(`node -e ${JSON.stringify(script)}`, { cwd: '/', timeout: 10000 });
8+
console.log(JSON.stringify(result));
9+
} finally {
10+
await dispose();
11+
}
12+
})().catch((error) => {
13+
console.error(error);
14+
process.exit(1);
15+
});

.agent/notes/loop-prd-baseline.md

Lines changed: 258 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
await kernel.writeFile('/package.json', JSON.stringify({
7+
name: 'test-npm-no-network',
8+
private: true,
9+
dependencies: { 'left-pad': '1.3.0' },
10+
}));
11+
const result = await kernel.exec('npm install --registry=http://localhost:1', {
12+
cwd: '/',
13+
timeout: 15000,
14+
});
15+
console.log(JSON.stringify(result));
16+
} finally {
17+
await dispose();
18+
}
19+
})().catch((error) => {
20+
console.error(error);
21+
process.exit(1);
22+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
const result = await kernel.exec('npm install semver --registry=http://localhost:1', {
7+
cwd: '/',
8+
timeout: 15000,
9+
});
10+
console.log(JSON.stringify(result));
11+
} finally {
12+
await dispose();
13+
}
14+
})().catch((error) => {
15+
console.error(error);
16+
process.exit(1);
17+
});

.agent/notes/npmcli_agent_probe.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
const script = [
7+
"const https=require('https');",
8+
"const { getAgent } = require('/__agentos/node-runtime/npx/node_modules/@npmcli/agent/lib/index.js');",
9+
"const agent = getAgent('https://registry.npmjs.org/', { timeout: 3000 });",
10+
"const req = https.get('https://registry.npmjs.org/', { agent }, (res) => {",
11+
" console.log('STATUS', res.statusCode);",
12+
" res.resume();",
13+
" res.on('end', () => process.exit(0));",
14+
"});",
15+
"req.on('socket', (socket) => {",
16+
" console.error('SOCKET', JSON.stringify({ connecting: socket.connecting, secureConnecting: socket.secureConnecting, authorized: socket.authorized, encrypted: socket.encrypted }));",
17+
" socket.on('connect', () => console.error('SOCKET_CONNECT'));",
18+
" socket.on('secureConnect', () => console.error('SOCKET_SECURE_CONNECT'));",
19+
" socket.on('timeout', () => console.error('SOCKET_TIMEOUT'));",
20+
" socket.on('close', () => console.error('SOCKET_CLOSE'));",
21+
" socket.on('error', (e) => console.error('SOCKET_ERROR', e && e.code, e && e.message));",
22+
"});",
23+
"req.on('response', () => console.error('REQ_RESPONSE'));",
24+
"req.on('timeout', () => console.error('REQ_TIMEOUT'));",
25+
"req.on('close', () => console.error('REQ_CLOSE'));",
26+
"req.on('error', (e) => { console.error('REQ_ERROR', e && e.code, e && e.message); process.exit(1); });",
27+
].join('\n');
28+
await kernel.writeFile('/probe.js', script);
29+
const result = await kernel.exec('node /probe.js', { cwd: '/', timeout: 10000 });
30+
console.log(JSON.stringify(result));
31+
} finally {
32+
await dispose();
33+
}
34+
})().catch((error) => {
35+
console.error(error);
36+
process.exit(1);
37+
});

.agent/notes/npx_debug_probe.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
const result = await kernel.exec('npx -y semver 1.2.3', {
7+
cwd: '/',
8+
timeout: 20000,
9+
env: {
10+
CODEX_DEBUG_NPM_CLI: '1',
11+
CODEX_DEBUG_HTTP_POLYFILL: '1',
12+
},
13+
});
14+
console.log(JSON.stringify(result));
15+
} finally {
16+
await dispose();
17+
}
18+
})().catch((error) => {
19+
console.error(error);
20+
process.exit(1);
21+
});

.agent/notes/npx_fs_probe.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { mkdtemp, rm } from 'node:fs/promises';
2+
import path from 'node:path';
3+
import { tmpdir } from 'node:os';
4+
import { existsSync } from 'node:fs';
5+
import {
6+
COMMANDS_DIR,
7+
createKernel,
8+
createNodeRuntime,
9+
createWasmVmRuntime,
10+
NodeFileSystem,
11+
} from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
12+
13+
(async () => {
14+
const tempDir = await mkdtemp(path.join(tmpdir(), 'kernel-npx-fs-probe-'));
15+
console.log('TEMP', tempDir);
16+
const vfs = new NodeFileSystem({ root: tempDir });
17+
const kernel = createKernel({ filesystem: vfs, cwd: '/' });
18+
await kernel.mount(createWasmVmRuntime({ commandDirs: [COMMANDS_DIR] }));
19+
await kernel.mount(createNodeRuntime());
20+
21+
try {
22+
const result = await kernel.exec('npx -y semver 1.2.3', { cwd: '/', timeout: 20000 });
23+
console.log('RESULT', JSON.stringify(result));
24+
console.log('HAS_NODE_MODULES', existsSync(path.join(tempDir, 'node_modules')));
25+
console.log('HAS_HOME_NPM', existsSync(path.join(tempDir, 'home', 'user', '.npm')));
26+
} finally {
27+
await kernel.dispose();
28+
// leave temp dir for inspection on failure
29+
}
30+
})().catch((error) => {
31+
console.error(error);
32+
process.exit(1);
33+
});

.agent/notes/npx_semver_probe.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
const result = await kernel.exec('npx -y semver 1.2.3', {
7+
cwd: '/',
8+
timeout: 20000,
9+
});
10+
console.log(JSON.stringify(result));
11+
} finally {
12+
await dispose();
13+
}
14+
})().catch((error) => {
15+
console.error(error);
16+
process.exit(1);
17+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Release Readiness Report — 2026-04-12
2+
3+
Branch: `finish-ts-rust-migration`. Goal: ship a release candidate where every quickstart + documented-feature path is exercised by a working test.
4+
5+
## TL;DR
6+
7+
**Not shippable yet.** Four of fourteen quickstart examples have zero end-to-end test coverage, one is explicitly broken per `packages/core/CLAUDE.md` (`vm.fetch` does not route guest listener ports back to the host), and the full-suite `pnpm test` run hangs on this branch (per CLAUDE.md, bare `pnpm test` must be avoided). The PRD has **130 pending stories**; only a small subset actually blocks a first release.
8+
9+
## 1. Quickstart Coverage (14 scripts)
10+
11+
| Quickstart | Test coverage | Status |
12+
|---|---|---|
13+
| `hello-world.ts` | `filesystem.test.ts`, `agent-os-base-filesystem.test.ts` | OK |
14+
| `filesystem.ts` | `filesystem.test.ts`, `batch-file-ops.test.ts`, `filesystem-move-delete.test.ts`, `readdir-recursive.test.ts` | OK |
15+
| `bash.ts` | `execute.test.ts`, `shell-flat-api.test.ts` | OK (needs WASM binaries built) |
16+
| `processes.ts` | `spawn-flat-api.test.ts`, `execute.test.ts`, `all-processes.test.ts`, `process-tree.test.ts` | OK |
17+
| `cron.ts` | `cron-integration.test.ts` (exec action at lines 68/85), `cron-manager.test.ts` | OK |
18+
| `tools.ts` | `host-tools.test.ts`, `host-tools-zod.test.ts`, `sidecar-tool-dispatch.test.ts` | OK |
19+
| `nodejs.ts` | `execute.test.ts`, `spawn-flat-api.test.ts` | OK |
20+
| `agent-session.ts` | `claude-session.test.ts`, `codex-session.test.ts`, `opencode-session.test.ts`, `pi-headless.test.ts` | OK (uses llmock) |
21+
| **`network.ts`** | none | **BROKEN**`vm.fetch()` does not translate guest listener ports to host (CLAUDE.md: "Network tests on the native sidecar path should stick to listener bind/state assertions... `vm.fetch()` does not currently translate arbitrary guest listener ports back to the host"). Quickstart relies on this path. |
22+
| **`git.ts`** | none | Gap — no git-specific test; depends on `@rivet-dev/agent-os-git` + `exec()`. Needs WASM git build. |
23+
| **`pi-extensions.ts`** | none | Gap — `before_agent_start` hook and extension discovery path have no test. |
24+
| **`s3-filesystem.ts`** | none | Gap — `createS3Backend` / S3 plugin descriptor path not exercised by any test. |
25+
| **`sandbox.ts`** | none | Gap — `createSandboxFs` / `createSandboxToolkit` / Docker sandbox-agent integration untested. |
26+
27+
**Required new tests to call the release candidate honest:**
28+
1. `network-vm-fetch.test.ts` — spawn a Node HTTP server inside a VM and assert `vm.fetch(port, req)` round-trips (and fix the underlying listener→host translation or document the exact limitation and land a smaller, working API).
29+
2. `git-quickstart.test.ts` — exercise `init`/`add`/`commit`/`clone`/`checkout` with `@rivet-dev/agent-os-git` package, gated on WASM git artifact.
30+
3. `pi-extensions.test.ts` — write extension to `~/.pi/agent/extensions/` inside VM, assert `before_agent_start` hook fires and modifies system prompt (llmock).
31+
4. `s3-backend.test.ts` — stand up MinIO in-process (or mock S3 HTTP), mount through `createS3Backend`, assert read/write/readdir round-trip.
32+
5. `sandbox-integration.test.ts` — guarded on Docker availability; mount `createSandboxFs` and call `sandbox` toolkit via the RPC port pattern the quickstart uses.
33+
34+
## 2. Documentation Coverage (`~/r10/docs/`)
35+
36+
### Covered
37+
- Filesystem basic + batch (`readFiles` / `writeFiles``batch-file-ops.test.ts`)
38+
- Process mgmt, `writeProcessStdin` / `closeProcessStdin`, `getProcess`, `processTree`, `allProcesses`
39+
- Interactive PTY (`openShell`, `writeShell`, `resizeShell`, `closeShell`)
40+
- Permission hooks (`onPermissionRequest`)
41+
- `additionalInstructions` / `skipOsInstructions` (`os-instructions.test.ts`)
42+
- Session lifecycle (`resumeSession` / `destroySession`) via session tests
43+
- `setModel` / `setMode` (partial)
44+
45+
### Missing / Partial
46+
- **`vmFetch` / `vm.fetch`** — zero tests; partially broken (see above).
47+
- **`createSignedPreviewUrl` / `expireSignedPreviewUrl`** — RivetKit-layer only. Either delete from core docs or add a RivetKit driver-test-suite coverage item upstream.
48+
- **`listPersistedSessions`** — documented but untested.
49+
- **`mcpServers`** in session config — documented but no test.
50+
- **`workflow()` / `c.step()` / `c.queue.iter()`** — documented extensively; not found anywhere in core package. Either RivetKit-only (then scope out of core docs) or unimplemented → hard release blocker.
51+
- **`GoogleDriveBlockStore`** — untested.
52+
- **`createS3BackendForAgent`** — untested.
53+
- **`setThoughtLevel` / `getModes` / `getConfigOptions`** — not covered in tests.
54+
- **Pi `before_agent_start` extensions** — documented, untested.
55+
56+
## 3. PRD Triage — Actual Release Blockers
57+
58+
Of the 130 pending stories, only a small set actually gates "core functionality working + quickstart passes."
59+
60+
### True release blockers (must land before RC)
61+
| Priority | ID | Why it blocks |
62+
|---|---|---|
63+
| 4 | **US-088** | Gate story — "full first-party workspace green with no product-debt skips or ignored Rust tests" is the definition of ready. |
64+
| 5 | **US-089** | Final verification sweep, same gate. |
65+
|| (new) | Add and fix the five missing quickstart tests listed in §1. |
66+
| 13 | **US-217** | Default sidecar permissions must be **deny**, not allow-all. Shipping with allow-all default = security footgun. |
67+
| 14 | **US-218** | Reject empty-op / empty-path permission rules. Complements US-217. |
68+
| 15 | **US-219** | Permission glob `*` must not cross path separators — classic permission escape. |
69+
| 9 | **US-190** | Permission-gate `FindListener` / `FindBoundUdp` / `GetProcessSnapshot` — listed for the same reason. |
70+
| 16 | **US-243** | Route guest `http/https.request` through kernel socket table. Any real-world agent hits this immediately. Also unblocks fixing `network.ts`. |
71+
| 17 | **US-250** | Dev-shell hybrid-VFS host fallthrough = isolation violation. Hardcore invariant per repo CLAUDE.md. |
72+
| 18 | **US-251** | Browser sidecar kernel routing — same class of host-escape. |
73+
| 6 | **US-173** | Panic-on-serialize → fallible path. Any serialization edge case crashes the session. |
74+
| 7 | **US-184** | ACP inbound request must wait for host response. Incorrectly returning -32601 breaks permission round-trips. |
75+
| 8 | **US-188** | `register_toolkit` duplicate detection + permission gating. Tool dispatch safety. |
76+
| 11 | **US-202** | Panic in `pump_process_events` on reaped VM → sidecar crash. |
77+
78+
### Release-notes or follow-up (safe to ship without)
79+
Everything in priority ≥ 19 in the PRD is hardening, compatibility polish, or edge-case correctness. Ship them in point releases. Specifically:
80+
- Most v8-bridge polyfill gaps (US-156, US-157, US-227, US-237, US-261, US-266, US-280, US-292, US-299, US-303, US-305) — needed for broader npm compat but not for the quickstart set.
81+
- All BARE codec expansion (US-313/314/315) — codec is green for the surface you use today.
82+
- Cosmetic cron / EventEmitter / perf_hooks items (US-199, US-276, US-286, US-287, US-288, US-289).
83+
- Private filesystem semantics that current tests don't care about (US-234, US-235, US-236, US-247, US-267, US-274, US-283, US-284).
84+
85+
### Scope-decision items (ask the user)
86+
- **US-147** — "Resolve uncommitted deletion of public docs/ tree." This directly touches what we're measuring coverage against. Decide whether the `~/r10/docs/` surface you're targeting includes the deleted paths.
87+
- **US-148**`@rivet-dev/agent-os-shell` package cleanup. If it's not part of the release, delete it.
88+
- **US-146** — Remove `minimal_root_snapshot` fallback. Decides whether the base-filesystem story is locked in.
89+
90+
## 4. Test-suite Execution Facts
91+
- Bare `pnpm --filter @rivet-dev/agent-os-core test` hangs (the integration tests do not terminate on this branch). Per `packages/core/CLAUDE.md` this is expected — tests must always be run scoped.
92+
- `native-sidecar-process.test.ts` (11 tests) passes cleanly in isolation (~4.5 s total).
93+
- WASM binaries are present under `registry/native/target/wasm32-wasip1/release/commands/` and C artifacts under `registry/native/c/build/`, so the 70+ "skipIf WASM missing" blocks should actually run — but that needs to be confirmed on a clean CI pass (US-088 / US-089).
94+
- Only three credentialed skipIfs remain by design: `duckdb-package`, `browserbase-e2e`, `browserbase-ws`.
95+
96+
## 5. Recommended Release Order of Attack
97+
98+
1. **Land the 5 missing quickstart tests** (§1). These define "quickstart is honest."
99+
2. **Fix `vm.fetch` guest-port translation** or shrink the API to what actually works and update the quickstart + docs to match.
100+
3. **US-217 / US-218 / US-219 / US-190** — lock down the permission model before anyone publishes a package under the current allow-all default.
101+
4. **US-173 / US-184 / US-188 / US-202** — ACP + toolkit crash/safety items.
102+
5. **US-243 / US-250 / US-251** — the remaining host-escape / host-fallthrough items that violate the core virtualization invariant.
103+
6. Get `pnpm test` and the five `cargo test -p agent-os-{kernel,bridge,execution,v8-runtime,sidecar}` suites green end-to-end on a clean clone (US-088 then US-089). That's the RC gate.
104+
7. **Decide `workflow()` / MCP / preview-URL scope**: either mark core-docs-only for RivetKit and move them, or implement.
105+
8. Everything else in the PRD moves to point releases.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createIntegrationKernel } from '/home/nathan/a5/registry/tests/kernel/helpers.ts';
2+
3+
(async () => {
4+
const { kernel, dispose } = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
5+
try {
6+
await kernel.writeFile('/probe.js', [
7+
"const { createRequire } = require('module');",
8+
"const npmRequire = createRequire('/__agentos/node-runtime/npx/lib/npm.js');",
9+
"try {",
10+
" console.log('RES1', require.resolve('@npmcli/agent'));",
11+
"} catch (e) { console.error('ERR1', e && e.message); }",
12+
"try {",
13+
" console.log('RES2', require.resolve('@npmcli/agent', { paths: ['/__agentos/node-runtime/npx'] }));",
14+
"} catch (e) { console.error('ERR2', e && e.message); }",
15+
"try {",
16+
" console.log('RES3', require.resolve('@npmcli/agent', { paths: ['/__agentos/node-runtime/npx/lib'] }));",
17+
"} catch (e) { console.error('ERR3', e && e.message); }",
18+
"try {",
19+
" console.log('RES4', require.resolve('make-fetch-happen', { paths: ['/__agentos/node-runtime/npx/lib'] }));",
20+
"} catch (e) { console.error('ERR4', e && e.message); }",
21+
"try {",
22+
" console.log('RES5', npmRequire.resolve('@npmcli/agent'));",
23+
"} catch (e) { console.error('ERR5', e && e.message); }",
24+
"try {",
25+
" console.log('RES6', npmRequire.resolve('make-fetch-happen'));",
26+
"} catch (e) { console.error('ERR6', e && e.message); }",
27+
].join('\n'));
28+
const result = await kernel.exec('node /probe.js', { cwd: '/', timeout: 5000 });
29+
console.log(JSON.stringify(result));
30+
} finally {
31+
await dispose();
32+
}
33+
})().catch((error) => {
34+
console.error(error);
35+
process.exit(1);
36+
});

0 commit comments

Comments
 (0)