mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 02:44:50 +01:00
fix: fail closed on unusable Codex auth during sandbox auth merge
When reusing a Codex sandbox, the workspace-restore step now performs an auth-merge check before adopting the sandbox's auth.json. The merge fails closed when: - host or sandbox Codex auth is unusable (missing token or account) - the subscription payload shapes differ between host and sandbox - subscription account identities do not match Additional hardening: node absence on PATH during the embedded decision script now exits non-zero instead of silently falling through. Co-change comments link parseAuth (the embedded script) to hasUsableAuthPayload (codex-home.ts) so a future auth-format change cannot silently diverge between the two implementations. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
98df214917
3422 files changed
+1614966
No files matched your search
@@ -0,0 +1,131 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
resolveConcurrency,
|
||||
runWithConcurrency,
|
||||
} from "../build-standalone-public-packages.mjs";
|
||||
|
||||
function deferred() {
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
test("runs every item and preserves result order", async () => {
|
||||
const items = [1, 2, 3, 4, 5];
|
||||
const results = await runWithConcurrency(items, 2, async (value) => value * 10);
|
||||
assert.deepEqual(results, [10, 20, 30, 40, 50]);
|
||||
});
|
||||
|
||||
test("never exceeds the concurrency limit", async () => {
|
||||
const items = Array.from({ length: 8 }, (_, index) => index);
|
||||
const limit = 3;
|
||||
let active = 0;
|
||||
let peak = 0;
|
||||
const gates = items.map(() => deferred());
|
||||
|
||||
const run = runWithConcurrency(items, limit, async (value) => {
|
||||
active += 1;
|
||||
peak = Math.max(peak, active);
|
||||
assert.ok(active <= limit, `active ${active} exceeded limit ${limit}`);
|
||||
await gates[value].promise;
|
||||
active -= 1;
|
||||
return value;
|
||||
});
|
||||
|
||||
// Release gates progressively; the pool must keep at most `limit` in flight.
|
||||
for (const gate of gates) {
|
||||
await Promise.resolve();
|
||||
gate.resolve();
|
||||
}
|
||||
|
||||
await run;
|
||||
assert.equal(peak, limit);
|
||||
});
|
||||
|
||||
test("aggregates failures and still runs remaining items", async () => {
|
||||
const items = [0, 1, 2, 3];
|
||||
const processed = [];
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
runWithConcurrency(items, 2, async (value) => {
|
||||
processed.push(value);
|
||||
if (value === 1) {
|
||||
throw new Error(`boom-${value}`);
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
(error) => {
|
||||
assert.match(error.message, /1 standalone package build\(s\) failed/);
|
||||
assert.equal(error.failures.length, 1);
|
||||
assert.equal(error.failures[0].index, 1);
|
||||
assert.match(error.failures[0].error.message, /boom-1/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
// A single failure must not abort the rest of the queue.
|
||||
assert.deepEqual(processed.sort((a, b) => a - b), [0, 1, 2, 3]);
|
||||
});
|
||||
|
||||
test("reports failures sorted by original index", async () => {
|
||||
const items = [0, 1, 2, 3, 4];
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
runWithConcurrency(items, 5, async (value) => {
|
||||
if (value === 3 || value === 1) {
|
||||
throw new Error(`fail-${value}`);
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
(error) => {
|
||||
assert.deepEqual(
|
||||
error.failures.map(({ index }) => index),
|
||||
[1, 3],
|
||||
);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveConcurrency honors a valid env override, capped by package count", () => {
|
||||
const previous = process.env.STANDALONE_BUILD_CONCURRENCY;
|
||||
try {
|
||||
process.env.STANDALONE_BUILD_CONCURRENCY = "2";
|
||||
assert.equal(resolveConcurrency(7), 2);
|
||||
assert.equal(resolveConcurrency(1), 1);
|
||||
assert.equal(resolveConcurrency(0), 1);
|
||||
|
||||
process.env.STANDALONE_BUILD_CONCURRENCY = "0";
|
||||
assert.ok(resolveConcurrency(7) >= 1);
|
||||
|
||||
process.env.STANDALONE_BUILD_CONCURRENCY = "not-a-number";
|
||||
assert.ok(resolveConcurrency(7) >= 1);
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.STANDALONE_BUILD_CONCURRENCY;
|
||||
} else {
|
||||
process.env.STANDALONE_BUILD_CONCURRENCY = previous;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveConcurrency never returns more than the package count", () => {
|
||||
const previous = process.env.STANDALONE_BUILD_CONCURRENCY;
|
||||
try {
|
||||
delete process.env.STANDALONE_BUILD_CONCURRENCY;
|
||||
assert.equal(resolveConcurrency(0), 1);
|
||||
assert.ok(resolveConcurrency(3) <= 3);
|
||||
} finally {
|
||||
if (previous !== undefined) {
|
||||
process.env.STANDALONE_BUILD_CONCURRENCY = previous;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const script = path.join(repoRoot, "scripts", "run-vitest-stable.mjs");
|
||||
|
||||
function dryRun(args) {
|
||||
const result = spawnSync(process.execPath, [script, ...args, "--dry-run"], {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function dryRunJson(args) {
|
||||
const result = dryRun(args);
|
||||
assert.equal(result.status, 0, `expected success for ${args.join(" ")}: ${result.stderr}`);
|
||||
return JSON.parse(result.stdout);
|
||||
}
|
||||
|
||||
const SHARD_COUNT = 3;
|
||||
|
||||
test("the general-server shards form a complete, non-overlapping partition", () => {
|
||||
const shards = Array.from({ length: SHARD_COUNT }, (_, index) =>
|
||||
dryRunJson(["--mode", "general", "--group", "general-server", "--shard-index", String(index), "--shard-count", String(SHARD_COUNT)]),
|
||||
);
|
||||
|
||||
const total = shards[0].generalServerSuiteCount;
|
||||
assert.ok(total > 0, "expected a non-empty general-server suite set");
|
||||
|
||||
const seen = new Set();
|
||||
let selectedTotal = 0;
|
||||
for (const shard of shards) {
|
||||
assert.equal(shard.generalServerSuiteCount, total, "suite count must be stable across shards");
|
||||
for (const file of shard.selectedGeneralServerSuites) {
|
||||
assert.ok(!seen.has(file), `suite assigned to more than one shard: ${file}`);
|
||||
seen.add(file);
|
||||
selectedTotal += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Every suite runs exactly once: union covers the whole set with no overlap.
|
||||
assert.equal(selectedTotal, total, "every suite must be selected exactly once");
|
||||
assert.equal(seen.size, total, "union of shards must cover the whole suite set");
|
||||
});
|
||||
|
||||
test("a route/authz suite never leaks into the general-server shards", () => {
|
||||
const shard = dryRunJson(["--mode", "general", "--group", "general-server", "--shard-index", "0", "--shard-count", SHARD_COUNT.toString()]);
|
||||
for (const file of shard.selectedGeneralServerSuites) {
|
||||
assert.ok(
|
||||
!/[^/]*(?:route|routes|authz)[^/]*\.test\.ts$/.test(file),
|
||||
`route/authz suite must stay in the serialized lane, not general-server: ${file}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("shard flags are rejected for the parallel workspace groups", () => {
|
||||
const result = dryRun(["--mode", "general", "--group", "general-workspaces-a", "--shard-index", "0", "--shard-count", "3"]);
|
||||
assert.notEqual(result.status, 0, "workspace groups must not accept shard flags");
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import test from "node:test";
|
||||
|
||||
const script = new URL("../serve-storybook-static.mjs", import.meta.url).pathname;
|
||||
|
||||
test("--port followed by another flag is not parsed as the port value", () => {
|
||||
const result = spawnSync(process.execPath, [script, "--port", "--unused-flag"], {
|
||||
env: { ...process.env, PORT: "65536" },
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
assert.equal(result.status, 1);
|
||||
assert.match(result.stderr, /Invalid Storybook static server port: 65536/);
|
||||
assert.doesNotMatch(result.stderr, /--unused-flag/);
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import test from "node:test";
|
||||
|
||||
const script = new URL("../storybook-visual-baseline.mjs", import.meta.url).pathname;
|
||||
|
||||
test("downloads and verifies a checksum-pinned local baseline archive", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "storybook-visual-baseline-test-"));
|
||||
try {
|
||||
const source = join(root, "source");
|
||||
const cache = join(root, "cache");
|
||||
const snapshots = join(root, "snapshots");
|
||||
const manifest = join(root, "manifest.json");
|
||||
mkdirSync(source, { recursive: true });
|
||||
writeFileSync(join(source, "one.png"), "png-one");
|
||||
writeFileSync(join(source, "two.png"), "png-two");
|
||||
const archive = join(root, "snapshots.tgz");
|
||||
run("tar", ["--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", "-czf", archive, "-C", source, "."]);
|
||||
const sha256 = createHash("sha256").update(readFileSync(archive)).digest("hex");
|
||||
const byteSize = statSync(archive).size;
|
||||
writeFileSync(
|
||||
manifest,
|
||||
JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
baselineId: "test-baseline",
|
||||
snapshotCount: 2,
|
||||
archive: {
|
||||
url: `file://${archive}`,
|
||||
sha256,
|
||||
byteSize,
|
||||
},
|
||||
environment: {
|
||||
browser: "chromium",
|
||||
viewport: "1200x800",
|
||||
deviceScaleFactor: 1,
|
||||
platform: "test",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
STORYBOOK_VISUAL_BASELINE_MANIFEST: manifest,
|
||||
STORYBOOK_VISUAL_BASELINE_CACHE_DIR: cache,
|
||||
STORYBOOK_VISUAL_SNAPSHOT_DIR: snapshots,
|
||||
};
|
||||
const download = spawnSync(process.execPath, [script, "download"], { env, encoding: "utf8" });
|
||||
assert.equal(download.status, 0, download.stderr);
|
||||
const verify = spawnSync(process.execPath, [script, "verify"], { env, encoding: "utf8" });
|
||||
assert.equal(verify.status, 0, verify.stderr);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("verify fails closed on snapshot count mismatch", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "storybook-visual-baseline-test-"));
|
||||
try {
|
||||
const snapshots = join(root, "snapshots");
|
||||
const manifest = join(root, "manifest.json");
|
||||
mkdirSync(snapshots, { recursive: true });
|
||||
writeFileSync(join(snapshots, "one.png"), "png-one");
|
||||
const archive = join(root, "snapshots.tgz");
|
||||
run("tar", ["--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", "-czf", archive, "-C", snapshots, "."]);
|
||||
const sha256 = createHash("sha256").update(readFileSync(archive)).digest("hex");
|
||||
const manifestBody = {
|
||||
version: 1,
|
||||
baselineId: "test-baseline",
|
||||
snapshotCount: 1,
|
||||
archive: {
|
||||
url: `file://${archive}`,
|
||||
sha256,
|
||||
byteSize: statSync(archive).size,
|
||||
},
|
||||
};
|
||||
writeFileSync(manifest, JSON.stringify(manifestBody));
|
||||
const env = {
|
||||
...process.env,
|
||||
STORYBOOK_VISUAL_BASELINE_MANIFEST: manifest,
|
||||
STORYBOOK_VISUAL_BASELINE_CACHE_DIR: root,
|
||||
STORYBOOK_VISUAL_SNAPSHOT_DIR: snapshots,
|
||||
};
|
||||
const download = spawnSync(process.execPath, [script, "download"], { env, encoding: "utf8" });
|
||||
assert.equal(download.status, 0, download.stderr);
|
||||
writeFileSync(manifest, JSON.stringify({ ...manifestBody, snapshotCount: 2 }));
|
||||
unlinkSync(join(snapshots, "one.png"));
|
||||
const result = spawnSync(process.execPath, [script, "verify"], { env, encoding: "utf8" });
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /snapshot count mismatch/i);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function run(cmd, args) {
|
||||
const result = spawnSync(cmd, args, { stdio: "pipe", encoding: "utf8" });
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
}
|
||||
Reference in new issue
Block a user