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.
- package/CHANGELOG.md +40 -0
- package/README.md +49 -385
- package/docs/ORACLE_DESIGN.md +1 -1
- package/docs/development.md +99 -0
- package/docs/platform-smoke.md +18 -7
- package/docs/reference.md +338 -0
- package/extensions/oracle/index.ts +1 -0
- package/extensions/oracle/lib/poller.ts +3 -8
- package/extensions/oracle/lib/queue.ts +2 -4
- package/extensions/oracle/shared/browser-profile-helpers.d.mts +1 -0
- package/extensions/oracle/shared/browser-profile-helpers.mjs +32 -0
- package/extensions/oracle/worker/auth-bootstrap.mjs +35 -6
- package/extensions/oracle/worker/chatgpt-ui-helpers.d.mts +1 -0
- package/extensions/oracle/worker/chatgpt-ui-helpers.mjs +18 -13
- package/extensions/oracle/worker/chromium-cookie-source.mjs +50 -6
- package/extensions/oracle/worker/linux-chromium-cookie-source.d.mts +6 -0
- package/extensions/oracle/worker/linux-chromium-cookie-source.mjs +98 -0
- package/extensions/oracle/worker/run-job.mjs +34 -34
- package/package.json +12 -11
- package/platform-smoke.config.mjs +1 -22
- package/scripts/oracle-auth.test.mjs +35 -0
- package/scripts/oracle-chatgpt-preset-proof.mjs +50 -21
- package/scripts/oracle-cookie-import.test.mjs +204 -0
- package/scripts/oracle-model.test.mjs +24 -17
- package/scripts/oracle-real-smoke.mjs +30 -9
- package/scripts/platform-smoke/Dockerfile.ubuntu +3 -2
- package/scripts/platform-smoke/crabbox-runner.mjs +18 -6
- package/scripts/platform-smoke/doctor.mjs +58 -41
- package/scripts/platform-smoke/invariants.mjs +254 -26
- package/scripts/platform-smoke/platform-build-windows.ps1 +21 -17
- package/scripts/platform-smoke/targets.mjs +59 -30
- 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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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, $
|
|
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
|
|
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 $
|
|
165
|
-
Write-Output "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_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
|
-
|
|
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" "$
|
|
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
|
|
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 ] || [ "$
|
|
210
|
-
lines.push(' echo "PLATFORM_BUILD_FAILED: node=$NODE_VERSION_EXIT ci=$CI_EXIT deps=$DEPS_EXIT test=$TEST_EXIT pack=$PACK_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: "
|
|
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(
|
|
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: "
|
|
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)
|
|
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
|
-
|
|
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
|
-
}
|