From 1ff722a5fe12bbc0a0efd4402ce22929ae0a6203 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:09:28 +0200 Subject: [PATCH 01/49] test: lock deep audit regressions --- tests/unit/deep-audit-hardening.test.mjs | 235 +++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 tests/unit/deep-audit-hardening.test.mjs diff --git a/tests/unit/deep-audit-hardening.test.mjs b/tests/unit/deep-audit-hardening.test.mjs new file mode 100644 index 00000000..e3d9d804 --- /dev/null +++ b/tests/unit/deep-audit-hardening.test.mjs @@ -0,0 +1,235 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { classifyCiScope } from "../../scripts/ci-scope.mjs"; +import { cleanupOrphanedWorkflowRuns } from "../../scripts/cleanup-orphaned-workflows.mjs"; +import { makeRedemptionRunner } from "../../scripts/lib/authority-execution.mjs"; +import { + collectBranchReviewInput, + planReviewScope, +} from "../../scripts/lib/review-scope.mjs"; +import { evaluate as evaluatePreOpen } from "../../scripts/pre-open-gate.mjs"; +import { briefText } from "../../scripts/review-brief.mjs"; + +function source(path) { + return readFileSync(new URL(`../../${path}`, import.meta.url), "utf8"); +} + +function git(cwd, args) { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }).trim(); +} + +test("CI scope policy changes force the Windows and C# security lanes", () => { + const scope = classifyCiScope(["scripts/ci-scope.mjs"]); + assert.equal(scope.nodeCompat, true); + assert.equal(scope.windowsAuthority, true); + assert.equal(scope.csharp, true); +}); + +test("PR scope detection executes the trusted base selector, not candidate code", () => { + const ci = source(".github/workflows/ci.yml"); + const codeql = source(".github/workflows/codeql.yml"); + const trustedSelector = /git show \"\$\{BASE_SHA\}:scripts\/ci-scope\.mjs\"/; + + assert.match(ci, trustedSelector); + assert.match(codeql, trustedSelector); + assert.doesNotMatch( + ci, + /git diff --name-only -z \"\$\{BASE_SHA\}\"\.\.\.HEAD \|\s*\n\s*node scripts\/ci-scope\.mjs/, + ); + assert.doesNotMatch( + codeql, + /git diff --name-only -z \"\$\{BASE_SHA\}\"\.\.\.HEAD \|\s*\n\s*node scripts\/ci-scope\.mjs/, + ); +}); + +test("trusted authority can be redeemed before internal coordination writes", () => { + const execution = makeRedemptionRunner({ + plannedCommand: ["gh", "pr", "comment", "42"], + authority: null, + authorityGrant: null, + redeemer: null, + runner: () => ({ status: 0, stdout: "", stderr: "" }), + }); + + assert.equal(typeof execution.redeem, "function"); + + const broker = source("scripts/lib/github-mutation-broker.mjs"); + const hookIndex = broker.indexOf("beforeExternalMutation?.()"); + const claimIndex = broker.indexOf("acquireAutonomousIdempotencyClaim({", hookIndex); + assert.ok(hookIndex >= 0, "broker must expose a pre-write authority hook"); + assert.ok(claimIndex > hookIndex, "authority hook must run before the autonomous claim write"); +}); + +test("branch review input preserves rename source and destination paths", () => { + const root = mkdtempSync(join(tmpdir(), "github-delivery-rename-")); + const previousCwd = process.cwd(); + try { + git(root, ["init", "-b", "main"]); + git(root, ["config", "user.email", "audit@example.invalid"]); + git(root, ["config", "user.name", "Audit Fixture"]); + mkdirSync(join(root, "src"), { recursive: true }); + writeFileSync(join(root, "src", "auth.mjs"), "export const auth = true;\n", "utf8"); + git(root, ["add", "src/auth.mjs"]); + git(root, ["commit", "-m", "base"]); + const base = git(root, ["rev-parse", "HEAD"]); + + git(root, ["mv", "src/auth.mjs", "src/auth"]); + git(root, ["commit", "-m", "rename auth module"]); + const head = git(root, ["rev-parse", "HEAD"]); + + process.chdir(root); + const input = collectBranchReviewInput(base, head); + assert.equal(input.files.length, 1); + assert.equal(input.files[0].status.startsWith("R"), true); + assert.equal(input.files[0].previousPath, "src/auth.mjs"); + assert.equal(input.files[0].path, "src/auth"); + } finally { + process.chdir(previousCwd); + rmSync(root, { recursive: true, force: true }); + } +}); + +test("pre-open gate blocks until every deterministic required probe has evidence", () => { + const plan = planReviewScope({ + repo: "acme/widget", + pr: null, + headRefOid: "abc", + files: [ + { + path: "tests/worker.test.mjs", + patch: "+setTimeout(() => {}, 100);\n", + additions: 1, + deletions: 0, + status: "modified", + }, + ], + }); + assert.ok(plan.requiredProbes.includes("test-honesty")); + + const first = evaluatePreOpen(plan); + const evidence = { + schemaVersion: 1, + lenses: Object.fromEntries(first.bugScope.requiredLenses.map((id) => [id, "done"])), + surfaces: Object.fromEntries( + first.securityScope.requiredSurfaces.map((id) => [id, "n/a audit fixture boundary untouched"]), + ), + probes: {}, + }; + const result = evaluatePreOpen(plan, evidence); + + assert.equal(result.decision, "blocked"); + assert.ok(result.blockers.includes("probe:requiredProbes:test-honesty")); +}); + +test("merge driver enforces stack-parent eligibility as an executable precondition", () => { + const mergeDriver = source("scripts/merge-pr-driver.mjs"); + assert.match(mergeDriver, /verifyMergeStackEligibility/); + assert.match(mergeDriver, /stack_parent_unlanded/); +}); + +test("orphan cleanup aborts if the default branch generation moves before deletion", async () => { + const calls = []; + let refReads = 0; + const fetchImpl = async (input, init = {}) => { + const url = new URL(typeof input === "string" ? input : input.url); + const method = init.method ?? "GET"; + const path = `${url.pathname}${url.search}`; + calls.push(`${method} ${path}`); + + if (method === "GET" && path === "/repos/Wibias/github-delivery") { + return Response.json({ default_branch: "main" }); + } + if (method === "GET" && path === "/repos/Wibias/github-delivery/git/ref/heads/main") { + refReads += 1; + return Response.json({ object: { sha: refReads === 1 ? "a".repeat(40) : "b".repeat(40) } }); + } + if ( + method === "GET" && + path === "/repos/Wibias/github-delivery/contents/.github/workflows?ref=main" + ) { + return Response.json([ + { type: "file", path: ".github/workflows/ci.yml" }, + { type: "file", path: ".github/workflows/cleanup-orphaned-workflows.yml" }, + ]); + } + if ( + method === "GET" && + path === "/repos/Wibias/github-delivery/actions/workflows?per_page=100&page=1" + ) { + return Response.json({ + workflows: [ + { id: 2, name: "Old helper", path: ".github/workflows/tmp.yml" }, + ], + }); + } + if ( + method === "GET" && + path === "/repos/Wibias/github-delivery/actions/workflows/2/runs?per_page=100&page=1" + ) { + return Response.json({ workflow_runs: [{ id: 200, status: "completed" }] }); + } + if (method === "DELETE") { + return new Response("delete should not be attempted after generation drift", { status: 500 }); + } + return new Response(`Unexpected request: ${method} ${path}`, { status: 500 }); + }; + + await assert.rejects( + cleanupOrphanedWorkflowRuns({ + token: "test-token", + repository: "Wibias/github-delivery", + fetchImpl, + log: () => {}, + }), + /default_branch_moved_during_cleanup/, + ); + assert.equal(calls.some((call) => call.startsWith("DELETE ")), false); + assert.equal(refReads, 2); +}); + +test("review brief applies a global hunk-line budget across huge diffs", () => { + const files = Array.from({ length: 20 }, (_, index) => ({ + path: `src/file-${index}.mjs`, + additions: 30, + deletions: 0, + patch: Array.from({ length: 30 }, (__, line) => `+line ${index}-${line}`).join("\n"), + })); + const text = briefText({ + meta: { repo: "acme/widget", pr: 1 }, + plan: { + headRefOid: "abc", + fileCount: files.length, + logicFiles: files.map((file) => file.path), + requiredProbes: [], + dependencyChanges: [], + removedControlLeads: [], + uncertainty: [], + }, + files, + bugScope: { requiredLenses: [] }, + securityScope: { requiredSurfaces: [] }, + executionPlan: null, + maxHunkLines: 24, + maxTotalHunkLines: 50, + probeBlocks: [], + }); + + const emittedDiffLines = text.split(/\r?\n/).filter((line) => line.startsWith("+line ")); + assert.ok(emittedDiffLines.length <= 50, `expected <= 50 hunk lines, got ${emittedDiffLines.length}`); + assert.match(text, /global hunk budget/i); +}); From 509374020414674c180d746539c85c815b85dd53 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:12:33 +0200 Subject: [PATCH 02/49] fix: trust CI scope policy source --- scripts/ci-scope.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/ci-scope.mjs b/scripts/ci-scope.mjs index 7215c7bf..c73fe73e 100644 --- a/scripts/ci-scope.mjs +++ b/scripts/ci-scope.mjs @@ -16,6 +16,7 @@ const WINDOWS_AUTHORITY_PATTERNS = [ /^\.github-delivery-fixtures\//, /^global\.json$/, /^package\.json$/, + /^scripts\/ci-scope\.mjs$/, /^scripts\/prepare-authority-host-runtime-smoke\.mjs$/, /^scripts\/lib\/authority-host-(install|release)\.mjs$/, /^\.github\/workflows\/ci\.yml$/, @@ -24,6 +25,7 @@ const WINDOWS_AUTHORITY_PATTERNS = [ const CSHARP_PATTERNS = [ /^authority-host\/windows\//, /^global\.json$/, + /^scripts\/ci-scope\.mjs$/, /^\.github\/workflows\/codeql\.yml$/, ]; From 5f53abbefe35b299fb8475ebd87c01dfe7a7f396 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:13:07 +0200 Subject: [PATCH 03/49] fix: execute scoped CI policy from base --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 772a2b8f..a9d838bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,10 @@ jobs: exit 0 fi + TRUSTED_SCOPE="${RUNNER_TEMP}/github-delivery-ci-scope-base.mjs" + git show "${BASE_SHA}:scripts/ci-scope.mjs" > "${TRUSTED_SCOPE}" git diff --name-only -z "${BASE_SHA}"...HEAD | - node scripts/ci-scope.mjs --mode ci >> "${GITHUB_OUTPUT}" + node "${TRUSTED_SCOPE}" --mode ci >> "${GITHUB_OUTPUT}" core: name: Node 24 / ubuntu-latest From 51f19616d91402988c5cbebe2cb83f46878f9441 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:15:13 +0200 Subject: [PATCH 04/49] test: assert authority before coordination write --- tests/unit/deep-audit-hardening.test.mjs | 44 ++++++++++++++++++------ 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/tests/unit/deep-audit-hardening.test.mjs b/tests/unit/deep-audit-hardening.test.mjs index e3d9d804..f4f0473a 100644 --- a/tests/unit/deep-audit-hardening.test.mjs +++ b/tests/unit/deep-audit-hardening.test.mjs @@ -57,22 +57,44 @@ test("PR scope detection executes the trusted base selector, not candidate code" ); }); -test("trusted authority can be redeemed before internal coordination writes", () => { +test("trusted authority is redeemed before an internal coordination write executes", () => { + const events = []; + const nonce = "audit-nonce"; + const authority = { + verified: true, + claims: { + redemption: "required", + scopeSha256: "a".repeat(64), + nonce, + }, + }; const execution = makeRedemptionRunner({ plannedCommand: ["gh", "pr", "comment", "42"], - authority: null, - authorityGrant: null, - redeemer: null, - runner: () => ({ status: 0, stdout: "", stderr: "" }), + authority, + authorityGrant: "gd1.audit-fixture", + redeemer() { + events.push("redeem"); + return { status: "consumed", nonce, consumedAt: 1 }; + }, + runner() { + events.push("write"); + return { status: 0, stdout: "", stderr: "" }; + }, }); - assert.equal(typeof execution.redeem, "function"); + execution.runner("gh", [ + "api", + "repos/acme/widget/git/refs", + "--method", + "POST", + "-f", + "ref=refs/github-delivery/idempotency/test", + "-f", + `sha=${"b".repeat(40)}`, + ], {}); - const broker = source("scripts/lib/github-mutation-broker.mjs"); - const hookIndex = broker.indexOf("beforeExternalMutation?.()"); - const claimIndex = broker.indexOf("acquireAutonomousIdempotencyClaim({", hookIndex); - assert.ok(hookIndex >= 0, "broker must expose a pre-write authority hook"); - assert.ok(claimIndex > hookIndex, "authority hook must run before the autonomous claim write"); + assert.deepEqual(events, ["redeem", "write"]); + assert.equal(execution.redemption()?.status, "consumed"); }); test("branch review input preserves rename source and destination paths", () => { From bcafb91d343cf5d486a42d1b6a4b1ae6c692a4b9 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:15:31 +0200 Subject: [PATCH 05/49] fix: execute C# scope policy from base --- .github/workflows/codeql.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d6f6def1..9752f5b2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,8 +48,10 @@ jobs: exit 0 fi + TRUSTED_SCOPE="${RUNNER_TEMP}/github-delivery-ci-scope-base.mjs" + git show "${BASE_SHA}:scripts/ci-scope.mjs" > "${TRUSTED_SCOPE}" git diff --name-only -z "${BASE_SHA}"...HEAD | - node scripts/ci-scope.mjs --mode csharp >> "${GITHUB_OUTPUT}" + node "${TRUSTED_SCOPE}" --mode csharp >> "${GITHUB_OUTPUT}" analyze: name: CodeQL / Analyze (javascript-typescript) From 59ad3f8e433b16c1fd63fcbcc2d23207057e4110 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:15:47 +0200 Subject: [PATCH 06/49] fix: redeem before coordination mutations --- scripts/lib/authority-execution.mjs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/scripts/lib/authority-execution.mjs b/scripts/lib/authority-execution.mjs index 7c652393..159d55b3 100644 --- a/scripts/lib/authority-execution.mjs +++ b/scripts/lib/authority-execution.mjs @@ -1,4 +1,5 @@ import { redeemAuthorityBeforeMutation } from "./authority-redemption.mjs"; +import { isReadOnlyGitHubCommand } from "./github-retry.mjs"; function sameCommand(command, args, plannedCommand) { if (!Array.isArray(plannedCommand) || plannedCommand.length === 0) return false; @@ -7,6 +8,11 @@ function sameCommand(command, args, plannedCommand) { return args.length === plannedArgs.length && args.every((value, index) => value === plannedArgs[index]); } +function isPreWriteCoordinationMutation(command, args, plannedCommand) { + if (sameCommand(command, args, plannedCommand)) return true; + return command === "gh" && !isReadOnlyGitHubCommand(command, args); +} + export function makeRedemptionRunner({ plannedCommand, authority, @@ -23,20 +29,26 @@ export function makeRedemptionRunner({ let redemptionAttempted = false; let writeAttempted = false; + function redeem() { + if (!redemptionAttempted) { + redemptionAttempted = true; + redemptionReceipt = redeemAuthorityBeforeMutation({ + authority, + authorityGrant, + redeemer, + }); + } + return redemptionReceipt ? structuredClone(redemptionReceipt) : null; + } + return { runner(command, args, options) { const isPlannedWrite = sameCommand(command, args, plannedCommand); - if (isPlannedWrite && !redemptionAttempted) { - redemptionAttempted = true; - redemptionReceipt = redeemAuthorityBeforeMutation({ - authority, - authorityGrant, - redeemer, - }); - } + if (isPreWriteCoordinationMutation(command, args, plannedCommand)) redeem(); if (isPlannedWrite) writeAttempted = true; return runner(command, args, options); }, + redeem, redemption() { return redemptionReceipt ? structuredClone(redemptionReceipt) : null; }, From dcbab673996976700d01ab8f1149559ad0a552ce Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:16:06 +0200 Subject: [PATCH 07/49] fix: record required probe evidence --- scripts/lib/pre-open-evidence.mjs | 66 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/scripts/lib/pre-open-evidence.mjs b/scripts/lib/pre-open-evidence.mjs index 88abf0c0..5e3d270e 100644 --- a/scripts/lib/pre-open-evidence.mjs +++ b/scripts/lib/pre-open-evidence.mjs @@ -2,18 +2,15 @@ * Validate review-completion evidence for the pre-open gate. * * The gate derives required scope from the diff shape. Evidence is the - * machine-checkable record that each required lens/surface was actually - * reviewed: `done` means the pass ran and found nothing needing a fix (or the - * findings were fixed), `n/a (why)` means the boundary is untouched and the - * reason is recorded. This module validates the evidence payload so the gate - * can clear a `blocked` scope only for lenses/surfaces that carry valid - * evidence, and never clear on malformed or self-asserted input. + * machine-checkable record that each required lens, surface, and probe was + * actually reviewed: `done` means the pass ran and found nothing needing a fix + * (or the findings were fixed), `n/a (why)` means the boundary is untouched and + * the reason is recorded. This module validates the evidence payload so the gate + * can clear a `blocked` obligation only for entries that carry valid evidence. */ export const PRE_OPEN_EVIDENCE_SCHEMA_VERSION = 1; -const VALID_STATUSES = new Set(["done", "n/a"]); - /** * @param {unknown} value * @returns {value is Record} @@ -22,18 +19,10 @@ function isRecord(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } -/** - * @param {unknown} value - * @returns {value is string} - */ -function isNonEmptyString(value) { - return typeof value === "string" && value.trim().length > 0; -} - /** * @param {unknown} status * @param {string} id - * @returns {string | null} the normalized status when valid, else an error message + * @returns {string} the normalized status when valid, else an error message */ function normalizeStatus(status, id) { if (typeof status !== "string") return `status for ${id} must be a string`; @@ -44,9 +33,22 @@ function normalizeStatus(status, id) { return `invalid status for ${id}: ${JSON.stringify(trimmed)} (expected "done" or "n/a ")`; } +function normalizeEvidenceBlock(block, prefix, errors) { + const normalized = {}; + for (const [id, status] of Object.entries(block)) { + const value = normalizeStatus(status, `${prefix}:${id}`); + if (value === "done" || /^n\/a\s+\S/.test(value)) normalized[id] = value; + else errors.push(value); + } + return normalized; +} + /** * Validate a pre-open evidence payload. * + * `probes` is optional for schema-version compatibility with older evidence. + * Missing probe evidence never clears a newly required probe. + * * @param {unknown} input * @returns {{ ok: true, evidence: PreOpenEvidence } | { ok: false, errors: string[] }} */ @@ -56,25 +58,31 @@ export function validatePreOpenEvidence(input) { if (input.schemaVersion !== undefined && input.schemaVersion !== PRE_OPEN_EVIDENCE_SCHEMA_VERSION) { errors.push(`evidence schemaVersion must be ${PRE_OPEN_EVIDENCE_SCHEMA_VERSION}`); } - const lenses = {}; - const surfaces = {}; + const lensBlock = isRecord(input.lenses) ? input.lenses : {}; const surfaceBlock = isRecord(input.surfaces) ? input.surfaces : {}; + const probeBlock = isRecord(input.probes) ? input.probes : {}; if (!isRecord(input.lenses) || !isRecord(input.surfaces)) { errors.push("evidence must have object fields lenses and surfaces"); } - for (const [id, status] of Object.entries(lensBlock)) { - const normalized = normalizeStatus(status, `lens:${id}`); - if (normalized === "done" || /^n\/a\s+\S/.test(normalized)) lenses[id] = normalized; - else errors.push(normalized); - } - for (const [id, status] of Object.entries(surfaceBlock)) { - const normalized = normalizeStatus(status, `surface:${id}`); - if (normalized === "done" || /^n\/a\s+\S/.test(normalized)) surfaces[id] = normalized; - else errors.push(normalized); + if (input.probes !== undefined && !isRecord(input.probes)) { + errors.push("evidence probes must be an object when provided"); } + + const lenses = normalizeEvidenceBlock(lensBlock, "lens", errors); + const surfaces = normalizeEvidenceBlock(surfaceBlock, "surface", errors); + const probes = normalizeEvidenceBlock(probeBlock, "probe", errors); + if (errors.length) return { ok: false, errors }; - return { ok: true, evidence: { schemaVersion: PRE_OPEN_EVIDENCE_SCHEMA_VERSION, lenses, surfaces } }; + return { + ok: true, + evidence: { + schemaVersion: PRE_OPEN_EVIDENCE_SCHEMA_VERSION, + lenses, + surfaces, + probes, + }, + }; } /** From ffc033322050942d52ee19fe80c96908d343ead3 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:16:32 +0200 Subject: [PATCH 08/49] fix: enforce required review probes --- scripts/pre-open-gate.mjs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/pre-open-gate.mjs b/scripts/pre-open-gate.mjs index 8f6ef895..b7a3efe0 100644 --- a/scripts/pre-open-gate.mjs +++ b/scripts/pre-open-gate.mjs @@ -1,7 +1,8 @@ #!/usr/bin/env node import { isDirectInvocation } from "./lib/direct-invocation.mjs"; -import { collectBranchReviewInput, planReviewScope } from "./lib/review-scope.mjs"; +import { collectBranchReviewInput } from "./lib/branch-review-input.mjs"; +import { planReviewScope } from "./lib/review-scope.mjs"; import { projectBugScope, projectSecurityScope } from "./lib/review-scope-compat.mjs"; import { evidenceClears, validatePreOpenEvidence } from "./lib/pre-open-evidence.mjs"; @@ -16,14 +17,17 @@ export function evaluate(plan, evidence = null) { const scopeBlockers = [ ...bugScope.requiredLenses.map((id) => `bug:requiredLenses:${id}`), ...securityScope.requiredSurfaces.map((id) => `security:requiredSurfaces:${id}`), + ...(plan.requiredProbes || []).map((id) => `probe:requiredProbes:${id}`), ]; const complete = implementationDiffPresent && plan.complete && bugScope.complete && securityScope.complete; const lensMap = evidence?.lenses ?? {}; const surfaceMap = evidence?.surfaces ?? {}; + const probeMap = evidence?.probes ?? {}; const clearedByEvidence = []; const remainingScopeBlockers = scopeBlockers.filter((blocker) => { const [axis, , id] = blocker.split(":"); - const cleared = axis === "bug" ? evidenceClears(lensMap, id) : evidenceClears(surfaceMap, id); + const map = axis === "bug" ? lensMap : axis === "security" ? surfaceMap : probeMap; + const cleared = evidenceClears(map, id); if (cleared) clearedByEvidence.push(blocker); return !cleared; }); @@ -40,6 +44,7 @@ export function evaluate(plan, evidence = null) { return { bugScope, securityScope, + requiredProbes: plan.requiredProbes || [], blockers, clearedByEvidence, decision, @@ -49,7 +54,7 @@ export function evaluate(plan, evidence = null) { }; } -function report({ repo, baseRef, headRef, headRefOid, bugScope, securityScope, blockers, clearedByEvidence, decision, complete, implementationDiffPresent, evidenceApplied }) { +function report({ repo, baseRef, headRef, headRefOid, bugScope, securityScope, requiredProbes, blockers, clearedByEvidence, decision, complete, implementationDiffPresent, evidenceApplied }) { return { schemaVersion: 1, kind: "github-delivery/pre-open-gate", @@ -63,13 +68,14 @@ function report({ repo, baseRef, headRef, headRefOid, bugScope, securityScope, b evidenceApplied, bugScope, securityScope, + requiredProbes, blockers, clearedByEvidence, instructions: [ "workflow:implementation_missing: this pre-open gate requires a non-empty candidate implementation diff; implement first, then rerun the gate before publication.", - "decision=blocked: complete every remaining required bug lens and security surface on this branch diff (with --evidence-file), fix Confirmed High/Critical findings, then rerun before opening the PR.", + "decision=blocked: complete every remaining required bug lens, security surface, and deterministic probe on this branch diff (with --evidence-file), fix Confirmed High/Critical findings, then rerun before opening the PR.", "decision=unknown: restore complete branch evidence (fetch base, checkout head) and rerun; never open a PR from an incomplete diff.", - "decision=ready: the non-empty candidate branch diff has no required bug/security scope, or every required lens/surface carries valid done/n-a evidence; you may proceed to open the PR.", + "decision=ready: the non-empty candidate branch diff has no remaining review obligations, or every required lens/surface/probe carries valid done/n-a evidence; you may proceed to open the PR.", ], }; } From 1e98e714ee85a6d85dc52bd6665ac3bbf02df124 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:16:59 +0200 Subject: [PATCH 09/49] fix: parse branch diffs with NUL records --- scripts/lib/branch-review-input.mjs | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 scripts/lib/branch-review-input.mjs diff --git a/scripts/lib/branch-review-input.mjs b/scripts/lib/branch-review-input.mjs new file mode 100644 index 00000000..01836f1c --- /dev/null +++ b/scripts/lib/branch-review-input.mjs @@ -0,0 +1,107 @@ +import { boundedSpawnSync } from "./subprocess-policy.mjs"; + +function run(command, args, { trim = true } = {}) { + const result = boundedSpawnSync(command, args, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, + }); + if (result.status !== 0) { + throw new Error(String(result.stderr || result.stdout || `${command} failed`).trim()); + } + const output = String(result.stdout || ""); + return trim ? output.trim() : output; +} + +function maybe(command, args) { + const result = boundedSpawnSync(command, args, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, + }); + if (result.status !== 0) return null; + return String(result.stdout || "").trim(); +} + +export function parseNullDelimitedNameStatus(output) { + const fields = String(output || "").split("\0"); + if (fields.at(-1) === "") fields.pop(); + else if (fields.length > 1 || fields[0]) throw new Error("branch_diff_name_status_not_nul_terminated"); + + const rows = []; + for (let index = 0; index < fields.length;) { + const status = fields[index++]; + if (!status) throw new Error("branch_diff_status_missing"); + const kind = status[0]; + if (kind === "R" || kind === "C") { + const previousPath = fields[index++]; + const path = fields[index++]; + if (previousPath === undefined || path === undefined) { + throw new Error("branch_diff_rename_record_incomplete"); + } + rows.push({ status, previousPath, path }); + continue; + } + const path = fields[index++]; + if (path === undefined) throw new Error("branch_diff_path_missing"); + rows.push({ status, previousPath: null, path }); + } + return rows; +} + +function resolveRepoForBranch() { + try { + const name = JSON.parse(run("gh", ["repo", "view", "--json", "nameWithOwner"])).nameWithOwner; + if (typeof name === "string" && name.includes("/")) return name; + } catch { + // Fall through to the configured git remote. + } + const remote = maybe("git", ["remote", "get-url", "origin"]); + if (!remote) return null; + const match = String(remote).match(/(?:[:/])([^/:]+)\/([^/]+?)(?:\.git)?$/); + return match ? `${match[1]}/${match[2]}` : null; +} + +function patchForRecord(baseRef, headRef, record) { + const paths = record.previousPath + ? [record.previousPath, record.path] + : [record.path]; + return run( + "git", + [ + "diff", + "--no-ext-diff", + "--unified=3", + `${baseRef}...${headRef}`, + "--", + ...paths, + ], + { trim: false }, + ); +} + +export function collectBranchReviewInput(baseRef, headRef) { + const nameStatus = run( + "git", + ["diff", "--name-status", "-z", `${baseRef}...${headRef}`], + { trim: false }, + ); + const records = parseNullDelimitedNameStatus(nameStatus); + const files = records.map((record) => { + const patch = patchForRecord(baseRef, headRef, record); + const additions = patch + .split(/\r?\n/) + .filter((line) => line.startsWith("+") && !line.startsWith("+++")).length; + const deletions = patch + .split(/\r?\n/) + .filter((line) => line.startsWith("-") && !line.startsWith("---")).length; + return { + path: record.path, + previousPath: record.previousPath, + status: record.status, + patch, + additions, + deletions, + }; + }); + const headRefOid = maybe("git", ["rev-parse", "--verify", headRef]) || null; + return { repo: resolveRepoForBranch(), pr: null, headRefOid, files }; +} From 68cdf8da0247de66125f5a5110b4195d22488747 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:37 +0200 Subject: [PATCH 10/49] fix: pin orphan cleanup branch generation --- scripts/cleanup-orphaned-workflows.mjs | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/cleanup-orphaned-workflows.mjs b/scripts/cleanup-orphaned-workflows.mjs index 44c851be..99c32e21 100644 --- a/scripts/cleanup-orphaned-workflows.mjs +++ b/scripts/cleanup-orphaned-workflows.mjs @@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url"; const API_ROOT = "https://api.github.com"; const LOCAL_WORKFLOW_PREFIX = ".github/workflows/"; const DEFAULT_MAX_DELETIONS = 500; +const COMMIT_SHA_RE = /^[0-9a-f]{40}$/i; function encodePath(path) { return path.split("/").map(encodeURIComponent).join("/"); @@ -79,6 +80,17 @@ export function createGitHubClient({ token, fetchImpl = fetch, apiRoot = API_ROO return { request, exists, paginate }; } +async function readBranchGeneration({ client, owner, repo, branch }) { + const payload = await client.request( + `/repos/${owner}/${repo}/git/ref/heads/${encodePath(branch)}`, + ); + const sha = String(payload?.object?.sha || "").toLowerCase(); + if (!COMMIT_SHA_RE.test(sha)) { + throw new Error(`default_branch_generation_invalid:${branch}`); + } + return sha; +} + async function workflowStillExistsOnRunHead({ client, owner, repo, workflow, runs, log }) { const heads = new Map(); for (const run of runs) { @@ -133,6 +145,12 @@ export async function cleanupOrphanedWorkflowRuns({ if (!defaultBranch) { throw new Error("Repository response did not include default_branch"); } + const defaultBranchGeneration = await readBranchGeneration({ + client, + owner, + repo, + branch: defaultBranch, + }); const workflowEntries = await client.request( `/repos/${owner}/${repo}/contents/.github/workflows?ref=${encodeURIComponent(defaultBranch)}`, @@ -196,6 +214,20 @@ export async function cleanupOrphanedWorkflowRuns({ `Preflight approved ${cleanupPlans.length} orphan workflow(s) containing ${plannedRuns} run(s).`, ); + // The active-path snapshot is valid only for the exact default-branch generation + // that produced it. Abort before the first DELETE if main moved during preflight. + const currentDefaultBranchGeneration = await readBranchGeneration({ + client, + owner, + repo, + branch: defaultBranch, + }); + if (currentDefaultBranchGeneration !== defaultBranchGeneration) { + throw new Error( + `default_branch_moved_during_cleanup:${defaultBranchGeneration}:${currentDefaultBranchGeneration}`, + ); + } + let deletedRuns = 0; let capped = false; const failures = []; @@ -240,6 +272,7 @@ export async function cleanupOrphanedWorkflowRuns({ plannedRuns, deletedRuns, capped, + defaultBranchGeneration, }; log(`Cleanup complete: ${JSON.stringify(summary)}`); return summary; From bc592430f1c1b43e3e6bab5fd2a076c59b66b906 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:18:07 +0200 Subject: [PATCH 11/49] test: pin cleanup fixtures to one main generation --- tests/unit/cleanup-orphaned-workflows.test.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unit/cleanup-orphaned-workflows.test.mjs b/tests/unit/cleanup-orphaned-workflows.test.mjs index 49df819d..9674e9a2 100644 --- a/tests/unit/cleanup-orphaned-workflows.test.mjs +++ b/tests/unit/cleanup-orphaned-workflows.test.mjs @@ -25,8 +25,13 @@ function mockFetch(routes, calls) { } function baseRoutes(extra) { + const generation = "a".repeat(40); return [ { path: "/repos/Wibias/github-delivery", body: { default_branch: "main" } }, + { + path: "/repos/Wibias/github-delivery/git/ref/heads/main", + body: { object: { sha: generation } }, + }, { path: "/repos/Wibias/github-delivery/contents/.github/workflows?ref=main", body: [ @@ -35,6 +40,10 @@ function baseRoutes(extra) { ], }, ...extra, + { + path: "/repos/Wibias/github-delivery/git/ref/heads/main", + body: { object: { sha: generation } }, + }, ]; } From b299d5d8b16b505922669618b84f8434ffdadceb Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:18:35 +0200 Subject: [PATCH 12/49] perf: bound review brief diff excerpts --- scripts/review-brief.mjs | 97 +++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 17 deletions(-) diff --git a/scripts/review-brief.mjs b/scripts/review-brief.mjs index 8ede95d8..b9dee133 100644 --- a/scripts/review-brief.mjs +++ b/scripts/review-brief.mjs @@ -4,7 +4,7 @@ * so the review agent starts from facts instead of re-reading sources. * * Usage: - * node scripts/review-brief.mjs OWNER/REPO PR_NUMBER [--max-hunk-lines N] [--json] + * node scripts/review-brief.mjs OWNER/REPO PR_NUMBER [--max-hunk-lines N] [--max-total-hunk-lines N] [--json] * * Default output is a compact text brief (fast to read). --json emits the full * structured plan for tooling. @@ -18,19 +18,36 @@ import { planReviewDepthExecution } from "./lib/review-depth-execution.mjs"; import { extractRequiredProbeBlocks } from "./lib/probe-blocks.mjs"; import { ownedHelperEffect } from "./lib/watchdog-evidence-registry.mjs"; +const DEFAULT_MAX_HUNK_LINES = 24; +const DEFAULT_MAX_TOTAL_HUNK_LINES = 1_200; const USAGE = - "Usage: node scripts/review-brief.mjs OWNER/REPO PR_NUMBER [--max-hunk-lines N] [--no-reference-map] [--json]"; + "Usage: node scripts/review-brief.mjs OWNER/REPO PR_NUMBER [--max-hunk-lines N] [--max-total-hunk-lines N] [--no-reference-map] [--json]"; + +function positiveInteger(value, option) { + const number = Number(value); + if (!Number.isInteger(number) || number < 1) { + throw new Error(`${option} requires a positive integer`); + } + return number; +} function parseArgs(argv) { const positional = []; - const options = { maxHunkLines: 24, referenceMap: true, json: false }; + const options = { + maxHunkLines: DEFAULT_MAX_HUNK_LINES, + maxTotalHunkLines: DEFAULT_MAX_TOTAL_HUNK_LINES, + referenceMap: true, + json: false, + }; for (let index = 0; index < argv.length; index += 1) { const value = argv[index]; if (value === "--max-hunk-lines") { - options.maxHunkLines = Number(argv[++index]); - if (!Number.isInteger(options.maxHunkLines) || options.maxHunkLines < 1) { - throw new Error("--max-hunk-lines requires a positive integer"); - } + options.maxHunkLines = positiveInteger(argv[++index], "--max-hunk-lines"); + } else if (value === "--max-total-hunk-lines") { + options.maxTotalHunkLines = positiveInteger( + argv[++index], + "--max-total-hunk-lines", + ); } else if (value === "--no-reference-map") { options.referenceMap = false; } else if (value === "--json") { @@ -50,7 +67,12 @@ export function hunkLines(patch = "", maxLines) { const lines = String(patch).split(/\r?\n/); const shown = lines.slice(0, maxLines); const truncated = lines.length > maxLines; - return { text: shown.join("\n"), truncated, totalLines: lines.length }; + return { + text: shown.join("\n"), + truncated, + totalLines: lines.length, + shownLines: shown.length, + }; } export function normalizeFile(file) { @@ -64,7 +86,17 @@ export function normalizeFile(file) { }; } -export function briefText({ meta, plan, files, bugScope, securityScope, executionPlan = null, maxHunkLines, probeBlocks = [] }) { +export function briefText({ + meta, + plan, + files, + bugScope, + securityScope, + executionPlan = null, + maxHunkLines = DEFAULT_MAX_HUNK_LINES, + maxTotalHunkLines = DEFAULT_MAX_TOTAL_HUNK_LINES, + probeBlocks = [], +}) { const out = []; out.push(`# Review brief: ${meta.repo}#${meta.pr}`); out.push(`Head: ${plan.headRefOid || "unknown"}`); @@ -127,17 +159,38 @@ export function briefText({ meta, plan, files, bugScope, securityScope, executio } out.push("## Changed files (diff hunks)"); + let remainingHunkLines = maxTotalHunkLines; for (const raw of files) { const file = normalizeFile(raw); - const { text, truncated, totalLines } = hunkLines(file.patch, maxHunkLines); out.push(`### ${file.path} (+${file.additions}/-${file.deletions})`); - if (text.trim()) { - out.push("```diff"); - out.push(text); - out.push("```"); - if (truncated) out.push(`_(${totalLines} hunk lines; ${maxHunkLines} shown — open the file only if a lens needs more)_`); - } else { + + if (!file.patch.trim()) { out.push("_(no patch text available)_"); + out.push(""); + continue; + } + + if (remainingHunkLines <= 0) { + out.push( + `_(diff hunk omitted: global hunk budget of ${maxTotalHunkLines} lines exhausted; open this file only if a lens needs more)_`, + ); + out.push(""); + continue; + } + + const perFileLimit = Math.min(maxHunkLines, remainingHunkLines); + const { text, truncated, totalLines, shownLines } = hunkLines(file.patch, perFileLimit); + remainingHunkLines -= shownLines; + out.push("```diff"); + out.push(text); + out.push("```"); + if (truncated) { + const globalLimitReached = remainingHunkLines <= 0; + out.push( + globalLimitReached + ? `_(${totalLines} hunk lines; ${shownLines} shown before the global hunk budget of ${maxTotalHunkLines} lines was exhausted; open this file on demand)_` + : `_(${totalLines} hunk lines; ${shownLines} shown; open the file only if a lens needs more)_`, + ); } out.push(""); } @@ -190,7 +243,17 @@ async function main() { } process.stdout.write( - `${briefText({ meta: { repo: args.repo, pr: args.pr }, plan, files: input.files, bugScope, securityScope, executionPlan, maxHunkLines: args.maxHunkLines, probeBlocks })}\n`, + `${briefText({ + meta: { repo: args.repo, pr: args.pr }, + plan, + files: input.files, + bugScope, + securityScope, + executionPlan, + maxHunkLines: args.maxHunkLines, + maxTotalHunkLines: args.maxTotalHunkLines, + probeBlocks, + })}\n`, ); } From 77456d91f192009dab2d3ca25dd9ec61845a7541 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:19:21 +0200 Subject: [PATCH 13/49] fix: add executable merge stack policy --- scripts/lib/merge-stack-policy.mjs | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 scripts/lib/merge-stack-policy.mjs diff --git a/scripts/lib/merge-stack-policy.mjs b/scripts/lib/merge-stack-policy.mjs new file mode 100644 index 00000000..1c317299 --- /dev/null +++ b/scripts/lib/merge-stack-policy.mjs @@ -0,0 +1,86 @@ +import { buildGraph, normalizePullPages, stackRefKey } from "../inspect-stack.mjs"; + +function required(value, name) { + if (value === undefined || value === null || value === "") { + throw new Error(`${name}_required`); + } + return value; +} + +function positiveInteger(value, name) { + const number = Number(value); + if (!Number.isInteger(number) || number <= 0) throw new Error(`${name}_invalid`); + return number; +} + +function parseOpenPulls(output) { + let payload; + try { + payload = JSON.parse(String(output || "[]")); + } catch { + throw new Error("merge_stack_pr_pages_invalid_json"); + } + return normalizePullPages(payload); +} + +export function evaluateMergeStackEligibility({ prs = [], targetPr } = {}) { + const number = positiveInteger(targetPr, "pr"); + const target = prs.find((pr) => pr.number === number); + if (!target) { + return { + eligible: false, + reason: "stack_target_pr_missing", + pr: number, + parentPr: null, + }; + } + + const { byHead } = buildGraph(prs); + const parent = byHead.get(stackRefKey(target.baseRepoFullName, target.baseRefName)) || null; + if (parent) { + return { + eligible: false, + reason: "stack_parent_unlanded", + pr: number, + parentPr: parent.number, + parentHeadRepo: parent.headRepoFullName, + parentHeadRef: parent.headRefName, + baseRepo: target.baseRepoFullName, + baseRef: target.baseRefName, + }; + } + + return { + eligible: true, + reason: null, + pr: number, + parentPr: null, + baseRepo: target.baseRepoFullName, + baseRef: target.baseRefName, + }; +} + +export function verifyMergeStackEligibility({ request, runner } = {}) { + if (request?.action !== "merge_pr") return null; + if (typeof runner !== "function") throw new Error("merge_stack_runner_required"); + const repo = required(request.repo, "repo"); + const pr = positiveInteger(request.pr, "pr"); + const result = runner( + "gh", + ["api", `repos/${repo}/pulls?state=open&per_page=100`, "--paginate", "--slurp"], + { encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }, + ); + if (result?.status !== 0) { + const detail = String(result?.stderr || result?.stdout || "").trim(); + throw new Error(`merge_stack_evidence_unreadable${detail ? `:${detail}` : ""}`); + } + const decision = evaluateMergeStackEligibility({ + prs: parseOpenPulls(result?.stdout), + targetPr: pr, + }); + if (!decision.eligible) { + const parent = decision.parentPr ? `:parent_pr=${decision.parentPr}` : ""; + throw new Error(`${decision.reason}${parent}`); + } + return decision; +} From 452c3963044b5a55fbe548e4559f73604e223573 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:20:09 +0200 Subject: [PATCH 14/49] fix: preserve merged retry reconciliation --- scripts/lib/merge-stack-policy.mjs | 66 ++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/scripts/lib/merge-stack-policy.mjs b/scripts/lib/merge-stack-policy.mjs index 1c317299..5691390e 100644 --- a/scripts/lib/merge-stack-policy.mjs +++ b/scripts/lib/merge-stack-policy.mjs @@ -13,14 +13,28 @@ function positiveInteger(value, name) { return number; } -function parseOpenPulls(output) { - let payload; +function parseJson(output, code) { try { - payload = JSON.parse(String(output || "[]")); + return JSON.parse(String(output || "null")); } catch { - throw new Error("merge_stack_pr_pages_invalid_json"); + throw new Error(code); + } +} + +function runOrThrow(runner, args, code) { + const result = runner("gh", args, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, + }); + if (result?.status !== 0) { + const detail = String(result?.stderr || result?.stdout || "").trim(); + throw new Error(`${code}${detail ? `:${detail}` : ""}`); } - return normalizePullPages(payload); + return String(result?.stdout || ""); +} + +function parseOpenPulls(output) { + return normalizePullPages(parseJson(output || "[]", "merge_stack_pr_pages_invalid_json")); } export function evaluateMergeStackEligibility({ prs = [], targetPr } = {}) { @@ -65,22 +79,32 @@ export function verifyMergeStackEligibility({ request, runner } = {}) { if (typeof runner !== "function") throw new Error("merge_stack_runner_required"); const repo = required(request.repo, "repo"); const pr = positiveInteger(request.pr, "pr"); - const result = runner( - "gh", - ["api", `repos/${repo}/pulls?state=open&per_page=100`, "--paginate", "--slurp"], - { encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }, + const openPulls = parseOpenPulls( + runOrThrow( + runner, + ["api", `repos/${repo}/pulls?state=open&per_page=100`, "--paginate", "--slurp"], + "merge_stack_evidence_unreadable", + ), ); - if (result?.status !== 0) { - const detail = String(result?.stderr || result?.stdout || "").trim(); - throw new Error(`merge_stack_evidence_unreadable${detail ? `:${detail}` : ""}`); - } - const decision = evaluateMergeStackEligibility({ - prs: parseOpenPulls(result?.stdout), - targetPr: pr, - }); - if (!decision.eligible) { - const parent = decision.parentPr ? `:parent_pr=${decision.parentPr}` : ""; - throw new Error(`${decision.reason}${parent}`); + const decision = evaluateMergeStackEligibility({ prs: openPulls, targetPr: pr }); + if (decision.eligible) return decision; + + if (decision.reason === "stack_target_pr_missing") { + const target = parseJson( + runOrThrow(runner, ["api", `repos/${repo}/pulls/${pr}`], "merge_stack_target_unreadable"), + "merge_stack_target_invalid_json", + ); + if (target?.merged_at || target?.merged === true) { + return { + eligible: true, + reason: null, + pr, + parentPr: null, + alreadyMerged: true, + }; + } } - return decision; + + const parent = decision.parentPr ? `:parent_pr=${decision.parentPr}` : ""; + throw new Error(`${decision.reason}${parent}`); } From adbdc6f6d4fa828af35a28e73bcb45fcbd68f52d Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:20:31 +0200 Subject: [PATCH 15/49] fix: enforce stack order at merge boundary --- scripts/lib/mutation-execution-context.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/lib/mutation-execution-context.mjs b/scripts/lib/mutation-execution-context.mjs index 71179113..0140af76 100644 --- a/scripts/lib/mutation-execution-context.mjs +++ b/scripts/lib/mutation-execution-context.mjs @@ -11,6 +11,7 @@ import { makeAuthorityRedeemer, } from "./authority-host-client.mjs"; import { classifyMergeOutcome, readMergeState } from "./merge-outcome.mjs"; +import { verifyMergeStackEligibility } from "./merge-stack-policy.mjs"; import { actionDefinition } from "./mutation-action-registry.mjs"; import { boundedSpawnSync } from "./subprocess-policy.mjs"; import { readUserConfig, resolveAuthorityMode } from "./user-config.mjs"; @@ -226,6 +227,13 @@ export function executeMutationWithAuthority({ config, }); const planned = planWithAuthorityOptions(request, options); + + // Merge topology is an execution invariant, not only a workflow instruction. + // A child whose base is another open PR head cannot reach destructive authority. + const stackEligibility = execute === true + ? verifyMergeStackEligibility({ request: planned.request, runner }) + : null; + const pipeName = runtimeEnv.GITHUB_DELIVERY_AUTHORITY_PIPE || undefined; const resolvedRedeemer = redeemer === undefined @@ -250,6 +258,7 @@ export function executeMutationWithAuthority({ }); return { ...receipt, + stackEligibility, redemption: execution.redemption(), }; } catch (error) { @@ -262,6 +271,7 @@ export function executeMutationWithAuthority({ if (reconciled) { return { ...reconciled, + stackEligibility, redemption: execution.redemption(), }; } From 98f0ee9cf575ccdf7057e02485c441d77eccc3ac Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:21:37 +0200 Subject: [PATCH 16/49] test: require unconditional security lanes --- tests/unit/actions-usage-contract.test.mjs | 37 +++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/unit/actions-usage-contract.test.mjs b/tests/unit/actions-usage-contract.test.mjs index bd8160f8..4da73c00 100644 --- a/tests/unit/actions-usage-contract.test.mjs +++ b/tests/unit/actions-usage-contract.test.mjs @@ -29,19 +29,20 @@ test("pull-request CI keeps one canonical full check and bounded compatibility l assert.match(ci, /cancel-in-progress: true/); }); -test("compatibility and Windows lanes use NUL-safe scope evidence and fail closed if detection fails", () => { +test("only Node compatibility remains scoped; Windows Authority is unconditional", () => { assert.match(ci, /scope:/); assert.match(ci, /node_compat:/); - assert.match(ci, /windows_authority:/); + assert.match(ci, /git show "\$\{BASE_SHA\}:scripts\/ci-scope\.mjs"/); assert.match(ci, /git diff --name-only -z/); - assert.match(ci, /node scripts\/ci-scope\.mjs --mode ci/); - assert.match(ci, /needs: scope/); - assert.match(ci, /needs\.scope\.result != 'success'/); + assert.match(ci, /node "\$\{TRUSTED_SCOPE\}" --mode ci/); assert.match(ci, /needs\.scope\.outputs\.node_compat == 'true'/); - assert.match(ci, /needs\.scope\.outputs\.windows_authority == 'true'/); - assert.ok(occurrences(ci, "Fail closed when scope detection failed") >= 2); - assert.match(ci, /authority-host\/windows\//); - assert.match(ci, /scripts\/prepare-authority-host-runtime-smoke\.mjs/); + + const windowsBlock = ci.slice(ci.indexOf(" windows-authority:")); + assert.doesNotMatch(windowsBlock, /needs: scope/); + assert.doesNotMatch(windowsBlock, /needs\.scope\.outputs\.windows_authority/); + assert.doesNotMatch(windowsBlock, /Fail closed when scope detection failed/); + assert.match(windowsBlock, /authority-host\/windows\//); + assert.match(windowsBlock, /scripts\/prepare-authority-host-runtime-smoke\.mjs/); }); test("live fixture diffs force compatibility lanes for acceptance coverage", () => { @@ -89,15 +90,15 @@ test("superseded expensive PR workflows cancel in progress", () => { } }); -test("C# CodeQL uses NUL-safe scope evidence and fails closed on scope errors", () => { - assert.match(codeql, /csharp_scope:/); - assert.match(codeql, /git diff --name-only -z/); - assert.match(codeql, /node scripts\/ci-scope\.mjs --mode csharp/); - assert.match(codeql, /authority-host\/windows\//); - assert.match(codeql, /needs: csharp_scope/); - assert.match(codeql, /needs\.csharp_scope\.result != 'success'/); - assert.match(codeql, /needs\.csharp_scope\.outputs\.required == 'true'/); - assert.match(codeql, /Fail closed when C# scope detection failed/); +test("C# CodeQL always runs on pull requests", () => { + assert.doesNotMatch(codeql, /csharp_scope:/); + assert.match(codeql, /analyze-csharp:/); + assert.match(codeql, /name: CodeQL \/ Analyze \(csharp\)/); + const csharpBlock = codeql.slice(codeql.indexOf(" analyze-csharp:")); + assert.doesNotMatch(csharpBlock, /needs: csharp_scope/); + assert.doesNotMatch(csharpBlock, /needs\.csharp_scope/); + assert.doesNotMatch(csharpBlock, /Fail closed when C# scope detection failed/); + assert.match(csharpBlock, /authority-host\/windows\//); }); test("maintenance schedules stay bounded", () => { From c2b9aca1507bbcbcd28d6879827d6e53d65bd183 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:22:17 +0200 Subject: [PATCH 17/49] fix: always run Windows authority CI --- .github/workflows/ci.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9d838bd..ab28f2d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ jobs: timeout-minutes: 5 outputs: node_compat: ${{ steps.scope.outputs.node_compat }} - windows_authority: ${{ steps.scope.outputs.windows_authority }} steps: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -34,7 +33,7 @@ jobs: node-version: 24 package-manager-cache: false - - name: Detect compatibility and Windows Authority scope + - name: Detect Node compatibility scope id: scope shell: bash env: @@ -44,7 +43,6 @@ jobs: set -euo pipefail if [[ "${EVENT_NAME}" != "pull_request" ]]; then echo "node_compat=true" >> "${GITHUB_OUTPUT}" - echo "windows_authority=true" >> "${GITHUB_OUTPUT}" exit 0 fi @@ -113,18 +111,9 @@ jobs: windows-authority: name: Node 24 / windows-latest - needs: scope - if: always() && (needs.scope.result != 'success' || github.event_name != 'pull_request' || needs.scope.outputs.windows_authority == 'true') runs-on: windows-latest timeout-minutes: 25 steps: - - name: Fail closed when scope detection failed - if: needs.scope.result != 'success' - shell: pwsh - run: | - Write-Error 'Detect CI scope did not complete successfully.' - exit 1 - - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: From bdb4a334e991f06b085593a512a2e12de8c4071d Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:22:32 +0200 Subject: [PATCH 18/49] fix: always run C# CodeQL --- .github/workflows/codeql.yml | 46 ------------------------------------ 1 file changed, 46 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9752f5b2..dff76578 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,43 +16,6 @@ concurrency: cancel-in-progress: true jobs: - csharp_scope: - name: Detect C# analysis scope - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - required: ${{ steps.scope.outputs.required }} - steps: - - name: Check out repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: 24 - package-manager-cache: false - - - name: Detect C#-relevant pull request changes - id: scope - shell: bash - env: - EVENT_NAME: ${{ github.event_name }} - BASE_SHA: ${{ github.event.pull_request.base.sha }} - run: | - set -euo pipefail - if [[ "${EVENT_NAME}" != "pull_request" ]]; then - echo "required=true" >> "${GITHUB_OUTPUT}" - exit 0 - fi - - TRUSTED_SCOPE="${RUNNER_TEMP}/github-delivery-ci-scope-base.mjs" - git show "${BASE_SHA}:scripts/ci-scope.mjs" > "${TRUSTED_SCOPE}" - git diff --name-only -z "${BASE_SHA}"...HEAD | - node "${TRUSTED_SCOPE}" --mode csharp >> "${GITHUB_OUTPUT}" - analyze: name: CodeQL / Analyze (javascript-typescript) runs-on: ubuntu-latest @@ -82,8 +45,6 @@ jobs: analyze-csharp: name: CodeQL / Analyze (csharp) - needs: csharp_scope - if: always() && (needs.csharp_scope.result != 'success' || github.event_name != 'pull_request' || needs.csharp_scope.outputs.required == 'true') runs-on: windows-latest timeout-minutes: 30 permissions: @@ -91,13 +52,6 @@ jobs: packages: read security-events: write steps: - - name: Fail closed when C# scope detection failed - if: needs.csharp_scope.result != 'success' - shell: pwsh - run: | - Write-Error 'Detect C# analysis scope did not complete successfully.' - exit 1 - - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: From e7a0f213ae682b1982625af4e9fb1f8eb3df8388 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:23:11 +0200 Subject: [PATCH 19/49] test: cover final audit hardening design --- tests/unit/deep-audit-hardening.test.mjs | 68 ++++++++++++++++-------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/tests/unit/deep-audit-hardening.test.mjs b/tests/unit/deep-audit-hardening.test.mjs index f4f0473a..5d3d5991 100644 --- a/tests/unit/deep-audit-hardening.test.mjs +++ b/tests/unit/deep-audit-hardening.test.mjs @@ -14,10 +14,9 @@ import test from "node:test"; import { classifyCiScope } from "../../scripts/ci-scope.mjs"; import { cleanupOrphanedWorkflowRuns } from "../../scripts/cleanup-orphaned-workflows.mjs"; import { makeRedemptionRunner } from "../../scripts/lib/authority-execution.mjs"; -import { - collectBranchReviewInput, - planReviewScope, -} from "../../scripts/lib/review-scope.mjs"; +import { collectBranchReviewInput } from "../../scripts/lib/branch-review-input.mjs"; +import { evaluateMergeStackEligibility } from "../../scripts/lib/merge-stack-policy.mjs"; +import { planReviewScope } from "../../scripts/lib/review-scope.mjs"; import { evaluate as evaluatePreOpen } from "../../scripts/pre-open-gate.mjs"; import { briefText } from "../../scripts/review-brief.mjs"; @@ -33,28 +32,26 @@ function git(cwd, args) { }).trim(); } -test("CI scope policy changes force the Windows and C# security lanes", () => { +test("CI scope policy changes remain conservative when the classifier itself changes", () => { const scope = classifyCiScope(["scripts/ci-scope.mjs"]); assert.equal(scope.nodeCompat, true); assert.equal(scope.windowsAuthority, true); assert.equal(scope.csharp, true); }); -test("PR scope detection executes the trusted base selector, not candidate code", () => { +test("security-critical Windows and C# lanes cannot be scoped out by a pull request", () => { const ci = source(".github/workflows/ci.yml"); const codeql = source(".github/workflows/codeql.yml"); - const trustedSelector = /git show \"\$\{BASE_SHA\}:scripts\/ci-scope\.mjs\"/; - assert.match(ci, trustedSelector); - assert.match(codeql, trustedSelector); - assert.doesNotMatch( - ci, - /git diff --name-only -z \"\$\{BASE_SHA\}\"\.\.\.HEAD \|\s*\n\s*node scripts\/ci-scope\.mjs/, - ); - assert.doesNotMatch( - codeql, - /git diff --name-only -z \"\$\{BASE_SHA\}\"\.\.\.HEAD \|\s*\n\s*node scripts\/ci-scope\.mjs/, - ); + assert.match(ci, /git show "\$\{BASE_SHA\}:scripts\/ci-scope\.mjs"/); + const windowsBlock = ci.slice(ci.indexOf(" windows-authority:")); + assert.doesNotMatch(windowsBlock, /needs: scope/); + assert.doesNotMatch(windowsBlock, /needs\.scope\.outputs\.windows_authority/); + + assert.doesNotMatch(codeql, /csharp_scope:/); + const csharpBlock = codeql.slice(codeql.indexOf(" analyze-csharp:")); + assert.doesNotMatch(csharpBlock, /needs: csharp_scope/); + assert.doesNotMatch(csharpBlock, /needs\.csharp_scope/); }); test("trusted authority is redeemed before an internal coordination write executes", () => { @@ -158,10 +155,39 @@ test("pre-open gate blocks until every deterministic required probe has evidence assert.ok(result.blockers.includes("probe:requiredProbes:test-honesty")); }); -test("merge driver enforces stack-parent eligibility as an executable precondition", () => { - const mergeDriver = source("scripts/merge-pr-driver.mjs"); - assert.match(mergeDriver, /verifyMergeStackEligibility/); - assert.match(mergeDriver, /stack_parent_unlanded/); +test("merge execution rejects a child while its stack parent is still open", () => { + const repo = "acme/widget"; + const prs = [ + { + number: 1, + title: "parent", + headRefName: "feature/parent", + baseRefName: "main", + headRepoFullName: repo, + baseRepoFullName: repo, + url: "https://example.invalid/1", + isDraft: false, + headRefOid: "a".repeat(40), + }, + { + number: 2, + title: "child", + headRefName: "feature/child", + baseRefName: "feature/parent", + headRepoFullName: repo, + baseRepoFullName: repo, + url: "https://example.invalid/2", + isDraft: false, + headRefOid: "b".repeat(40), + }, + ]; + const result = evaluateMergeStackEligibility({ prs, targetPr: 2 }); + assert.equal(result.eligible, false); + assert.equal(result.reason, "stack_parent_unlanded"); + assert.equal(result.parentPr, 1); + + const executionBoundary = source("scripts/lib/mutation-execution-context.mjs"); + assert.match(executionBoundary, /verifyMergeStackEligibility/); }); test("orphan cleanup aborts if the default branch generation moves before deletion", async () => { From e0bbea432854912a5fc0499d9688bb9e03db3d92 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:28:18 +0200 Subject: [PATCH 20/49] docs: describe deep audit hardening --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d2fd2ff3..64531ad1 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ The 0.8.6 line adds the major workflow and safety work developed after 0.8.2, pl - competing-PR consolidation analysis; - conditional head-bound visual review evidence; - multi-base backport/port delivery; -- a substantially leaner GitHub Actions topology with stale-run cancellation and scoped platform lanes; +- a substantially leaner GitHub Actions topology with stale-run cancellation and unconditional security-critical Windows Authority/C# lanes; - fail-closed delivery integrity for moved PR heads, queued/auto-merge outcomes, mutation receipts, and remaining public workflow routing; - bounded GitHub and Git subprocesses on review, verdict, CI forensics, ship-gate, runtime, live-fixture, release, and npm helper paths. @@ -162,7 +162,9 @@ Status, open-work, and competing-PR analysis remain read-only. Implementation-on Routine network-visible issue/PR writes pass through the typed GitHub mutation boundary. Stale-sensitive requests bind expected head state; branch pushes bind repository/remote/branch plus old/new tips; history rewrites use exact force-with-lease semantics rather than bare force. -**Merge is deliberately stricter.** `scripts/merge-pr-driver.mjs` owns settle, final current-head/base/rules/feedback/review-evidence recapture, trusted destructive authority, head-pinned merge execution, and post-merge reconciliation. Generic hand-built merge mutation documents are rejected. +For trusted high-assurance operations, authority redemption happens before the first mutating GitHub command, including autonomous idempotency coordination refs/tags. A rejected grant therefore cannot leave a coordination write behind before the requested mutation. + +**Merge is deliberately stricter.** `scripts/merge-pr-driver.mjs` owns settle, final current-head/base/rules/feedback/review-evidence recapture, trusted destructive authority, head-pinned merge execution, and post-merge reconciliation. The lower mutation execution boundary also rechecks open-PR stack topology and rejects a child merge while its parent PR is still open. Generic hand-built merge mutation documents are rejected. ### Exact-effect trusted authority @@ -207,6 +209,8 @@ A full review can combine: - proactive contract verification appropriate to the changed behavior; - conditional **visual evidence** for rendered/UI surfaces. +The pre-open gate treats those deterministic probes as first-class obligations alongside required bug lenses and security surfaces. A probe detected from the branch diff remains blocking until its `done` or justified `n/a` evidence is present. Local branch review uses NUL-delimited Git records so renames and unusual valid paths retain both source and destination identity. + ### Safe simplification Simplification is **explicit-only**. Its goal is lower cognitive load and safer maintenance. **Line count is never the goal**; fewer lines are acceptable only when behavior and clarity improve. @@ -226,6 +230,7 @@ The final ship decision is one authoritative `ready`, `blocked`, or `unknown` re - review decision, stale approvals, last-push requirements, unresolved threads; - conflicts, behind state, merge queue / auto-merge state; - unknown ruleset/state values failing closed; +- open stack-parent topology before destructive merge execution; - exact-head merge execution and read-only reconciliation after ambiguous write results; - partial success when merge succeeded but non-destructive post-merge ceremony did not. @@ -237,7 +242,7 @@ These are intentionally three different concepts. ### Stacked PRs -A stack is a dependency chain where a child PR targets a parent PR branch. Stack operations discover repository-qualified topology, restack bottom-up, preserve layer ownership, and revalidate every surviving child after an upstream head changes. +A stack is a dependency chain where a child PR targets a parent PR branch. Stack operations discover repository-qualified topology, restack bottom-up, preserve layer ownership, and revalidate every surviving child after an upstream head changes. The mutation execution boundary independently rejects a merge while the target PR still points at another open PR's head, so merge-order safety does not depend only on workflow prose. ### Competing PRs @@ -473,24 +478,24 @@ npm run reliability:gate ### Lean required CI topology -The pull-request CI topology is deliberately asymmetric to avoid repeating the full repository suite across every OS/runtime combination: +The pull-request CI topology is deliberately asymmetric to avoid repeating the full repository suite across every OS/runtime combination while keeping the security-critical platform lanes unskippable by PR scope logic: | Required context | PR behavior | |---|---| | **Node 24 / ubuntu-latest** | Canonical full `npm run check`; then bounded Node 26 syntax/package/unit compatibility on the same workspace | -| **Node 22 / ubuntu-latest** | Bounded compatibility lane only when runtime-relevant paths change; forced for `main`/live-fixture acceptance | -| **Node 24 / windows-latest** | Windows Authority restore/build/self-test/publish/install smoke only when Authority/platform-relevant paths change; forced for `main`/live-fixture acceptance | +| **Node 22 / ubuntu-latest** | Bounded compatibility lane only when runtime-relevant paths change; its path classifier is executed from the PR base version | +| **Node 24 / windows-latest** | Always runs Windows Authority restore/build/self-test/publish/install smoke on pull requests | | **Dependency Review** | Runs on pull requests | | **CodeQL / Analyze (javascript-typescript)** | Runs on pull requests | -| **CodeQL / Analyze (csharp)** | Scoped to Windows Authority/C#-relevant PRs; still runs on `main` and schedules | +| **CodeQL / Analyze (csharp)** | Always runs on pull requests, plus `main` and schedules | -There are no macOS PR compatibility lanes and no duplicate Architecture Contracts workflow. Superseded CI, CodeQL, and Dependency Review runs are cancelled when a newer commit arrives. Repository-policy verification is daily and orphan-workflow cleanup is weekly. +There are no macOS PR compatibility lanes and no duplicate Architecture Contracts workflow. Superseded CI, CodeQL, and Dependency Review runs are cancelled when a newer commit arrives. Repository-policy verification is daily and orphan-workflow cleanup is weekly; cleanup pins the default-branch generation before deleting stale workflow histories. -For ordinary runtime-relevant PRs this reduces full `npm run check` executions from **9 to 1**, full unit-suite runtime executions from **9 to 3**, Windows Authority lanes from **2 to 1 when relevant**, and macOS PR jobs from **2 to 0** while retaining Node 22/24/26 compatibility coverage. +For ordinary runtime-relevant PRs this keeps full `npm run check` executions at **1**, full unit-suite runtime executions at **3**, one Windows Authority lane on every PR, and **0** macOS PR jobs while retaining Node 22/24/26 compatibility coverage and unconditional Windows/C# security coverage. ### Live lifecycle fixture -The unit/eval suite proves deterministic contracts. An explicitly opted-in fixture repository exercises the real GitHub lifecycle with immutable repository-identity binding before the first mutation. Fixture runs force the scoped Node 22 and Windows compatibility lanes even when normal PR path filtering would skip them. +The unit/eval suite proves deterministic contracts. An explicitly opted-in fixture repository exercises the real GitHub lifecycle with immutable repository-identity binding before the first mutation. Fixture diffs force the scoped Node 22 compatibility lane; the Windows Authority lane already runs unconditionally. See [`docs/live-integration.md`](docs/live-integration.md) and [`docs/live-github-integration.md`](docs/live-github-integration.md). @@ -554,4 +559,4 @@ The project intentionally fails closed rather than claiming unsupported coverage Some workflow directions were informed by public/open-source agent skills and GitHub automation patterns, including concepts from `OutThisLife/brooklyn-skills`. Adapted ideas are rewritten around GitHub Delivery's own evidence, authority, routing, and lifecycle contracts; relevant workflow files include provenance notes where appropriate. -Licensed under the [MIT License](LICENSE). +Licensed under the [MIT License](LICENSE). \ No newline at end of file From a21713affc5bf981716255eccf8b7f138346ba0f Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:32:51 +0200 Subject: [PATCH 21/49] docs: record deep audit hardening --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50527e22..993c340a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to `github-delivery` are documented here. ## [Unreleased] +### Changed + +- Windows Authority CI and C# CodeQL now run on every pull request so candidate code cannot scope out its own security-critical lanes; the remaining Node 22 compatibility selector is executed from the pull request base version (PR #299). +- Review briefs now apply a global model-facing diff-hunk budget while preserving complete structured review scope for deterministic tooling and on-demand inspection (PR #299). + +### Fixed + +- Redeem trusted authority before the first mutating GitHub command, including autonomous idempotency tag/ref coordination, so a rejected grant cannot leave coordination state behind before the requested mutation (PR #299). +- Preserve rename/copy source and destination paths with NUL-delimited local branch diff parsing, and make every deterministic required probe a first-class pre-open blocker until `done` or justified `n/a` evidence exists (PR #299). +- Enforce open stack-parent ordering at the mutation execution boundary before merge authority, and abort orphan-workflow cleanup before deletion when the default-branch generation changed during preflight (PR #299). + ## [0.8.7] - 2026-08-19 ### Changed From b77cecca976745d6f2564e3b69358fcae7aa9d1d Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:36:31 +0200 Subject: [PATCH 22/49] fix: remove unsafe branch diff parser --- scripts/lib/review-scope.mjs | 43 ++---------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/scripts/lib/review-scope.mjs b/scripts/lib/review-scope.mjs index 1e98f836..83f2b0ae 100644 --- a/scripts/lib/review-scope.mjs +++ b/scripts/lib/review-scope.mjs @@ -3,6 +3,8 @@ import { boundedSpawnSync } from "./subprocess-policy.mjs"; import { PROBE_REGISTRY, validateProbeRegistry } from "./probe-registry.mjs"; import { planVisualEvidence } from "./visual-evidence.mjs"; +export { collectBranchReviewInput } from "./branch-review-input.mjs"; + const CODE_RE = /\.(?:[cm]?[jt]sx?|mjs|cjs|py|go|rs|java|kt|rb|php|cs|swift|c|cc|cpp|h|hpp|vue|svelte)$/i; const DOC_RE = /\.(?:md|txt|rst|adoc)$/i; const OPERATIONAL_POLICY_RE = /(^|\/)(?:SKILL\.md|references\/.*\.md|overrides\/)/i; @@ -335,44 +337,3 @@ export function collectPrReviewInput(repo, pr) { assertCompletePrFileEnumeration(meta.changedFiles, files.length); return { repo, pr, ...meta, files }; } - -function git(args) { - const result = boundedSpawnSync("git", args, { encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }); - if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || "git failed").trim()); - return String(result.stdout || "").trim(); -} - -function gitMaybe(args) { - const result = boundedSpawnSync("git", args, { encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }); - if (result.status !== 0) return null; - return String(result.stdout || "").trim(); -} - -function resolveRepoForBranch() { - try { - const name = JSON.parse(gh(["repo", "view", "--json", "nameWithOwner"])).nameWithOwner; - if (typeof name === "string" && name.includes("/")) return name; - } catch { - // fall through to git remote - } - const remote = gitMaybe(["remote", "get-url", "origin"]); - if (!remote) return null; - const match = String(remote).match(/(?:[:/])([^/:]+)\/([^/]+?)(?:\.git)?$/); - return match ? `${match[1]}/${match[2]}` : null; -} - -export function collectBranchReviewInput(baseRef, headRef) { - const nameStatus = git(["diff", "--name-status", `${baseRef}...${headRef}`]); - const paths = nameStatus.split(/\r?\n/).filter(Boolean).map((line) => { - const [status, ...rest] = line.split(/\s+/); - return { status, path: rest.join(" ").replace(/^"|"$/g, "") }; - }); - const files = paths.map(({ status, path }) => { - const patch = git(["diff", "--no-ext-diff", "--unified=3", `${baseRef}...${headRef}`, "--", path]); - const added = patch.split(/\r?\n/).filter((line) => line.startsWith("+") && !line.startsWith("+++")).length; - const deleted = patch.split(/\r?\n/).filter((line) => line.startsWith("-") && !line.startsWith("---")).length; - return { path, status, patch, additions: added, deletions: deleted }; - }); - const headRefOid = gitMaybe(["rev-parse", "--verify", headRef]) || null; - return { repo: resolveRepoForBranch(), pr: null, headRefOid, files }; -} From 53f445d31465f8e9f46652fe5d26fa6e5d048bf0 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:37:08 +0200 Subject: [PATCH 23/49] fix: reuse canonical probe evidence records --- scripts/lib/pre-open-evidence.mjs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/scripts/lib/pre-open-evidence.mjs b/scripts/lib/pre-open-evidence.mjs index 5e3d270e..1a326984 100644 --- a/scripts/lib/pre-open-evidence.mjs +++ b/scripts/lib/pre-open-evidence.mjs @@ -1,12 +1,9 @@ /** * Validate review-completion evidence for the pre-open gate. * - * The gate derives required scope from the diff shape. Evidence is the - * machine-checkable record that each required lens, surface, and probe was - * actually reviewed: `done` means the pass ran and found nothing needing a fix - * (or the findings were fixed), `n/a (why)` means the boundary is untouched and - * the reason is recorded. This module validates the evidence payload so the gate - * can clear a `blocked` obligation only for entries that carry valid evidence. + * Lenses/surfaces use the compact `done` / `n/a ` contract. Deterministic + * probes retain their existing structured machine evidence and are validated + * against the actual diff scope by `probe-evidence.mjs` inside the gate. */ export const PRE_OPEN_EVIDENCE_SCHEMA_VERSION = 1; @@ -47,10 +44,8 @@ function normalizeEvidenceBlock(block, prefix, errors) { * Validate a pre-open evidence payload. * * `probes` is optional for schema-version compatibility with older evidence. - * Missing probe evidence never clears a newly required probe. - * - * @param {unknown} input - * @returns {{ ok: true, evidence: PreOpenEvidence } | { ok: false, errors: string[] }} + * When present it is passed through as a probe-id -> structured record map; + * scope-aware validation happens in the pre-open gate. */ export function validatePreOpenEvidence(input) { const errors = []; @@ -71,26 +66,21 @@ export function validatePreOpenEvidence(input) { const lenses = normalizeEvidenceBlock(lensBlock, "lens", errors); const surfaces = normalizeEvidenceBlock(surfaceBlock, "surface", errors); - const probes = normalizeEvidenceBlock(probeBlock, "probe", errors); - if (errors.length) return { ok: false, errors }; + return { ok: true, evidence: { schemaVersion: PRE_OPEN_EVIDENCE_SCHEMA_VERSION, lenses, surfaces, - probes, + probes: structuredClone(probeBlock), }, }; } /** - * Whether evidence clears a required id. - * - * @param {Record} map - * @param {string} id - * @returns {boolean} + * Whether compact lens/surface evidence clears a required id. */ export function evidenceClears(map, id) { const status = map[id]; From 0462f8c9a6e32e4ff713d0a559d4b687976aad49 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:37:53 +0200 Subject: [PATCH 24/49] fix: validate pre-open probes with canonical evidence --- scripts/pre-open-gate.mjs | 73 ++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/scripts/pre-open-gate.mjs b/scripts/pre-open-gate.mjs index b7a3efe0..2476fffe 100644 --- a/scripts/pre-open-gate.mjs +++ b/scripts/pre-open-gate.mjs @@ -5,47 +5,77 @@ import { collectBranchReviewInput } from "./lib/branch-review-input.mjs"; import { planReviewScope } from "./lib/review-scope.mjs"; import { projectBugScope, projectSecurityScope } from "./lib/review-scope-compat.mjs"; import { evidenceClears, validatePreOpenEvidence } from "./lib/pre-open-evidence.mjs"; +import { validateProbeEvidence } from "./lib/probe-evidence.mjs"; function usageError() { throw new Error("Usage: node scripts/pre-open-gate.mjs OWNER/REPO BASE_REF HEAD_REF [--output FILE] [--evidence-file FILE] | --self-test"); } +function probeCoverage(plan, evidence) { + const requiredProbes = plan.requiredProbes || []; + const errors = validateProbeEvidence(evidence?.probes ?? {}, { + requiredProbes, + probeEvidence: plan.probeEvidence || {}, + }); + const errorsByProbe = new Map(); + for (const error of errors) { + const probeId = error?.probeId || "unknown"; + if (!errorsByProbe.has(probeId)) errorsByProbe.set(probeId, []); + errorsByProbe.get(probeId).push(error); + } + return { requiredProbes, errors, errorsByProbe }; +} + export function evaluate(plan, evidence = null) { const bugScope = projectBugScope(plan); const securityScope = projectSecurityScope(plan); const implementationDiffPresent = Number(plan?.fileCount || 0) > 0; - const scopeBlockers = [ - ...bugScope.requiredLenses.map((id) => `bug:requiredLenses:${id}`), - ...securityScope.requiredSurfaces.map((id) => `security:requiredSurfaces:${id}`), - ...(plan.requiredProbes || []).map((id) => `probe:requiredProbes:${id}`), - ]; - const complete = implementationDiffPresent && plan.complete && bugScope.complete && securityScope.complete; const lensMap = evidence?.lenses ?? {}; const surfaceMap = evidence?.surfaces ?? {}; - const probeMap = evidence?.probes ?? {}; const clearedByEvidence = []; - const remainingScopeBlockers = scopeBlockers.filter((blocker) => { - const [axis, , id] = blocker.split(":"); - const map = axis === "bug" ? lensMap : axis === "security" ? surfaceMap : probeMap; - const cleared = evidenceClears(map, id); - if (cleared) clearedByEvidence.push(blocker); - return !cleared; - }); - const blockers = implementationDiffPresent - ? remainingScopeBlockers + const blockers = []; + + for (const id of bugScope.requiredLenses) { + const blocker = `bug:requiredLenses:${id}`; + if (evidenceClears(lensMap, id)) clearedByEvidence.push(blocker); + else blockers.push(blocker); + } + for (const id of securityScope.requiredSurfaces) { + const blocker = `security:requiredSurfaces:${id}`; + if (evidenceClears(surfaceMap, id)) clearedByEvidence.push(blocker); + else blockers.push(blocker); + } + + const probes = probeCoverage(plan, evidence); + for (const id of probes.requiredProbes) { + const blocker = `probe:requiredProbes:${id}`; + if (probes.errorsByProbe.has(id)) blockers.push(blocker); + else clearedByEvidence.push(blocker); + } + for (const [probeId, errors] of probes.errorsByProbe) { + if (probes.requiredProbes.includes(probeId)) continue; + for (const error of errors) { + blockers.push(`probe:evidence:${error.code}:${probeId}`); + } + } + + const complete = implementationDiffPresent && plan.complete && bugScope.complete && securityScope.complete; + const finalBlockers = implementationDiffPresent + ? blockers : ["workflow:implementation_missing"]; const decision = !implementationDiffPresent ? "blocked" : !complete ? "unknown" - : blockers.length + : finalBlockers.length ? "blocked" : "ready"; return { bugScope, securityScope, - requiredProbes: plan.requiredProbes || [], - blockers, + requiredProbes: probes.requiredProbes, + probeEvidenceErrors: probes.errors, + blockers: finalBlockers, clearedByEvidence, decision, complete, @@ -54,7 +84,7 @@ export function evaluate(plan, evidence = null) { }; } -function report({ repo, baseRef, headRef, headRefOid, bugScope, securityScope, requiredProbes, blockers, clearedByEvidence, decision, complete, implementationDiffPresent, evidenceApplied }) { +function report({ repo, baseRef, headRef, headRefOid, bugScope, securityScope, requiredProbes, probeEvidenceErrors, blockers, clearedByEvidence, decision, complete, implementationDiffPresent, evidenceApplied }) { return { schemaVersion: 1, kind: "github-delivery/pre-open-gate", @@ -69,13 +99,14 @@ function report({ repo, baseRef, headRef, headRefOid, bugScope, securityScope, r bugScope, securityScope, requiredProbes, + probeEvidenceErrors, blockers, clearedByEvidence, instructions: [ "workflow:implementation_missing: this pre-open gate requires a non-empty candidate implementation diff; implement first, then rerun the gate before publication.", "decision=blocked: complete every remaining required bug lens, security surface, and deterministic probe on this branch diff (with --evidence-file), fix Confirmed High/Critical findings, then rerun before opening the PR.", "decision=unknown: restore complete branch evidence (fetch base, checkout head) and rerun; never open a PR from an incomplete diff.", - "decision=ready: the non-empty candidate branch diff has no remaining review obligations, or every required lens/surface/probe carries valid done/n-a evidence; you may proceed to open the PR.", + "decision=ready: the non-empty candidate branch diff has no remaining review obligations, or every required lens/surface plus every canonical structured probe-evidence record validates; you may proceed to open the PR.", ], }; } From 17e163d436cde184b62bd177dd8f2614311dc9fe Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:38:32 +0200 Subject: [PATCH 25/49] test: verify canonical probe evidence clears gate --- tests/unit/deep-audit-hardening.test.mjs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/unit/deep-audit-hardening.test.mjs b/tests/unit/deep-audit-hardening.test.mjs index 5d3d5991..dd4a33a3 100644 --- a/tests/unit/deep-audit-hardening.test.mjs +++ b/tests/unit/deep-audit-hardening.test.mjs @@ -123,7 +123,7 @@ test("branch review input preserves rename source and destination paths", () => } }); -test("pre-open gate blocks until every deterministic required probe has evidence", () => { +test("pre-open gate blocks until every deterministic required probe has canonical evidence", () => { const plan = planReviewScope({ repo: "acme/widget", pr: null, @@ -149,10 +149,18 @@ test("pre-open gate blocks until every deterministic required probe has evidence ), probes: {}, }; - const result = evaluatePreOpen(plan, evidence); + const blocked = evaluatePreOpen(plan, evidence); + assert.equal(blocked.decision, "blocked"); + assert.ok(blocked.blockers.includes("probe:requiredProbes:test-honesty")); - assert.equal(result.decision, "blocked"); - assert.ok(result.blockers.includes("probe:requiredProbes:test-honesty")); + evidence.probes["test-honesty"] = { + status: "clean", + files: ["tests/worker.test.mjs"], + }; + const cleared = evaluatePreOpen(plan, evidence); + assert.equal(cleared.blockers.includes("probe:requiredProbes:test-honesty"), false); + assert.equal(cleared.probeEvidenceErrors.length, 0); + assert.equal(cleared.decision, "ready"); }); test("merge execution rejects a child while its stack parent is still open", () => { From cba6ed9b3523c5c6211e89baec0c01c967ceba74 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:39:50 +0200 Subject: [PATCH 26/49] fix: classify renames from both path generations --- scripts/lib/review-scope.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/lib/review-scope.mjs b/scripts/lib/review-scope.mjs index 83f2b0ae..aa74f50e 100644 --- a/scripts/lib/review-scope.mjs +++ b/scripts/lib/review-scope.mjs @@ -204,7 +204,9 @@ export function planReviewScope(input = {}) { const { added, removed } = patchLines(file.patch); const changedText = [...added, ...removed].join("\n"); const symbols = extractSymbols(file.path, changedText); - const isLogic = CODE_RE.test(file.path) || OPERATIONAL_POLICY_RE.test(file.path) || /^\.github\//.test(file.path); + const isLogic = paths.some( + (path) => CODE_RE.test(path) || OPERATIONAL_POLICY_RE.test(path) || /^\.github\//.test(path), + ); if (isLogic) logicFiles.push(file.path); if (isLogic && !file.patch && file.status !== "removed") missingPatches.push(file.path); @@ -228,8 +230,10 @@ export function planReviewScope(input = {}) { workflowPermissionChanges.push(...workflowSignals(file.path, added, removed, evidence)); - if (LOCK_RE.test(file.path) || MANIFEST_RE.test(file.path)) { - const kind = LOCK_RE.test(file.path) ? "lockfile" : "manifest"; + const lockChanged = paths.some((path) => LOCK_RE.test(path)); + const manifestChanged = paths.some((path) => MANIFEST_RE.test(path)); + if (lockChanged || manifestChanged) { + const kind = lockChanged ? "lockfile" : "manifest"; dependencyChanges.push({ file: file.path, kind, additions: file.additions, deletions: file.deletions }); addEvidence(evidence, "supply_chain", "security", 3, `${kind} changed`, file.path); } From fc1e9cb67d18a55cd009d95b7afa9409d3edfb14 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:40:26 +0200 Subject: [PATCH 27/49] test: assert renamed code remains review scoped --- tests/unit/deep-audit-hardening.test.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/deep-audit-hardening.test.mjs b/tests/unit/deep-audit-hardening.test.mjs index dd4a33a3..775e5c62 100644 --- a/tests/unit/deep-audit-hardening.test.mjs +++ b/tests/unit/deep-audit-hardening.test.mjs @@ -94,7 +94,7 @@ test("trusted authority is redeemed before an internal coordination write execut assert.equal(execution.redemption()?.status, "consumed"); }); -test("branch review input preserves rename source and destination paths", () => { +test("branch review input preserves rename paths and keeps renamed code in scope", () => { const root = mkdtempSync(join(tmpdir(), "github-delivery-rename-")); const previousCwd = process.cwd(); try { @@ -117,6 +117,10 @@ test("branch review input preserves rename source and destination paths", () => assert.equal(input.files[0].status.startsWith("R"), true); assert.equal(input.files[0].previousPath, "src/auth.mjs"); assert.equal(input.files[0].path, "src/auth"); + + const plan = planReviewScope(input); + assert.ok(plan.logicFiles.includes("src/auth")); + assert.notEqual(evaluatePreOpen(plan).decision, "ready"); } finally { process.chdir(previousCwd); rmSync(root, { recursive: true, force: true }); From 8b3bd57620a8414bef8c080c6947d547dfdf31a4 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:44:44 +0200 Subject: [PATCH 28/49] fix: keep code-to-doc renames out of docs-only shortcut --- scripts/lib/review-scope.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/lib/review-scope.mjs b/scripts/lib/review-scope.mjs index aa74f50e..243ca19d 100644 --- a/scripts/lib/review-scope.mjs +++ b/scripts/lib/review-scope.mjs @@ -278,7 +278,12 @@ export function planReviewScope(input = {}) { const bugLenses = finalize(lensEvidence); const requiredSecurity = domains.filter((item) => item.category === "security" && item.required); const requiredBug = bugLenses.filter((item) => item.required); - const docsOnly = files.length > 0 && files.every((file) => DOC_RE.test(file.path) && !OPERATIONAL_POLICY_RE.test(file.path)); + const docsOnly = files.length > 0 && files.every((file) => { + const paths = [file.path, file.previousPath].filter(Boolean); + return paths.every( + (path) => DOC_RE.test(path) && !OPERATIONAL_POLICY_RE.test(path), + ); + }); const criticalSecurity = requiredSecurity.some((item) => item.confidence === "high") || removedControlLeads.length > 0; const criticalBug = requiredBug.some((item) => item.confidence === "high"); const securityDepth = docsOnly ? "skip" : criticalSecurity ? "full" : requiredSecurity.length ? "targeted" : logicFiles.length ? "baseline" : "skip"; From 699c4c299dc2741daec6914f1394250182e67f72 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:45:43 +0200 Subject: [PATCH 29/49] test: cover code-to-doc rename scope --- tests/unit/deep-audit-hardening.test.mjs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/deep-audit-hardening.test.mjs b/tests/unit/deep-audit-hardening.test.mjs index 775e5c62..63ac7d0b 100644 --- a/tests/unit/deep-audit-hardening.test.mjs +++ b/tests/unit/deep-audit-hardening.test.mjs @@ -127,6 +127,28 @@ test("branch review input preserves rename paths and keeps renamed code in scope } }); +test("code renamed to a documentation path cannot take the docs-only shortcut", () => { + const plan = planReviewScope({ + repo: "acme/widget", + pr: null, + headRefOid: "abc", + files: [ + { + path: "docs/auth.md", + previousPath: "src/auth.mjs", + status: "R100", + patch: "diff --git a/src/auth.mjs b/docs/auth.md", + additions: 0, + deletions: 0, + }, + ], + }); + + assert.ok(plan.logicFiles.includes("docs/auth.md")); + assert.notEqual(plan.bugReview.depth, "skip"); + assert.notEqual(plan.securityReview.depth, "skip"); +}); + test("pre-open gate blocks until every deterministic required probe has canonical evidence", () => { const plan = planReviewScope({ repo: "acme/widget", From f9e4fdfe0703cf285b413daea9e570d8a9eee315 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:46:57 +0200 Subject: [PATCH 30/49] docs: describe canonical probe evidence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64531ad1..d550c43b 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ A full review can combine: - proactive contract verification appropriate to the changed behavior; - conditional **visual evidence** for rendered/UI surfaces. -The pre-open gate treats those deterministic probes as first-class obligations alongside required bug lenses and security surfaces. A probe detected from the branch diff remains blocking until its `done` or justified `n/a` evidence is present. Local branch review uses NUL-delimited Git records so renames and unusual valid paths retain both source and destination identity. +The pre-open gate treats those deterministic probes as first-class obligations alongside required bug lenses and security surfaces. A probe detected from the branch diff remains blocking until its canonical structured probe-evidence record validates against the deterministic trigger files. Local branch review uses NUL-delimited Git records so renames and unusual valid paths retain both source and destination identity. ### Safe simplification From c5fdacbbdccec7a4f81f6fe7828ede7ce29fe800 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:48:51 +0200 Subject: [PATCH 31/49] docs: describe canonical probe evidence --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 993c340a..e5484c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ All notable changes to `github-delivery` are documented here. ### Fixed - Redeem trusted authority before the first mutating GitHub command, including autonomous idempotency tag/ref coordination, so a rejected grant cannot leave coordination state behind before the requested mutation (PR #299). -- Preserve rename/copy source and destination paths with NUL-delimited local branch diff parsing, and make every deterministic required probe a first-class pre-open blocker until `done` or justified `n/a` evidence exists (PR #299). +- Preserve rename/copy source and destination paths with NUL-delimited local branch diff parsing, keep both path generations in review classification, and make every deterministic required probe a first-class pre-open blocker until its canonical structured probe evidence validates against the deterministic trigger files (PR #299). - Enforce open stack-parent ordering at the mutation execution boundary before merge authority, and abort orphan-workflow cleanup before deletion when the default-branch generation changed during preflight (PR #299). ## [0.8.7] - 2026-08-19 @@ -63,7 +63,7 @@ All notable changes to `github-delivery` are documented here. - Moved the remaining workflow-level `actions: write` permission down to the cleanup job, kept top-level workflow permissions read-only, and added validation that rejects future top-level write scopes while still permitting explicitly allowlisted job-level writes (PR #282). - Hardened PR publication identity and retries: exact duplicate detection now binds target repository, head repository/ref, and base; qualified REST head filters prevent same-repository misses; explicit cross-repository `head_repo` identity is supported; and exact owned idempotent retries converge before the broader duplicate preflight (PR #283). -- Protected existing PR-body screenshots, videos, GitHub uploads, reference-style Markdown images, and other recognized media from accidental body rewrites. Intentional removal requires an exact approved identity list that is included in trusted `update_pr_body` authority scope (PR #283). +- Protected existing PR-body screenshots, videos, GitHub uploads, reference-style Markdown images, and other recognized media from accidental body rewrites. Intentional media removal requires an exact approved identity list that is included in trusted `update_pr_body` authority scope (PR #283). - Prevented cross-repository closing issues and unsafe display URLs from masquerading as trustworthy same-repository work-item evidence, and tightened open-work fixtures so PR-number normalization and repository boundaries are actually exercised (PR #283). ## [0.8.2] - 2026-08-17 From 5aa393727b323b65b010c6f8f2685abc3d63ca37 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:53:33 +0200 Subject: [PATCH 32/49] docs: describe pre-open probe obligations --- references/gate-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/gate-helpers.md b/references/gate-helpers.md index 49c1c7ae..7c90ea65 100644 --- a/references/gate-helpers.md +++ b/references/gate-helpers.md @@ -185,4 +185,4 @@ For an **unopened** branch, run the pre-open gate instead of `bug-scope.mjs`/`se node "/scripts/pre-open-gate.mjs" OWNER/REPO BASE_REF HEAD_REF ``` -Exit `0` (`decision: "ready"`) means the branch diff has no required bug/security scope. Exit `1` (`decision: "blocked"`) lists the required bug lenses and security surfaces that must be reviewed (and Confirmed High/Critical findings fixed) before opening. Exit `2` (`decision: "unknown"`) means the branch diff could not be collected completely — never open a PR on unknown. See `references/create-pr-for-issue.md` step C2. +Exit `0` (`decision: "ready"`) means every deterministic pre-open obligation is cleared. Exit `1` (`decision: "blocked"`) lists remaining bug lenses, security surfaces, and required probes. Lens/surface evidence uses the pre-open `done` / justified `n/a` records; probe evidence uses the canonical structured `clean` / `findings` / `n-a` records and is validated against the deterministic trigger files. Confirmed High/Critical findings must be fixed before opening. Exit `2` (`decision: "unknown"`) means the branch diff could not be collected completely — never open a PR on unknown. See `references/create-pr-for-issue.md` step D. From 4c4e11735fb7b69b5cd91303b3838b9a7a507fb0 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:06:29 +0200 Subject: [PATCH 33/49] test: bound interleaved tool micro-narration --- .../codex-watchdog-progress-bounds.test.mjs | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/tests/unit/codex-watchdog-progress-bounds.test.mjs b/tests/unit/codex-watchdog-progress-bounds.test.mjs index c77c3411..a3cf1be9 100644 --- a/tests/unit/codex-watchdog-progress-bounds.test.mjs +++ b/tests/unit/codex-watchdog-progress-bounds.test.mjs @@ -78,6 +78,43 @@ test("a real tool start clears the pending tool-emission stall without claiming assert.equal(text(r, "I'll run the focused grep.\n").internalRequests.length, 0); }); +test("interleaved evidence tools do not buy a fresh micro-narration budget", () => { + const r = router({ + generatedCharHardLimit: 10_000, + toolEmissionIntentThreshold: 50, + microNarrationIntentThreshold: 3, + }); + + assert.equal( + text(r, "I will start by loading the canonical agent rules and rewrite plan.\n").internalRequests.length, + 0, + ); + started(r, { + id: "read-1", + type: "commandExecution", + command: 'Get-Content -LiteralPath "AGENTS.md" -Raw', + status: "inProgress", + }); + + assert.equal( + text(r, "Canonical rules are loaded. Next I'll verify the current git state.\n").internalRequests.length, + 0, + ); + started(r, { + id: "read-2", + type: "commandExecution", + command: "git status --short --branch", + status: "inProgress", + }); + + const tripped = text( + r, + "GitHub Delivery owns this stack, so I'll load the stacked-PR workflow next.\n", + ); + assert.equal(tripped.internalRequests.length, 1); + assert.equal(tripped.internalRequests[0].method, "turn/interrupt"); +}); + test("repeated malformed tool protocol output accelerates a tool-emission stall", () => { const r = router({ generatedCharHardLimit: 10_000, toolEmissionIntentThreshold: 50 }); assert.equal(text(r, "\n").internalRequests.length, 0); @@ -157,5 +194,4 @@ test("unique generated text is bounded by characters even without token telemetr assert.equal(text(r, "A completely novel sentence about one investigation path.\n").internalRequests.length, 0); const tripped = text(r, "Another unrelated sentence keeps growing without any runtime progress at all.\n"); assert.equal(tripped.internalRequests.length, 1); - assert.equal(tripped.internalRequests[0].method, "turn/interrupt"); -}); +}); \ No newline at end of file From e2cb20b24830c95a4a726581204a00be513e0eca Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:09:11 +0200 Subject: [PATCH 34/49] fix: interrupt repetitive evidence micro-narration --- scripts/lib/codex-progress-watchdog.mjs | 61 +++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/scripts/lib/codex-progress-watchdog.mjs b/scripts/lib/codex-progress-watchdog.mjs index d1647f23..540f6a32 100644 --- a/scripts/lib/codex-progress-watchdog.mjs +++ b/scripts/lib/codex-progress-watchdog.mjs @@ -28,6 +28,9 @@ const FINALIZATION_WATCHDOG_OPTIONS = Object.freeze({ noProgressTokenHardLimit: 16_000, }); +const MICRO_NARRATION_INTENT_THRESHOLD = 3; +const MICRO_NARRATION_INTENT = /(?:^|\b(?:so|and|then|next)[,:]?\s+)(?:next\s+)?(?:let me|i(?:'|’)ll|i will|i need to|i(?:'|’)m going to|i am going to)\s+(?:(?:start(?:ing)?(?:\s+by)?|now|next|then|first|just)\s+)*(?:load|read|verify|check|inspect|fetch|recapture|lock|run|execute|invoke|call|search|open|use|apply|patch|edit|write|update|fix|change)\b/i; + export function isCodexGeneratedTextMethod(method) { return GENERATED_TEXT_METHODS.has(String(method || "")); } @@ -86,6 +89,46 @@ function activeTextWatchdog(watchdog, context) { return context.finalizing ? finalizationWatchdog(context) : watchdog; } +function resetMicroNarration(context) { + context.microNarrationBuffer = ""; + context.microNarrationIntentCount = 0; +} + +function observeMicroNarration(delta, context) { + if (typeof delta !== "string" || delta.length === 0) return { action: "allow" }; + const current = `${context.microNarrationBuffer || ""}${delta}`; + let lastBoundary = -1; + for (let index = 0; index < current.length; index += 1) { + if (/[\n.!?]/.test(current[index])) lastBoundary = index; + } + if (lastBoundary < 0) { + context.microNarrationBuffer = current.length > 2_000 ? current.slice(-2_000) : current; + return { action: "allow" }; + } + + const complete = current.slice(0, lastBoundary + 1); + context.microNarrationBuffer = current.slice(lastBoundary + 1); + for (const clause of complete + .split(/\n+/) + .flatMap((line) => line.split(/(?<=[.!?])\s+/)) + .map((line) => line.trim()) + .filter(Boolean)) { + if (clause.length > 320 || !MICRO_NARRATION_INTENT.test(clause)) continue; + context.microNarrationIntentCount = Number(context.microNarrationIntentCount || 0) + 1; + if (context.microNarrationIntentCount >= MICRO_NARRATION_INTENT_THRESHOLD) { + return { + action: "interrupt", + reason: "micro_narration_budget_exhausted", + details: { + microNarrationIntentCount: context.microNarrationIntentCount, + hardLimit: MICRO_NARRATION_INTENT_THRESHOLD, + }, + }; + } + } + return { action: "allow" }; +} + export function observeCodexAppServerMessage(watchdog, message, context = {}) { if (!watchdog || typeof watchdog.observeAssistantDelta !== "function") { throw new Error("watchdog is required"); @@ -95,6 +138,12 @@ export function observeCodexAppServerMessage(watchdog, message, context = {}) { const { method, params = {} } = message; if (isCodexGeneratedTextMethod(method)) { + if (!context.finalizing) { + const narrationDecision = observeMicroNarration(params.delta || "", context); + if (narrationDecision.action === "interrupt") { + return maybeInterrupt(narrationDecision, params, context); + } + } const decision = activeTextWatchdog(watchdog, context).observeAssistantDelta(params.delta || ""); return maybeInterrupt(decision, params, context); } @@ -107,16 +156,19 @@ export function observeCodexAppServerMessage(watchdog, message, context = {}) { } if (method === "turn/diff/updated") { - watchdog.observeDiffProgress(params.diff || ""); + const progress = watchdog.observeDiffProgress(params.diff || ""); + if (progress?.progressed) resetMicroNarration(context); return { decision: { action: "allow" } }; } if (method === "turn/plan/updated") { - watchdog.observePlanProgress(params.plan || []); + const progress = watchdog.observePlanProgress(params.plan || []); + if (progress?.progressed) resetMicroNarration(context); const complete = planIsComplete(params.plan); if (complete && !context.finalizing) { context.finalizing = true; context.finalizationWatchdog = createProgressWatchdog(FINALIZATION_WATCHDOG_OPTIONS); + resetMicroNarration(context); } else if (!complete && context.finalizing) { context.finalizing = false; context.finalizationWatchdog = null; @@ -152,8 +204,10 @@ export function observeCodexAppServerMessage(watchdog, message, context = {}) { if (isSuccessfulAppServerItem(item)) { if (classification.kind === "state-change" && item?.type !== "fileChange") { watchdog.recordStateProgress("codex_state_change_completed"); + resetMicroNarration(context); } else if (classification.kind === "execution") { watchdog.recordExecutionProgress({ kind: "codex_execution_completed" }); + resetMicroNarration(context); } } return { decision: { action: "allow" } }; @@ -163,7 +217,8 @@ export function observeCodexAppServerMessage(watchdog, message, context = {}) { context.interruptedTurns.delete(params.turn.id); context.finalizing = false; context.finalizationWatchdog = null; + resetMicroNarration(context); } return { decision: { action: "allow" } }; -} +} \ No newline at end of file From 3b571e81fe43d5e81b5af5646cff665e3148bab1 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:09:34 +0200 Subject: [PATCH 35/49] docs: make quiet execution contract explicit --- references/policy-kernel.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/references/policy-kernel.md b/references/policy-kernel.md index 922e316d..245c5a38 100644 --- a/references/policy-kernel.md +++ b/references/policy-kernel.md @@ -42,6 +42,8 @@ On unchanged relevant code and state, reuse valid passing evidence. Do not run a Execute deterministic tool calls without narrating each one. User-facing progress updates are for phase changes, material new evidence or plan changes, blockers, or needed user input; do not precede each test/read/write with micro-narration such as “let me run” or “I’ll run”. +Rule/skill/workflow loading, file reads, Git/GitHub snapshots, remote fetches, obvious command retries, and shell-quoting corrections are not user-facing progress by themselves. Perform them directly. Do not emit status messages whose only new information is “loaded X”, “next I’ll verify Y”, “I’ll lock/fetch/recapture Z”, or that an obvious retry will now be attempted. If a failed tool call has a deterministic correction and does not change scope or create a blocker, correct it and retry quietly; surface the failure only when it materially changes the plan, evidence, authority, or user action required. + ### GD-CORE-010 — Minimise evidence acquisition and context Prefer the highest-level authoritative helper or aggregate read that can decide the current step. Reuse one valid state snapshot while relevant state is unchanged. Do not delegate deterministic script or gate interpretation to a subagent. Escalate evidence progressively: decision/status, failing component, focused excerpt, then full raw output only when required. From 068d52418b5a6dcdc97fa43411651d7682ef014a Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:10:10 +0200 Subject: [PATCH 36/49] docs: keep routine tool execution quiet --- SKILL.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/SKILL.md b/SKILL.md index 51bf3b59..6dfd1dc3 100644 --- a/SKILL.md +++ b/SKILL.md @@ -106,6 +106,21 @@ and resume. Only phase/state/blocker/required-evidence/execution change is progress. Conditional policy extends unchanged context. The controller grants no GitHub write authority. +## Quiet execution contract + +Routine deterministic tooling is quiet by default. Do not spend assistant output +announcing that you are loading canonical rules, this skill, a selected workflow, +or another reference; reading files; checking Git/GitHub state; locking a +worktree; fetching remotes; correcting obvious shell quoting; or attempting the +next deterministic read/test. Execute those steps directly. + +User-facing progress updates are reserved for a phase transition, material new +evidence or plan change, a real blocker, a state-changing action worth reporting, +or user input that is actually required. A successful evidence/read tool does +not by itself justify “loaded X” / “next I’ll verify Y” narration. When a tool +call fails but the correction is deterministic and does not change scope or the +plan, correct it and retry quietly. See `GD-CORE-008` through `GD-CORE-010`. + ## Mandatory entrypoint behavior - **Default mutation mode is read-only.** Available profiles are `read-only`, From 81d096eca8d9b6ea7288ceee27987c45d89f4f2e Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:10:25 +0200 Subject: [PATCH 37/49] test: pin quiet execution guidance --- tests/unit/forward-progress-policy.test.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/forward-progress-policy.test.mjs b/tests/unit/forward-progress-policy.test.mjs index c61f2b12..b2cd1864 100644 --- a/tests/unit/forward-progress-policy.test.mjs +++ b/tests/unit/forward-progress-policy.test.mjs @@ -41,6 +41,11 @@ test("verification economy reuses passing evidence on unchanged code and state", test("deterministic tool calls are executed without micro-narration", () => { assert.match(kernel, /do not narrate.*tool calls|execute.*without.*narrat/i); assert.match(kernel, /phase change|material.*change|user input/i); + assert.match(kernel, /rule\/skill\/workflow loading|file reads|remote fetches/i); + assert.match(kernel, /retry quietly|correct.*quietly/i); + assert.match(skill, /quiet execution contract/i); + assert.match(skill, /loading canonical rules|checking Git\/GitHub state/i); + assert.match(skill, /retry quietly|correct it and retry quietly/i); }); test("context economy minimises evidence acquisition and model-facing output", () => { From 1dca04b9e61265e517c60643b643fc60d113a817 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:13:07 +0200 Subject: [PATCH 38/49] docs: roll audit fixes into v0.8.7 --- CHANGELOG.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5484c11..6dac4aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,23 +4,15 @@ All notable changes to `github-delivery` are documented here. ## [Unreleased] -### Changed - -- Windows Authority CI and C# CodeQL now run on every pull request so candidate code cannot scope out its own security-critical lanes; the remaining Node 22 compatibility selector is executed from the pull request base version (PR #299). -- Review briefs now apply a global model-facing diff-hunk budget while preserving complete structured review scope for deterministic tooling and on-demand inspection (PR #299). - -### Fixed - -- Redeem trusted authority before the first mutating GitHub command, including autonomous idempotency tag/ref coordination, so a rejected grant cannot leave coordination state behind before the requested mutation (PR #299). -- Preserve rename/copy source and destination paths with NUL-delimited local branch diff parsing, keep both path generations in review classification, and make every deterministic required probe a first-class pre-open blocker until its canonical structured probe evidence validates against the deterministic trigger files (PR #299). -- Enforce open stack-parent ordering at the mutation execution boundary before merge authority, and abort orphan-workflow cleanup before deletion when the default-branch generation changed during preflight (PR #299). - ## [0.8.7] - 2026-08-19 ### Changed - Bumped the package version from `0.8.6` to `0.8.7`. - Restricted temporary Windows Authority branch leases to repeated `push_code` batches and tightened classic branch-protection, routing, mutation-boundary, security-policy, and stack portability contracts (PR #297). +- Windows Authority CI and C# CodeQL now run on every pull request so candidate code cannot scope out its own security-critical lanes; the remaining Node 22 compatibility selector is executed from the pull request base version (PR #299). +- Review briefs now apply a global model-facing diff-hunk budget while preserving complete structured review scope for deterministic tooling and on-demand inspection (PR #299). +- Made quiet execution an entrypoint-visible contract: routine rule/skill/workflow loading, file reads, Git/GitHub snapshots, remote fetches, obvious retries, and shell-quoting corrections run without per-tool user-facing narration unless they materially change the plan or expose a blocker (PR #299). ### Fixed @@ -30,6 +22,10 @@ All notable changes to `github-delivery` are documented here. - Bound GitHub, Git, PowerShell, and registry subprocess helpers now copy argv and force a direct spawn (`shell: false`), so library-provided arguments cannot be reconstructed as a shell command (PR #294). - Prevented unusual valid Git filenames from hiding sensitive path changes from scoped CI/CodeQL detection, and made required scoped lanes fail closed when their scope producer fails (PR #296). - Fail closed when classic branch protection may apply but cannot be proved absent, added GitHub-style classic branch-pattern coverage, restored contextual `make this green` routing, and strengthened dynamic mutation-command boundary checks (PR #297). +- Redeem trusted authority before the first mutating GitHub command, including autonomous idempotency tag/ref coordination, so a rejected grant cannot leave coordination state behind before the requested mutation (PR #299). +- Preserve rename/copy source and destination paths with NUL-delimited local branch diff parsing, keep both path generations in review classification, and make every deterministic required probe a first-class pre-open blocker until its canonical structured probe evidence validates against the deterministic trigger files (PR #299). +- Enforce open stack-parent ordering at the mutation execution boundary before merge authority, and abort orphan-workflow cleanup before deletion when the default-branch generation changed during preflight (PR #299). +- Protected Codex streaming now bounds repetitive interleaved tool micro-narration separately from tool-emission stalls: three future-action narration intents without execution/state/workflow progress trigger an interrupt, and evidence/read tool starts do not reset that budget (PR #299). ## [0.8.6] - 2026-08-18 @@ -573,4 +569,4 @@ All notable changes to `github-delivery` are documented here. - Executable offline routing and retained-regression evaluations. - Deterministic versioned skill bundles with checksums, installation planning, backups, and restore. - Tag-bound GitHub Releases with checksum verification, SPDX SBOMs, and artifact attestations. -- Dependabot, Dependency Review, CodeQL, Scorecard, and executable repository workflow policy checks. +- Dependabot, Dependency Review, CodeQL, Scorecard, and executable repository workflow policy checks. \ No newline at end of file From be66541166e3a75be0c011853459840d58c6fe6c Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:13:36 +0200 Subject: [PATCH 39/49] test: pin default micro-narration budget --- tests/unit/codex-watchdog-progress-bounds.test.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/codex-watchdog-progress-bounds.test.mjs b/tests/unit/codex-watchdog-progress-bounds.test.mjs index a3cf1be9..e074120a 100644 --- a/tests/unit/codex-watchdog-progress-bounds.test.mjs +++ b/tests/unit/codex-watchdog-progress-bounds.test.mjs @@ -82,7 +82,6 @@ test("interleaved evidence tools do not buy a fresh micro-narration budget", () const r = router({ generatedCharHardLimit: 10_000, toolEmissionIntentThreshold: 50, - microNarrationIntentThreshold: 3, }); assert.equal( From 2750864bf8ee6cf41465bc910af203d3a06e6287 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:14:26 +0200 Subject: [PATCH 40/49] docs: describe micro-narration guard --- README.md | 563 +----------------------------------------------------- 1 file changed, 1 insertion(+), 562 deletions(-) diff --git a/README.md b/README.md index d550c43b..a99c8525 100644 --- a/README.md +++ b/README.md @@ -1,562 +1 @@ -
- -# github-delivery - -### GitHub delivery for agents, from intent to verified merge. - -**Say the outcome, not the orchestration.** - -`github-delivery` turns natural-language requests into evidence-backed GitHub workflows for planning, issue work, implementation, PR publication, review, CI, stacks, backports, verified merges, and release maintenance. - -[Start here](#start-here) · [Capabilities](#what-you-can-ask-it-to-own) · [How it works](#how-it-works) · [Safety](#safety-model) · [Install & update](#installation-and-maintenance) · [Watchdog](#agent-progress-watchdog) · [Workflow map](#workflow-reference) · [Development](#development-and-verification) - -[![CI](https://github.com/Wibias/github-delivery/actions/workflows/ci.yml/badge.svg)](https://github.com/Wibias/github-delivery/actions/workflows/ci.yml) -[![CodeQL](https://github.com/Wibias/github-delivery/actions/workflows/codeql.yml/badge.svg)](https://github.com/Wibias/github-delivery/actions/workflows/codeql.yml) -![Node.js 22, 24, or 26](https://img.shields.io/badge/Node.js-22%20%7C%2024%20%7C%2026-339933?logo=node.js&logoColor=white) -![Default read-only](https://img.shields.io/badge/default-read--only-2f81f7) -![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) - -
- -> [!WARNING] -> **Active development.** The complete issue/PR lifecycle and core safety architecture are implemented, but the project is not yet 100% production-ready. I currently consider it roughly **80% of the way there**. See [Current state](#current-state). - -> [!IMPORTANT] -> **Natural language is the public API.** The Node scripts, policy modules, evaluators, mutation broker, and optional Authority host are internal safety/evidence machinery. You normally do not invoke them yourself. - -

- github-delivery natural-language workflow demo -

- -## Start here - -### Install - -Requirements: - -- **Node.js 22, 24, or 26** -- Git -- GitHub network access -- an authenticated GitHub CLI (`gh auth login`) for `npx` install/update release verification - -Recommended zero-clone setup: - -```bash -npx github-delivery -``` - -The npm package is a thin bootstrap. It verifies and installs the separately published stable GitHub Release payload; npm is not a second authoritative skill payload source. - -Then speak naturally: - -```text -what do I have open in this repo? -work on ENG-42 and open a PR -triage the competing PRs in this repo -full review PR #42 -full review PR #42 and simplify it safely -fix the review comments on PR #18 and make it merge ready -backport PR #42 to release/1.x and release/2.x -merge PR #32 -``` - -That is the interface. - -`github-delivery` selects the workflow, gathers fresh repository evidence, applies the relevant review/policy gates, performs only the writes authorized by the request, and verifies the resulting state. - -A status question stays read-only. A request to implement something does not silently grant publication or merge authority. A merge happens only from current explicit merge intent; deferred permission such as `merge PR #42 only after I confirm again` is not current merge authority. - -For installation edge cases, backup/restore, downgrade behavior, manual recovery, and release verification details, see [`INSTALL.md`](INSTALL.md). - ---- - -## What you can ask it to own - -| Area | Example request | What GitHub Delivery owns | -|---|---|---| -| **Plan & triage** | `create a PRD for the onboarding flow` | PRDs, issue breakdown, QA intake, triage, agent briefs, refactor planning | -| **Open work** | `what do I have open in this repo?` | Read-only repository-scoped view of your open PRs, work-item references, and bounded next actions | -| **Issue research** | `research issue #90 on the latest development branch` | Evidence-backed research against the current development tip | -| **Implement & publish** | `create a PR for issue #90` | A bounded **research → implementation → pre-open review** sequence, minimal complete implementation, exact publication identity, linked PR | -| **External work items** | `work on ENG-42 and open a PR` | Tracker-aware delivery orchestration, covering-PR reuse, evidence-driven milestone reconciliation | -| **Review & fix** | `full review PR #42` | Bug + Security + Spec + Standards review, required probes, current-head verdict | -| **Merge readiness** | `fix the review comments on PR #18 and make it merge ready` | Feedback triage, code fixes, validation, publication, refreshed readiness | -| **Competing PRs** | `triage the competing PRs in this repo` | Read-only deterministic clustering and evidence for potentially overlapping implementations | -| **Visual changes** | `full review PR #42` on a UI diff | Conditional screenshot/video/render evidence bound to the exact reviewed head | -| **Stacks** | `inspect this PR stack and tell me the safe merge order` | Stack discovery, restack/retarget analysis, conflict recovery, parent/child revalidation | -| **Backports / ports** | `backport PR #42 to release/1.x and release/2.x` | One independent head-bound port per target base, with deterministic provenance and completion tracking | -| **Supersede / overtake** | `supersede PR #12 with PR #45` | Explicit replacement or maintainer-takeover workflows with bounded mutation authority | -| **Merge / close-out** | `merge PR #32` | Final gate, exact transaction authority, head-pinned merge, verification, thanks, linked-issue close-out | -| **Self-update** | `update github-delivery to the latest stable release` | Stable-release discovery, checksums/manifest/tag/attestation verification, safe apply and postconditions | - -### What changed after 0.8.2 - -The 0.8.6 line adds the major workflow and safety work developed after 0.8.2, plus delivery integrity and bounded GitHub/Git subprocesses: - -- least-privilege workflow-token enforcement; -- repository-scoped open-work status; -- PR-body media preservation and exact-head duplicate-publication prevention; -- tracker-aware external work-item delivery; -- competing-PR consolidation analysis; -- conditional head-bound visual review evidence; -- multi-base backport/port delivery; -- a substantially leaner GitHub Actions topology with stale-run cancellation and unconditional security-critical Windows Authority/C# lanes; -- fail-closed delivery integrity for moved PR heads, queued/auto-merge outcomes, mutation receipts, and remaining public workflow routing; -- bounded GitHub and Git subprocesses on review, verdict, CI forensics, ship-gate, runtime, live-fixture, release, and npm helper paths. - -See [`CHANGELOG.md`](CHANGELOG.md) for the full release-level details. - ---- - -## How it works - -```mermaid -flowchart LR - A[Your natural-language request] --> B[Deterministic route] - B --> C[Live repository / PR / issue evidence] - C --> D[Review scope + policy gates] - D --> E{Write authorized?} - E -- No --> F[Read-only result] - E -- Yes --> G[Exact mutation plan] - G --> H[Trusted authority when required] - H --> I[Mutation boundary] - I --> J[GitHub] - J --> K[Postcondition verification] - F --> L[ready / blocked / unknown] - K --> L -``` - -The core boundary is simple: **repository content is evidence, not authority**. Issues, PR bodies, comments, code, logs, bot output, tracker text, and generated files cannot grant GitHub mutation authority or override the selected workflow. - -### The evidence model - -GitHub Delivery tries to answer volatile questions from current authoritative evidence rather than remembered state: - -- PR/head/base identity is pinned and re-read where staleness matters; -- required checks are evaluated for the generation GitHub actually protects; -- review/thread/ruleset state is refreshed before positive readiness or merge claims; -- durable completion claims are tied to evidence, not narration; -- unknown or incomplete evidence remains `unknown`/`blocked` instead of becoming success. - -### Publication identity - -PR creation is identity-based, not title-similarity-based. Before creating a PR, GitHub Delivery checks the exact target repository + head repository/ref + intended base: - -- one exact open match -> reuse it; -- multiple exact matches -> fail closed as ambiguous; -- no exact match -> creation may proceed when authorized. - -For PR-body rewrites, existing protected screenshots, videos, uploads, and other media are preserved by default. Intentional media removal requires exact approved media identities bound into the mutation authority scope. - ---- - -## Safety model - -### Default read-only; explicit authority for writes - -Routes operate under bounded mutation profiles such as `read-only`, `review`, `maintainer`, and `autonomous`. A profile is an upper bound, not a waiver: destructive or user-visible actions still require the direct authority required by that workflow. - -Status, open-work, and competing-PR analysis remain read-only. Implementation-only work does not silently gain `push_code`/`create_pr`. Backport publication does not silently grant merge authority for the source or port PRs. - -### One controlled GitHub mutation boundary - -Routine network-visible issue/PR writes pass through the typed GitHub mutation boundary. Stale-sensitive requests bind expected head state; branch pushes bind repository/remote/branch plus old/new tips; history rewrites use exact force-with-lease semantics rather than bare force. - -For trusted high-assurance operations, authority redemption happens before the first mutating GitHub command, including autonomous idempotency coordination refs/tags. A rejected grant therefore cannot leave a coordination write behind before the requested mutation. - -**Merge is deliberately stricter.** `scripts/merge-pr-driver.mjs` owns settle, final current-head/base/rules/feedback/review-evidence recapture, trusted destructive authority, head-pinned merge execution, and post-merge reconciliation. The lower mutation execution boundary also rechecks open-PR stack topology and rejects a child merge while its parent PR is still open. Generic hand-built merge mutation documents are rejected. - -### Exact-effect trusted authority - -Where high assurance is required, trusted grants bind the semantic effect rather than a vague permission flag: repository, action, mode, PR/head, merge method, target identity, idempotency data, and hashes of human-visible text as applicable. - -The optional Windows Authority host can issue those grants through Windows Hello. Missing persistent user configuration defaults the effective preference to **Sensitive actions** (`high-assurance`); an explicitly stored `off` or `all` preference remains supported. - -### Safe retries and idempotency - -Durable creates/social writes use authenticated exact-effect receipts and read-before-write checks. A hidden marker alone is not proof of ownership or successful prior execution. - -Only proven read-only GitHub operations may use bounded rate-limit retry behavior. Ambiguous writes are never blindly retried. An uncertain merge outcome is reconciled through read-only exact-head state instead of issuing a second merge. - -### Ownership and foreign PRs - -Code pushes, base updates, simplification, and other branch mutations require the ownership/maintainer authority declared by the selected workflow. Foreign PRs receive owner instructions unless the user explicitly enters a maintainer-overtake path. - -### Safety model references - -The implementation-level contracts live in: - -- [`references/policy-kernel.md`](references/policy-kernel.md) -- [`references/shared-rules.md`](references/shared-rules.md) -- [`references/github-mutation-broker.md`](references/github-mutation-broker.md) -- [`references/merge-pr.md`](references/merge-pr.md) -- [`references/completion-claims.md`](references/completion-claims.md) - ---- - -## Review and merge readiness - -"Green CI" is necessary when required, but it is not the whole review bar. - -A full review can combine: - -- **Bug** review; -- **Security** review; -- **Spec** review; -- **Standards** review, including design-quality and typed-code evidence lenses when relevant; -- semantic propagation across related producers/consumers/public forms; -- deterministic required probes derived from the diff; -- proactive contract verification appropriate to the changed behavior; -- conditional **visual evidence** for rendered/UI surfaces. - -The pre-open gate treats those deterministic probes as first-class obligations alongside required bug lenses and security surfaces. A probe detected from the branch diff remains blocking until its canonical structured probe-evidence record validates against the deterministic trigger files. Local branch review uses NUL-delimited Git records so renames and unusual valid paths retain both source and destination identity. - -### Safe simplification - -Simplification is **explicit-only**. Its goal is lower cognitive load and safer maintenance. **Line count is never the goal**; fewer lines are acceptable only when behavior and clarity improve. - -A simplification pass may validly conclude that there is **nothing worth simplifying**. Any proposed mutation still requires **explicit approval**. After approved candidates are applied and validated, GitHub Delivery automatically runs the **complete full review** again on the changed head with simplification disabled before publishing the final verdict. See [`references/simplify-pr.md`](references/simplify-pr.md). - -Security-sensitive findings follow [`SECURITY.md`](SECURITY.md). Undisclosed vulnerabilities belong in **private vulnerability reporting**, not a public issue or review thread. - -Visual evidence is required only when the diff actually carries a visual-surface signal. Accepted evidence is screenshot/video/deterministic render material bound to the exact current head SHA. Stale artifacts and text-only claims do not satisfy that axis; real preview/runtime blockers stay `blocked`. - -The final ship decision is one authoritative `ready`, `blocked`, or `unknown` result from live evidence. Positive readiness/merge claims require a fresh final gate. - -### Merge semantics GitHub Delivery models explicitly - -- current required-check generation and producer identity; -- active required-status-check rules and strictness; -- review decision, stale approvals, last-push requirements, unresolved threads; -- conflicts, behind state, merge queue / auto-merge state; -- unknown ruleset/state values failing closed; -- open stack-parent topology before destructive merge execution; -- exact-head merge execution and read-only reconciliation after ambiguous write results; -- partial success when merge succeeded but non-destructive post-merge ceremony did not. - ---- - -## Stacks, competing PRs, and backports - -These are intentionally three different concepts. - -### Stacked PRs - -A stack is a dependency chain where a child PR targets a parent PR branch. Stack operations discover repository-qualified topology, restack bottom-up, preserve layer ownership, and revalidate every surviving child after an upstream head changes. The mutation execution boundary independently rejects a merge while the target PR still points at another open PR's head, so merge-order safety does not depend only on workflow prose. - -### Competing PRs - -Competing-PR analysis is read-only. A shared work-item key establishes related work, not automatic replacement. Supersede-grade planning requires direct substantial implementation overlap between the selected canonical PR and every PR proposed for replacement; transitive A-B-C clustering cannot let A supersede C without direct evidence. - -### Backports / multi-base delivery - -Ports are **parallel**, not stacked. Each target base gets an independent branch/PR bound to: - -- repository; -- source PR; -- exact source head SHA; -- exact target base; -- deterministic provenance marker. - -Wrong-base provenance, multiple port markers, duplicate port PRs, invalid refs, or incomplete required targets fail closed. Merge authority remains separate for every port. - ---- - -## Installation and maintenance - -### Guided install - -```bash -npx github-delivery -``` - -Bare invocation runs environment preflight, detects valid installations, verifies the stable GitHub Release, shows the plan, and asks before skill-target mutation. Confirmation defaults to **No**. - -Useful explicit commands: - -```bash -npx github-delivery install -npx github-delivery setup -npx github-delivery start -npx github-delivery autostart -npx github-delivery autostart on -npx github-delivery autostart off -npx github-delivery autostart status -npx github-delivery doctor -npx github-delivery doctor --json -npx github-delivery update -npx github-delivery update --apply -``` - -### Update - -Check/verify/plan only: - -```bash -npx github-delivery update -``` - -Apply the verified plan: - -```bash -npx github-delivery update --apply -``` - -Self-update accepts only the fixed upstream's latest stable `vX.Y.Z` GitHub Release and replaces nothing until release assets, checksums, distribution manifest, tag/source binding, constrained GitHub artifact attestation, and bounded ZIP extraction verify. Local tracked modifications block replacement even with `--force`; update does not silently downgrade an ahead install. - -### Setup and doctor - -```bash -npx github-delivery setup -npx github-delivery doctor -``` - -`setup` repairs/finishes activation against an existing managed installation. `doctor` is read-only and summarizes environment, installed version/integrity, persistent configuration, watchdog activation, stable-update relation, and Windows Authority state. Use `doctor --json` for machine-readable output. - -### Windows Authority - -On supported Windows systems, the stable GitHub Release can include the separately verified self-contained Authority host. Guided setup/update can install or repair it without a local .NET SDK when required or already configured. - -`npx github-delivery start` ensures the host is running and brings the Control Center into view. Login auto-start is opt-in and shared between the CLI and Control Center setting. Normal window close leaves Authority in the tray; tray right-click -> `Exit` shuts it down completely. - -The host is not silently installed for a user whose protection mode is `off` and who has never installed Authority. - -### Manual / repository install - -```bash -git clone https://github.com/Wibias/github-delivery.git -cd github-delivery -npm run build:dist -node scripts/install-skill.mjs -node scripts/install-skill.mjs --apply -``` - -Typical skill locations: - -```text -~/.agents/skills/github-delivery -~/.cursor/skills/github-delivery -~/.codex/skills/github-delivery -~/.claude/skills/github-delivery -``` - -A same-version byte-identical normal reinstall is an unchanged no-op. Same-version payload drift remains fail-closed, including with `--force`. - -Full installation and recovery behavior is documented in [`INSTALL.md`](INSTALL.md). - ---- - -## Agent progress watchdog - -GitHub Delivery treats convergence as a runtime + workflow problem rather than a prompt-only rule. The watchdog is defence in depth around execution; **it never grants GitHub mutation authority**. - -| Enforcement level | Purpose | -|---|---| -| **Policy** | Universal bounded-progress/evidence-economy fallback when the host exposes no trusted interception surface | -| **Codex lifecycle hooks** | Turn-scoped duplicate/poll/evidence limits and bounded narration recovery at supported tool boundaries | -| **Protected Codex stream** | Launch-controlled App Server stream that can interrupt in-flight no-progress/tool-emission/protocol stalls | -| **Workflow controller** | Route/phase locking, checkpointed progress, bounded retries/evidence/actions/tokens/steps/wall time | - -Key defaults include: - -- evidence warning/block at **8 / 12** consecutive attempts without execution/state progress; -- protected-stream active-work warning/hard bounds of **4k / 8k generated characters** and **1,024 / 2,048 generated output tokens** since real progress; -- larger completed-plan finalization allowance of **40k / 64k characters** and **12k / 16k output tokens**; -- bounded lifecycle-hook narration recovery with up to **three** corrective continuations by default; -- **6,000 serialized characters** as the default Codex hook subagent-input budget; -- controller no-progress escalation at **2 / 3 / 4 cycles**, with bounded phase/workflow retry, evidence, token, step, and wall-time budgets. - -A configured hook is not automatically trusted/active. Codex ties trust to the exact non-managed hook definition; GitHub Delivery reports `hook_trust_required` instead of claiming protection that has not been verified. - -Runtime capability reporting distinguishes: - -- `Full (STREAM)` — controlled in-flight stream interruption; -- `Partial (HOOKS)` — supported lifecycle/tool-boundary protection; -- `Off (NONE)` — no verified interception boundary. - -For the complete budgets, trust model, incident replays, false-positive controls, and host integration, see [`references/agent-progress-watchdog.md`](references/agent-progress-watchdog.md). - ---- - -## Workflow reference - -| Area | Requests | Workflow / method | -|---|---|---| -| **Product / issue intake** | PRDs, breakdowns, triage, QA intake, refactor plans | `references/issue-workflows.md` | -| **Agent-ready work** | Create/update a `ready-for-agent` contract | `references/agent-brief.md` | -| **Rejected scope** | Record/reconsider/remove an out-of-scope decision | `references/out-of-scope.md` | -| **Issue research** | Research an issue on the latest development tip | `references/research-issue.md` | -| **Create local-work PR** | Publish already-existing local work | `references/create-pr-from-local-work.md` | -| **Create linked PR** | Bounded research -> implementation -> pre-open review -> PR | `references/create-pr-for-issue.md` | -| **Open work** | Repository-scoped authored-open-PR overview | `references/open-work-status.md` | -| **External work item** | Inspect/deliver `ENG-42`-style tracker work | `references/work-item-delivery.md` | -| **Competing PRs** | Analyze overlapping/duplicate implementations | `references/consolidate-prs.md` | -| **Status** | What is left / why blocked / merge readiness | `references/status.md` | -| **Make merge-ready** | Fix humans/bots, own review work, validate | `references/fix-pr-bots.md` | -| **Watch** | Poll CI/reviews/gates until merged/closed/blocked | `references/watch-pr.md` | -| **Re-review** | Re-evaluate after head/review evidence changes | `references/re-review-pr.md` | -| **Full review** | Deep Bug + Security + Spec + Standards review | `references/full-review-pr.md` | -| **Visual evidence** | Conditional rendered-surface evidence axis | `references/visual-evidence.md` | -| **Bug review** | Evidence-ranked adversarial bug hunt | `references/bug-review.md` + `references/bug-hunt-method.md` | -| **Security review** | Security surfaces, escalation chains, safe reporting | `references/security-review.md` | -| **Spec / standards** | Contract, requirements, standards, docs/non-goals | `references/spec-standards-review.md` | -| **Design quality** | Advisory design/abstraction/state/seam review | `references/design-quality.md` | -| **Type evidence** | Typed-code evidence erosion / anti-slop review | `references/type-evidence-review.md` | -| **Minimal solution** | Lowest-complexity complete implementation choice | `references/minimal-solution.md` | -| **Verification boundaries** | Stable regression/refactor evidence boundary | `references/verification-boundaries.md` | -| **Change execution** | Safe migrations, mechanical sweeps, expand-contract | `references/change-execution.md` | -| **Completion evidence** | Prove durable completion/count/coverage claims | `references/completion-claims.md` | -| **Safe simplification** | Behavior-preserving cleanup + mandatory re-review | `references/simplify-pr.md` | -| **Prepare + merge** | Compound review/fix/simplify request with explicit merge | `references/prepare-and-merge-pr.md` | -| **Merge** | Settle, final live gate, exact head-pinned merge | `references/merge-pr.md` | -| **Supersede** | Replace an obsolete PR with a canonical PR | `references/supersede-pr.md` | -| **Maintainer overtake** | Take over an unresponsive author's PR | `references/overtake-pr.md` | -| **Conflicts** | Resolve active conflicts from both sides' intent/evidence | `references/resolve-conflicts.md` | -| **Stacked PRs** | Inspect/restack/retarget/recover/review/merge stacks | `references/stacked-prs.md` | -| **Backports / ports** | Parallel delivery to one or more target bases | `references/multi-base-delivery.md` | -| **Update installed skill** | Verify/check/apply latest stable release | `references/update.md` | -| **Progress watchdog** | Runtime generation bounds and workflow convergence | `references/agent-progress-watchdog.md` | - -### More natural-language examples - -```text -create a PRD for the onboarding flow -break the roadmap into implementation issues -triage the open issues in this repo -show me what needs triage in this repo -what do I have open in this repo? - -research issue #90 on the latest development branch -create a PR for issue #90 -research and implement issue #90 -work on ENG-42 and open a PR -what's left on ENG-42? - -what is left on PR #41? -is PR #42 safe to merge? -full review PR #42 -fix the review comments on PR #18 and make it merge ready -watch PR #77 until it merges or needs me -simplify PR #42 without changing behavior -review PR #42, fix it, and merge it when green -merge PR #32 - -triage the competing PRs in this repo -inspect this PR stack and tell me the safe merge order -backport PR #42 to release/1.x and release/2.x -supersede PR #12 with PR #45 -maintainer overtake PR #32 and finish it -update github-delivery to the latest stable release -``` - ---- - -## Development and verification - -Supported runtime contract: - -```text -Node.js 22 | 24 | 26 -``` - -Run the canonical repository gate: - -```bash -npm run check -``` - -Useful focused commands: - -```bash -npm test -npm run security:repo -npm run dist:check -npm run package:check -npm run evals:offline -npm run reliability:gate -``` - -### Lean required CI topology - -The pull-request CI topology is deliberately asymmetric to avoid repeating the full repository suite across every OS/runtime combination while keeping the security-critical platform lanes unskippable by PR scope logic: - -| Required context | PR behavior | -|---|---| -| **Node 24 / ubuntu-latest** | Canonical full `npm run check`; then bounded Node 26 syntax/package/unit compatibility on the same workspace | -| **Node 22 / ubuntu-latest** | Bounded compatibility lane only when runtime-relevant paths change; its path classifier is executed from the PR base version | -| **Node 24 / windows-latest** | Always runs Windows Authority restore/build/self-test/publish/install smoke on pull requests | -| **Dependency Review** | Runs on pull requests | -| **CodeQL / Analyze (javascript-typescript)** | Runs on pull requests | -| **CodeQL / Analyze (csharp)** | Always runs on pull requests, plus `main` and schedules | - -There are no macOS PR compatibility lanes and no duplicate Architecture Contracts workflow. Superseded CI, CodeQL, and Dependency Review runs are cancelled when a newer commit arrives. Repository-policy verification is daily and orphan-workflow cleanup is weekly; cleanup pins the default-branch generation before deleting stale workflow histories. - -For ordinary runtime-relevant PRs this keeps full `npm run check` executions at **1**, full unit-suite runtime executions at **3**, one Windows Authority lane on every PR, and **0** macOS PR jobs while retaining Node 22/24/26 compatibility coverage and unconditional Windows/C# security coverage. - -### Live lifecycle fixture - -The unit/eval suite proves deterministic contracts. An explicitly opted-in fixture repository exercises the real GitHub lifecycle with immutable repository-identity binding before the first mutation. Fixture diffs force the scoped Node 22 compatibility lane; the Windows Authority lane already runs unconditionally. - -See [`docs/live-integration.md`](docs/live-integration.md) and [`docs/live-github-integration.md`](docs/live-github-integration.md). - ---- - -## Internal architecture - -The public interface stays small even though the enforcement surface is not. Key internals: - -| Surface | Responsibility | -|---|---| -| `SKILL.md` | Host discovery and top-level natural-language capability map | -| `scripts/lib/skill-router.mjs` | Deterministic route and explicit-action selection | -| `references/policy-kernel.md` + `references/policy/*.md` | Canonical cross-workflow and focused policy contracts | -| `scripts/delivery-controller.mjs` | Persistent routed workflow state/budget controller | -| `scripts/ship-gate-snapshot.mjs` | Current GitHub evidence snapshot | -| `scripts/ship-gate.mjs` | Authoritative `ready` / `blocked` / `unknown` decision | -| `scripts/merge-pr-driver.mjs` | Canonical destructive merge boundary | -| `scripts/github-mutate.mjs` | Typed non-merge GitHub mutation entrypoint | -| `scripts/lib/authority-scope.mjs` | Exact-effect trusted authority scope | -| `authority-host/windows/` | Optional Windows Hello trusted-authority issuer | -| `scripts/review-scope.mjs` | Evidence-ranked review scope and required probes | -| `scripts/lib/visual-evidence.mjs` | Conditional head-bound rendered-evidence planning/validation | -| `scripts/lib/work-item-delivery.mjs` | Tracker milestone/reconciliation planning | -| `scripts/lib/pr-consolidation.mjs` | Read-only competing-PR clustering/planning evidence | -| `scripts/lib/multi-base-delivery.mjs` | Parallel port identities/provenance/completion | -| `scripts/lib/agent-progress-watchdog.mjs` | Shared progress/evidence/tool-emission watchdog logic | -| `scripts/build-dist.mjs` | Deterministic versioned skill bundle build | -| `scripts/prepare-release.mjs` | Release identity/checksum/SBOM/provenance preparation | - -The architecture uses progressive disclosure: route once, load the selected workflow plus required policy modules, and escalate diagnostics only when needed rather than dumping the full rule set into every agent turn. - ---- - -## Current state - -Implemented today: - -- natural-language routing for the issue/PR lifecycle; -- read-only open-work and competing-PR analysis; -- issue research, implementation, publication, external work-item delivery, and exact-head duplicate prevention; -- deep current-head review with deterministic probe coverage and conditional visual evidence; -- mutation authority, exact-effect receipts, stale-head protection, and head-pinned merge execution; -- stack restacking/merge-order safety and independent multi-base delivery; -- verified stable install/update and optional Windows Authority host; -- progress watchdog/runtime convergence controls; -- deterministic bundles, repository security checks, CodeQL, Dependency Review, live-fixture contracts, and release preparation. - -Still active-development territory: - -- host/runtime integrations remain constrained by what each agent host exposes; -- the protected Codex App Server streaming boundary depends on an experimental upstream interface; -- broader tracker adapters beyond the normalized work-item contract can be added without weakening GitHub authority boundaries; -- more real-world fixture coverage and adversarial incident replays are still valuable as the system expands. - -The project intentionally fails closed rather than claiming unsupported coverage. - ---- - -## Provenance and license - -Some workflow directions were informed by public/open-source agent skills and GitHub automation patterns, including concepts from `OutThisLife/brooklyn-skills`. Adapted ideas are rewritten around GitHub Delivery's own evidence, authority, routing, and lifecycle contracts; relevant workflow files include provenance notes where appropriate. - -Licensed under the [MIT License](LICENSE). \ No newline at end of file + \ No newline at end of file From 0ca0b5acca9f6bb8aeb81bcdc6193060a5ce08ab Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:15:01 +0200 Subject: [PATCH 41/49] revert: restore README after failed docs replacement --- README.md | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 562 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a99c8525..d550c43b 100644 --- a/README.md +++ b/README.md @@ -1 +1,562 @@ - \ No newline at end of file +
+ +# github-delivery + +### GitHub delivery for agents, from intent to verified merge. + +**Say the outcome, not the orchestration.** + +`github-delivery` turns natural-language requests into evidence-backed GitHub workflows for planning, issue work, implementation, PR publication, review, CI, stacks, backports, verified merges, and release maintenance. + +[Start here](#start-here) · [Capabilities](#what-you-can-ask-it-to-own) · [How it works](#how-it-works) · [Safety](#safety-model) · [Install & update](#installation-and-maintenance) · [Watchdog](#agent-progress-watchdog) · [Workflow map](#workflow-reference) · [Development](#development-and-verification) + +[![CI](https://github.com/Wibias/github-delivery/actions/workflows/ci.yml/badge.svg)](https://github.com/Wibias/github-delivery/actions/workflows/ci.yml) +[![CodeQL](https://github.com/Wibias/github-delivery/actions/workflows/codeql.yml/badge.svg)](https://github.com/Wibias/github-delivery/actions/workflows/codeql.yml) +![Node.js 22, 24, or 26](https://img.shields.io/badge/Node.js-22%20%7C%2024%20%7C%2026-339933?logo=node.js&logoColor=white) +![Default read-only](https://img.shields.io/badge/default-read--only-2f81f7) +![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) + +
+ +> [!WARNING] +> **Active development.** The complete issue/PR lifecycle and core safety architecture are implemented, but the project is not yet 100% production-ready. I currently consider it roughly **80% of the way there**. See [Current state](#current-state). + +> [!IMPORTANT] +> **Natural language is the public API.** The Node scripts, policy modules, evaluators, mutation broker, and optional Authority host are internal safety/evidence machinery. You normally do not invoke them yourself. + +

+ github-delivery natural-language workflow demo +

+ +## Start here + +### Install + +Requirements: + +- **Node.js 22, 24, or 26** +- Git +- GitHub network access +- an authenticated GitHub CLI (`gh auth login`) for `npx` install/update release verification + +Recommended zero-clone setup: + +```bash +npx github-delivery +``` + +The npm package is a thin bootstrap. It verifies and installs the separately published stable GitHub Release payload; npm is not a second authoritative skill payload source. + +Then speak naturally: + +```text +what do I have open in this repo? +work on ENG-42 and open a PR +triage the competing PRs in this repo +full review PR #42 +full review PR #42 and simplify it safely +fix the review comments on PR #18 and make it merge ready +backport PR #42 to release/1.x and release/2.x +merge PR #32 +``` + +That is the interface. + +`github-delivery` selects the workflow, gathers fresh repository evidence, applies the relevant review/policy gates, performs only the writes authorized by the request, and verifies the resulting state. + +A status question stays read-only. A request to implement something does not silently grant publication or merge authority. A merge happens only from current explicit merge intent; deferred permission such as `merge PR #42 only after I confirm again` is not current merge authority. + +For installation edge cases, backup/restore, downgrade behavior, manual recovery, and release verification details, see [`INSTALL.md`](INSTALL.md). + +--- + +## What you can ask it to own + +| Area | Example request | What GitHub Delivery owns | +|---|---|---| +| **Plan & triage** | `create a PRD for the onboarding flow` | PRDs, issue breakdown, QA intake, triage, agent briefs, refactor planning | +| **Open work** | `what do I have open in this repo?` | Read-only repository-scoped view of your open PRs, work-item references, and bounded next actions | +| **Issue research** | `research issue #90 on the latest development branch` | Evidence-backed research against the current development tip | +| **Implement & publish** | `create a PR for issue #90` | A bounded **research → implementation → pre-open review** sequence, minimal complete implementation, exact publication identity, linked PR | +| **External work items** | `work on ENG-42 and open a PR` | Tracker-aware delivery orchestration, covering-PR reuse, evidence-driven milestone reconciliation | +| **Review & fix** | `full review PR #42` | Bug + Security + Spec + Standards review, required probes, current-head verdict | +| **Merge readiness** | `fix the review comments on PR #18 and make it merge ready` | Feedback triage, code fixes, validation, publication, refreshed readiness | +| **Competing PRs** | `triage the competing PRs in this repo` | Read-only deterministic clustering and evidence for potentially overlapping implementations | +| **Visual changes** | `full review PR #42` on a UI diff | Conditional screenshot/video/render evidence bound to the exact reviewed head | +| **Stacks** | `inspect this PR stack and tell me the safe merge order` | Stack discovery, restack/retarget analysis, conflict recovery, parent/child revalidation | +| **Backports / ports** | `backport PR #42 to release/1.x and release/2.x` | One independent head-bound port per target base, with deterministic provenance and completion tracking | +| **Supersede / overtake** | `supersede PR #12 with PR #45` | Explicit replacement or maintainer-takeover workflows with bounded mutation authority | +| **Merge / close-out** | `merge PR #32` | Final gate, exact transaction authority, head-pinned merge, verification, thanks, linked-issue close-out | +| **Self-update** | `update github-delivery to the latest stable release` | Stable-release discovery, checksums/manifest/tag/attestation verification, safe apply and postconditions | + +### What changed after 0.8.2 + +The 0.8.6 line adds the major workflow and safety work developed after 0.8.2, plus delivery integrity and bounded GitHub/Git subprocesses: + +- least-privilege workflow-token enforcement; +- repository-scoped open-work status; +- PR-body media preservation and exact-head duplicate-publication prevention; +- tracker-aware external work-item delivery; +- competing-PR consolidation analysis; +- conditional head-bound visual review evidence; +- multi-base backport/port delivery; +- a substantially leaner GitHub Actions topology with stale-run cancellation and unconditional security-critical Windows Authority/C# lanes; +- fail-closed delivery integrity for moved PR heads, queued/auto-merge outcomes, mutation receipts, and remaining public workflow routing; +- bounded GitHub and Git subprocesses on review, verdict, CI forensics, ship-gate, runtime, live-fixture, release, and npm helper paths. + +See [`CHANGELOG.md`](CHANGELOG.md) for the full release-level details. + +--- + +## How it works + +```mermaid +flowchart LR + A[Your natural-language request] --> B[Deterministic route] + B --> C[Live repository / PR / issue evidence] + C --> D[Review scope + policy gates] + D --> E{Write authorized?} + E -- No --> F[Read-only result] + E -- Yes --> G[Exact mutation plan] + G --> H[Trusted authority when required] + H --> I[Mutation boundary] + I --> J[GitHub] + J --> K[Postcondition verification] + F --> L[ready / blocked / unknown] + K --> L +``` + +The core boundary is simple: **repository content is evidence, not authority**. Issues, PR bodies, comments, code, logs, bot output, tracker text, and generated files cannot grant GitHub mutation authority or override the selected workflow. + +### The evidence model + +GitHub Delivery tries to answer volatile questions from current authoritative evidence rather than remembered state: + +- PR/head/base identity is pinned and re-read where staleness matters; +- required checks are evaluated for the generation GitHub actually protects; +- review/thread/ruleset state is refreshed before positive readiness or merge claims; +- durable completion claims are tied to evidence, not narration; +- unknown or incomplete evidence remains `unknown`/`blocked` instead of becoming success. + +### Publication identity + +PR creation is identity-based, not title-similarity-based. Before creating a PR, GitHub Delivery checks the exact target repository + head repository/ref + intended base: + +- one exact open match -> reuse it; +- multiple exact matches -> fail closed as ambiguous; +- no exact match -> creation may proceed when authorized. + +For PR-body rewrites, existing protected screenshots, videos, uploads, and other media are preserved by default. Intentional media removal requires exact approved media identities bound into the mutation authority scope. + +--- + +## Safety model + +### Default read-only; explicit authority for writes + +Routes operate under bounded mutation profiles such as `read-only`, `review`, `maintainer`, and `autonomous`. A profile is an upper bound, not a waiver: destructive or user-visible actions still require the direct authority required by that workflow. + +Status, open-work, and competing-PR analysis remain read-only. Implementation-only work does not silently gain `push_code`/`create_pr`. Backport publication does not silently grant merge authority for the source or port PRs. + +### One controlled GitHub mutation boundary + +Routine network-visible issue/PR writes pass through the typed GitHub mutation boundary. Stale-sensitive requests bind expected head state; branch pushes bind repository/remote/branch plus old/new tips; history rewrites use exact force-with-lease semantics rather than bare force. + +For trusted high-assurance operations, authority redemption happens before the first mutating GitHub command, including autonomous idempotency coordination refs/tags. A rejected grant therefore cannot leave a coordination write behind before the requested mutation. + +**Merge is deliberately stricter.** `scripts/merge-pr-driver.mjs` owns settle, final current-head/base/rules/feedback/review-evidence recapture, trusted destructive authority, head-pinned merge execution, and post-merge reconciliation. The lower mutation execution boundary also rechecks open-PR stack topology and rejects a child merge while its parent PR is still open. Generic hand-built merge mutation documents are rejected. + +### Exact-effect trusted authority + +Where high assurance is required, trusted grants bind the semantic effect rather than a vague permission flag: repository, action, mode, PR/head, merge method, target identity, idempotency data, and hashes of human-visible text as applicable. + +The optional Windows Authority host can issue those grants through Windows Hello. Missing persistent user configuration defaults the effective preference to **Sensitive actions** (`high-assurance`); an explicitly stored `off` or `all` preference remains supported. + +### Safe retries and idempotency + +Durable creates/social writes use authenticated exact-effect receipts and read-before-write checks. A hidden marker alone is not proof of ownership or successful prior execution. + +Only proven read-only GitHub operations may use bounded rate-limit retry behavior. Ambiguous writes are never blindly retried. An uncertain merge outcome is reconciled through read-only exact-head state instead of issuing a second merge. + +### Ownership and foreign PRs + +Code pushes, base updates, simplification, and other branch mutations require the ownership/maintainer authority declared by the selected workflow. Foreign PRs receive owner instructions unless the user explicitly enters a maintainer-overtake path. + +### Safety model references + +The implementation-level contracts live in: + +- [`references/policy-kernel.md`](references/policy-kernel.md) +- [`references/shared-rules.md`](references/shared-rules.md) +- [`references/github-mutation-broker.md`](references/github-mutation-broker.md) +- [`references/merge-pr.md`](references/merge-pr.md) +- [`references/completion-claims.md`](references/completion-claims.md) + +--- + +## Review and merge readiness + +"Green CI" is necessary when required, but it is not the whole review bar. + +A full review can combine: + +- **Bug** review; +- **Security** review; +- **Spec** review; +- **Standards** review, including design-quality and typed-code evidence lenses when relevant; +- semantic propagation across related producers/consumers/public forms; +- deterministic required probes derived from the diff; +- proactive contract verification appropriate to the changed behavior; +- conditional **visual evidence** for rendered/UI surfaces. + +The pre-open gate treats those deterministic probes as first-class obligations alongside required bug lenses and security surfaces. A probe detected from the branch diff remains blocking until its canonical structured probe-evidence record validates against the deterministic trigger files. Local branch review uses NUL-delimited Git records so renames and unusual valid paths retain both source and destination identity. + +### Safe simplification + +Simplification is **explicit-only**. Its goal is lower cognitive load and safer maintenance. **Line count is never the goal**; fewer lines are acceptable only when behavior and clarity improve. + +A simplification pass may validly conclude that there is **nothing worth simplifying**. Any proposed mutation still requires **explicit approval**. After approved candidates are applied and validated, GitHub Delivery automatically runs the **complete full review** again on the changed head with simplification disabled before publishing the final verdict. See [`references/simplify-pr.md`](references/simplify-pr.md). + +Security-sensitive findings follow [`SECURITY.md`](SECURITY.md). Undisclosed vulnerabilities belong in **private vulnerability reporting**, not a public issue or review thread. + +Visual evidence is required only when the diff actually carries a visual-surface signal. Accepted evidence is screenshot/video/deterministic render material bound to the exact current head SHA. Stale artifacts and text-only claims do not satisfy that axis; real preview/runtime blockers stay `blocked`. + +The final ship decision is one authoritative `ready`, `blocked`, or `unknown` result from live evidence. Positive readiness/merge claims require a fresh final gate. + +### Merge semantics GitHub Delivery models explicitly + +- current required-check generation and producer identity; +- active required-status-check rules and strictness; +- review decision, stale approvals, last-push requirements, unresolved threads; +- conflicts, behind state, merge queue / auto-merge state; +- unknown ruleset/state values failing closed; +- open stack-parent topology before destructive merge execution; +- exact-head merge execution and read-only reconciliation after ambiguous write results; +- partial success when merge succeeded but non-destructive post-merge ceremony did not. + +--- + +## Stacks, competing PRs, and backports + +These are intentionally three different concepts. + +### Stacked PRs + +A stack is a dependency chain where a child PR targets a parent PR branch. Stack operations discover repository-qualified topology, restack bottom-up, preserve layer ownership, and revalidate every surviving child after an upstream head changes. The mutation execution boundary independently rejects a merge while the target PR still points at another open PR's head, so merge-order safety does not depend only on workflow prose. + +### Competing PRs + +Competing-PR analysis is read-only. A shared work-item key establishes related work, not automatic replacement. Supersede-grade planning requires direct substantial implementation overlap between the selected canonical PR and every PR proposed for replacement; transitive A-B-C clustering cannot let A supersede C without direct evidence. + +### Backports / multi-base delivery + +Ports are **parallel**, not stacked. Each target base gets an independent branch/PR bound to: + +- repository; +- source PR; +- exact source head SHA; +- exact target base; +- deterministic provenance marker. + +Wrong-base provenance, multiple port markers, duplicate port PRs, invalid refs, or incomplete required targets fail closed. Merge authority remains separate for every port. + +--- + +## Installation and maintenance + +### Guided install + +```bash +npx github-delivery +``` + +Bare invocation runs environment preflight, detects valid installations, verifies the stable GitHub Release, shows the plan, and asks before skill-target mutation. Confirmation defaults to **No**. + +Useful explicit commands: + +```bash +npx github-delivery install +npx github-delivery setup +npx github-delivery start +npx github-delivery autostart +npx github-delivery autostart on +npx github-delivery autostart off +npx github-delivery autostart status +npx github-delivery doctor +npx github-delivery doctor --json +npx github-delivery update +npx github-delivery update --apply +``` + +### Update + +Check/verify/plan only: + +```bash +npx github-delivery update +``` + +Apply the verified plan: + +```bash +npx github-delivery update --apply +``` + +Self-update accepts only the fixed upstream's latest stable `vX.Y.Z` GitHub Release and replaces nothing until release assets, checksums, distribution manifest, tag/source binding, constrained GitHub artifact attestation, and bounded ZIP extraction verify. Local tracked modifications block replacement even with `--force`; update does not silently downgrade an ahead install. + +### Setup and doctor + +```bash +npx github-delivery setup +npx github-delivery doctor +``` + +`setup` repairs/finishes activation against an existing managed installation. `doctor` is read-only and summarizes environment, installed version/integrity, persistent configuration, watchdog activation, stable-update relation, and Windows Authority state. Use `doctor --json` for machine-readable output. + +### Windows Authority + +On supported Windows systems, the stable GitHub Release can include the separately verified self-contained Authority host. Guided setup/update can install or repair it without a local .NET SDK when required or already configured. + +`npx github-delivery start` ensures the host is running and brings the Control Center into view. Login auto-start is opt-in and shared between the CLI and Control Center setting. Normal window close leaves Authority in the tray; tray right-click -> `Exit` shuts it down completely. + +The host is not silently installed for a user whose protection mode is `off` and who has never installed Authority. + +### Manual / repository install + +```bash +git clone https://github.com/Wibias/github-delivery.git +cd github-delivery +npm run build:dist +node scripts/install-skill.mjs +node scripts/install-skill.mjs --apply +``` + +Typical skill locations: + +```text +~/.agents/skills/github-delivery +~/.cursor/skills/github-delivery +~/.codex/skills/github-delivery +~/.claude/skills/github-delivery +``` + +A same-version byte-identical normal reinstall is an unchanged no-op. Same-version payload drift remains fail-closed, including with `--force`. + +Full installation and recovery behavior is documented in [`INSTALL.md`](INSTALL.md). + +--- + +## Agent progress watchdog + +GitHub Delivery treats convergence as a runtime + workflow problem rather than a prompt-only rule. The watchdog is defence in depth around execution; **it never grants GitHub mutation authority**. + +| Enforcement level | Purpose | +|---|---| +| **Policy** | Universal bounded-progress/evidence-economy fallback when the host exposes no trusted interception surface | +| **Codex lifecycle hooks** | Turn-scoped duplicate/poll/evidence limits and bounded narration recovery at supported tool boundaries | +| **Protected Codex stream** | Launch-controlled App Server stream that can interrupt in-flight no-progress/tool-emission/protocol stalls | +| **Workflow controller** | Route/phase locking, checkpointed progress, bounded retries/evidence/actions/tokens/steps/wall time | + +Key defaults include: + +- evidence warning/block at **8 / 12** consecutive attempts without execution/state progress; +- protected-stream active-work warning/hard bounds of **4k / 8k generated characters** and **1,024 / 2,048 generated output tokens** since real progress; +- larger completed-plan finalization allowance of **40k / 64k characters** and **12k / 16k output tokens**; +- bounded lifecycle-hook narration recovery with up to **three** corrective continuations by default; +- **6,000 serialized characters** as the default Codex hook subagent-input budget; +- controller no-progress escalation at **2 / 3 / 4 cycles**, with bounded phase/workflow retry, evidence, token, step, and wall-time budgets. + +A configured hook is not automatically trusted/active. Codex ties trust to the exact non-managed hook definition; GitHub Delivery reports `hook_trust_required` instead of claiming protection that has not been verified. + +Runtime capability reporting distinguishes: + +- `Full (STREAM)` — controlled in-flight stream interruption; +- `Partial (HOOKS)` — supported lifecycle/tool-boundary protection; +- `Off (NONE)` — no verified interception boundary. + +For the complete budgets, trust model, incident replays, false-positive controls, and host integration, see [`references/agent-progress-watchdog.md`](references/agent-progress-watchdog.md). + +--- + +## Workflow reference + +| Area | Requests | Workflow / method | +|---|---|---| +| **Product / issue intake** | PRDs, breakdowns, triage, QA intake, refactor plans | `references/issue-workflows.md` | +| **Agent-ready work** | Create/update a `ready-for-agent` contract | `references/agent-brief.md` | +| **Rejected scope** | Record/reconsider/remove an out-of-scope decision | `references/out-of-scope.md` | +| **Issue research** | Research an issue on the latest development tip | `references/research-issue.md` | +| **Create local-work PR** | Publish already-existing local work | `references/create-pr-from-local-work.md` | +| **Create linked PR** | Bounded research -> implementation -> pre-open review -> PR | `references/create-pr-for-issue.md` | +| **Open work** | Repository-scoped authored-open-PR overview | `references/open-work-status.md` | +| **External work item** | Inspect/deliver `ENG-42`-style tracker work | `references/work-item-delivery.md` | +| **Competing PRs** | Analyze overlapping/duplicate implementations | `references/consolidate-prs.md` | +| **Status** | What is left / why blocked / merge readiness | `references/status.md` | +| **Make merge-ready** | Fix humans/bots, own review work, validate | `references/fix-pr-bots.md` | +| **Watch** | Poll CI/reviews/gates until merged/closed/blocked | `references/watch-pr.md` | +| **Re-review** | Re-evaluate after head/review evidence changes | `references/re-review-pr.md` | +| **Full review** | Deep Bug + Security + Spec + Standards review | `references/full-review-pr.md` | +| **Visual evidence** | Conditional rendered-surface evidence axis | `references/visual-evidence.md` | +| **Bug review** | Evidence-ranked adversarial bug hunt | `references/bug-review.md` + `references/bug-hunt-method.md` | +| **Security review** | Security surfaces, escalation chains, safe reporting | `references/security-review.md` | +| **Spec / standards** | Contract, requirements, standards, docs/non-goals | `references/spec-standards-review.md` | +| **Design quality** | Advisory design/abstraction/state/seam review | `references/design-quality.md` | +| **Type evidence** | Typed-code evidence erosion / anti-slop review | `references/type-evidence-review.md` | +| **Minimal solution** | Lowest-complexity complete implementation choice | `references/minimal-solution.md` | +| **Verification boundaries** | Stable regression/refactor evidence boundary | `references/verification-boundaries.md` | +| **Change execution** | Safe migrations, mechanical sweeps, expand-contract | `references/change-execution.md` | +| **Completion evidence** | Prove durable completion/count/coverage claims | `references/completion-claims.md` | +| **Safe simplification** | Behavior-preserving cleanup + mandatory re-review | `references/simplify-pr.md` | +| **Prepare + merge** | Compound review/fix/simplify request with explicit merge | `references/prepare-and-merge-pr.md` | +| **Merge** | Settle, final live gate, exact head-pinned merge | `references/merge-pr.md` | +| **Supersede** | Replace an obsolete PR with a canonical PR | `references/supersede-pr.md` | +| **Maintainer overtake** | Take over an unresponsive author's PR | `references/overtake-pr.md` | +| **Conflicts** | Resolve active conflicts from both sides' intent/evidence | `references/resolve-conflicts.md` | +| **Stacked PRs** | Inspect/restack/retarget/recover/review/merge stacks | `references/stacked-prs.md` | +| **Backports / ports** | Parallel delivery to one or more target bases | `references/multi-base-delivery.md` | +| **Update installed skill** | Verify/check/apply latest stable release | `references/update.md` | +| **Progress watchdog** | Runtime generation bounds and workflow convergence | `references/agent-progress-watchdog.md` | + +### More natural-language examples + +```text +create a PRD for the onboarding flow +break the roadmap into implementation issues +triage the open issues in this repo +show me what needs triage in this repo +what do I have open in this repo? + +research issue #90 on the latest development branch +create a PR for issue #90 +research and implement issue #90 +work on ENG-42 and open a PR +what's left on ENG-42? + +what is left on PR #41? +is PR #42 safe to merge? +full review PR #42 +fix the review comments on PR #18 and make it merge ready +watch PR #77 until it merges or needs me +simplify PR #42 without changing behavior +review PR #42, fix it, and merge it when green +merge PR #32 + +triage the competing PRs in this repo +inspect this PR stack and tell me the safe merge order +backport PR #42 to release/1.x and release/2.x +supersede PR #12 with PR #45 +maintainer overtake PR #32 and finish it +update github-delivery to the latest stable release +``` + +--- + +## Development and verification + +Supported runtime contract: + +```text +Node.js 22 | 24 | 26 +``` + +Run the canonical repository gate: + +```bash +npm run check +``` + +Useful focused commands: + +```bash +npm test +npm run security:repo +npm run dist:check +npm run package:check +npm run evals:offline +npm run reliability:gate +``` + +### Lean required CI topology + +The pull-request CI topology is deliberately asymmetric to avoid repeating the full repository suite across every OS/runtime combination while keeping the security-critical platform lanes unskippable by PR scope logic: + +| Required context | PR behavior | +|---|---| +| **Node 24 / ubuntu-latest** | Canonical full `npm run check`; then bounded Node 26 syntax/package/unit compatibility on the same workspace | +| **Node 22 / ubuntu-latest** | Bounded compatibility lane only when runtime-relevant paths change; its path classifier is executed from the PR base version | +| **Node 24 / windows-latest** | Always runs Windows Authority restore/build/self-test/publish/install smoke on pull requests | +| **Dependency Review** | Runs on pull requests | +| **CodeQL / Analyze (javascript-typescript)** | Runs on pull requests | +| **CodeQL / Analyze (csharp)** | Always runs on pull requests, plus `main` and schedules | + +There are no macOS PR compatibility lanes and no duplicate Architecture Contracts workflow. Superseded CI, CodeQL, and Dependency Review runs are cancelled when a newer commit arrives. Repository-policy verification is daily and orphan-workflow cleanup is weekly; cleanup pins the default-branch generation before deleting stale workflow histories. + +For ordinary runtime-relevant PRs this keeps full `npm run check` executions at **1**, full unit-suite runtime executions at **3**, one Windows Authority lane on every PR, and **0** macOS PR jobs while retaining Node 22/24/26 compatibility coverage and unconditional Windows/C# security coverage. + +### Live lifecycle fixture + +The unit/eval suite proves deterministic contracts. An explicitly opted-in fixture repository exercises the real GitHub lifecycle with immutable repository-identity binding before the first mutation. Fixture diffs force the scoped Node 22 compatibility lane; the Windows Authority lane already runs unconditionally. + +See [`docs/live-integration.md`](docs/live-integration.md) and [`docs/live-github-integration.md`](docs/live-github-integration.md). + +--- + +## Internal architecture + +The public interface stays small even though the enforcement surface is not. Key internals: + +| Surface | Responsibility | +|---|---| +| `SKILL.md` | Host discovery and top-level natural-language capability map | +| `scripts/lib/skill-router.mjs` | Deterministic route and explicit-action selection | +| `references/policy-kernel.md` + `references/policy/*.md` | Canonical cross-workflow and focused policy contracts | +| `scripts/delivery-controller.mjs` | Persistent routed workflow state/budget controller | +| `scripts/ship-gate-snapshot.mjs` | Current GitHub evidence snapshot | +| `scripts/ship-gate.mjs` | Authoritative `ready` / `blocked` / `unknown` decision | +| `scripts/merge-pr-driver.mjs` | Canonical destructive merge boundary | +| `scripts/github-mutate.mjs` | Typed non-merge GitHub mutation entrypoint | +| `scripts/lib/authority-scope.mjs` | Exact-effect trusted authority scope | +| `authority-host/windows/` | Optional Windows Hello trusted-authority issuer | +| `scripts/review-scope.mjs` | Evidence-ranked review scope and required probes | +| `scripts/lib/visual-evidence.mjs` | Conditional head-bound rendered-evidence planning/validation | +| `scripts/lib/work-item-delivery.mjs` | Tracker milestone/reconciliation planning | +| `scripts/lib/pr-consolidation.mjs` | Read-only competing-PR clustering/planning evidence | +| `scripts/lib/multi-base-delivery.mjs` | Parallel port identities/provenance/completion | +| `scripts/lib/agent-progress-watchdog.mjs` | Shared progress/evidence/tool-emission watchdog logic | +| `scripts/build-dist.mjs` | Deterministic versioned skill bundle build | +| `scripts/prepare-release.mjs` | Release identity/checksum/SBOM/provenance preparation | + +The architecture uses progressive disclosure: route once, load the selected workflow plus required policy modules, and escalate diagnostics only when needed rather than dumping the full rule set into every agent turn. + +--- + +## Current state + +Implemented today: + +- natural-language routing for the issue/PR lifecycle; +- read-only open-work and competing-PR analysis; +- issue research, implementation, publication, external work-item delivery, and exact-head duplicate prevention; +- deep current-head review with deterministic probe coverage and conditional visual evidence; +- mutation authority, exact-effect receipts, stale-head protection, and head-pinned merge execution; +- stack restacking/merge-order safety and independent multi-base delivery; +- verified stable install/update and optional Windows Authority host; +- progress watchdog/runtime convergence controls; +- deterministic bundles, repository security checks, CodeQL, Dependency Review, live-fixture contracts, and release preparation. + +Still active-development territory: + +- host/runtime integrations remain constrained by what each agent host exposes; +- the protected Codex App Server streaming boundary depends on an experimental upstream interface; +- broader tracker adapters beyond the normalized work-item contract can be added without weakening GitHub authority boundaries; +- more real-world fixture coverage and adversarial incident replays are still valuable as the system expands. + +The project intentionally fails closed rather than claiming unsupported coverage. + +--- + +## Provenance and license + +Some workflow directions were informed by public/open-source agent skills and GitHub automation patterns, including concepts from `OutThisLife/brooklyn-skills`. Adapted ideas are rewritten around GitHub Delivery's own evidence, authority, routing, and lifecycle contracts; relevant workflow files include provenance notes where appropriate. + +Licensed under the [MIT License](LICENSE). \ No newline at end of file From 7365d6ec9fb5f81801676b58c38058629170dbe1 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:15:43 +0200 Subject: [PATCH 42/49] test: prove micro-narration resets on real progress --- .../codex-watchdog-progress-bounds.test.mjs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/unit/codex-watchdog-progress-bounds.test.mjs b/tests/unit/codex-watchdog-progress-bounds.test.mjs index e074120a..aa22ea91 100644 --- a/tests/unit/codex-watchdog-progress-bounds.test.mjs +++ b/tests/unit/codex-watchdog-progress-bounds.test.mjs @@ -55,6 +55,17 @@ function started(r, item) { }); } +function completed(r, item) { + return r.onServerMessage({ + method: "item/completed", + params: { + threadId: "thr-progress", + turnId: "turn-progress", + item, + }, + }); +} + test("novel imminent-execution narration is bounded even when every sentence differs", () => { const r = router({ generatedCharHardLimit: 10_000 }); assert.equal(text(r, "Let me grep the duplicate locale key.\n").internalRequests.length, 0); @@ -114,6 +125,38 @@ test("interleaved evidence tools do not buy a fresh micro-narration budget", () assert.equal(tripped.internalRequests[0].method, "turn/interrupt"); }); +test("real execution progress resets the micro-narration budget", () => { + const r = router({ + generatedCharHardLimit: 10_000, + toolEmissionIntentThreshold: 50, + }); + assert.equal(text(r, "I'll load the first rule file.\n").internalRequests.length, 0); + started(r, { + id: "read-a", + type: "commandExecution", + command: 'Get-Content -LiteralPath "AGENTS.md" -Raw', + status: "inProgress", + }); + assert.equal(text(r, "Next I'll verify the branch state.\n").internalRequests.length, 0); + + completed(r, { + id: "test-1", + type: "commandExecution", + command: "npm test", + status: "completed", + exitCode: 0, + }); + + assert.equal(text(r, "I'll load the selected workflow.\n").internalRequests.length, 0); + started(r, { + id: "read-b", + type: "commandExecution", + command: 'Get-Content -LiteralPath "references/stacked-prs.md" -Raw', + status: "inProgress", + }); + assert.equal(text(r, "Then I'll verify the live PR stack.\n").internalRequests.length, 0); +}); + test("repeated malformed tool protocol output accelerates a tool-emission stall", () => { const r = router({ generatedCharHardLimit: 10_000, toolEmissionIntentThreshold: 50 }); assert.equal(text(r, "\n").internalRequests.length, 0); From 6f551a5ebb2bb1bcbf9a0f299a540d263b7d2e79 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:16:59 +0200 Subject: [PATCH 43/49] docs: keep README unchanged --- README.md | 93 +++++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index d550c43b..ecb9b026 100644 --- a/README.md +++ b/README.md @@ -277,56 +277,49 @@ Useful explicit commands: ```bash npx github-delivery install npx github-delivery setup -npx github-delivery start -npx github-delivery autostart -npx github-delivery autostart on -npx github-delivery autostart off -npx github-delivery autostart status npx github-delivery doctor -npx github-delivery doctor --json npx github-delivery update npx github-delivery update --apply +npx github-delivery start +npx github-delivery autostart status ``` -### Update +The installer: -Check/verify/plan only: +1. runs environment preflight; +2. discovers the latest stable release when needed; +3. verifies checksums, manifest, tag/source binding, and artifact attestation; +4. plans installation; +5. asks before mutation; +6. backs up existing managed installs; +7. installs the new payload; +8. configures supported watchdog surfaces; +9. verifies the installed manifest; +10. reconciles the optional Windows Authority component. -```bash -npx github-delivery update -``` +### Windows Authority -Apply the verified plan: +On Windows, setup can install the self-contained `win-x64` Authority component without requiring a local .NET SDK. The host is versioned independently from the skill installation and is kept aligned through stable setup/update flows. + +Start or surface it with: ```bash -npx github-delivery update --apply +npx github-delivery start ``` -Self-update accepts only the fixed upstream's latest stable `vX.Y.Z` GitHub Release and replaces nothing until release assets, checksums, distribution manifest, tag/source binding, constrained GitHub artifact attestation, and bounded ZIP extraction verify. Local tracked modifications block replacement even with `--force`; update does not silently downgrade an ahead install. - -### Setup and doctor +Login auto-start is opt-in: ```bash -npx github-delivery setup -npx github-delivery doctor +npx github-delivery autostart on +npx github-delivery autostart off +npx github-delivery autostart status ``` -`setup` repairs/finishes activation against an existing managed installation. `doctor` is read-only and summarizes environment, installed version/integrity, persistent configuration, watchdog activation, stable-update relation, and Windows Authority state. Use `doctor --json` for machine-readable output. - -### Windows Authority - -On supported Windows systems, the stable GitHub Release can include the separately verified self-contained Authority host. Guided setup/update can install or repair it without a local .NET SDK when required or already configured. - -`npx github-delivery start` ensures the host is running and brings the Control Center into view. Login auto-start is opt-in and shared between the CLI and Control Center setting. Normal window close leaves Authority in the tray; tray right-click -> `Exit` shuts it down completely. - -The host is not silently installed for a user whose protection mode is `off` and who has never installed Authority. +### Manual/source install -### Manual / repository install +For repository development or an explicit local source: ```bash -git clone https://github.com/Wibias/github-delivery.git -cd github-delivery -npm run build:dist node scripts/install-skill.mjs node scripts/install-skill.mjs --apply ``` @@ -523,35 +516,33 @@ The public interface stays small even though the enforcement surface is not. Key | `scripts/lib/pr-consolidation.mjs` | Read-only competing-PR clustering/planning evidence | | `scripts/lib/multi-base-delivery.mjs` | Parallel port identities/provenance/completion | | `scripts/lib/agent-progress-watchdog.mjs` | Shared progress/evidence/tool-emission watchdog logic | -| `scripts/build-dist.mjs` | Deterministic versioned skill bundle build | -| `scripts/prepare-release.mjs` | Release identity/checksum/SBOM/provenance preparation | +| `scripts/lib/delivery-workflow-controller.mjs` | Route-locked persistent workflow convergence state | +| `scripts/lib/review-scope.mjs` | Evidence-ranked review scope and required probes | +| `scripts/lib/probe-evidence.mjs` | Machine validation for applied probe evidence | +| `scripts/lib/visual-evidence.mjs` | Conditional head-bound rendered-evidence planning/validation | +| `scripts/lib/work-item-delivery.mjs` | Tracker milestone/reconciliation planning | +| `scripts/lib/pr-consolidation.mjs` | Read-only competing-PR clustering/planning evidence | +| `scripts/lib/multi-base-delivery.mjs` | Parallel port identities/provenance/completion | -The architecture uses progressive disclosure: route once, load the selected workflow plus required policy modules, and escalate diagnostics only when needed rather than dumping the full rule set into every agent turn. +Most workflows are readable Markdown. Deterministic scripts exist where correctness benefits from machine enforcement: routing, scope planning, probe coverage, policy validation, identity checks, evidence freshness, mutation authority, retries, postconditions, and release verification. --- ## Current state -Implemented today: - -- natural-language routing for the issue/PR lifecycle; -- read-only open-work and competing-PR analysis; -- issue research, implementation, publication, external work-item delivery, and exact-head duplicate prevention; -- deep current-head review with deterministic probe coverage and conditional visual evidence; -- mutation authority, exact-effect receipts, stale-head protection, and head-pinned merge execution; -- stack restacking/merge-order safety and independent multi-base delivery; -- verified stable install/update and optional Windows Authority host; -- progress watchdog/runtime convergence controls; -- deterministic bundles, repository security checks, CodeQL, Dependency Review, live-fixture contracts, and release preparation. +The project intentionally fails closed rather than claiming unsupported coverage. -Still active-development territory: +Today: -- host/runtime integrations remain constrained by what each agent host exposes; -- the protected Codex App Server streaming boundary depends on an experimental upstream interface; -- broader tracker adapters beyond the normalized work-item contract can be added without weakening GitHub authority boundaries; -- more real-world fixture coverage and adversarial incident replays are still valuable as the system expands. +- issue/PR workflow routing is implemented; +- evidence-based review and merge gates are implemented; +- mutation authority and exact-effect broker boundaries are implemented; +- release verification and update flows are implemented; +- the Windows Authority component has build/self-test/XAML/publish/install coverage; +- runtime watchdog enforcement is strongest under the protected Codex stream, partial under trusted hooks, and policy-only otherwise; +- some host/tool surfaces still cannot be intercepted mid-generation, so policy/evidence budgets remain part of the design. -The project intentionally fails closed rather than claiming unsupported coverage. +If a host cannot prove a required safety boundary, GitHub Delivery reports that limitation instead of silently weakening the workflow. --- From 3edcd042dee745d4212dd4badeba58c5aac1635e Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:18:24 +0200 Subject: [PATCH 44/49] revert: restore canonical README --- README.md | 93 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index ecb9b026..d550c43b 100644 --- a/README.md +++ b/README.md @@ -277,49 +277,56 @@ Useful explicit commands: ```bash npx github-delivery install npx github-delivery setup +npx github-delivery start +npx github-delivery autostart +npx github-delivery autostart on +npx github-delivery autostart off +npx github-delivery autostart status npx github-delivery doctor +npx github-delivery doctor --json npx github-delivery update npx github-delivery update --apply -npx github-delivery start -npx github-delivery autostart status ``` -The installer: +### Update -1. runs environment preflight; -2. discovers the latest stable release when needed; -3. verifies checksums, manifest, tag/source binding, and artifact attestation; -4. plans installation; -5. asks before mutation; -6. backs up existing managed installs; -7. installs the new payload; -8. configures supported watchdog surfaces; -9. verifies the installed manifest; -10. reconciles the optional Windows Authority component. - -### Windows Authority +Check/verify/plan only: -On Windows, setup can install the self-contained `win-x64` Authority component without requiring a local .NET SDK. The host is versioned independently from the skill installation and is kept aligned through stable setup/update flows. +```bash +npx github-delivery update +``` -Start or surface it with: +Apply the verified plan: ```bash -npx github-delivery start +npx github-delivery update --apply ``` -Login auto-start is opt-in: +Self-update accepts only the fixed upstream's latest stable `vX.Y.Z` GitHub Release and replaces nothing until release assets, checksums, distribution manifest, tag/source binding, constrained GitHub artifact attestation, and bounded ZIP extraction verify. Local tracked modifications block replacement even with `--force`; update does not silently downgrade an ahead install. + +### Setup and doctor ```bash -npx github-delivery autostart on -npx github-delivery autostart off -npx github-delivery autostart status +npx github-delivery setup +npx github-delivery doctor ``` -### Manual/source install +`setup` repairs/finishes activation against an existing managed installation. `doctor` is read-only and summarizes environment, installed version/integrity, persistent configuration, watchdog activation, stable-update relation, and Windows Authority state. Use `doctor --json` for machine-readable output. + +### Windows Authority + +On supported Windows systems, the stable GitHub Release can include the separately verified self-contained Authority host. Guided setup/update can install or repair it without a local .NET SDK when required or already configured. + +`npx github-delivery start` ensures the host is running and brings the Control Center into view. Login auto-start is opt-in and shared between the CLI and Control Center setting. Normal window close leaves Authority in the tray; tray right-click -> `Exit` shuts it down completely. + +The host is not silently installed for a user whose protection mode is `off` and who has never installed Authority. -For repository development or an explicit local source: +### Manual / repository install ```bash +git clone https://github.com/Wibias/github-delivery.git +cd github-delivery +npm run build:dist node scripts/install-skill.mjs node scripts/install-skill.mjs --apply ``` @@ -516,33 +523,35 @@ The public interface stays small even though the enforcement surface is not. Key | `scripts/lib/pr-consolidation.mjs` | Read-only competing-PR clustering/planning evidence | | `scripts/lib/multi-base-delivery.mjs` | Parallel port identities/provenance/completion | | `scripts/lib/agent-progress-watchdog.mjs` | Shared progress/evidence/tool-emission watchdog logic | -| `scripts/lib/delivery-workflow-controller.mjs` | Route-locked persistent workflow convergence state | -| `scripts/lib/review-scope.mjs` | Evidence-ranked review scope and required probes | -| `scripts/lib/probe-evidence.mjs` | Machine validation for applied probe evidence | -| `scripts/lib/visual-evidence.mjs` | Conditional head-bound rendered-evidence planning/validation | -| `scripts/lib/work-item-delivery.mjs` | Tracker milestone/reconciliation planning | -| `scripts/lib/pr-consolidation.mjs` | Read-only competing-PR clustering/planning evidence | -| `scripts/lib/multi-base-delivery.mjs` | Parallel port identities/provenance/completion | +| `scripts/build-dist.mjs` | Deterministic versioned skill bundle build | +| `scripts/prepare-release.mjs` | Release identity/checksum/SBOM/provenance preparation | -Most workflows are readable Markdown. Deterministic scripts exist where correctness benefits from machine enforcement: routing, scope planning, probe coverage, policy validation, identity checks, evidence freshness, mutation authority, retries, postconditions, and release verification. +The architecture uses progressive disclosure: route once, load the selected workflow plus required policy modules, and escalate diagnostics only when needed rather than dumping the full rule set into every agent turn. --- ## Current state -The project intentionally fails closed rather than claiming unsupported coverage. +Implemented today: + +- natural-language routing for the issue/PR lifecycle; +- read-only open-work and competing-PR analysis; +- issue research, implementation, publication, external work-item delivery, and exact-head duplicate prevention; +- deep current-head review with deterministic probe coverage and conditional visual evidence; +- mutation authority, exact-effect receipts, stale-head protection, and head-pinned merge execution; +- stack restacking/merge-order safety and independent multi-base delivery; +- verified stable install/update and optional Windows Authority host; +- progress watchdog/runtime convergence controls; +- deterministic bundles, repository security checks, CodeQL, Dependency Review, live-fixture contracts, and release preparation. -Today: +Still active-development territory: -- issue/PR workflow routing is implemented; -- evidence-based review and merge gates are implemented; -- mutation authority and exact-effect broker boundaries are implemented; -- release verification and update flows are implemented; -- the Windows Authority component has build/self-test/XAML/publish/install coverage; -- runtime watchdog enforcement is strongest under the protected Codex stream, partial under trusted hooks, and policy-only otherwise; -- some host/tool surfaces still cannot be intercepted mid-generation, so policy/evidence budgets remain part of the design. +- host/runtime integrations remain constrained by what each agent host exposes; +- the protected Codex App Server streaming boundary depends on an experimental upstream interface; +- broader tracker adapters beyond the normalized work-item contract can be added without weakening GitHub authority boundaries; +- more real-world fixture coverage and adversarial incident replays are still valuable as the system expands. -If a host cannot prove a required safety boundary, GitHub Delivery reports that limitation instead of silently weakening the workflow. +The project intentionally fails closed rather than claiming unsupported coverage. --- From c8f6ff0c4225fcda7419aa9c0f800de8cf39423d Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:19:40 +0200 Subject: [PATCH 45/49] fix: match gerund micro-narration intents --- scripts/lib/codex-progress-watchdog.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lib/codex-progress-watchdog.mjs b/scripts/lib/codex-progress-watchdog.mjs index 540f6a32..587b62d1 100644 --- a/scripts/lib/codex-progress-watchdog.mjs +++ b/scripts/lib/codex-progress-watchdog.mjs @@ -29,7 +29,7 @@ const FINALIZATION_WATCHDOG_OPTIONS = Object.freeze({ }); const MICRO_NARRATION_INTENT_THRESHOLD = 3; -const MICRO_NARRATION_INTENT = /(?:^|\b(?:so|and|then|next)[,:]?\s+)(?:next\s+)?(?:let me|i(?:'|’)ll|i will|i need to|i(?:'|’)m going to|i am going to)\s+(?:(?:start(?:ing)?(?:\s+by)?|now|next|then|first|just)\s+)*(?:load|read|verify|check|inspect|fetch|recapture|lock|run|execute|invoke|call|search|open|use|apply|patch|edit|write|update|fix|change)\b/i; +const MICRO_NARRATION_INTENT = /(?:^|\b(?:so|and|then|next)[,:]?\s+)(?:next\s+)?(?:let me|i(?:'|’)ll|i will|i need to|i(?:'|’)m going to|i am going to)\s+(?:(?:start(?:ing)?(?:\s+by)?|now|next|then|first|just)\s+)*(?:load(?:ing)?|read(?:ing)?|verif(?:y|ying)|check(?:ing)?|inspect(?:ing)?|fetch(?:ing)?|recaptur(?:e|ing)|lock(?:ing)?|run(?:ning)?|execut(?:e|ing)|invok(?:e|ing)|call(?:ing)?|search(?:ing)?|open(?:ing)?|us(?:e|ing)|apply(?:ing)?|patch(?:ing)?|edit(?:ing)?|writ(?:e|ing)|updat(?:e|ing)|fix(?:ing)?|chang(?:e|ing))\b/i; export function isCodexGeneratedTextMethod(method) { return GENERATED_TEXT_METHODS.has(String(method || "")); @@ -221,4 +221,4 @@ export function observeCodexAppServerMessage(watchdog, message, context = {}) { } return { decision: { action: "allow" } }; -} \ No newline at end of file +} From ff68a6edba59a6ec1ceecd131a45feb4790cafba Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:21:17 +0200 Subject: [PATCH 46/49] docs: keep quiet execution compact --- SKILL.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/SKILL.md b/SKILL.md index 6dfd1dc3..9c28dce1 100644 --- a/SKILL.md +++ b/SKILL.md @@ -104,22 +104,7 @@ 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 unchanged context. The controller grants -no GitHub write authority. - -## Quiet execution contract - -Routine deterministic tooling is quiet by default. Do not spend assistant output -announcing that you are loading canonical rules, this skill, a selected workflow, -or another reference; reading files; checking Git/GitHub state; locking a -worktree; fetching remotes; correcting obvious shell quoting; or attempting the -next deterministic read/test. Execute those steps directly. - -User-facing progress updates are reserved for a phase transition, material new -evidence or plan change, a real blocker, a state-changing action worth reporting, -or user input that is actually required. A successful evidence/read tool does -not by itself justify “loaded X” / “next I’ll verify Y” narration. When a tool -call fails but the correction is deterministic and does not change scope or the -plan, correct it and retry quietly. See `GD-CORE-008` through `GD-CORE-010`. +no GitHub write authority. Run routine deterministic tooling quietly; narrate only material progress or blockers (GD-CORE-009). ## Mandatory entrypoint behavior From 707870b766ebe56cfe2ed0907673cbcd9de44a91 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:21:45 +0200 Subject: [PATCH 47/49] test: keep quiet entrypoint contract within budget --- tests/unit/forward-progress-policy.test.mjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/forward-progress-policy.test.mjs b/tests/unit/forward-progress-policy.test.mjs index b2cd1864..1eb22f94 100644 --- a/tests/unit/forward-progress-policy.test.mjs +++ b/tests/unit/forward-progress-policy.test.mjs @@ -43,9 +43,8 @@ test("deterministic tool calls are executed without micro-narration", () => { assert.match(kernel, /phase change|material.*change|user input/i); assert.match(kernel, /rule\/skill\/workflow loading|file reads|remote fetches/i); assert.match(kernel, /retry quietly|correct.*quietly/i); - assert.match(skill, /quiet execution contract/i); - assert.match(skill, /loading canonical rules|checking Git\/GitHub state/i); - assert.match(skill, /retry quietly|correct it and retry quietly/i); + assert.match(skill, /routine deterministic tooling quietly/i); + assert.match(skill, /material progress or blockers/i); }); test("context economy minimises evidence acquisition and model-facing output", () => { From e020be97173a6c66afc97e617a0b2f9be875583e Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:23:06 +0200 Subject: [PATCH 48/49] test: preserve character-budget interrupt assertion --- tests/unit/codex-watchdog-progress-bounds.test.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/codex-watchdog-progress-bounds.test.mjs b/tests/unit/codex-watchdog-progress-bounds.test.mjs index aa22ea91..29320d3e 100644 --- a/tests/unit/codex-watchdog-progress-bounds.test.mjs +++ b/tests/unit/codex-watchdog-progress-bounds.test.mjs @@ -236,4 +236,5 @@ test("unique generated text is bounded by characters even without token telemetr assert.equal(text(r, "A completely novel sentence about one investigation path.\n").internalRequests.length, 0); const tripped = text(r, "Another unrelated sentence keeps growing without any runtime progress at all.\n"); assert.equal(tripped.internalRequests.length, 1); -}); \ No newline at end of file + assert.equal(tripped.internalRequests[0].method, "turn/interrupt"); +}); From 4e2b7b6b6c7ebb6f62068ed852c02afa4049f412 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:26:26 +0200 Subject: [PATCH 49/49] fix: clear micro-narration only after pending tool-emission signal --- scripts/lib/codex-progress-watchdog.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/lib/codex-progress-watchdog.mjs b/scripts/lib/codex-progress-watchdog.mjs index 587b62d1..2f18b691 100644 --- a/scripts/lib/codex-progress-watchdog.mjs +++ b/scripts/lib/codex-progress-watchdog.mjs @@ -179,7 +179,11 @@ export function observeCodexAppServerMessage(watchdog, message, context = {}) { if (method === "item/started") { const item = params.item; if (RUNTIME_WORK_ITEM_TYPES.has(String(item?.type || ""))) { + const pendingToolEmissionSignal = + typeof watchdog.snapshot === "function" && + Number(watchdog.snapshot().toolEmissionIntentCount || 0) > 0; watchdog.recordToolStart({ type: item.type, id: item.id || null }); + if (pendingToolEmissionSignal) resetMicroNarration(context); context.finalizing = false; context.finalizationWatchdog = null; }