pi-oracle 0.7.21 → 0.7.23

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