Files
PaperClipAI/scripts/check-runner-status-authority.mjs
2026-08-11 12:56:59 +00:00

328 lines
14 KiB
JavaScript

#!/usr/bin/env node
// Structural conformance gate for the status-authority corpus.
//
// Usage: pnpm check:runner-status-authority
//
// This validates the language-neutral fixture matrix before runtime
// implementations exist. Runtime suites consume the same fixture IDs and add
// database/transport setup without changing the expected semantics.
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const specPath = join(root, "packages", "paperclip-runner", "spec", "native-runner-contract.md");
const fixturePath = join(root, "packages", "paperclip-runner", "spec", "fixtures", "status-authority-sdk.json");
const spec = readFileSync(specPath, "utf8");
const corpus = JSON.parse(readFileSync(fixturePath, "utf8"));
const failures = [];
const checks = [];
function check(name, fn) {
try {
const detail = fn();
checks.push(`PASS ${name}${detail ? ` — ${detail}` : ""}`);
} catch (error) {
failures.push(`FAIL ${name} — ${error.message}`);
}
}
function section(source, heading) {
const start = source.indexOf(heading);
if (start === -1) throw new Error(`missing heading: ${heading}`);
const level = heading.match(/^#+/)[0].length;
const rest = source.slice(start + heading.length);
const next = rest.search(new RegExp(`\\n#{1,${level}} `));
return next === -1 ? rest : rest.slice(0, next);
}
function tableIds(source, prefix) {
return [...source.matchAll(new RegExp(`^\\| (${prefix}-\\d+) \\|`, "gm"))].map((match) => match[1]);
}
function assertExactCoverage(label, definedIds, coverKey) {
const covered = new Set(corpus.fixtures.flatMap((fixture) => fixture.covers[coverKey]));
const missing = definedIds.filter((id) => !covered.has(id));
const unknown = [...covered].filter((id) => !definedIds.includes(id));
if (missing.length || unknown.length) {
throw new Error([
missing.length ? `missing ${missing.join(", ")}` : null,
unknown.length ? `unknown ${unknown.join(", ")}` : null,
].filter(Boolean).join("; "));
}
return `${definedIds.length} ${label} rows covered`;
}
const decisionSection = section(spec, "### 18.3 Status authority and human-needed signaling");
const attentionSection = section(spec, "#### 18.3.7 Weak-agent and adversarial scenario check");
const terminalSection = section(spec, "### 18.5 Complete terminal conversion contract");
const statusAuthority = section(spec, "### 18.13 Status-authority conformance, migration, and rollback contract");
check("corpus header is versioned", () => {
if (corpus.schema !== "paperclip.status-authority-conformance.v1") {
throw new Error(`unexpected schema ${corpus.schema}`);
}
if (!Number.isInteger(corpus.corpusRevision) || corpus.corpusRevision < 1) {
throw new Error("corpusRevision must be a positive integer");
}
if (typeof corpus.policyVersion !== "string" || corpus.policyVersion.length === 0) {
throw new Error("policyVersion is required");
}
if (!Array.isArray(corpus.fixtures) || corpus.fixtures.length < 35) {
throw new Error(`fixture corpus looks truncated (${corpus.fixtures?.length ?? 0})`);
}
return `revision ${corpus.corpusRevision}, ${corpus.fixtures.length} fixtures`;
});
check("fixture IDs and required fields are valid", () => {
const ids = new Set();
const coverKeys = [
"decisionRows",
"terminalRows",
"attentionRows",
"livenessRows",
"reconciliationRows",
"compatibilityRows",
"migrationRows",
];
const requiredGiven = [
"priorIssueStatus",
"turnTerminalState",
"runTerminalState",
"reportedWorkDisposition",
"nativeFinalization",
"completionState",
"trigger",
];
const requiredExpected = [
"runStatus",
"statusAction",
"reasonCode",
"requiredEffects",
"forbiddenEffects",
"livePathKind",
"preserveClaim",
"nativeRecords",
"decisionCount",
"maxWakeCount",
"maxNotificationCount",
];
for (const fixture of corpus.fixtures) {
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(fixture.id)) throw new Error(`invalid fixture id ${fixture.id}`);
if (ids.has(fixture.id)) throw new Error(`duplicate fixture id ${fixture.id}`);
ids.add(fixture.id);
if (!["native", "legacy"].includes(fixture.mode)) throw new Error(`${fixture.id}: invalid mode`);
if (!fixture.covers || coverKeys.some((key) => !Array.isArray(fixture.covers[key]))) {
throw new Error(`${fixture.id}: incomplete covers object`);
}
if (!Array.isArray(fixture.tags)) throw new Error(`${fixture.id}: tags must be an array`);
if (!fixture.given || requiredGiven.some((key) => !(key in fixture.given))) {
throw new Error(`${fixture.id}: incomplete given facts`);
}
if (fixture.given.reviewGate !== undefined && !["completion", "mid_work"].includes(fixture.given.reviewGate)) {
throw new Error(`${fixture.id}: invalid reviewGate`);
}
if (!fixture.expected || requiredExpected.some((key) => !(key in fixture.expected))) {
throw new Error(`${fixture.id}: incomplete expected facts`);
}
if (!fixture.replay || !Number.isInteger(fixture.replay.attempts) || fixture.replay.attempts < 1) {
throw new Error(`${fixture.id}: invalid replay contract`);
}
}
return `${ids.size} unique IDs`;
});
check("every status-decision row has a fixture", () =>
assertExactCoverage("decision", tableIds(decisionSection, "SD"), "decisionRows"));
check("every terminal-conversion row has a fixture", () =>
assertExactCoverage("terminal", tableIds(terminalSection, "TC"), "terminalRows"));
check("every adversarial-attention row has a fixture", () =>
assertExactCoverage("attention", tableIds(attentionSection, "ATT"), "attentionRows"));
for (const [label, heading, prefix, coverKey] of [
["atomic liveness", "#### 18.13.2 Atomic liveness fixtures", "LIVE", "livenessRows"],
["reconciliation", "#### 18.13.3 Deterministic replay and reconciliation fixtures", "REC", "reconciliationRows"],
["compatibility", "#### 18.13.4 Native, legacy, and existing-state compatibility", "COMP", "compatibilityRows"],
["migration", "#### 18.13.5 Rollout and migration sequence", "MIG", "migrationRows"],
]) {
check(`every ${label} row has a fixture`, () =>
assertExactCoverage(label, tableIds(section(spec, heading), prefix), coverKey));
}
check("all stable decision reason codes are exercised", () => {
const enumBlock = spec.match(/`StatusDecisionReasonCode` is a stable protocol enum[\s\S]*?```text\n([\s\S]*?)```/);
if (!enumBlock) throw new Error("could not locate StatusDecisionReasonCode enum");
const defined = enumBlock[1].trim().split("\n").map((line) => line.trim()).filter(Boolean);
const exercised = new Set(corpus.fixtures.map((fixture) => fixture.expected.reasonCode).filter(Boolean));
const missing = defined.filter((code) => !exercised.has(code));
if (missing.length) throw new Error(`unexercised reason code(s): ${missing.join(", ")}`);
return `${defined.length} reason codes exercised`;
});
check("required adversarial tags are all selectable", () => {
const required = [
"premature_done_claim",
"incomplete_evidence",
"required_review",
"continuation",
"partial_progress",
"real_blocker",
"excessive_human_request",
"repeated_question",
"false_blocker",
"partial_evidence_before_failure",
"finalization_failure",
"cancellation_scope",
"authorized_resume",
"supersession",
"native_legacy_distinction",
"existing_issue_state",
"atomic_liveness",
"deterministic_replay",
"reconciliation",
"rollback",
];
const tags = new Set(corpus.fixtures.flatMap((fixture) => fixture.tags));
const missing = required.filter((tag) => !tags.has(tag));
if (missing.length) throw new Error(`missing tag(s): ${missing.join(", ")}`);
return `${required.length} required tags present`;
});
check("non-terminal status fixtures carry an atomic liveness path", () => {
const acceptedEffects = {
in_review: new Set(["bind_reviewer", "create_interaction"]),
blocked: new Set(["bind_blocker"]),
in_progress: new Set(["enqueue_continuation", "schedule_retry", "create_delegated_issue"]),
};
const violations = [];
for (const fixture of corpus.fixtures) {
const expected = fixture.expected;
const allowed = acceptedEffects[expected.statusAction];
if (!allowed) continue;
if (expected.livePathKind === null || !expected.requiredEffects.some((effect) => allowed.has(effect))) {
violations.push(fixture.id);
}
}
if (violations.length) throw new Error(`missing atomic path/effect: ${violations.join(", ")}`);
return "in_review, blocked, and in_progress paths validated";
});
check("duplicate attention creates no status decision or unbounded loop", () => {
const duplicates = corpus.fixtures.filter((fixture) => fixture.expected.reasonCode === "attention_duplicate_suppressed");
if (duplicates.length === 0) throw new Error("no duplicate-attention fixture");
const unsafe = duplicates.filter((fixture) =>
fixture.expected.decisionCount !== 0 ||
fixture.expected.maxWakeCount !== 0 ||
fixture.expected.maxNotificationCount !== 0 ||
fixture.replay.maxSemanticDecisions !== 0 ||
fixture.replay.maxDomainEffectsPerKey > 1);
if (unsafe.length) throw new Error(`unsafe duplicate fixture(s): ${unsafe.map((fixture) => fixture.id).join(", ")}`);
return `${duplicates.length} duplicate fixture(s) bounded at zero decisions/wakes/notifications`;
});
check("audit-only duplicate and stale fixtures retain exact durable targets", () => {
const expected = new Map([
["duplicate-and-fresh-key-question", ["equivalent_attention_family", "link_canonical_request"]],
["stale-attention-response", ["response_after_supersession", "record_stale_response"]],
]);
for (const [id, [completionState, effect]] of expected) {
const fixture = corpus.fixtures.find((candidate) => candidate.id === id);
if (!fixture) throw new Error(`missing ${id}`);
if (
fixture.mode !== "native"
|| fixture.given.completionState !== completionState
|| fixture.expected.runStatus !== "succeeded"
|| fixture.expected.statusAction !== "preserve"
|| fixture.expected.reasonCode !== "attention_duplicate_suppressed"
|| fixture.expected.decisionCount !== 0
|| fixture.expected.maxWakeCount !== 0
|| fixture.expected.maxNotificationCount !== 0
|| !fixture.expected.requiredEffects.includes(effect)
) {
throw new Error(`${id} does not pin the zero-decision ${effect} outcome`);
}
}
return "duplicate and stale targets are zero-decision audit outcomes";
});
check("completion-gating human judgment is explicit", () => {
const fixture = corpus.fixtures.find((candidate) => candidate.id === "human-authority-required");
if (!fixture) throw new Error("missing human-authority-required fixture");
if (!fixture.covers.decisionRows.includes("SD-11") ||
fixture.given.completionState !== "intentional_human_judgment" ||
fixture.given.reviewGate !== "completion" ||
fixture.expected.statusAction !== "in_review") {
throw new Error("human-authority-required does not pin the SD-11 completion-review branch");
}
return "human-authority-required pins reviewGate=completion";
});
check("replay effects are at-most-once per semantic key", () => {
const unsafe = corpus.fixtures.filter((fixture) =>
fixture.replay.maxDomainEffectsPerKey !== 1 ||
fixture.replay.maxSemanticDecisions > fixture.expected.decisionCount + 1);
if (unsafe.length) throw new Error(`unsafe replay bounds: ${unsafe.map((fixture) => fixture.id).join(", ")}`);
const replayed = corpus.fixtures.filter((fixture) => fixture.replay.attempts > 1);
if (replayed.length < 30) throw new Error(`too few replayed fixtures (${replayed.length})`);
return `${replayed.length} fixtures replayed with one effect per key`;
});
check("native and legacy expectations remain intentionally distinct", () => {
const legacy = corpus.fixtures.filter((fixture) => fixture.mode === "legacy");
const native = corpus.fixtures.filter((fixture) => fixture.mode === "native");
if (!legacy.length || !native.length) throw new Error("both modes are required");
const badLegacy = legacy.filter((fixture) =>
fixture.expected.nativeRecords || fixture.expected.decisionCount !== 0 || fixture.expected.statusAction !== "legacy_finalizer");
const badNative = native.filter((fixture) => fixture.expected.statusAction === "legacy_finalizer");
if (badLegacy.length || badNative.length) {
throw new Error(`mode leakage in: ${[...badLegacy, ...badNative].map((fixture) => fixture.id).join(", ")}`);
}
return `${native.length} native / ${legacy.length} legacy fixtures`;
});
check("rollback fixture forbids active-run mode conversion", () => {
const rollback = corpus.fixtures.find((fixture) => fixture.covers.migrationRows.includes("MIG-08"));
if (!rollback) throw new Error("missing MIG-08 fixture");
if (!rollback.expected.requiredEffects.includes("fresh_flag_off_run_selects_legacy") ||
!rollback.expected.requiredEffects.includes("finish_as_native") ||
!rollback.expected.forbiddenEffects.includes("convert_active_run_to_legacy") ||
!rollback.expected.forbiddenEffects.includes("persist_agent_kill_switch")) {
throw new Error("MIG-08 does not prove global flag-off selection plus immutable active-run mode");
}
return rollback.id;
});
check("status-authority conformance markdown tables are rectangular", () => {
const lines = statusAuthority.split("\n");
let inFence = false;
let expectedColumns = null;
let tables = 0;
lines.forEach((line, index) => {
if (line.trimStart().startsWith("```")) inFence = !inFence;
if (inFence) return;
const isRow = line.trimStart().startsWith("|") && line.trimEnd().endsWith("|");
if (!isRow) {
expectedColumns = null;
return;
}
const columns = line.trim().slice(1, -1).split(/(?<!\\)\|/).length;
if (expectedColumns === null) {
expectedColumns = columns;
tables += 1;
} else if (columns !== expectedColumns) {
throw new Error(`ragged table at status-authority conformance offset line ${index + 1}: ${columns} vs ${expectedColumns}`);
}
});
if (inFence) throw new Error("unbalanced code fence in section 18.13");
return `${tables} tables`;
});
for (const line of checks) console.log(line);
for (const line of failures) console.error(line);
console.log(`\n${checks.length} passed, ${failures.length} failed`);
process.exit(failures.length ? 1 : 0);