📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-07-29 07:59:17 +00:00
parent 60364c6660
commit 0c634043e3
427 changed files with 26138 additions and 2336 deletions
@@ -4,6 +4,7 @@ from __future__ import annotations
import argparse
import json
import re
import tempfile
from collections import Counter, defaultdict
from pathlib import Path
@@ -16,13 +17,92 @@ from check_readme_credits import (
normalize_repo_slug,
parse_frontmatter,
)
from git_change_records import ChangeRecord, list_tree, materialize_tree, read_change_records, read_path
from git_change_records import (
ChangeRecord,
list_tree,
materialize_tree,
read_change_records,
read_path,
resolve_commit,
)
from security_scanner import scan_skill_file
SCHEMA_VERSION = 1
RISK_RANK = {"unknown": -1, "none": 0, "safe": 1, "critical": 2, "offensive": 3}
SEVERITY_RANK = {"info": 0, "warning": 1, "error": 2}
PROVENANCE_EXCEPTION_PATH = "docs/maintainers/provenance-identity-exceptions.json"
DATE_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}")
def load_provenance_exceptions(
repo: Path,
policy_ref: str,
) -> tuple[str, set[tuple[str, str, str, str]]]:
"""Load exact, maintainer-reviewed provenance transitions from trusted policy."""
policy_oid = resolve_commit(repo, policy_ref)
payload = read_path(repo, policy_oid, PROVENANCE_EXCEPTION_PATH)
if payload is None:
return policy_oid, set()
try:
document = json.loads(payload.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as error:
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: {error}") from error
if not isinstance(document, dict) or document.get("schema_version") != 1:
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: schema_version must be 1")
entries = document.get("exceptions")
if not isinstance(entries, list):
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exceptions must be a list")
allowed: set[tuple[str, str, str, str]] = set()
required_fields = {
"skill_id",
"field",
"before",
"after",
"upstream_repository_id",
"verified_at",
"evidence_url",
}
for index, entry in enumerate(entries):
if not isinstance(entry, dict) or set(entry) != required_fields:
raise ValueError(
f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} has unexpected fields"
)
skill_id = entry["skill_id"]
field = entry["field"]
before = normalize_repo_slug(entry["before"])
after = normalize_repo_slug(entry["after"])
repository_id = entry["upstream_repository_id"]
verified_at = entry["verified_at"]
evidence_url = entry["evidence_url"]
if not isinstance(skill_id, str) or not skill_id or "/" in skill_id:
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} skill_id")
if field != "source_repo":
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} field")
if (
not isinstance(before, str)
or not SOURCE_REPO_PATTERN.fullmatch(before)
or not isinstance(after, str)
or not SOURCE_REPO_PATTERN.fullmatch(after)
or before == after
):
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} transition")
if not isinstance(repository_id, int) or isinstance(repository_id, bool) or repository_id <= 0:
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} repository id")
if not isinstance(verified_at, str) or not DATE_PATTERN.fullmatch(verified_at):
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} verified_at")
if (
not isinstance(evidence_url, str)
or not evidence_url.startswith("https://github.com/")
or normalize_repo_slug(evidence_url) != after
):
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: exception {index} evidence_url")
transition = (skill_id, field, before, after)
if transition in allowed:
raise ValueError(f"Invalid {PROVENANCE_EXCEPTION_PATH}: duplicate exception {index}")
allowed.add(transition)
return policy_oid, allowed
def canonical_skill_roots(repo: Path, commit_oid: str) -> set[str]:
@@ -264,14 +344,16 @@ def provenance_reasons(
before: dict[str, object] | None,
after: dict[str, object] | None,
readme_credits: dict[str, set[str]],
) -> list[str]:
provenance_exceptions: set[tuple[str, str, str, str]],
) -> tuple[list[str], list[str]]:
if change_type == "deleted" or after is None:
return []
return [], []
provenance = after["provenance"]
source = provenance.get("source")
source_type = provenance.get("source_type")
source_repo = provenance.get("source_repo")
reasons: list[str] = []
applied_exceptions: list[str] = []
source_is_self = isinstance(source, str) and source.strip().lower() == "self"
before_provenance = before.get("provenance") if before else None
before_source = before_provenance.get("source") if before_provenance else None
@@ -282,8 +364,22 @@ def provenance_reasons(
if change_type in {"modified", "renamed"} and before_provenance:
if not before_is_self or not source_is_self:
for field in ("source", "source_type", "source_repo"):
if before_provenance.get(field) != provenance.get(field):
reasons.append(f"{skill_id}:provenance_identity_changed:{field}")
before_value = before_provenance.get(field)
after_value = provenance.get(field)
if before_value != after_value:
transition = (
(skill_id, field, before_value, after_value)
if field == "source_repo"
and isinstance(before_value, str)
and isinstance(after_value, str)
else None
)
if transition is not None and transition in provenance_exceptions:
applied_exceptions.append(
f"{skill_id}:{field}:{before_value}->{after_value}"
)
else:
reasons.append(f"{skill_id}:provenance_identity_changed:{field}")
needs_full_validation = change_type in {"added", "copied"} or (
before_provenance is not None and before_is_self and not source_is_self
@@ -297,7 +393,7 @@ def provenance_reasons(
reasons.append(
f"{skill_id}:new_external_skill_missing_readme_credit:{source_type}:{source_repo}"
)
return reasons
return reasons, applied_exceptions
def _unsafe_counter(entries: list[dict[str, str]], skill_id: str | None) -> Counter[tuple[str, str, str]]:
@@ -312,15 +408,24 @@ def _unsafe_counter(entries: list[dict[str, str]], skill_id: str | None) -> Coun
)
def build_report(repo: str | Path, base_ref: str, head_ref: str) -> dict[str, object]:
def build_report(
repo: str | Path,
base_ref: str,
head_ref: str,
policy_ref: str | None = None,
) -> dict[str, object]:
root = Path(repo)
base_oid, head_oid, records = read_change_records(root, base_ref, head_ref, merge_base=True)
policy_oid, provenance_exceptions = load_provenance_exceptions(
root, policy_ref or base_ref
)
old_roots = canonical_skill_roots(root, base_oid)
new_roots = canonical_skill_roots(root, head_oid)
readme_bytes = read_path(root, head_oid, "README.md") or b""
readme_credits = extract_credit_repos(readme_bytes.decode("utf-8", "replace"))
changes: list[dict[str, object]] = []
all_reasons: list[str] = []
all_applied_exceptions: list[str] = []
with tempfile.TemporaryDirectory(prefix="changed-skill-evidence-") as temporary:
temp_root = Path(temporary)
@@ -375,17 +480,18 @@ def build_report(repo: str | Path, base_ref: str, head_ref: str) -> dict[str, ob
)
comparison_before = None if change_type in {"added", "copied"} else before
reasons.extend(regression_reasons(effective_id, change_type, comparison_before, after))
reasons.extend(
provenance_reasons(
effective_id,
change_type,
comparison_before,
after,
readme_credits,
)
provenance_blockers, applied_exceptions = provenance_reasons(
effective_id,
change_type,
comparison_before,
after,
readme_credits,
provenance_exceptions,
)
reasons.extend(provenance_blockers)
reasons = sorted(set(reasons))
all_reasons.extend(reasons)
all_applied_exceptions.extend(applied_exceptions)
changes.append(
{
"change_type": change_type,
@@ -397,6 +503,7 @@ def build_report(repo: str | Path, base_ref: str, head_ref: str) -> dict[str, ob
"unsafe_entries": {"before": before_unsafe, "after": after_unsafe},
"blocking": bool(reasons),
"reasons": reasons,
"provenance_exceptions_applied": sorted(applied_exceptions),
}
)
@@ -408,9 +515,11 @@ def build_report(repo: str | Path, base_ref: str, head_ref: str) -> dict[str, ob
"head_ref": head_ref,
"base_oid": base_oid,
"head_oid": head_oid,
"policy_oid": policy_oid,
"changes": changes,
"blocking": bool(reasons),
"reasons": reasons,
"provenance_exceptions_applied": sorted(set(all_applied_exceptions)),
}
@@ -422,6 +531,10 @@ def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Build changed-skill before/after evidence.")
parser.add_argument("--base", required=True)
parser.add_argument("--head", required=True)
parser.add_argument(
"--policy-ref",
help="Trusted policy commit containing provenance exception records (defaults to --base).",
)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument(
"--repo",
@@ -435,7 +548,7 @@ def parse_args() -> argparse.Namespace:
def main() -> int:
args = parse_args()
root = args.repo.resolve() if args.repo else find_repo_root(__file__)
report = build_report(root, args.base, args.head)
report = build_report(root, args.base, args.head, policy_ref=args.policy_ref)
payload = stable_json(report)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(payload, encoding="utf-8")
@@ -0,0 +1,258 @@
#!/usr/bin/env node
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const MODES = new Set(["source-preview", "canonical-exact-tree"]);
const SHA_RE = /^[0-9a-f]{40}$/;
const DIGEST_RE = /^[0-9a-f]{64}$/;
const REPOSITORY_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
function canonicalize(value) {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]),
);
}
return value;
}
function canonicalJson(value) {
return JSON.stringify(canonicalize(value));
}
function sha256(value) {
return crypto.createHash("sha256").update(value).digest("hex");
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
function parsePositiveInteger(value, label) {
if (!/^[1-9]\d*$/.test(String(value))) throw new Error(`${label} must be a positive integer`);
const parsed = Number(value);
if (!Number.isSafeInteger(parsed)) throw new Error(`${label} is outside the safe integer range`);
return parsed;
}
function validateString(value, label) {
if (typeof value !== "string" || value.length === 0 || /[\0-\x1f\x7f]/.test(value)) {
throw new Error(`${label} must be a non-empty string without control characters`);
}
return value;
}
function validateRelativePath(value) {
validateString(value, "drift file");
if (value.includes("\\") || path.posix.isAbsolute(value) || path.posix.normalize(value) !== value) {
throw new Error(`Drift file must be a normalized repository-relative POSIX path: ${value}`);
}
if (value.split("/").some((part) => part === "" || part === "." || part === "..")) {
throw new Error(`Drift file contains an unsafe path segment: ${value}`);
}
return value;
}
function validateOrderedUniqueStrings(values, label, itemValidator = validateString) {
if (!Array.isArray(values)) throw new Error(`${label} must be an array`);
let previous = null;
return values.map((value, index) => {
const validated = itemValidator(value, `${label}[${index}]`);
if (previous !== null && previous >= validated) {
throw new Error(`${label} must be strictly sorted and contain no duplicates`);
}
previous = validated;
return validated;
});
}
function validateManifest(manifest) {
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
throw new Error("Manifest must be a JSON object");
}
const expectedKeys = [
"categories", "driftFiles", "headSha", "mode", "primaryCategory", "repository",
"runAttempt", "runId", "schemaVersion", "workflowSha",
].sort();
const actualKeys = Object.keys(manifest).sort();
if (JSON.stringify(actualKeys) !== JSON.stringify(expectedKeys)) {
throw new Error("Manifest contains missing or unsupported fields");
}
if (manifest.schemaVersion !== 1) throw new Error("Unsupported manifest schemaVersion");
if (!MODES.has(manifest.mode)) throw new Error(`Unsupported preview mode: ${manifest.mode}`);
if (!REPOSITORY_RE.test(validateString(manifest.repository, "repository"))) {
throw new Error("repository must use the owner/name form");
}
if (!/^\d+$/.test(String(manifest.runId))) throw new Error("runId must contain decimal digits");
parsePositiveInteger(manifest.runAttempt, "runAttempt");
for (const [label, value] of [["workflowSha", manifest.workflowSha], ["headSha", manifest.headSha]]) {
if (!SHA_RE.test(value)) throw new Error(`${label} must be a full lowercase SHA-1`);
}
validateString(manifest.primaryCategory, "primaryCategory");
validateOrderedUniqueStrings(manifest.categories, "categories");
validateOrderedUniqueStrings(manifest.driftFiles, "driftFiles", validateRelativePath);
if (manifest.mode === "canonical-exact-tree" && manifest.driftFiles.length !== 0) {
throw new Error("canonical-exact-tree manifests must not contain generated drift");
}
return manifest;
}
function parseOptions(argv) {
if (argv.length === 0) throw new Error("Expected create or verify-summary command");
const command = argv[0];
if (!new Set(["create", "verify-summary"]).has(command)) throw new Error(`Unknown command: ${command}`);
const repeatable = new Set(["drift-file"]);
const flags = new Set(["write-github-output", "write-step-summary"]);
const options = { command, driftFile: [] };
for (let index = 1; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith("--")) throw new Error(`Unexpected argument: ${token}`);
const name = token.slice(2);
if (flags.has(name)) {
const key = name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
if (options[key]) throw new Error(`Duplicate option: ${token}`);
options[key] = true;
continue;
}
const value = argv[index + 1];
if (!value || value.startsWith("--")) throw new Error(`${token} requires a value`);
const key = name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
if (!repeatable.has(name) && options[key] !== undefined) throw new Error(`Duplicate option: ${token}`);
if (repeatable.has(name)) options[key].push(value);
else options[key] = value;
index += 1;
}
return options;
}
function requireOptions(options, names) {
for (const name of names) {
if (options[name] === undefined) throw new Error(`--${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)} is required`);
}
}
function appendGithubOutput(digest) {
if (!process.env.GITHUB_OUTPUT) throw new Error("GITHUB_OUTPUT is required with --write-github-output");
fs.appendFileSync(process.env.GITHUB_OUTPUT, `manifest_digest=${digest}\n`, "utf8");
}
function createManifest(options) {
requireOptions(options, [
"output", "mode", "repository", "runId", "runAttempt", "workflowSha", "headSha",
"primaryCategory", "categoriesJson",
]);
let categories;
try {
categories = JSON.parse(options.categoriesJson);
} catch (error) {
throw new Error(`--categories-json must be valid JSON: ${error.message}`);
}
const manifest = validateManifest({
schemaVersion: 1,
mode: options.mode,
repository: options.repository,
runId: String(options.runId),
runAttempt: parsePositiveInteger(options.runAttempt, "runAttempt"),
workflowSha: options.workflowSha,
headSha: options.headSha,
primaryCategory: options.primaryCategory,
categories,
driftFiles: options.driftFile,
});
const serialized = canonicalJson(manifest);
const digest = sha256(serialized);
fs.mkdirSync(path.dirname(path.resolve(options.output)), { recursive: true });
fs.writeFileSync(options.output, `${serialized}\n`, { encoding: "utf8", mode: 0o600 });
if (options.writeGithubOutput) appendGithubOutput(digest);
process.stdout.write(`${digest}\n`);
return { digest, manifest };
}
function readCanonicalManifest(filePath) {
const raw = fs.readFileSync(filePath, "utf8");
let manifest;
try {
manifest = JSON.parse(raw);
} catch (error) {
throw new Error(`Manifest is not valid JSON: ${error.message}`);
}
validateManifest(manifest);
const serialized = canonicalJson(manifest);
if (raw !== `${serialized}\n`) throw new Error("Manifest is not encoded as canonical JSON with one trailing newline");
return { manifest, serialized };
}
function appendSummary(manifest, digest) {
if (!process.env.GITHUB_STEP_SUMMARY) throw new Error("GITHUB_STEP_SUMMARY is required with --write-step-summary");
const drift = manifest.driftFiles.length
? manifest.driftFiles.map((file) => `- <code>${escapeHtml(file)}</code>`).join("\n")
: "- none";
const lines = [
"## Artifact Preview", "",
`- Mode: <code>${escapeHtml(manifest.mode)}</code>`,
`- Primary change: <code>${escapeHtml(manifest.primaryCategory)}</code>`,
`- Categories: ${manifest.categories.length ? manifest.categories.map((item) => `<code>${escapeHtml(item)}</code>`).join(", ") : "none"}`,
`- Workflow SHA: <code>${escapeHtml(manifest.workflowSha)}</code>`,
`- Manifest SHA-256: <code>${escapeHtml(digest)}</code>`, "", "Generated drift:", drift, "",
];
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, lines.join("\n"), "utf8");
}
function verifySummary(options) {
requireOptions(options, [
"manifest", "expectedRepository", "expectedRunId", "expectedRunAttempt",
"expectedWorkflowSha", "expectedHeadSha", "expectedDigest",
]);
if (!DIGEST_RE.test(options.expectedDigest)) throw new Error("--expected-digest must be a lowercase SHA-256");
const { manifest, serialized } = readCanonicalManifest(options.manifest);
const bindings = [
["repository", options.expectedRepository],
["runId", String(options.expectedRunId)],
["runAttempt", parsePositiveInteger(options.expectedRunAttempt, "expectedRunAttempt")],
["workflowSha", options.expectedWorkflowSha],
["headSha", options.expectedHeadSha],
];
for (const [key, expected] of bindings) {
if (manifest[key] !== expected) throw new Error(`Manifest ${key} does not match the expected workflow identity`);
}
const digest = sha256(serialized);
if (digest !== options.expectedDigest) throw new Error("Manifest SHA-256 does not match --expected-digest");
if (options.writeStepSummary) appendSummary(manifest, digest);
process.stdout.write(`${digest}\n`);
return manifest;
}
function main(argv = process.argv.slice(2)) {
const options = parseOptions(argv);
return options.command === "create" ? createManifest(options) : verifySummary(options);
}
if (require.main === module) {
try {
main();
} catch (error) {
console.error(`[ci-artifact-preview] ${error.message}`);
process.exit(2);
}
}
module.exports = {
canonicalJson,
createManifest,
escapeHtml,
main,
parseOptions,
readCanonicalManifest,
sha256,
validateManifest,
validateRelativePath,
verifySummary,
};
@@ -6,6 +6,7 @@ const path = require("path");
const { spawnSync } = require("child_process");
const { findProjectRoot } = require("../lib/project-root");
const { resolveBlobSizes } = require("../lib/git-blob-sizes");
const { parseRawDiff } = require("../lib/git-raw-diff");
const {
classifyChangeRecords,
@@ -13,11 +14,6 @@ const {
} = require("../lib/workflow-contract");
const DEFAULT_POLL_SECONDS = 20;
const BASE_BRANCH_MODIFIED_PATTERNS = [
/base branch was modified/i,
/base branch has been modified/i,
/branch was modified/i,
];
const REQUIRED_CHECKS = [
["pr-policy", { label: "pr-policy", aliases: ["pr-policy"], appId: 15368 }],
["pr-evidence", { label: "pr-evidence", aliases: ["pr-evidence"], appId: 15368 }],
@@ -352,6 +348,8 @@ function recomputeChangedSkillEvidence(
mergeBaseOid,
"--head",
headOid,
"--policy-ref",
evaluatorOid,
"--output",
outputPath,
],
@@ -413,43 +411,6 @@ function readRawChangeRecords(projectRoot, baseOid, headOid, dependencies = {})
return parseRawDiff(raw);
}
function resolveBlobSizes(projectRoot, records, dependencies = {}) {
const execute = dependencies.runCommand || runCommand;
const objectIds = [...new Set(records.flatMap((record) => [record.old_oid, record.new_oid]))]
.filter((oid) => FULL_SHA_PATTERN.test(String(oid || "")) && !/^0+$/u.test(oid));
if (!objectIds.length) {
throw new Error("Raw Git diff did not contain any materialized blob object IDs.");
}
const stdout = execute(
"git",
["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
projectRoot,
{ capture: true, input: `${objectIds.join("\n")}\n` },
);
const sizes = new Map();
for (const line of String(stdout || "").split(/\r?\n/u).filter(Boolean)) {
const match = line.match(/^(?<oid>[0-9a-f]{40}) (?<type>\S+) (?<size>\d+)$/u);
if (!match?.groups || !objectIds.includes(match.groups.oid)) {
throw new Error(`Unexpected git cat-file response: ${line}`);
}
if (match.groups.type !== "blob") {
throw new Error(`Object ${match.groups.oid} is ${match.groups.type}, not a blob.`);
}
const size = Number(match.groups.size);
if (!Number.isSafeInteger(size) || size < 0) {
throw new Error(`Object ${match.groups.oid} has an invalid size.`);
}
sizes.set(match.groups.oid, size);
}
for (const oid of objectIds) {
if (!sizes.has(oid)) {
throw new Error(`git cat-file did not return metadata for ${oid}.`);
}
}
return sizes;
}
function runGhJson(projectRoot, args, options = {}) {
const stdout = runCommand(
"gh",
@@ -1222,11 +1183,6 @@ function mergePullRequestImmediately(projectRoot, repoSlug, prDetails, dependenc
return response;
}
function isRetryableMergeError(error) {
const message = String(error?.message || error || "");
return BASE_BRANCH_MODIFIED_PATTERNS.some((pattern) => pattern.test(message));
}
function gitCheckoutMain(projectRoot) {
runCommand("git", ["checkout", "main"], projectRoot);
}
@@ -1364,7 +1320,6 @@ module.exports = {
assertEffectiveMainProtection,
assertFullSha,
assertUnchangedTuple,
baseBranchModifiedPatterns: BASE_BRANCH_MODIFIED_PATTERNS,
buildSquashMergeBody,
buildSquashMergeSubject,
checkRunMatchesAliases,
@@ -1376,7 +1331,6 @@ module.exports = {
getRequiredCheckAliases,
gitCheckoutMain,
gitPullMain,
isRetryableMergeError,
listActionRequiredRuns,
listCheckRuns,
listWorkflowDefinitions,
@@ -5,10 +5,13 @@ const path = require("path");
const { spawnSync } = require("child_process");
const sanitizeFilename = require("sanitize-filename");
const { resolveBlobSizes } = require("../lib/git-blob-sizes");
const { findProjectRoot } = require("../lib/project-root");
const { parseRawDiff } = require("../lib/git-raw-diff");
const {
classifyChangeRecords,
classifyChangedFiles,
classifyShadowImpact,
getDirectDerivedChanges,
hasIssueLink,
hasQualityChecklist,
@@ -19,10 +22,12 @@ const {
function parseArgs(argv) {
const args = {
repo: null,
base: null,
head: "HEAD",
eventPath: null,
checkPolicy: false,
checkForkSafety: false,
noRun: false,
writeGithubOutput: false,
writeStepSummary: false,
@@ -31,7 +36,10 @@ function parseArgs(argv) {
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--base") {
if (arg === "--repo") {
args.repo = argv[index + 1] || null;
index += 1;
} else if (arg === "--base") {
args.base = argv[index + 1];
index += 1;
} else if (arg === "--head") {
@@ -42,6 +50,8 @@ function parseArgs(argv) {
index += 1;
} else if (arg === "--check-policy") {
args.checkPolicy = true;
} else if (arg === "--check-fork-safety") {
args.checkForkSafety = true;
} else if (arg === "--no-run") {
args.noRun = true;
} else if (arg === "--write-github-output") {
@@ -163,14 +173,39 @@ function changedFilesFromRecords(records) {
.map(normalizeRepoPath))];
}
function loadPullRequestBody(eventPath) {
function loadPullRequestEvent(eventPath) {
if (!eventPath) {
return null;
}
const rawEvent = fs.readFileSync(safeUserPath(eventPath), "utf8");
const event = JSON.parse(rawEvent);
return event.pull_request?.body || "";
return JSON.parse(rawEvent).pull_request || null;
}
function evaluateForkSafety(projectRoot, changeRecords, pullRequest) {
if (!pullRequest) {
return { applicable: false, approvalSafe: true, reasons: [], requiresHumanReview: false };
}
const headRepository = String(pullRequest?.head?.repo?.full_name || "").toLowerCase();
const baseRepository = String(pullRequest?.base?.repo?.full_name || "").toLowerCase();
if (!headRepository || !baseRepository) {
return {
applicable: true,
approvalSafe: false,
reasons: ["pull_request_repository_identity_unavailable"],
requiresHumanReview: false,
};
}
if (headRepository === baseRepository) {
return { applicable: false, approvalSafe: true, reasons: [], requiresHumanReview: false };
}
const preliminary = classifyChangeRecords(changeRecords, { requireBlobSizes: false });
if (!preliminary.approvalSafe) {
return { applicable: true, ...preliminary };
}
const blobSizes = resolveBlobSizes(projectRoot, changeRecords);
return { applicable: true, ...classifyChangeRecords(changeRecords, { blobSizes }) };
}
function appendGithubOutput(result) {
@@ -188,6 +223,11 @@ function appendGithubOutput(result) {
`changed_files_count=${String(result.changedFiles.length)}`,
`has_quality_checklist=${String(result.prBody.hasQualityChecklist)}`,
`has_issue_link=${String(result.prBody.hasIssueLink)}`,
`fork_safety_applicable=${String(result.forkSafety.applicable)}`,
`fork_approval_safe=${String(result.forkSafety.approvalSafe)}`,
`fork_safety_reasons=${JSON.stringify(result.forkSafety.reasons)}`,
`impact_profile=${result.shadowImpact.profile}`,
`impact_reasons=${JSON.stringify(result.shadowImpact.reasons)}`,
];
fs.appendFileSync(outputPath, `${lines.join("\n")}\n`, "utf8");
@@ -214,6 +254,8 @@ function appendStepSummary(result) {
`- \`validate:references\` required: ${result.requiresReferencesValidation ? "yes" : "no"}`,
`- PR template checklist: ${result.prBody.hasQualityChecklist ? "present" : "missing"}`,
`- Issue auto-close link: ${result.prBody.hasIssueLink ? "detected" : "not detected"}`,
`- Fork approval safety: ${result.forkSafety.applicable ? (result.forkSafety.approvalSafe ? "safe" : "blocked") : "not applicable"}`,
`- Shadow impact profile: \`${result.shadowImpact.profile}\` (observational only; no test is skipped)`,
"",
"> Generated drift is reported separately in the artifact preview job and remains informational on pull requests.",
];
@@ -223,14 +265,17 @@ function appendStepSummary(result) {
function main() {
const args = parseArgs(process.argv.slice(2));
const projectRoot = findProjectRoot(__dirname);
const projectRoot = args.repo ? path.resolve(args.repo) : findProjectRoot(__dirname);
const contract = loadWorkflowContract(__dirname);
const baseRef = args.base || resolveBaseRef(projectRoot);
const changeRecords = getChangeRecords(projectRoot, baseRef, args.head);
const changedFiles = changedFilesFromRecords(changeRecords);
const classification = classifyChangedFiles(changedFiles, contract);
const directDerivedChanges = getDirectDerivedChanges(changedFiles, contract);
const pullRequestBody = loadPullRequestBody(args.eventPath);
const pullRequest = loadPullRequestEvent(args.eventPath);
const pullRequestBody = pullRequest?.body ?? null;
const forkSafety = evaluateForkSafety(projectRoot, changeRecords, pullRequest);
const shadowImpact = classifyShadowImpact(changeRecords, contract);
const result = {
baseRef,
@@ -241,6 +286,8 @@ function main() {
primaryCategory: classification.primaryCategory,
directDerivedChanges,
requiresReferencesValidation: requiresReferencesValidation(changedFiles, contract),
forkSafety,
shadowImpact,
prBody: {
available: pullRequestBody !== null,
hasQualityChecklist: hasQualityChecklist(pullRequestBody),
@@ -284,6 +331,11 @@ function main() {
}
}
if (args.checkForkSafety && forkSafety.applicable && !forkSafety.approvalSafe) {
console.error(`Fork PR is not approval-safe: ${forkSafety.reasons.slice(0, 12).join(", ") || "unclassified diff"}.`);
process.exit(1);
}
if (!args.noRun) {
runCommand("npm", ["run", "validate"], projectRoot);
@@ -299,4 +351,11 @@ if (require.main === module) {
main();
}
module.exports = { changedFilesFromRecords, getChangeRecords, getChangedFiles, parseArgs };
module.exports = {
changedFilesFromRecords,
evaluateForkSafety,
getChangeRecords,
getChangedFiles,
loadPullRequestEvent,
parseArgs,
};
@@ -162,9 +162,13 @@ function validateReleaseSuccessors(projectRoot, releaseCommit, headCommit, depen
const commits = runCommand("git", ["rev-list", "--reverse", `${releaseCommit}..${headCommit}`], projectRoot, {
capture: true,
}).split(/\r?\n/u).filter(Boolean);
const canonicalSyncSubjects = new Set([
"chore: synchronize canonical repository state",
"[skip pages] chore: synchronize canonical repository state",
]);
for (const commit of commits) {
const subject = runCommand("git", ["show", "-s", "--format=%s", commit], projectRoot, { capture: true });
if (subject !== "chore: synchronize canonical repository state") {
if (!canonicalSyncSubjects.has(subject)) {
throw new Error(`Unexpected commit ${commit} landed after the release candidate: ${subject}`);
}
}
@@ -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">&'\\\``),
"&lt;tag attr=&quot;value&quot;&gt;&amp;&#39;\\`",
"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"),