pi-oracle 0.7.21 → 0.7.22

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +49 -385
  3. package/docs/ORACLE_DESIGN.md +1 -1
  4. package/docs/development.md +99 -0
  5. package/docs/platform-smoke.md +3 -3
  6. package/docs/reference.md +331 -0
  7. package/extensions/oracle/index.ts +1 -0
  8. package/extensions/oracle/lib/poller.ts +3 -8
  9. package/extensions/oracle/lib/queue.ts +2 -4
  10. package/extensions/oracle/shared/browser-profile-helpers.d.mts +1 -0
  11. package/extensions/oracle/shared/browser-profile-helpers.mjs +32 -0
  12. package/extensions/oracle/worker/auth-bootstrap.mjs +35 -6
  13. package/extensions/oracle/worker/chromium-cookie-source.mjs +50 -6
  14. package/extensions/oracle/worker/linux-chromium-cookie-source.d.mts +6 -0
  15. package/extensions/oracle/worker/linux-chromium-cookie-source.mjs +98 -0
  16. package/extensions/oracle/worker/run-job.mjs +10 -5
  17. package/package.json +12 -11
  18. package/platform-smoke.config.mjs +1 -22
  19. package/scripts/oracle-auth.test.mjs +35 -0
  20. package/scripts/oracle-chatgpt-preset-proof.mjs +19 -12
  21. package/scripts/oracle-cookie-import.test.mjs +204 -0
  22. package/scripts/oracle-model.test.mjs +5 -1
  23. package/scripts/oracle-real-smoke.mjs +17 -7
  24. package/scripts/platform-smoke/Dockerfile.ubuntu +3 -2
  25. package/scripts/platform-smoke/crabbox-runner.mjs +3 -5
  26. package/scripts/platform-smoke/doctor.mjs +9 -24
  27. package/scripts/platform-smoke/invariants.mjs +234 -22
  28. package/scripts/platform-smoke/platform-build-windows.ps1 +4 -16
  29. package/scripts/platform-smoke/targets.mjs +35 -27
  30. package/scripts/platform-smoke.mjs +2 -2
@@ -3,12 +3,13 @@
3
3
 
4
4
  import assert from "node:assert/strict";
5
5
  import { spawnSync } from "node:child_process";
6
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
6
+ import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import config from "../../platform-smoke.config.mjs";
11
11
  import { writeManifest } from "./artifacts.mjs";
12
+ import { buildTargetBaseArgs } from "./crabbox-runner.mjs";
12
13
  import { buildPlatformBuildCommand, buildRealExtensionCommand } from "./targets.mjs";
13
14
 
14
15
  const repoRoot = fileURLToPath(new URL("../..", import.meta.url));
@@ -33,6 +34,13 @@ function testTargetSelection() {
33
34
  assert.match(`${result.stdout ?? ""}\n${result.stderr ?? ""}`, /unsupported target: not-a-target/);
34
35
  }
35
36
 
37
+ function testEmptyTargetSelectionRejected() {
38
+ const result = runNode(["scripts/platform-smoke.mjs", "run", "--target", ",,", "--suite", "platform-build"]);
39
+ assert.notEqual(result.status, 0, "an --target value that resolves to zero targets must fail instead of running nothing");
40
+ assert.ifError(result.error);
41
+ assert.match(`${result.stdout ?? ""}\n${result.stderr ?? ""}`, /--target did not resolve to any targets/);
42
+ }
43
+
36
44
  function testPackedInstallCommandRendering() {
37
45
  const command = buildPlatformBuildCommand("ubuntu", config.packageName, config.nodeValidationMajor);
38
46
  assert.match(command, /verify:oracle:platform/, "platform-build should run the platform-focused verification gate");
@@ -61,17 +69,220 @@ function testManifestFailure() {
61
69
  }
62
70
  }
63
71
 
