From 01931a75d58e649642f739beb8533e99f2bae841 Mon Sep 17 00:00:00 2001 From: Winterrks <160774215+Winterrks@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:51:28 +0100 Subject: [PATCH] retire the spent one-run workflows: moved out of .github/workflows into inert .github/retired-workflows, with a gate that keeps them out --- .../cowork-guided-packet-compile-fixture.yml | 0 .../cowork-reader-compile-fixture.yml | 0 .../cowork-reader-native-reviewed.yml | 0 .../guest-guided-build/test_guided_build.py | 2 +- .../test_fixture_stderr.py | 2 +- .../guest-reader-native/test_report_family.py | 4 +- .../gates/guided-packet-compile-gates.test.ts | 9 ++-- test/gates/guided-packet-pin.test.ts | 6 ++- test/gates/release-gates.test.ts | 7 +-- test/gates/retired-workflows.test.ts | 51 +++++++++++++++++++ 10 files changed, 68 insertions(+), 13 deletions(-) rename .github/{workflows => retired-workflows}/cowork-guided-packet-compile-fixture.yml (100%) rename .github/{workflows => retired-workflows}/cowork-reader-compile-fixture.yml (100%) rename .github/{workflows => retired-workflows}/cowork-reader-native-reviewed.yml (100%) create mode 100644 test/gates/retired-workflows.test.ts diff --git a/.github/workflows/cowork-guided-packet-compile-fixture.yml b/.github/retired-workflows/cowork-guided-packet-compile-fixture.yml similarity index 100% rename from .github/workflows/cowork-guided-packet-compile-fixture.yml rename to .github/retired-workflows/cowork-guided-packet-compile-fixture.yml diff --git a/.github/workflows/cowork-reader-compile-fixture.yml b/.github/retired-workflows/cowork-reader-compile-fixture.yml similarity index 100% rename from .github/workflows/cowork-reader-compile-fixture.yml rename to .github/retired-workflows/cowork-reader-compile-fixture.yml diff --git a/.github/workflows/cowork-reader-native-reviewed.yml b/.github/retired-workflows/cowork-reader-native-reviewed.yml similarity index 100% rename from .github/workflows/cowork-reader-native-reviewed.yml rename to .github/retired-workflows/cowork-reader-native-reviewed.yml diff --git a/scripts/guest-guided-build/test_guided_build.py b/scripts/guest-guided-build/test_guided_build.py index 2942ab9..7973fd6 100644 --- a/scripts/guest-guided-build/test_guided_build.py +++ b/scripts/guest-guided-build/test_guided_build.py @@ -10,7 +10,7 @@ def load(name,path): rt=load('reader_test_helpers',HERE.parent/'guest-reader-build'/'test_provenance.py') # inert ELF/Bun-layout/git fixtures only elf,standalone_pair,git_commit,put=rt.elf,rt.standalone_pair,rt.git_commit,rt.put REPO=HERE.parents[1] -WORKFLOW=REPO/'.github/workflows/cowork-guided-packet-compile-fixture.yml' +WORKFLOW=REPO/'.github/retired-workflows/cowork-guided-packet-compile-fixture.yml' GUEST='packages/runtime/src/guest/' class Fixture(unittest.TestCase): diff --git a/scripts/guest-reader-native/test_fixture_stderr.py b/scripts/guest-reader-native/test_fixture_stderr.py index 4390046..9474270 100644 --- a/scripts/guest-reader-native/test_fixture_stderr.py +++ b/scripts/guest-reader-native/test_fixture_stderr.py @@ -140,7 +140,7 @@ def test_hosted_export_has_no_production_import_route_or_raw_log(self): root=Path(__file__).resolve().parents[2] for source in (root/'src').rglob('*.ts'): self.assertNotIn('guest-reader-native/wrapper',source.read_text()) - workflow=(root/'.github/workflows/cowork-reader-native-reviewed.yml').read_text() + workflow=(root/'.github/retired-workflows/cowork-reader-native-reviewed.yml').read_text() self.assertEqual(workflow.count('module.log_result_projection(result)'),2) self.assertNotIn('print(json.dumps(result,sort_keys=True))',workflow) diff --git a/scripts/guest-reader-native/test_report_family.py b/scripts/guest-reader-native/test_report_family.py index bfefde2..435fa10 100644 --- a/scripts/guest-reader-native/test_report_family.py +++ b/scripts/guest-reader-native/test_report_family.py @@ -939,7 +939,7 @@ def test_the_compile_pin_acquisition_and_intake_pins_agree_across_every_module(s self.assertIn(needle, download) def test_workflow_and_gate_pins_name_the_actual_bootstrap_and_wrapper(self): - workflow = (ROOT / '.github/workflows/cowork-reader-native-reviewed.yml').read_text() + workflow = (ROOT / '.github/retired-workflows/cowork-reader-native-reviewed.yml').read_text() gate = (ROOT / 'test/gates/release-gates.test.ts').read_text() bootstrap, wrapper = self.digest('bootstrap.py'), self.digest('wrapper.py') self.assertEqual(workflow.count(bootstrap), 4); self.assertEqual(workflow.count(wrapper), 1) @@ -950,7 +950,7 @@ def test_workflow_and_gate_pins_name_the_actual_bootstrap_and_wrapper(self): self.assertEqual(workflow.count(download), 1); self.assertEqual(gate.count('"' + download + '"'), 1) def test_origins_base_is_a_commit_that_carries_exactly_the_files_the_bootstrap_pins(self): - workflow = (ROOT / '.github/workflows/cowork-reader-native-reviewed.yml').read_text() + workflow = (ROOT / '.github/retired-workflows/cowork-reader-native-reviewed.yml').read_text() gate = (ROOT / 'test/gates/release-gates.test.ts').read_text() ref = re.search(r"ref: ([0-9a-f]{40})\n\s+path: origins/base", workflow).group(1) self.assertEqual(gate.count('{ ref: "' + ref + '", path: "origins/base"'), 1) diff --git a/test/gates/guided-packet-compile-gates.test.ts b/test/gates/guided-packet-compile-gates.test.ts index 473a396..3ca1eb8 100644 --- a/test/gates/guided-packet-compile-gates.test.ts +++ b/test/gates/guided-packet-compile-gates.test.ts @@ -23,7 +23,8 @@ const ALLOWED_ACTIONS = new Set(["actions/checkout", "actions/upload-artifact"]) const USED_BRANCHES = ["cowork/instruction-reader-compile-fixture", "cowork/reader-hosted-dependency-probe", "cowork/reader-hosted-fixture-stderr", "cowork/guided-packet-build", "cowork/guided-packet-compile-fixture", "cowork/guided-packet-compile-fixture-2", "cowork/guided-packet-compile-fixture-3", "cowork/guided-packet-compile-fixture-4"]; const ZERO_HEAD = "0".repeat(40); const BUN_URL = "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-linux-aarch64.zip"; -const WORKFLOW_PATH = ".github/workflows/cowork-guided-packet-compile-fixture.yml"; +const WORKFLOW_PATH = ".github/workflows/cowork-guided-packet-compile-fixture.yml"; // the path the RETIRED file had: the arming guard text inside it still names it + /** The arming guard, line for line. It runs before any builder code is checked out and refuses unless BUILDER_HEAD is an * ancestor of the pushed commit AND the whole difference is the one BUILDER_HEAD line of this workflow. */ const GUARD: Array<[string, string]> = [ @@ -292,7 +293,7 @@ describe("the runtime-sdk origin's tree is its head's real tree", () => { }); describe("guided packet compile-fixture workflow gates", () => { - const WORKFLOW = read(`.github/workflows/${WORKFLOW_NAME}`); + const WORKFLOW = read(`.github/retired-workflows/${WORKFLOW_NAME}`); test("the committed workflow satisfies every gate", () => { expect(violations(WORKFLOW)).toEqual([]); @@ -464,7 +465,7 @@ const MUTATIONS: Array<[string, (text: string) => string, string]> = [ // A mutation is only evidence if it changed the text and was caught by the violation it names. describe("guided packet compile-fixture workflow gate mutations", () => { - const WORKFLOW = read(`.github/workflows/${WORKFLOW_NAME}`); + const WORKFLOW = read(`.github/retired-workflows/${WORKFLOW_NAME}`); const ctx = loadContext(); test("the table has one entry per gate it claims and no duplicate names", () => { expect(new Set(MUTATIONS.map(([name]) => name)).size).toBe(MUTATIONS.length); @@ -502,7 +503,7 @@ describe("guided packet compile-fixture workflow gate mutations", () => { // The guard is run for real, as bash, against throwaway Git repositories: each way an arming commit can be wrong must fail. describe("arming guard behaviour (runs the workflow's own guard script against temporary repositories)", () => { - const doc = yaml.parse(read(`.github/workflows/${WORKFLOW_NAME}`)) as Doc; + const doc = yaml.parse(read(`.github/retired-workflows/${WORKFLOW_NAME}`)) as Doc; const script = (Object.values(doc.jobs ?? {})[0]?.steps ?? [])[1]?.run ?? ""; const workflow = (head: string, extra = "") => `name: x\nenv:\n BUILDER_HEAD: '${head}'\n OTHER: 1\n${extra}`; function git(cwd: string, ...args: string[]): string { diff --git a/test/gates/guided-packet-pin.test.ts b/test/gates/guided-packet-pin.test.ts index 892c21a..b4829e5 100644 --- a/test/gates/guided-packet-pin.test.ts +++ b/test/gates/guided-packet-pin.test.ts @@ -94,6 +94,8 @@ const RUN5_ORIGINS: Record; placeholders: string[]; superseded: readonly string[] } /** The runs this directory pins, by run id. A pin whose run id is not here is refused. */ const RUNS: Record = { @@ -101,7 +103,7 @@ const RUNS: Record = { 39227365159: { branch: "cowork/guided-packet-compile-fixture-2", artifactId: 12174387240, origins: RUN2_ORIGINS, placeholders: [], superseded: RUN2_HISTORY_BUILDER_FILES }, 39327386044: { branch: "cowork/guided-packet-compile-fixture-3", artifactId: 12223150881, origins: RUN3_ORIGINS, placeholders: [], superseded: RUN3_SUPERSEDED_BUILDER_FILES }, 39391435225: { branch: "cowork/guided-packet-compile-fixture-4", artifactId: 12249582986, origins: RUN4_ORIGINS, placeholders: [], superseded: RUN4_SUPERSEDED_BUILDER_FILES }, - 39399991186: { branch: "cowork/guided-packet-compile-fixture-5", artifactId: 12254327073, origins: RUN5_ORIGINS, placeholders: [], superseded: [] }, + 39399991186: { branch: "cowork/guided-packet-compile-fixture-5", artifactId: 12254327073, origins: RUN5_ORIGINS, placeholders: [], superseded: RUN5_SUPERSEDED_BUILDER_FILES }, }; export function pinFiles(): string[] { @@ -109,7 +111,7 @@ export function pinFiles(): string[] { } export function loadContext(): Context { const inputs = JSON.parse(read(`${BUILD}/guided-compile-inputs.pin.json`)) as Inputs; - return { inputs, dependencyListSha256: sha256(`${BUILD}/dependencies.lst`), pinsPy: read(`${BUILD}/pins.py`), workflow: read(".github/workflows/cowork-guided-packet-compile-fixture.yml"), limits: inputs.limits, + return { inputs, dependencyListSha256: sha256(`${BUILD}/dependencies.lst`), pinsPy: read(`${BUILD}/pins.py`), workflow: read(".github/retired-workflows/cowork-guided-packet-compile-fixture.yml"), limits: inputs.limits, files: (path) => (existsSync(join(ROOT, path)) ? sha256(path) : null) }; } diff --git a/test/gates/release-gates.test.ts b/test/gates/release-gates.test.ts index 112b23a..89dacdb 100644 --- a/test/gates/release-gates.test.ts +++ b/test/gates/release-gates.test.ts @@ -10,13 +10,14 @@ // guard, and the absence of any committed Anthropic artifact (this repository's own hard rule). import { describe, expect, test } from "bun:test"; import { execFileSync } from "node:child_process"; -import { readdirSync, readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; const REPO_ROOT = resolve(import.meta.dir, "..", ".."); const WORKFLOW_DIR = join(REPO_ROOT, ".github", "workflows"); -const readWorkflow = (name: string): string => readFileSync(join(WORKFLOW_DIR, name), "utf8"); +const RETIRED_DIR = join(REPO_ROOT, ".github", "retired-workflows"); // spent one-run workflows: inert records, never run by GitHub +const readWorkflow = (name: string): string => readFileSync(join(existsSync(join(WORKFLOW_DIR, name)) ? WORKFLOW_DIR : RETIRED_DIR, name), "utf8"); /** * The parsed workflow. `Bun.YAML.parse` is the structural reading (review r1, M6): the text slice @@ -71,7 +72,7 @@ describe("the release workflow", () => { test("the publish steps are gated behind that workflow and nowhere else", () => { const workflows = readdirSync(WORKFLOW_DIR).sort(); - expect(workflows).toEqual(["ci.yml", "cowork-guided-build-source.yml", "cowork-guided-packet-compile-fixture.yml", "cowork-reader-abi-source.yml", "cowork-reader-build-source.yml", "cowork-reader-compile-fixture.yml", "cowork-reader-intake-source.yml", "cowork-reader-native-reviewed.yml", "cowork-reader-native-source.yml", "release.yml"]); + expect(workflows).toEqual(["ci.yml", "cowork-guided-build-source.yml", "cowork-reader-abi-source.yml", "cowork-reader-build-source.yml", "cowork-reader-intake-source.yml", "cowork-reader-native-source.yml", "release.yml"]); for (const name of workflows.filter((name) => name !== "release.yml")) { const source = readWorkflow(name); // Non-release workflows may explain this rule in comments, but cannot run publication. diff --git a/test/gates/retired-workflows.test.ts b/test/gates/retired-workflows.test.ts new file mode 100644 index 0000000..2ce6a43 --- /dev/null +++ b/test/gates/retired-workflows.test.ts @@ -0,0 +1,51 @@ +// The spent one-run workflows are RETIRED: moved out of `.github/workflows/` (GitHub runs nothing outside it) into `.github/retired-workflows/`, where the gate tests +// and the pin tests still read them as inert records (the guided compile pins hash the workflow's env; the reader gates assert its reviewed shape). +// +// Why moved and not edited to "disabled": each fired on the FIRST CREATION of a named branch (or a push to one with matching paths). Those branches now exist and are +// frozen, but deleting and recreating a name would fire a live file again, and an `if: false` edit is one revert from live. A file outside the workflows directory cannot run. +// +// Kept live: ci.yml, release.yml and the five `*-source` workflows (portable source tests on named branches; they arm and run nothing privileged). +import { describe, expect, test } from "bun:test"; +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { join, resolve } from "node:path"; + +const ROOT = resolve(import.meta.dir, "..", ".."); +export const RETIRED = ["cowork-guided-packet-compile-fixture.yml", "cowork-reader-compile-fixture.yml", "cowork-reader-native-reviewed.yml"] as const; +export const LIVE = ["ci.yml", "cowork-guided-build-source.yml", "cowork-reader-abi-source.yml", "cowork-reader-build-source.yml", "cowork-reader-intake-source.yml", "cowork-reader-native-source.yml", "release.yml"] as const; +/** First-creation branches of the retired one-run workflows. All exist and are frozen. */ +export const SPENT_BRANCHES = [ + "cowork/guided-packet-compile-fixture", "cowork/guided-packet-compile-fixture-2", "cowork/guided-packet-compile-fixture-3", "cowork/guided-packet-compile-fixture-4", + "cowork/guided-packet-compile-fixture-5", "cowork/instruction-reader-compile-fixture", +] as const; + +/** Pure: the violations of a listing of the live workflows directory (name -> text). */ +export function violations(files: Record): string[] { + const found: string[] = []; + for (const name of Object.keys(files).sort()) { + if ((RETIRED as readonly string[]).includes(name)) found.push(`retired workflow is back: ${name}`); + else if (!(LIVE as readonly string[]).includes(name)) found.push(`workflow is not on the allow-list: ${name}`); + for (const branch of SPENT_BRANCHES) if (new RegExp(`${branch.replace(/[/.]/g, "\\$&")}(?![\\w-])`).test(files[name]!)) found.push(`${name} names a spent branch: ${branch}`); + } + return found; +} +const live = (): Record => Object.fromEntries(readdirSync(join(ROOT, ".github/workflows")).filter((name) => /\.ya?ml$/.test(name)).map((name) => [name, readFileSync(join(ROOT, ".github/workflows", name), "utf8")])); + +describe("retired workflows stay out of .github/workflows", () => { + test("the retired files are records under .github/retired-workflows, not workflows", () => { + for (const name of RETIRED) { + expect(existsSync(join(ROOT, ".github/workflows", name))).toBe(false); + expect(existsSync(join(ROOT, ".github/retired-workflows", name))).toBe(true); + } + }); + test("the live workflows are exactly the allow-list and name no spent branch", () => { + expect(violations(live())).toEqual([]); + expect(Object.keys(live()).sort()).toEqual([...LIVE].sort()); + }); + test("the pure check flags a returned file, a new workflow and a spent branch name (mutations)", () => { + const base = live(); + expect(violations({ ...base, "cowork-guided-packet-compile-fixture.yml": "on: push" })).toEqual(["retired workflow is back: cowork-guided-packet-compile-fixture.yml"]); + expect(violations({ ...base, "one-run.yml": "on: push" })).toEqual(["workflow is not on the allow-list: one-run.yml"]); + expect(violations({ ...base, "ci.yml": `${base["ci.yml"]}\n# branches: [cowork/guided-packet-compile-fixture-5]` })).toEqual(["ci.yml names a spent branch: cowork/guided-packet-compile-fixture-5"]); + expect(violations({ ...base, "ci.yml": `${base["ci.yml"]}\n# cowork/guided-packet-compile-fixture-6` })).toEqual([]); + }); +});