Reconcile sandbox runtime hardening with current recovery

Combine task-scoped workspace adoption with chat isolation, bounded shutdown draining, and provider identity handling. Preserve per-run capability rotation evidence and the new master warm-session transition coverage.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-11 14:15:45 -05:00
commit cd008e3444
1324 files changed
+1488084 -52191

No files matched your search

+8 -2
View File
@@ -222,6 +222,7 @@ test("the trusted PR workflow limits full CI to merge-relevant stack layers", ()
for (const jobId of [
"typecheck_release_registry",
"general_tests",
"verify_paperclip_runner",
"build",
"verify_serialized_server",
"canary_dry_run",
@@ -243,11 +244,16 @@ test("the trusted PR workflow limits full CI to merge-relevant stack layers", ()
const verify = jobs.get("verify");
assert.match(
verify,
/^ {4}needs: \[gate, policy, typecheck_release_registry, general_tests, build, docker_context_integrity\]$/m,
/^ {4}needs: \[gate, policy, typecheck_release_registry, general_tests, verify_paperclip_runner, build, docker_context_integrity\]$/m,
);
assert.match(verify, /POLICY_RESULT: \$\{\{ needs\.policy\.result \}\}/);
assert.match(verify, /test "\$TYPECHECK_RELEASE_REGISTRY_RESULT" = "skipped"/);
assert.match(verify, /test "\$GENERAL_TESTS_RESULT" = "skipped"/);
// Runner verification must participate in the legacy aggregate required
// check, or a runner regression could be merged while `verify` succeeds.
assert.match(verify, /RUNNER_VERIFICATION_RESULT: \$\{\{ needs\.verify_paperclip_runner\.result \}\}/);
assert.match(verify, /test "\$RUNNER_VERIFICATION_RESULT" = "success"/);
assert.match(verify, /test "\$RUNNER_VERIFICATION_RESULT" = "skipped"/);
assert.match(verify, /test "\$BUILD_RESULT" = "skipped"/);
// Both halves of the docker-context lane's gating: the result must be
// wired into the aggregate's env AND asserted successful on full CI —
@@ -327,7 +333,7 @@ test("the trusted PR workflow regenerates stale stacked lockfiles", () => {
const restoreSteps = workflow.match(
/- name: Restore regenerated PR lockfile \(if policy uploaded one\)\n if: needs\.policy\.outputs\.lockfile_regenerated == '1'/g,
) ?? [];
assert.equal(restoreSteps.length, 6, "every downstream install job must restore a required regenerated artifact");
assert.equal(restoreSteps.length, 7, "every downstream install job must restore a required regenerated artifact");
assert.doesNotMatch(
workflow,
/- name: Restore regenerated PR lockfile \(if policy uploaded one\)[\s\S]{0,220}continue-on-error:/,
@@ -16,7 +16,7 @@ const testPath = [path.dirname(process.execPath), "/usr/bin", "/bin"].join(":");
const cleanupDirs = [];
function makeTempDir(prefix) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), prefix)));
cleanupDirs.push(dir);
return dir;
}
@@ -102,8 +102,9 @@ process.exit(0);
return baseCwd;
}
function runProvision(baseCwd, { pathPrefix } = {}) {
const worktreeCwd = makeTempDir("paperclip-provision-worktree-");
function runProvision(baseCwd, { pathPrefix, setupWorktree, existingWorktree } = {}) {
const worktreeCwd = existingWorktree ?? makeTempDir("paperclip-provision-worktree-");
setupWorktree?.(worktreeCwd);
const worktreesHome = makeTempDir("paperclip-provision-home-");
const paperclipHome = makeInstanceHome();
const result = spawnSync("bash", [script], {
@@ -524,3 +525,53 @@ test("every pnpm install call site silences DEP0169 without overwriting NODE_OPT
}
}
});
for (const failure of ["ERR_PNPM_LOCKFILE_CONFIG_MISMATCH", "ERR_PNPM_OUTDATED_LOCKFILE", "ENOTFOUND", "retry-fails"]) {
test(`dependency provisioning preserves failures and bounds recovery: ${failure}`, () => {
const baseCwd = makeBaseWorkspace({ helpExit: 0, initExit: 0 });
const bin = makeTempDir("paperclip-fake-pnpm-");
fs.writeFileSync(path.join(bin, "pnpm"), `#!/bin/sh
printf '%s\\n' "$*" >> pnpm-calls
case "$*" in
*--frozen-lockfile*) echo '${failure === "retry-fails" ? "ERR_PNPM_LOCKFILE_CONFIG_MISMATCH" : failure}' >&2; exit 42 ;;
*) ${failure === "retry-fails" ? "exit 43" : "mkdir -p node_modules; exit 0"} ;;
esac
`, { mode: 0o700 });
const { result, worktreeCwd } = runProvision(baseCwd, {
pathPrefix: bin,
setupWorktree(root) {
fs.writeFileSync(path.join(root, "package.json"), "{}\n");
fs.writeFileSync(path.join(root, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
},
});
const recovers = failure.startsWith("ERR_PNPM_");
assert.equal(result.status, recovers ? 0 : failure === "ENOTFOUND" ? 42 : 1, result.stderr);
assert.equal(fs.existsSync(path.join(worktreeCwd, ".paperclip/pnpm-install-fingerprint")), recovers);
const calls = fs.readFileSync(path.join(worktreeCwd, "pnpm-calls"), "utf8").trim().split("\n").filter((call) => call.startsWith("install "));
assert.equal(calls.length, failure === "ENOTFOUND" ? 1 : 2);
if (calls.length === 2) assert.match(calls[1], /--no-frozen-lockfile/);
});
}
test("patch content changes invalidate an otherwise matching install fingerprint", () => {
const baseCwd = makeBaseWorkspace({ helpExit: 0, initExit: 0 });
const bin = makeTempDir("paperclip-patch-pnpm-");
fs.writeFileSync(path.join(bin, "pnpm"), '#!/bin/sh\ncase "$1" in install) echo install >> pnpm-calls; mkdir -p node_modules cli/node_modules ;; esac\n', { mode: 0o700 });
const first = runProvision(baseCwd, { pathPrefix: bin, setupWorktree(root) {
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ pnpm: { patchedDependencies: { "dependency@1": "patches/dependency.diff" } } }));
fs.writeFileSync(path.join(root, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
fs.mkdirSync(path.join(root, "patches"));
fs.writeFileSync(path.join(root, "patches/dependency.diff"), "first patch");
} });
assert.equal(first.result.status, 0, first.result.stderr);
const options = { pathPrefix: bin, existingWorktree: first.worktreeCwd };
assert.equal(runProvision(baseCwd, options).result.status, 0);
const callsPath = path.join(first.worktreeCwd, "pnpm-calls");
assert.equal(fs.readFileSync(callsPath, "utf8"), "install\n");
fs.writeFileSync(path.join(first.worktreeCwd, "unrelated.patch"), "unrelated change");
assert.equal(runProvision(baseCwd, options).result.status, 0);
assert.equal(fs.readFileSync(callsPath, "utf8"), "install\n");
fs.writeFileSync(path.join(first.worktreeCwd, "patches/dependency.diff"), "changed patch");
assert.equal(runProvision(baseCwd, options).result.status, 0);
assert.equal(fs.readFileSync(callsPath, "utf8"), "install\ninstall\n");
});
@@ -14,13 +14,37 @@ function readWorkflow(name) {
return readFileSync(path.join(repoRoot, ".github/workflows", name), "utf8");
}
test("release workflow delegates stable and canary verification to the reusable workflow", () => {
const releaseWorkflow = readWorkflow("release.yml");
test("chaos verification isolates callers that verify the same source commit", () => {
const chaosWorkflow = readWorkflow("runner-chaos-evals.yml");
const group = chaosWorkflow.match(/^ group: (.+)$/m)?.[1];
assert.ok(group, "chaos verification must define its concurrency group");
assert.match(
releaseWorkflow,
/verify_canary:\n\s+if: github\.event_name == 'push'\n\s+uses: \.\/\.github\/workflows\/release-verify\.yml\n\s+with:\n\s+ref: \$\{\{ github\.sha \}\}/,
);
// GitHub supplies the top-level caller's workflow name to reusable calls.
const resolveGroup = (caller, ref) => group
.replaceAll("${{ github.workflow }}", readWorkflow(caller).match(/^name: (.+)$/m)[1])
.replaceAll("${{ inputs.ref || github.ref }}", ref)
.toLowerCase();
const sha = "a".repeat(40);
const callers = ["cloud-readiness.yml", "release.yml", "runner-chaos-evals.yml"];
const groups = callers.map((caller) => resolveGroup(caller, sha));
assert.equal(new Set(groups).size, callers.length,
"Cloud readiness, Release, and standalone evals must not cancel each other");
assert.ok(groups.every((value) => !value.includes("${{")), "resolve every group input");
assert.notEqual(resolveGroup("cloud-readiness.yml", sha),
resolveGroup("cloud-readiness.yml", "b".repeat(40)), "different sources remain independent");
assert.match(chaosWorkflow, /cancel-in-progress: true/);
});
test("canary reuses exact-source proof while stable keeps full verification", () => {
const releaseWorkflow = readWorkflow("release.yml");
const canary = releaseWorkflow.split(" verify_canary:\n")[1].split("\n publish_canary:")[0];
assert.match(canary, /github\.repository == 'paperclipai\/paperclip' && github\.event_name == 'push' && github\.ref == 'refs\/heads\/master'/);
assert.match(canary, /actions: read/);
assert.match(canary, /ref: \$\{\{ github\.sha \}\}/);
assert.match(canary, /SOURCE_SHA: \$\{\{ github\.sha \}\}/);
assert.match(canary, /run: node scripts\/cloud-source-verification\.mjs "\$SOURCE_SHA"/);
assert.doesNotMatch(canary, /release-verify\.yml|continue-on-error|always\(\)/);
assert.match(releaseWorkflow, /publish_canary:\n\s+if: github\.event_name == 'push'\n\s+needs: verify_canary/);
// The stable lane is gated on the stable channel since the nightly lane
// was added; a `needs:` line (for example a preflight job) may sit between
// the gate and the delegation.
@@ -36,6 +60,17 @@ test("release workflow delegates stable and canary verification to the reusable
);
});
test("source proof requires every source check and does not wait on image publication", () => {
const readiness = readWorkflow("cloud-readiness.yml");
const proof = readiness.split(" source_verified:\n")[1].split("\n ready:")[0];
assert.match(proof, /name: Cloud source verified v1/);
assert.match(proof, /needs: \[verify\]/);
assert.match(proof, /node --test scripts\/cloud-source-verification.test.mjs/);
assert.match(proof, /SOURCE_SHA: \$\{\{ github\.sha \}\}/);
assert.doesNotMatch(proof, /always\(\)|continue-on-error|needs:.*(?:image|artifacts)/);
assert.match(readiness.split(" ready:\n")[1], /needs: \[verify, image, artifacts\]/);
});
test("onboard smoke container binds beyond loopback so the mapped port is reachable", () => {
const dockerfile = readFileSync(
path.join(repoRoot, "docker/Dockerfile.onboard-smoke"),
@@ -202,28 +237,20 @@ test("release verify workflow covers the same split test surface as stable PR ve
);
assert.match(verifyWorkflow, /pnpm test:runner-workflow-evals/);
for (const group of [
"general-server",
"general-workspaces-a",
"general-workspaces-b",
]) {
const buildJob = verifyWorkflow.match(/ build:\n[\s\S]*?(?=\n [A-Za-z0-9_-]+:|$)/)?.[0] ?? "";
assert.match(buildJob, /persist-credentials: false/);
assert.doesNotMatch(buildJob, /cache: pnpm/);
for (const group of ["general-server-without-chat", "general-chat", "general-workspaces-a", "general-workspaces-b"]) {
assert.match(verifyWorkflow, new RegExp(`group: ${group}`));
}
for (const shardIndex of [0, 1, 2]) {
assert.match(
verifyWorkflow,
new RegExp(
`group: general-server[\\s\\S]*?shard_index: ${shardIndex}[\\s\\S]*?shard_count: 3`,
),
);
for (const [group, count] of [["general-server-without-chat", 10], ["general-chat", 3]]) {
const rows = [...verifyWorkflow.matchAll(new RegExp(`group: ${group}\\n\\s+group_label: [^\\n]+\\n\\s+shard_index: (\\d+)\\n\\s+shard_count: (\\d+)`, "g"))];
assert.deepEqual(rows.map((row) => [Number(row[1]), Number(row[2])]),
Array.from({ length: count }, (_, index) => [index, count]));
}
for (const shardIndex of [0, 1, 2, 3, 4]) {
assert.match(
verifyWorkflow,
new RegExp(`shard_index: ${shardIndex}[\\s\\S]*?shard_count: 5`),
);
assert.match(verifyWorkflow, new RegExp(`shard_index: ${shardIndex}[\\s\\S]*?shard_count: 5`));
}
// workspaces-a splits with Vitest native --shard in pr.yml; release
@@ -10,6 +10,8 @@ import {
partitionGeneralServerSuites,
} from "../general-server-shard.mjs";
import { assertSelectedTests, partitionTestLines } from "../test-line-shard.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
const script = path.join(repoRoot, "scripts", "run-vitest-stable.mjs");
const durationsManifest = path.join(repoRoot, "scripts", "general-server-shard-durations.json");
@@ -172,6 +174,46 @@ test("the checked-in manifest loads and covers most of the current suite set", (
);
});
test("the measured chat integration cohort does not share a general-server shard", () => {
const chatSuite = "server/src/__tests__/chat-channels.integration.test.ts";
const durations = loadShardDurations(durationsManifest);
assert.ok(
Number.isFinite(durations[chatSuite]),
"the full chat cohort must have a measured duration, not the median fallback",
);
const unsharded = dryRunJson([
"--mode",
"general",
"--group",
"general-server",
"--shard-index",
"0",
"--shard-count",
"1",
]);
const shards = partitionGeneralServerSuites(
unsharded.selectedGeneralServerSuites,
SHARD_COUNT,
durations,
);
const chatShards = shards.filter((shard) => shard.files.includes(chatSuite));
assert.equal(
chatShards.length,
1,
"the full chat cohort must run exactly once",
);
assert.deepEqual(
chatShards[0].files,
[chatSuite],
"its measured cost must reserve one existing shard without other suites",
);
assert.deepEqual(
shards.flatMap((shard) => shard.files).sort(),
[...unsharded.selectedGeneralServerSuites].sort(),
"duration balancing must not omit or duplicate any general-server suite",
);
});
test("the checked-in serialized manifest loads and covers most of the current suite set", () => {
const durations = loadShardDurations(serializedDurationsManifest);
assert.ok(Object.keys(durations).length > 0, "manifest must parse to a non-empty duration map");
@@ -224,3 +266,53 @@ test("the real shard partition is duration-balanced", () => {
`shard weight spread ${maxTotal - minTotal}ms exceeds heaviest suite ${heaviest}ms: ${totals.join(", ")}`,
);
});
test("release server shards plus the dedicated chat file cover the original server group exactly", () => {
const full = dryRunJson(["--mode", "general", "--group", "general-server", "--shard-index", "0", "--shard-count", "1"]);
const shards = Array.from({ length: 10 }, (_, index) => dryRunJson([
"--mode", "general", "--group", "general-server-without-chat",
"--shard-index", String(index), "--shard-count", "10",
]));
const files = shards.flatMap((shard) => shard.selectedGeneralServerSuites);
const chat = "server/src/__tests__/chat-channels.integration.test.ts";
assert.ok(!files.includes(chat));
assert.deepEqual([...files, chat].sort(), full.selectedGeneralServerSuites.sort());
assert.equal(new Set(files).size, files.length);
const defaultRun = dryRunJson([]);
assert.ok(defaultRun.generalServerSuiteCount === full.generalServerSuiteCount);
});
const lineShardFile = path.join(repoRoot, "server/src/__tests__/chat-channels.integration.test.ts");
const caseAt = (line, name) => ({ name, file: lineShardFile, projectName: "@paperclipai/server", location: { line, column: 3 } });
test("test-line shards cover nested and parameterized cases exactly once without splitting a source line", () => {
const cases = [caseAt(10, "suite > nested > first"), caseAt(10, "suite > nested > second"),
caseAt(20, "same name"), caseAt(30, "same name"), caseAt(40, "last"), caseAt(50, "new case")];
const shards = partitionTestLines(cases, 3, lineShardFile);
assert.deepEqual(shards.map((shard) => shard.tests.length), [2, 2, 2]);
assert.equal(shards.filter((shard) => shard.lines.includes(10)).length, 1);
assert.equal(shards.find((shard) => shard.lines.includes(10)).tests.length, 2);
assert.equal(shards.flatMap((shard) => shard.lines).length, 5);
assert.deepEqual(shards.flatMap((shard) => shard.tests).sort((a, b) => a.location.line - b.location.line), cases);
assert.deepEqual(partitionTestLines([...cases].reverse(), 3, lineShardFile).map((shard) => shard.lines), shards.map((shard) => shard.lines));
});
test("line-shard collection rejects empty, foreign, or unlocated tests and invalid shard counts", () => {
const good = caseAt(10, "valid");
for (const input of [[], null, [{ ...good, file: "/another.test.ts" }], [{ ...good, projectName: "wrong" }],
[{ ...good, location: undefined }], [{ ...good, location: { line: 0 } }], [{ ...good, name: "" }]]) {
assert.throws(() => partitionTestLines(input, 1, lineShardFile));
}
for (const count of [0, -1, 1.5, Infinity, 2]) assert.throws(() => partitionTestLines([good], count, lineShardFile));
});
test("filtered collection must match the exact assigned case identities, including duplicates", () => {
const expected = [caseAt(10, "same"), caseAt(10, "same"), caseAt(20, "nested > case")];
assertSelectedTests(expected, [...expected].reverse(), lineShardFile);
for (const actual of [expected.slice(1), [...expected, caseAt(30, "extra")],
[expected[0], expected[1], caseAt(20, "renamed")],
[expected[0], expected[1], caseAt(21, "nested > case")]]) {
assert.throws(() => assertSelectedTests(expected, actual, lineShardFile));
}
});
+235
View File
@@ -0,0 +1,235 @@
import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import authorize from "../../.github/scripts/authorize-storybook-deploy.cjs";
const ownerFile = ".github/** @cryppadotta @devinfoley @nickyleach @forgottendev\n";
function fixture(overrides = {}) {
const calls = [];
const context = {
repo: { owner: "paperclipai", repo: "paperclip" },
eventName: "workflow_dispatch",
ref: "refs/heads/codex/example",
actor: "cryppadotta",
...overrides.context,
};
const environment = {
can_admins_bypass: false,
protection_rules: [{
type: "required_reviewers",
reviewers: [{ type: "User", reviewer: { login: "cryppadotta" } }],
}],
...overrides.environment,
};
const github = { rest: { repos: {
get: async () => ({ data: { default_branch: "master" } }),
getContent: async (params) => {
calls.push(params);
if (overrides.apiError) throw new Error("GitHub unavailable");
return { data: { encoding: "base64", content: Buffer.from(overrides.codeowners ?? ownerFile).toString("base64") } };
},
getEnvironment: async () => ({ data: environment }),
} } };
return { github, context, calls };
}
// Tests are serial because the Actions rerunner is an environment variable.
process.env.GITHUB_TRIGGERING_ACTOR = "cryppadotta";
test("allows each current CODEOWNER on a feature branch; reads policy from master", async () => {
for (const actor of ["cryppadotta", "devinfoley", "nickyleach", "forgottendev"]) {
const f = fixture({ context: { actor } });
await authorize(f);
assert.equal(f.calls[0].ref, "master");
assert.equal(f.calls[0].path, ".github/CODEOWNERS");
}
});
test("rejects non-owner initiators", async () => {
await assert.rejects(authorize(fixture({ context: { actor: "contributor" } })), /Only default-branch CODEOWNERS/);
});
test("rejects non-owner and missing rerunners, including deployment-only reruns", async () => {
for (const actor of ["contributor", ""]) {
process.env.GITHUB_TRIGGERING_ACTOR = actor;
await assert.rejects(authorize(fixture()), /Only default-branch CODEOWNERS/);
}
process.env.GITHUB_TRIGGERING_ACTOR = "cryppadotta";
});
test("comments, teams, emails and partial account matches do not grant access", async () => {
for (const codeowners of [
"# @cryppadotta\n.github/** @other",
".github/** @other # @cryppadotta",
".github/** @paperclipai/cryppadotta",
".github/** cryppadotta@example.com",
".github/** @cryppadotta-extra",
"",
]) await assert.rejects(authorize(fixture({ codeowners })), /CODEOWNERS/);
});
test("case-insensitive GitHub login matching", async () => {
await authorize(fixture({ context: { actor: "CryppaDotta" } }));
});
test("rejects forks, PR events, automatic events and tags", async () => {
for (const context of [
{ repo: { owner: "outsider", repo: "paperclip" } },
{ eventName: "pull_request" }, { eventName: "push" },
{ eventName: "workflow_call" }, { ref: "refs/tags/release" },
]) await assert.rejects(authorize(fixture({ context })));
});
test("fails closed when GitHub cannot return authoritative CODEOWNERS", async () => {
await assert.rejects(authorize(fixture({ apiError: true })), /GitHub unavailable/);
});
test("requires CODEOWNER environment reviewers with administrator bypass disabled", async () => {
for (const environment of [
{ can_admins_bypass: true },
{ protection_rules: [] },
{ protection_rules: [{ type: "required_reviewers", reviewers: [] }] },
{ protection_rules: [{ type: "required_reviewers", reviewers: [{ type: "User", reviewer: { login: "contributor" } }] }] },
{ protection_rules: [{ type: "required_reviewers", reviewers: [{ type: "Team", reviewer: { login: "cryppadotta" } }] }] },
]) await assert.rejects(authorize(fixture({ environment })), /must require CODEOWNER reviewers/);
});
test("workflow keeps branch build read-only and reauthorizes the protected deploy", () => {
const workflow = readFileSync(new URL("../../.github/workflows/storybook-deploy.yml", import.meta.url), "utf8");
const [build, deploy] = workflow.split(" build:")[1].split(" deploy:");
assert.doesNotMatch(build, /pages: write|id-token: write|secrets\./);
assert.match(build, /permissions: \{\}/);
assert.doesNotMatch(build, /actions\/checkout|cache: pnpm/);
assert.match(build, /package-manager-cache: false/);
assert.match(build, /pnpm install --frozen-lockfile --ignore-scripts/);
assert.match(deploy, /name: storybook-deploy/);
assert.match(deploy, /authorize-storybook-deploy.cjs/);
assert.match(deploy, /name: \$\{\{ needs.build.outputs.artifact_name \}\}/);
assert.doesNotMatch(workflow.split("permissions:")[0], /push:|pull_request:/);
});
import { storybookDestination, branchIndex } from '../../.github/scripts/storybook-destination.cjs';
const input = { branch: 'feature/foo', sha: 'a'.repeat(40), runId: 123, runAttempt: 1,
bucket: 'storybook-test', baseUrl: 'https://example.cloudfront.net' };
test('different branches have distinct stable URLs, including names that sanitize alike', () => {
const branches = ['feature/foo', 'feature-foo', 'Feature/foo', 'master', 'feature_foo', 'a'.repeat(100), 'a'.repeat(101)];
const urls = branches.map(branch => storybookDestination({ ...input, branch }).url);
assert.equal(new Set(urls).size, branches.length);
assert.ok(urls.every(url => /^https:\/\/example.cloudfront.net\/storybook\/branches\/[A-Za-z0-9_~\-]+\/$/.test(url)));
assert.equal(storybookDestination({ ...input, branch: 'master' }).url,
'https://example.cloudfront.net/storybook/branches/master/');
assert.equal(storybookDestination(input).url,
'https://example.cloudfront.net/storybook/branches/feature~2Ffoo/');
});
test('bookmark paths cannot collide with other branches or existing immutable build directories', () => {
const branches = ['feature/foo', 'feature~2Ffoo', '../master', 'master/index.html',
'master', storybookDestination({ ...input, branch: 'master' }).branchKey,
'a'.repeat(1000), 'a'.repeat(1001), 'café', 'caf~C3~A9'];
const destinations = branches.map(branch => storybookDestination({ ...input, branch }));
assert.equal(new Set(destinations.map(d => d.url)).size, branches.length);
for (const d of destinations) {
assert.doesNotMatch(d.bookmarkPrefix.slice('storybook/branches/'.length), /[/.]/);
assert.ok(Buffer.byteLength(`${d.bookmarkPrefix}/index.html`) <= 1024);
assert.ok(destinations.every(other => d.bookmarkPrefix !== other.prefix));
}
});
test('redeploying a branch preserves its entry URL and creates a new build URL', () => {
const a = storybookDestination(input);
const b = storybookDestination({ ...input, sha: 'b'.repeat(40), runId: 124 });
assert.equal(a.url, b.url);
assert.notEqual(a.buildUrl, b.buildUrl);
assert.notEqual(a.buildUrl, storybookDestination({ ...input, runAttempt: 2 }).buildUrl);
});
test('invalid source and destination inputs fail closed', () => {
for (const change of [{ branch: '' }, { branch: 'a\nb' }, { sha: 'master' }, { runId: '../x' },
{ runAttempt: 0 }, { bucket: '../bucket' }, { baseUrl: 'http://example.com' },
{ baseUrl: 'https://user:password@example.com' }, { baseUrl: 'https://example.com/path' },
{ baseUrl: 'https://example.com?x=y' }]) {
assert.throws(() => storybookDestination({ ...input, ...change }));
}
});
test('branch entry preserves Storybook query and fragment deep links', () => {
const html = branchIndex(storybookDestination(input).buildUrl);
assert.match(html, /target.search = location.search/);
assert.match(html, /target.hash = location.hash/);
assert.match(html, /location.replace/);
});
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, symlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const publisher = fileURLToPath(new URL('../../.github/scripts/publish-storybook.cjs', import.meta.url));
function publishFixture(options = {}) {
const dir = mkdtempSync(path.join(tmpdir(), 'storybook-publish-test-'));
mkdirSync(path.join(dir, 'storybook-static'));
mkdirSync(path.join(dir, 'bin'));
for (const name of ['index.html', 'iframe.html', 'index.json']) writeFileSync(path.join(dir, 'storybook-static', name), 'fixture');
if (options.symlink) symlinkSync('/etc/passwd', path.join(dir, 'storybook-static', 'unsafe'));
const stub = path.join(dir, 'bin', 'aws');
writeFileSync(stub, `#!${process.execPath}\nconst fs=require('node:fs');fs.appendFileSync(process.env.UPLOAD_LOG,JSON.stringify(process.argv.slice(2))+'\\n');if(process.env.FAIL_UPLOAD==='1')process.exit(1);\n`);
chmodSync(stub, 0o755);
const result = spawnSync(process.execPath, [publisher], { cwd: dir, encoding: 'utf8', env: {
...process.env, PATH: `${path.join(dir, 'bin')}:${process.env.PATH}`, RUNNER_TEMP: dir,
SOURCE_BRANCH: input.branch, SOURCE_SHA: input.sha, GITHUB_RUN_ID: '123', GITHUB_RUN_ATTEMPT: '1',
STORYBOOK_S3_BUCKET: input.bucket, STORYBOOK_PUBLIC_BASE_URL: input.baseUrl,
GITHUB_OUTPUT: path.join(dir, 'output'), GITHUB_STEP_SUMMARY: path.join(dir, 'summary'),
UPLOAD_LOG: path.join(dir, 'uploads'), FAIL_UPLOAD: options.fail ? '1' : '0',
} });
let uploads = [];
try { uploads = readFileSync(path.join(dir, 'uploads'), 'utf8').trim().split('\n').map(JSON.parse); } catch {}
let report = '';
let summary = '';
if (result.status === 0) {
report = readFileSync(path.join(dir, 'storybook-deployment.md'), 'utf8');
summary = readFileSync(path.join(dir, 'summary'), 'utf8');
}
rmSync(dir, { recursive: true, force: true });
return { result, uploads, report, summary };
}
test('publisher uploads a complete build then updates only that branch entry', () => {
const { result, uploads } = publishFixture();
assert.equal(result.status, 0, result.stderr);
assert.equal(uploads.length, 3);
const d = storybookDestination(input);
assert.ok(uploads[0].includes(`s3://${input.bucket}/${d.buildPrefix}/`));
assert.ok(uploads[1].includes(`s3://${input.bucket}/${d.prefix}/index.html`));
assert.ok(uploads[2].includes(`s3://${input.bucket}/${d.bookmarkPrefix}/index.html`));
assert.ok(uploads[2].includes('no-cache,max-age=0,must-revalidate'));
assert.ok(uploads[0].includes('--no-follow-symlinks'));
assert.doesNotMatch(JSON.stringify(uploads), /--delete/);
});
test('a failed build upload never changes the stable branch entry', () => {
const { result, uploads } = publishFixture({ fail: true });
assert.notEqual(result.status, 0);
assert.equal(uploads.length, 1);
assert.ok(uploads[0].includes('--recursive'));
});
test('artifact symlinks fail before any upload', () => {
const { result, uploads } = publishFixture({ symlink: true });
assert.notEqual(result.status, 0);
assert.equal(uploads.length, 0);
});
test('successful publication produces a downloadable Markdown report matching the run summary', () => {
const { result, report, summary } = publishFixture();
assert.equal(result.status, 0, result.stderr);
const d = storybookDestination(input);
assert.ok(report.includes(`[Branch Storybook](${d.url})`));
assert.ok(report.includes(`[This build](${d.buildUrl})`));
assert.ok(report.includes(d.sha));
assert.equal(report, summary);
});
import { verifyStorybook } from '../../.github/scripts/verify-storybook.cjs';
test('public verification retries a stale stable branch entry until it points to the new build', async () => {
const d = storybookDestination(input);
let indexReads = 0;
await verifyStorybook({ branchUrl: d.url, buildUrl: d.buildUrl, sha: d.sha,
sleep: async () => {}, attempts: 2, fetch: async (url) => String(url).endsWith('deployment.json')
? new Response(JSON.stringify({ sha: d.sha }))
: new Response(branchIndex(++indexReads === 1 ? d.buildUrl.replace('123-1', '122-1') : d.buildUrl)) });
assert.equal(indexReads, 2);
});
test('public verification rejects a permanently stale branch URL or wrong source commit', async () => {
const d = storybookDestination(input);
for (const wrong of ['branch', 'sha']) {
await assert.rejects(verifyStorybook({ branchUrl: d.url, buildUrl: d.buildUrl, sha: d.sha,
attempts: 1, fetch: async (url) => String(url).endsWith('deployment.json')
? new Response(JSON.stringify({ sha: wrong === 'sha' ? 'b'.repeat(40) : d.sha }))
: new Response(branchIndex(wrong === 'branch' ? d.buildUrl.replace('123-1', '122-1') : d.buildUrl)) }),
wrong === 'branch' ? /does not point to this build/ : /wrong source commit/);
}
});
+112 -10
View File
@@ -13,6 +13,8 @@ import { readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { createRequire } from "node:module";
import { runInNewContext } from "node:vm";
import cliEsbuildConfig from "../cli/esbuild.config.mjs";
import { bundledCliNpmDependencies } from "./cli-bundled-npm-dependencies.mjs";
@@ -43,10 +45,68 @@ const acpxRuntimePatch = await readFile(
"utf8",
);
const claudeAcpPatch = await readFile(
new URL("../patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch", import.meta.url),
new URL("../patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch", import.meta.url),
"utf8",
);
for (const version of ["0.12.0", "0.13.1"]) {
test(`ACPX ${version} release patch uses portable generated unified hunks`, async () => {
const patch = await readFile(
new URL(`../patches/acpx@${version}.patch`, import.meta.url),
"utf8",
);
const lines = patch.split("\n");
let hunkCount = 0;
for (let index = 0; index < lines.length; index += 1) {
const header = lines[index].match(
/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/,
);
if (!header) continue;
hunkCount += 1;
const body = [];
let oldLines = 0;
let newLines = 0;
while (index + 1 < lines.length && (oldLines < Number(header[2] ?? 1) || newLines < Number(header[4] ?? 1))) {
const line = lines[++index];
// Unified diff's EOF marker is metadata, not a source/destination
// line, and may occur between the removed and added final lines.
if (line === "\\ No newline at end of file") continue;
// Git accepts an empty context line with its optional space omitted.
assert.ok(line === "" || /^[ +\-]/.test(line), `invalid unified hunk line: ${line}`);
const normalized = line === "" ? " " : line;
body.push(normalized);
if (!normalized.startsWith("+")) oldLines += 1;
if (!normalized.startsWith("-")) newLines += 1;
}
assert.equal(
body.filter((line) => !line.startsWith("+")).length,
Number(header[2] ?? 1),
);
assert.equal(
body.filter((line) => !line.startsWith("-")).length,
Number(header[4] ?? 1),
);
const prefix = body.findIndex((line) => !line.startsWith(" "));
const suffix = body
.slice()
.reverse()
.findIndex((line) => !line.startsWith(" "));
// pnpm patch-commit emits three context lines. Hand-added asymmetric
// context can force GNU patch's locate_hunk() to require EOF even when
// BSD patch and git apply accept the same source and hunk.
assert.ok(
prefix >= 0 && prefix <= 3,
`regenerate ${version} hunk at old line ${header[1]} with pnpm patch-commit (prefix ${prefix})`,
);
assert.ok(
suffix >= 0 && suffix <= 3,
`regenerate ${version} hunk at old line ${header[1]} with pnpm patch-commit (suffix ${suffix})`,
);
}
assert.ok(hunkCount > 0);
});
}
test("published packages preserve the patched ACPX runtime", () => {
assert.equal(
rootPackage.pnpm.patchedDependencies["acpx@0.12.0"],
@@ -59,7 +119,7 @@ test("published packages preserve the patched ACPX runtime", () => {
assert.equal(adapterUtilsPackage.dependencies.acpx, "0.12.0");
assert.deepEqual(adapterUtilsPackage.bundleDependencies, ["acpx"]);
assert.equal(serverPackage.dependencies.acpx, "0.13.1");
assert.deepEqual(serverPackage.bundleDependencies, ["acpx"]);
assert.ok(serverPackage.bundleDependencies.includes("acpx"));
assert.equal(bundledCliNpmDependencies.has("acpx"), true);
assert.equal(cliEsbuildConfig.external.includes("acpx"), false);
});
@@ -67,11 +127,11 @@ test("published packages preserve the patched ACPX runtime", () => {
test("Paperclip Runner pins the qualified ACPX host callbacks", () => {
assert.equal(rootPackage.pnpm.patchedDependencies["acpx@0.13.1"], "patches/acpx@0.13.1.patch");
assert.equal(
rootPackage.pnpm.patchedDependencies["@agentclientprotocol/claude-agent-acp@0.70.0"],
"patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch",
rootPackage.pnpm.patchedDependencies["@agentclientprotocol/claude-agent-acp@0.73.0"],
"patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch",
);
assert.equal(runnerPackage.dependencies.acpx, "0.13.1");
assert.equal(runnerPackage.dependencies["@agentclientprotocol/claude-agent-acp"], "0.70.0");
assert.equal(runnerPackage.dependencies["@agentclientprotocol/claude-agent-acp"], "0.73.0");
assert.equal(runnerPackage.dependencies["@agentclientprotocol/codex-acp"], "1.6.2");
for (const callback of [
"spawnEnvironment", "spawnCwd", "spawnAgent", "isPlainStringEnvironment",
@@ -224,7 +284,7 @@ test("bundled package patch selection rejects an unpatched installed version", (
);
});
test("server package staging bundles and patches the vendored runner's acpx runtime", (t) => {
test("server package staging applies every bundled runtime patch and preserves the vendored runner", (t) => {
const fixtureDir = mkdtempSync(join(tmpdir(), "paperclip-bundled-stage-"));
const sourceDir = join(fixtureDir, "source");
const destinationDir = join(fixtureDir, "destination");
@@ -261,7 +321,7 @@ mkdir -p "$destination/node_modules/.pnpm"
set -euo pipefail
printf 'npm %s\\n' "$*" >> "$FAKE_CALL_LOG"
[ "$*" = "install --omit=dev --ignore-scripts --no-audit --no-fund" ]
node -e 'const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1)'
node -e 'const fs = require("node:fs"); const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1); for (const [name, version] of Object.entries(pkg.dependencies)) { const dir = "node_modules/" + name; fs.mkdirSync(dir + "/dist", { recursive: true }); fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version })); }'
mkdir -p node_modules/acpx/dist
printf 'unpatched runtime\\n' > node_modules/acpx/dist/runtime.js
printf '{"name":"acpx","version":"0.13.1"}\\n' > node_modules/acpx/package.json
@@ -282,6 +342,10 @@ while [ "$#" -gt 0 ]; do
fi
done
patch_input="$(cat)"
printf '%s\\n' "$patch_input" > "$target/applied.patch"
if [[ "$target" != */acpx ]]; then
exit 0
fi
grep -q spawnEnvironment <<< "$patch_input"
grep -q spawnAgent <<< "$patch_input"
grep -q onAgentStderr <<< "$patch_input"
@@ -291,7 +355,11 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
execFileSync(
process.execPath,
[new URL("./prepare-bundled-package.mjs", import.meta.url).pathname, sourceDir, destinationDir],
[
new URL("./prepare-bundled-package.mjs", import.meta.url).pathname,
sourceDir,
destinationDir,
],
{
env: {
...process.env,
@@ -316,9 +384,23 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
/patch -p1 --forward -d .*node_modules\/acpx/,
);
assert.equal(
readFileSync(callLog, "utf8").split("\n").filter((line) => line.startsWith("patch ")).length,
1,
readFileSync(callLog, "utf8")
.split("\n")
.filter((line) => line.startsWith("patch ")).length,
serverPackage.bundleDependencies.length,
);
for (const name of serverPackage.bundleDependencies) {
const specifier = `${name}@${serverPackage.dependencies[name]}`;
const patchPath = rootPackage.pnpm.patchedDependencies[specifier];
assert.equal(
readFileSync(
join(destinationDir, "node_modules", name, "applied.patch"),
"utf8",
),
`${readFileSync(new URL(`../${patchPath}`, import.meta.url), "utf8").trimEnd()}\n`,
`${specifier} receives its own full configured patch`,
);
}
});
test("bundled package dry runs preview without querying published versions", () => {
@@ -342,3 +424,23 @@ test("npm builds use corepack instead of requiring a global pnpm", () => {
assert.match(buildNpmScript, /corepack pnpm -r typecheck/);
assert.doesNotMatch(buildNpmScript, /^\s*pnpm -r typecheck/m);
});
test("installed ACPX runtime persists and restores optional goal capabilities", () => {
const requireRunner = createRequire(new URL("../packages/paperclip-runner/package.json", import.meta.url));
const runtimeSource = readFileSync(requireRunner.resolve("acpx/runtime"), "utf8");
const start = runtimeSource.indexOf("function persistedGoalCapability(");
const end = runtimeSource.indexOf("function planUpdateEvent(", start);
assert.ok(start >= 0 && end > start, "the installed patch must define both goal helpers");
const helpers = runInNewContext(runtimeSource.slice(start, end) + ";({ persistedGoalCapability, restoredGoalCapability })", {
isRecord: (value) => value !== null && typeof value === "object" && !Array.isArray(value),
});
assert.equal(helpers.persistedGoalCapability(undefined), undefined);
assert.equal(helpers.restoredGoalCapability(undefined), undefined);
const goal = { version: 1, controlMethod: "_session/goal", actions: ["set", "pause", "clear"] };
const saved = JSON.parse(JSON.stringify(helpers.persistedGoalCapability(goal)));
assert.equal(saved.control_method, "_session/goal");
assert.deepEqual(JSON.parse(JSON.stringify(helpers.restoredGoalCapability(saved))), goal);
assert.equal(helpers.persistedGoalCapability({ ...goal, version: 2 }), undefined);
assert.equal(helpers.persistedGoalCapability({ ...goal, actions: ["set"] }), undefined);
});
+115
View File
@@ -0,0 +1,115 @@
/** Isolated, repeatable preparation benchmark. Run with pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-skill-preparation.ts. */
import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { companies, companySkills, createDb, startEmbeddedPostgresTestDatabase } from "../packages/db/src/index.js";
import { removeRuntimeSkillCache } from "../server/src/services/runtime-skill-cache.js";
import { companySkillService } from "../server/src/services/company-skills.js";
const child = process.argv.includes("--warm-child");
const home = child ? process.env.PAPERCLIP_HOME! : await fs.mkdtemp(path.join(os.tmpdir(), "skill-preparation-benchmark-"));
process.env.PAPERCLIP_HOME = home;
process.env.PAPERCLIP_INSTANCE_ID = "default";
const database = child ? null : await startEmbeddedPostgresTestDatabase("skill-preparation-benchmark-");
const db = createDb(database?.connectionString ?? process.env.SKILL_BENCH_DATABASE_URL!);
const companyId = child ? process.env.SKILL_BENCH_COMPANY_ID! : randomUUID();
const svc = companySkillService(db);
let upstreamFetches = 0;
let inventoryRefreshes = 0;
let rebuilds = 0;
const originalFetch = globalThis.fetch;
globalThis.fetch = async (input) => { upstreamFetches++; return new Response(`# Fixture\n${String(input)}\n`); };
const originalSelect = db.select.bind(db);
db.select = ((fields?: Record<string, unknown>) => {
if (fields && Object.keys(fields).length === 1 && fields.id === companies.id) inventoryRefreshes++;
return originalSelect(fields as never);
}) as typeof db.select;
const originalMkdtemp = fs.mkdtemp;
fs.mkdtemp = ((prefix: string, ...args: unknown[]) => {
if (String(prefix).includes("__runtime_cache_v1__") && String(prefix).includes(".staging-")) rebuilds++;
return Reflect.apply(originalMkdtemp, fs, [prefix, ...args]);
}) as typeof fs.mkdtemp;
async function measure(label: string) {
upstreamFetches = inventoryRefreshes = rebuilds = 0;
const start = performance.now();
const entries = await svc.listRuntimeSkillEntries(companyId);
const durationMs = performance.now() - start;
const missingEntries = entries.filter((entry) => entry.sourceStatus === "missing").length;
assert.equal(entries.length, 114);
assert.equal(missingEntries, 0);
assert.equal(inventoryRefreshes, 1);
if (label !== "cold") { assert.equal(upstreamFetches, 0); assert.equal(rebuilds, 0); }
// Check every synthetic supporting file against its installed revision, outside the timed section.
const installed = await db.select().from(companySkills);
for (const skill of installed.filter((skill) => skill.sourceType === "github" || skill.sourceType === "skills_sh")) {
const entry = entries.find((entry) => entry.key === skill.key)!;
for (const file of skill.fileInventory) {
const expected = `# Fixture\nhttps://raw.githubusercontent.com/fixture/skills/${skill.sourceRef}/${skill.slug}/${file.path}\n`;
assert.equal(await fs.readFile(path.join(entry.source, file.path), "utf8"), expected);
}
}
return { label, durationMs: Math.round(durationMs * 100) / 100, inventoryRefreshes, upstreamFetches, rebuilds, missingEntries, entries: entries.length };
}
try {
if (child) {
console.log(JSON.stringify(await measure("warm-new-process")));
} else {
await db.insert(companies).values({ id: companyId, name: "Skill benchmark", issuePrefix: "BENCH" });
const bundled = await svc.listFull(companyId);
const additional = 114 - bundled.length;
assert.ok(additional >= 6, "Bundled inventory leaves insufficient room for mixed benchmark fixtures");
const remoteCount = 33;
const catalogCount = 21;
assert.ok(additional > remoteCount + catalogCount, "Bundled inventory leaves no room for local fixtures");
const filesPerRemote = Math.max(9, Math.ceil(400 / remoteCount));
for (let index = 0; index < additional; index++) {
const slug = `fixture-${index}`;
const sourceType = index < remoteCount ? (index < 28 ? "github" : "skills_sh")
: index === remoteCount ? "url" : index <= remoteCount + catalogCount ? "catalog" : "local_path";
const localDir = path.join(home, "instances", "default", "skills", companyId, slug);
if (sourceType === "local_path" || sourceType === "catalog") {
await fs.mkdir(localDir, { recursive: true });
await fs.writeFile(path.join(localDir, "SKILL.md"), `---\nname: ${slug}\ndescription: Benchmark\n---\n# Local\n`);
}
await db.insert(companySkills).values({
id: randomUUID(), companyId, key: `company/${companyId}/${slug}`, slug, name: slug,
markdown: `# ${slug}`, sourceType, sourceLocator: sourceType === "local_path" || sourceType === "catalog" ? localDir : `https://example.com/${slug}`,
sourceRef: index < remoteCount ? "a".repeat(40) : null,
trustLevel: "markdown_only", compatibility: "compatible",
metadata: { owner: "fixture", repo: "skills", repoSkillDir: slug },
fileInventory: [{ path: "SKILL.md", kind: "skill" }, ...Array.from({ length: index < remoteCount ? filesPerRemote - 1 : 0 }, (_, file) => ({ path: `references/${file}.md`, kind: "reference" as const }))],
});
}
const samples = [await measure("cold")];
for (let index = 0; index < 9; index++) samples.push(await measure(`warm-${index + 1}`));
const childOutput = await new Promise<string>((resolve, reject) => {
const proc = spawn(process.execPath, ["--import", fileURLToPath(new URL("../server/node_modules/tsx/dist/loader.mjs", import.meta.url)), fileURLToPath(import.meta.url), "--warm-child"], {
env: { ...process.env, SKILL_BENCH_DATABASE_URL: database!.connectionString, SKILL_BENCH_COMPANY_ID: companyId },
stdio: ["ignore", "pipe", "inherit"],
});
let output = "";
proc.stdout.on("data", (chunk) => { output += chunk; });
proc.on("error", reject);
proc.on("exit", (code) => code === 0 ? resolve(output) : reject(new Error(`Warm subprocess failed: ${code}`)));
});
samples.push(JSON.parse(childOutput.trim().split("\n").at(-1)!));
const warm = samples.slice(1).map((sample) => sample.durationMs).sort((a, b) => a - b);
console.log(JSON.stringify({ inventory: 114, materializedRemoteFiles: remoteCount * filesPerRemote,
warmMedianMs: (warm[4] + warm[5]) / 2, warmMaxMs: warm.at(-1), samples }, null, 2));
}
} finally {
globalThis.fetch = originalFetch;
fs.mkdtemp = originalMkdtemp;
if (!child) for (const skill of await db.select().from(companySkills)) {
await removeRuntimeSkillCache(path.join(home, "instances", "default", "skills", companyId), skill.id);
}
await db.$client.end();
await database?.cleanup();
if (!child) await fs.rm(home, { recursive: true, force: true });
}
@@ -0,0 +1,122 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import {
createBundledInstallManifest,
materializePublishManifest,
selectBundledDependencyPatches,
} from "./prepare-bundled-package.mjs";
const repoRoot = fileURLToPath(new URL("..", import.meta.url));
const rootPackage = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
);
const serverPackage = JSON.parse(
readFileSync(new URL("../server/package.json", import.meta.url), "utf8"),
);
const workspace = readFileSync(
new URL("../pnpm-workspace.yaml", import.meta.url),
"utf8",
);
const required = [
["@chat-adapter/discord", "4.39.0"],
["@chat-adapter/github", "4.39.0"],
["@chat-adapter/slack", "4.39.0"],
["@chat-adapter/teams", "4.39.0"],
["@chat-adapter/telegram", "4.39.0"],
["@discordjs/ws", "1.2.3"],
];
for (const [name, version] of required) {
test(`published server retains the patched ${name}@${version} runtime`, () => {
const published = materializePublishManifest(serverPackage);
const installed = createBundledInstallManifest(
published,
serverPackage.bundleDependencies,
);
assert.equal(
serverPackage.dependencies[name],
version,
"the patched runtime must have an exact direct version",
);
assert.ok(
serverPackage.bundleDependencies.includes(name),
"npm consumers cannot apply this repository's pnpm patches",
);
assert.equal(
installed.dependencies[name],
version,
"release staging must install the runtime before patching",
);
assert.ok(
published.bundleDependencies.includes(name),
"the patched runtime must remain in the published tarball",
);
const specifier = `${name}@${version}`;
const patchPath = rootPackage.pnpm.patchedDependencies[specifier];
assert.equal(typeof patchPath, "string");
assert.ok(
workspace.includes(`"${specifier}": ${patchPath}`),
"both supported pnpm configuration paths must match",
);
// Parse the actual full patch, not merely its configured filename. This is
// read-only; clean-package materialization is a separate qualification.
execFileSync("git", ["apply", "--stat", patchPath], {
cwd: repoRoot,
stdio: "pipe",
});
});
}
test("release selection includes every chat runtime patch and the existing ACPX patch", (t) => {
const destination = mkdtempSync(
join(tmpdir(), "paperclip-chat-release-contract-"),
);
t.after(() => rmSync(destination, { recursive: true, force: true }));
for (const [name, version] of [...required, ["acpx", "0.13.1"]]) {
const directory = join(destination, "node_modules", name);
mkdirSync(directory, { recursive: true });
writeFileSync(
join(directory, "package.json"),
JSON.stringify({ name, version }),
);
}
const selected = selectBundledDependencyPatches(
destination,
serverPackage.bundleDependencies,
rootPackage.pnpm.patchedDependencies,
);
assert.deepEqual(
selected.map(({ specifier }) => specifier).sort(),
[
...required.map(([name, version]) => `${name}@${version}`),
"acpx@0.13.1",
].sort(),
);
writeFileSync(
join(destination, "node_modules", "@discordjs/ws", "package.json"),
JSON.stringify({
name: "@discordjs/ws",
version: "1.2.4",
}),
);
assert.throws(
() =>
selectBundledDependencyPatches(
destination,
serverPackage.bundleDependencies,
rootPackage.pnpm.patchedDependencies,
),
/installed @discordjs\/ws@1\.2\.4/,
);
});
+15 -3
View File
@@ -104,8 +104,18 @@ export function scanModuleBoundaries({
addViolation(violations, sourceLabel, "domain", specifier, "domain cannot import database packages");
} else if (specifier.startsWith("node:")) {
addViolation(violations, sourceLabel, "domain", specifier, "domain cannot import Node.js runtime modules");
} else if (targetSegments.includes("services") || targetSegments.includes("routes")) {
addViolation(violations, sourceLabel, "domain", specifier, "domain cannot import server services or routes");
} else if (
targetSegments.includes("services") ||
targetSegments.includes("routes") ||
targetSegments.includes("adapters")
) {
addViolation(
violations,
sourceLabel,
"domain",
specifier,
"domain cannot import server services, routes, or adapters",
);
} else if (targetLocation?.layer === "application" || targetLocation?.layer === "adapters") {
addViolation(violations, sourceLabel, "domain", specifier, "domain cannot depend on outer module layers");
}
@@ -114,8 +124,10 @@ export function scanModuleBoundaries({
if (sourceLocation?.layer === "application") {
if (isDatabasePackage(specifier)) {
addViolation(violations, sourceLabel, "application", specifier, "application cannot import database packages");
} else if (targetLocation?.layer === "adapters") {
} else if (targetLocation?.layer === "adapters" || targetSegments.includes("adapters")) {
addViolation(violations, sourceLabel, "application", specifier, "application cannot import concrete adapters");
} else if (targetSegments.includes("services") || targetSegments.includes("routes")) {
addViolation(violations, sourceLabel, "application", specifier, "application cannot import server services or routes");
} else if (targetSegments.join("/") === "errors.js" || targetSegments.join("/") === "errors.ts") {
addViolation(violations, sourceLabel, "application", specifier, "application cannot import HTTP error helpers");
}
+19 -1
View File
@@ -32,13 +32,16 @@ test("scanModuleBoundaries rejects outward dependencies and module-internal impo
write("modules/watchdog/domain/policy.ts", [
'import { eq } from "drizzle-orm";',
'import { service } from "../../../services/example.js";',
'import { parse } from "../../../adapters/utils.js";',
'import { run } from "../application/run.js";',
].join("\n"));
write(
"modules/watchdog/application/run.ts",
[
'import { adapter } from "../adapters/postgres.js";',
'import { parse } from "../../../adapters/application-utils.js";',
'import { forbidden } from "../../../errors.js";',
'import { helper } from "../../../services/example.js";',
'import db = require("@paperclipai/db");',
].join("\n"),
);
@@ -51,10 +54,25 @@ test("scanModuleBoundaries rejects outward dependencies and module-internal impo
violations.map(({ specifier, reason }) => ({ specifier, reason })),
[
{ specifier: "../adapters/postgres.js", reason: "application cannot import concrete adapters" },
{
specifier: "../../../adapters/application-utils.js",
reason: "application cannot import concrete adapters",
},
{ specifier: "../../../errors.js", reason: "application cannot import HTTP error helpers" },
{
specifier: "../../../services/example.js",
reason: "application cannot import server services or routes",
},
{ specifier: "@paperclipai/db", reason: "application cannot import database packages" },
{ specifier: "drizzle-orm", reason: "domain cannot import database packages" },
{ specifier: "../../../services/example.js", reason: "domain cannot import server services or routes" },
{
specifier: "../../../services/example.js",
reason: "domain cannot import server services, routes, or adapters",
},
{
specifier: "../../../adapters/utils.js",
reason: "domain cannot import server services, routes, or adapters",
},
{ specifier: "../application/run.js", reason: "domain cannot depend on outer module layers" },
{
specifier: "../modules/watchdog/application/run.js",
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env node
import { pathToFileURL } from "node:url";
import { imageExists, packageExists, versionFor } from "./preview-artifacts.mjs";
/** Read-only availability gate. Deployment still resolves and pins artifacts. */
export async function waitForCloudArtifacts(sha, {
fetchImpl = fetch,
now = () => performance.now(),
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
timeoutMs = 30 * 60_000,
intervalMs = 20_000,
log = console.log,
} = {}) {
const version = versionFor(sha);
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !Number.isFinite(intervalMs) || intervalMs <= 0) {
throw new Error("Cloud readiness requires positive finite timeout and poll interval.");
}
const deadline = now() + timeoutMs;
let previous;
let missing = ["image", "shared", "db"];
while (now() < deadline) {
// Recheck every artifact on the successful poll. Only an explicit 404
// means publication is pending; identity errors and upstream outages fail.
const results = await Promise.all([
imageExists(sha, fetchImpl),
packageExists("@paperclipai/shared", sha, fetchImpl),
packageExists("@paperclipai/db", sha, fetchImpl),
]);
missing = ["image", "shared", "db"].filter((_, index) => !results[index]);
if (missing.length === 0) {
log(`Cloud artifacts available for ${sha}: verified image and exact-source migrator ${version}.`);
return { version: 1, sha, packageVersion: version };
}
const state = missing.join(", ");
if (state !== previous) log(`Waiting for cloud artifacts for ${sha}: ${state}.`);
previous = state;
const remaining = deadline - now();
if (remaining > 0) await sleep(Math.min(intervalMs, remaining));
}
throw new Error(`Cloud artifacts timed out for ${sha}; missing: ${missing.join(", ")}.`);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try { await waitForCloudArtifacts(process.argv[2]); }
catch (error) { console.error(error.message); process.exitCode = 1; }
}
+99
View File
@@ -0,0 +1,99 @@
import { appendFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
const repository = "paperclipai/paperclip";
const workflowPath = ".github/workflows/cloud-readiness.yml";
export const sourceVerificationJob = "Cloud source verified v1";
function assertSha(sha) {
if (!/^[a-f0-9]{40}$/.test(sha ?? "")) throw new Error("A full lowercase source SHA is required.");
}
function trustedRun(run, sha, workflowId) {
return run.workflow_id === workflowId && run.path === workflowPath &&
run.repository?.full_name === repository && run.head_repository?.full_name === repository &&
run.head_sha === sha && run.head_branch === "master" && run.event === "push" &&
Number.isSafeInteger(run.id) && run.id > 0 &&
Number.isSafeInteger(run.run_attempt) && run.run_attempt > 0;
}
// Consume one versioned job, independent of image/migrator availability. A
// failed image build must not invalidate source checks that already passed.
export async function readSourceVerification(sha, api) {
assertSha(sha);
const workflow = await api(`/repos/${repository}/actions/workflows/cloud-readiness.yml`);
if (workflow.path !== workflowPath || !Number.isSafeInteger(workflow.id) || workflow.id < 1) {
throw new Error("Cloud readiness workflow identity does not match.");
}
const listing = await api(`/repos/${repository}/actions/workflows/${workflow.id}/runs?head_sha=${sha}&event=push&branch=master&per_page=100`);
if (!Array.isArray(listing.workflow_runs) || !Number.isSafeInteger(listing.total_count) ||
listing.total_count < 0 || listing.total_count > 100 || listing.workflow_runs.length !== listing.total_count) {
throw new Error("Cloud readiness run listing is incomplete.");
}
const run = listing.workflow_runs.filter((candidate) => trustedRun(candidate, sha, workflow.id))
.sort((a, b) => b.id - a.id)[0];
if (!run) return undefined;
// Attempt-specific jobs prevent an earlier successful attempt from blessing
// a later rerun. Keep pagination even though today's matrix fits one page.
const jobs = [];
for (let page = 1; page <= 10; page += 1) {
const batch = await api(`/repos/${repository}/actions/runs/${run.id}/attempts/${run.run_attempt}/jobs?per_page=100&page=${page}`);
if (!Array.isArray(batch.jobs)) throw new Error("Cloud readiness job listing is malformed.");
jobs.push(...batch.jobs);
if (batch.jobs.length < 100) break;
if (page === 10) throw new Error("Cloud readiness job listing is incomplete.");
}
const matches = jobs.filter((job) => job.name === sourceVerificationJob);
if (matches.length > 1) throw new Error("Cloud source verification job is ambiguous.");
const job = matches[0];
if (job?.status === "completed" && job.conclusion === "success" &&
job.head_sha === sha && job.run_id === run.id && job.run_attempt === run.run_attempt) {
// Re-read the run after its jobs: a rerun that started during polling must
// not let the previous attempt through. Changes are retried next poll.
const current = await api(`/repos/${repository}/actions/runs/${run.id}`);
if (!trustedRun(current, sha, workflow.id) || current.run_attempt !== run.run_attempt) return undefined;
return { sha, runId: run.id, attempt: run.run_attempt, jobId: job.id };
}
if (job?.status === "completed" || run.status === "completed") {
throw new Error(`Cloud source verification did not pass for ${sha} (run ${run.id}, attempt ${run.run_attempt}). Rerun Cloud readiness before retrying the release.`);
}
return undefined;
}
export async function waitForSourceVerification(sha, {
api, now = Date.now, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
timeoutMs = 45 * 60_000, intervalMs = 30_000, log = console.log,
} = {}) {
assertSha(sha);
const deadline = now() + timeoutMs;
log(`Waiting for ${sourceVerificationJob} for ${sha}.`);
while (now() < deadline) {
const result = await readSourceVerification(sha, api);
if (result) return result;
const remaining = deadline - now();
if (remaining > 0) await sleep(Math.min(intervalMs, remaining));
}
throw new Error(`Cloud source verification timed out for ${sha}. Rerun Cloud readiness before retrying the release.`);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
if (!process.env.GITHUB_TOKEN) throw new Error("GITHUB_TOKEN with Actions read access is required.");
const api = async (path) => {
const response = await fetch(`https://api.github.com${path}`, {
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" },
signal: AbortSignal.timeout(30_000), redirect: "error",
});
if (!response.ok) throw new Error(`GitHub Actions read failed (HTTP ${response.status}).`);
return response.json();
};
const proof = await waitForSourceVerification(process.argv[2], { api });
const message = `Source verification passed for ${proof.sha}: https://github.com/${repository}/actions/runs/${proof.runId}/attempts/${proof.attempt} (job ${proof.jobId}).`;
console.log(message);
if (process.env.GITHUB_STEP_SUMMARY) await appendFile(process.env.GITHUB_STEP_SUMMARY, `${message}\n`);
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
}
+125
View File
@@ -0,0 +1,125 @@
import assert from "node:assert/strict";
import test from "node:test";
import { readSourceVerification, sourceVerificationJob, waitForSourceVerification } from "./cloud-source-verification.mjs";
const sha = "a".repeat(40);
const workflow = { id: 123, path: ".github/workflows/cloud-readiness.yml" };
const baseRun = {
id: 456, workflow_id: workflow.id, path: workflow.path, run_attempt: 2,
repository: { full_name: "paperclipai/paperclip" }, head_repository: { full_name: "paperclipai/paperclip" },
head_sha: sha, head_branch: "master", event: "push", status: "in_progress", conclusion: null,
};
const baseJob = { id: 789, name: sourceVerificationJob, run_id: 456, run_attempt: 2, head_sha: sha, status: "completed", conclusion: "success" };
function fixture({ runs = [baseRun], jobs = [baseJob], current = baseRun, total, workflowRecord = workflow } = {}) {
const calls = [];
const api = async (path) => {
calls.push(path);
if (path.endsWith("/workflows/cloud-readiness.yml")) return workflowRecord;
if (path.includes("/workflows/123/runs?")) return { total_count: total ?? runs.length, workflow_runs: runs };
if (path.includes("/attempts/")) {
assert.ok(path.includes(`/attempts/${runs.at(-1).run_attempt}/jobs?`));
const page = Number(new URL("https://api.github.com" + path).searchParams.get("page"));
return { jobs: jobs.slice((page - 1) * 100, page * 100) };
}
if (path.endsWith("/runs/456")) return current;
throw new Error(`Unexpected API path: ${path}`);
};
return { api, calls };
}
test("source proof passes while image work is still running, or has failed", async () => {
for (const overrides of [{}, { status: "completed", conclusion: "failure" }]) {
const run = { ...baseRun, ...overrides };
const { api, calls } = fixture({ runs: [run], current: run });
assert.deepEqual(await readSourceVerification(sha, api), { sha, runId: 456, attempt: 2, jobId: 789 });
assert.ok(calls.some((path) => path.includes(`head_sha=${sha}&event=push&branch=master`)));
assert.ok(calls.some((path) => path.includes("/attempts/2/jobs?")));
}
});
test("only the expected workflow, repository, master push, and full SHA can supply proof", async () => {
for (const overrides of [
{ workflow_id: 999 }, { path: ".github/workflows/pr.yml" },
{ repository: { full_name: "other/paperclip" } }, { head_repository: { full_name: "fork/paperclip" } },
{ head_sha: "b".repeat(40) }, { head_branch: "feature" }, { event: "workflow_dispatch" },
{ run_attempt: undefined }, { run_attempt: 0 },
]) {
const { api, calls } = fixture({ runs: [{ ...baseRun, ...overrides }] });
assert.equal(await readSourceVerification(sha, api), undefined, JSON.stringify(overrides));
assert.equal(calls.length, 2);
}
});
test("a newer pending run cannot fall back to an older successful run", async () => {
const newer = { ...baseRun, id: 457, run_attempt: 1 };
const { api } = fixture({ runs: [baseRun, newer], jobs: [] });
assert.equal(await readSourceVerification(sha, api), undefined);
});
test("job identities and terminal failures fail closed", async () => {
for (const overrides of [
{ head_sha: "b".repeat(40) }, { run_id: 999 }, { run_attempt: 1 },
{ conclusion: "failure" }, { conclusion: "cancelled" }, { conclusion: "skipped" },
]) {
const { api } = fixture({ jobs: [{ ...baseJob, ...overrides }] });
await assert.rejects(readSourceVerification(sha, api), /did not pass/);
}
});
test("a rerun racing the jobs read cannot reuse the previous attempt", async () => {
const { api } = fixture({ current: { ...baseRun, run_attempt: 3 } });
assert.equal(await readSourceVerification(sha, api), undefined);
});
test("the versioned proof must exist and be unique", async () => {
for (const jobs of [[], [{ ...baseJob, name: "Cloud deployable v1" }]]) {
await assert.rejects(readSourceVerification(sha, fixture({ runs: [{ ...baseRun, status: "completed" }], jobs }).api), /did not pass/);
}
await assert.rejects(readSourceVerification(sha, fixture({ jobs: [baseJob, baseJob] }).api), /ambiguous/);
});
test("proof may appear after the first page of jobs", async () => {
const jobs = [...Array.from({ length: 100 }, (_, index) => ({ ...baseJob, name: `test ${index}` })), baseJob];
const { api, calls } = fixture({ jobs });
assert.equal((await readSourceVerification(sha, api)).jobId, 789);
assert.ok(calls.some((path) => path.endsWith("page=2")));
});
test("incomplete discovery and API failures cannot bless a release", async () => {
for (const total of [2, 101, -1]) {
await assert.rejects(readSourceVerification(sha, fixture({ total }).api), /incomplete/);
}
await assert.rejects(readSourceVerification(sha, fixture({ workflowRecord: { ...workflow, path: ".github/workflows/pr.yml" } }).api), /identity/);
await assert.rejects(readSourceVerification(sha, async () => { throw new Error("API unavailable"); }), /API unavailable/);
});
test("a missing or pending source proof waits and has a bounded deadline", async () => {
let time = 0;
let polls = 0;
const { api } = fixture({ runs: [] });
await assert.rejects(waitForSourceVerification(sha, {
api: async (path) => { if (path.endsWith(".yml")) polls += 1; return api(path); },
now: () => time, sleep: async (ms) => { time += ms; }, timeoutMs: 50, intervalMs: 30, log: () => {},
}), /timed out/);
assert.equal(time, 50);
assert.equal(polls, 2);
});
test("pending verification succeeds when its exact job completes", async () => {
let time = 0;
const pending = fixture({ jobs: [{ ...baseJob, status: "in_progress", conclusion: null }] });
const passed = fixture();
const proof = await waitForSourceVerification(sha, {
api: (path) => (time ? passed.api : pending.api)(path), now: () => time,
sleep: async (ms) => { time += ms; }, timeoutMs: 100, intervalMs: 30, log: () => {},
});
assert.equal(proof.sha, sha);
assert.equal(time, 30);
});
test("malformed source refs are rejected before any request", async () => {
for (const ref of ["master", "a".repeat(7), "A".repeat(40), undefined]) {
await assert.rejects(readSourceVerification(ref, () => assert.fail("must not request")), /full lowercase/);
}
});
+64
View File
@@ -0,0 +1,64 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { runInNewContext } from "node:vm";
import test from "node:test";
const require = createRequire(new URL("../packages/paperclip-runner/package.json", import.meta.url));
const source = readFileSync(require.resolve("@agentclientprotocol/codex-acp"), "utf8");
const helperStart = source.indexOf("function paperclipSandboxPolicy(");
const helperEnd = source.indexOf("\nvar CodexAcpClient", helperStart);
const methodStart = source.indexOf(" async sendPrompt(");
const methodEnd = source.indexOf("\n async runAgentFileChangeReport", methodStart);
assert.ok(helperStart >= 0 && helperEnd > helperStart, "installed Codex ACP must contain the network policy patch");
assert.ok(methodStart >= 0 && methodEnd > methodStart, "installed Codex ACP must expose the patched turn boundary");
async function turnPolicy(policy, env) {
const context = {
process: { env },
buildPromptItems: (prompt) => prompt,
addAdditionalDirectoriesToSandboxPolicy: (value) => value,
};
const client = runInNewContext(
source.slice(helperStart, helperEnd) + "\n({" + source.slice(methodStart, methodEnd) + "})",
context,
);
client.refreshSkills = async () => {};
client.codexClient = { runTurn: async (params) => params };
return client.sendPrompt(
{ sessionId: "fresh-or-resumed-thread", prompt: [] },
{ approvalPolicy: "on-request", sandboxPolicy: policy },
{ model: "test-model", effort: "low" },
null, false, "/workspace", [], undefined, undefined,
);
}
test("ACP forwards workspace networking to the actual Codex turn without changing confinement", async () => {
const policy = { type: "workspaceWrite", writableRoots: ["/workspace"], networkAccess: false };
const result = await turnPolicy(policy, { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true" });
assert.equal(result.sandboxPolicy.networkAccess, true);
assert.equal(result.sandboxPolicy.type, "workspaceWrite");
assert.equal(result.sandboxPolicy.writableRoots, policy.writableRoots);
assert.equal(result.approvalPolicy, "on-request");
assert.equal(policy.networkAccess, false, "must not mutate the shared mode preset");
});
test("ACP preserves operator and execution-target network denials", async () => {
const policy = { type: "workspaceWrite", networkAccess: true };
for (const env of [
{ PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" },
{ PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true", PAPERCLIP_RUNNER_NETWORK_ACCESS: "disabled" },
]) {
assert.equal((await turnPolicy(policy, env)).sandboxPolicy.networkAccess, false);
}
});
test("ACP leaves other sandbox modes and callers without a valid override unchanged", async () => {
for (const policy of [{ type: "readOnly", networkAccess: false }, { type: "dangerFullAccess" }]) {
assert.equal((await turnPolicy(policy, { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true" })).sandboxPolicy, policy);
}
const policy = { type: "workspaceWrite", networkAccess: false };
for (const value of [undefined, "", "1", "invalid"]) {
assert.equal((await turnPolicy(policy, { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: value })).sandboxPolicy, policy);
}
});
+1 -1
View File
@@ -12,7 +12,7 @@ const DURATIONS_MANIFEST = path.join(HERE, "e2e-shard-durations.json");
// Specs the default local_trusted Playwright project deliberately skips. Keep
// this in sync with `testIgnore` in tests/e2e/playwright.config.ts — the unit
// test in scripts/__tests__/e2e-shard.test.mjs fails if the two ever drift.
export const IGNORED_SPECS = ["multi-user.spec.ts", "multi-user-authenticated.spec.ts"];
export const IGNORED_SPECS = ["in-feed-native/**", "multi-user.spec.ts", "multi-user-authenticated.spec.ts"];
// Enumerates the specs the default e2e lane actually runs, as repo-relative
// paths so the output can be handed straight to `playwright test`.
@@ -1,6 +1,9 @@
{
"$comment": "Per-suite wall-clock durations (ms) for the general-server vitest lane, used by scripts/general-server-shard.mjs to balance suites across the PR shard matrix. Sampled from a real PR run of .github/workflows/pr.yml (actions run 32708351172, 2026-08-24) by diffing consecutive per-suite completion timestamps in the 'Run grouped general test suites' logs \u2014 that captures each suite's true serial cost (import + collect + tests), not just the vitest-reported test time. Suites missing here get the median weight, so the manifest only needs occasional refreshes.",
"$chatSample": "chat-channels.integration.test.ts: actions run 34405038082, job 102646337040, 2026-09-09. The first suite completed at 21:19:52.9026416Z after Vitest RUN at 21:09:07.5491992Z: 645354ms rounded up, including startup/import/collection; the 985 tests themselves took 629654ms. All 2972 tests in the shard passed, but the job exceeded its unchanged 20-minute bound during cleanup. Recording this missing heavy-suite weight lets the existing LPT partition reserve one of the existing five shards without changing test coverage, isolation, or deadlines.",
"$nativeRunnerSample": "native-codex-runner.integration.test.ts: actions run 34555686996, job 103127786254, 2026-09-11. Consecutive suite completions at 02:50:34.2830368Z and 02:55:07.9837588Z give 273701ms including import/collection (test body 270773ms). This previously unweighted suite made one four-way server shard take 14m38s; recording its cost lets the existing LPT partition balance it in both PR and release runs.",
"durations": {
"server/src/services/native-runtime/native-codex-runner.integration.test.ts": 273701,
"server/src/__tests__/access-service.test.ts": 4757,
"server/src/__tests__/access-validators.test.ts": 645,
"server/src/__tests__/activity-log-responsible-user.test.ts": 4407,
@@ -40,6 +43,7 @@
"server/src/__tests__/built-in-agents.test.ts": 10282,
"server/src/__tests__/bundled-plugins.test.ts": 235,
"server/src/__tests__/change-consent-gate.test.ts": 4215,
"server/src/__tests__/chat-channels.integration.test.ts": 645354,
"server/src/__tests__/claude-local-adapter-environment.test.ts": 1105,
"server/src/__tests__/claude-local-adapter.test.ts": 751,
"server/src/__tests__/claude-local-execute.test.ts": 3248,
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,74 @@
const REJECTION_REASONS = new Set([
"method",
"host",
"path",
"malformed_target",
]);
/**
* Observe only a classified rejection, without reading or resuming its body.
* The caller retains all HTTP policy and explicitly counts bytes it consumes.
* `bytes` means observed body bytes before the terminal response/abort event,
* not Content-Length and not bytes attributed from a shared keep-alive socket.
*/
export function observeChatWebhookProxyRejection(req, res, options = {}) {
const now = options.now ?? (() => performance.now());
const emit = options.emit ?? (() => {});
const connectionId =
Number.isSafeInteger(options.connectionId) && options.connectionId > 0
? options.connectionId
: null;
let start;
try {
start = now();
} catch {
start = 0;
}
let reason = null;
let bytes = 0;
let recorded = false;
const record = () => {
if (recorded) return;
recorded = true;
req.removeListener("aborted", record);
res.removeListener("finish", record);
res.removeListener("close", record);
if (reason === null) return;
try {
const elapsed = now() - start;
const result = emit({
event: "chat_webhook_proxy_rejection",
reason,
connectionId,
statusCode:
res.headersSent &&
Number.isInteger(res.statusCode) &&
res.statusCode >= 100 &&
res.statusCode <= 599
? res.statusCode
: null,
durationMs: Number.isFinite(elapsed)
? Math.min(86_400_000, Math.max(0, Math.round(elapsed * 1000) / 1000))
: 0,
bytes,
});
if (result instanceof Promise) void result.catch(() => {});
} catch {
// Diagnostics must not change the response or expose arbitrary errors.
}
};
req.once("aborted", record);
res.once("finish", record);
res.once("close", record);
return {
reject(value) {
if (!recorded && reason === null && REJECTION_REASONS.has(value))
reason = value;
},
countBytes(length) {
if (!recorded && Number.isSafeInteger(length) && length > 0) {
bytes = Math.min(Number.MAX_SAFE_INTEGER, bytes + length);
}
},
};
}
@@ -6,18 +6,88 @@ function writeMessage(message) {
process.stdout.write(`${JSON.stringify(message)}\n`);
}
let supportsTypedSessionFailure = false;
async function handleRequest(request) {
if (request.method === "initialize") {
process.stderr.write("Error handling request { method: 'nes/close' } { code: -32601 }\n");
const air = request.params?.clientCapabilities?._meta?.jetbrains?.air;
supportsTypedSessionFailure =
Number.isInteger(air?.version) &&
air.version >= 1 &&
Array.isArray(air?.capabilities) &&
air.capabilities.includes("sessionFailure");
process.stderr.write(
"Error handling request { method: 'nes/close' } { code: -32601 }\n",
);
process.stderr.write("paperclip-acp-echo-agent started\n");
return {
protocolVersion: 1,
agentCapabilities: { loadSession: false, sessionCapabilities: { close: {} } },
agentCapabilities: {
loadSession: false,
sessionCapabilities: { close: {} },
},
agentInfo: { name: "paperclip-acp-echo-agent", version: "1.0.0" },
};
}
if (request.method === "session/new") return { sessionId: randomUUID() };
if (request.method === "session/prompt") {
const typedFailureCanary = process.env.PAPERCLIP_ACPX_TYPED_FAILURE_CANARY;
if (typedFailureCanary) {
if (!supportsTypedSessionFailure) {
throw new Error(
"client did not advertise typed session-failure support",
);
}
const sessionFailure = {
id: `${request.params.sessionId}:error`,
revision: 1,
category: "request",
severity: "error",
title: typedFailureCanary,
actions: [],
};
writeMessage({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: request.params.sessionId,
update: {
sessionUpdate: "session_info_update",
_meta: { jetbrains: { air: { version: 1, sessionFailure } } },
},
},
});
return {
stopReason: "end_turn",
_meta: { jetbrains: { air: { version: 1, sessionFailure } } },
};
}
const typedWarningCanary = process.env.PAPERCLIP_ACPX_TYPED_WARNING_CANARY;
let responseMeta;
if (typedWarningCanary) {
if (!supportsTypedSessionFailure) {
throw new Error(
"client did not advertise typed session-failure support",
);
}
const sessionFailure = {
id: `${request.params.sessionId}:warning`,
revision: 1,
category: "connection",
severity: "warning",
title: typedWarningCanary,
actions: [],
};
responseMeta = { jetbrains: { air: { version: 1, sessionFailure } } };
writeMessage({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: request.params.sessionId,
update: { sessionUpdate: "session_info_update", _meta: responseMeta },
},
});
}
writeMessage({
jsonrpc: "2.0",
method: "session/update",
@@ -25,13 +95,24 @@ async function handleRequest(request) {
sessionId: request.params.sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: process.env.PAPERCLIP_ACPX_SPAWN_SMOKE ?? "missing" },
content: {
type: "text",
text: process.env.PAPERCLIP_ACPX_SPAWN_SMOKE ?? "missing",
},
},
},
});
return { stopReason: "end_turn" };
return {
stopReason: "end_turn",
...(responseMeta ? { _meta: responseMeta } : {}),
};
}
if (request.method === "session/close" || request.method === "session/set_mode" || request.method === "session/set_config_option") return {};
if (
request.method === "session/close" ||
request.method === "session/set_mode" ||
request.method === "session/set_config_option"
)
return {};
if (request.method === "session/cancel") return null;
throw new Error(`Unsupported ACP method: ${request.method}`);
}
@@ -42,10 +123,15 @@ lines.on("line", async (line) => {
try {
request = JSON.parse(line);
const result = await handleRequest(request);
if (request.id !== undefined && result !== null) writeMessage({ jsonrpc: "2.0", id: request.id, result });
if (request.id !== undefined && result !== null)
writeMessage({ jsonrpc: "2.0", id: request.id, result });
} catch (error) {
if (request?.id !== undefined) {
writeMessage({ jsonrpc: "2.0", id: request.id, error: { code: -32603, message: String(error?.message ?? error) } });
writeMessage({
jsonrpc: "2.0",
id: request.id,
error: { code: -32603, message: String(error?.message ?? error) },
});
}
}
});
@@ -0,0 +1,79 @@
#!/usr/bin/env node
// Deterministic ACP process for interruption and same-session continuation tests.
import fs from 'node:fs';
import { createHash, randomUUID } from 'node:crypto';
import { createInterface } from 'node:readline';
const root = process.env.PAPERCLIP_STOP_FIXTURE_ROOT ?? process.cwd();
const send = (message) => process.stdout.write(`${JSON.stringify(message)}\n`);
let active;
let timer;
if (process.env.PAPERCLIP_STOP_FIXTURE_HANG_ON_CLOSE === '1') {
process.on('SIGTERM', () => {});
setInterval(() => {}, 1000);
}
const update = (sessionId, value) => send({ jsonrpc: '2.0', method: 'session/update', params: { sessionId, update: value } });
async function request(message) {
fs.appendFileSync(`${root}/requests`, `${Date.now()} ${message.method}\n`);
switch (message.method) {
case 'initialize': return { protocolVersion: 1, agentCapabilities: { loadSession: true, sessionCapabilities: { close: {} } }, agentInfo: { name: 'stop-fixture', version: '1' } };
case 'session/new': {
const sessionId = randomUUID();
fs.writeFileSync(`${root}/session`, sessionId);
return { sessionId };
}
case 'session/load':
if (!fs.existsSync(`${root}/session`) || fs.readFileSync(`${root}/session`, 'utf8') !== message.params.sessionId) throw Error('Unknown session');
return {};
case 'session/prompt': {
fs.appendFileSync(`${root}/prompts`, `${JSON.stringify(message.params)}\n`);
fs.appendFileSync(`${root}/run-env`, `${JSON.stringify({ runId: process.env.PAPERCLIP_RUN_ID, tokenHash: createHash('sha256').update(process.env.PAPERCLIP_API_KEY ?? '').digest('hex'), scratchDir: process.env.PAPERCLIP_RUN_SCRATCH_DIR })}\n`);
if (fs.existsSync(`${root}/continued`)) {
const paused = JSON.stringify(message.params.prompt).includes('tree-hold interaction: yes');
if (!paused) {
fs.appendFileSync(`${root}/completed`, 'follow-up\n');
// Browser journeys finish the task through the normal agent API so
// the scheduler does not need a separate successful-run handoff.
if (process.env.PAPERCLIP_STOP_FIXTURE_FINISH_TASK === '1') {
const base = process.env.PAPERCLIP_API_URL.replace(/\/api\/?$/, '').replace(/\/$/, '');
const response = await fetch(`${base}/api/issues/${process.env.PAPERCLIP_TASK_ID}`, {
method: 'PATCH',
headers: { 'content-type': 'application/json', authorization: `Bearer ${process.env.PAPERCLIP_API_KEY}`, 'X-Paperclip-Run-Id': process.env.PAPERCLIP_RUN_ID },
body: JSON.stringify({ status: 'done' }),
});
if (!response.ok) throw new Error(`Task completion failed: ${response.status} ${await response.text()}`);
}
}
update(message.params.sessionId, { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: paused ? 'Task remains paused. Use Resume work to continue.' : 'Answered the pending follow-up once.' } });
return { stopReason: 'end_turn' };
}
active = message;
if (process.env.PAPERCLIP_STOP_FIXTURE_TOOL === 'read') {
update(message.params.sessionId, { sessionUpdate: 'tool_call', toolCallId: 'read-1', title: 'Read local file', kind: 'read', status: 'completed' });
}
if (process.env.PAPERCLIP_STOP_FIXTURE_TOOL === 'write') {
update(message.params.sessionId, { sessionUpdate: 'tool_call', toolCallId: 'write-1', title: 'Write local file', kind: 'edit', status: 'in_progress' });
timer = setInterval(() => fs.appendFileSync(`${root}/writes`, 'tick\n'), 20);
}
update(message.params.sessionId, { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'Waiting for Stop.' } });
return undefined;
}
case 'session/cancel':
clearInterval(timer);
fs.writeFileSync(`${root}/continued`, 'ready');
if (active) send({ jsonrpc: '2.0', id: active.id, result: { stopReason: 'cancelled' } });
active = undefined;
return undefined;
case 'session/close': clearInterval(timer); return {};
case 'session/set_mode': case 'session/set_config_option': return {};
default: throw Error(`Unsupported method: ${message.method}`);
}
}
createInterface({ input: process.stdin }).on('line', async (line) => {
const message = JSON.parse(line);
try {
const result = await request(message);
if (message.id !== undefined && result !== undefined) send({ jsonrpc: '2.0', id: message.id, result });
} catch (error) {
if (message.id !== undefined) send({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: error.message } });
}
});
+5 -5
View File
@@ -116,8 +116,8 @@ export function selectBundledDependencyPatches(
return selectedPatches;
}
export function applyBundledDependencyPatches(destinationDir, bundledDependencies) {
const rootPackage = JSON.parse(readFileSync(resolve(repoRoot, "package.json"), "utf8"));
export function applyBundledDependencyPatches(destinationDir, bundledDependencies, sourceRoot = repoRoot) {
const rootPackage = JSON.parse(readFileSync(resolve(sourceRoot, "package.json"), "utf8"));
const patchedDependencies = rootPackage.pnpm?.patchedDependencies ?? {};
for (const { packageName, patchPath } of selectBundledDependencyPatches(
@@ -129,14 +129,14 @@ export function applyBundledDependencyPatches(destinationDir, bundledDependencie
"patch",
["-p1", "--forward", "-d", resolve(destinationDir, "node_modules", packageName)],
{
input: readFileSync(resolve(repoRoot, patchPath)),
input: readFileSync(resolve(sourceRoot, patchPath)),
stdio: ["pipe", "inherit", "inherit"],
},
);
}
}
export function prepareBundledPackage(sourceDir, destinationDir) {
export function prepareBundledPackage(sourceDir, destinationDir, { sourceRoot = repoRoot } = {}) {
const sourcePackagePath = resolve(sourceDir, "package.json");
const sourcePackage = JSON.parse(readFileSync(sourcePackagePath, "utf8"));
const bundledDependencies = sourcePackage.bundleDependencies ?? sourcePackage.bundledDependencies ?? [];
@@ -166,7 +166,7 @@ export function prepareBundledPackage(sourceDir, destinationDir) {
{ cwd: destinationDir, stdio: "inherit" },
);
writeFileSync(deployedPackagePath, `${JSON.stringify(publishManifest, null, 2)}\n`);
applyBundledDependencyPatches(destinationDir, bundledDependencies);
applyBundledDependencyPatches(destinationDir, bundledDependencies, sourceRoot);
if (bundledDependencies.includes("acpx")) {
const acpxPackage = JSON.parse(
+209
View File
@@ -0,0 +1,209 @@
#!/usr/bin/env node
// Trusted release tooling. Packaging runs without publish credentials; publishing
// accepts only the two fixed package artifacts and never executes their scripts.
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, mkdirSync, cpSync, renameSync, appendFileSync } from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { gunzipSync } from "node:zlib";
import { createHash } from "node:crypto";
import { materializePublishManifest, prepareBundledPackage } from "./prepare-bundled-package.mjs";
export const versionFor = (sha) => {
if (!/^[0-9a-f]{40}$/.test(sha ?? "")) throw new Error("Preview builds require a full immutable commit SHA.");
return `0.0.0-preview.g${sha}`;
};
export function validateRequest(sha, requestId) {
versionFor(sha);
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(requestId ?? "")) throw new Error("A correlation UUID is required.");
}
export function previewManifest(pkg, sha) {
if (!["@paperclipai/shared", "@paperclipai/db"].includes(pkg.name)) throw new Error("Unexpected preview package.");
const version = versionFor(sha);
const exact = structuredClone(pkg);
for (const section of ["dependencies", "optionalDependencies", "peerDependencies"]) {
for (const [name, specifier] of Object.entries(exact[section] ?? {})) {
if (typeof specifier === "string" && specifier.startsWith("workspace:")) exact[section][name] = version;
}
}
const result = materializePublishManifest({ ...exact, version });
result.gitHead = sha;
result.paperclipPreviewCommit = sha;
if (pkg.name === "@paperclipai/db") result.dependencies = { ...result.dependencies, "@paperclipai/shared": version };
return result;
}
export function assertMetadata(pkg, name, sha) {
if (pkg?.publishConfig !== undefined || pkg?.name !== name || pkg.version !== versionFor(sha) || pkg.gitHead !== sha || pkg.paperclipPreviewCommit !== sha ||
(name === "@paperclipai/db" && pkg.dependencies?.["@paperclipai/shared"] !== versionFor(sha))) {
throw new Error("Preview package identity or dependency pin mismatch.");
}
}
export function tarManifest(bytes) {
const tar = gunzipSync(bytes, { maxOutputLength: 128 * 1024 * 1024 });
let manifest;
for (let offset = 0; offset + 512 <= tar.length;) {
const h = tar.subarray(offset, offset + 512);
if (h.every((v) => v === 0)) break;
const field = (start, size) => h.subarray(start, start + size).toString("utf8").split("\0")[0].trim();
const sizeText = field(124, 12);
if (!/^[0-7]+$/.test(sizeText)) throw new Error("Invalid package archive.");
const size = Number.parseInt(sizeText, 8);
if (offset + 512 + size > tar.length) throw new Error("Truncated package archive.");
const name = `${field(345, 155) ? field(345, 155) + "/" : ""}${field(0, 100)}`;
if (!name.startsWith("package/") || name.split("/").some((part) => part === "." || part === "..") || ![0, 48, 53].includes(h[156])) throw new Error("Unsupported package archive entry.");
if (name === "package/package.json") {
if (manifest || ![0, 48].includes(h[156])) throw new Error("Invalid package manifest entry.");
manifest = JSON.parse(tar.subarray(offset + 512, offset + 512 + size).toString("utf8"));
}
offset += 512 + Math.ceil(size / 512) * 512;
}
if (!manifest) throw new Error("Missing package manifest.");
return manifest;
}
export async function packageExists(name, sha, fetchImpl = fetch) {
const response = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(name)}/${versionFor(sha)}`, { signal: AbortSignal.timeout(30_000) });
if (response.status === 404) return false;
if (!response.ok) throw new Error(`npm lookup failed: HTTP ${response.status}`);
const pkg = await response.json();
assertMetadata(pkg, name, sha);
if (!pkg.dist?.integrity || !pkg.dist?.tarball) throw new Error("Published preview has no immutable distribution pin.");
return true;
}
export async function planArtifacts(sha, { migrator = false, image = true, fetchImpl = fetch } = {}) {
versionFor(sha);
return {
image: image && !await imageExists(sha, fetchImpl),
packages: migrator && !(await packageExists("@paperclipai/shared", sha, fetchImpl) && await packageExists("@paperclipai/db", sha, fetchImpl)),
};
}
export async function imageExists(sha, fetchImpl = fetch) {
versionFor(sha);
const tokenRes = await fetchImpl("https://ghcr.io/token?service=ghcr.io&scope=repository:paperclipai/paperclip:pull", { signal: AbortSignal.timeout(30_000) });
if (!tokenRes.ok) throw new Error(`GHCR lookup failed: HTTP ${tokenRes.status}`);
const { token } = await tokenRes.json();
if (typeof token !== "string") throw new Error("GHCR did not return a pull token.");
const base = "https://ghcr.io/v2/paperclipai/paperclip";
const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json" };
const get = (url) => fetchImpl(url, { headers, redirect: "error", signal: AbortSignal.timeout(30_000) });
let res = await get(`${base}/manifests/sha-${sha}-cloud`);
if (res.status === 404) return false;
if (!res.ok) throw new Error(`GHCR lookup failed: HTTP ${res.status}`);
let manifest = await res.json();
const digest = (value) => {
if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) throw new Error("Invalid image digest.");
return value;
};
if (Array.isArray(manifest.manifests)) {
const amd64 = manifest.manifests.find((entry) => entry.platform?.os === "linux" && entry.platform?.architecture === "amd64");
if (!amd64) throw new Error("Cloud image has no Linux amd64 manifest.");
res = await get(`${base}/manifests/${digest(amd64.digest)}`);
if (!res.ok) throw new Error(`GHCR manifest lookup failed: HTTP ${res.status}`);
manifest = await res.json();
}
res = await fetchImpl(`${base}/blobs/${digest(manifest.config?.digest)}`, { headers, redirect: "manual", signal: AbortSignal.timeout(30_000) });
// Registry blob storage may redirect to its signed storage URL. Follow only
// with no Authorization header, so the GHCR token cannot leave the registry.
if ([301, 302, 307, 308].includes(res.status)) {
const location = new URL(res.headers.get("location"));
if (location.protocol !== "https:" || location.username || location.password) throw new Error("Invalid registry blob redirect.");
res = await fetchImpl(location.href, { redirect: "error", signal: AbortSignal.timeout(30_000) });
}
if (!res.ok) throw new Error(`GHCR config lookup failed: HTTP ${res.status}`);
const config = await res.json();
if (config.config?.Labels?.["org.opencontainers.image.revision"] !== sha) throw new Error("Existing SHA image tag does not match the requested full commit.");
return true;
}
/** Publication loads image data, but never runs a container or source scripts. */
export async function publishImage(file, sha, { exec = execFileSync, fetchImpl = fetch } = {}) {
versionFor(sha);
const image = `ghcr.io/paperclipai/paperclip:sha-${sha}-cloud`;
if (await imageExists(sha, fetchImpl)) { console.log("Reusing the verified SHA cloud image."); return; }
exec("docker", ["load", "--input", path.resolve(file)], { encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
const [metadata] = JSON.parse(exec("docker", ["image", "inspect", image], { encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }));
if (metadata?.Config?.Labels?.["org.opencontainers.image.revision"] !== sha || metadata.Os !== "linux" || metadata.Architecture !== "amd64" ||
!/^sha256:[0-9a-f]{64}$/.test(metadata.Id ?? "")) throw new Error("Built image identity or platform does not match the request.");
// Push only this verified image ID under the one permitted tag, regardless
// of any additional tag names present in the untrusted Docker archive.
exec("docker", ["tag", metadata.Id, image], { stdio: "inherit" });
exec("docker", ["push", image], { stdio: "inherit" });
}
export function packPreview(source, output, sha, { exec = execFileSync } = {}) {
versionFor(sha);
source = path.resolve(source); output = path.resolve(output);
if (exec("git", ["rev-parse", "HEAD"], { cwd: source, encoding: "utf8" }).trim() !== sha) throw new Error("Source checkout differs from the requested commit.");
mkdirSync(output, { recursive: true });
for (const short of ["shared", "db"]) {
exec("pnpm", ["--filter", `@paperclipai/${short}`, "build"], { cwd: source, stdio: "inherit" });
const packageDir = path.join(source, "packages", short);
const originalText = readFileSync(path.join(packageDir, "package.json"), "utf8");
const original = JSON.parse(originalText);
const pkg = previewManifest(original, sha);
const staging = path.join(output, `package-${short}`);
if ((pkg.bundleDependencies ?? []).length) {
// The established helper materializes patched embedded-postgres instead
// of publishing pnpm's dependency symlinks.
writeFileSync(path.join(packageDir, "package.json"), JSON.stringify(pkg));
try { prepareBundledPackage(packageDir, staging, { sourceRoot: source }); }
finally { writeFileSync(path.join(packageDir, "package.json"), originalText); }
} else {
mkdirSync(staging, { recursive: true });
cpSync(path.join(packageDir, "dist"), path.join(staging, "dist"), { recursive: true });
writeFileSync(path.join(staging, "package.json"), JSON.stringify(pkg));
}
const packed = JSON.parse(exec("npx", ["--yes", "npm@10.9.7", "pack", "--ignore-scripts", "--json", "--pack-destination", output], { cwd: staging, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }));
renameSync(path.join(output, path.basename(packed[0].filename)), path.join(output, `${short}.tgz`));
assertMetadata(tarManifest(readFileSync(path.join(output, `${short}.tgz`))), `@paperclipai/${short}`, sha);
}
}
export async function publishPreview(dir, sha, { fetchImpl = fetch, exec = execFileSync, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
for (const short of ["shared", "db"]) {
const name = `@paperclipai/${short}`;
const file = path.resolve(dir, `${short}.tgz`);
const bytes = readFileSync(file);
assertMetadata(tarManifest(bytes), name, sha);
if (await packageExists(name, sha, fetchImpl)) { console.log(`Reusing ${name}@${versionFor(sha)}`); continue; }
console.log(`Publishing ${name}@${versionFor(sha)} (${createHash("sha256").update(bytes).digest("hex").slice(0, 12)})`);
// No package checkout, lifecycle scripts, npmrc, or branch code runs here.
exec("npm", ["publish", file, "--tag", "preview", "--access", "public", "--ignore-scripts", "--provenance", "--registry", "https://registry.npmjs.org"], { stdio: "inherit" });
let published = false;
for (let attempt = 0; attempt < 60; attempt++) {
if (await packageExists(name, sha, fetchImpl)) { published = true; break; }
await sleep(10_000);
}
if (!published) throw new Error("npm accepted the preview but it is not yet visible. Retry reuses published packages.");
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const [command, ...args] = process.argv.slice(2);
try {
if (command === "plan" || command === "plan-migrator") {
const [sha, requestId, migrator] = args;
validateRequest(sha, requestId);
if (process.env.GITHUB_REF !== "refs/heads/master") throw new Error("Preview workflow definitions must run from master.");
const { image, packages } = await planArtifacts(sha, {
image: command === "plan", migrator: command === "plan-migrator" || migrator === "true",
});
appendFileSync(process.env.GITHUB_OUTPUT, `image=${image}\npackages=${packages}\n`);
} else if (command === "pack") packPreview(...args);
else if (command === "publish") await publishPreview(...args);
else if (command === "publish-image") await publishImage(...args);
else if (command === "result") {
const [sha, requestId] = args;
validateRequest(sha, requestId);
if (!await imageExists(sha)) throw new Error("Cloud image is still missing.");
if (process.env.PREVIEW_MIGRATOR === "true" && !(await packageExists("@paperclipai/shared", sha) && await packageExists("@paperclipai/db", sha))) throw new Error("Preview packages are still missing.");
mkdirSync("stack-deploy-result", { recursive: true });
writeFileSync("stack-deploy-result/result.json", JSON.stringify({ version: 1, stage: "build", requestId, sha, status: "ready" }) + "\n");
} else throw new Error("Expected plan, plan-migrator, pack, publish, publish-image, or result.");
} catch (error) { console.error(error.message); process.exitCode = 1; }
}
+293
View File
@@ -0,0 +1,293 @@
import test from "node:test";
import assert from "node:assert/strict";
import { planArtifacts } from "./preview-artifacts.mjs";
import { readFileSync, mkdtempSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { gzipSync } from "node:zlib";
import { execFileSync, spawnSync } from "node:child_process";
import { previewManifest, assertMetadata, validateRequest, versionFor, tarManifest, packageExists, imageExists, publishPreview, publishImage } from "./preview-artifacts.mjs";
const sha = "a".repeat(40);
const id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
const manifest = (name) => previewManifest({ name, version: "0.0.0", dependencies: name.endsWith("/db") ? { "@paperclipai/shared": "workspace:*" } : {}, publishConfig: { exports: { ".": "./dist/index.js" } } }, sha);
function pack(pkg) {
const b = Buffer.from(JSON.stringify(pkg)); const h = Buffer.alloc(512);
h.write("package/package.json"); h.write(b.length.toString(8).padStart(11, "0"), 124, 11); h[156] = 48;
const padded = Buffer.alloc(Math.ceil(b.length / 512) * 512); b.copy(padded);
return gzipSync(Buffer.concat([h, padded, Buffer.alloc(1024)]));
}
const json = (body, status = 200) => new Response(JSON.stringify(body), { status });
test("preview request requires immutable SHA and correlation UUID", () => {
validateRequest(sha, id);
for (const ref of ["master", "origin/master", "a".repeat(7), "$(unsafe)", "A".repeat(40)]) assert.throws(() => versionFor(ref));
assert.throws(() => validateRequest(sha, "not-a-request"));
});
test("migrator-only planning never waits for GHCR and reuses complete exact-source packages", async () => {
for (const available of [[], ["@paperclipai/shared"], ["@paperclipai/shared", "@paperclipai/db"]]) {
const calls = [];
const result = await planArtifacts(sha, { image: false, migrator: true, fetchImpl: async (url) => {
assert.equal(new URL(url).hostname, "registry.npmjs.org");
const name = decodeURIComponent(new URL(url).pathname.split("/")[1]);
calls.push(name);
return available.includes(name) ? json({ ...manifest(name), dist: { integrity: "test-integrity", tarball: "https://registry.npmjs.org/package.tgz" } }) : json({}, 404);
} });
assert.deepEqual(result, { image: false, packages: available.length !== 2 });
assert.ok(calls.includes("@paperclipai/shared"));
if (available.length) assert.ok(calls.includes("@paperclipai/db"));
}
});
test("migrator-only planning rejects registry outages and mismatched source identity", async () => {
for (const response of [json({}, 403), json({}, 503), json({ ...manifest("@paperclipai/shared"), gitHead: "b".repeat(40) })]) {
await assert.rejects(planArtifacts(sha, { image: false, migrator: true, fetchImpl: async () => response }));
}
});
test("ordinary preview planning still requests a missing image without publishing unsolicited packages", async () => {
const result = await planArtifacts(sha, { fetchImpl: async (url) => {
assert.equal(new URL(url).hostname, "ghcr.io");
return url.includes("/token?") ? json({ token: "test-pull-token" }) : json({}, 404);
} });
assert.deepEqual(result, { image: true, packages: false });
});
test("preview manifests carry exact source, isolated versions and shared dependency", () => {
const pkg = manifest("@paperclipai/db");
assert.equal(pkg.version, `0.0.0-preview.g${sha}`);
assert.equal(pkg.dependencies["@paperclipai/shared"], pkg.version);
assert.deepEqual(pkg.exports, { ".": "./dist/index.js" });
assertMetadata(pkg, "@paperclipai/db", sha);
assert.throws(() => assertMetadata({ ...pkg, gitHead: "b".repeat(40) }, pkg.name, sha));
assert.throws(() => assertMetadata({ ...pkg, dependencies: { "@paperclipai/shared": "latest" } }, pkg.name, sha));
assert.deepEqual(tarManifest(pack(pkg)), pkg);
});
test("only 404 means an artifact is missing; auth and outages are fatal", async () => {
assert.equal(await packageExists("@paperclipai/db", sha, async () => json({}, 404)), false);
await assert.rejects(packageExists("@paperclipai/db", sha, async () => json({}, 403)));
await assert.rejects(packageExists("@paperclipai/db", sha, async () => json({}, 503)));
await assert.rejects(imageExists(sha, async () => json({}, 503)));
assert.equal(await imageExists(sha, async (url) => url.includes("/token?") ? json({ token: "test-pull-token" }) : json({}, 404)), false);
await assert.rejects(packageExists("@paperclipai/db", sha, async () => json({ ...manifest("@paperclipai/db"), gitHead: "b".repeat(40) })));
});
test("publishing reuses existing previews and never executes package lifecycle hooks", async () => {
const dir = mkdtempSync(path.join(tmpdir(), "preview-publish-test-"));
const published = new Set(["@paperclipai/shared"]);
const calls = [];
try {
for (const short of ["shared", "db"]) writeFileSync(path.join(dir, `${short}.tgz`), pack({ ...manifest(`@paperclipai/${short}`), scripts: { prepublishOnly: "do-not-run" } }));
await publishPreview(dir, sha, {
fetchImpl: async (url) => {
const name = decodeURIComponent(new URL(url).pathname.split("/")[1]);
return published.has(name) ? json({ ...manifest(name), dist: { integrity: "test-integrity", tarball: "https://registry.npmjs.org/package.tgz" } }) : json({}, 404);
},
exec: (command, args) => { calls.push({ command, args }); published.add("@paperclipai/db"); },
sleep: async () => {},
});
assert.equal(calls.length, 1);
assert.equal(calls[0].command, "npm");
assert.ok(calls[0].args.includes("--ignore-scripts"));
assert.equal(calls[0].args[calls[0].args.indexOf("--tag") + 1], "preview");
assert.ok(!calls[0].args.includes("canary"));
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test("preview workflow separates branch compilation from trusted publishing", () => {
const workflow = readFileSync(new URL("../.github/workflows/release.yml", import.meta.url), "utf8");
const builder = workflow.split(" package_preview:")[1].split(" publish_preview:")[0];
const publisher = workflow.split(" publish_preview:")[1].split(" image_preview:")[0];
const image = workflow.split(" image_preview:")[1].split(" publish_image_preview:")[0];
const imagePublisher = workflow.split(" publish_image_preview:")[1].split(" result_preview:")[0];
assert.doesNotMatch(builder, /id-token: write|packages: write|secrets\./);
assert.doesNotMatch(publisher, /ref: \$\{\{ inputs.source_ref|working-directory: source|pnpm install/);
assert.match(publisher, /environment: npm-canary/);
assert.match(image, /PAPERCLIP_BUILD_COMMIT=\$\{\{ inputs.source_ref \}\}/);
assert.doesNotMatch(image, /cache-(?:to|from):|canary-cloud|latest-cloud|packages: write|secrets\./);
assert.doesNotMatch(imagePublisher, /ref: \$\{\{ inputs.source_ref|docker\/build-push-action|pnpm install/);
assert.match(imagePublisher, /publish-image/);
assert.match(imagePublisher, /environment: npm-canary/);
assert.match(imagePublisher, /github.ref == 'refs\/heads\/master'/);
assert.match(publisher, /github.ref == 'refs\/heads\/master'/);
assert.doesNotMatch(workflow.split(" verify_canary:")[0], /uses: [^\n]+@v\d/);
assert.match(workflow, /Stack deploy \{0\} build/);
});
test("merge dispatch uses the existing publisher outside full-release concurrency without claiming image readiness", () => {
const dispatcher = readFileSync(new URL("../.github/workflows/cloud-artifacts.yml", import.meta.url), "utf8");
const release = readFileSync(new URL("../.github/workflows/release.yml", import.meta.url), "utf8");
assert.match(dispatcher, /branches: \[master\]/);
assert.match(dispatcher, /github.ref == 'refs\/heads\/master'/);
assert.match(dispatcher, /SOURCE_SHA: \$\{\{ github.sha \}\}/);
assert.match(dispatcher, /gh workflow run release.yml .*--ref master/);
assert.match(dispatcher, /--field channel=cloud-migrator/);
assert.doesNotMatch(dispatcher, /actions\/checkout|id-token: write|packages: write|secrets\./);
assert.match(release, /\(inputs.channel == 'preview' \|\| inputs.channel == 'cloud-migrator'\) && format\('\{0\}-\{1\}', inputs.channel, inputs.source_ref\)/);
const publisher = release.split(" publish_preview:")[1].split(" image_preview:")[0];
assert.match(publisher, /group: preview-package-publish-\$\{\{ inputs.source_ref \}\}/);
assert.match(publisher, /cancel-in-progress: false/);
assert.match(release, /PLAN_COMMAND: \$\{\{ inputs.channel == 'cloud-migrator' && 'plan-migrator' \|\| 'plan' \}\}/);
const result = release.split(" result_preview:")[1].split(" verify_canary:")[0];
assert.match(result, /always\(\) && inputs.channel == 'preview'/);
});
test("existing image reuse verifies the full revision behind the immutable tag", async () => {
const digest = "sha256:" + "b".repeat(64);
for (const revision of [sha, "c".repeat(40)]) {
const fetchImpl = async (url) => url.includes("/token?") ? json({ token: "test-pull-token" }) :
url.includes("/blobs/") ? json({ config: { Labels: { "org.opencontainers.image.revision": revision } } }) :
url.endsWith(digest) ? json({ config: { digest } }) : json({ manifests: [{ digest, platform: { os: "linux", architecture: "amd64" } }] });
if (revision === sha) assert.equal(await imageExists(sha, fetchImpl), true);
else await assert.rejects(imageExists(sha, fetchImpl), /full commit/);
}
});
test("image publisher verifies source and platform before pushing exactly one immutable tag", async () => {
for (const revision of [sha, "c".repeat(40)]) {
const calls = [];
const operation = publishImage("preview-image.tar", sha, {
fetchImpl: async (url) => url.includes("/token?") ? json({ token: "test-pull-token" }) : json({}, 404),
exec: (command, args) => {
calls.push({ command, args });
if (args[0] === "image") return JSON.stringify([{ Id: "sha256:" + "b".repeat(64), Os: "linux", Architecture: "amd64", Config: { Labels: { "org.opencontainers.image.revision": revision } } }]);
return "";
},
});
if (revision === sha) {
await operation;
assert.deepEqual(calls.filter((call) => call.args[0] === "push").map((call) => call.args), [["push", `ghcr.io/paperclipai/paperclip:sha-${sha}-cloud`]]);
} else { await assert.rejects(operation, /identity/); assert.ok(!calls.some((call) => call.args[0] === "push")); }
assert.ok(!calls.some((call) => ["run", "build"].includes(call.args[0])));
}
});
test("commits sharing a short prefix use separate full-SHA image addresses", async () => {
const urls = [];
const fetchImpl = async (url) => { urls.push(url); return url.includes("/token?") ? json({ token: "test-pull-token" }) : json({}, 404); };
const other = sha.slice(0, 7) + "b".repeat(33);
await imageExists(sha, fetchImpl);
await imageExists(other, fetchImpl);
assert.deepEqual(urls.filter((url) => url.includes("/manifests/")), [sha, other].map((commit) => `https://ghcr.io/v2/paperclipai/paperclip/manifests/sha-${commit}-cloud`));
});
test("cloud builds start per commit and preserve tag promotion dependencies", () => {
const docker = readFileSync(new URL("../.github/workflows/docker.yml", import.meta.url), "utf8");
const cloud = readFileSync(new URL("../.github/workflows/docker-cloud.yml", import.meta.url), "utf8");
const readiness = readFileSync(new URL("../.github/workflows/cloud-readiness.yml", import.meta.url), "utf8");
assert.match(readiness, /branches: \[master\]/);
assert.match(readiness, /uses: \.\/\.github\/workflows\/docker-cloud.yml/);
assert.doesNotMatch(cloud, /^ push:/m);
assert.match(cloud, /workflow_call:/);
assert.match(cloud, /group: docker-cloud-\$\{\{ github.sha \}\}/);
assert.match(cloud, /cancel-in-progress: false/);
assert.doesNotMatch(cloud, /uses: .*@v\d\b/);
assert.match(cloud, /cache-to: type=registry,ref=ghcr.io\/\$\{\{ github.repository \}\}:buildcache-cloud-\$\{\{ github.sha \}\},mode=max/);
const caller = docker.split(" build-and-push-cloud:")[1].split(" promote_canary_channel:")[0];
assert.match(caller, /if: github.event_name != 'push' \|\| github.ref != 'refs\/heads\/master'/);
assert.match(caller, /uses: .\/.github\/workflows\/docker-cloud.yml/);
assert.match(docker.split(" promote_canary_channel:")[1], /needs: \[merge-and-push, build-and-push-cloud\]/);
const reaping = cloud.indexOf(" - name: Verify cloud PID 1 reaps orphaned processes");
assert.ok(reaping > cloud.indexOf(" - name: Verify the pushed image resolves the declared Sentry version"));
assert.ok(reaping < cloud.indexOf(" - name: Publish verified full-SHA cloud tag"));
});
test("cloud builds bake the managed runtime identity and verify it before publication", () => {
const workflow = readFileSync(new URL("../.github/workflows/docker-cloud.yml", import.meta.url), "utf8");
const build = workflow.split(" - name: Build and push (cloud)")[1].split(" - name:")[0];
assert.match(build, /build-args: \|\n\s+USER_UID=1001\n\s+USER_GID=1001\n/);
const verify = workflow.indexOf(" - name: Verify cloud runtime user");
assert.ok(verify > workflow.indexOf(" - name: Verify the pushed image resolves the declared Sentry version"));
assert.ok(verify < workflow.indexOf(" - name: Publish verified full-SHA cloud tag"));
const step = workflow.slice(verify).split("\n - name:")[0];
assert.match(step, /IMAGE: ghcr.io\/\$\{\{ github.repository \}\}@\$\{\{ steps.build-cloud.outputs.digest \}\}/);
assert.doesNotMatch(step, /continue-on-error:|if:/);
assert.ok(step.indexOf('--entrypoint sh "$IMAGE"') < step.indexOf('-e USER_UID=1001 -e USER_GID=1001'));
for (const flag of ["u", "g"]) {
assert.ok(step.includes(`test "$(id -${flag} node)" = 1001`));
assert.ok(step.includes(`test "$(id -${flag})" = 1001`));
}
assert.ok(step.includes('test -w "$PAPERCLIP_HOME"'));
});
test("cloud cache imports are bounded, follow master ancestry, and retain the legacy fallback", () => {
const workflow = readFileSync(new URL("../.github/workflows/docker-cloud.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Select cloud cache ancestry")[1].split(" - name: Setup pnpm")[0];
const script = step.split(" run: |\n")[1].split("\n").map((line) => line.replace(/^ {10}/, "")).join("\n");
const dir = mkdtempSync(path.join(tmpdir(), "cloud-cache-test-"));
const output = path.join(dir, "output");
const env = { ...process.env, GIT_AUTHOR_NAME: "Test", GIT_AUTHOR_EMAIL: "test@example.test", GIT_COMMITTER_NAME: "Test", GIT_COMMITTER_EMAIL: "test@example.test" };
const git = (...args) => execFileSync("git", ["-c", "core.hooksPath=/dev/null", "-c", "commit.gpgsign=false", ...args], { cwd: dir, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
try {
git("init", "--initial-branch=master");
const commits = [];
for (let i = 0; i < 12; i++) {
git("commit", "--allow-empty", "-m", `main ${i}`);
commits.unshift(git("rev-parse", "HEAD"));
}
git("checkout", "-b", "topic", "HEAD~1");
git("commit", "--allow-empty", "-m", "topic");
git("checkout", "master");
git("merge", "--no-ff", "topic", "-m", "merge topic");
commits.unshift(git("rev-parse", "HEAD"));
const result = spawnSync("bash", ["-c", script], { cwd: dir, encoding: "utf8", env: { ...env, CACHE_IMAGE: "ghcr.io/paperclipai/paperclip", GITHUB_OUTPUT: output } });
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(readFileSync(output, "utf8").trim().split("\n"), [
"sources<<CACHE_SOURCES",
...commits.slice(0, 10).map((commit) => `type=registry,ref=ghcr.io/paperclipai/paperclip:buildcache-cloud-${commit}`),
"type=registry,ref=ghcr.io/paperclipai/paperclip:buildcache-cloud",
"CACHE_SOURCES",
]);
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test("normal cloud builds publish the checked digest only when source and platform match", () => {
const workflow = readFileSync(new URL("../.github/workflows/docker-cloud.yml", import.meta.url), "utf8");
const cloud = workflow.split(" build-and-push-cloud:")[1];
const verify = cloud.indexOf(" - name: Verify the pushed image resolves the declared Sentry version");
const publish = cloud.indexOf(" - name: Publish verified full-SHA cloud tag");
assert.ok(verify >= 0 && publish > verify);
const verification = cloud.slice(verify, publish);
assert.match(verification, /IMAGE: ghcr.io\/\$\{\{ github.repository \}\}@\$\{\{ steps.build-cloud.outputs.digest \}\}/);
assert.doesNotMatch(verification, /continue-on-error:|if: always\(/);
const step = cloud.slice(publish).split(/\n(?: #| - name:)/)[0];
assert.doesNotMatch(step, /continue-on-error:|if:/);
assert.match(step, /FULL_SHA_TAG: ghcr.io\/\$\{\{ github.repository \}\}:sha-\$\{\{ github.sha \}\}-cloud/);
const script = step.split(" run: |\n")[1].split("\n").map((line) => line.replace(/^ {10}/, "")).join("\n");
const dir = mkdtempSync(path.join(tmpdir(), "cloud-tag-test-"));
const image = `ghcr.io/paperclipai/paperclip@sha256:${"b".repeat(64)}`;
const tag = `ghcr.io/paperclipai/paperclip:sha-${sha}-cloud`;
try {
writeFileSync(path.join(dir, "docker"), `#!/bin/sh
case "$1 $2" in
'image inspect')
case "$5" in
*revision*) printf '%s\\n' "$TEST_REVISION" ;;
*) printf '%s\\n' "$TEST_PLATFORM" ;;
esac ;;
'buildx imagetools') printf '%s\\n' "$@" > "$TEST_CALLS" ;;
*) exit 99 ;;
esac
`, { mode: 0o755 });
for (const [revision, platform, succeeds] of [[sha, "linux/amd64", true], ["c".repeat(40), "linux/amd64", false], [sha, "linux/arm64", false]]) {
const calls = path.join(dir, "calls");
rmSync(calls, { force: true });
const result = spawnSync("bash", ["-c", script], { encoding: "utf8", env: {
...process.env, PATH: `${dir}${path.delimiter}${process.env.PATH}`, GITHUB_SHA: sha,
IMAGE: image, FULL_SHA_TAG: tag, TEST_REVISION: revision, TEST_PLATFORM: platform, TEST_CALLS: calls,
} });
if (succeeds) {
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(readFileSync(calls, "utf8").trim().split("\n"), ["buildx", "imagetools", "create", "--prefer-index=false", "--tag", tag, image]);
} else {
assert.notEqual(result.status, 0);
assert.throws(() => readFileSync(calls), { code: "ENOENT" });
}
}
} finally { rmSync(dir, { recursive: true, force: true }); }
});
+12 -3
View File
@@ -692,6 +692,14 @@ function walk(dir) {
}
walk(root);
// package.json is the pnpm 9 patch manifest for this repository. Hash the
// declared paths, including non-.patch filenames and patches outside patches/.
const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
for (const patch of Object.values(manifest.pnpm?.patchedDependencies ?? {})) {
if (typeof patch !== "string") throw new Error("Invalid pnpm patch path");
const file = path.resolve(root, patch);
if (!files.includes(file)) files.push(file);
}
files.sort((left, right) => path.relative(root, left).localeCompare(path.relative(root, right)));
const hash = crypto.createHash("sha256");
@@ -768,7 +776,7 @@ if [[ -f "$worktree_cwd/package.json" && -f "$worktree_cwd/pnpm-lock.yaml" ]]; t
}
run_pnpm_install() {
local stdout_path stderr_path
local stdout_path stderr_path exit_code
stdout_path="$(mktemp)"
stderr_path="$(mktemp)"
@@ -783,12 +791,13 @@ if [[ -f "$worktree_cwd/package.json" && -f "$worktree_cwd/pnpm-lock.yaml" ]]; t
cat "$stderr_path" >&2
rm -f "$stdout_path" "$stderr_path"
return 0
else
exit_code=$?
fi
local exit_code=$?
cat "$stdout_path"
cat "$stderr_path" >&2
if grep -q "ERR_PNPM_OUTDATED_LOCKFILE" "$stdout_path" "$stderr_path"; then
if grep -Eq "ERR_PNPM_(OUTDATED_LOCKFILE|LOCKFILE_CONFIG_MISMATCH)" "$stdout_path" "$stderr_path"; then
rm -f "$stdout_path" "$stderr_path"
return 90
fi
+51 -14
View File
@@ -1,11 +1,13 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readdirSync, realpathSync, statSync } from "node:fs";
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { loadShardDurations, selectGeneralServerShard } from "./general-server-shard.mjs";
import { assertSelectedTests, partitionTestLines } from "./test-line-shard.mjs";
const repoRoot = process.cwd();
const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
const generalServerShardDurations = loadShardDurations(
@@ -66,11 +68,15 @@ const serializedModeName = "serialized";
const generalModeName = "general";
const allModeName = "all";
const generalServerGroupName = "general-server";
const generalServerWithoutChatGroupName = "general-server-without-chat";
const generalChatGroupName = "general-chat";
const chatSuite = "server/src/__tests__/chat-channels.integration.test.ts";
const generalWorkspacesAGroupName = "general-workspaces-a";
const generalWorkspacesBGroupName = "general-workspaces-b";
const generalWorkspacesAProjects = ["@paperclipai/ui", "paperclipai"];
const generalWorkspacesBProjects = nonServerProjects.filter((project) => !generalWorkspacesAProjects.includes(project));
const generalGroupNames = [generalServerGroupName, generalWorkspacesAGroupName, generalWorkspacesBGroupName];
const allowedGeneralGroupNames = [...generalGroupNames, generalServerWithoutChatGroupName, generalChatGroupName];
const serializedServerVitestArgs = [
"--no-file-parallelism",
"--maxWorkers=1",
@@ -216,10 +222,10 @@ function parseCliOptions(argv) {
const shardAllowed =
mode === serializedModeName ||
(mode === generalModeName &&
(group === generalServerGroupName || group === generalWorkspacesAGroupName));
([generalServerGroupName, generalServerWithoutChatGroupName, generalChatGroupName, generalWorkspacesAGroupName].includes(group)));
if (!shardAllowed && shardIndex !== null) {
fail(
"--shard-index/--shard-count are only valid with --mode serialized, --mode general --group general-server, or --mode general --group general-workspaces-a.",
"--shard-index/--shard-count are only valid with serialized mode or a shardable general server/chat/workspaces-a group.",
);
}
@@ -227,8 +233,8 @@ function parseCliOptions(argv) {
fail("--group is only valid with --mode general.");
}
if (group !== null && !generalGroupNames.includes(group)) {
fail(`Unknown group "${group}". Expected one of: ${generalGroupNames.join(", ")}.`);
if (group !== null && !allowedGeneralGroupNames.includes(group)) {
fail(`Unknown group "${group}". Expected one of: ${allowedGeneralGroupNames.join(", ")}.`);
}
if (shardIndex !== null) {
@@ -271,22 +277,45 @@ function selectSerializedSuites(routeTests, shardIndex, shardCount) {
return shardFiles.map((file) => byRepoPath.get(file));
}
function runVitest(args, label) {
function runVitest(args, label, testShard = null) {
console.log(`\n[test:run] ${label}`);
invocationIndex += 1;
const tempRootParent = process.platform === "win32" ? os.tmpdir() : "/tmp";
// Canonical roots keep security fixtures valid on macOS, where /tmp is a symlink.
const testRoot = realpathSync(mkdtempSync(path.join(tempRootParent, `pcvt-${process.pid}-${invocationIndex}-`)));
// Production workspace/security checks reject symlink aliases. In particular
// /tmp is /private/tmp on macOS, so fixture roots must use the canonical path.
const testRoot = realpathSync(mkdtempSync(path.join(tempRootParent, "pv-")));
// Keep per-run paths compact so Unix socket fixtures stay under macOS path limits.
const env = {
...process.env,
NODE_ENV: "test",
PAPERCLIP_HOME: path.join(testRoot, "h"),
// Config discovery otherwise prefers the checkout's .paperclip/config.json
// over PAPERCLIP_HOME, importing preview scheduling policy into unit tests.
PAPERCLIP_CONFIG: path.join(testRoot, "h", "config.json"),
PAPERCLIP_INSTANCE_ID: `vt-${process.pid}-${invocationIndex}`,
TMPDIR: path.join(testRoot, "t"),
};
mkdirSync(env.PAPERCLIP_HOME, { recursive: true });
mkdirSync(env.TMPDIR, { recursive: true });
if (testShard) {
const collect = (filters, name) => {
const output = path.join(testRoot, `${name}.json`);
const result = spawnSync("pnpm", ["exec", "vitest", "list", ...sourceOnlyVitestArgs,
...filters, "--allowOnly=false", "--includeTaskLocation", `--json=${output}`], {
cwd: repoRoot, env, stdio: "inherit",
});
if (result.error || result.status !== 0) fail(`Vitest collection failed: ${result.error?.message ?? result.status}`);
return JSON.parse(readFileSync(output, "utf8"));
};
const collected = collect(args, "all");
const file = path.resolve(repoRoot, chatSuite);
const selected = partitionTestLines(collected, testShard.count, file)[testShard.index];
const filters = selected.lines.map((line) => `${chatSuite}:${line}`);
args = [...args.filter((arg) => arg !== chatSuite), ...filters];
assertSelectedTests(selected.tests, collect(args, "selected"), file);
console.log(`[test:run] chat shard ${testShard.index + 1}/${testShard.count}: ${selected.tests.length}/${collected.length} tests, ${selected.lines.length} source lines; exact filter coverage verified`);
args.push("--allowOnly=false");
}
const result = spawnSync("pnpm", ["exec", "vitest", "run", ...sourceOnlyVitestArgs, ...args], {
cwd: repoRoot,
env,
@@ -321,16 +350,23 @@ function runProjectGroup(projects, groupName, shardIndex = null, shardCount = nu
}
function runGeneralGroup(routeTests, groupName, shardIndex = null, shardCount = null) {
if (groupName === generalServerGroupName) {
if (groupName === generalChatGroupName) {
runVitest(["--project", "@paperclipai/server", ...serializedServerVitestArgs, chatSuite],
"chat integration test shard", { index: shardIndex ?? 0, count: shardCount ?? 1 });
return;
}
if (groupName === generalServerGroupName || groupName === generalServerWithoutChatGroupName) {
const withoutChat = groupName === generalServerWithoutChatGroupName;
const files = withoutChat ? generalServerTestFiles.filter((file) => file !== chatSuite) : generalServerTestFiles;
if (shardCount !== null && shardCount > 1) {
const shardFiles = selectGeneralServerShard(
generalServerTestFiles,
files,
shardIndex,
shardCount,
generalServerShardDurations,
);
console.log(
`\n[test:run] general-server shard ${shardIndex + 1}/${shardCount} running ${shardFiles.length} of ${generalServerTestFiles.length} suites`,
`\n[test:run] general-server shard ${shardIndex + 1}/${shardCount} running ${shardFiles.length} of ${files.length} suites`,
);
if (shardFiles.length === 0) {
return;
@@ -349,6 +385,7 @@ function runGeneralGroup(routeTests, groupName, shardIndex = null, shardCount =
}
const excludeRouteArgs = routeTests.flatMap((file) => ["--exclude", file.serverPath]);
if (withoutChat) excludeRouteArgs.push("--exclude", "src/__tests__/chat-channels.integration.test.ts");
runVitest(
[
"--project",
@@ -432,16 +469,16 @@ if (options.dryRun) {
shardIndex: options.shardIndex,
shardCount: options.shardCount,
group: options.group,
availableGeneralGroups: generalGroupNames,
availableGeneralGroups: allowedGeneralGroupNames,
serializedSuiteCount: routeTests.length,
selectedSerializedSuites: serializedSuites.map((routeTest) => routeTest.repoPath),
generalServerSuiteCount: generalServerTestFiles.length,
selectedGeneralServerSuites:
options.mode === generalModeName &&
options.group === generalServerGroupName &&
[generalServerGroupName, generalServerWithoutChatGroupName].includes(options.group) &&
options.shardCount !== null
? selectGeneralServerShard(
generalServerTestFiles,
options.group === generalServerWithoutChatGroupName ? generalServerTestFiles.filter((file) => file !== chatSuite) : generalServerTestFiles,
options.shardIndex,
options.shardCount,
generalServerShardDurations,
+9 -5
View File
@@ -13,6 +13,7 @@ import { createRunnerdCodexTransport, defaultCapabilityRunnerdBinary } from "../
import { createSkilllessCodexThreadConfig } from "../packages/paperclip-runner/src/drivers/codex/codex-app-server-driver.js";
import { AttemptJournal } from "../packages/paperclip-runner/src/evals/attempt-journal.js";
import { estimateModelCostNanodollars } from "../packages/paperclip-runner/src/evals/model-pricing.js";
import { CONNECTION_INTENT_AGENT_GUIDANCE } from "../packages/shared/src/connection-intent-guidance.js";
if (process.argv.includes("--catalog")) {
process.stdout.write(JSON.stringify(runnerApiCatalog()) + "\n");
@@ -64,7 +65,8 @@ try {
const isOpenRouter = OPENROUTER_MODELS.has(request.model);
const provider = isOpenRouter ? "opencode" : request.model === "claude-sonnet-5" ? "acpx" : "codex";
let providerVersion: string | null = null;
const fixture = await server.fixture({ mode: request.mode, apiToolsEnabled: request.arm !== "baseline", reset: true });
const fixture = await server.fixture({ mode: request.mode, apiToolsEnabled: request.arm !== "baseline", reset: true, connectionScenario: request.connectionScenario });
const initialState = await fixture.snapshot();
const substitutions = Object.fromEntries(Object.entries(fixture).filter(([, value]) => typeof value === "string"));
const expand = (value: any): any => typeof value === "string" ? value.replace(/\{\{(\w+)\}\}/g, (_, key) => String(substitutions[key] ?? (() => { throw new Error(`Unknown fixture variable ${key}`); })())) : Array.isArray(value) ? value.map(expand) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, expand(entry)])) : value;
const calls: Record<string, any>[] = [], notifications: unknown[] = [];
@@ -91,6 +93,7 @@ try {
try {
if (request.calls) {
for (const [i, call] of request.calls.entries()) await invoke(call.tool, expand(call.arguments), call.callId ?? `direct-${i}`);
usage = { inputTokens: 0, outputTokens: 0, cachedInputTokens: 0, estimatedCostNanodollars: 0, providerRequests: 0, accountingProvenance: "Provider-free direct authority contract; no provider or runnerd dispatched" };
} else {
if (request.model === "claude-sonnet-5" && (process.platform !== "linux" || process.arch !== "x64")) throw new Error("Qualified ACPX Claude requires Linux x64; no provider turn was dispatched");
if (!request.reservationId || request.maxCostUsd !== 0.5 || !["gpt-5.6-luna", "claude-sonnet-5", ...OPENROUTER_MODELS].includes(request.model)) throw new Error("Paid attempt requires ledger reservation and qualified model");
@@ -126,7 +129,7 @@ try {
completionContract: { revision: "runner-api-eval-v1", criterionIds: ["objective"] },
config: { ...createSkilllessCodexThreadConfig(fixture.workspace), model_reasoning_effort: "low" },
permissions: "paperclip-runner-workspace-only", runtimeWorkspaceRoots: [fixture.workspace], approvalPolicy: "never",
baseInstructions: "You are operating a disposable real Paperclip company. Use the provided tools to do the user's task. Do not use shell, network, skills, or credentials. Stop when the requested work is verified. " + (request.arm === "baseline" ? "" : "Prefer available dedicated tools. Only use search_api and call_api when no dedicated tool supports the required operation or parameters. Do not search before ordinary dedicated tool use."),
baseInstructions: "You are operating a disposable real Paperclip company. Use the provided tools to do the user's task. Do not use shell, network, skills, or credentials. Stop when the requested work is verified. " + (request.arm === "baseline" ? "" : "Prefer available dedicated tools. Only use search_api and call_api when no dedicated tool supports the required operation or parameters. Do not search before ordinary dedicated tool use.") + "\n" + CONNECTION_INTENT_AGENT_GUIDANCE,
dynamicTools: definitions, experimentalRawEvents: true, persistExtendedHistory: true,
});
if (request.preflight) {
@@ -198,14 +201,15 @@ try {
}
const artifact = {
schema: "paperclip-runner/eval-session-artifact/v1", attemptId: request.attemptId,
requestedModel: request.model ?? "provider-free", provider, driver: "real-server-api-tools",
requestedModel: request.calls ? "provider-free" : request.model ?? "provider-free", provider: request.calls ? "none" : provider, driver: request.calls ? "direct-authority-contract" : "real-server-api-tools",
evidenceMode: request.calls ? "provider-free-contract" : "live-provider",
providerSessionId: record(thread.thread).id ?? null, effectiveModel: record(thread.thread).model ?? null,
providerVersion: request.model === "gpt-5.6-luna" ? execFileSync("codex", ["--version"], { encoding: "utf8" }).trim() : providerVersion ?? record(evidence).providerVersion ?? null,
providerVersion: request.calls ? null : request.model === "gpt-5.6-luna" ? execFileSync("codex", ["--version"], { encoding: "utf8" }).trim() : providerVersion ?? record(evidence).providerVersion ?? null,
runtimeVersions: { node: process.versions.node, acpx: record(evidence).providerVersion, agentServer: record(evidence).agentServerVersion, agentRuntime: record(evidence).agentRuntimeVersion },
runtimeBuild,
timing: { startedAt, finishedAt: new Date().toISOString(), durationMs: performance.now() - started },
usage, accountingComplete: Boolean(request.calls) || !providerTurnStarted || terminalSeen, providerTurnStarted, observedProviderToolCalls: observedToolCallIds.size, diagnosticTail, error, calls, notifications, evidence, thread, prompt, arm: request.arm ?? "treatment",
fixture: substitutions, state: await fixture.snapshot(),
fixture: substitutions, initialState, state: await fixture.snapshot(),
toolSchemaBytes: Buffer.byteLength(JSON.stringify(definitions)), definitions,
};
// The controller supplies a unique retained attempt directory; never overwrite evidence.
+396
View File
@@ -0,0 +1,396 @@
#!/usr/bin/env node
// Opt-in transport diagnostic, NOT a signed Slack event or an agent smoke test.
import { Resolver } from "node:dns/promises";
import http from "node:http";
import https from "node:https";
import { BlockList, isIP } from "node:net";
import { performance } from "node:perf_hooks";
import { pathToFileURL } from "node:url";
export const TIMEOUT_MS = 8_000;
export const INERT_BODY = "{}";
// A real v0 signature has 64 hexadecimal hash characters. This cannot match it.
export const INVALID_SIGNATURE = "v0=ingress-canary-invalid";
const USAGE = `Usage: node scripts/smoke/chat-webhook-ingress.mjs --url HTTPS_SLACK_WEBHOOK
[--compare-url HTTP_LOOPBACK_SAME_PATH] [--relay-ip PUBLIC_IP]
No arguments or --help sends nothing. The public URL must use a DNS hostname
and /api/chat-webhooks/<public-id>/slack; no credentials, query or fragment.
The optional comparator must use literal 127.0.0.1 or [::1] with the same path.
The relay override pins one public IP while retaining the public hostname/SNI.
Without an override, DNS answers must all be public. HTTP requests are not retried.
One inert, invalid-signature POST per target; 8 seconds maximum per target.
No redirects, response bodies, cookies, credentials or environment proxy use.
Clear NODE_DEBUG/NODE_DEBUG_NATIVE and TLS tracing before network diagnostics.
Only 401 is an expected rejection; other statuses are unexpected (exit 1).
A 401 is compatible with reaching the signature verifier, not proof of signed
event admission or chat quality. Correlate with local verifier logs if needed.
Timings are cumulative milliseconds from each target's start; null means unseen.
Output excludes URLs, public IDs, addresses, headers and raw error messages.
`;
class ConfigurationError extends Error {
constructor(code) {
super(code);
this.code = code;
}
}
const privateV4 = new BlockList();
for (const [address, prefix] of [
["0.0.0.0", 8],
["10.0.0.0", 8],
["100.64.0.0", 10],
["127.0.0.0", 8],
["169.254.0.0", 16],
["172.16.0.0", 12],
["192.0.0.0", 24],
["192.0.2.0", 24],
["192.88.99.0", 24],
["192.168.0.0", 16],
["198.18.0.0", 15],
["198.51.100.0", 24],
["203.0.113.0", 24],
["224.0.0.0", 3],
])
privateV4.addSubnet(address, prefix, "ipv4");
const globalV6 = new BlockList();
globalV6.addSubnet("2000::", 3, "ipv6");
const specialV6 = new BlockList();
for (const [address, prefix] of [
["2001::", 23],
["2001:db8::", 32],
["2002::", 16],
["3fff::", 20],
])
specialV6.addSubnet(address, prefix, "ipv6");
export function isPublicAddress(value) {
if (typeof value !== "string" || /[%\s]/u.test(value)) return false;
const family = isIP(value);
if (family === 4) return !privateV4.check(value, "ipv4");
return (
family === 6 &&
globalV6.check(value, "ipv6") &&
!specialV6.check(value, "ipv6")
);
}
function parseUrl(raw, comparison = false) {
// Reject parser normalization, encoded path/header tricks and even empty ?/#.
if (
typeof raw !== "string" ||
/[\s\u0000-\u001f\u007f-\u009f%\\@?#]/u.test(raw)
) {
throw new ConfigurationError("invalid_url");
}
const match = /^(https?):\/\/([^/]+)(\/.*)$/u.exec(raw);
let url;
try {
url = new URL(raw);
} catch {
throw new ConfigurationError("invalid_url");
}
if (
!match ||
match[3] !== url.pathname ||
url.port === "0" ||
!/^\/api\/chat-webhooks\/[A-Za-z0-9_-]{16,128}\/slack$/u.test(url.pathname)
) {
throw new ConfigurationError("invalid_webhook_path");
}
if (comparison) {
// Exact literals only: no host-file, DNS, alternate integer IPv4 or rebinding.
if (!/^http:\/\/(127\.0\.0\.1|\[::1\])(?::[1-9][0-9]{0,4})?\//u.test(raw)) {
throw new ConfigurationError("comparison_must_be_loopback_http");
}
} else {
if (
url.protocol !== "https:" ||
isIP(url.hostname) ||
!/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/u.test(
url.hostname,
) ||
/\.(?:localhost|local|internal|invalid|test)$/u.test(url.hostname)
) {
throw new ConfigurationError("public_url_must_be_https_hostname");
}
}
return url;
}
export function parseArgs(argv) {
if (!Array.isArray(argv) || argv.some((item) => typeof item !== "string")) {
throw new ConfigurationError("invalid_arguments");
}
if (argv.length === 0 || (argv.length === 1 && argv[0] === "--help"))
return { help: true };
const values = new Map();
for (let index = 0; index < argv.length; index += 2) {
const key = argv[index];
if (
!["--url", "--compare-url", "--relay-ip"].includes(key) ||
values.has(key) ||
!argv[index + 1] ||
argv[index + 1].startsWith("--")
) {
throw new ConfigurationError("invalid_arguments");
}
values.set(key, argv[index + 1]);
}
if (!values.has("--url"))
throw new ConfigurationError("explicit_public_url_required");
const publicUrl = parseUrl(values.get("--url"));
const relayIp = values.get("--relay-ip") ?? null;
if (relayIp !== null && !isPublicAddress(relayIp))
throw new ConfigurationError("unsafe_relay_ip");
const targets = [
{ label: "public", url: publicUrl, relayIp, host: publicUrl.host },
];
if (values.has("--compare-url")) {
const url = parseUrl(values.get("--compare-url"), true);
if (url.pathname !== publicUrl.pathname)
throw new ConfigurationError("comparison_path_mismatch");
targets.push({
label: "comparison",
url,
relayIp: null,
host: publicUrl.host,
});
}
return { help: false, targets };
}
const NETWORK_CODES = new Set([
"ECONNREFUSED",
"ECONNRESET",
"ENETUNREACH",
"EHOSTUNREACH",
"ETIMEDOUT",
"ENOTFOUND",
"ENODATA",
"EAI_AGAIN",
"ESERVFAIL",
"ECANCELLED",
"EPIPE",
"ERR_TLS_CERT_ALTNAME_INVALID",
"CERT_HAS_EXPIRED",
"DEPTH_ZERO_SELF_SIGNED_CERT",
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
"HPE_HEADER_OVERFLOW",
"HPE_INVALID_HEADER_TOKEN",
]);
function safeNetworkCode(error) {
return NETWORK_CODES.has(error?.code) ? error.code : "NETWORK_ERROR";
}
// Injectable I/O is for local fixture tests only; the CLI always uses these native
// implementations. There is no CLI switch for TLS bypass or a private relay.
export async function probeTarget(
target,
{
createResolver = () => new Resolver({ timeout: TIMEOUT_MS, tries: 1 }),
request = target.label === "public" ? https.request : http.request,
timeoutMs = TIMEOUT_MS,
} = {},
) {
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > TIMEOUT_MS) {
throw new ConfigurationError("invalid_timeout");
}
const start = performance.now();
const result = {
target: target.label,
startedAt: new Date().toISOString(),
outcome: null,
status: null,
errorCode: null,
timingsMs: { dns: null, connect: null, tls: null, ttfb: null, total: null },
};
const elapsed = () => Math.round((performance.now() - start) * 1_000) / 1_000;
let resolver;
let agent;
let req;
let response;
let done = false;
return new Promise((resolve) => {
function finish(outcome, code = null) {
if (done) return;
done = true;
clearTimeout(deadline);
resolver?.cancel();
response?.destroy(); // Never consume, retain or display response content.
req?.destroy();
agent?.destroy();
result.outcome = outcome;
result.errorCode = code;
result.timingsMs.total = elapsed();
resolve(result);
}
// Covers DNS as well as TCP/TLS/header wait; destroy cancels actual I/O.
const deadline = setTimeout(
() => finish("timeout", "DEADLINE_EXCEEDED"),
timeoutMs,
);
async function send() {
let address = target.relayIp;
if (target.label === "public" && !address) {
resolver = createResolver();
const answers = await Promise.allSettled([
resolver.resolve4(target.url.hostname),
resolver.resolve6(target.url.hostname),
]);
if (done) return;
result.timingsMs.dns = elapsed();
const addresses = answers.flatMap((answer) =>
answer.status === "fulfilled" ? answer.value : [],
);
if (addresses.length === 0) {
finish(
"network_error",
safeNetworkCode(
answers.find((answer) => answer.status === "rejected")?.reason,
),
);
return;
}
if (!addresses.every(isPublicAddress)) {
finish("unsafe_resolution", "NON_PUBLIC_DNS_ANSWER");
return;
}
address = addresses[0]; // One pinned address, no fallback/retry.
}
if (done) return;
if (target.label === "comparison")
address = target.url.hostname.replace(/^\[|\]$/gu, "");
// A fresh explicit agent ignores global/env proxy agents and session caches.
const Agent = target.label === "public" ? https.Agent : http.Agent;
agent = new Agent({
keepAlive: false,
maxSockets: 1,
proxyEnv: {},
maxCachedSessions: 0,
});
function onResponse(res) {
response = res;
res.on("error", () => {}); // Raw response errors may contain private data.
if (done) {
res.destroy();
return;
}
result.status = res.statusCode ?? null;
result.timingsMs.ttfb = elapsed();
finish(
result.status === 401 ? "expected_rejection" : "unexpected_status",
);
}
req = request(
{
protocol: target.url.protocol,
hostname: target.url.hostname.replace(/^\[|\]$/gu, ""),
port: target.url.port || (target.label === "public" ? 443 : 80),
path: target.url.pathname,
method: "POST",
agent,
servername:
target.label === "public" ? target.url.hostname : undefined,
rejectUnauthorized: true,
autoSelectFamily: false,
family: isIP(address),
lookup: (_hostname, options, callback) => {
const selected = { address, family: isIP(address) };
callback(
null,
options?.all ? [selected] : address,
selected.family,
);
},
maxHeaderSize: 16_384,
headers: {
Host: target.host,
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(INERT_BODY),
"X-Slack-Request-Timestamp": String(Math.floor(Date.now() / 1_000)),
"X-Slack-Signature": INVALID_SIGNATURE,
Connection: "close",
},
},
onResponse,
);
req.once("upgrade", (res, socket) => {
socket.destroy(); // A 101 is unexpected too; never enter another protocol.
onResponse(res);
});
req.on("socket", (socket) => {
socket.once("connect", () => {
if (!done) result.timingsMs.connect = elapsed();
});
socket.once("secureConnect", () => {
if (!done) result.timingsMs.tls = elapsed();
});
});
req.on("error", (error) =>
finish("network_error", safeNetworkCode(error)),
);
req.end(INERT_BODY);
}
void send().catch((error) =>
finish("network_error", safeNetworkCode(error)),
);
});
}
export async function main(
argv,
{ write = (line) => process.stdout.write(line), probe = probeTarget } = {},
) {
let config;
try {
config = parseArgs(argv);
} catch (error) {
write(
`${JSON.stringify({ outcome: "invalid_configuration", errorCode: error instanceof ConfigurationError ? error.code : "INVALID_CONFIGURATION" })}\n`,
);
return 2;
}
if (config.help) {
write(USAGE);
return 0;
}
// Core's debug logging bypasses our redaction and can include request options.
// Refuse networking rather than trying to intercept global stdout/stderr.
if (
process.env.NODE_DEBUG?.trim() ||
process.env.NODE_DEBUG_NATIVE?.trim() ||
process.execArgv.some((arg) => /^--trace[-_]tls(?:=|$)/u.test(arg)) ||
/(?:^|[\s"'])--trace[-_]tls(?:[=\s"']|$)/u.test(
process.env.NODE_OPTIONS ?? "",
)
) {
write(
`${JSON.stringify({ outcome: "invalid_configuration", errorCode: "UNSAFE_DEBUG_ENVIRONMENT" })}\n`,
);
return 2;
}
let exitCode = 0;
for (const target of config.targets) {
try {
const result = await probe(target);
write(`${JSON.stringify(result)}\n`);
if (result.outcome !== "expected_rejection") exitCode = 1;
} catch {
// Never surface subprocess/network messages, stacks, paths or caller input.
write(
`${JSON.stringify({ target: target.label, outcome: "diagnostic_error", errorCode: "DIAGNOSTIC_ERROR" })}\n`,
);
exitCode = 1;
}
}
return exitCode;
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href
) {
process.exitCode = await main(process.argv.slice(2));
}
+771
View File
@@ -0,0 +1,771 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { EventEmitter, once } from "node:events";
import http from "node:http";
import https from "node:https";
import net from "node:net";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import test from "node:test";
import {
INERT_BODY,
INVALID_SIGNATURE,
TIMEOUT_MS,
isPublicAddress,
main,
parseArgs,
probeTarget,
} from "./chat-webhook-ingress.mjs";
const script = fileURLToPath(
new URL("./chat-webhook-ingress.mjs", import.meta.url),
);
const webhookPath = "/api/chat-webhooks/fixture-public-id-secret/slack";
const publicUrl = `https://canary.example.com:8443${webhookPath}`;
const secret = "PRIVATE_RESPONSE_CREDENTIAL_CANARY";
const runFile = promisify(execFile);
async function fixture(t, handler) {
const server = http.createServer(handler);
const sockets = new Set();
server.on("connection", (socket) => {
sockets.add(socket);
socket.once("close", () => sockets.delete(socket));
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
t.after(async () => {
for (const socket of sockets) socket.destroy();
await new Promise((resolve) => server.close(resolve));
});
const url = `http://127.0.0.1:${server.address().port}${webhookPath}`;
const target = parseArgs(["--url", publicUrl, "--compare-url", url])
.targets[1];
return { server, target, url };
}
function assertRedacted(value) {
const output = typeof value === "string" ? value : JSON.stringify(value);
for (const denied of [
secret,
"fixture-public-id-secret",
"canary.example.com",
"/api/chat-webhooks",
"127.0.0.1",
"http:",
"https:",
]) {
assert.equal(
output.includes(denied),
false,
`unexpected private output: ${denied}`,
);
}
}
test("no URL, help, invalid options and missing values never invoke networking", async () => {
for (const argv of [
[],
["--help"],
["--url"],
["--compare-url", publicUrl],
["--relay-ip", "8.8.8.8"],
["--url", publicUrl, "--url", publicUrl],
[publicUrl],
["--help", "--url", publicUrl],
["--url", publicUrl, "--headers", secret],
["--url", publicUrl, "--retry", "1"],
["--url", publicUrl, "--timeout", "999"],
["--url", publicUrl, "--"],
["--url", 123],
]) {
let calls = 0;
let output = "";
const status = await main(argv, {
write: (line) => {
output += line;
},
probe: () => {
calls++;
},
});
assert.equal(calls, 0);
assert.equal(status, argv.length === 0 || argv.join() === "--help" ? 0 : 2);
assert.equal(output.includes(secret), false);
assert.equal(output.includes("fixture-public-id-secret"), false);
}
});
for (const raw of [
`http://canary.example.com${webhookPath}`,
`https://user:password@canary.example.com${webhookPath}`,
`${publicUrl}?token=${secret}`,
`${publicUrl}#${secret}`,
`${publicUrl}?`,
`${publicUrl}#`,
`${publicUrl}\r\nAuthorization: ${secret}`,
`${publicUrl}\t`,
` ${publicUrl}`,
`https://canary.example.com/%2e%2e${webhookPath}`,
`https://canary.example.com/wrong/..${webhookPath}`,
`https://canary.example.com\\evil${webhookPath}`,
`https://canary.example.com${webhookPath.replace("/slack", "/telegram")}`,
`https://canary.example.com${webhookPath}/`,
`https://127.0.0.1${webhookPath}`,
`https://[::1]${webhookPath}`,
`https://canary.local${webhookPath}`,
`https://canary.internal${webhookPath}`,
`https://canary.example.com:0${webhookPath}`,
`https://canary.example.com:999999${webhookPath}`,
]) {
test(`rejects unsafe public URL variant ${[...raw].reduce((sum, char) => sum + char.codePointAt(0), 0)}`, async () => {
let output = "";
assert.equal(
await main(["--url", raw], {
write: (line) => {
output += line;
},
probe: () => assert.fail("networking"),
}),
2,
);
assertRedacted(output);
});
}
test("comparator is exact literal loopback HTTP with exactly the same path", () => {
for (const authority of ["127.0.0.1:3104", "[::1]:3104"]) {
assert.equal(
parseArgs([
"--url",
publicUrl,
"--compare-url",
`http://${authority}${webhookPath}`,
]).targets.length,
2,
);
}
for (const value of [
`https://127.0.0.1${webhookPath}`,
`http://localhost${webhookPath}`,
`http://127.1${webhookPath}`,
`http://2130706433${webhookPath}`,
`http://127.0.0.2${webhookPath}`,
`http://10.0.0.1${webhookPath}`,
`http://127.0.0.1:3104${webhookPath.replace("fixture-public", "another-public")}`,
`http://127.0.0.1:3104${webhookPath}\r\nCookie: ${secret}`,
])
assert.throws(() =>
parseArgs(["--url", publicUrl, "--compare-url", value]),
);
});
test("only unambiguous public relay IPs are accepted, including no IPv6 tunneling", () => {
for (const address of [
"8.8.8.8",
"209.177.145.192",
"2607:f740:f::b31",
"2001:4860:4860::8888",
]) {
assert.equal(isPublicAddress(address), true);
assert.equal(
parseArgs(["--url", publicUrl, "--relay-ip", address]).targets[0].relayIp,
address,
);
}
for (const address of [
"0.0.0.0",
"10.0.0.1",
"100.64.0.1",
"100.127.255.255",
"127.0.0.1",
"169.254.169.254",
"172.16.0.1",
"192.0.0.1",
"192.0.2.1",
"192.88.99.1",
"192.168.1.1",
"198.18.0.1",
"198.51.100.1",
"203.0.113.1",
"224.0.0.1",
"255.255.255.255",
"::",
"::1",
"fc00::1",
"fe80::1",
"ff02::1",
"::ffff:127.0.0.1",
"::ffff:8.8.8.8",
"64:ff9b::808:808",
"2001::1",
"2001:db8::1",
"2002:7f00:1::1",
"3fff::1",
"4000::1",
"fe80::1%en0",
"[2607:f740:f::b31]",
"8.8.8.8:443",
"008.008.008.008",
"0x08080808",
"134744072",
"example.com",
"8.8.8.8\r\nAuthorization: secret",
" 8.8.8.8",
"8.8.8.8 ",
"",
]) {
assert.equal(isPublicAddress(address), false);
assert.throws(() => parseArgs(["--url", publicUrl, "--relay-ip", address]));
}
});
test("401 is one inert POST, with no copied auth or response content", async (t) => {
let calls = 0;
let headers;
let method;
let path;
let body = "";
const { target } = await fixture(t, (req, res) => {
calls++;
headers = req.headers;
method = req.method;
path = req.url;
req.setEncoding("utf8");
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () =>
res
.writeHead(401, { "Set-Cookie": secret, "X-Request-Id": secret })
.end(secret),
);
});
const result = await probeTarget(target);
assert.equal(calls, 1);
assert.equal(method, "POST");
assert.equal(path, webhookPath);
assert.equal(body, INERT_BODY);
assert.equal(headers.host, "canary.example.com:8443");
assert.equal(headers.authorization, undefined);
assert.equal(headers.cookie, undefined);
assert.equal(headers["proxy-authorization"], undefined);
assert.equal(headers["x-slack-signature"], INVALID_SIGNATURE);
assert.equal(/^v0=[a-f0-9]{64}$/u.test(headers["x-slack-signature"]), false);
assert.ok(
Math.abs(
Number(headers["x-slack-request-timestamp"]) * 1_000 - Date.now(),
) < 2_000,
);
assert.equal(result.outcome, "expected_rejection");
assert.equal(result.status, 401);
assert.equal(result.errorCode, null);
assert.equal(result.timingsMs.dns, null);
assert.equal(result.timingsMs.tls, null);
assert.ok(result.timingsMs.connect <= result.timingsMs.ttfb);
assert.ok(result.timingsMs.ttfb <= result.timingsMs.total);
assertRedacted(result);
});
for (const status of [
101, 200, 202, 204, 301, 302, 307, 308, 400, 403, 404, 429, 500, 502,
]) {
test(`HTTP ${status} is unexpected; no redirect, retry or body wait`, async (t) => {
let destinationCalls = 0;
let calls = 0;
const destination = await fixture(t, (_req, res) => {
destinationCalls++;
res.end();
});
const { target } = await fixture(t, (_req, res) => {
calls++;
res.writeHead(status, {
Location: `${destination.url}?token=${secret}`,
"Set-Cookie": secret,
});
res.flushHeaders(); // Deliberately never end a body: the probe must discard it.
});
const result = await probeTarget(target, { timeoutMs: 1_000 });
assert.equal(result.status, status);
assert.equal(result.outcome, "unexpected_status");
assert.equal(calls, 1);
assert.equal(destinationCalls, 0);
assertRedacted(result);
let output = "";
assert.equal(
await main(["--url", publicUrl], {
write: (line) => {
output += line;
},
probe: async () => result,
}),
1,
);
assertRedacted(output);
});
}
test("hard deadline destroys a real held HTTP connection instead of retrying", async (t) => {
let calls = 0;
let close;
const closed = new Promise((resolve) => {
close = resolve;
});
const { target } = await fixture(t, (req) => {
calls++;
req.socket.once("close", close);
});
const result = await probeTarget(target, { timeoutMs: 200 });
assert.equal(result.outcome, "timeout");
assert.equal(result.errorCode, "DEADLINE_EXCEEDED");
assert.equal(result.status, null);
assert.equal(result.timingsMs.ttfb, null);
await closed;
assert.equal(calls, 1);
assert.equal(TIMEOUT_MS, 8_000);
await assert.rejects(probeTarget(target, { timeoutMs: 8_001 }));
assertRedacted(result);
});
test("default eight-second deadline cancels original DNS and forbids late request", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout"] });
let release4;
let release6;
let cancelled = 0;
let requests = 0;
const target = parseArgs(["--url", publicUrl]).targets[0];
const promise = probeTarget(target, {
createResolver: () => ({
resolve4: () =>
new Promise((resolve) => {
release4 = resolve;
}),
resolve6: () =>
new Promise((resolve) => {
release6 = resolve;
}),
cancel: () => {
cancelled++;
release4(["8.8.8.8"]);
release6([]);
},
}),
request: () => {
requests++;
assert.fail("late request");
},
});
t.mock.timers.tick(7_999);
assert.equal(cancelled, 0);
t.mock.timers.tick(1);
const result = await promise;
await Promise.resolve();
assert.equal(result.outcome, "timeout");
assert.equal(cancelled, 1);
assert.equal(requests, 0);
});
test("deadline also destroys an actual socket held during TLS handshake", async (t) => {
let close;
let connections = 0;
const closed = new Promise((resolve) => {
close = resolve;
});
const sockets = new Set();
const server = net.createServer((socket) => {
connections++;
sockets.add(socket);
socket.on("data", () => {}); // Consume the ClientHello, but never answer it.
socket.once("close", () => {
sockets.delete(socket);
close();
});
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
t.after(async () => {
for (const socket of sockets) socket.destroy();
await new Promise((resolve) => server.close(resolve));
});
const target = parseArgs(["--url", publicUrl, "--relay-ip", "8.8.8.8"])
.targets[0];
const result = await probeTarget(target, {
timeoutMs: 200,
// Test-only transport routes the real TLS client to this owned TCP fixture.
// It does not disable TLS verification or expose a CLI private-relay option.
request: (options, callback) =>
https.request(
{
...options,
port: server.address().port,
lookup: (_hostname, _options, callback) =>
callback(null, "127.0.0.1", 4),
},
callback,
),
});
assert.equal(result.outcome, "timeout");
assert.equal(result.status, null);
assert.notEqual(result.timingsMs.connect, null);
assert.equal(result.timingsMs.tls, null);
await closed;
assert.equal(connections, 1);
assertRedacted(result);
});
test("DNS private or mixed answers fail closed before any connection", async () => {
for (const addresses of [
["100.64.0.1"],
["8.8.8.8", "127.0.0.1"],
["::ffff:127.0.0.1"],
]) {
const result = await probeTarget(
parseArgs(["--url", publicUrl]).targets[0],
{
createResolver: () => ({
resolve4: async () => addresses,
resolve6: async () => [],
cancel() {},
}),
request: () => assert.fail("private network connection"),
},
);
assert.equal(result.outcome, "unsafe_resolution");
assert.equal(result.errorCode, "NON_PUBLIC_DNS_ANSWER");
assertRedacted(result);
}
});
test("public resolution/override pins one address but preserves Host, TLS SNI and verification", async () => {
for (const override of [null, "2607:f740:f::b31"]) {
const argv = [
"--url",
publicUrl,
...(override ? ["--relay-ip", override] : []),
];
let requests = 0;
let dnsCalls = 0;
let destroyed = 0;
const result = await probeTarget(parseArgs(argv).targets[0], {
createResolver: () => ({
resolve4: async () => {
dnsCalls++;
return ["8.8.8.8", "8.8.4.4"];
},
resolve6: async () => {
dnsCalls++;
return [];
},
cancel() {},
}),
request: (options, callback) => {
requests++;
assert.equal(options.hostname, "canary.example.com");
assert.equal(options.servername, "canary.example.com");
assert.equal(options.headers.Host, "canary.example.com:8443");
assert.equal(options.path, webhookPath);
assert.equal(options.rejectUnauthorized, true);
assert.equal(options.auth, undefined);
assert.equal(options.autoSelectFamily, false);
assert.ok(options.agent instanceof https.Agent);
assert.deepEqual(options.agent.options.proxyEnv, {});
options.lookup(options.hostname, {}, (error, address) => {
assert.equal(error, null);
assert.equal(address, override ?? "8.8.8.8");
});
const req = new EventEmitter();
req.destroy = () => {
destroyed++;
};
req.end = (body) => {
assert.equal(body, "{}");
const socket = new EventEmitter();
req.emit("socket", socket);
socket.emit("connect");
socket.emit("secureConnect");
const response = new EventEmitter();
response.statusCode = 401;
response.on("newListener", (event) => assert.notEqual(event, "data"));
response.read = () => assert.fail("response body must never be read");
response.destroy = () => {};
callback(response);
};
return req;
},
});
assert.equal(requests, 1);
assert.equal(dnsCalls, override ? 0 : 2);
assert.equal(destroyed, 1);
assert.equal(result.outcome, "expected_rejection");
assert.ok(result.timingsMs.connect <= result.timingsMs.tls);
assert.ok(result.timingsMs.tls <= result.timingsMs.ttfb);
assertRedacted(result);
}
});
test("failed pinned family is not retried with another DNS answer; asynchronous errors are redacted", async () => {
let calls = 0;
let destroys = 0;
let cancelled = 0;
const result = await probeTarget(parseArgs(["--url", publicUrl]).targets[0], {
createResolver: () => ({
resolve4: async () => ["8.8.8.8", "8.8.4.4"],
resolve6: async () => ["2001:4860:4860::8888"],
cancel: () => {
cancelled++;
},
}),
request: (options) => {
calls++;
options.lookup(options.hostname, { all: true }, (error, addresses) => {
assert.equal(error, null);
assert.deepEqual(addresses, [{ address: "8.8.8.8", family: 4 }]);
});
const request = new EventEmitter();
request.destroy = () => {
destroys++;
};
request.end = () =>
queueMicrotask(() =>
request.emit(
"error",
Object.assign(new Error(`${publicUrl} ${secret}`), {
code: "ENETUNREACH",
}),
),
);
return request;
},
});
assert.equal(result.outcome, "network_error");
assert.equal(result.errorCode, "ENETUNREACH");
assert.equal(calls, 1);
assert.equal(destroys, 1);
assert.equal(cancelled, 1);
assertRedacted(result);
});
test("network/DNS exceptions expose only closed error codes, never messages or stdout/stderr", async () => {
for (const code of ["ECONNRESET", "ENOTFOUND", secret, undefined]) {
const error = Object.assign(new Error(`${publicUrl} ${secret}`), {
code,
stdout: secret,
stderr: secret,
});
const target = parseArgs(["--url", publicUrl, "--relay-ip", "8.8.8.8"])
.targets[0];
const result = await probeTarget(target, {
request: () => {
throw error;
},
});
assert.equal(result.outcome, "network_error");
assert.equal(
result.errorCode,
["ECONNRESET", "ENOTFOUND"].includes(code) ? code : "NETWORK_ERROR",
);
assertRedacted(result);
const dnsResult = await probeTarget(
parseArgs(["--url", publicUrl]).targets[0],
{
createResolver: () => ({
resolve4: async () => {
throw error;
},
resolve6: async () => {
throw error;
},
cancel() {},
}),
request: () => assert.fail("failed DNS must not connect"),
},
);
assert.equal(dnsResult.outcome, "network_error");
assertRedacted(dnsResult);
let output = "";
assert.equal(
await main(["--url", publicUrl], {
write: (line) => {
output += line;
},
probe: async () => {
throw error;
},
}),
1,
);
assertRedacted(output);
}
});
test("actual socket failure is redacted and not retried", async (t) => {
let calls = 0;
const { target } = await fixture(t, (req) => {
calls++;
req.socket.destroy();
});
const result = await probeTarget(target);
assert.equal(result.outcome, "network_error");
assert.equal(result.errorCode, "ECONNRESET");
assert.equal(calls, 1);
assertRedacted(result);
});
test("CLI child and real comparator ignore proxy/auth environments and redact bad argv", async (t) => {
let proxyCalls = 0;
const proxy = await fixture(t, (_req, res) => {
proxyCalls++;
res.end(secret);
});
const proxyUrl = proxy.url
.replace(webhookPath, "")
.replace("http://", `http://user:${secret}@`);
const env = {
...process.env,
NODE_USE_ENV_PROXY: "1",
HTTP_PROXY: proxyUrl,
HTTPS_PROXY: proxyUrl,
ALL_PROXY: proxyUrl,
http_proxy: proxyUrl,
https_proxy: proxyUrl,
NO_PROXY: "",
no_proxy: "",
PAPERCLIP_AUTH_HEADER: `Bearer ${secret}`,
PAPERCLIP_COOKIE: secret,
SLACK_BOT_TOKEN: secret,
};
for (const argv of [
[],
["--help"],
["--url", `${publicUrl}\r\nCookie: ${secret}`],
]) {
let child;
try {
child = await runFile(process.execPath, [script, ...argv], {
env,
timeout: 5_000,
});
} catch (error) {
assert.equal(error.code, 2);
child = error;
}
assert.equal(child.stderr, "");
assert.equal(child.stdout.includes(secret), false);
assert.equal(child.stdout.includes("fixture-public-id-secret"), false);
}
// Explicitly poison Node's GLOBAL agent as --use-env-proxy does. The canary's
// fresh agent must still connect straight to the one local fixture.
const originalAgent = http.globalAgent;
http.globalAgent = new http.Agent({ proxyEnv: env });
t.after(() => {
http.globalAgent.destroy();
http.globalAgent = originalAgent;
});
const direct = await fixture(t, (req, res) => {
assert.equal(req.headers.authorization, undefined);
assert.equal(req.headers.cookie, undefined);
res.writeHead(401).end();
});
assert.equal(
(await probeTarget(direct.target)).outcome,
"expected_rejection",
);
assert.equal(proxyCalls, 0);
});
test("CLI refuses native debug output before starting a request", async () => {
for (const debug of [
{ NODE_DEBUG: "http,net,tls" },
{ NODE_DEBUG_NATIVE: "TLS" },
{ NODE_OPTIONS: "--trace-tls" },
]) {
const env = {
...process.env,
NODE_DEBUG: "",
NODE_DEBUG_NATIVE: "",
NODE_OPTIONS: "",
...debug,
};
const child = await runFile(
process.execPath,
[script, "--url", publicUrl, "--relay-ip", "8.8.8.8"],
{ env, timeout: 5_000 },
).catch((error) => error);
assert.equal(child.code, 2);
assert.equal(
JSON.parse(child.stdout).errorCode,
"UNSAFE_DEBUG_ENVIRONMENT",
);
assertRedacted(child.stdout);
assertRedacted(child.stderr);
}
});
for (const flag of ["--trace_tls", "--trace-tls=true", "--trace_tls=true"]) {
for (const source of ["argv", "environment"]) {
test(`refuses ${source} TLS tracing alias ${flag} without networking`, async (t) => {
const previousArgv = process.execArgv;
const previousOptions = process.env.NODE_OPTIONS;
t.after(() => {
process.execArgv = previousArgv;
if (previousOptions === undefined) delete process.env.NODE_OPTIONS;
else process.env.NODE_OPTIONS = previousOptions;
});
if (source === "argv") process.execArgv = [...previousArgv, flag];
else process.env.NODE_OPTIONS = flag;
let calls = 0;
let output = "";
const status = await main(["--url", publicUrl], {
write: (line) => {
output += line;
},
probe: async () => {
calls++;
return { outcome: "expected_rejection" };
},
});
assert.equal(calls, 0);
assert.equal(status, 2);
assert.equal(JSON.parse(output).errorCode, "UNSAFE_DEBUG_ENVIRONMENT");
assertRedacted(output);
});
}
}
test("two explicit targets run once in order and any unexpected result fails the command", async () => {
for (const statuses of [
[401, 401],
[502, 401],
[401, 202],
]) {
const calls = [];
let output = "";
const exit = await main(
[
"--url",
publicUrl,
"--compare-url",
`http://127.0.0.1:3104${webhookPath}`,
],
{
write: (line) => {
output += line;
},
probe: async (target) => {
const status = statuses[calls.length];
calls.push(target.label);
return {
target: target.label,
status,
outcome:
status === 401 ? "expected_rejection" : "unexpected_status",
};
},
},
);
assert.deepEqual(calls, ["public", "comparison"]);
assert.equal(exit, statuses.every((status) => status === 401) ? 0 : 1);
assert.equal(output.trim().split("\n").length, 2);
assertRedacted(output);
}
});
+45
View File
@@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import path from "node:path";
function validateTests(tests, file) {
assert.ok(Array.isArray(tests) && tests.length > 0, "Vitest must collect at least one test");
for (const test of tests) {
assert.equal(test.projectName, "@paperclipai/server", "unexpected test project");
assert.equal(path.resolve(test.file), path.resolve(file), "unexpected test file");
assert.ok(typeof test.name === "string" && test.name.length > 0, "missing test name");
assert.ok(Number.isSafeInteger(test.location?.line) && test.location.line > 0, "missing test source line");
}
}
// Keep all cases registered on one source line together, including it.each
// and loop-generated cases. Balance by collected case count, not line count.
export function partitionTestLines(tests, count, file) {
validateTests(tests, file);
assert.ok(Number.isSafeInteger(count) && count > 0, "invalid shard count");
const byLine = new Map();
for (const test of tests) {
const line = test.location.line;
if (!byLine.has(line)) byLine.set(line, []);
byLine.get(line).push(test);
}
assert.ok(byLine.size >= count, "each shard must contain a source line");
const groups = [...byLine].sort((a, b) => b[1].length - a[1].length || a[0] - b[0]);
const shards = Array.from({ length: count }, () => ({ lines: [], tests: [] }));
for (const [line, cases] of groups) {
const shard = shards.reduce((best, next) => next.tests.length < best.tests.length ? next : best);
shard.lines.push(line);
shard.tests.push(...cases);
}
for (const shard of shards) shard.lines.sort((a, b) => a - b);
return shards;
}
// Re-collect using the exact filters passed to the subsequent test run. A
// Vitest filtering change must fail here instead of silently dropping cases.
export function assertSelectedTests(expected, actual, file) {
validateTests(actual, file);
const identities = (tests) => tests.map((test) => JSON.stringify([
test.projectName, path.resolve(test.file), test.location.line, test.name,
])).sort();
assert.deepEqual(identities(actual), identities(expected), "Vitest filters must select exactly the assigned tests");
}
@@ -0,0 +1,515 @@
import assert from "node:assert/strict";
import { EventEmitter, once } from "node:events";
import { readFileSync } from "node:fs";
import http from "node:http";
import net from "node:net";
import test from "node:test";
import { observeChatWebhookProxyRejection } from "../lib/chat-webhook-proxy-diagnostics.mjs";
const allowedHost = "dottas-macbook-pro.tail29c1aa.ts.net:8443";
const allowedPath = "/api/chat-webhooks/PRIVATE_ENDPOINT_SENTINEL/slack";
const closedKeys = [
"bytes",
"connectionId",
"durationMs",
"event",
"reason",
"statusCode",
];
function assertPrivate(rows) {
assert.doesNotMatch(
JSON.stringify(rows),
/private|sentinel|authorization|forwarded|cookie|https?:\/\//i,
);
}
function fixtureHandler(rows, connections, upstreamPort) {
return (req, res) => {
const observation = observeChatWebhookProxyRejection(req, res, {
connectionId: connections.get(req.socket),
emit: (row) => rows.push(row),
});
const reject = (reason, status) => {
observation.reject(reason);
req.on("data", (chunk) => observation.countBytes(chunk.length));
res.writeHead(status).end();
req.resume();
};
let url;
try {
url = new URL(req.url ?? "/", "http://localhost");
} catch {
reject("malformed_target", 400);
return;
}
if (req.method !== "POST") return reject("method", 404);
if (req.headers.host?.toLowerCase() !== allowedHost)
return reject("host", 404);
if (
!/^\/api\/chat-webhooks\/[A-Za-z0-9_-]+\/(slack|github|telegram|microsoft-teams)\/?$/.test(
url.pathname,
)
)
return reject("path", 404);
const upstream = http.request(
{
hostname: "127.0.0.1",
port: upstreamPort,
method: req.method,
path: req.url,
headers: req.headers,
},
(reply) => {
res.writeHead(reply.statusCode, reply.headers);
reply.pipe(res);
},
);
upstream.on("error", () => {
if (!res.headersSent) res.writeHead(502);
res.end();
});
req.pipe(upstream);
};
}
async function harness(t, options = {}) {
const rows = [];
const connections = new WeakMap();
const upstreamRequests = [];
const upstream = http.createServer((req, res) => {
upstreamRequests.push({ path: req.url, host: req.headers.host });
req.on("error", () => {});
req.on("end", () => res.writeHead(202).end());
req.resume();
});
upstream.listen(0, "127.0.0.1");
await once(upstream, "listening");
let server;
if (process.env.PAPERCLIP_WEBHOOK_PROXY_UNDER_TEST) {
// Optional integration against the ignored local proxy; the portable suite
// needs no live deployment/configuration or ignored workspace artifacts.
const source = readFileSync(
process.env.PAPERCLIP_WEBHOOK_PROXY_UNDER_TEST,
"utf8",
).replace(/^import .*;\r?\n/gm, "");
const wrappedHttp = {
createServer(handler) {
server = http.createServer(handler);
return { on: (...args) => server.on(...args), listen() {} };
},
request(options, callback) {
assert.equal(options.hostname, "127.0.0.1");
assert.equal(options.port, 3137);
assert.equal(options.timeout, 30_000);
return http.request(
{ ...options, port: upstream.address().port },
callback,
);
},
};
new Function(
"http",
"console",
"observeChatWebhookProxyRejection",
"process",
source,
)(
wrappedHttp,
{
log: (line) => {
try {
rows.push(JSON.parse(line));
} catch {}
},
},
observeChatWebhookProxyRejection,
{ env: { PAPERCLIP_QA_FAIL_ONCE_GITHUB_PATH: options.faultPath } },
);
} else {
server = http.createServer(
fixtureHandler(rows, connections, upstream.address().port),
);
let nextId = 1;
server.on("connection", (socket) => connections.set(socket, nextId++));
}
const baseline = http.createServer();
assert.equal(server.listenerCount("clientError"), 0);
for (const key of [
"keepAliveTimeout",
"keepAliveTimeoutBuffer",
"headersTimeout",
"requestTimeout",
"timeout",
])
assert.equal(server[key], baseline[key]);
server.listen(0, "127.0.0.1");
await once(server, "listening");
const agent = new http.Agent({ keepAlive: true, maxSockets: 1 });
t.after(async () => {
agent.destroy();
server.closeAllConnections();
upstream.closeAllConnections();
await Promise.all([
new Promise((resolve) => server.close(resolve)),
new Promise((resolve) => upstream.close(resolve)),
]);
});
const request = ({
method = "POST",
path = allowedPath,
host = allowedHost,
body = "PRIVATE_BODY_SENTINEL",
headers = {},
} = {}) =>
new Promise((resolve, reject) => {
const req = http.request(
{
agent,
host: "127.0.0.1",
port: server.address().port,
method,
path,
headers: {
host,
authorization: "PRIVATE_AUTH_SENTINEL",
cookie: "PRIVATE_COOKIE_SENTINEL",
"content-length": Buffer.byteLength(body),
...headers,
},
},
(res) => {
res.resume();
res.on("end", () =>
resolve({ status: res.statusCode, socket: req.socket }),
);
},
);
req.on("error", reject);
req.end(body);
});
return { rows, server, upstreamRequests, request };
}
test(
"real keep-alive HTTP observes each rejection exactly once alongside accepted traffic",
{ timeout: 5_000 },
async (t) => {
const { rows, request, upstreamRequests } = await harness(t);
const first = await request();
assert.equal(first.status, 202);
for (const [input, reason, status] of [
[{ method: "PUT" }, "method", 404],
[{ host: "PRIVATE_HOST_SENTINEL.invalid" }, "host", 404],
[
{ path: "/PRIVATE_PATH_SENTINEL?secret=PRIVATE_QUERY_SENTINEL" },
"path",
404,
],
[{ path: "http://[PRIVATE_TARGET_SENTINEL" }, "malformed_target", 400],
]) {
const prior = rows.filter(
(row) => row.event === "chat_webhook_proxy_rejection",
).length;
const response = await request(input);
assert.equal(response.status, status);
assert.equal(response.socket, first.socket);
const observations = rows.filter(
(row) => row.event === "chat_webhook_proxy_rejection",
);
assert.equal(observations.length, prior + 1);
assert.equal(observations.at(-1).reason, reason);
assert.equal(observations.at(-1).statusCode, status);
assert.equal(observations.at(-1).connectionId, 1);
assert.deepEqual(Object.keys(observations.at(-1)).sort(), closedKeys);
}
const last = await request({
path: `${allowedPath}?token=PRIVATE_QUERY_SENTINEL`,
});
assert.equal(last.status, 202);
assert.equal(last.socket, first.socket);
assert.equal(upstreamRequests.length, 2);
assertPrivate(rows);
},
);
test(
"real Node parser retains native 400/431 and never reaches rejection observer",
{ timeout: 5_000 },
async (t) => {
const { server, rows, upstreamRequests } = await harness(t);
const exchange = (raw) =>
new Promise((resolve, reject) => {
const socket = net.createConnection({
host: "127.0.0.1",
port: server.address().port,
});
t.after(() => socket.destroy());
let response = "";
socket.setTimeout(2_000, () =>
socket.destroy(new Error("fixture timeout")),
);
socket.on("connect", () => socket.write(raw));
socket.on("data", (chunk) => {
response += chunk.toString();
});
socket.on("error", reject);
socket.on("close", () => resolve(response));
});
assert.match(
await exchange(
"INVALID_PRIVATE_METHOD_SENTINEL / HTTP/1.1\r\nHost: private.invalid\r\n\r\n",
),
/^HTTP\/1\.1 400 /,
);
assert.match(
await exchange(
`GET / HTTP/1.1\r\nHost: private.invalid\r\nX-Private: ${"x".repeat(http.maxHeaderSize + 1024)}\r\n\r\n`,
),
/^HTTP\/1\.1 431 /,
);
assert.equal(server.listenerCount("clientError"), 0);
assert.equal(upstreamRequests.length, 0);
assert.equal(
rows.filter((row) => row.event === "chat_webhook_proxy_rejection").length,
0,
);
assertPrivate(rows);
},
);
test("observer does not read/resume bodies and emits one closed event for abort then close", () => {
const req = new EventEmitter();
req.resume = () => assert.fail("observer must not resume request");
const res = Object.assign(new EventEmitter(), { headersSent: false });
const rows = [];
const observation = observeChatWebhookProxyRejection(req, res, {
connectionId: 4,
emit: (row) => rows.push(row),
});
assert.equal(req.listenerCount("data"), 0);
observation.reject("path");
observation.countBytes(17);
req.emit("aborted");
res.emit("close");
res.emit("finish");
assert.equal(rows.length, 1);
assert.equal(rows[0].bytes, 17);
assert.equal(rows[0].statusCode, null);
assert.equal(req.listenerCount("aborted"), 0);
assertPrivate(rows);
});
for (const failure of ["client_abort", "response_error"]) {
test(
`real HTTP ${failure} records one rejection without private error prose`,
{ timeout: 5_000 },
async (t) => {
const rows = [];
let signalBytes;
const bytesRead = new Promise((resolve) => {
signalBytes = resolve;
});
let response;
let signalObservation;
const observed = new Promise((resolve) => {
signalObservation = resolve;
});
const server = http.createServer((req, res) => {
response = res;
const observation = observeChatWebhookProxyRejection(req, res, {
connectionId: 1,
emit: (row) => {
rows.push(row);
signalObservation();
},
});
assert.equal(req.listenerCount("data"), 0);
observation.reject("path");
req.on("data", (chunk) => {
observation.countBytes(chunk.length);
signalBytes();
});
// The fixture deliberately holds its response so it can exercise the
// observer's abort/close path, not change the deployed rejection policy.
req.on("error", () => {});
res.on("error", () => {});
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
const socket = net.createConnection({
host: "127.0.0.1",
port: server.address().port,
});
socket.on("error", () => {});
const closed = once(socket, "close");
t.after(async () => {
socket.destroy();
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
});
await once(socket, "connect");
socket.write(
"POST /PRIVATE_PATH_SENTINEL HTTP/1.1\r\nHost: PRIVATE_HOST_SENTINEL.invalid\r\nContent-Length: 100\r\n\r\nabc",
);
await bytesRead;
if (failure === "client_abort") socket.destroy();
else response.destroy(new Error("PRIVATE_ERROR_SENTINEL"));
await Promise.all([observed, closed]);
await new Promise((resolve) => setImmediate(resolve));
assert.equal(server.listenerCount("clientError"), 0);
assert.equal(rows.length, 1);
assert.equal(rows[0].bytes, 3);
assert.equal(rows[0].statusCode, null);
assert.deepEqual(Object.keys(rows[0]).sort(), closedKeys);
assertPrivate(rows);
},
);
}
if (process.env.PAPERCLIP_WEBHOOK_PROXY_UNDER_TEST) {
test(
"wired proxy preserves its 1 MiB streaming limit and accepted timing",
{ timeout: 5_000 },
async (t) => {
const { rows, request } = await harness(t);
assert.equal(
(await request({ body: Buffer.alloc(1_048_576) })).status,
202,
);
assert.equal(
(await request({ body: Buffer.alloc(1_048_577) })).status,
413,
);
const timings = rows.filter(
(row) => row.event === "chat_webhook_proxy_timing",
);
assert.deepEqual(
timings.map((row) => [row.statusCode, row.outcome, row.bytes]),
[
[202, "completed", 1_048_576],
[413, "body_too_large", 1_048_577],
],
);
assert.equal(
rows.filter((row) => row.event === "chat_webhook_proxy_rejection")
.length,
0,
);
assertPrivate(rows);
},
);
test(
"wired proxy preserves exact one-shot GitHub fault without rejected-route consumption",
{ timeout: 5_000 },
async (t) => {
const faultPath = "/api/chat-webhooks/PRIVATE_ENDPOINT_SENTINEL/github";
const { rows, request, upstreamRequests } = await harness(t, {
faultPath,
});
assert.equal(
(
await request({
method: "PUT",
path: faultPath,
headers: { "x-github-event": "issue_comment" },
})
).status,
404,
);
assert.equal(
(
await request({
path: faultPath,
headers: { "x-github-event": "ping" },
})
).status,
202,
);
assert.equal(
(
await request({
path: faultPath,
headers: { "x-github-event": "issue_comment" },
})
).status,
503,
);
assert.equal(
(
await request({
path: faultPath,
headers: { "x-github-event": "issue_comment" },
})
).status,
202,
);
assert.equal(upstreamRequests.length, 2);
assert.equal(
rows.filter((row) => row.outcome === "qualification_injected_503")
.length,
1,
);
assert.equal(
rows.filter((row) => row.event === "chat_webhook_proxy_rejection")
.length,
1,
);
assertPrivate(rows);
},
);
}
test("closed labels, bounds and failing diagnostic sinks cannot change response handling", async () => {
for (const value of ["PRIVATE_REASON_SENTINEL", "method"]) {
const req = new EventEmitter();
const res = Object.assign(new EventEmitter(), {
headersSent: true,
statusCode: 404,
});
const rows = [];
let clock = 20;
const observation = observeChatWebhookProxyRejection(req, res, {
connectionId: "PRIVATE_ID_SENTINEL",
now: () => clock,
emit: (row) => rows.push(row),
});
observation.reject(value);
observation.countBytes(Infinity);
observation.countBytes(-1);
observation.countBytes("PRIVATE_BYTES_SENTINEL");
observation.countBytes(Number.MAX_SAFE_INTEGER);
observation.countBytes(1);
clock = -100;
res.emit("finish");
assert.equal(rows.length, value === "method" ? 1 : 0);
if (rows.length)
assert.deepEqual(rows[0], {
event: "chat_webhook_proxy_rejection",
reason: "method",
connectionId: null,
statusCode: 404,
durationMs: 0,
bytes: Number.MAX_SAFE_INTEGER,
});
assertPrivate(rows);
}
for (const emit of [
() => {
throw new Error("PRIVATE_ERROR_SENTINEL");
},
async () => {
throw new Error("PRIVATE_ERROR_SENTINEL");
},
]) {
const req = new EventEmitter();
const res = Object.assign(new EventEmitter(), {
headersSent: true,
statusCode: 404,
});
observeChatWebhookProxyRejection(req, res, { emit }).reject("method");
assert.doesNotThrow(() => res.emit("finish"));
}
await new Promise((resolve) => setImmediate(resolve));
});
@@ -0,0 +1,272 @@
// Opt-in real Codex qualification; no model turn or live account data.
// Run from the repository root:
// node --import ./server/node_modules/tsx/dist/loader.mjs scripts/tests/native-cleanup-paginated-codex.mjs
// PAPERCLIP_TEST_CODEX_BINARY may select the exact installed Codex 0.153.4 binary.
// Fresh synthetic fixture directories are retained for inspection; never reuse live homes.
import { spawn, execFileSync } from "node:child_process";
import {
mkdtemp,
readdir,
mkdir,
writeFile,
rename,
cp,
readFile,
readlink,
realpath,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, dirname, relative, isAbsolute } from "node:path";
import { createInterface } from "node:readline";
import { createHash } from "node:crypto";
import { rebaseRetainedNativeCleanupProviderHome } from "../../server/src/services/native-runtime/native-session-executor.ts";
const codexBinary = process.env.PAPERCLIP_TEST_CODEX_BINARY ?? "codex";
if (
execFileSync(codexBinary, ["--version"], {
encoding: "utf8",
timeout: 10000,
}).trim() !== "codex-cli 0.153.4"
) {
throw new Error(
"This opt-in qualification requires Codex CLI 0.153.4. Requalify deliberately before changing the pin.",
);
}
const home = await mkdtemp(join(tmpdir(), "paperclip-paginated-probe-"));
const methods = [];
async function withServer(home, callback) {
const child = spawn(codexBinary, ["app-server"], {
cwd: home,
env: {
PATH: process.env.PATH ?? "/usr/bin:/bin",
HOME: home,
CODEX_HOME: home,
},
stdio: ["pipe", "pipe", "pipe"],
});
const finished = new Promise((resolve) => {
child.once("exit", (code, signal) => resolve({ code, signal }));
child.once("error", (error) => resolve({ error }));
});
child.stderr.on("data", () => {});
const pending = new Map();
let next = 0;
createInterface({ input: child.stdout }).on("line", (line) => {
const frame = JSON.parse(line);
if (frame.id != null && pending.has(frame.id)) {
const { resolve, reject, timer } = pending.get(frame.id);
pending.delete(frame.id);
clearTimeout(timer);
frame.error
? reject(new Error(JSON.stringify(frame.error)))
: resolve(frame.result);
}
});
const rpc = (method, params) =>
new Promise((resolve, reject) => {
methods.push(method);
const id = ++next;
const timer = setTimeout(
() => reject(new Error("local_rpc_timeout")),
10000,
);
pending.set(id, { resolve, reject, timer });
child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
});
try {
await rpc("initialize", {
clientInfo: { name: "paperclip-fixture", version: "1" },
capabilities: { experimentalApi: true },
});
child.stdin.write(JSON.stringify({ method: "initialized" }) + "\n");
return await callback(rpc);
} finally {
let killTimer;
let deadline;
try {
child.stdin.end();
child.kill("SIGTERM");
killTimer = setTimeout(() => child.kill("SIGKILL"), 2000);
const exit = await Promise.race([
finished,
new Promise((_, reject) => {
deadline = setTimeout(
() => reject(new Error("fixture_child_exit_unproven")),
5000,
);
}),
]);
if (exit.error) throw exit.error;
} finally {
clearTimeout(killTimer);
clearTimeout(deadline);
for (const { timer } of pending.values()) clearTimeout(timer);
}
}
}
const result = await withServer(home, (rpc) =>
rpc("thread/start", { cwd: home, model: "gpt-5.6-luna", ephemeral: false }),
);
const threadId = result.thread.id;
const canonicalHome = await realpath(home);
const rolloutRelative = relative(canonicalHome, result.thread.path);
if (
!/^[a-f0-9-]{36}$/.test(threadId) ||
isAbsolute(rolloutRelative) ||
!rolloutRelative.startsWith("sessions/") ||
rolloutRelative.split("/").includes("..") ||
!rolloutRelative.endsWith(`-${threadId}.jsonl`)
) {
throw new Error("Provider fixture path escaped the exact temporary home");
}
const timestamp = new Date().toISOString();
// Same minimal paginated fixture shape as the pinned provider's own
// app-server/tests/common/rollout.rs. No model call or turn/start is sent.
const lines =
[
{
type: "session_meta",
payload: {
id: threadId,
session_id: threadId,
timestamp,
cwd: home,
originator: "codex",
cli_version: "0.153.4",
source: "cli",
model_provider: "openai",
selected_capability_roots: [],
history_mode: "paginated",
},
},
{
type: "response_item",
payload: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Synthetic retained fixture. No action requested.",
},
],
},
},
{
type: "event_msg",
payload: {
type: "user_message",
message: "Synthetic retained fixture. No action requested.",
kind: "plain",
},
},
]
.map((v, ordinal) => JSON.stringify({ timestamp, ordinal, ...v }))
.join("\n") + "\n";
await mkdir(dirname(result.thread.path), { recursive: true });
await writeFile(result.thread.path, lines, { flag: "wx" });
// Materialize the synthetic persisted thread through the real provider so
// SQLite, paginated history and rollout selection use the producer schema.
await withServer(canonicalHome, (rpc) =>
rpc("thread/resume", {
threadId,
cwd: canonicalHome,
model: "gpt-5.6-luna",
excludeTurns: true,
}),
);
const original = `${canonicalHome}-archive`;
await rename(canonicalHome, original);
const fingerprint = async (root) => {
const files = [];
async function visit(relative) {
for (const entry of (
await readdir(join(root, relative), { withFileTypes: true })
).sort((a, b) => a.name.localeCompare(b.name))) {
const p = join(relative, entry.name);
if (entry.isDirectory()) await visit(p);
else if (entry.isFile())
files.push([
p,
createHash("sha256")
.update(await readFile(join(root, p)))
.digest("hex"),
]);
else if (entry.isSymbolicLink())
files.push([p, "symlink", await readlink(join(root, p))]);
else throw new Error("unsupported_synthetic_fixture_entry");
}
}
await visit("");
return createHash("sha256").update(JSON.stringify(files)).digest("hex");
};
const originalFingerprint = await fingerprint(original);
const copied = `${canonicalHome}-copy`;
await cp(original, copied, {
recursive: true,
filter: (source) => !["tmp", ".tmp"].includes(source.split("/").at(-1)),
});
const resume = (rpc) =>
rpc("thread/resume", {
threadId,
cwd: copied,
model: "gpt-5.6-luna",
excludeTurns: true,
});
let red;
try {
await withServer(copied, resume);
} catch (error) {
red = error.message;
}
if (!red?.includes(`no rollout found for thread id ${threadId}`)) {
console.log(JSON.stringify({ red, canonicalHome, copied, threadId }));
throw new Error("expected_exact_missing_paginated_path");
}
rebaseRetainedNativeCleanupProviderHome(
copied,
canonicalHome,
threadId,
"staging",
);
const green = await withServer(copied, resume);
if (green.thread.id !== threadId || green.thread.historyMode !== "paginated")
throw new Error("wrong_resumed_identity");
rebaseRetainedNativeCleanupProviderHome(
copied,
canonicalHome,
threadId,
"canonical",
);
await rename(copied, canonicalHome);
const activated = await withServer(canonicalHome, (rpc) =>
rpc("thread/resume", {
threadId,
cwd: canonicalHome,
model: "gpt-5.6-luna",
excludeTurns: true,
}),
);
if (
activated.thread.id !== threadId ||
activated.thread.historyMode !== "paginated"
)
throw new Error("wrong_activated_identity");
if (methods.some((method) => method === "turn/start"))
throw new Error("unexpected_provider_turn");
if (originalFingerprint !== (await fingerprint(original)))
throw new Error("original_fixture_changed");
console.log(
JSON.stringify({
red: "exact_paginated_path_missing",
green: true,
activatedResume: true,
threadId,
historyMode: green.thread.historyMode,
methods,
original,
canonicalHome,
originalFingerprint,
originalSnapshotPreserved: true,
}),
);
@@ -0,0 +1,584 @@
// Opt-in real Codex qualification. No model turn, credentials or live history.
// node --import ./server/node_modules/tsx/dist/loader.mjs \
// scripts/tests/native-provider-startup-codex.mjs --run \
// --codex-binary /absolute/path/to/codex --expected-runner-sha256 <staged-hash>
// Fresh 0700 fixture directories and their synthetic evidence are retained.
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { createHash, randomUUID } from "node:crypto";
import {
access,
copyFile,
mkdir,
mkdtemp,
readFile,
realpath,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import {
DurablePrpControlPlane,
spawnRunner,
} from "../../packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts";
import {
createRunnerdCodexAppServerArgs,
defaultCapabilityRunnerdBinary,
} from "../../packages/paperclip-runner/src/live/runnerd-codex-transport.ts";
const args = process.argv.slice(2);
if (args.length === 0 || (args.length === 1 && args[0] === "--help")) {
console.log(
"Opt-in only: --run --codex-binary ABSOLUTE_CODEX --expected-runner-sha256 STAGED_SHA256. Creates retained synthetic fixtures; never sends turn/start.",
);
} else {
let fixture;
try {
assert.equal(args.length, 5, "invalid_arguments");
assert.equal(args[0], "--run", "explicit_run_required");
assert.equal(args[1], "--codex-binary", "explicit_codex_binary_required");
assert.ok(isAbsolute(args[2]), "absolute_codex_binary_required");
assert.equal(
args[3],
"--expected-runner-sha256",
"expected_staged_digest_required",
);
assert.match(args[4], /^[a-f0-9]{64}$/u, "invalid_expected_digest");
const codexBinary = await realpath(args[2]);
const runnerBinary = await realpath(defaultCapabilityRunnerdBinary());
// Never silently qualify a debug fallback or a caller-selected alternate runner.
assert.match(
runnerBinary,
/\/dist\/bin\/paperclip-runnerd$/u,
"normal_staged_runner_required",
);
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
const runnerSha256 = digest(await readFile(runnerBinary));
assert.equal(runnerSha256, args[4], "staged_runner_digest_changed");
const codexSha256 = digest(await readFile(codexBinary));
fixture = await realpath(
await mkdtemp(join(tmpdir(), "paperclip-real-startup-")),
);
const home = join(fixture, "empty-codex-home");
const workspace = join(fixture, "workspace");
const controlDirectory = join(fixture, "control-plane");
const runnerDirectory = join(fixture, "runner");
const snapshots = join(fixture, "before-reopen");
for (const directory of [home, workspace, snapshots])
await mkdir(directory, { mode: 0o700 });
// Pin file-only auth storage: this fixture must not consult a shared keychain.
await writeFile(
join(home, "config.toml"),
'cli_auth_credentials_store = "file"\n',
{ flag: "wx", mode: 0o600 },
);
const environment = {
PATH: `${dirname(codexBinary)}:/usr/bin:/bin`,
HOME: home,
CODEX_HOME: home,
LC_ALL: "C",
};
const version = execFileSync(codexBinary, ["--version"], {
cwd: workspace,
env: environment,
encoding: "utf8",
timeout: 10_000,
stdio: ["ignore", "pipe", "pipe"],
}).trim();
assert.equal(
version,
"codex-cli 0.153.4",
"qualified_codex_version_required",
);
const ledger = join(fixture, "provider-process-starts.txt");
const identityLedger = join(fixture, "provider-process-identities.txt");
const trace = join(fixture, "provider-trace.jsonl");
const shim = join(fixture, "record-and-exec-codex");
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
await writeFile(
shim,
`#!/bin/sh\nset -eu\nprintf '%s\\n' "$$" >> ${quote(ledger)}\nLC_ALL=C /bin/ps -p "$$" -o pid= -o pgid= -o lstart= >> ${quote(identityLedger)}\nexec ${quote(codexBinary)} "$@"\n`,
{ flag: "wx", mode: 0o700 },
);
environment.PAPERCLIP_PROVIDER_TRACE_PATH = trace;
environment.PAPERCLIP_PROVIDER_TRACE_MAX_BYTES = "1048576";
const identity = {
runnerInstanceId: `runner-${randomUUID()}`,
environmentLeaseId: `lease-${randomUUID()}`,
runId: randomUUID(),
normalizedSessionId: randomUUID(),
turnId: randomUUID(),
itemId: randomUUID(),
};
const missingThread = randomUUID();
const events = [];
const startupCommandStatuses = [];
const startupEvents = () =>
events.filter(
(event) =>
event.eventType === "harness.diagnostic" &&
event.payload.code === "provider_startup_ownership",
);
const makeCore = () =>
new DurablePrpControlPlane({
stateDirectory: controlDirectory,
identity,
expectedRunnerVersion: "0.3.0",
expectedRunnerDigest: `sha256:${runnerSha256}`,
onCommittedEvent: async (event) => {
events.push(structuredClone(event));
if (
event.eventType === "harness.diagnostic" &&
event.payload.code === "provider_startup_ownership"
) {
startupCommandStatuses.push(
core.store.state.commands.find(
(command) => command.commandId === "startup-open",
)?.status,
);
}
},
});
const readProvider = async () =>
JSON.parse(
await readFile(
join(runnerDirectory, "codex-provider-state.json"),
"utf8",
),
);
const readStarts = async () =>
(await readFile(ledger, "utf8")).trim().split("\n").map(Number);
const waitUntil = async (predicate, milliseconds = 20_000) => {
const deadline = performance.now() + milliseconds;
while (!(await predicate())) {
if (performance.now() >= deadline)
throw new Error("fixture_condition_timeout");
await delay(10);
}
};
const launch = (core, iteration) =>
spawnRunner({
connectUrl: core.connectUrl,
stateDirectory: runnerDirectory,
identity,
ticket: core.issueBootstrapTicket(60_000),
maxOutboxBytes: 16 * 1024 * 1024,
p0ReserveBytes: 1024 * 1024,
maxRuntimeMs: 10_000,
reconnectGraceMs: 1_000,
runnerBinaryPath: runnerBinary,
runnerVersion: "0.3.0",
runnerDigest: `sha256:${runnerSha256}`,
environment,
diagnosticsDirectory: join(fixture, `runner-diagnostics-${iteration}`),
});
// Join the exact ChildProcess on all paths. Signalling alone is never proof.
const stopAndJoin = async (handle) => {
if (!handle) return null;
let terminateTimer;
let killTimer;
let finalTimer;
try {
terminateTimer = setTimeout(() => handle.child.kill("SIGTERM"), 12_000);
killTimer = setTimeout(() => handle.child.kill("SIGKILL"), 14_000);
const finished = handle.completion;
const deadline = new Promise((_, reject) => {
finalTimer = setTimeout(() => {
reject(new Error("fixture_runner_exit_not_joined"));
}, 17_000);
});
return await Promise.race([finished, deadline]);
} finally {
clearTimeout(terminateTimer);
clearTimeout(killTimer);
clearTimeout(finalTimer);
}
};
// This is emergency hygiene for this fixture's recorded direct child only.
// A PID absence check is not wait(2), and never proves whole-tree retirement.
const retireRecordedProvider = async () => {
const unproven = () => new Error("fixture_provider_cleanup_unproven");
let recordedPids;
let recordedIdentities;
try {
recordedPids = await readStarts();
} catch {
throw unproven();
}
try {
recordedIdentities = (await readFile(identityLedger, "utf8"))
.trim()
.split("\n");
} catch {
throw unproven();
}
if (
recordedPids.length !== recordedIdentities.length ||
new Set(recordedPids).size !== recordedPids.length
)
throw unproven();
const normalize = (value) => value.trim().replaceAll(/\s+/gu, " ");
for (const [index, pid] of recordedPids.entries()) {
if (!Number.isSafeInteger(pid) || pid <= 1) throw unproven();
const expected = normalize(recordedIdentities[index]);
const [recordedPid, pgid, ...birth] = expected.split(" ");
if (
recordedPid !== String(pid) ||
pgid !== String(pid) ||
!/^[A-Z][a-z]{2} [A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2} \d{4}$/u.test(
birth.join(" "),
)
)
throw unproven();
const stillOwned = () => {
let output;
try {
output = execFileSync(
"/bin/ps",
[
"-ww",
"-p",
String(pid),
"-o",
"pid=",
"-o",
"pgid=",
"-o",
"lstart=",
"-o",
"command=",
],
{
env: { PATH: "/usr/bin:/bin", LC_ALL: "C" },
encoding: "utf8",
timeout: 1_000,
stdio: ["ignore", "pipe", "pipe"],
},
);
} catch (error) {
if (error?.status === 1 && String(error.stdout ?? "").trim() === "")
return false;
throw unproven();
}
const line = normalize(output);
if (!line.startsWith(`${expected} `)) throw unproven();
const command = line.slice(expected.length + 1);
if (
!(
command.startsWith(`${normalize(codexBinary)} `) ||
command.startsWith(`/bin/sh ${normalize(shim)} `)
) ||
!command.includes(`--cd ${normalize(workspace)} `)
)
throw unproven();
return true;
};
if (!stillOwned()) continue;
for (const signal of ["SIGTERM", "SIGKILL"]) {
if (!stillOwned()) break;
try {
process.kill(pid, signal);
} catch (error) {
if (error?.code !== "ESRCH") throw unproven();
}
const deadline = performance.now() + 2_000;
while (stillOwned() && performance.now() < deadline) await delay(25);
}
if (stillOwned()) throw unproven();
}
};
const finishIteration = async (activeCore, activeHandle) => {
try {
return await stopAndJoin(activeHandle);
} finally {
try {
await activeCore.stop();
} finally {
if (activeHandle) await retireRecordedProvider();
}
}
};
let core = makeCore();
let handle;
let firstExit;
const prepare = core.queueCommand(
"run.prepare",
{
provider: {
kind: "codex",
provider: "codex",
driver: "codex_app_server",
providerVersion: "0.153.4",
command: shim,
args: [
"--cd",
workspace,
...createRunnerdCodexAppServerArgs({
environment,
codexHome: home,
}),
],
cwd: workspace,
model: null,
providerSessionId: missingThread,
instructions:
"Synthetic startup qualification only. No model turn is authorized.",
approvalPolicy: "never",
externallySandboxed: false,
},
},
"startup-prepare",
);
const open = core.queueCommand("session.open", {}, "startup-open");
try {
await core.start();
handle = launch(core, 1);
// Atomic controller commits publish new snapshots. Observe commands by
// stable ID instead of waiting on the original queueCommand object.
await waitUntil(
() =>
(core.getCommand(open.commandId)?.status ?? "pending") !== "pending",
);
assert.equal(
core.getCommand(prepare.commandId)?.status,
"completed",
"prepare_must_succeed",
);
assert.equal(
core.getCommand(open.commandId)?.status,
"failed",
"missing_thread_must_fail",
);
assert.ok(
core
.getCommand(open.commandId)
?.result?.result?.message?.includes(
`no rollout found for thread id ${missingThread}`,
),
"exact_missing_rollout_required",
);
// The callback completed each durable event commit before failed-command receipt.
assert.deepEqual(
startupEvents().map((event) => event.payload.startup.phase),
["intent", "spawned", "initialization_failed"],
);
assert.deepEqual(startupCommandStatuses, [
"pending",
"pending",
"pending",
]);
} finally {
firstExit = await finishIteration(core, handle);
}
const providerBefore = await readProvider();
const facts = startupEvents().map((event) => event.payload.startup);
const [intent, spawned, failed] = facts;
const starts = await readStarts();
assert.equal(starts.length, 1, "exactly_one_real_provider_process");
assert.ok(
Number.isInteger(starts[0]) && starts[0] > 0,
"valid_recorded_pid_required",
);
for (const fact of facts) {
assert.equal(fact.schema, "paperclip.provider_startup.v1");
assert.equal(fact.launchId, intent.launchId);
assert.equal(fact.requestedThreadId, missingThread);
assert.equal(fact.authenticatedThreadId, null);
assert.equal(fact.processTreeRetired, false);
assert.equal(fact.origin.runId, identity.runId);
assert.equal(fact.origin.runnerInstanceId, identity.runnerInstanceId);
assert.equal(fact.command.commandId, open.commandId);
assert.equal(fact.command.controllerSeq, open.controllerSeq);
assert.equal(
fact.configurationFingerprint,
intent.configurationFingerprint,
);
}
assert.equal(spawned.processId, starts[0]);
assert.equal(spawned.processGroupId, starts[0]);
assert.equal(spawned.directChildExitObserved, false);
assert.equal(failed.processId, starts[0]);
assert.equal(failed.failedStage, "thread_open");
assert.equal(failed.directChildExitObserved, true);
assert.notEqual(failed.exitCode === null, failed.signal === null);
assert.deepEqual(providerBefore.startupAttempt, failed);
assert.equal(providerBefore.providerProcessGeneration, 0);
const traceBefore = await readFile(trace);
const traceRows = traceBefore
.toString("utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line));
assert.equal(
traceRows.at(-1)?.kind,
"trace_status",
"trace_terminal_status_required",
);
assert.equal(
traceRows.at(-1)?.status,
"complete",
"complete_provider_trace_required",
);
assert.deepEqual(
traceRows.map((row) => row.debugSequence),
traceRows.map((_, index) => index + 1),
"complete_trace_sequence_required",
);
const sent = traceRows
.filter(
(row) => row.kind === "frame" && row.direction === "client_to_provider",
)
.map((row) =>
JSON.parse(Buffer.from(row.rawBase64, "base64").toString("utf8")),
);
assert.deepEqual(
sent.map((frame) => frame.method),
["initialize", "initialized", "thread/resume"],
);
assert.equal(sent[2].params.threadId, missingThread);
await assert.rejects(access(join(home, "auth.json")), { code: "ENOENT" });
for (const [source, name] of [
[
join(controlDirectory, "control-plane-state.json"),
"control-plane-state.json",
],
[join(runnerDirectory, "runner-state.json"), "runner-state.json"],
[
join(runnerDirectory, "codex-provider-state.json"),
"codex-provider-state.json",
],
])
await copyFile(source, join(snapshots, name));
const firstFailure = structuredClone(
core.getCommand(open.commandId)?.result,
);
const ledgerBefore = await readFile(ledger);
const identityLedgerBefore = await readFile(identityLedger);
core = makeCore();
const snapshot = core.queueCommand(
"session.snapshot",
{},
"reopened-snapshot",
);
const reopen = core.queueCommand("session.open", {}, "reopened-open");
handle = null;
let secondExit;
try {
await core.start();
handle = launch(core, 2);
await waitUntil(
() =>
(core.getCommand(snapshot.commandId)?.status ?? "pending") !==
"pending" &&
(core.getCommand(reopen.commandId)?.status ?? "pending") !==
"pending",
);
for (const queued of [snapshot, reopen]) {
const command = core.getCommand(queued.commandId);
assert.ok(command, "reopened_command_must_exist");
assert.equal(command.status, "failed");
assert.ok(
command.result?.result?.message?.includes(
"provider startup ownership remains unadmitted",
),
"persisted_startup_fence_required",
);
}
} finally {
secondExit = await finishIteration(core, handle);
}
assert.deepEqual(
await readFile(ledger),
ledgerBefore,
"reopen_started_another_provider_process",
);
assert.deepEqual(
await readFile(identityLedger),
identityLedgerBefore,
"reopen_changed_process_identity_ledger",
);
assert.deepEqual(
await readFile(trace),
traceBefore,
"reopen_sent_provider_rpc",
);
assert.deepEqual(
(await readProvider()).startupAttempt,
failed,
"original_startup_receipt_changed",
);
assert.deepEqual(
core.store.state.commands.find(
(command) => command.commandId === open.commandId,
).result,
firstFailure,
);
assert.ok(
core.store.state.commands.every(
(command) => !command.type.startsWith("turn."),
),
"no_turn_command_authorized",
);
assert.equal(startupEvents().length, 3);
assert.equal(
digest(await readFile(codexBinary)),
codexSha256,
"codex_binary_changed",
);
assert.equal(
digest(await readFile(runnerBinary)),
runnerSha256,
"runner_binary_changed",
);
const summary = {
outcome: "passed",
fixture,
codexVersion: version,
codexSha256,
runnerSha256,
realCodexBehindRecordingExecShim: true,
providerProcessId: starts[0],
phases: facts.map((fact) => fact.phase),
requestedThreadId: missingThread,
authenticatedThreadId: null,
directChildExitObserved: true,
processTreeRetired: false,
providerMethods: sent.map((frame) => frame.method),
providerProcesses: starts.length,
reopenDeniedBeforeProviderLaunch: true,
originalReceiptPreserved: true,
startupFactsCommittedBeforeFailure: true,
runnerExits: [firstExit, secondExit].map((exit) => ({
code: exit.code,
signal: exit.signal,
})),
noHistoricalRecoveryAuthority: true,
};
await writeFile(
join(fixture, "qualification-summary.json"),
`${JSON.stringify(summary, null, 2)}\n`,
{ flag: "wx", mode: 0o600 },
);
console.log(JSON.stringify(summary));
} catch (error) {
// Never print raw Codex/runner frames, command output or provider error prose.
console.error(
JSON.stringify({
outcome: "failed",
fixture: fixture ?? null,
code:
error instanceof Error &&
[
"fixture_runner_exit_not_joined",
"fixture_provider_cleanup_unproven",
].includes(error.message)
? error.message
: error instanceof assert.AssertionError
? "qualification_assertion_failed"
: "qualification_failed",
}),
);
process.exitCode = 1;
}
}