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.
- package/CHANGELOG.md +11 -0
- package/README.md +49 -385
- package/docs/ORACLE_DESIGN.md +1 -1
- package/docs/development.md +99 -0
- package/docs/platform-smoke.md +3 -3
- package/docs/reference.md +331 -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/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 +10 -5
- 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 +19 -12
- package/scripts/oracle-cookie-import.test.mjs +204 -0
- package/scripts/oracle-model.test.mjs +5 -1
- package/scripts/oracle-real-smoke.mjs +17 -7
- package/scripts/platform-smoke/Dockerfile.ubuntu +3 -2
- package/scripts/platform-smoke/crabbox-runner.mjs +3 -5
- package/scripts/platform-smoke/doctor.mjs +9 -24
- package/scripts/platform-smoke/invariants.mjs +234 -22
- package/scripts/platform-smoke/platform-build-windows.ps1 +4 -16
- package/scripts/platform-smoke/targets.mjs +35 -27
- 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
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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, $
|
|
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
|
|
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
|
|
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" "$
|
|
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
|
|
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
|
|
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(
|
|
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: () => !
|
|
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)
|
|
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
|
-
|
|
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) => {
|