64
- function testCleanupFailureInvariant() {
65
- const source = readFileSync(new URL("./targets.mjs", import.meta.url), "utf8");
66
- assert.match(source, /recordStopResultOnSuite/, "multi-suite runs should record stop evidence on suite artifacts");
67
- assert.match(source, /lease-stop/, "cleanup results should be asserted as lease-stop");
68
- assert.match(source, /stop exit/, "cleanup failure should surface a stop exit error");
72
+ function withEnv(overrides, run) {
73
+ const names = Object.keys(overrides);
74
+ const saved = {};
75
+ for (const name of names) {
76
+ saved[name] = process.env[name];
77
+ if (overrides[name] === undefined) delete process.env[name];
78
+ else process.env[name] = overrides[name];
79
+ }
80
+ try {
81
+ return run();
82
+ } finally {
83
+ for (const name of names) {
84
+ if (saved[name] === undefined) delete process.env[name];
85
+ else process.env[name] = saved[name];
86
+ }
87
+ }
88
+ }
89
+
90
+ function testTargetBaseArgsEnvPrecedence() {
91
+ withEnv({
92
+ PI_ORACLE_SMOKE_UBUNTU_IMAGE: undefined,
93
+ PLATFORM_SMOKE_UBUNTU_IMAGE: undefined,
94
+ PI_ORACLE_SMOKE_MAC_WORK_ROOT: undefined,
95
+ PLATFORM_SMOKE_MAC_WORK_ROOT: undefined,
96
+ PI_ORACLE_SMOKE_WINDOWS_NATIVE_WORK_ROOT: undefined,
97
+ PLATFORM_SMOKE_WINDOWS_WORK_ROOT: undefined,
98
+ }, () => {
99
+ const baseline = buildTargetBaseArgs("ubuntu", {});
100
+ assert.deepEqual(
101
+ baseline,
102
+ ["--provider", "local-container", "--target", "linux", "--local-container-image", "pi-oracle-platform-smoke:node24"],
103
+ "default ubuntu target args should use the documented default image",
104
+ );
105
+
106
+ process.env.PLATFORM_SMOKE_UBUNTU_IMAGE = "invariants-shared-image";
107
+ let args = buildTargetBaseArgs("ubuntu", {});
108
+ assert(args.includes("invariants-shared-image"), "shared PLATFORM_SMOKE_UBUNTU_IMAGE override should reach target args");
109
+
110
+ process.env.PI_ORACLE_SMOKE_UBUNTU_IMAGE = "invariants-private-image";
111
+ args = buildTargetBaseArgs("ubuntu", {});
112
+ assert(args.includes("invariants-private-image") && !args.includes("invariants-shared-image"), "private PI_ORACLE_SMOKE_UBUNTU_IMAGE alias should win over the shared alias");
113
+
114
+ delete process.env.PI_ORACLE_SMOKE_UBUNTU_IMAGE;
115
+ delete process.env.PLATFORM_SMOKE_UBUNTU_IMAGE;
116
+
117
+ process.env.PLATFORM_SMOKE_MAC_WORK_ROOT = "/invariants/shared-work";
118
+ args = buildTargetBaseArgs("macos", {});
119
+ assert(args.includes("/invariants/shared-work"), "shared PLATFORM_SMOKE_MAC_WORK_ROOT override should reach target args");
120
+
121
+ process.env.PI_ORACLE_SMOKE_MAC_WORK_ROOT = "/invariants/private-work";
122
+ args = buildTargetBaseArgs("macos", {});
123
+ assert(args.includes("/invariants/private-work") && !args.includes("/invariants/shared-work"), "private PI_ORACLE_SMOKE_MAC_WORK_ROOT alias should win over the shared alias");
124
+
125
+ process.env.PLATFORM_SMOKE_WINDOWS_WORK_ROOT = "C:\\invariants\\shared-work";
126
+ args = buildTargetBaseArgs("windows-native", {});
127
+ assert(args.includes("C:\\invariants\\shared-work"), "shared PLATFORM_SMOKE_WINDOWS_WORK_ROOT override should reach target args");
128
+
129
+ process.env.PI_ORACLE_SMOKE_WINDOWS_NATIVE_WORK_ROOT = "C:\\invariants\\private-work";
130
+ args = buildTargetBaseArgs("windows-native", {});
131
+ assert(args.includes("C:\\invariants\\private-work") && !args.includes("C:\\invariants\\shared-work"), "private PI_ORACLE_SMOKE_WINDOWS_NATIVE_WORK_ROOT alias should win over the shared alias");
132
+ });
133
+ }
134
+
135
+ const DRIVER_SOURCE = [
136
+ "import { writeFileSync } from 'node:fs';",
137
+ "import { pathToFileURL } from 'node:url';",
138
+ "const [targetsPath, artifactRoot, payloadPath] = process.argv.slice(2);",
139
+ "const { runTargetSuites } = await import(pathToFileURL(targetsPath).href);",
140
+ "const config = {",
141
+ " packageName: 'pi-oracle',",
142
+ " artifactRoot,",
143
+ " realSmoke: { authEnvByProvider: { zai: ['ZAI_API_KEY'] } },",
144
+ "};",
145
+ "const result = await runTargetSuites(config, 'ubuntu', ['real-extension']);",
146
+ "writeFileSync(payloadPath, `${JSON.stringify({",
147
+ " ok: result.ok,",
148
+ " results: result.results.map((entry) => ({ ok: entry.ok, suiteDir: entry.suiteDir })),",
149
+ "})}\n`);",
150
+ ].join("\n");
151
+
152
+ function fakeCrabboxScript(argvLogPath) {
153
+ return [
154
+ "#!/bin/sh",
155
+ `printf '%s\\n' "$*" >> ${JSON.stringify(argvLogPath)}`,
156
+ "case \"$1\" in",
157
+ " warmup)",
158
+ " printf '%s\\n' 'leased invariants-fake-lease'",
159
+ " exit 0",
160
+ " ;;",
161
+ " run)",
162
+ " printf '%s\\n' 'Oracle real smoke doctor ok provider: zai'",
163
+ " printf '%s\\n' 'mode=packed extension=./node_modules/pi-oracle'",
164
+ " printf '%s\\n' 'Oracle real smoke passed: invariants-fake-run'",
165
+ " exit 0",
166
+ " ;;",
167
+ " stop)",
168
+ " if [ -n \"${FAKE_STOP_FAILURE:-}\" ]; then",
169
+ " printf '%s\\n' 'stop failed FAKE_STOP_TOKEN=abcdefghijklmnop1234' >&2",
170
+ " exit 3",
171
+ " fi",
172
+ " if [ -n \"${FAKE_STOP_SECRET:-}\" ]; then",
173
+ " printf '%s\\n' 'FAKE_STOP_TOKEN=abcdefghijklmnop1234' >&2",
174
+ " exit 0",
175
+ " fi",
176
+ " printf '%s\\n' 'stopped'",
177
+ " exit 0",
178
+ " ;;",
179
+ "esac",
180
+ "printf '%s\\n' \"unexpected subcommand: $1\" >&2",
181
+ "exit 9",
182
+ "",
183
+ ].join("\n");
184
+ }
185
+
186
+ function runFakeLeaseSuites({ dir, modelAgent }) {
187
+ const runRoot = mkdtempSync(join(dir, "run-"));
188
+ const argvLog = join(runRoot, "argv.log");
189
+ const fakeCrabbox = join(runRoot, "crabbox");
190
+ const artifactRoot = join(runRoot, "artifacts");
191
+ const payloadPath = join(runRoot, "payload.json");
192
+ const driverPath = join(runRoot, "driver.mjs");
193
+ writeFileSync(fakeCrabbox, fakeCrabboxScript(argvLog), { mode: 0o700 });
194
+ chmodSync(fakeCrabbox, 0o700);
195
+ writeFileSync(driverPath, DRIVER_SOURCE);
196
+ const childEnv = { ...process.env };
197
+ childEnv.PI_ORACLE_SMOKE_CRABBOX = fakeCrabbox;
198
+ childEnv.PLATFORM_SMOKE_CRABBOX = fakeCrabbox;
199
+ childEnv.PLATFORM_SMOKE_UBUNTU_IMAGE = "invariants-override-image";
200
+ delete childEnv.PI_ORACLE_SMOKE_UBUNTU_IMAGE;
201
+ delete childEnv.PI_ORACLE_REAL_TEST_PROVIDER;
202
+ delete childEnv.PI_ORACLE_REAL_TEST_MODEL;
203
+ delete childEnv.PI_ORACLE_REAL_TEST_MODEL_AGENT;
204
+ if (modelAgent) childEnv.PI_ORACLE_REAL_TEST_MODEL_AGENT = "1";
205
+ const result = spawnSync(process.execPath, [driverPath, join(repoRoot, "scripts", "platform-smoke", "targets.mjs"), artifactRoot, payloadPath], {
206
+ cwd: repoRoot,
207
+ encoding: "utf8",
208
+ env: childEnv,
209
+ });
210
+ assert.equal(result.status, 0, `fake-lease driver should exit cleanly: ${result.stderr}`);
211
+ return { payload: JSON.parse(readFileSync(payloadPath, "utf8")), argvLog, runRoot };
212
+ }
213
+
214
+ function testLeaseStopFailureSurfacesAndRescansEvidence() {
215
+ if (process.platform === "win32") return; // owner-waived Windows gates; execCrabbox cannot spawn a .cmd fake without a shell
216
+ const dir = mkdtempSync(join(tmpdir(), "pi-oracle-stop-invariants-"));
217
+ const savedStopFailure = process.env.FAKE_STOP_FAILURE;
218
+ const savedStopSecret = process.env.FAKE_STOP_SECRET;
219
+ try {
220
+ // Failing stop with a secret in stop stderr: the failure must surface in
221
+ // the overall result, per-suite assertions, and the appended stop evidence
222
+ // must be rescanned for secrets after it exists.
223
+ process.env.FAKE_STOP_FAILURE = "1";
224
+ const failure = runFakeLeaseSuites({ dir, modelAgent: false });
225
+ assert.equal(failure.payload.ok, false, "a failing lease stop must fail the overall multi-suite result");
226
+ const [suite, leaseCleanup] = failure.payload.results;
227
+ assert.ok(suite, "multi-suite run should record the executed suite result");
228
+ const suiteChecks = JSON.parse(readFileSync(join(suite.suiteDir, "assertions.json"), "utf8")).checks;
229
+ const leaseStop = suiteChecks.find((check) => check.id === "lease-stop");
230
+ assert.ok(leaseStop && leaseStop.ok === false && leaseStop.error === "stop exit 3", `failing stop must be recorded as a lease-stop assertion failure, got ${JSON.stringify(leaseStop)}`);
231
+ const noSecrets = suiteChecks.find((check) => check.id === "no-secrets");
232
+ assert.ok(noSecrets && noSecrets.ok === false, "secrets in appended stop output must fail the suite's no-secrets assertion");
233
+ const violationsPath = join(suite.suiteDir, "redaction-violations.json");
234
+ assert.ok(existsSync(violationsPath), "stop-output secrets must be recorded in redaction-violations.json");
235
+ const violations = JSON.parse(readFileSync(violationsPath, "utf8"));
236
+ assert(violations.some((entry) => String(entry).includes("crabbox.stop.stderr.txt")), `recorded violations must attribute the stop-secret to its artifact, got ${JSON.stringify(violations)}`);
237
+ assert.ok(existsSync(join(suite.suiteDir, "crabbox.stop.exit-code.txt")), "stop artifacts must be appended to the suite directory");
238
+ assert.ok(existsSync(join(suite.suiteDir, "failures.md")), "a failing stop must leave a failures.md trail");
239
+ assert.ok(leaseCleanup && leaseCleanup.ok === false, "a failing stop must surface a lease-cleanup result");
240
+ assert.match(readFileSync(failure.argvLog, "utf8"), /invariants-override-image/, "target base args must deliver the env image override to the actual crabbox wrapper");
241
+
242
+ // Clean stop: lease-stop passes, evidence stays clean, and the suite stays green.
243
+ delete process.env.FAKE_STOP_FAILURE;
244
+ const clean = runFakeLeaseSuites({ dir, modelAgent: true });
245
+ assert.equal(clean.payload.ok, true, `a clean stop must keep the suite green, got ${JSON.stringify(readFileSync(join(clean.payload.results[0].suiteDir, "assertions.json"), "utf8"))}`);
246
+ const cleanSuite = clean.payload.results[0];
247
+ const cleanLeaseStop = JSON.parse(readFileSync(join(cleanSuite.suiteDir, "assertions.json"), "utf8")).checks.find((check) => check.id === "lease-stop");
248
+ assert.ok(cleanLeaseStop && cleanLeaseStop.ok === true, "a clean stop must be recorded as a passing lease-stop assertion");
249
+ assert.ok(!existsSync(join(cleanSuite.suiteDir, "redaction-violations.json")), "clean stop evidence must not create redaction violations");
250
+ const cleanArgv = readFileSync(clean.argvLog, "utf8");
251
+ assert.match(cleanArgv, /--allow-env ZAI_API_KEY/, "provider auth env may only be forwarded on the opt-in model-agent path");
252
+
253
+ const off = runFakeLeaseSuites({ dir, modelAgent: false });
254
+ assert.equal(off.payload.ok, true, "default (non-model-agent) run with a clean stop should stay green");
255
+ const offArgv = readFileSync(off.argvLog, "utf8");
256
+ const offRunLines = offArgv.split("\n").filter((line) => line.startsWith("run "));
257
+ assert.ok(offRunLines.length > 0 && offRunLines.every((line) => !line.includes("--allow-env ZAI_API_KEY")), "default real-extension runs must not forward provider auth env");
258
+
259
+ // Clean stop (exit 0) that still emits a secret: the aggregate result must
260
+ // fail with the persisted evidence, not just rewrite artifacts on disk.
261
+ process.env.FAKE_STOP_SECRET = "1";
262
+ const cleanStopSecret = runFakeLeaseSuites({ dir, modelAgent: false });
263
+ assert.equal(cleanStopSecret.payload.ok, false, "a stop-secret failure must propagate into the overall multi-suite result, not only the persisted artifacts");
264
+ const secretSuite = cleanStopSecret.payload.results[0];
265
+ assert.equal(secretSuite.ok, false, "a stop-secret failure must propagate into the suite result object");
266
+ const secretChecks = JSON.parse(readFileSync(join(secretSuite.suiteDir, "assertions.json"), "utf8")).checks;
267
+ const secretNoSecrets = secretChecks.find((check) => check.id === "no-secrets");
268
+ assert.ok(secretNoSecrets && secretNoSecrets.ok === false, "a clean stop with a secret must still flip the suite's no-secrets assertion");
269
+ const secretLeaseStop = secretChecks.find((check) => check.id === "lease-stop");
270
+ assert.ok(secretLeaseStop && secretLeaseStop.ok === true, "a zero-exit stop must still record a passing lease-stop while its evidence fails the secret scan");
271
+ } finally {
272
+ if (savedStopFailure === undefined) delete process.env.FAKE_STOP_FAILURE;
273
+ else process.env.FAKE_STOP_FAILURE = savedStopFailure;
274
+ if (savedStopSecret === undefined) delete process.env.FAKE_STOP_SECRET;
275
+ else process.env.FAKE_STOP_SECRET = savedStopSecret;
276
+ rmSync(dir, { recursive: true, force: true });
277
+ }
69
278
  }
70
279
 
71
280
  function testPackageExclusion() {
72
281
  const result = spawnSync("npm", ["pack", "--dry-run", "--json"], { cwd: repoRoot, encoding: "utf8", shell: process.platform === "win32" });
73
282
  assert.equal(result.status, 0, `npm pack dry-run failed: ${result.stderr}`);
74
- const files = JSON.parse(result.stdout)[0].files.map((file) => file.path);
283
+ // npm 11 returns an array; npm 12 keys the same records by package name.
284
+ const [pack] = Object.values(JSON.parse(result.stdout));
285
+ const files = pack.files.map((file) => file.path);
75
286
  for (const forbidden of [".artifacts/", ".crabbox/", ".debug/", ".platform-smoke-runs/", ".env", "context.md"]) {
76
287
  assert(!files.some((file) => file === forbidden || file.startsWith(forbidden)), `package should exclude ${forbidden}`);
77
288
  }
@@ -85,39 +296,40 @@ function testSourceSmokeExplicitlyDebugOnly() {
85
296
 
86
297
  function testCanonicalWorkflowConfig() {
87
298
  const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
88
- assert.deepEqual(config.workflows?.everyday?.commands, ["npm run verify:oracle"], "everyday workflow should use the local verification gate");
89
- assert(config.workflows?.platformSensitive?.commands?.includes("npm run smoke:platform:doctor"), "platform-sensitive workflow should start with doctor");
90
- assert(config.workflows?.platformSensitive?.commands?.some((command) => command.includes("--target <target> --suite <suite>")), "platform-sensitive workflow should document focused target/suite runs");
91
- assert.deepEqual(config.workflows?.platformMatrix?.commands, ["npm run smoke:platform:all"], "platform matrix workflow should use the full target matrix");
92
- assert.deepEqual(config.workflows?.release?.commands, ["npm run release:check"], "release workflow should use the full local-plus-platform release gate");
93
299
  assert.equal(config.requiredCrabbox?.minVersion, "0.26.0", "Crabbox baseline should match the documented provider contract");
94
300
  assert.equal(pkg.scripts["smoke:platform:all"], "npm run smoke:platform:doctor && node scripts/platform-smoke.mjs run --target macos,ubuntu,windows-native", "full platform smoke should remain doctor-first and cover all required targets");
95
301
  assert.match(pkg.scripts["release:check"], /npm run verify:oracle && npm run release:proof:chatgpt-presets && npm run smoke:platform:all/, "release check should combine local verification, ChatGPT preset proof, and full platform smoke");
96
- const runnerSource = readFileSync(new URL("./crabbox-runner.mjs", import.meta.url), "utf8");
97
- assert.match(runnerSource, /PLATFORM_SMOKE_CRABBOX/, "runner should honor reusable Crabbox binary override");
98
- assert.match(runnerSource, /PLATFORM_SMOKE_MAC_WORK_ROOT/, "runner should honor reusable macOS work-root override");
99
- assert.match(runnerSource, /PLATFORM_SMOKE_WINDOWS_WORK_ROOT/, "runner should honor reusable Windows work-root override");
100
302
  }
101
303
 
102
304
  function testRealSmokeExpensiveAgentPathsAreOptIn() {
103
305
  const source = readFileSync(new URL("../oracle-real-smoke.mjs", import.meta.url), "utf8");
104
306
  assert.match(source, /runPiLoaderStatus/, "default real smoke should execute a deterministic command through Pi's extension loader");
105
307
  assert.doesNotMatch(source, /tsx\/cli|runDirectOracleSubmit/, "default real smoke must not bypass Pi's loader through checkout tsx");
106
- assert.match(source, /PI_ORACLE_REAL_TEST_MODEL_AGENT/, "real smoke should expose the optional model-agent toggle");
107
- const targetsSource = readFileSync(new URL("./targets.mjs", import.meta.url), "utf8");
108
- assert.match(targetsSource, /PI_ORACLE_REAL_TEST_MODEL_AGENT/, "platform real-extension command should forward the optional model-agent toggle");
109
- assert.match(targetsSource, /realSmokeUsesModelAgent\(\) \? config\.realSmoke\?\.authEnvByProvider/, "platform smoke should allow provider auth env only for the optional model-agent path");
110
- assert.match(source, /truthy\(env\("PI_ORACLE_REAL_TEST_MODEL_AGENT"\)\)/, "model-agent real smoke path should be opt-in");
308
+ assert.match(source, /const modelAgent = truthy\(env\("PI_ORACLE_REAL_TEST_MODEL_AGENT"\)\);/, "real smoke model-agent dispatch must read the env toggle, not default to the expensive path (full dispatch proof lives in the default packed real-smoke assertions)");
111
309
  assert.match(source, /PI_ORACLE_REAL_TEST_NEGATIVE_SYMLINK/, "real smoke should expose the optional negative symlink toggle");
112
310
  assert.match(source, /truthy\(env\("PI_ORACLE_REAL_TEST_NEGATIVE_SYMLINK"\)\)/, "negative symlink real-agent check should be opt-in");
311
+
312
+ // Rendered-command boundary: the model-agent toggle is off by default and
313
+ // only a truthy env value turns it on. (The credential boundary — auth env
314
+ // forwarding only on the opt-in path — is proven at the crabbox transport
315
+ // boundary in testLeaseStopFailureSurfacesAndRescansEvidence.)
316
+ withEnv({ PI_ORACLE_REAL_TEST_MODEL_AGENT: undefined }, () => {
317
+ const off = buildRealExtensionCommand("ubuntu", {});
318
+ assert.match(off, /export PI_ORACLE_REAL_TEST_MODEL_AGENT=''/, "default real smoke must render the model-agent toggle off");
319
+ process.env.PI_ORACLE_REAL_TEST_MODEL_AGENT = "1";
320
+ const on = buildRealExtensionCommand("ubuntu", {});
321
+ assert.match(on, /export PI_ORACLE_REAL_TEST_MODEL_AGENT='1'/, "opt-in model-agent real smoke should render the toggle on");
322
+ });
113
323
  }
114
324
 
115
325
  testHelpTextIncludesTargetsAndExamples();
116
326
  testTargetSelection();
327
+ testEmptyTargetSelectionRejected();
117
328
  testPackedInstallCommandRendering();
118
329
  testRealExtensionPackedInstallRendering();
119
330
  testManifestFailure();
120
- testCleanupFailureInvariant();
331
+ testTargetBaseArgsEnvPrecedence();
332
+ testLeaseStopFailureSurfacesAndRescansEvidence();
121
333
  testPackageExclusion();
122
334
  testSourceSmokeExplicitlyDebugOnly();
123
335
  testCanonicalWorkflowConfig();
@@ -24,11 +24,9 @@ Write-Output "Starting pi-oracle platform-build in $(Get-Location) at $(Get-Date
24
24
  $SourceRoot = (Get-Location).Path
25
25
  $RunRoot = Join-Path $SourceRoot (Join-Path ".platform-smoke-runs" ("platform-build-" + (Get-Date -Format "yyyyMMddTHHmmssZ") + "-" + $PID))
26
26
  $PackDir = Join-Path $RunRoot "pack"
27
- $TestWorkspace = Join-Path $RunRoot "test-workspace"
28
27
  $PiProject = Join-Path $RunRoot "pi-project"
29
- New-Item -ItemType Directory -Force -Path $PackDir, $TestWorkspace, $PiProject | Out-Null
28
+ New-Item -ItemType Directory -Force -Path $PackDir, $PiProject | Out-Null
30
29
  Write-Output "PLATFORM_RUN_ROOT=$RunRoot"
31
- Write-Output "PLATFORM_TEST_WORKSPACE=$TestWorkspace"
32
30
  Write-Output "PLATFORM_PI_PROJECT=$PiProject"
33
31
 
34
32
  $NodeVersion = (& node.exe --version).Trim()
@@ -82,16 +80,6 @@ if ($PackTarball -and (Test-Path -LiteralPath $PackTarball)) {
82
80
  Write-Output "PLATFORM_PACKED_TARBALL=$PackTarball"
83
81
  Set-Content -Path (Join-Path $PackDir "packed-tarball.txt") -Value $PackTarball
84
82
 
85
- Write-Output "=== fixture workspace ==="
86
- Copy-Item -LiteralPath package.json, README.md -Destination $TestWorkspace -ErrorAction SilentlyContinue
87
- Copy-Item -LiteralPath extensions, prompts, docs -Destination $TestWorkspace -Recurse -ErrorAction SilentlyContinue
88
- if ((Test-Path -LiteralPath (Join-Path $TestWorkspace "package.json")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "README.md")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "extensions")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "prompts")) -and (Test-Path -LiteralPath (Join-Path $TestWorkspace "docs"))) {
89
- $FIXTURE_EXIT = 0
90
- } else {
91
- $FIXTURE_EXIT = 1
92
- }
93
- Write-Output "PLATFORM_FIXTURE_EXIT=$FIXTURE_EXIT"
94
-
95
83
  $PiCli = Join-Path (Get-Location) "node_modules\.bin\pi.cmd"
96
84
  if (-not (Test-Path -LiteralPath $PiCli)) { $PiCli = Join-Path (Get-Location) "node_modules\.bin\pi" }
97
85
  if (-not (Test-Path -LiteralPath $PiCli)) {
@@ -160,9 +148,9 @@ Write-Output "PLATFORM_PI_LIST_EXIT=$PI_LIST_EXIT"
160
148
  Write-SectionFile "PI_LIST_STDOUT" $PiListOut
161
149
  Write-SectionFile "PI_LIST_STDERR" $PiListErr
162
150
 
163
- Write-Output "node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"
164
- if ($NODE_VERSION_EXIT -ne 0 -or $CI_EXIT -ne 0 -or $DEPS_EXIT -ne 0 -or $TEST_EXIT -ne 0 -or $PACK_EXIT -ne 0 -or $FIXTURE_EXIT -ne 0 -or $PACKED_NODE_INSTALL_EXIT -ne 0 -or $PI_INSTALL_EXIT -ne 0 -or $PI_LIST_EXIT -ne 0) {
165
- Write-Output "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"
151
+ Write-Output "node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"
152
+ if ($NODE_VERSION_EXIT -ne 0 -or $CI_EXIT -ne 0 -or $DEPS_EXIT -ne 0 -or $TEST_EXIT -ne 0 -or $PACK_EXIT -ne 0 -or $PACKED_NODE_INSTALL_EXIT -ne 0 -or $PI_INSTALL_EXIT -ne 0 -or $PI_LIST_EXIT -ne 0) {
153
+ Write-Output "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"
166
154
  exit 1
167
155
  }
168
156
  Write-Output "PLATFORM_BUILD_OK"
@@ -3,7 +3,7 @@
3
3
  * The suites prove the package builds, packs, installs, loads, and runs through pi's package path.
4
4
  */
5
5
 
6
- import { readFileSync, writeFileSync } from "node:fs";
6
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { resolve } from "node:path";
8
8
 
9
9
  import { runAssertions } from "./assertions.mjs";
@@ -127,11 +127,9 @@ export function buildPlatformBuildCommand(targetName = "ubuntu", packageName = "
127
127
  lines.push('RUN_ROOT=".platform-smoke-runs/platform-build-$(date -u +%Y%m%dT%H%M%SZ)-$$"');
128
128
  lines.push('SOURCE_ROOT="$(pwd)"');
129
129
  lines.push('PACK_DIR="$SOURCE_ROOT/$RUN_ROOT/pack"');
130
- lines.push('TEST_WORKSPACE="$SOURCE_ROOT/$RUN_ROOT/test-workspace"');
131
130
  lines.push('PI_PROJECT="$SOURCE_ROOT/$RUN_ROOT/pi-project"');
132
- lines.push('mkdir -p "$PACK_DIR" "$TEST_WORKSPACE" "$PI_PROJECT"');
131
+ lines.push('mkdir -p "$PACK_DIR" "$PI_PROJECT"');
133
132
  lines.push('echo "PLATFORM_RUN_ROOT=$RUN_ROOT"');
134
- lines.push('echo "PLATFORM_TEST_WORKSPACE=$TEST_WORKSPACE"');
135
133
  lines.push('echo "PLATFORM_PI_PROJECT=$PI_PROJECT"');
136
134
  lines.push("");
137
135
  lines.push('NODE_VERSION=$(node --version)');
@@ -177,15 +175,6 @@ export function buildPlatformBuildCommand(targetName = "ubuntu", packageName = "
177
175
  lines.push('echo "PLATFORM_PACKED_TARBALL=$PACK_TARBALL"');
178
176
  lines.push('printf "%s\\n" "$PACK_TARBALL" > "$PACK_DIR/packed-tarball.txt"');
179
177
  lines.push("");
180
- lines.push('echo "=== fixture workspace ==="');
181
- lines.push('cp package.json README.md "$TEST_WORKSPACE"/ 2>"$PACK_DIR/fixture.stderr.txt"');
182
- lines.push('FIXTURE_COPY_EXIT=$?');
183
- lines.push('cp -R extensions prompts docs "$TEST_WORKSPACE"/ 2>>"$PACK_DIR/fixture.stderr.txt"');
184
- lines.push('TREE_COPY_EXIT=$?');
185
- lines.push('if [ "$FIXTURE_COPY_EXIT" -eq 0 ] && [ "$TREE_COPY_EXIT" -eq 0 ]; then FIXTURE_EXIT=0; else FIXTURE_EXIT=1; fi');
186
- lines.push('cat "$PACK_DIR/fixture.stderr.txt"');
187
- lines.push('echo "PLATFORM_FIXTURE_EXIT=$FIXTURE_EXIT"');
188
- lines.push("");
189
178
  lines.push('echo "=== pi install packed package ==="');
190
179
  lines.push('PI_CLI="$(pwd)/node_modules/.bin/pi"');
191
180
  lines.push('if [ ! -x "$PI_CLI" ]; then PI_CLI="$(command -v pi || true)"; fi');
@@ -205,9 +194,9 @@ export function buildPlatformBuildCommand(targetName = "ubuntu", packageName = "
205
194
  lines.push(...posixSection("PI_LIST_STDOUT", 'cat "$PACK_DIR/pi-list.stdout.txt" 2>/dev/null || true'));
206
195
  lines.push(...posixSection("PI_LIST_STDERR", 'cat "$PACK_DIR/pi-list.stderr.txt" 2>/dev/null || true'));
207
196
  lines.push("");
208
- lines.push('echo "node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"');
209
- lines.push('if [ "$NODE_VERSION_EXIT" -ne 0 ] || [ "$CI_EXIT" -ne 0 ] || [ "$DEPS_EXIT" -ne 0 ] || [ "$TEST_EXIT" -ne 0 ] || [ "$PACK_EXIT" -ne 0 ] || [ "$FIXTURE_EXIT" -ne 0 ] || [ "$PACKED_NODE_INSTALL_EXIT" -ne 0 ] || [ "$PI_INSTALL_EXIT" -ne 0 ] || [ "$PI_LIST_EXIT" -ne 0 ]; then');
210
- lines.push(' echo "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT fixture=$FIXTURE_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"');
197
+ lines.push('echo "node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"');
198
+ lines.push('if [ "$NODE_VERSION_EXIT" -ne 0 ] || [ "$CI_EXIT" -ne 0 ] || [ "$DEPS_EXIT" -ne 0 ] || [ "$TEST_EXIT" -ne 0 ] || [ "$PACK_EXIT" -ne 0 ] || [ "$PACKED_NODE_INSTALL_EXIT" -ne 0 ] || [ "$PI_INSTALL_EXIT" -ne 0 ] || [ "$PI_LIST_EXIT" -ne 0 ]; then');
199
+ lines.push(' echo "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_EXIT packedNodeInstall=$PACKED_NODE_INSTALL_EXIT install=$PI_INSTALL_EXIT list=$PI_LIST_EXIT"');
211
200
  lines.push(' exit 1');
212
201
  lines.push('fi');
213
202
  lines.push('echo "PLATFORM_BUILD_OK"');
@@ -239,7 +228,6 @@ export async function runTargetSuite(config, targetName, suiteName, leaseSession
239
228
  const startedAt = Date.now();
240
229
  console.log(` executing ${suiteName} on ${targetName}...`);
241
230
  const result = await runOnLease(config, targetName, warmup.leaseId, command, {
242
- shell: true,
243
231
  timeout: suiteName === "real-extension" ? 900_000 : 900_000,
244
232
  sync: leaseSession?.sync,
245
233
  allowEnvNames: suiteName === "real-extension" ? realSmokeAllowedEnvNames(config) : undefined,
@@ -281,11 +269,10 @@ export async function runTargetSuite(config, targetName, suiteName, leaseSession
281
269
  { id: "platform-dependencies", fn: () => /PLATFORM_DEPS_EXIT=0/.test(stdout) },
282
270
  { id: "platform-verification", fn: () => /PLATFORM_NPM_TEST_EXIT=0/.test(stdout) },
283
271
  { id: "npm-pack", fn: () => /PLATFORM_NPM_PACK_EXIT=0/.test(stdout) && /PLATFORM_PACKED_TARBALL=\S+/.test(stdout) },
284
- { id: "fixture-workspace", fn: () => /PLATFORM_FIXTURE_EXIT=0/.test(stdout) },
285
272
  { id: "packed-node-install", fn: () => /PLATFORM_PACKED_NODE_INSTALL_EXIT=0/.test(stdout) },
286
273
  { id: "pi-install", fn: () => /PLATFORM_PI_INSTALL_EXIT=0/.test(stdout) },
287
274
  { id: "pi-list", fn: () => /PLATFORM_PI_LIST_EXIT=0/.test(stdout) && listOutput.includes(packageName) && packageInstallPattern.test(listOutput) },
288
- { id: "no-source-extension-path", fn: () => !/\bpi\s+(?:-e|--extension)\s+\./.test(stdout) },
275
+ { id: "no-source-extension-path", fn: () => !/\bpi\s+(?:-e|--extension)\s+\./.test(command), error: "platform-build must execute through the packed install path, not a source extension" },
289
276
  { id: "no-secrets", fn: () => violations.length === 0, error: "redaction violations found" },
290
277
  ];
291
278
  expectedFiles = [
@@ -302,7 +289,7 @@ export async function runTargetSuite(config, targetName, suiteName, leaseSession
302
289
  { id: "real-smoke-doctor", fn: () => stdout.includes("Oracle real smoke doctor") && stdout.includes(`provider: ${provider}`) },
303
290
  { id: "real-smoke-marker", fn: () => stdout.includes("Oracle real smoke passed:") },
304
291
  { id: "real-smoke-packed-install", fn: () => stdout.includes("mode=packed") && stdout.includes("extension=./node_modules/pi-oracle") },
305
- { id: "real-smoke-no-source-extension", fn: () => !stdout.includes("extensions/oracle/index.ts") && !/\bpi\s+(?:-e|--extension)\s+extensions\/oracle/.test(stdout) },
292
+ { id: "real-smoke-no-source-extension", fn: () => !command.includes("extensions/oracle/index.ts") && !/\bpi\s+(?:-e|--extension)\s+extensions\/oracle/.test(command) },
306
293
  { id: "no-secrets", fn: () => violations.length === 0, error: "redaction violations found" },
307
294
  ];
308
295
  expectedFiles = [
@@ -338,7 +325,13 @@ export async function runTargetSuites(config, targetName, suiteNames) {
338
325
  }
339
326
  } finally {
340
327
  stopResult = await stopLease(config, targetName, warmup.leaseId);
341
- for (const result of results) recordStopResultOnSuite(result.suiteDir, stopResult);
328
+ for (const result of results) {
329
+ const stopAssertions = recordStopResultOnSuite(result.suiteDir, stopResult);
330
+ if (stopAssertions) {
331
+ result.ok = Boolean(result.ok && stopAssertions.ok);
332
+ result.assertions = stopAssertions;
333
+ }
334
+ }
342
335
  }
343
336
  if (stopResult?.code !== 0) results.push(createStopFailureResult(config, targetName, warmup.leaseId, stopResult));
344
337
  return { ok: results.every((result) => result.ok), results };
@@ -351,15 +344,33 @@ function writeStopArtifacts(suiteDir, stopResult) {
351
344
  }
352
345
 
353
346
  function recordStopResultOnSuite(suiteDir, stopResult) {
354
- if (!suiteDir || !stopResult) return;
347
+ if (!suiteDir || !stopResult) return undefined;
355
348
  writeStopArtifacts(suiteDir, stopResult);
349
+ // Stop output is appended after the suite ran; rescan the final evidence so
350
+ // secrets in stop stdout/stderr cannot slip past the no-secrets gate.
351
+ const violationsPath = resolve(suiteDir, "redaction-violations.json");
352
+ const stopViolations = [
353
+ ...scanForSecrets(stopResult.stdout ?? "").map((violation) => `crabbox.stop.stdout.txt: ${violation}`),
354
+ ...scanForSecrets(stopResult.stderr ?? "").map((violation) => `crabbox.stop.stderr.txt: ${violation}`),
355
+ ];
356
+ if (stopViolations.length > 0) {
357
+ const previousViolations = existsSync(violationsPath) ? JSON.parse(readFileSync(violationsPath, "utf8")) : [];
358
+ writeFileSync(violationsPath, JSON.stringify([...new Set([...previousViolations, ...stopViolations])], null, 2));
359
+ }
356
360
  const assertionsPath = resolve(suiteDir, "assertions.json");
357
361
  const summaryPath = resolve(suiteDir, "summary.json");
358
362
  const manifestPath = resolve(suiteDir, "artifact-manifest.json");
359
363
  const assertions = JSON.parse(readFileSync(assertionsPath, "utf8"));
360
364
  const summary = JSON.parse(readFileSync(summaryPath, "utf8"));
361
365
  const stopCheck = { id: "lease-stop", ok: stopResult.code === 0, ...(stopResult.code === 0 ? {} : { error: `stop exit ${stopResult.code}` }) };
362
- assertions.checks = [...(assertions.checks ?? []).filter((check) => check.id !== "lease-stop"), stopCheck];
366
+ const checks = (assertions.checks ?? []).filter((check) => check.id !== "lease-stop");
367
+ if (stopViolations.length > 0) {
368
+ const noSecretsIndex = checks.findIndex((check) => check.id === "no-secrets");
369
+ const failingNoSecrets = { id: "no-secrets", ok: false, error: "redaction violations found" };
370
+ if (noSecretsIndex === -1) checks.push(failingNoSecrets);
371
+ else checks[noSecretsIndex] = failingNoSecrets;
372
+ }
373
+ assertions.checks = [...checks, stopCheck];
363
374
  assertions.ok = assertions.checks.every((check) => check.ok);
364
375
  writeFileSync(assertionsPath, JSON.stringify({ ...assertions, writtenAt: new Date().toISOString() }, null, 2));
365
376
  if (!assertions.ok) {
@@ -370,6 +381,7 @@ function recordStopResultOnSuite(suiteDir, stopResult) {
370
381
  const previousManifest = JSON.parse(readFileSync(manifestPath, "utf8"));
371
382
  const expected = [...new Set([...(previousManifest.expected ?? []), "crabbox.stop.stdout.txt", "crabbox.stop.stderr.txt", "crabbox.stop.exit-code.txt", "artifact-manifest.json"])]
372
383
  writeManifest(suiteDir, expected);
384
+ return assertions;
373
385
  }
374
386
 
375
387
  function finalizeSuiteArtifacts(suiteDir, checks, summary, expectedFiles) {
@@ -428,7 +440,3 @@ function createStopFailureResult(config, targetName, leaseId, stopResult) {
428
440
  );
429
441
  return { ok: false, suiteDir, assertions };
430
442
  }
431
-
432
- export function readSuiteSummary(suiteDir) {
433
- return JSON.parse(readFileSync(resolve(suiteDir, "summary.json"), "utf8"));
434
- }
@@ -131,6 +131,8 @@ async function main() {
131
131
  if (args.command === "run") {
132
132
  const targets = args.target ? splitCsv(args.target) : config.requiredTargets;
133
133
  const suites = args.suite ? [args.suite] : config.requiredSuites;
134
+ if (targets.length === 0) throw new Error("--target did not resolve to any targets");
135
+ if (suites.length === 0) throw new Error("--suite did not resolve to any suites");
134
136
  validateSelection(targets, suites);
135
137
  const results = await Promise.all(targets.map(async (target) => {
136
138
  console.log(`\n=== Target: ${target} ===`);
@@ -142,8 +144,6 @@ async function main() {
142
144
  }
143
145
  return;
144
146
  }
145
-
146
- throw new Error(`unknown command: ${args.command}`);
147
147
  }
148
148
 
149
149
  main().catch((error) => {