From 7c957284b21fb1cab4dede89b2a8b1d95d9051f7 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 03:25:02 +0200 Subject: [PATCH 01/12] test: define mandatory workflow profiles and one-shot policy packets --- .../unit/delivery-workflow-profiles.test.mjs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/unit/delivery-workflow-profiles.test.mjs diff --git a/tests/unit/delivery-workflow-profiles.test.mjs b/tests/unit/delivery-workflow-profiles.test.mjs new file mode 100644 index 0000000..8dc813f --- /dev/null +++ b/tests/unit/delivery-workflow-profiles.test.mjs @@ -0,0 +1,104 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + buildWorkflowPacket, + listDeliveryWorkflowProfiles, + resolveDeliveryWorkflowProfile, +} from "../../scripts/lib/delivery-workflow-profiles.mjs"; + +const ROUTED_WORKFLOWS = [ + "issue-workflows", + "agent-brief", + "out-of-scope", + "fix-pr-bots", + "watch-pr", + "re-review-pr", + "research-issue", + "create-pr-from-local-work", + "create-pr-for-issue", + "full-review-pr", + "spec-standards-review", + "simplify-pr", + "security-review", + "status", + "merge-pr", + "supersede-pr", + "overtake-pr", + "resolve-conflicts", + "stacked-prs", + "prepare-and-merge-pr", +]; + +test("every routed GitHub Delivery workflow has a controller profile", () => { + const available = new Set(listDeliveryWorkflowProfiles().map((profile) => profile.workflow)); + for (const workflow of ROUTED_WORKFLOWS) { + assert.equal(available.has(workflow), true, `missing controller profile: ${workflow}`); + } +}); + +test("create PR profile preserves bounded preflight through final gate lifecycle", () => { + const profile = resolveDeliveryWorkflowProfile("references/create-pr-for-issue.md"); + assert.equal(profile.workflow, "create-pr-for-issue"); + assert.deepEqual(profile.graph.PREFLIGHT, ["IMPLEMENT", "EXISTING_PR", "DONE"]); + assert.deepEqual(profile.graph.IMPLEMENT, ["LOCAL_VERIFY"]); + assert.deepEqual(profile.graph.CI, ["FINAL_GATE"]); + assert.deepEqual(profile.graph.FINAL_GATE, ["DONE"]); +}); + +test("status profile cannot drift into mutation phases", () => { + const profile = resolveDeliveryWorkflowProfile("status"); + const phases = new Set(Object.keys(profile.graph)); + for (const forbidden of ["IMPLEMENT", "PUBLISH_CHANGE", "MERGE"]) { + assert.equal(phases.has(forbidden), false, forbidden); + } +}); + +test("one-shot workflow packet contains selected workflow and unconditional policy exactly once", () => { + const packet = buildWorkflowPacket({ + root: process.cwd(), + workflow: "status", + }); + assert.equal(packet.kind, "github-delivery/workflow-packet"); + assert.equal(packet.workflow, "status"); + assert.equal(packet.profile.workflow, "status"); + const paths = packet.documents.map((document) => document.path); + assert.equal(paths.length, new Set(paths).size); + assert.equal(paths.includes("references/status.md"), true); + assert.equal(paths.includes("references/policy-kernel.md"), true); + assert.equal(paths.includes("references/shared-rules.md"), false); + assert.match(packet.packetHash, /^[a-f0-9]{64}$/); + for (const document of packet.documents) { + assert.match(document.sha256, /^[a-f0-9]{64}$/); + assert.equal(typeof document.content, "string"); + assert.ok(document.content.length > 0); + } +}); + +test("conditional modules are excluded by default and included only when explicitly activated", () => { + const base = buildWorkflowPacket({ + root: process.cwd(), + workflow: "create-pr-for-issue", + }); + const conditional = base.policy.conditionalModules[0]; + if (!conditional) return; + + assert.equal(base.documents.some((document) => document.path === conditional.path), false); + const activated = buildWorkflowPacket({ + root: process.cwd(), + workflow: "create-pr-for-issue", + activeConditionalModules: [conditional.module], + }); + assert.equal( + activated.documents.filter((document) => document.path === conditional.path).length, + 1, + ); +}); + +test("unknown or non-routed workflow names fail closed", () => { + assert.throws(() => resolveDeliveryWorkflowProfile("made-up-workflow"), /unknown.*workflow/i); + assert.throws( + () => buildWorkflowPacket({ root: process.cwd(), workflow: "made-up-workflow" }), + /unknown.*workflow/i, + ); +}); From fc3ff3810d86b88ef6d3709f26372f9793b18cb0 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 03:25:48 +0200 Subject: [PATCH 02/12] feat: map all routed workflows to controller profiles and one-shot packets --- scripts/lib/delivery-workflow-profiles.mjs | 265 +++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 scripts/lib/delivery-workflow-profiles.mjs diff --git a/scripts/lib/delivery-workflow-profiles.mjs b/scripts/lib/delivery-workflow-profiles.mjs new file mode 100644 index 0000000..05638ea --- /dev/null +++ b/scripts/lib/delivery-workflow-profiles.mjs @@ -0,0 +1,265 @@ +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { basename, join, resolve } from "node:path"; + +import { + parsePolicyDependencies, + resolvePolicyBundle, +} from "./policy-bundle.mjs"; + +const TERMINAL = Object.freeze({ DONE: [] }); + +const ISSUE_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["DRAFT", "DONE"], + DRAFT: ["VERIFY"], + VERIFY: ["PUBLISH", "DONE"], + PUBLISH: ["FINAL_GATE"], + FINAL_GATE: ["DONE"], + ...TERMINAL, +}); + +const REVIEW_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["ANALYZE", "DONE"], + ANALYZE: ["APPLY_FIXES", "VERIFY"], + APPLY_FIXES: ["VERIFY"], + VERIFY: ["CI", "FINAL_GATE"], + CI: ["FINAL_GATE"], + FINAL_GATE: ["PUBLISH_VERDICT", "DONE"], + PUBLISH_VERDICT: ["DONE"], + ...TERMINAL, +}); + +const CREATE_PR_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["IMPLEMENT", "EXISTING_PR", "DONE"], + IMPLEMENT: ["LOCAL_VERIFY"], + EXISTING_PR: ["REVIEW_FEEDBACK", "LOCAL_VERIFY"], + LOCAL_VERIFY: ["PREOPEN_GATE", "REVIEW_FEEDBACK"], + PREOPEN_GATE: ["OPEN_PR", "REVIEW_FEEDBACK"], + OPEN_PR: ["REVIEW_FEEDBACK"], + REVIEW_FEEDBACK: ["CI", "FINAL_GATE"], + CI: ["FINAL_GATE"], + FINAL_GATE: ["DONE"], + ...TERMINAL, +}); + +const LOCAL_PR_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["LOCAL_VERIFY", "DONE"], + LOCAL_VERIFY: ["PREOPEN_GATE"], + PREOPEN_GATE: ["OPEN_PR"], + OPEN_PR: ["REVIEW_FEEDBACK"], + REVIEW_FEEDBACK: ["CI", "FINAL_GATE"], + CI: ["FINAL_GATE"], + FINAL_GATE: ["DONE"], + ...TERMINAL, +}); + +const WATCH_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["WATCH", "DONE"], + WATCH: ["FINAL_GATE", "BLOCKED", "DONE"], + FINAL_GATE: ["WATCH", "DONE"], + BLOCKED: ["DONE"], + ...TERMINAL, +}); + +const RESEARCH_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["RESEARCH", "DONE"], + RESEARCH: ["VERIFY"], + VERIFY: ["PUBLISH", "DONE"], + PUBLISH: ["DONE"], + ...TERMINAL, +}); + +const STATUS_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["SNAPSHOT"], + SNAPSHOT: ["FINAL_GATE", "REPORT"], + FINAL_GATE: ["REPORT"], + REPORT: ["DONE"], + ...TERMINAL, +}); + +const MERGE_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["PREPARE", "DONE"], + PREPARE: ["FINAL_GATE"], + FINAL_GATE: ["MERGE", "DONE"], + MERGE: ["VERIFY"], + VERIFY: ["DONE"], + ...TERMINAL, +}); + +const CHANGE_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["PREPARE", "DONE"], + PREPARE: ["PUBLISH_CHANGE", "VERIFY"], + PUBLISH_CHANGE: ["VERIFY"], + VERIFY: ["FINAL_GATE", "DONE"], + FINAL_GATE: ["DONE"], + ...TERMINAL, +}); + +const CONFLICT_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["RESOLVE", "DONE"], + RESOLVE: ["LOCAL_VERIFY"], + LOCAL_VERIFY: ["CI", "FINAL_GATE"], + CI: ["FINAL_GATE"], + FINAL_GATE: ["DONE"], + ...TERMINAL, +}); + +const STACK_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["TOPOLOGY", "DONE"], + TOPOLOGY: ["RESTACK", "VERIFY"], + RESTACK: ["VERIFY"], + VERIFY: ["FINAL_GATE", "DONE"], + FINAL_GATE: ["DONE"], + ...TERMINAL, +}); + +const PREPARE_MERGE_GRAPH = Object.freeze({ + ROUTE: ["PREFLIGHT"], + PREFLIGHT: ["PREPARE", "DONE"], + PREPARE: ["ANALYZE"], + ANALYZE: ["APPLY_FIXES", "VERIFY"], + APPLY_FIXES: ["VERIFY"], + VERIFY: ["CI", "FINAL_GATE"], + CI: ["FINAL_GATE"], + FINAL_GATE: ["MERGE", "DONE"], + MERGE: ["VERIFY_MERGE"], + VERIFY_MERGE: ["DONE"], + ...TERMINAL, +}); + +const PROFILE_DEFINITIONS = Object.freeze({ + "issue-workflows": { graph: ISSUE_GRAPH, mutation: "profile-dependent" }, + "agent-brief": { graph: ISSUE_GRAPH, mutation: "profile-dependent" }, + "out-of-scope": { graph: ISSUE_GRAPH, mutation: "profile-dependent" }, + "fix-pr-bots": { graph: REVIEW_GRAPH, mutation: "review" }, + "watch-pr": { graph: WATCH_GRAPH, mutation: "read-mostly" }, + "re-review-pr": { graph: REVIEW_GRAPH, mutation: "review" }, + "research-issue": { graph: RESEARCH_GRAPH, mutation: "read-mostly" }, + "create-pr-from-local-work": { graph: LOCAL_PR_GRAPH, mutation: "maintainer" }, + "create-pr-for-issue": { graph: CREATE_PR_GRAPH, mutation: "maintainer" }, + "full-review-pr": { graph: REVIEW_GRAPH, mutation: "review" }, + "spec-standards-review": { graph: REVIEW_GRAPH, mutation: "review" }, + "simplify-pr": { graph: REVIEW_GRAPH, mutation: "maintainer" }, + "security-review": { graph: REVIEW_GRAPH, mutation: "review" }, + status: { graph: STATUS_GRAPH, mutation: "read-only" }, + "merge-pr": { graph: MERGE_GRAPH, mutation: "maintainer" }, + "supersede-pr": { graph: CHANGE_GRAPH, mutation: "maintainer" }, + "overtake-pr": { graph: CHANGE_GRAPH, mutation: "maintainer" }, + "resolve-conflicts": { graph: CONFLICT_GRAPH, mutation: "maintainer" }, + "stacked-prs": { graph: STACK_GRAPH, mutation: "profile-dependent" }, + "prepare-and-merge-pr": { graph: PREPARE_MERGE_GRAPH, mutation: "maintainer" }, +}); + +function normalizeWorkflow(value) { + let workflow = String(value || "").trim().replaceAll("\\", "/"); + if (workflow.startsWith("references/")) workflow = workflow.slice("references/".length); + if (workflow.endsWith(".md")) workflow = workflow.slice(0, -3); + return workflow; +} + +function copyGraph(graph) { + return Object.fromEntries(Object.entries(graph).map(([phase, targets]) => [phase, [...targets]])); +} + +export function listDeliveryWorkflowProfiles() { + return Object.entries(PROFILE_DEFINITIONS) + .map(([workflow, definition]) => ({ + workflow, + workflowPath: `references/${workflow}.md`, + startPhase: "ROUTE", + mutation: definition.mutation, + graph: copyGraph(definition.graph), + })) + .sort((a, b) => a.workflow.localeCompare(b.workflow)); +} + +export function resolveDeliveryWorkflowProfile(value) { + const workflow = normalizeWorkflow(value); + const definition = PROFILE_DEFINITIONS[workflow]; + if (!definition) throw new Error(`unknown delivery workflow: ${workflow || "(empty)"}`); + return { + workflow, + workflowPath: `references/${workflow}.md`, + startPhase: "ROUTE", + mutation: definition.mutation, + graph: copyGraph(definition.graph), + }; +} + +function hash(value) { + return createHash("sha256").update(value).digest("hex"); +} + +function document(root, path) { + const content = readFileSync(join(root, ...path.split("/")), "utf8"); + return { path, sha256: hash(content), content }; +} + +function dependencyPath(name) { + if (name === "policy-kernel") return "references/policy-kernel.md"; + return `references/policy/${name}.md`; +} + +function addDocumentWithDependencies(root, path, documents, seen) { + if (seen.has(path)) return; + const item = document(root, path); + seen.add(path); + documents.push(item); + if (!path.startsWith("references/policy/") || !path.endsWith(".md")) return; + for (const dependency of parsePolicyDependencies(item.content)) { + addDocumentWithDependencies(root, dependencyPath(dependency), documents, seen); + } +} + +export function buildWorkflowPacket({ + root = process.cwd(), + workflow, + activeConditionalModules = [], +} = {}) { + const repositoryRoot = resolve(root); + const profile = resolveDeliveryWorkflowProfile(workflow); + const policy = resolvePolicyBundle({ root: repositoryRoot, workflow: profile.workflow }); + const allowedConditional = new Map( + policy.conditionalModules.map((entry) => [entry.module, entry.path]), + ); + const active = [...new Set(activeConditionalModules.map(String))].sort(); + for (const module of active) { + if (!allowedConditional.has(module)) { + throw new Error(`conditional policy module is not declared for ${profile.workflow}: ${module}`); + } + } + + const documents = []; + const seen = new Set(); + addDocumentWithDependencies(repositoryRoot, profile.workflowPath, documents, seen); + addDocumentWithDependencies(repositoryRoot, policy.kernelPath, documents, seen); + for (const path of policy.modules) addDocumentWithDependencies(repositoryRoot, path, documents, seen); + for (const module of active) { + addDocumentWithDependencies(repositoryRoot, allowedConditional.get(module), documents, seen); + } + + const packetHash = hash( + documents.map((item) => `${item.path}\0${item.sha256}`).join("\n"), + ); + return { + schemaVersion: 1, + kind: "github-delivery/workflow-packet", + workflow: profile.workflow, + profile, + policy, + activeConditionalModules: active, + documents, + packetHash, + }; +} From 13248a57d2d6705c171e36947781085dc44d335b Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 03:26:16 +0200 Subject: [PATCH 03/12] feat: add one-shot workflow packet CLI --- scripts/workflow-brief.mjs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 scripts/workflow-brief.mjs diff --git a/scripts/workflow-brief.mjs b/scripts/workflow-brief.mjs new file mode 100644 index 0000000..57cf79f --- /dev/null +++ b/scripts/workflow-brief.mjs @@ -0,0 +1,37 @@ +#!/usr/bin/env node +import { resolve } from "node:path"; + +import { buildWorkflowPacket } from "./lib/delivery-workflow-profiles.mjs"; + +const USAGE = + "Usage: node scripts/workflow-brief.mjs WORKFLOW [--root ROOT] [--conditional MODULE ...]"; + +function parseArgs(argv) { + const workflow = argv[0]; + if (!workflow) throw new Error(USAGE); + let root = process.cwd(); + const activeConditionalModules = []; + for (let index = 1; index < argv.length; index += 1) { + const value = argv[index]; + if (value === "--root") { + root = argv[++index]; + if (!root) throw new Error("--root requires a path"); + } else if (value === "--conditional") { + const module = argv[++index]; + if (!module) throw new Error("--conditional requires a module name"); + activeConditionalModules.push(module); + } else { + throw new Error(`Unknown option: ${value}\n${USAGE}`); + } + } + return { workflow, root: resolve(root), activeConditionalModules }; +} + +try { + const args = parseArgs(process.argv.slice(2)); + const packet = buildWorkflowPacket(args); + process.stdout.write(`${JSON.stringify(packet, null, 2)}\n`); +} catch (error) { + console.error(String(error?.message || error)); + process.exitCode = 2; +} From a2e8f23eea7869b696da9fc099d401c1b43eff89 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 03:26:37 +0200 Subject: [PATCH 04/12] feat: add persistent delivery controller CLI --- scripts/delivery-controller.mjs | 184 ++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 scripts/delivery-controller.mjs diff --git a/scripts/delivery-controller.mjs b/scripts/delivery-controller.mjs new file mode 100644 index 0000000..df8006b --- /dev/null +++ b/scripts/delivery-controller.mjs @@ -0,0 +1,184 @@ +#!/usr/bin/env node +import { resolve } from "node:path"; + +import { + createDeliveryWorkflowController, + readDeliveryWorkflowCheckpoint, + writeDeliveryWorkflowCheckpoint, +} from "./lib/delivery-workflow-controller.mjs"; +import { resolveDeliveryWorkflowProfile } from "./lib/delivery-workflow-profiles.mjs"; + +const USAGE = `Usage: + node scripts/delivery-controller.mjs start WORKFLOW --repo OWNER/REPO --checkpoint FILE [--issue N] [--pr N] [--base SHA] [--head SHA] + node scripts/delivery-controller.mjs transition CHECKPOINT PHASE + node scripts/delivery-controller.mjs cycle CHECKPOINT [--state-changed] [--blocker-removed] [--required-evidence-produced] [--execution-completed] + 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 blocker-add CHECKPOINT BLOCKER + node scripts/delivery-controller.mjs blocker-remove CHECKPOINT BLOCKER + node scripts/delivery-controller.mjs show CHECKPOINT`; + +function positiveInteger(value, label) { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} requires a positive integer`); + return parsed; +} + +function nonNegativeInteger(value, label) { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${label} requires a non-negative integer`); + return parsed; +} + +function takeOption(argv, name) { + const index = argv.indexOf(name); + if (index < 0) return null; + if (index + 1 >= argv.length) throw new Error(`${name} requires a value`); + const value = argv[index + 1]; + argv.splice(index, 2); + return value; +} + +function takeFlag(argv, name) { + const index = argv.indexOf(name); + if (index < 0) return false; + argv.splice(index, 1); + return true; +} + +function assertEmpty(argv) { + if (argv.length) throw new Error(`Unknown arguments: ${argv.join(" ")}\n${USAGE}`); +} + +function load(path) { + const checkpoint = resolve(path); + const snapshot = readDeliveryWorkflowCheckpoint(checkpoint); + const profile = resolveDeliveryWorkflowProfile(snapshot.workflow); + return { + checkpoint, + controller: createDeliveryWorkflowController({ + snapshot, + graph: profile.graph, + }), + }; +} + +function persist(loaded) { + const snapshot = loaded.controller.snapshot(); + writeDeliveryWorkflowCheckpoint(loaded.checkpoint, snapshot); + return snapshot; +} + +function print(payload) { + process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`); +} + +try { + const argv = process.argv.slice(2); + const command = argv.shift(); + if (!command) throw new Error(USAGE); + + if (command === "start") { + const workflow = argv.shift(); + if (!workflow) throw new Error(USAGE); + const repo = takeOption(argv, "--repo"); + const checkpointValue = takeOption(argv, "--checkpoint"); + const issueRaw = takeOption(argv, "--issue"); + const prRaw = takeOption(argv, "--pr"); + const baseSha = takeOption(argv, "--base"); + const headSha = takeOption(argv, "--head"); + assertEmpty(argv); + if (!repo || !checkpointValue) throw new Error("start requires --repo and --checkpoint"); + const profile = resolveDeliveryWorkflowProfile(workflow); + const checkpoint = resolve(checkpointValue); + const controller = createDeliveryWorkflowController({ + workflow: profile.workflow, + repo, + issue: issueRaw === null ? null : positiveInteger(issueRaw, "--issue"), + pr: prRaw === null ? null : positiveInteger(prRaw, "--pr"), + baseSha, + headSha, + graph: profile.graph, + startPhase: profile.startPhase, + }); + const snapshot = controller.snapshot(); + writeDeliveryWorkflowCheckpoint(checkpoint, snapshot); + print(snapshot); + } else if (command === "show") { + const checkpoint = argv.shift(); + if (!checkpoint) throw new Error(USAGE); + assertEmpty(argv); + print(readDeliveryWorkflowCheckpoint(resolve(checkpoint))); + } else { + const checkpointValue = argv.shift(); + if (!checkpointValue) throw new Error(USAGE); + const loaded = load(checkpointValue); + let result; + + if (command === "transition") { + const phase = argv.shift(); + if (!phase) throw new Error(USAGE); + assertEmpty(argv); + result = loaded.controller.transition(phase); + } else if (command === "cycle") { + const signal = { + stateChanged: takeFlag(argv, "--state-changed"), + blockerRemoved: takeFlag(argv, "--blocker-removed"), + requiredEvidenceProduced: takeFlag(argv, "--required-evidence-produced"), + executionCompleted: takeFlag(argv, "--execution-completed"), + }; + assertEmpty(argv); + result = loaded.controller.observeCycle(signal); + } else if (command === "retry") { + assertEmpty(argv); + result = loaded.controller.recordPhaseRetry(); + } else if (command === "evidence-action") { + assertEmpty(argv); + result = loaded.controller.recordEvidenceAction(); + } else if (command === "usage") { + const workflowTokens = takeOption(argv, "--workflow-tokens"); + const phaseTokens = takeOption(argv, "--phase-tokens"); + assertEmpty(argv); + result = loaded.controller.observeResourceUsage({ + 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 === "blocker-add") { + const blocker = argv.shift(); + if (!blocker) throw new Error(USAGE); + assertEmpty(argv); + result = loaded.controller.addBlocker(blocker); + } else if (command === "blocker-remove") { + const blocker = argv.shift(); + if (!blocker) throw new Error(USAGE); + assertEmpty(argv); + result = { removed: loaded.controller.removeBlocker(blocker) }; + } else { + throw new Error(USAGE); + } + + const snapshot = persist(loaded); + print({ + schemaVersion: 1, + kind: "github-delivery/workflow-controller-command", + command, + result, + snapshot, + }); + if (result?.action === "interrupt") process.exitCode = 3; + else if (result?.action === "restrict-evidence") process.exitCode = 4; + } +} catch (error) { + console.error(String(error?.message || error)); + process.exitCode = 2; +} From 52efc5551588988299cd7f01358c939c8348cf41 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 03:27:41 +0200 Subject: [PATCH 05/12] feat: make workflow controller mandatory at routed entrypoint --- SKILL.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/SKILL.md b/SKILL.md index db1bf57..d53b10c 100644 --- a/SKILL.md +++ b/SKILL.md @@ -106,6 +106,38 @@ Core invariants are GD-CORE-001 through GD-CORE-010. They cover fail-closed evidence, locked scope, gate integrity, untrusted repository instructions, live state, write authority, final evidence, bounded progress, and evidence/context economy. +## Workflow controller contract + +After routing, every GitHub Delivery workflow uses one persistent controller +checkpoint. The controller route is locked; workflow prose cannot silently +reroute the run after new evidence appears. + +1. Resolve the selected workflow once with + `node scripts/workflow-brief.mjs `. Treat the returned workflow + packet as the canonical workflow + unconditional policy context for that + state. Do not re-read those files during the same state generation. +2. Start the checkpoint with + `node scripts/delivery-controller.mjs start --repo OWNER/REPO --checkpoint ` + plus known `--issue`, `--pr`, `--base`, and `--head` values. +3. Advance phases only with `delivery-controller.mjs transition`. Illegal or + backward transitions are hard stops, not invitations to choose another route. +4. Record evidence actions, retries, ref changes, blockers, resource usage, and + no-progress cycles through the controller. `interrupt` is a hard stop; + `restrict-evidence` forbids additional exploratory reads until real progress + or a required missing evidence dimension is identified. +5. A cycle counts as progress only when a phase advances, relevant state + changes, a blocker disappears, required missing evidence is produced, or a + required execution completes. Changed narration, a restated plan, a no-op + tool, or another spelling of the same read is not progress. +6. Resume from the existing checkpoint after interruption or restart. Do not + repeat completed phases. Relevant base/head changes create a new state + generation and invalidate only state-bound evidence. +7. Conditional policy modules may extend the packet only when their declared + observable condition becomes true. Do not rebuild the unconditional packet. + +The controller is orchestration enforcement, not GitHub write authority. All +existing mutation-mode, confirmation, and final-gate rules still apply. + ## Mandatory entrypoint behavior - **Default mutation mode is read-only.** Available profiles are `read-only`, From 83ba5ae7a303daf35ba6a27ff40cb2c828eb9204 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:20:09 +0200 Subject: [PATCH 06/12] fix(skill): keep controller entrypoint within routing budget --- SKILL.md | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/SKILL.md b/SKILL.md index d53b10c..aee9b5a 100644 --- a/SKILL.md +++ b/SKILL.md @@ -108,35 +108,16 @@ state, write authority, final evidence, bounded progress, and evidence/context e ## Workflow controller contract -After routing, every GitHub Delivery workflow uses one persistent controller -checkpoint. The controller route is locked; workflow prose cannot silently -reroute the run after new evidence appears. - -1. Resolve the selected workflow once with - `node scripts/workflow-brief.mjs `. Treat the returned workflow - packet as the canonical workflow + unconditional policy context for that - state. Do not re-read those files during the same state generation. -2. Start the checkpoint with - `node scripts/delivery-controller.mjs start --repo OWNER/REPO --checkpoint ` - plus known `--issue`, `--pr`, `--base`, and `--head` values. -3. Advance phases only with `delivery-controller.mjs transition`. Illegal or - backward transitions are hard stops, not invitations to choose another route. -4. Record evidence actions, retries, ref changes, blockers, resource usage, and - no-progress cycles through the controller. `interrupt` is a hard stop; - `restrict-evidence` forbids additional exploratory reads until real progress - or a required missing evidence dimension is identified. -5. A cycle counts as progress only when a phase advances, relevant state - changes, a blocker disappears, required missing evidence is produced, or a - required execution completes. Changed narration, a restated plan, a no-op - tool, or another spelling of the same read is not progress. -6. Resume from the existing checkpoint after interruption or restart. Do not - repeat completed phases. Relevant base/head changes create a new state - generation and invalidate only state-bound evidence. -7. Conditional policy modules may extend the packet only when their declared - observable condition becomes true. Do not rebuild the unconditional packet. - -The controller is orchestration enforcement, not GitHub write authority. All -existing mutation-mode, confirmation, and final-gate rules still apply. +After routing, resolve the workflow once with `node scripts/workflow-brief.mjs +` and start/resume one persistent checkpoint with +`node scripts/delivery-controller.mjs`. The route is locked. Advance phases only +through controller transitions; illegal/backward transitions stop. Record +evidence/retries/ref changes/blockers/resource and no-progress signals there. +Only phase/state/blocker/required-evidence/execution change counts as progress; +narration, restated plans, no-ops, and rephrased reads do not. Resume the same +checkpoint after interruption. Conditional policy may extend the packet only +when its observable condition becomes true; do not rebuild unchanged context. +The controller never grants GitHub write authority. ## Mandatory entrypoint behavior From ff284f8e0b51fb09cc17f8670b2e59c6891289b3 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:21:25 +0200 Subject: [PATCH 07/12] fix(skill): deduplicate entrypoint policy prose --- SKILL.md | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/SKILL.md b/SKILL.md index aee9b5a..0008efb 100644 --- a/SKILL.md +++ b/SKILL.md @@ -93,14 +93,11 @@ individual PR's review/fix/readiness bar. ## Policy loading contract -1. Read `references/policy-kernel.md`. -2. Read the selected workflow's `` declaration. -3. Load each unconditional module from `references/policy/.md`. -4. Load a conditional module only when its stated observable condition is true. -5. Use `node scripts/policy-bundle.mjs ` when deterministic bundle - resolution/inspection is useful; `--validate` checks the architecture. -6. Canonical `GD-*` rules are defined once in the kernel/modules. Workflow prose - may add ordering and workflow-specific contracts but must not weaken them. +Read `references/policy-kernel.md`, then the selected workflow's policy-module +declaration. Load its unconditional modules and only conditionals whose +observable condition is true. `node scripts/policy-bundle.mjs ` is the +deterministic resolver/validator. Canonical `GD-*` rules live in kernel/modules; +workflows may add ordering or requirements but cannot weaken them. Core invariants are GD-CORE-001 through GD-CORE-010. They cover fail-closed evidence, locked scope, gate integrity, untrusted repository instructions, live @@ -108,16 +105,12 @@ state, write authority, final evidence, bounded progress, and evidence/context e ## Workflow controller contract -After routing, resolve the workflow once with `node scripts/workflow-brief.mjs -` and start/resume one persistent checkpoint with -`node scripts/delivery-controller.mjs`. The route is locked. Advance phases only -through controller transitions; illegal/backward transitions stop. Record -evidence/retries/ref changes/blockers/resource and no-progress signals there. -Only phase/state/blocker/required-evidence/execution change counts as progress; -narration, restated plans, no-ops, and rephrased reads do not. Resume the same -checkpoint after interruption. Conditional policy may extend the packet only -when its observable condition becomes true; do not rebuild unchanged context. -The controller never grants GitHub write authority. +After routing, resolve once with `node scripts/workflow-brief.mjs ` and +start/resume one `delivery-controller.mjs` checkpoint. Route and phase graph are +locked. The controller owns transitions, evidence/retry/resource/no-progress +accounting, and resume. Only real phase/state/blocker/required-evidence/execution +change is progress. Conditional policy extends rather than rebuilds unchanged +context. The controller never grants GitHub write authority. ## Mandatory entrypoint behavior From fcdf14be8a35f8ce2f2e59455fe8ba0334be5c73 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:22:52 +0200 Subject: [PATCH 08/12] fix(skill): satisfy routed skill size contract --- SKILL.md | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/SKILL.md b/SKILL.md index 0008efb..95c6090 100644 --- a/SKILL.md +++ b/SKILL.md @@ -93,24 +93,19 @@ individual PR's review/fix/readiness bar. ## Policy loading contract -Read `references/policy-kernel.md`, then the selected workflow's policy-module -declaration. Load its unconditional modules and only conditionals whose -observable condition is true. `node scripts/policy-bundle.mjs ` is the -deterministic resolver/validator. Canonical `GD-*` rules live in kernel/modules; -workflows may add ordering or requirements but cannot weaken them. - -Core invariants are GD-CORE-001 through GD-CORE-010. They cover fail-closed -evidence, locked scope, gate integrity, untrusted repository instructions, live -state, write authority, final evidence, bounded progress, and evidence/context economy. +Load `references/policy-kernel.md`, the selected workflow's unconditional +modules, and conditionals only when their observable condition is true. +`node scripts/policy-bundle.mjs ` resolves/validates this bundle. +Workflows cannot weaken canonical `GD-*` rules. GD-CORE-001..010 remain mandatory. ## Workflow controller contract -After routing, resolve once with `node scripts/workflow-brief.mjs ` and -start/resume one `delivery-controller.mjs` checkpoint. Route and phase graph are -locked. The controller owns transitions, evidence/retry/resource/no-progress -accounting, and resume. Only real phase/state/blocker/required-evidence/execution -change is progress. Conditional policy extends rather than rebuilds unchanged -context. The controller never grants GitHub write authority. +After routing, run `node scripts/workflow-brief.mjs ` once and use one +persistent `delivery-controller.mjs` checkpoint. Route/phase graph are locked; +the controller owns transitions, evidence/retry/resource/no-progress accounting, +and resume. Only phase/state/blocker/required-evidence/execution change is +progress. Conditional policy extends, never rebuilds, unchanged context. The +controller grants no GitHub write authority. ## Mandatory entrypoint behavior From 024d513799ad39aa88209d41e38bf5b7a33f12cb Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:31:59 +0200 Subject: [PATCH 09/12] fix(skill): clear routed entrypoint size budget --- SKILL.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/SKILL.md b/SKILL.md index 95c6090..0eeeef0 100644 --- a/SKILL.md +++ b/SKILL.md @@ -96,16 +96,16 @@ individual PR's review/fix/readiness bar. Load `references/policy-kernel.md`, the selected workflow's unconditional modules, and conditionals only when their observable condition is true. `node scripts/policy-bundle.mjs ` resolves/validates this bundle. -Workflows cannot weaken canonical `GD-*` rules. GD-CORE-001..010 remain mandatory. +Workflows cannot weaken `GD-*` rules. GD-CORE-001..010 remain mandatory. ## Workflow controller contract After routing, run `node scripts/workflow-brief.mjs ` once and use one -persistent `delivery-controller.mjs` checkpoint. Route/phase graph are locked; -the controller owns transitions, evidence/retry/resource/no-progress accounting, +persistent `delivery-controller.mjs` checkpoint. Route/phase graph stay locked; +the controller owns transitions, evidence/retry/resource/no-progress accounting and resume. Only phase/state/blocker/required-evidence/execution change is -progress. Conditional policy extends, never rebuilds, unchanged context. The -controller grants no GitHub write authority. +progress. Conditional policy extends unchanged context. The controller grants +no GitHub write authority. ## Mandatory entrypoint behavior @@ -165,7 +165,6 @@ expected absences and rejected values. One representative member is insufficient ## Safety precedence -Policy kernel/modules and executable gates are stricter than workflow prose. A -workflow may add requirements but cannot waive a canonical rule. If two runtime -instructions genuinely conflict and the stricter safe behavior is not clear, -fail closed and surface the contradiction rather than inventing authority. +Kernel/modules and executable gates override workflow prose; workflows cannot +waive canonical rules. If runtime instructions genuinely conflict and the +stricter safe behavior is unclear, fail closed and surface the contradiction. From af50ba8324c28e2a8f7bbf17758785e99384bbf8 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:50:01 +0200 Subject: [PATCH 10/12] Fix context economy entrypoint contract --- SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SKILL.md b/SKILL.md index 0eeeef0..41a07fb 100644 --- a/SKILL.md +++ b/SKILL.md @@ -15,7 +15,7 @@ description: > # GitHub Delivery Own GitHub work from product intake through merged PR. Natural language is the -public API; scripts and policy modules are internal evidence/safety machinery. +public API; scripts and policy modules are internal evidence/context economy machinery. ## Route From 0a48a754a6d77bd2dc81fa6aa69991da397740f4 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:53:16 +0200 Subject: [PATCH 11/12] Fix core policy range entrypoint contract --- SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SKILL.md b/SKILL.md index 41a07fb..2868ef2 100644 --- a/SKILL.md +++ b/SKILL.md @@ -15,7 +15,7 @@ description: > # GitHub Delivery Own GitHub work from product intake through merged PR. Natural language is the -public API; scripts and policy modules are internal evidence/context economy machinery. +public API; internals enforce evidence/context economy. ## Route @@ -96,7 +96,7 @@ individual PR's review/fix/readiness bar. Load `references/policy-kernel.md`, the selected workflow's unconditional modules, and conditionals only when their observable condition is true. `node scripts/policy-bundle.mjs ` resolves/validates this bundle. -Workflows cannot weaken `GD-*` rules. GD-CORE-001..010 remain mandatory. +Workflows cannot weaken `GD-*` rules. GD-CORE-001 through GD-CORE-010 remain mandatory. ## Workflow controller contract From e4d40e0d416a7760b4560001ecd8ab891cfd51d6 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:54:46 +0200 Subject: [PATCH 12/12] Restore bounded progress entrypoint contract --- SKILL.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SKILL.md b/SKILL.md index 2868ef2..0f6f4c2 100644 --- a/SKILL.md +++ b/SKILL.md @@ -15,15 +15,14 @@ description: > # GitHub Delivery Own GitHub work from product intake through merged PR. Natural language is the -public API; internals enforce evidence/context economy. +public API; internals enforce bounded progress and evidence/context economy. ## Route Match the request, then load **only** the selected workflow plus the policy modules declared at the top of that workflow. Do **not** load `references/shared-rules.md` as mandatory context; it is now a compatibility -index. Every routed workflow includes `policy-kernel` plus only the domains it -needs. +index. Each route includes `policy-kernel` plus only needed domains. **Full-review routing is explicit:** when the user asks for a full review, route to `references/full-review-pr.md`; bot-fix, CodeRabbit, Codex, security, or