Skip to content
13 changes: 3 additions & 10 deletions scripts/delivery-controller.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const USAGE = `Usage:
node scripts/delivery-controller.mjs retry CHECKPOINT
node scripts/delivery-controller.mjs evidence-action CHECKPOINT
node scripts/delivery-controller.mjs usage CHECKPOINT --workflow-tokens N --phase-tokens N
node scripts/delivery-controller.mjs refs CHECKPOINT [--base SHA] [--head SHA]
node scripts/delivery-controller.mjs authorize-mutation CHECKPOINT --request FILE [--workflow-intent] [--exact-text-confirmed]
node scripts/delivery-controller.mjs blocker-add CHECKPOINT BLOCKER
node scripts/delivery-controller.mjs blocker-remove CHECKPOINT BLOCKER
Expand Down Expand Up @@ -176,7 +175,9 @@ try {
const checkpoint = argv.shift();
if (!checkpoint) throw new Error(USAGE);
assertEmpty(argv);
print(readDeliveryWorkflowCheckpoint(resolve(checkpoint)));
print(load(checkpoint).controller.snapshot());
} else if (command === "refs") {
throw new Error("controller_refs_are_internal_only");
} else {
const checkpointValue = argv.shift();
if (!checkpointValue) throw new Error(USAGE);
Expand Down Expand Up @@ -211,14 +212,6 @@ try {
workflowTokens: workflowTokens === null ? undefined : nonNegativeInteger(workflowTokens, "--workflow-tokens"),
phaseTokens: phaseTokens === null ? undefined : nonNegativeInteger(phaseTokens, "--phase-tokens"),
});
} else if (command === "refs") {
const baseSha = takeOption(argv, "--base");
const headSha = takeOption(argv, "--head");
assertEmpty(argv);
result = loaded.controller.updateRefs({
...(baseSha !== null ? { baseSha } : {}),
...(headSha !== null ? { headSha } : {}),
});
} else if (command === "authorize-mutation") {
const requestPath = takeOption(argv, "--request");
const trustedWorkflowIntent = takeFlag(argv, "--workflow-intent");
Expand Down
47 changes: 46 additions & 1 deletion scripts/lib/delivery-workflow-controller.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,36 @@ function normalizeHygienePasses(value) {
};
}

function normalizePhaseReceipts(value, graph, completedPhases) {
if (!Array.isArray(value)) return [];
const completed = new Set(completedPhases);
const receipts = [];
const seen = new Set();
for (const entry of value) {
const phase = String(entry?.phase || "");
if (!phase || seen.has(phase) || !completed.has(phase) || !Object.hasOwn(graph, phase)) continue;
if (entry?.authority !== "controller-transition") continue;
receipts.push({
phase,
authority: "controller-transition",
stateGeneration: nonNegativeInteger(entry?.stateGeneration),
baseSha: entry?.baseSha ? String(entry.baseSha) : null,
headSha: entry?.headSha ? String(entry.headSha) : null,
issue: entry?.issue ?? null,
pr: entry?.pr ?? null,
completedAt: Number.isFinite(entry?.completedAt) ? entry.completedAt : null,
});
seen.add(phase);
}
return receipts;
}

function nextActionForPhase(phase) {
return phase === "DONE"
? { action: "stop", phase: "DONE", authority: "controller-checkpoint" }
: { action: "execute_phase", phase, authority: "controller-checkpoint" };
}

function sameIdentity(left, right) {
return String(left || "").toLowerCase() === String(right || "").toLowerCase();
}
Expand Down Expand Up @@ -234,6 +264,7 @@ export function createDeliveryWorkflowController(options = {}) {
);
let stateGeneration = nonNegativeInteger(snapshot?.stateGeneration);
const completedPhases = [...(snapshot?.completedPhases || [])].map(String);
const phaseReceipts = normalizePhaseReceipts(snapshot?.phaseReceipts, graph, completedPhases);
const blockers = new Set((snapshot?.blockers || []).map(String));
const attempts = {
workflowSteps: nonNegativeInteger(snapshot?.attempts?.workflowSteps),
Expand Down Expand Up @@ -269,8 +300,10 @@ export function createDeliveryWorkflowController(options = {}) {
publicationPlan: publicationPlan ? structuredClone(publicationPlan) : null,
publicationReceipts: structuredClone(publicationReceipts),
phase,
nextAction: nextActionForPhase(phase),
graph,
completedPhases: [...completedPhases],
phaseReceipts: phaseReceipts.map((entry) => structuredClone(entry)),
blockers: [...blockers].sort(),
stateGeneration,
attempts: { ...attempts },
Expand Down Expand Up @@ -309,7 +342,19 @@ export function createDeliveryWorkflowController(options = {}) {
if (phase === "OPEN_PR" && workflow === "create-pr-from-local-work") {
assertCreatePrPublicationComplete(snapshotState());
}
if (!completedPhases.includes(phase)) completedPhases.push(phase);
if (!completedPhases.includes(phase)) {
completedPhases.push(phase);
phaseReceipts.push({
phase,
authority: "controller-transition",
stateGeneration,
baseSha: baseSha ? String(baseSha) : null,
headSha: headSha ? String(headSha) : null,
issue,
pr,
completedAt: now(),
});
}
phase = target;
attempts.noProgressSteps = 0;
attempts.phaseRetries = 0;
Expand Down
15 changes: 11 additions & 4 deletions scripts/lib/workflow-bootstrap.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { dirname, join, resolve } from "node:path";
import {
createDeliveryWorkflowController,
readDeliveryWorkflowCheckpoint,
writeDeliveryWorkflowCheckpoint,
} from "./delivery-workflow-controller.mjs";
import { resolveDeliveryWorkflowProfile } from "./delivery-workflow-profiles.mjs";

Expand Down Expand Up @@ -94,19 +95,25 @@ export function bootstrapLocalPrWorkflow({ repo, headSha, baseSha = null, stateD
reused = true;
}

const snapshot = readDeliveryWorkflowCheckpoint(checkpointPath);
assertCheckpointIdentity(snapshot, {
const storedSnapshot = readDeliveryWorkflowCheckpoint(checkpointPath);
assertCheckpointIdentity(storedSnapshot, {
repo: normalizedRepo,
headSha: normalizedHead,
});
if (
baseSha &&
snapshot.baseSha &&
String(snapshot.baseSha).toLowerCase() !== String(baseSha).toLowerCase()
storedSnapshot.baseSha &&
String(storedSnapshot.baseSha).toLowerCase() !== String(baseSha).toLowerCase()
) {
throw new Error("workflow_bootstrap_checkpoint_base_mismatch");
}

const snapshot = createDeliveryWorkflowController({
snapshot: storedSnapshot,
graph: profile.graph,
}).snapshot();
if (reused) writeDeliveryWorkflowCheckpoint(checkpointPath, snapshot);

return {
checkpointPath,
reused,
Expand Down
11 changes: 10 additions & 1 deletion scripts/lib/workflow-execution-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ const NORMAL_HELPERS = Object.freeze({
shipGate: "scripts/ship-gate.mjs",
});

const CONTROLLER_STATE_CONTRACT = Object.freeze({
source: "controller-checkpoint",
nextAction: "authoritative",
completedPhaseReceipts: "authoritative",
reasoningClaims: "non-authoritative",
externalStateClaims: "structured-evidence-only",
});

const DECLARED_ACTIONS = Object.freeze({
"create-pr-for-issue": Object.freeze([
"assign_issue",
Expand Down Expand Up @@ -65,9 +73,10 @@ export function executionContractForWorkflow(workflow) {
helpers: { ...NORMAL_HELPERS },
declaredActions: [...(DECLARED_ACTIONS[workflow] || [])],
sourceDiscovery: "diagnostic-only",
controllerState: { ...CONTROLLER_STATE_CONTRACT },
...(workflowPlan ? { workflowPlan: structuredClone(workflowPlan) } : {}),
normalOperation:
"Use this packet and its declared helpers/actions for normal workflow execution. Do not re-decide a locked route, publication path, or initial PR state after packet/controller resolution. Read or grep github-delivery implementation source only after a concrete internal contract/helper failure requires diagnostic escalation. If a higher-priority instruction genuinely conflicts with the locked safe write path, fail closed once; do not fall back to a different GitHub write path.",
"Use this packet and its controller checkpoint for normal workflow execution. Treat checkpoint nextAction and completed phase receipts as authoritative progress state; reasoning prose, remembered SHAs/PRs/checks, and unstamped claims are non-authoritative. Do not re-decide a locked route, publication path, initial PR state, or already completed phase. Read or grep github-delivery implementation source only after a concrete internal contract/helper failure requires diagnostic escalation. If a higher-priority instruction genuinely conflicts with the locked safe write path, fail closed once; do not fall back to a different GitHub write path.",
};
}

Expand Down
209 changes: 209 additions & 0 deletions tests/unit/workflow-state-grounding.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import test from "node:test";

import {
createDeliveryWorkflowController,
readDeliveryWorkflowCheckpoint,
writeDeliveryWorkflowCheckpoint,
} from "../../scripts/lib/delivery-workflow-controller.mjs";
import {
bootstrapLocalPrWorkflow,
localPrWorkflowCheckpointPath,
} from "../../scripts/lib/workflow-bootstrap.mjs";
import { executionContractForWorkflow } from "../../scripts/lib/workflow-execution-contract.mjs";

const DELIVERY_CONTROLLER = fileURLToPath(
new URL("../../scripts/delivery-controller.mjs", import.meta.url),
);
const HEAD = "b".repeat(40);
const BASE = "a".repeat(40);
const GRAPH = {
ROUTE: ["PREFLIGHT"],
PREFLIGHT: ["LOCAL_VERIFY", "DONE"],
LOCAL_VERIFY: ["DONE"],
DONE: [],
};

function controller() {
let at = 1_000;
return createDeliveryWorkflowController({
workflow: "create-pr-from-local-work",
repo: "acme/widgets",
baseSha: BASE,
headSha: HEAD,
graph: GRAPH,
startPhase: "ROUTE",
now: () => ++at,
});
}

test("controller exposes one authoritative current action and controller-owned phase receipts", () => {
const current = controller();
let snapshot = current.snapshot();
assert.deepEqual(snapshot.nextAction, {
action: "execute_phase",
phase: "ROUTE",
authority: "controller-checkpoint",
});
assert.deepEqual(snapshot.phaseReceipts, []);

current.transition("PREFLIGHT");
snapshot = current.snapshot();
assert.deepEqual(snapshot.nextAction, {
action: "execute_phase",
phase: "PREFLIGHT",
authority: "controller-checkpoint",
});
assert.equal(snapshot.phaseReceipts.length, 1);
assert.deepEqual(snapshot.phaseReceipts[0], {
phase: "ROUTE",
authority: "controller-transition",
stateGeneration: 0,
baseSha: BASE,
headSha: HEAD,
issue: null,
pr: null,
completedAt: snapshot.phaseReceipts[0].completedAt,
});
assert.ok(Number.isFinite(snapshot.phaseReceipts[0].completedAt));

current.transition("DONE");
snapshot = current.snapshot();
assert.deepEqual(snapshot.nextAction, {
action: "stop",
phase: "DONE",
authority: "controller-checkpoint",
});
assert.deepEqual(snapshot.phaseReceipts.map((entry) => entry.phase), ["ROUTE", "PREFLIGHT"]);
});

test("checkpoint resume preserves receipts and the same singular next action", () => {
const directory = mkdtempSync(join(tmpdir(), "github-delivery-grounding-"));
const checkpoint = join(directory, "checkpoint.json");
try {
const current = controller();
current.transition("PREFLIGHT");
writeDeliveryWorkflowCheckpoint(checkpoint, current.snapshot());

const saved = readDeliveryWorkflowCheckpoint(checkpoint);
const resumed = createDeliveryWorkflowController({ snapshot: saved, graph: GRAPH });
assert.deepEqual(resumed.snapshot().nextAction, saved.nextAction);
assert.deepEqual(resumed.snapshot().phaseReceipts, saved.phaseReceipts);
assert.equal(resumed.snapshot().phase, "PREFLIGHT");
assert.deepEqual(resumed.snapshot().completedPhases, ["ROUTE"]);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});

test("show re-derives controller state instead of trusting raw checkpoint guidance", () => {
const directory = mkdtempSync(join(tmpdir(), "github-delivery-show-grounding-"));
const checkpoint = join(directory, "checkpoint.json");
try {
const snapshot = controller().snapshot();
snapshot.nextAction = { action: "stop", phase: "DONE", authority: "model-prose" };
writeDeliveryWorkflowCheckpoint(checkpoint, snapshot);

const result = spawnSync(process.execPath, [DELIVERY_CONTROLLER, "show", checkpoint], {
encoding: "utf8",
});
assert.equal(result.status, 0, result.stderr);
const shown = JSON.parse(result.stdout);
assert.deepEqual(shown.nextAction, {
action: "execute_phase",
phase: "ROUTE",
authority: "controller-checkpoint",
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
});

test("local workflow bootstrap upgrades reused raw checkpoint guidance", () => {
const stateDir = mkdtempSync(join(tmpdir(), "github-delivery-bootstrap-grounding-"));
try {
const initial = controller();
initial.transition("PREFLIGHT");
const legacy = initial.snapshot();
delete legacy.phaseReceipts;
legacy.nextAction = { action: "stop", phase: "DONE", authority: "model-prose" };
const checkpointPath = localPrWorkflowCheckpointPath({
repo: "acme/widgets",
headSha: HEAD,
stateDir,
});
writeDeliveryWorkflowCheckpoint(checkpointPath, legacy);

const bootstrapped = bootstrapLocalPrWorkflow({
repo: "acme/widgets",
headSha: HEAD,
baseSha: BASE,
stateDir,
});
assert.equal(bootstrapped.reused, true);
assert.deepEqual(bootstrapped.snapshot.nextAction, {
action: "execute_phase",
phase: "PREFLIGHT",
authority: "controller-checkpoint",
});
assert.deepEqual(bootstrapped.snapshot.phaseReceipts, []);
} finally {
rmSync(stateDir, { recursive: true, force: true });
}
});

test("model reasoning claims cannot change controller identity, phase, or receipts", () => {
const current = controller();
const before = current.snapshot();
current.observeCycle({
narrationChanged: true,
claimedHeadSha: "f".repeat(40),
claimedBaseSha: "e".repeat(40),
claimedPr: 999,
claimedChecks: "green",
claimedPhase: "DONE",
});
const after = current.snapshot();

assert.equal(after.headSha, before.headSha);
assert.equal(after.baseSha, before.baseSha);
assert.equal(after.pr, before.pr);
assert.equal(after.phase, before.phase);
assert.deepEqual(after.phaseReceipts, before.phaseReceipts);
assert.deepEqual(after.nextAction, before.nextAction);
assert.equal(after.attempts.noProgressSteps, 1);
});

test("public controller CLI cannot inject model-authored refs", () => {
const directory = mkdtempSync(join(tmpdir(), "github-delivery-ref-grounding-"));
const checkpoint = join(directory, "checkpoint.json");
try {
writeDeliveryWorkflowCheckpoint(checkpoint, controller().snapshot());
const result = spawnSync(
process.execPath,
[DELIVERY_CONTROLLER, "refs", checkpoint, "--head", "f".repeat(40)],
{ encoding: "utf8" },
);
assert.equal(result.status, 2);
assert.match(`${result.stderr}\n${result.stdout}`, /controller_refs_are_internal_only/);
assert.equal(readDeliveryWorkflowCheckpoint(checkpoint).headSha, HEAD);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});

test("execution contract makes checkpoint state authoritative over reasoning claims", () => {
const contract = executionContractForWorkflow("create-pr-from-local-work");
assert.deepEqual(contract.controllerState, {
source: "controller-checkpoint",
nextAction: "authoritative",
completedPhaseReceipts: "authoritative",
reasoningClaims: "non-authoritative",
externalStateClaims: "structured-evidence-only",
});
});
Loading