📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -101,20 +101,38 @@ test("strict JSON-lines parser rejects invalid UTF-8, duplicate keys, excess dep
|
||||
);
|
||||
});
|
||||
|
||||
test("initialize fails closed on a protocol version other than 2025-06-18", async () => {
|
||||
test("initialize negotiates the server-supported version for a newer client", async () => {
|
||||
const server = new McpServer({ root: ROOT });
|
||||
const response = await server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: "init",
|
||||
method: "initialize",
|
||||
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "test", version: "1" } },
|
||||
params: { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "test", version: "1" } },
|
||||
});
|
||||
assert.equal(response.error.code, -32602);
|
||||
assert.equal(response.error.data.code, "AAS_MCP_PROTOCOL_VERSION_INCOMPATIBLE");
|
||||
assert.equal(response.error.data.expected, "2025-06-18");
|
||||
assert.equal(response.result.protocolVersion, core.protocolVersion);
|
||||
await server.handle({ jsonrpc: "2.0", method: "notifications/initialized", params: {} });
|
||||
const bypass = await server.handle({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
|
||||
assert.equal(bypass.error.code, -32002);
|
||||
const tools = await server.handle({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
|
||||
assert.deepEqual(tools.result.tools.map((entry) => entry.name), TOOL_NAMES);
|
||||
});
|
||||
|
||||
test("initialize rejects a missing or malformed protocol version", async () => {
|
||||
const invalidVersions = [undefined, "", " ", null, 20250618];
|
||||
for (const protocolVersion of invalidVersions) {
|
||||
const server = new McpServer({ root: ROOT });
|
||||
const params = { capabilities: {}, clientInfo: { name: "test", version: "1" } };
|
||||
if (protocolVersion !== undefined) params.protocolVersion = protocolVersion;
|
||||
const response = await server.handle({
|
||||
jsonrpc: "2.0",
|
||||
id: "init",
|
||||
method: "initialize",
|
||||
params,
|
||||
});
|
||||
assert.equal(response.error.code, -32602);
|
||||
assert.equal(response.error.data.code, "AAS_MCP_PROTOCOL_VERSION_INVALID");
|
||||
await server.handle({ jsonrpc: "2.0", method: "notifications/initialized", params: {} });
|
||||
const bypass = await server.handle({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
|
||||
assert.equal(bypass.error.code, -32002);
|
||||
}
|
||||
});
|
||||
|
||||
test("MCP preserves the five stack tools and adds two read-only evidence tools", async () => {
|
||||
@@ -733,7 +751,7 @@ test("stdio entrypoint emits protocol-only stdout and survives a malformed line"
|
||||
const stderr = [];
|
||||
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
||||
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
||||
child.stdin.write('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}\n');
|
||||
child.stdin.write('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}\n');
|
||||
child.stdin.write('{"a":1,"a":2}\n');
|
||||
child.stdin.write('{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}\n');
|
||||
child.stdin.write('{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}\n');
|
||||
|
||||
@@ -218,7 +218,7 @@ test("the packed runtime launches MCP from an isolated verified dependency closu
|
||||
id: 1,
|
||||
method: "initialize",
|
||||
params: {
|
||||
protocolVersion: core.protocolVersion,
|
||||
protocolVersion: "2025-11-25",
|
||||
capabilities: {},
|
||||
clientInfo: { name: "isolated-runtime-test", version: "1" },
|
||||
},
|
||||
|
||||
@@ -169,6 +169,12 @@ assert.ok(
|
||||
ciWorkflow.indexOf("- name: Intake PR change"),
|
||||
"PR policy dependencies must be installed before preflight executes",
|
||||
);
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/- name: Intake PR change[\s\S]*?git worktree add --detach "\$trusted_root" "\$\{\{ github\.event\.pull_request\.base\.sha \}\}"[\s\S]*?"\$trusted_root\/tools\/scripts\/pr_preflight\.cjs"[\s\S]*?--base "\$\{\{ github\.event\.pull_request\.base\.sha \}\}"[\s\S]*?--head "\$\{\{ github\.event\.pull_request\.head\.sha \}\}"[\s\S]*?--check-fork-safety/,
|
||||
"PR policy must execute trusted-base fork classification against the exact base/head tuple",
|
||||
);
|
||||
assert.match(ciWorkflow, /impact_profile: \$\{\{ steps\.intake\.outputs\.impact_profile \}\}/);
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/GH_TOKEN: \$\{\{ github\.token \}\}/,
|
||||
@@ -201,11 +207,26 @@ assert.match(
|
||||
/- name: Checkout[\s\S]*?uses: actions\/checkout@[a-f0-9]{40}[\s\S]*?with:[\s\S]*?fetch-depth: 0[\s\S]*?persist-credentials: false/,
|
||||
"Pages should use an unshallowed, credential-free checkout because canonical provenance validation reads git history",
|
||||
);
|
||||
assert.match(
|
||||
pagesWorkflow,
|
||||
/- name: Checkout[\s\S]*?- name: Verify release provenance[\s\S]*?- name: Setup Node/,
|
||||
"Pages should verify immutable release provenance before dependency setup or installation",
|
||||
);
|
||||
assert.match(
|
||||
pagesWorkflow,
|
||||
/Verify release provenance[\s\S]*?GH_TOKEN: \$\{\{ github\.token \}\}[\s\S]*?GITHUB_REF_TYPE[\s\S]*?expected_tag="v\$\{package_version\}"[\s\S]*?refs\/tags\/\$\{GITHUB_REF_NAME\}\^\{commit\}[\s\S]*?releases\/tags\/\$\{GITHUB_REF_NAME\}[\s\S]*?\.draft == false[\s\S]*?\.published_at/,
|
||||
"Pages should bind deployment to the exact package tag, commit, and published GitHub Release using the read-only token",
|
||||
);
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/artifact-preview:[\s\S]*?actions\/checkout@[a-f0-9]{40}[\s\S]*?fetch-depth: 0[\s\S]*?persist-credentials: false/,
|
||||
"artifact-preview should retain history because canonical provenance generation reads git history",
|
||||
);
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/source-validation:[\s\S]*?ci_artifact_preview\.cjs create[\s\S]*?actions\/upload-artifact@[a-f0-9]{40}[\s\S]*?artifact-preview:[\s\S]*?actions\/download-artifact@[a-f0-9]{40}[\s\S]*?ci_artifact_preview\.cjs" verify-summary/,
|
||||
"normal PR artifact preview must reuse the exact-head manifest produced by source validation",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
offlineCatalogBuilder,
|
||||
/buildMetadataOverrides|metadata-overrides|review-queue/,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
const preview = require("../ci_artifact_preview.cjs");
|
||||
|
||||
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "ci-artifact-preview-"));
|
||||
const MANIFEST = path.join(ROOT, "preview.json");
|
||||
const OUTPUT = path.join(ROOT, "github-output.txt");
|
||||
const SUMMARY = path.join(ROOT, "summary.md");
|
||||
const WORKFLOW_SHA = "a".repeat(40);
|
||||
const HEAD_SHA = "b".repeat(40);
|
||||
|
||||
assert.strictEqual(
|
||||
preview.escapeHtml(`<tag attr="value">&'\\\``),
|
||||
"<tag attr="value">&'\\`",
|
||||
"step-summary values must be HTML-encoded instead of relying on incomplete Markdown escaping",
|
||||
);
|
||||
|
||||
process.env.GITHUB_OUTPUT = OUTPUT;
|
||||
const created = preview.createManifest({
|
||||
output: MANIFEST,
|
||||
mode: "source-preview",
|
||||
repository: "owner/repo",
|
||||
runId: "12345",
|
||||
runAttempt: "1",
|
||||
workflowSha: WORKFLOW_SHA,
|
||||
headSha: HEAD_SHA,
|
||||
primaryCategory: "skill",
|
||||
categoriesJson: '["docs","skill"]',
|
||||
driftFile: ["CATALOG.md", "data/skills.json"],
|
||||
writeGithubOutput: true,
|
||||
});
|
||||
assert.match(created.digest, /^[0-9a-f]{64}$/);
|
||||
assert.strictEqual(fs.readFileSync(OUTPUT, "utf8"), `manifest_digest=${created.digest}\n`);
|
||||
assert.strictEqual(
|
||||
fs.readFileSync(MANIFEST, "utf8"),
|
||||
`${preview.canonicalJson(created.manifest)}\n`,
|
||||
"create must use byte-stable canonical JSON",
|
||||
);
|
||||
|
||||
process.env.GITHUB_STEP_SUMMARY = SUMMARY;
|
||||
const verified = preview.verifySummary({
|
||||
manifest: MANIFEST,
|
||||
expectedRepository: "owner/repo",
|
||||
expectedRunId: "12345",
|
||||
expectedRunAttempt: "1",
|
||||
expectedWorkflowSha: WORKFLOW_SHA,
|
||||
expectedHeadSha: HEAD_SHA,
|
||||
expectedDigest: created.digest,
|
||||
writeStepSummary: true,
|
||||
});
|
||||
assert.deepStrictEqual(verified, created.manifest);
|
||||
assert.match(fs.readFileSync(SUMMARY, "utf8"), /Artifact Preview[\s\S]*CATALOG\.md/);
|
||||
|
||||
for (const [field, value, pattern] of [
|
||||
["expectedRepository", "other/repo", /repository/],
|
||||
["expectedRunId", "999", /runId/],
|
||||
["expectedRunAttempt", "2", /runAttempt/],
|
||||
["expectedWorkflowSha", "c".repeat(40), /workflowSha/],
|
||||
["expectedHeadSha", "d".repeat(40), /headSha/],
|
||||
["expectedDigest", "e".repeat(64), /SHA-256/],
|
||||
]) {
|
||||
const options = {
|
||||
manifest: MANIFEST,
|
||||
expectedRepository: "owner/repo",
|
||||
expectedRunId: "12345",
|
||||
expectedRunAttempt: "1",
|
||||
expectedWorkflowSha: WORKFLOW_SHA,
|
||||
expectedHeadSha: HEAD_SHA,
|
||||
expectedDigest: created.digest,
|
||||
[field]: value,
|
||||
};
|
||||
assert.throws(() => preview.verifySummary(options), pattern);
|
||||
}
|
||||
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, workflowSha: "short" }),
|
||||
/full lowercase SHA-1/,
|
||||
);
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, categories: ["skill", "docs"] }),
|
||||
/strictly sorted/,
|
||||
);
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, driftFiles: ["CATALOG.md", "CATALOG.md"] }),
|
||||
/strictly sorted/,
|
||||
);
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, driftFiles: ["../escape.md"] }),
|
||||
/unsafe path segment/,
|
||||
);
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, driftFiles: ["bad\\path.md"] }),
|
||||
/normalized repository-relative/,
|
||||
);
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, driftFiles: ["bad\npath.md"] }),
|
||||
/control characters/,
|
||||
);
|
||||
assert.throws(
|
||||
() => preview.validateManifest({ ...created.manifest, mode: "canonical-exact-tree" }),
|
||||
/must not contain generated drift/,
|
||||
);
|
||||
assert.doesNotThrow(() => preview.validateManifest({
|
||||
...created.manifest,
|
||||
mode: "canonical-exact-tree",
|
||||
driftFiles: [],
|
||||
}));
|
||||
|
||||
const nonCanonicalPath = path.join(ROOT, "noncanonical.json");
|
||||
fs.writeFileSync(nonCanonicalPath, `${JSON.stringify(created.manifest, null, 2)}\n`, "utf8");
|
||||
assert.throws(
|
||||
() => preview.readCanonicalManifest(nonCanonicalPath),
|
||||
/not encoded as canonical JSON/,
|
||||
);
|
||||
|
||||
const tamperedPath = path.join(ROOT, "tampered.json");
|
||||
fs.writeFileSync(tamperedPath, fs.readFileSync(MANIFEST, "utf8").replace("CATALOG.md", "README.md"), "utf8");
|
||||
assert.throws(
|
||||
() => preview.verifySummary({
|
||||
manifest: tamperedPath,
|
||||
expectedRepository: "owner/repo",
|
||||
expectedRunId: "12345",
|
||||
expectedRunAttempt: "1",
|
||||
expectedWorkflowSha: WORKFLOW_SHA,
|
||||
expectedHeadSha: HEAD_SHA,
|
||||
expectedDigest: created.digest,
|
||||
}),
|
||||
/SHA-256/,
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => preview.parseOptions(["create", "--mode", "source-preview", "--mode", "canonical-exact-tree"]),
|
||||
/Duplicate option/,
|
||||
);
|
||||
assert.throws(() => preview.parseOptions(["unknown"]), /Unknown command/);
|
||||
|
||||
fs.rmSync(ROOT, { recursive: true, force: true });
|
||||
console.log("ci artifact preview tests passed");
|
||||
@@ -158,11 +158,6 @@ function evidenceSnapshot(overrides = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
assert.strictEqual(mergeBatch.isRetryableMergeError(new Error("Base branch was modified")), true);
|
||||
assert.strictEqual(mergeBatch.isRetryableMergeError(new Error("Something else")), false);
|
||||
}
|
||||
|
||||
{
|
||||
const literalArg = "safe&echo injected";
|
||||
const stdout = mergeBatch.runCommand(
|
||||
|
||||
@@ -6,6 +6,10 @@ const { spawnSync } = require("child_process");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..", "..", "..");
|
||||
const scriptPath = path.join(repoRoot, "tools", "scripts", "pr_preflight.cjs");
|
||||
const { evaluateForkSafety, parseArgs } = require("../pr_preflight.cjs");
|
||||
|
||||
assert.strictEqual(parseArgs(["--repo", repoRoot, "--check-fork-safety"]).repo, repoRoot);
|
||||
assert.strictEqual(parseArgs(["--repo", repoRoot, "--check-fork-safety"]).checkForkSafety, true);
|
||||
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "aas-pr-preflight-"));
|
||||
const eventPath = path.join(tempDir, "event.json");
|
||||
@@ -15,6 +19,8 @@ fs.writeFileSync(
|
||||
JSON.stringify({
|
||||
pull_request: {
|
||||
body: "## Quality Bar Checklist ✅\n\n- [x] Canonical skill location\n",
|
||||
base: { repo: { full_name: "sickn33/agentic-awesome-skills" } },
|
||||
head: { repo: { full_name: "sickn33/agentic-awesome-skills" } },
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
@@ -44,3 +50,54 @@ assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
assert.strictEqual(parsed.prBody.available, true);
|
||||
assert.strictEqual(parsed.prBody.hasQualityChecklist, true);
|
||||
assert.strictEqual(parsed.forkSafety.applicable, false);
|
||||
assert.strictEqual(parsed.forkSafety.approvalSafe, true);
|
||||
assert.strictEqual(parsed.shadowImpact.profile, "unknown");
|
||||
|
||||
const ZERO_OID = "0".repeat(40);
|
||||
const WALKTHROUGH_OID = "1".repeat(40);
|
||||
const walkthroughPolicy = evaluateForkSafety(
|
||||
repoRoot,
|
||||
[{
|
||||
status: "A",
|
||||
old_path: null,
|
||||
new_path: "walkthrough.md",
|
||||
old_mode: "000000",
|
||||
new_mode: "100644",
|
||||
old_oid: ZERO_OID,
|
||||
new_oid: WALKTHROUGH_OID,
|
||||
}],
|
||||
{
|
||||
base: { repo: { full_name: "sickn33/agentic-awesome-skills" } },
|
||||
head: { repo: { full_name: "community/example-fork" } },
|
||||
},
|
||||
);
|
||||
assert.strictEqual(walkthroughPolicy.applicable, true);
|
||||
assert.strictEqual(walkthroughPolicy.approvalSafe, false);
|
||||
assert.ok(
|
||||
walkthroughPolicy.reasons.some((reason) => reason.includes("new_unapproved_path")),
|
||||
`PR #974-style root walkthrough.md must fail before merge: ${walkthroughPolicy.reasons.join(", ")}`,
|
||||
);
|
||||
|
||||
const readmeOid = spawnSync("git", ["rev-parse", "HEAD:README.md"], {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
});
|
||||
assert.strictEqual(readmeOid.status, 0, readmeOid.stderr);
|
||||
const safeForkPolicy = evaluateForkSafety(
|
||||
repoRoot,
|
||||
[{
|
||||
status: "M",
|
||||
old_path: "README.md",
|
||||
new_path: "README.md",
|
||||
old_mode: "100644",
|
||||
new_mode: "100644",
|
||||
old_oid: readmeOid.stdout.trim(),
|
||||
new_oid: readmeOid.stdout.trim(),
|
||||
}],
|
||||
{
|
||||
base: { repo: { full_name: "sickn33/agentic-awesome-skills" } },
|
||||
head: { repo: { full_name: "community/example-fork" } },
|
||||
},
|
||||
);
|
||||
assert.strictEqual(safeForkPolicy.approvalSafe, true);
|
||||
|
||||
@@ -73,21 +73,34 @@ assert.strictEqual(release.validateReleaseSuccessors(repo, releaseCommit, canoni
|
||||
}), true);
|
||||
assert.strictEqual(managedValidationCalls, 1);
|
||||
|
||||
fs.writeFileSync(path.join(repo, "README.md"), "unrelated\n");
|
||||
git(repo, "commit", "-am", "docs: unrelated change");
|
||||
const unrelatedCommit = git(repo, "rev-parse", "HEAD");
|
||||
fs.writeFileSync(path.join(repo, "README.md"), "release synced with pages skip\n");
|
||||
git(repo, "commit", "-am", "[skip pages] chore: synchronize canonical repository state");
|
||||
const skipPagesCanonicalCommit = git(repo, "rev-parse", "HEAD");
|
||||
managedValidationCalls = 0;
|
||||
assert.strictEqual(release.validateReleaseSuccessors(repo, releaseCommit, skipPagesCanonicalCommit, {
|
||||
validateManagedRange() { managedValidationCalls += 1; },
|
||||
}), true);
|
||||
assert.strictEqual(managedValidationCalls, 1);
|
||||
|
||||
fs.writeFileSync(path.join(repo, "README.md"), "near canonical but invalid\n");
|
||||
git(repo, "commit", "-am", "[skip ci] chore: synchronize canonical repository state");
|
||||
const invalidCanonicalCommit = git(repo, "rev-parse", "HEAD");
|
||||
let invalidManagedValidationCalls = 0;
|
||||
assert.throws(
|
||||
() => release.validateReleaseSuccessors(repo, releaseCommit, unrelatedCommit, { validateManagedRange() {} }),
|
||||
() => release.validateReleaseSuccessors(repo, releaseCommit, invalidCanonicalCommit, {
|
||||
validateManagedRange() { invalidManagedValidationCalls += 1; },
|
||||
}),
|
||||
/Unexpected commit/,
|
||||
);
|
||||
assert.strictEqual(invalidManagedValidationCalls, 0);
|
||||
|
||||
git(root, "init", "--bare", remote);
|
||||
git(repo, "remote", "add", "origin", remote);
|
||||
git(repo, "tag", "v1.2.3", canonicalCommit);
|
||||
assert.strictEqual(release.localTagTarget(repo, "v1.2.3"), canonicalCommit);
|
||||
git(repo, "tag", "v1.2.3", skipPagesCanonicalCommit);
|
||||
assert.strictEqual(release.localTagTarget(repo, "v1.2.3"), skipPagesCanonicalCommit);
|
||||
assert.strictEqual(release.remoteTagTarget(repo, "v1.2.3"), null);
|
||||
git(repo, "push", "origin", "v1.2.3");
|
||||
assert.strictEqual(release.remoteTagTarget(repo, "v1.2.3"), canonicalCommit);
|
||||
assert.strictEqual(release.remoteTagTarget(repo, "v1.2.3"), skipPagesCanonicalCommit);
|
||||
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
console.log("Release workflow tests passed.");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const fs = require("fs");
|
||||
const { spawnSync } = require("child_process");
|
||||
const crypto = require("crypto");
|
||||
const path = require("path");
|
||||
|
||||
const NETWORK_TEST_ENV = "ENABLE_NETWORK_TESTS";
|
||||
@@ -67,7 +68,109 @@ function isNetworkTestsEnabled() {
|
||||
: false;
|
||||
}
|
||||
|
||||
function parsePositiveInteger(value, flag) {
|
||||
if (!/^\d+$/.test(value)) {
|
||||
throw new Error(`${flag} must be an integer`);
|
||||
}
|
||||
|
||||
const parsed = Number(value);
|
||||
if (!Number.isSafeInteger(parsed)) {
|
||||
throw new Error(`${flag} is outside the supported integer range`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function readOptionValue(args, index, flag) {
|
||||
const argument = args[index];
|
||||
const prefix = `${flag}=`;
|
||||
if (argument.startsWith(prefix)) {
|
||||
return { value: argument.slice(prefix.length), consumed: 1 };
|
||||
}
|
||||
if (argument === flag) {
|
||||
if (index + 1 >= args.length || args[index + 1].startsWith("--")) {
|
||||
throw new Error(`${flag} requires a value`);
|
||||
}
|
||||
return { value: args[index + 1], consumed: 2 };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
let mode = null;
|
||||
let shardIndex = null;
|
||||
let shardCount = null;
|
||||
|
||||
for (let index = 0; index < args.length;) {
|
||||
const argument = args[index];
|
||||
if (argument === "--local" || argument === "--network") {
|
||||
if (mode) {
|
||||
throw new Error(`Test mode specified more than once: ${argument}`);
|
||||
}
|
||||
mode = argument;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const indexOption = readOptionValue(args, index, "--shard-index");
|
||||
if (indexOption) {
|
||||
if (shardIndex !== null) {
|
||||
throw new Error("--shard-index specified more than once");
|
||||
}
|
||||
shardIndex = parsePositiveInteger(indexOption.value, "--shard-index");
|
||||
index += indexOption.consumed;
|
||||
continue;
|
||||
}
|
||||
|
||||
const countOption = readOptionValue(args, index, "--shard-count");
|
||||
if (countOption) {
|
||||
if (shardCount !== null) {
|
||||
throw new Error("--shard-count specified more than once");
|
||||
}
|
||||
shardCount = parsePositiveInteger(countOption.value, "--shard-count");
|
||||
index += countOption.consumed;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown test option: ${argument}`);
|
||||
}
|
||||
|
||||
const hasShardOption = shardIndex !== null || shardCount !== null;
|
||||
if (hasShardOption && (shardIndex === null || shardCount === null)) {
|
||||
throw new Error("--shard-index and --shard-count must be supplied together");
|
||||
}
|
||||
if (hasShardOption && mode !== "--local") {
|
||||
throw new Error("Test sharding is supported only with explicit --local mode");
|
||||
}
|
||||
if (hasShardOption && shardCount < 1) {
|
||||
throw new Error("--shard-count must be at least 1");
|
||||
}
|
||||
if (hasShardOption && shardIndex >= shardCount) {
|
||||
throw new Error("--shard-index is zero-based and must be less than --shard-count");
|
||||
}
|
||||
|
||||
return { mode, shardIndex, shardCount };
|
||||
}
|
||||
|
||||
function stableShardIndex(testPath, shardCount) {
|
||||
const digest = crypto.createHash("sha256").update(testPath).digest();
|
||||
return digest.readUInt32BE(0) % shardCount;
|
||||
}
|
||||
|
||||
function shardCommands(commands, shardIndex, shardCount) {
|
||||
if (shardIndex === null || shardCount === null) {
|
||||
return commands;
|
||||
}
|
||||
return commands.filter(
|
||||
(commandArgs) => stableShardIndex(commandArgs.at(-1), shardCount) === shardIndex,
|
||||
);
|
||||
}
|
||||
|
||||
function emitTiming(record) {
|
||||
console.log(`[tests:timing] ${JSON.stringify(record)}`);
|
||||
}
|
||||
|
||||
function runNodeCommand(args) {
|
||||
const startedAt = process.hrtime.bigint();
|
||||
const result = spawnSync(process.execPath, args, {
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -76,6 +179,15 @@ function runNodeCommand(args) {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
||||
|
||||
emitTiming({
|
||||
type: "test",
|
||||
path: args.at(-1),
|
||||
elapsed_ms: Math.round(elapsedMs),
|
||||
status: result.error || result.signal || result.status !== 0 ? "failed" : "passed",
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
@@ -93,31 +205,39 @@ function runNodeCommand(args) {
|
||||
}
|
||||
}
|
||||
|
||||
function runCommandSet(commands) {
|
||||
function runCommandSet(commands, metadata = {}) {
|
||||
const startedAt = process.hrtime.bigint();
|
||||
for (const commandArgs of commands) {
|
||||
runNodeCommand(commandArgs);
|
||||
}
|
||||
|
||||
const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
||||
emitTiming({
|
||||
type: "summary",
|
||||
mode: metadata.mode || "default",
|
||||
shard_index: metadata.shardIndex,
|
||||
shard_count: metadata.shardCount,
|
||||
test_count: commands.length,
|
||||
elapsed_ms: Math.round(elapsedMs),
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
const mode = process.argv[2];
|
||||
const { mode, shardIndex, shardCount } = parseArgs(process.argv.slice(2));
|
||||
const { local, network } = discoverTestCommands();
|
||||
|
||||
if (mode === "--local") {
|
||||
runCommandSet(local);
|
||||
const selected = shardCommands(local, shardIndex, shardCount);
|
||||
runCommandSet(selected, { mode: "local", shardIndex, shardCount });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === "--network") {
|
||||
runCommandSet(network);
|
||||
runCommandSet(network, { mode: "network", shardIndex: null, shardCount: null });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode) {
|
||||
throw new Error(`Unknown test mode: ${mode}`);
|
||||
}
|
||||
|
||||
runCommandSet(local);
|
||||
runCommandSet(local, { mode: "local", shardIndex: null, shardCount: null });
|
||||
|
||||
if (!isNetworkTestsEnabled()) {
|
||||
console.log(
|
||||
@@ -127,7 +247,7 @@ function main() {
|
||||
}
|
||||
|
||||
console.log(`[tests] ${NETWORK_TEST_ENV} enabled; running network integration tests.`);
|
||||
runCommandSet(network);
|
||||
runCommandSet(network, { mode: "network", shardIndex: null, shardCount: null });
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
@@ -140,4 +260,7 @@ module.exports = {
|
||||
discoverTestCommands,
|
||||
isTestFile,
|
||||
listFiles,
|
||||
parseArgs,
|
||||
shardCommands,
|
||||
stableShardIndex,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,9 @@ const {
|
||||
discoverTestCommands,
|
||||
isTestFile,
|
||||
listFiles,
|
||||
parseArgs,
|
||||
shardCommands,
|
||||
stableShardIndex,
|
||||
} = require("./run-test-suite.js");
|
||||
|
||||
const TEST_ROOT = path.join("tools", "scripts", "tests");
|
||||
@@ -41,10 +44,83 @@ function testNetworkTestsRemainExplicitlySeparated() {
|
||||
}
|
||||
}
|
||||
|
||||
function testDefaultAndNetworkModesRejectSharding() {
|
||||
assert.deepStrictEqual(parseArgs([]), {
|
||||
mode: null,
|
||||
shardIndex: null,
|
||||
shardCount: null,
|
||||
});
|
||||
assert.deepStrictEqual(parseArgs(["--network"]), {
|
||||
mode: "--network",
|
||||
shardIndex: null,
|
||||
shardCount: null,
|
||||
});
|
||||
assert.throws(
|
||||
() => parseArgs(["--shard-index", "0", "--shard-count", "2"]),
|
||||
/only with explicit --local mode/,
|
||||
);
|
||||
assert.throws(
|
||||
() => parseArgs(["--network", "--shard-index=0", "--shard-count=2"]),
|
||||
/only with explicit --local mode/,
|
||||
);
|
||||
}
|
||||
|
||||
function testShardArgumentsFailClosed() {
|
||||
assert.deepStrictEqual(
|
||||
parseArgs(["--local", "--shard-index", "0", "--shard-count=3"]),
|
||||
{ mode: "--local", shardIndex: 0, shardCount: 3 },
|
||||
);
|
||||
assert.throws(() => parseArgs(["--local", "--shard-index", "0"]), /supplied together/);
|
||||
assert.throws(
|
||||
() => parseArgs(["--local", "--shard-index", "3", "--shard-count", "3"]),
|
||||
/zero-based/,
|
||||
);
|
||||
assert.throws(
|
||||
() => parseArgs(["--local", "--shard-index", "0", "--shard-count", "0"]),
|
||||
/at least 1/,
|
||||
);
|
||||
assert.throws(
|
||||
() => parseArgs(["--local", "--shard-index", "x", "--shard-count", "3"]),
|
||||
/must be an integer/,
|
||||
);
|
||||
assert.throws(() => parseArgs(["--local", "--unexpected"]), /Unknown test option/);
|
||||
}
|
||||
|
||||
function testStableShardingPartitionsEveryLocalTestExactlyOnce() {
|
||||
const { local } = discoverTestCommands();
|
||||
const shardCount = 4;
|
||||
const assignments = new Map();
|
||||
|
||||
for (let shardIndex = 0; shardIndex < shardCount; shardIndex += 1) {
|
||||
for (const command of shardCommands(local, shardIndex, shardCount)) {
|
||||
const testPath = commandPath(command);
|
||||
assert.strictEqual(stableShardIndex(testPath, shardCount), shardIndex);
|
||||
assignments.set(testPath, (assignments.get(testPath) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(
|
||||
[...assignments.keys()].sort(),
|
||||
local.map(commandPath).sort(),
|
||||
);
|
||||
assert.ok([...assignments.values()].every((count) => count === 1));
|
||||
|
||||
const reversed = [...local].reverse();
|
||||
for (let shardIndex = 0; shardIndex < shardCount; shardIndex += 1) {
|
||||
assert.deepStrictEqual(
|
||||
shardCommands(reversed, shardIndex, shardCount).map(commandPath).sort(),
|
||||
shardCommands(local, shardIndex, shardCount).map(commandPath).sort(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
testDiscoveryCoversEveryRepositoryTestFile();
|
||||
testNetworkTestsRemainExplicitlySeparated();
|
||||
console.log("run-test-suite discovery tests passed.");
|
||||
testDefaultAndNetworkModesRejectSharding();
|
||||
testShardArgumentsFailClosed();
|
||||
testStableShardingPartitionsEveryLocalTestExactlyOnce();
|
||||
console.log("run-test-suite discovery and sharding tests passed.");
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -432,6 +432,81 @@ class ChangedSkillEvidenceTests(unittest.TestCase):
|
||||
self.assertIn("external:provenance_identity_changed:source_type", report["reasons"])
|
||||
self.assertIn("external:provenance_identity_changed:source_repo", report["reasons"])
|
||||
|
||||
def test_exact_trusted_repo_rename_exception_allows_only_recorded_transition(self):
|
||||
root, _ = init_repo(with_skill=False)
|
||||
path = write_skill(
|
||||
root,
|
||||
"external",
|
||||
source="community",
|
||||
source_type="community",
|
||||
source_repo="owner/old-name",
|
||||
)
|
||||
git(root, "add", ".")
|
||||
git(root, "commit", "-m", "external base")
|
||||
base = git(root, "rev-parse", "HEAD")
|
||||
path.write_text(
|
||||
path.read_text(encoding="utf-8").replace(
|
||||
"source_repo: owner/old-name", "source_repo: owner/new-name"
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
git(root, "add", ".")
|
||||
git(root, "commit", "-m", "rename upstream")
|
||||
head = git(root, "rev-parse", "HEAD")
|
||||
|
||||
blocked = changed_skill_evidence.build_report(root, base, head)
|
||||
self.assertIn("external:provenance_identity_changed:source_repo", blocked["reasons"])
|
||||
|
||||
ledger = root / changed_skill_evidence.PROVENANCE_EXCEPTION_PATH
|
||||
ledger.parent.mkdir(parents=True)
|
||||
ledger.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": 1,
|
||||
"exceptions": [
|
||||
{
|
||||
"skill_id": "external",
|
||||
"field": "source_repo",
|
||||
"before": "owner/old-name",
|
||||
"after": "owner/new-name",
|
||||
"upstream_repository_id": 12345,
|
||||
"verified_at": "2026-07-28",
|
||||
"evidence_url": "https://github.com/owner/new-name",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
git(root, "add", ".")
|
||||
git(root, "commit", "-m", "trusted rename policy")
|
||||
policy = git(root, "rev-parse", "HEAD")
|
||||
|
||||
allowed = changed_skill_evidence.build_report(
|
||||
root, base, head, policy_ref=policy
|
||||
)
|
||||
self.assertFalse(allowed["blocking"])
|
||||
self.assertEqual(
|
||||
allowed["provenance_exceptions_applied"],
|
||||
["external:source_repo:owner/old-name->owner/new-name"],
|
||||
)
|
||||
|
||||
path.write_text(
|
||||
path.read_text(encoding="utf-8").replace(
|
||||
"source_repo: owner/new-name", "source_repo: owner/other-name"
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
git(root, "add", ".")
|
||||
git(root, "commit", "-m", "unrecorded rename")
|
||||
unrecorded_head = git(root, "rev-parse", "HEAD")
|
||||
unrecorded = changed_skill_evidence.build_report(
|
||||
root, base, unrecorded_head, policy_ref=policy
|
||||
)
|
||||
self.assertIn(
|
||||
"external:provenance_identity_changed:source_repo", unrecorded["reasons"]
|
||||
)
|
||||
|
||||
def test_declared_risk_downgrade_blocks(self):
|
||||
before = {
|
||||
"audit": {"findings": {}},
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
classifyChangeRecords,
|
||||
classifyChangedFiles,
|
||||
classifyPathPolicy,
|
||||
classifyShadowImpact,
|
||||
extractChangelogSection,
|
||||
getDirectDerivedChanges,
|
||||
hasIssueLink,
|
||||
@@ -64,6 +65,31 @@ const contract = {
|
||||
releaseManagedFiles: ["CHANGELOG.md", "package.json", "package-lock.json", "README.md"],
|
||||
};
|
||||
|
||||
assert.deepStrictEqual(
|
||||
classifyShadowImpact([modifiedRecord("skills/example/SKILL.md")], contract),
|
||||
{ profile: "narrow-skill", reasons: [] },
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
classifyShadowImpact([modifiedRecord("docs/users/guide.md")], contract),
|
||||
{ profile: "narrow-docs", reasons: [] },
|
||||
);
|
||||
assert.strictEqual(
|
||||
classifyShadowImpact([modifiedRecord("skills/example/SKILL.md"), modifiedRecord("docs/users/guide.md")], contract).profile,
|
||||
"full",
|
||||
);
|
||||
assert.strictEqual(
|
||||
classifyShadowImpact([modifiedRecord("tools/scripts/pr_preflight.cjs")], contract).profile,
|
||||
"full",
|
||||
);
|
||||
assert.strictEqual(
|
||||
classifyShadowImpact([addedRecord("walkthrough.md")], contract).profile,
|
||||
"full",
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
classifyShadowImpact([modifiedRecord("unclassified.bin")], contract),
|
||||
{ profile: "unknown", reasons: ["unclassified_path"] },
|
||||
);
|
||||
|
||||
const repositoryRoot = path.resolve(__dirname, "..", "..", "..");
|
||||
const agentInstructions = fs.readFileSync(path.join(repositoryRoot, "AGENTS.md"), "utf8");
|
||||
const maintenanceGuide = fs.readFileSync(path.join(repositoryRoot, ".github", "MAINTENANCE.md"), "utf8");
|
||||
@@ -95,6 +121,34 @@ assert.match(maintainerSkill, /authored by the repository owner/);
|
||||
assert.match(maintainerSkill, /exactly one merged release PR/);
|
||||
assert.match(maintenanceGuide, /canonical-repo-state` PR owns that state/);
|
||||
assert.match(autonomyGuide, /complete nearest skill-directory fingerprint/);
|
||||
for (const contractText of [maintainerSkill, maintenanceGuide, mergeBatchGuide, autonomyGuide]) {
|
||||
assert.doesNotMatch(contractText, /may normalize the PR body|close\/reopen the PR|retries `Base branch was modified`/);
|
||||
assert.match(contractText, /does not (?:rewrite|mutate).*PR (?:body|metadata)|PR-body rewriting or normalization/);
|
||||
assert.match(contractText, /does not retry base drift|does not .*retry base drift|no automatic retry/);
|
||||
}
|
||||
assert.doesNotMatch(maintenanceGuide, /runs the mandatory post-merge `sync:contributors`/);
|
||||
assert.match(maintenanceGuide, /hands contributor\/generated drift to the protected canonical-sync lane/);
|
||||
assert.match(maintenanceGuide, /`npm run chain` already includes catalog generation/);
|
||||
assert.doesNotMatch(maintenanceGuide, /npm run chain\n\s+npm run catalog/);
|
||||
assert.match(autonomyGuide, /explicitly dispatches main CI and CodeQL/);
|
||||
assert.match(autonomyGuide, /Pages remains release-only/);
|
||||
for (const contractText of [maintainerSkill, maintenanceGuide, mergeBatchGuide, autonomyGuide]) {
|
||||
assert.match(contractText, /protected[- ]base|protected base/);
|
||||
assert.match(contractText, /impact_profile.*shadow|shadow-only.*impact_profile/s);
|
||||
assert.match(contractText, /source-validation.*lightweight/s);
|
||||
assert.match(contractText, /required CI.*(?:complete|full).*unsharded/is);
|
||||
}
|
||||
assert.match(maintenanceGuide, /merge:batch.*only fork-run approval and merge authority/);
|
||||
assert.match(mergeBatchGuide, /merge:batch.*only command allowed to approve fork runs or merge/s);
|
||||
assert.match(autonomyGuide, /merge:batch.*sole authority for fork-run approval and merge/s);
|
||||
assert.match(maintainerSkill, /merge:batch.*recompute the current trusted decision/s);
|
||||
for (const contractText of [maintenanceGuide, mergeBatchGuide, autonomyGuide]) {
|
||||
assert.match(contractText, /source-validation.*(?:refresh|generate|generated-state).*once|(?:refresh|generate|generated-state).*once.*source-validation/s);
|
||||
assert.match(contractText, /artifact-preview.*(?:verif(?:y|ies).*manifest|manifest.*verif(?:y|ies))/s);
|
||||
assert.match(contractText, /final (?:CI and CodeQL|`main` CI and CodeQL)/i);
|
||||
}
|
||||
assert.match(autonomyGuide, /timing telemetry/);
|
||||
assert.match(autonomyGuide, /npm run test:local -- --shard-index N --shard-count M/);
|
||||
assert.match(mergingGuide, /No local-integration exception/);
|
||||
assert.doesNotMatch(mergingGuide, /Rare exception: local squash|`gh pr merge <PR_NUMBER>/);
|
||||
assert.match(maintainerSkillUi, /\$antigravity-maintainer-batch-release/);
|
||||
@@ -130,6 +184,27 @@ const pagesWorkflow = fs.readFileSync(
|
||||
);
|
||||
assert.match(pagesWorkflow, /^on:\s*\n\s+workflow_dispatch:/m);
|
||||
assert.doesNotMatch(pagesWorkflow, /^\s+push:/m);
|
||||
assert.match(pagesWorkflow, /permissions:\s*\n\s+contents: read\s*\n\s+pages: write\s*\n\s+id-token: write/);
|
||||
const pagesCheckoutIndex = pagesWorkflow.indexOf("- name: Checkout");
|
||||
const pagesProvenanceIndex = pagesWorkflow.indexOf("- name: Verify release provenance");
|
||||
const pagesSetupIndex = pagesWorkflow.indexOf("- name: Setup Node");
|
||||
assert.ok(
|
||||
pagesCheckoutIndex >= 0 && pagesCheckoutIndex < pagesProvenanceIndex && pagesProvenanceIndex < pagesSetupIndex,
|
||||
"Pages must fail closed on release provenance immediately after checkout and before setup/install work",
|
||||
);
|
||||
for (const provenanceContract of [
|
||||
/GH_TOKEN: \$\{\{ github\.token \}\}/,
|
||||
/GITHUB_REF_TYPE[^\n]+tag/,
|
||||
/GITHUB_REF_NAME[^\n]+\^v\[0-9\]\+\\\.\[0-9\]\+\\\.\[0-9\]\+\$/,
|
||||
/expected_tag="v\$\{package_version\}"/,
|
||||
/refs\/tags\/\$\{GITHUB_REF_NAME\}\^\{commit\}/,
|
||||
/tag_commit[^\n]+GITHUB_SHA[^\n]+head_commit[^\n]+GITHUB_SHA/,
|
||||
/gh api --method GET "repos\/\$\{GITHUB_REPOSITORY\}\/releases\/tags\/\$\{GITHUB_REF_NAME\}"/,
|
||||
/\.draft == false/,
|
||||
/\.published_at/,
|
||||
]) {
|
||||
assert.match(pagesWorkflow, provenanceContract);
|
||||
}
|
||||
for (const command of [
|
||||
"npm run validate:strict",
|
||||
"npm run validate:glossary",
|
||||
@@ -149,6 +224,25 @@ const ciWorkflow = fs.readFileSync(
|
||||
path.resolve(__dirname, "..", "..", "..", ".github", "workflows", "ci.yml"),
|
||||
"utf8",
|
||||
);
|
||||
const latestHeadConcurrency = [
|
||||
"concurrency:",
|
||||
" group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('run-{0}', github.run_id) }}",
|
||||
" cancel-in-progress: ${{ github.event_name == 'pull_request' }}",
|
||||
].join("\n");
|
||||
for (const workflowPath of [
|
||||
".github/workflows/ci.yml",
|
||||
".github/workflows/codeql.yml",
|
||||
".github/workflows/dependency-review.yml",
|
||||
".github/workflows/skill-review.yml",
|
||||
".github/workflows/aas-agent-first-preview.yml",
|
||||
".github/workflows/actionlint.yml",
|
||||
]) {
|
||||
const workflow = fs.readFileSync(path.resolve(__dirname, "..", "..", "..", workflowPath), "utf8");
|
||||
assert.ok(
|
||||
workflow.includes(latestHeadConcurrency),
|
||||
`${workflowPath} must cancel superseded PR heads while keeping every non-PR run in a unique concurrency group`,
|
||||
);
|
||||
}
|
||||
assert.doesNotMatch(
|
||||
ciWorkflow,
|
||||
/ENABLE_NETWORK_TESTS:\s*["']1["']/,
|
||||
@@ -157,7 +251,7 @@ assert.doesNotMatch(
|
||||
assert.match(ciWorkflow, /^permissions:\n contents: read$/m);
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/source-validation:[\s\S]*?- name: Refresh ephemeral derived sources for tests\n\s+run: npm run plugin-compat:sync && npm run index && npm run bundles:sync && npm run sync:metadata && npm run catalog && npm run build:aas-v1-catalog\n[\s\S]*?- name: Run tests\n\s+run: npm run test/,
|
||||
/source-validation:[\s\S]*?- name: Refresh ephemeral derived sources for tests\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR != 'true'\n\s+run: npm run plugin-compat:sync && npm run index && npm run bundles:sync && npm run sync:metadata && npm run catalog && npm run build:aas-v1-catalog && npm run sync:web-assets\n[\s\S]*?- name: Run tests\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR != 'true'\n\s+run: npm run test/,
|
||||
"source-only skill PRs must refresh uncommitted mirrors and indexes before tests read them",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
@@ -167,11 +261,60 @@ assert.doesNotMatch(
|
||||
);
|
||||
assert.match(ciWorkflow, /name: pr-evidence-/);
|
||||
assert.doesNotMatch(ciWorkflow, /pull_request_target:/);
|
||||
assert.doesNotMatch(ciWorkflow, /actions\/download-artifact/);
|
||||
assert.match(ciWorkflow, /actions\/download-artifact@[0-9a-f]{40}/);
|
||||
const prEvidenceJob = ciWorkflow.match(/^ pr-evidence:\n([\s\S]*?)(?=^ artifact-preview:)/m)?.[0] || "";
|
||||
assert.ok(prEvidenceJob, "pr-evidence job must exist");
|
||||
assert.doesNotMatch(prEvidenceJob, /(?:contents|pull-requests|actions): write/);
|
||||
assert.doesNotMatch(prEvidenceJob, /secrets\./);
|
||||
for (const stepName of ["Set up Python", "Set up Node", "Install trusted dependencies", "Fetch base branch"]) {
|
||||
assert.match(
|
||||
prEvidenceJob,
|
||||
new RegExp(`- name: ${stepName}\\n\\s+if: env\\.IS_TRUSTED_CANONICAL_SYNC_PR != 'true'`),
|
||||
`${stepName} must be skipped for canonical-sync evidence`,
|
||||
);
|
||||
}
|
||||
assert.match(
|
||||
prEvidenceJob,
|
||||
/- uses: actions\/checkout@[0-9a-f]{40}[^\n]*\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR != 'true'/,
|
||||
"canonical-sync evidence must not perform an unused checkout",
|
||||
);
|
||||
assert.match(
|
||||
prEvidenceJob,
|
||||
/- name: Record canonical-sync evidence boundary\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR == 'true'/,
|
||||
"canonical-sync evidence must retain its explicit successful boundary record",
|
||||
);
|
||||
|
||||
const sourceValidationJob = ciWorkflow.match(/^ source-validation:\n([\s\S]*?)(?=^ pr-evidence:)/m)?.[0] || "";
|
||||
assert.match(sourceValidationJob, /needs: pr-policy/, "source validation should not wait for independent PR evidence");
|
||||
assert.doesNotMatch(sourceValidationJob, /needs:.*pr-evidence/);
|
||||
assert.match(
|
||||
sourceValidationJob,
|
||||
/actions\/checkout@[0-9a-f]{40}[\s\S]*?ref: \$\{\{ github\.event\.pull_request\.head\.sha \}\}/,
|
||||
"source validation must generate its preview from the exact PR head instead of GitHub's synthetic merge commit",
|
||||
);
|
||||
assert.match(sourceValidationJob, /preview_manifest_digest: \$\{\{ steps\.preview_manifest\.outputs\.manifest_digest \}\}/);
|
||||
assert.match(sourceValidationJob, /ci_artifact_preview\.cjs create/);
|
||||
assert.match(sourceValidationJob, /actions\/upload-artifact@[0-9a-f]{40}/);
|
||||
assert.match(
|
||||
sourceValidationJob,
|
||||
/- name: Record canonical source-validation boundary\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR == 'true'/,
|
||||
"canonical source validation should become a lightweight boundary record after pr-policy exact-tree reproduction",
|
||||
);
|
||||
const artifactPreviewJob = ciWorkflow.match(/^ artifact-preview:\n([\s\S]*?)(?=^ main-validation-and-sync:)/m)?.[0] || "";
|
||||
assert.match(artifactPreviewJob, /needs: \[pr-policy, source-validation\]/);
|
||||
assert.match(artifactPreviewJob, /ref: \$\{\{ github\.event\.pull_request\.head\.sha \|\| github\.sha \}\}/);
|
||||
assert.match(
|
||||
artifactPreviewJob,
|
||||
/- name: Download exact-head artifact preview manifest[\s\S]*?actions\/download-artifact@[0-9a-f]{40}[\s\S]*?- name: Verify and report exact-head artifact preview[\s\S]*?ci_artifact_preview\.cjs" verify-summary/,
|
||||
"artifact preview should consume and verify the source-validation manifest instead of regenerating the same tree",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
artifactPreviewJob,
|
||||
/npm run chain|Generate canonical artifacts preview/,
|
||||
"artifact preview must not regenerate normal source-PR artifacts",
|
||||
);
|
||||
assert.match(artifactPreviewJob, /- name: Reproduce canonical-sync PR from main\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR == 'true'/);
|
||||
assert.match(artifactPreviewJob, /- name: Report generated drift\n\s+if: env\.IS_TRUSTED_CANONICAL_SYNC_PR == 'true'/);
|
||||
|
||||
const decisionModule = fs.readFileSync(
|
||||
path.resolve(__dirname, "..", "..", "lib", "pr-decision.js"),
|
||||
|
||||
Reference in New Issue
Block a user