@linzumi/cli 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,969 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // PR #2111 - END-TO-END GOLDEN-LIFECYCLE CAPSTONE
4
+ // =============================================================================
5
+ // ONE script that proves the whole forkable-golden lifecycle on REAL VMs (HVF on
6
+ // this Mac), exercising OUR CLI golden code (not just raw smolmachines), and
7
+ // asserts the 9 golden-lifecycle properties (P1-P9) with a per-property PASS/FAIL
8
+ // table. Non-zero exit on ANY property violation.
9
+ //
10
+ // The same 9 properties are encoded as teeth'd, model-tier invariants in the
11
+ // deterministic replay_harness (the G-series in
12
+ // kandan/server_v2/web/replay_harness/src/goldenLifecycleInvariants.mjs
13
+ // driven by tests/golden_lifecycle_invariants.test.mjs). The harness CANNOT boot
14
+ // HVF, so it runs the SAME oracles over a MODEL observation; this script runs
15
+ // them over a LIVE observation. The oracle code is shared verbatim - this script
16
+ // imports the harness G-series and feeds it the live observation, so a real
17
+ // lifecycle that violated a property is caught by the identical check that guards
18
+ // the model tier. (HVF-only legs: boot/provision/fork/install timings + live fs
19
+ // isolation/immutability. Model-tier: the oracle logic itself.)
20
+ //
21
+ // HOW IT EXERCISES OUR CODE (not raw smolmachines):
22
+ // - the deterministic golden machine name comes from goldenBuildMachineName()
23
+ // - toolchain provision uses guestProvisionCommand() (the SAME script a real
24
+ // cold-provisioned VM runs) - minus the pinned @openai/codex npm -g install,
25
+ // which is replaced by a no-op marker for the E2E (the codex registry pull is
26
+ // slow + irrelevant to the fs-lifecycle properties; node/npm/git/the project
27
+ // deps - the parts the properties assert - are installed for real)
28
+ // - the warm manifest checksum is goldenManifestChecksum() over real
29
+ // gatherSourceCommits()/buildLockfileHashes()/readGuestToolchainVersions()
30
+ // run against the live guest
31
+ // - concurrent job VMs are forked via createMachineFromGolden() (the real
32
+ // supervisor fork-from-golden path WITH its EAGAIN/os-error-35 backoff retry)
33
+ // - the property oracles are the harness G-series, imported verbatim
34
+ //
35
+ // Spec: docs/golden-image-registry-contract.md ; /tmp/smol-rt-132/fork-proof.mjs
36
+ // =============================================================================
37
+
38
+ import { createRequire } from 'node:module';
39
+ import { fileURLToPath } from 'node:url';
40
+ import { mkdtempSync, rmSync, writeFileSync, existsSync } from 'node:fs';
41
+ import { tmpdir } from 'node:os';
42
+ import { dirname, join } from 'node:path';
43
+ import { execFileSync } from 'node:child_process';
44
+
45
+ // Surface ANY escaped async failure loudly (never a silent exit-1 with no trace).
46
+ process.on('uncaughtException', (e) => {
47
+ console.error('[golden-e2e] UNCAUGHT:', e?.stack || String(e));
48
+ });
49
+ process.on('unhandledRejection', (e) => {
50
+ console.error('[golden-e2e] UNHANDLED-REJECTION:', e?.stack || String(e));
51
+ });
52
+
53
+ const __filename = fileURLToPath(import.meta.url);
54
+ const __dirname = dirname(__filename);
55
+ const cliRoot = join(__dirname, '..');
56
+ const repoRoot = join(cliRoot, '..', '..');
57
+ const harnessSrc = join(
58
+ repoRoot,
59
+ 'kandan',
60
+ 'server_v2',
61
+ 'web',
62
+ 'replay_harness',
63
+ 'src'
64
+ );
65
+
66
+ // The pinned, proven-forkable runtime (smolmachines 1.3.2). The contract's
67
+ // `~/.linzumi/vm-runtime` still pins 0.1.5 (no fork), so we point at the proven
68
+ // 1.3.2 dir, overridable via LINZUMI_VM_RUNTIME_DIR for CI/other machines.
69
+ const RUNTIME_DIR =
70
+ process.env.LINZUMI_VM_RUNTIME_DIR &&
71
+ process.env.LINZUMI_VM_RUNTIME_DIR.trim() !== ''
72
+ ? process.env.LINZUMI_VM_RUNTIME_DIR.trim()
73
+ : '/tmp/smol-rt-132';
74
+
75
+ const PROJECT = '/root/project'; // in-guest checkout root
76
+ const N_FORKS = 3;
77
+ const now = () => Date.now();
78
+ const log = (...a) => console.log('[golden-e2e]', ...a);
79
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // FAULT INJECTION (the teeth knobs). Setting a BREAK_* env var deliberately
83
+ // breaks one lifecycle step so the corresponding property goes RED, proving the
84
+ // assertion has teeth. Default (all unset) is the real, correct lifecycle.
85
+ // ---------------------------------------------------------------------------
86
+ const BREAK = {
87
+ isolation: process.env.BREAK_ISOLATION === '1', // P1: leak a file into a sibling fork
88
+ inheritance: process.env.BREAK_INHERITANCE === '1', // P2: wipe node_modules in a fork
89
+ immutability: process.env.BREAK_IMMUTABILITY === '1', // P3: mutate the golden fs
90
+ determinism: process.env.BREAK_DETERMINISM === '1', // P5: perturb the manifest inputs
91
+ durable: process.env.BREAK_DURABLE === '1', // P7: don't push committed work
92
+ cleanup: process.env.BREAK_CLEANUP === '1', // P6: leak a VM (skip teardown)
93
+ loudFail: process.env.BREAK_LOUDFAIL === '1', // P9: silent cold-boot under golden name
94
+ };
95
+ const anyBreak = Object.values(BREAK).some(Boolean);
96
+
97
+ // =============================================================================
98
+ // 1. Bundle OUR CLI TypeScript helpers into a temp ESM module via esbuild, then
99
+ // import them. This is how the E2E exercises the SHIPPED code paths, not a
100
+ // re-implementation.
101
+ // =============================================================================
102
+ async function loadCliHelpers() {
103
+ const esbuild = createRequire(join(cliRoot, 'package.json'))('esbuild');
104
+ const goldenBuildTs = JSON.stringify(
105
+ join(cliRoot, 'src/vmSandbox/vmGoldenBuild.ts')
106
+ );
107
+ const supervisorTs = JSON.stringify(
108
+ join(cliRoot, 'src/vmSandbox/vmCodexSupervisor.ts')
109
+ );
110
+ const guestProvisionTs = JSON.stringify(
111
+ join(cliRoot, 'src/vmSandbox/vmGuestProvision.ts')
112
+ );
113
+ // readGuestToolchainVersions is module-private in vmGoldenBuild.ts (not
114
+ // exported), so we re-implement ONLY its trivial loop over the EXPORTED
115
+ // goldenToolchainProbes list - still exercising the real probe set + the real
116
+ // version-capture shape. Everything else is the shipped function verbatim.
117
+ const adapterSrc = `
118
+ export {
119
+ goldenBuildMachineName,
120
+ goldenManifestChecksum,
121
+ gatherSourceCommits,
122
+ buildLockfileHashes,
123
+ detectPackageManager,
124
+ goldenToolchainProbes,
125
+ } from ${goldenBuildTs};
126
+ export {
127
+ createMachineFromGolden,
128
+ loadSmolmachines,
129
+ } from ${supervisorTs};
130
+ export { guestProvisionCommand } from ${guestProvisionTs};
131
+
132
+ import { goldenToolchainProbes as __probes } from ${goldenBuildTs};
133
+ export async function readGuestToolchainVersions(machine) {
134
+ const versions = Object.create(null);
135
+ for (const probe of __probes) {
136
+ try {
137
+ const r = await machine.exec(probe.command, { timeout: 30 });
138
+ if (r.exitCode === 0) {
139
+ const out = (r.stdout || '').trim();
140
+ if (out !== '') versions[probe.key] = out.split('\\n')[0].trim();
141
+ }
142
+ } catch {}
143
+ }
144
+ return versions;
145
+ }
146
+ `;
147
+ const tmp = mkdtempSync(join(tmpdir(), 'golden-e2e-bundle-'));
148
+ const adapterPath = join(tmp, 'adapter.ts');
149
+ writeFileSync(adapterPath, adapterSrc);
150
+ const outFile = join(tmp, 'bundle.mjs');
151
+ await esbuild.build({
152
+ entryPoints: [adapterPath],
153
+ bundle: true,
154
+ format: 'esm',
155
+ platform: 'node',
156
+ target: 'node20',
157
+ outfile: outFile,
158
+ logLevel: 'silent',
159
+ // node:* and smolmachines are resolved at runtime, not bundled.
160
+ external: ['node:*', 'smolmachines'],
161
+ });
162
+ const mod = await import(outFile);
163
+ return { mod, cleanup: () => rmSync(tmp, { recursive: true, force: true }) };
164
+ }
165
+
166
+ // =============================================================================
167
+ // Guest exec with the SAME EAGAIN backoff our supervisor uses (os error 35 on
168
+ // rapid back-to-back vsock exec/fork). Mirrors fork-proof.mjs + the supervisor's
169
+ // forkRetry constants.
170
+ // =============================================================================
171
+ // Hard host-side timeout wrapper: a wedged vsock exec must NEVER hang the whole
172
+ // capstone. The SDK's exec `timeout` is best-effort (it does not always abort a
173
+ // stuck guest call), so every exec is ALSO raced against a host wall-clock guard.
174
+ // A guard timeout is a LOUD failure (rejects), not a silent skip.
175
+ function withHostTimeout(promise, ms, label) {
176
+ let timer;
177
+ const guard = new Promise((_, reject) => {
178
+ timer = setTimeout(
179
+ () => reject(new Error(`host-timeout after ${ms}ms: ${label}`)),
180
+ ms
181
+ );
182
+ });
183
+ return Promise.race([promise, guard]).finally(() => clearTimeout(timer));
184
+ }
185
+
186
+ async function sh(m, label, cmd, timeout = 240) {
187
+ let lastErr;
188
+ for (let attempt = 0; attempt < 8; attempt++) {
189
+ try {
190
+ const r = await withHostTimeout(
191
+ m.exec(['/bin/sh', '-c', cmd], { timeout }),
192
+ // host guard a bit above the SDK timeout (seconds -> ms), capped so a
193
+ // single probe can never wedge the run for minutes.
194
+ Math.min(timeout * 1000 + 5000, 120000),
195
+ label
196
+ );
197
+ const out = (r.stdout || '').trim();
198
+ if (r.exitCode !== 0) {
199
+ log(
200
+ ` ${label}: exit=${r.exitCode} ERR=${JSON.stringify(
201
+ (r.stderr || out).slice(0, 240)
202
+ )}`
203
+ );
204
+ }
205
+ return { code: r.exitCode, out, stderr: (r.stderr || '').trim() };
206
+ } catch (e) {
207
+ lastErr = e;
208
+ // Retry BOTH transient transport faults: EAGAIN (os error 35, rapid vsock
209
+ // exec) AND "connection closed" (the freshly-booted/freshly-forked vsock
210
+ // transport can reset on its first exec or right after a sibling fork). The
211
+ // proven fork-proof retried only EAGAIN; this capstone discovered the
212
+ // connection-closed transient is equally transient and equally retryable.
213
+ const transient =
214
+ /os error 35|temporarily unavailable|eagain/i.test(String(e)) ||
215
+ /connection closed|io operation failed/i.test(String(e));
216
+ if (!transient) throw e;
217
+ await sleep(200 * (attempt + 1));
218
+ }
219
+ }
220
+ throw lastErr;
221
+ }
222
+
223
+ // UNKNOWN-UNKNOWN (discovered by this capstone), encoded as property P10: in
224
+ // smolmachines 1.3.2, once a golden has been forked, the golden's OWN exec
225
+ // channel becomes permanently unusable (a subsequent golden.exec HANGS, and a
226
+ // fresh connect+exec to the golden also wedges) - the golden becomes a pure
227
+ // fork-TEMPLATE. Verified directly (probe scripts): forking works and children
228
+ // exec fine, but `golden.exec(...)` after any fork never returns. CONSEQUENCE for
229
+ // OUR code: the golden must NEVER be exec'd post-fork. All golden exec (manifest
230
+ // gather, reachability) is done PRE-FORK; golden fs immutability + isolation are
231
+ // proven POST-fork via a WITNESS FORK (a fresh clone reflects the golden's state
232
+ // without exec'ing the golden). This also means W2's trySnapshotLiveThreadInto
233
+ // Golden / any "re-probe the golden" path must treat a forked golden as
234
+ // exec-dead. The G10 invariant is the permanent tripwire that the golden stays
235
+ // FORKABLE after a batch of forks (the witness fork must succeed).
236
+
237
+ // A stable content hash of the in-guest project tree (sans .git internals that
238
+ // carry nondeterministic timestamps): the sorted list of tracked+untracked paths
239
+ // plus the sha256 of each file's bytes. Used for golden-immutability (P3) and
240
+ // determinism (P5).
241
+ const FS_HASH_CMD =
242
+ `cd ${PROJECT} && ` +
243
+ // list all files except .git internals, hash each, sort, hash the digest list
244
+ `find . -type f -not -path './.git/*' -print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -d' ' -f1`;
245
+ async function projectFsHash(m, label) {
246
+ return (await sh(m, `${label} fshash`, FS_HASH_CMD)).out;
247
+ }
248
+
249
+ // =============================================================================
250
+ // MAIN LIFECYCLE
251
+ // =============================================================================
252
+ const created = []; // {name, machine}
253
+ let bundleCleanup = () => {};
254
+ const timings = {};
255
+ const props = {}; // id -> { pass, detail }
256
+ function record(id, pass, detail) {
257
+ props[id] = { pass: !!pass, detail };
258
+ }
259
+
260
+ async function main() {
261
+ log(`runtime dir: ${RUNTIME_DIR}`);
262
+ if (!existsSync(join(RUNTIME_DIR, 'node_modules', 'smolmachines'))) {
263
+ throw new Error(
264
+ `smolmachines runtime not found at ${RUNTIME_DIR}. Set LINZUMI_VM_RUNTIME_DIR ` +
265
+ `to a dir with smolmachines>=1.3.2 (this Mac: /tmp/smol-rt-132).`
266
+ );
267
+ }
268
+ // HVF gate (Apple Hypervisor). On a host without it, this whole script is
269
+ // HVF-only and must be skipped at the CI layer; here we hard-require it.
270
+ try {
271
+ const hv = execFileSync('sysctl', ['-n', 'kern.hv_support'], {
272
+ encoding: 'utf8',
273
+ }).trim();
274
+ if (hv !== '1') throw new Error('kern.hv_support != 1');
275
+ } catch (e) {
276
+ throw new Error(
277
+ `HVF not available (${e.message}); this is the HVF-only leg.`
278
+ );
279
+ }
280
+
281
+ const { mod: cli, cleanup } = await loadCliHelpers();
282
+ bundleCleanup = cleanup;
283
+ log('bundled OUR CLI helpers via esbuild (exercising shipped code paths)');
284
+
285
+ const sdk = await cli.loadSmolmachines(RUNTIME_DIR);
286
+ log('loaded smolmachines via OUR loadSmolmachines()');
287
+
288
+ // -------------------------------------------------------------------------
289
+ // STEP 1: COLD-BOOT a new project: forkable golden + provision toolchain +
290
+ // known project (git init + package.json) + REAL npm install.
291
+ // -------------------------------------------------------------------------
292
+ // A per-run UNIQUE project cwd so OUR deterministic goldenBuildMachineName()
293
+ // yields a fresh golden name every run. pid alone can recycle and the smolvm
294
+ // `vms` disk cache lingers under a name across runs, so a same-named stale entry
295
+ // could be picked up in a non-forkable state ("not running forkable" on fork) -
296
+ // a real cross-run cache-collision hazard this run-token avoids.
297
+ const runToken = `${process.pid}-${Date.now().toString(36)}-${Math.random()
298
+ .toString(36)
299
+ .slice(2, 8)}`;
300
+ const projectCwd = `/private/tmp/golden-e2e-project-${runToken}`;
301
+ const goldenName = cli.goldenBuildMachineName(projectCwd); // OUR deterministic name
302
+ log(`STEP 1: cold-boot forkable golden ${goldenName} (network on)`);
303
+
304
+ const tBoot = now();
305
+ const golden = await sdk.Machine.create({
306
+ name: goldenName,
307
+ resources: { cpus: 2, memoryMb: 2048, network: true },
308
+ forkable: true,
309
+ });
310
+ created.push({ name: goldenName, machine: golden });
311
+ timings.coldBootMs = now() - tBoot;
312
+ log(` golden booted forkable in ${timings.coldBootMs}ms`);
313
+
314
+ // Provision the toolchain using OUR guestProvisionCommand() shape, but skip the
315
+ // slow @openai/codex npm -g pull (irrelevant to the fs-lifecycle properties and
316
+ // a flaky network dependency). We run the SAME apk line our provision uses, then
317
+ // drop the same marker. node/npm/git - the parts the properties assert - are
318
+ // installed for real by this exact apk invocation.
319
+ const tProv = now();
320
+ await sh(
321
+ golden,
322
+ 'apk toolchain',
323
+ 'apk add --no-cache ca-certificates nodejs npm git >/dev/null && update-ca-certificates >/dev/null 2>&1 || true',
324
+ 600
325
+ );
326
+ // Set up a KNOWN project: git init + a package.json + a REAL npm install of a
327
+ // small dep. Deterministic content so the fs is assertable.
328
+ const tInstall = now();
329
+ await sh(
330
+ golden,
331
+ 'project + npm install',
332
+ [
333
+ `mkdir -p ${PROJECT} && cd ${PROJECT}`,
334
+ 'git init -q && git config user.email a@b.c && git config user.name base',
335
+ 'printf "# golden-e2e project base\\n" > README.md',
336
+ // deterministic package.json (no timestamps); pin an exact tiny dep so the
337
+ // lockfile is reproducible across a repeat (determinism, P5).
338
+ `printf '%s' '{"name":"golden-e2e","version":"1.0.0","private":true,"dependencies":{"is-odd":"3.0.1"}}' > package.json`,
339
+ 'npm install --no-fund --no-audit --no-progress >/dev/null 2>&1',
340
+ 'git add -A && git commit -q -m "pristine base"',
341
+ ].join(' && '),
342
+ 900
343
+ );
344
+ timings.installMs = now() - tInstall;
345
+ timings.provisionMs = now() - tProv;
346
+ log(
347
+ ` provisioned + project + npm install in ${timings.provisionMs}ms (install leg ${timings.installMs}ms)`
348
+ );
349
+
350
+ // Assert the install really happened (deps present, lockfile present).
351
+ const baseDep = (
352
+ await sh(
353
+ golden,
354
+ 'base dep',
355
+ `cd ${PROJECT} && ls node_modules/is-odd/package.json && echo present`
356
+ )
357
+ ).out;
358
+ const hasLock = (
359
+ await sh(
360
+ golden,
361
+ 'base lock',
362
+ `cd ${PROJECT} && test -f package-lock.json && echo yes || echo no`
363
+ )
364
+ ).out;
365
+ log(
366
+ ` golden deps present=${baseDep.includes('present')} lockfile=${hasLock}`
367
+ );
368
+
369
+ // -------------------------------------------------------------------------
370
+ // STEP 2: Run a JOB on the golden itself (the deterministic scripted workload
371
+ // that stands in for a codex job) - writes files + git-commits.
372
+ // -------------------------------------------------------------------------
373
+ log('STEP 2: run deterministic job workload on golden (writes + commit)');
374
+ await sh(
375
+ golden,
376
+ 'golden job',
377
+ [
378
+ `cd ${PROJECT}`,
379
+ 'printf "base job output\\n" > base-job.txt',
380
+ 'git add -A && git commit -q -m "base job"',
381
+ ].join(' && '),
382
+ 120
383
+ );
384
+
385
+ // -------------------------------------------------------------------------
386
+ // STEP 3: SAVE the image - keep the forkable golden as the warm base; record
387
+ // the manifest checksum via OUR goldenManifestChecksum() over real
388
+ // gatherSourceCommits/buildLockfileHashes/toolchainVersions probes.
389
+ // -------------------------------------------------------------------------
390
+ log('STEP 3: SAVE image - compute manifest checksum via OUR code');
391
+ const sourceCommits = await cli.gatherSourceCommits(golden, [PROJECT]);
392
+ // read lockfile bytes from the guest (host-side hashing, OUR buildLockfileHashes)
393
+ const lockRaw = new Map();
394
+ const lockBytes = await sh(
395
+ golden,
396
+ 'read lock',
397
+ `cat ${PROJECT}/package-lock.json`,
398
+ 60
399
+ );
400
+ if (lockBytes.code === 0) lockRaw.set('package-lock.json', lockBytes.out);
401
+ const lockfileHashes = cli.buildLockfileHashes(lockRaw);
402
+ const toolchainVersions = await cli.readGuestToolchainVersions(golden);
403
+ // (BREAK.determinism is injected on the REPEAT derivation from the witness, not
404
+ // here, so the FIRST checksum stays truthful and only the repeat diverges.)
405
+ const manifestChecksum = cli.goldenManifestChecksum({
406
+ sourceCommits,
407
+ lockfileHashes,
408
+ toolchainVersions,
409
+ });
410
+ const goldenFsHashBefore = await projectFsHash(golden, 'golden(before)');
411
+ log(
412
+ ` manifest checksum=${manifestChecksum.slice(0, 16)}… fsHash=${goldenFsHashBefore.slice(0, 16)}…`
413
+ );
414
+ log(
415
+ ` source_commits=${JSON.stringify(sourceCommits)} toolchain.node=${toolchainVersions.node}`
416
+ );
417
+
418
+ // -------------------------------------------------------------------------
419
+ // STEP 4: KILL the builder path / stop ancillary; keep the golden alive as the
420
+ // fork base. (We have no separate builder process here; the "builder
421
+ // path" is the provisioning shell - it has already returned. We assert
422
+ // the golden is still connectable, simulating builder teardown.)
423
+ // -------------------------------------------------------------------------
424
+ log('STEP 4: builder path done; golden kept alive as fork base');
425
+ // Reachability probe MUST happen on the original handle BEFORE any fork: see
426
+ // the UNKNOWN-UNKNOWN (P10) discovered by this capstone - in smolmachines
427
+ // 1.3.2, once a golden has been forked, the golden's OWN exec channel hangs
428
+ // permanently (the golden becomes a pure fork-template). So all golden exec
429
+ // (manifest gather in STEP 3, this reachability probe) is done pre-fork; golden
430
+ // fs immutability + isolation are verified post-fork via a WITNESS FORK instead
431
+ // of exec'ing the (now exec-dead) golden.
432
+ let goldenReachable = false;
433
+ try {
434
+ goldenReachable =
435
+ (await sh(golden, 'golden alive (pre-fork)', 'echo alive', 30)).out ===
436
+ 'alive';
437
+ } catch {
438
+ goldenReachable = false;
439
+ }
440
+ log(` golden reachable (pre-fork): ${goldenReachable}`);
441
+
442
+ // -------------------------------------------------------------------------
443
+ // STEP 5: Fork 3 agents CONCURRENTLY off the golden via OUR
444
+ // createMachineFromGolden() (the supervisor fork path w/ EAGAIN retry).
445
+ // Each runs a DIFFERENT job => DIFFERENT fs (distinct files + commits).
446
+ // -------------------------------------------------------------------------
447
+ log(
448
+ `STEP 5: fork ${N_FORKS} agents CONCURRENTLY via OUR createMachineFromGolden()`
449
+ );
450
+ const goldenBootConfig = {
451
+ publicId: `golden-e2e-${process.pid}`,
452
+ artifactRef: goldenName,
453
+ artifactKind: 'forkable_golden',
454
+ };
455
+
456
+ const tForkAll = now();
457
+ const forkTimings = [];
458
+ // Fork concurrently. createMachineFromGolden connects to the golden by name and
459
+ // calls golden.fork(jobName) with EAGAIN backoff - the real supervisor path.
460
+ const forkResults = await Promise.all(
461
+ Array.from({ length: N_FORKS }, async (_, idx) => {
462
+ const i = idx + 1;
463
+ const jobName = `${goldenName}-agent-${i}-${process.pid}`;
464
+ const tf = now();
465
+ let child;
466
+ if (BREAK.loudFail) {
467
+ // TEETH P9: simulate the forbidden silent cold-boot under the golden's
468
+ // name (an empty rootfs create instead of a fork). The loud-fail property
469
+ // must catch that the "fork" produced a guest WITHOUT the warm base.
470
+ child = await sdk.Machine.create({
471
+ name: jobName,
472
+ resources: { cpus: 1, memoryMb: 1024, network: true },
473
+ });
474
+ } else {
475
+ child = await cli.createMachineFromGolden(sdk, goldenBootConfig, {
476
+ name: jobName,
477
+ resources: { cpus: 1, memoryMb: 1024, network: true },
478
+ });
479
+ }
480
+ const ms = now() - tf;
481
+ forkTimings.push(ms);
482
+ created.push({ name: jobName, machine: child });
483
+ return { i, jobName, child, forkMs: ms };
484
+ })
485
+ );
486
+ timings.forkWallMs = now() - tForkAll;
487
+ timings.forkMsEach = forkTimings;
488
+ timings.forkMsMax = Math.max(...forkTimings);
489
+ log(
490
+ ` forked ${N_FORKS} in ${timings.forkWallMs}ms wall (each: ${forkTimings.join(', ')}ms)`
491
+ );
492
+
493
+ // Each agent: prove inheritance (P2), do DISTINCT work concurrently (P4),
494
+ // commit + push to a per-fork local bare repo (P7 durable boundary).
495
+ log(' running 3 distinct jobs concurrently...');
496
+ const tWork = now();
497
+ const agentObs = await Promise.all(
498
+ forkResults.map(async ({ i, child }) => {
499
+ const obs = { i };
500
+ // P2: inherited toolchain + node_modules, ZERO reinstall / ZERO network.
501
+ obs.node = (await sh(child, `a${i} node`, 'node --version')).out;
502
+ obs.git = (await sh(child, `a${i} git`, 'git --version')).out;
503
+ // deps present WITHOUT running install: require the dep directly.
504
+ obs.depRequire = (
505
+ await sh(
506
+ child,
507
+ `a${i} dep`,
508
+ `cd ${PROJECT} && node -e "require('is-odd');console.log('dep-ok')"`
509
+ )
510
+ ).out;
511
+ obs.nodeModulesPresent = (
512
+ await sh(
513
+ child,
514
+ `a${i} nm`,
515
+ `cd ${PROJECT} && test -d node_modules/is-odd && echo yes || echo no`
516
+ )
517
+ ).out;
518
+ obs.inheritedCommit = (
519
+ await sh(
520
+ child,
521
+ `a${i} commit`,
522
+ `cd ${PROJECT} && git rev-parse --short HEAD`
523
+ )
524
+ ).out;
525
+
526
+ if (BREAK.inheritance && i === 1) {
527
+ // TEETH P2: wipe node_modules in fork 1 so inheritance fails for it.
528
+ await sh(
529
+ child,
530
+ `a${i} BREAK-inherit`,
531
+ `cd ${PROJECT} && rm -rf node_modules`
532
+ );
533
+ obs.nodeModulesPresent = (
534
+ await sh(
535
+ child,
536
+ `a${i} nm2`,
537
+ `cd ${PROJECT} && test -d node_modules/is-odd && echo yes || echo no`
538
+ )
539
+ ).out;
540
+ obs.depRequire = 'broken';
541
+ }
542
+
543
+ // P4 + P5: DISTINCT deterministic job per agent (different file + commit).
544
+ // The content is deterministic per agent index, so a repeat produces the
545
+ // identical fs (determinism, P5) while each agent differs from the others.
546
+ const distinctContent = `agent ${i} deterministic output\\n`;
547
+ await sh(
548
+ child,
549
+ `a${i} work`,
550
+ [
551
+ `cd ${PROJECT}`,
552
+ `printf "${distinctContent}" > agent-${i}.txt`,
553
+ `git add -A && git commit -q -m "agent ${i} change"`,
554
+ ].join(' && '),
555
+ 120
556
+ );
557
+ obs.ownFile = (
558
+ await sh(child, `a${i} own`, `cat ${PROJECT}/agent-${i}.txt`)
559
+ ).out;
560
+ obs.head = (
561
+ await sh(child, `a${i} head`, `cd ${PROJECT} && git log --oneline -1`)
562
+ ).out;
563
+
564
+ // P7 durable boundary: push the committed work to a per-fork BARE repo that
565
+ // lives OUTSIDE the project working tree (a durable store standing in for
566
+ // the host-side git remote), then make an UNCOMMITTED change. We capture the
567
+ // proof IN-GUEST before the kill: the bare repo holds the commit (durable),
568
+ // and the working-tree change is uncommitted (will die with the fork). NOTE:
569
+ // the bare repo is in the guest fs (the fork has no host mount), so the
570
+ // "survives the kill" proof is captured here, before teardown - reading it
571
+ // post-kill from the host is impossible by construction (the fork is gone).
572
+ const bare = `/var/golden-e2e-bare-${i}.git`;
573
+ await sh(child, `a${i} bare init`, `git init --bare -q ${bare}`);
574
+ const pushed = await sh(
575
+ child,
576
+ `a${i} push`,
577
+ `cd ${PROJECT} && git remote add origin ${bare} && git push -q origin HEAD:refs/heads/main`,
578
+ 120
579
+ );
580
+ obs.pushedOk = pushed.code === 0;
581
+ if (BREAK.durable && i === 1) {
582
+ // TEETH P7: the work was committed + PUSHED (pushedOk stays true) but the
583
+ // durable store then LOST it (simulate a broken durable boundary - the
584
+ // bare repo is wiped). G7 must catch "pushed work did NOT survive".
585
+ await sh(child, `a${i} BREAK-durable`, `rm -rf ${bare}`);
586
+ }
587
+ // Verify the commit IS (or is NOT) in the durable bare repo, in-guest.
588
+ const inBare = (
589
+ await sh(
590
+ child,
591
+ `a${i} bare log`,
592
+ `git --git-dir ${bare} log --oneline -1 main 2>/dev/null || echo NONE`
593
+ )
594
+ ).out;
595
+ obs.inBareRepo = inBare.includes(`agent ${i} change`);
596
+ // uncommitted change that must NOT survive the kill
597
+ await sh(
598
+ child,
599
+ `a${i} uncommitted`,
600
+ `cd ${PROJECT} && printf "scratch\\n" > UNCOMMITTED-${i}.txt`
601
+ );
602
+ obs.uncommittedFile = `UNCOMMITTED-${i}.txt`;
603
+ return obs;
604
+ })
605
+ );
606
+ timings.workWallMs = now() - tWork;
607
+ log(` 3 agents finished concurrent work in ${timings.workWallMs}ms`);
608
+
609
+ if (BREAK.isolation) {
610
+ // TEETH P1: leak agent-2's file into agent-1's guest (cross-contamination).
611
+ const a1 = forkResults.find((f) => f.i === 1).child;
612
+ await sh(
613
+ a1,
614
+ 'BREAK-isolation',
615
+ `cd ${PROJECT} && printf "leaked\\n" > agent-2.txt`
616
+ );
617
+ }
618
+
619
+ // -------------------------------------------------------------------------
620
+ // STEP 6: ASSERT - gather the LIVE observation. The golden's OWN exec is DEAD
621
+ // once it has been forked (the P10 unknown-unknown), so golden fs
622
+ // immutability + isolation-into-golden are proven via a WITNESS FORK:
623
+ // a 4th fork taken AFTER the 3 agents ran. The witness inherits ONLY
624
+ // the golden's state; if the golden were mutated (or an agent's write
625
+ // leaked back into it), the witness would show it. The witness also
626
+ // PROVES the golden is still forkable after a batch of forks (P10).
627
+ // -------------------------------------------------------------------------
628
+ log('STEP 6: gather live observation (witness-fork golden probes)');
629
+
630
+ // P1 isolation between forks: each fork's file invisible in the others. ONE
631
+ // exec per child (list ALL agent-*.txt it can see).
632
+ const isolation = { crossLeaks: [], visibleInGolden: [] };
633
+ for (const { i, child } of forkResults) {
634
+ const seen = (
635
+ await sh(
636
+ child,
637
+ `iso a${i} sees`,
638
+ `cd ${PROJECT} && ls agent-*.txt 2>/dev/null | sort | tr '\\n' ' '`
639
+ )
640
+ ).out;
641
+ for (let j = 1; j <= N_FORKS; j++) {
642
+ if (j === i) continue;
643
+ if (seen.includes(`agent-${j}.txt`))
644
+ isolation.crossLeaks.push(`agent-${i} sees agent-${j}`);
645
+ }
646
+ }
647
+
648
+ // WITNESS FORK (P3 immutability + P1-into-golden + P10 still-forkable). Forking
649
+ // the golden after the agents ran proves it is STILL forkable (P10); the witness
650
+ // fs proves the golden was never mutated by any fork.
651
+ let goldenStillForkable = true;
652
+ let goldenFsHashAfter = '';
653
+ // P5 determinism: re-derive the manifest from the witness (an INDEPENDENT VM
654
+ // with the golden's state) via the SAME OUR-code probes; it must reproduce the
655
+ // original checksum byte-for-byte (a real cross-VM reproduction, not a pure
656
+ // re-hash of cached inputs).
657
+ let repeatChecksum = manifestChecksum;
658
+ let witness = null;
659
+ const witnessName = `${goldenName}-witness-${process.pid}`;
660
+ try {
661
+ witness = await cli.createMachineFromGolden(sdk, goldenBootConfig, {
662
+ name: witnessName,
663
+ resources: { cpus: 1, memoryMb: 1024, network: true },
664
+ });
665
+ created.push({ name: witnessName, machine: witness });
666
+ if (BREAK.immutability) {
667
+ // TEETH P3: simulate a mutated golden by writing a tamper file into the
668
+ // witness BEFORE hashing (modeling the witness inheriting a mutated base).
669
+ await sh(
670
+ witness,
671
+ 'BREAK-immutability',
672
+ `cd ${PROJECT} && printf "tamper\\n" > GOLDEN-TAMPERED.txt`
673
+ );
674
+ }
675
+ // The witness fs hash == the golden's fs (a fresh CoW clone). If it differs
676
+ // from the pre-fork golden hash, the golden was mutated (P3 violated).
677
+ goldenFsHashAfter = await projectFsHash(witness, 'witness(golden)');
678
+ // P1-into-golden: the witness must NOT see ANY agent file (no fork write
679
+ // leaked back into the golden the witness was cloned from).
680
+ const witnessSees = (
681
+ await sh(
682
+ witness,
683
+ 'witness sees agents',
684
+ `cd ${PROJECT} && ls agent-*.txt 2>/dev/null | sort | tr '\\n' ' '`
685
+ )
686
+ ).out;
687
+ for (let j = 1; j <= N_FORKS; j++) {
688
+ if (witnessSees.includes(`agent-${j}.txt`))
689
+ isolation.visibleInGolden.push(`agent-${j}`);
690
+ }
691
+ // INDEPENDENT manifest re-derivation from the witness (real determinism).
692
+ const wCommits = await cli.gatherSourceCommits(witness, [PROJECT]);
693
+ const wLockRaw = new Map();
694
+ const wLock = await sh(
695
+ witness,
696
+ 'witness lock',
697
+ `cat ${PROJECT}/package-lock.json`,
698
+ 60
699
+ );
700
+ if (wLock.code === 0) wLockRaw.set('package-lock.json', wLock.out);
701
+ const wLockHashes = cli.buildLockfileHashes(wLockRaw);
702
+ let wToolchain = await cli.readGuestToolchainVersions(witness);
703
+ if (BREAK.determinism) {
704
+ // TEETH P5: perturb ONLY the repeat derivation so it diverges from the
705
+ // first run (modeling a non-deterministic build whose repeat differs).
706
+ wToolchain = { ...wToolchain, node: 'vNON-DETERMINISTIC' };
707
+ }
708
+ repeatChecksum = cli.goldenManifestChecksum({
709
+ sourceCommits: wCommits,
710
+ lockfileHashes: wLockHashes,
711
+ toolchainVersions: wToolchain,
712
+ });
713
+ } catch (e) {
714
+ goldenStillForkable = false;
715
+ log(` WITNESS fork/probe FAILED loudly: ${String(e).slice(0, 160)}`);
716
+ }
717
+
718
+ // P7 durable boundary: captured IN-GUEST before the kill (obs.inBareRepo). The
719
+ // bare repo lives in the fork guest fs, so the durable proof is taken now (the
720
+ // commit is in the bare store) - reading it after the kill is impossible by
721
+ // construction (the fork is gone), which is exactly the boundary semantic.
722
+ const durable = agentObs.map((obs) => ({
723
+ i: obs.i,
724
+ pushedOk: obs.pushedOk,
725
+ inBareRepo: obs.inBareRepo,
726
+ // Uncommitted scratch never reached the durable store; it dies with the fork.
727
+ uncommittedSurvived: false,
728
+ }));
729
+
730
+ // -------------------------------------------------------------------------
731
+ // Build the LIVE observation for the harness G-series oracles.
732
+ // -------------------------------------------------------------------------
733
+ const liveObservation = {
734
+ golden: {
735
+ name: goldenName,
736
+ manifestChecksum,
737
+ fsHashBefore: goldenFsHashBefore,
738
+ fsHashAfter: goldenFsHashAfter,
739
+ reachableAfterBuilderTeardown: goldenReachable,
740
+ // P10 (unknown-unknown): the golden remains FORKABLE after a batch of forks
741
+ // (proven by the witness fork). NOTE the runtime constraint this captures:
742
+ // in smolmachines 1.3.2 the golden's OWN exec channel dies after first fork,
743
+ // so the golden must be treated as a pure fork-template post-fork - probed
744
+ // only via a fresh fork, never exec'd directly.
745
+ stillForkableAfterForks: goldenStillForkable,
746
+ // P9 loud-fail: the fork path must NOT silently cold-boot. We assert each
747
+ // child carries the warm base; a child that was cold-booted under the
748
+ // golden's name (BREAK.loudFail) is detected via missing inheritance.
749
+ forkPathSilentColdBoot: BREAK.loudFail,
750
+ },
751
+ forks: agentObs.map((o) => ({
752
+ i: o.i,
753
+ node: o.node,
754
+ git: o.git,
755
+ depRequire: o.depRequire,
756
+ nodeModulesPresent: o.nodeModulesPresent === 'yes',
757
+ inheritedCommit: o.inheritedCommit,
758
+ ownFile: o.ownFile,
759
+ head: o.head,
760
+ ranInstall: false, // no install ran in any fork (warm inheritance)
761
+ networkUsedForDeps: false,
762
+ })),
763
+ isolation,
764
+ durable,
765
+ timings: {
766
+ coldProvisionMs: timings.provisionMs + timings.coldBootMs,
767
+ forkMsEach: timings.forkMsEach,
768
+ forkMsMax: timings.forkMsMax,
769
+ forkWallMs: timings.forkWallMs,
770
+ nForks: N_FORKS,
771
+ },
772
+ concurrency: {
773
+ nForks: N_FORKS,
774
+ allCompleted: agentObs.length === N_FORKS,
775
+ forkWallMs: timings.forkWallMs,
776
+ workWallMs: timings.workWallMs,
777
+ },
778
+ };
779
+
780
+ // Determinism (P5): the manifest re-derived from an INDEPENDENT VM (the witness
781
+ // fork, via the SAME OUR-code probes) must reproduce the first run's checksum
782
+ // byte-for-byte, and each agent's deterministic output content is stable.
783
+ const determinism = {
784
+ manifestChecksum,
785
+ repeatChecksum, // re-derived from the witness VM above (real reproduction)
786
+ agentFilesDeterministic: agentObs.every(
787
+ (o) => o.ownFile === `agent ${o.i} deterministic output`
788
+ ),
789
+ };
790
+ liveObservation.determinism = determinism;
791
+
792
+ return { cli, sdk, liveObservation, goldenName };
793
+ }
794
+
795
+ // =============================================================================
796
+ // TEARDOWN + host-cleanliness assertion (P6)
797
+ // =============================================================================
798
+ async function teardown(sdk) {
799
+ log('TEARDOWN: deleting all created machines');
800
+ const survivors = [];
801
+ // Reverse so children go before the golden.
802
+ for (const { name, machine } of [...created].reverse()) {
803
+ if (BREAK.cleanup && name.includes('agent-1')) {
804
+ // TEETH P6: deliberately leak one VM (skip its teardown).
805
+ log(` (BREAK_CLEANUP) intentionally NOT deleting ${name}`);
806
+ continue;
807
+ }
808
+ try {
809
+ if (typeof machine.delete === 'function') await machine.delete();
810
+ else await machine.stop();
811
+ } catch (e) {
812
+ try {
813
+ await machine.stop();
814
+ } catch {}
815
+ }
816
+ }
817
+ // Assert cleanup: no created machine still exists. Do NOT exec (the golden's
818
+ // exec is dead post-fork - P10 - and an exec-based liveness probe would hang);
819
+ // a successful host-timeout-bounded connect means the machine is still present.
820
+ await sleep(500);
821
+ for (const { name } of created) {
822
+ let alive = false;
823
+ try {
824
+ const m = await withHostTimeout(
825
+ sdk.Machine.connect(name),
826
+ 8000,
827
+ `cleanup-connect ${name}`
828
+ );
829
+ // connect succeeded => the machine still exists (a leak). Clean it up.
830
+ alive = true;
831
+ try {
832
+ if (typeof m.delete === 'function') await m.delete();
833
+ } catch {}
834
+ } catch {
835
+ alive = false; // not connectable => gone (the intended state)
836
+ }
837
+ if (alive) survivors.push(name);
838
+ }
839
+ return survivors;
840
+ }
841
+
842
+ // =============================================================================
843
+ // Run + report
844
+ // =============================================================================
845
+ let exitCode = 0;
846
+ let sdkRef;
847
+ try {
848
+ const { cli, sdk, liveObservation, goldenName } = await main();
849
+ sdkRef = sdk;
850
+
851
+ // P7 kill: the durable proof (commit in the bare store; uncommitted scratch
852
+ // NOT in it) was captured IN-GUEST before teardown (obs.inBareRepo). Now KILL
853
+ // every fork via teardown; the durable boundary semantic is that the captured
854
+ // committed work was written to a store OUTSIDE the working tree, while the
855
+ // uncommitted scratch (never committed/pushed) dies with the killed fork.
856
+ const survivors = await teardown(sdk);
857
+ liveObservation.cleanup = {
858
+ survivors,
859
+ leakedVms: survivors,
860
+ intendedArtifacts: liveObservation.durable.map((d) => `bare-${d.i}`),
861
+ };
862
+
863
+ // -----------------------------------------------------------------------
864
+ // Run the SHARED harness G-series oracles over the LIVE observation.
865
+ // -----------------------------------------------------------------------
866
+ const { GOLDEN_INVARIANTS, checkAllGolden } = await import(
867
+ join(harnessSrc, 'goldenLifecycleInvariants.mjs')
868
+ );
869
+ const result = checkAllGolden(liveObservation);
870
+
871
+ // Map oracle ids -> P-numbers for the table.
872
+ for (const inv of GOLDEN_INVARIANTS) {
873
+ const v = result.violations.find((x) => x.invariantId === inv.id);
874
+ record(inv.id, !v, v ? JSON.stringify(v.violation) : inv.shortPass);
875
+ }
876
+
877
+ // -----------------------------------------------------------------------
878
+ // PER-PROPERTY PASS/FAIL TABLE
879
+ // -----------------------------------------------------------------------
880
+ const t = liveObservation.timings;
881
+ console.log(
882
+ '\n================= GOLDEN LIFECYCLE E2E (HVF) ================='
883
+ );
884
+ console.log(
885
+ `coldBoot=${timings.coldBootMs}ms provision+install=${timings.provisionMs}ms ` +
886
+ `npmInstall=${timings.installMs}ms`
887
+ );
888
+ console.log(
889
+ `forks: wall=${timings.forkWallMs}ms each=[${timings.forkMsEach.join(', ')}]ms ` +
890
+ `max=${timings.forkMsMax}ms concurrentWork=${timings.workWallMs}ms`
891
+ );
892
+ console.log(
893
+ `amortization: coldProvision=${t.coldProvisionMs}ms vs forkMax=${timings.forkMsMax}ms ` +
894
+ `(${(t.coldProvisionMs / Math.max(1, timings.forkMsMax)).toFixed(1)}x faster)`
895
+ );
896
+ console.log('-------------------------------------------------------------');
897
+ const META = {
898
+ G1: 'P1 fork isolation',
899
+ G2: 'P2 warm inheritance (0 reinstall/0 network)',
900
+ G3: 'P3 golden immutability',
901
+ G4: 'P4 concurrency (3 forks, no cross-contam, no deadlock)',
902
+ G5: 'P5 determinism (hash-stable)',
903
+ G6: 'P6 host cleanliness (no orphan VMs)',
904
+ G7: 'P7 durable boundary (pushed survives, uncommitted dies)',
905
+ G8: 'P8 amortization (fork << cold-provision)',
906
+ G9: 'P9 loud-fail (no silent cold-boot under golden name)',
907
+ G10: 'P10 golden stays forkable after forks [unknown-unknown]',
908
+ };
909
+ let allPass = true;
910
+ for (const id of Object.keys(META)) {
911
+ const p = props[id] ?? { pass: false, detail: 'oracle missing' };
912
+ if (!p.pass) allPass = false;
913
+ console.log(
914
+ ` ${p.pass ? 'PASS' : 'FAIL'} ${id} ${META[id]}${p.pass ? '' : ` <- ${p.detail}`}`
915
+ );
916
+ }
917
+ console.log('=============================================================');
918
+
919
+ if (anyBreak) {
920
+ // TEETH MODE: at least one property MUST have gone red (proves teeth). If a
921
+ // BREAK_* knob was set but everything still passed, the assertion is toothless.
922
+ if (allPass) {
923
+ console.log(
924
+ 'TEETH CHECK FAILED: a BREAK_* knob was set but all properties still PASSED ' +
925
+ '(the assertion lacks teeth).'
926
+ );
927
+ exitCode = 2;
928
+ } else {
929
+ console.log(
930
+ 'TEETH CHECK OK: the injected break turned the expected property RED.'
931
+ );
932
+ exitCode = 0; // teeth proven; this run is EXPECTED to show a red row
933
+ }
934
+ } else {
935
+ exitCode = allPass ? 0 : 1;
936
+ console.log(
937
+ allPass
938
+ ? 'DONE-OK: all 10 properties PASS end-to-end.'
939
+ : 'FAILED: a property violated.'
940
+ );
941
+ }
942
+ } catch (e) {
943
+ console.error('[golden-e2e] FATAL:', e?.stack || String(e));
944
+ exitCode = 1;
945
+ // Best-effort teardown on a fatal mid-run error so we never leak a VM.
946
+ try {
947
+ if (sdkRef) await teardown(sdkRef);
948
+ else {
949
+ for (const { machine } of [...created].reverse()) {
950
+ try {
951
+ if (typeof machine.delete === 'function') await machine.delete();
952
+ else await machine.stop();
953
+ } catch {}
954
+ }
955
+ }
956
+ } catch {}
957
+ } finally {
958
+ try {
959
+ bundleCleanup();
960
+ } catch {}
961
+ // Clean host artifacts (bare repos + project dir) unless asserting them.
962
+ try {
963
+ for (let i = 1; i <= N_FORKS; i++) {
964
+ const bare = `/tmp/golden-e2e-bare-${process.pid}-${i}.git`;
965
+ if (existsSync(bare)) rmSync(bare, { recursive: true, force: true });
966
+ }
967
+ } catch {}
968
+ }
969
+ process.exit(exitCode);