From 52e8d45b78d6be1be678ca89a4548d1de07a5d7c Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 18:32:58 +0000 Subject: [PATCH 1/5] test: pin ceremony governance source --- .ceremony/README.md | 2 +- AGENTS.md | 2 +- test/governance.test.js | 23 +++++++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.ceremony/README.md b/.ceremony/README.md index c080fb7..1167b1b 100644 --- a/.ceremony/README.md +++ b/.ceremony/README.md @@ -2,7 +2,7 @@ Never edit these files ad hoc. The six manifest-listed doctrine files are byte-identical copies of -[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony) at 0.6.3, but +[heavy-duty/ceremony](https://forgejo.heavyduty.builders/heavy-duty/ceremony) at 0.6.3, but stoke does not run `docs-sync` or re-diff the mirror in CI. Change doctrine upstream through its own flow, then re-vendor it here when the pin moves. diff --git a/AGENTS.md b/AGENTS.md index e047dee..df97449 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # AGENTS.md — start at .ceremony/ This repository is governed by -[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony). Read +[heavy-duty/ceremony](https://forgejo.heavyduty.builders/heavy-duty/ceremony). Read `.ceremony/AGENTS.md` first — it routes you to your role file, vendored beside it. Repo specifics (the review panel roster, the scope labels, what a drill means here, code conventions) live in CONTRIBUTING.md. diff --git a/test/governance.test.js b/test/governance.test.js index 62eca37..11eb7c4 100644 --- a/test/governance.test.js +++ b/test/governance.test.js @@ -11,6 +11,12 @@ const REPOSITORY_CONFIG = path.join(__dirname, '..', '.github', 'labels.conf'); const REPOSITORY_LABELER = path.join(__dirname, '..', '.github', 'labeler.yml'); const REPOSITORY_MIRROR = path.join(__dirname, '..', '.ceremony'); const ROOT_AGENTS = path.join(__dirname, '..', 'AGENTS.md'); +const CEREMONY_REPOSITORY = 'https://forgejo.heavyduty.builders/heavy-duty/ceremony'; +const CEREMONY_VERSION = '0.6.3'; +const CEREMONY_WORKFLOWS = [ + path.join(__dirname, '..', '.forgejo', 'workflows', 'labels.yml'), + path.join(__dirname, '..', '.forgejo', 'workflows', 'labels-sweep.yml'), +]; const cleanups = []; process.on('exit', () => { for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); @@ -131,14 +137,27 @@ test('repository scope mapping covers every configured scope with the ruled path } }); -test('repository carries the complete 0.6.1 doctrine mirror and root router', () => { +test('repository carries the complete Forgejo 0.6.3 doctrine mirror and root router', () => { const vendored = ['AGENTS.md', 'TRIAGE.md', 'BUILDER.md', 'REVIEWER.md', 'LABELS.md', 'RELEASES.md']; for (const filename of vendored) { assert.ok(fs.statSync(path.join(REPOSITORY_MIRROR, filename)).isFile(), `${filename} is missing`); } const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8'); + assert.ok( + mirrorReadme.includes(`[heavy-duty/ceremony](${CEREMONY_REPOSITORY}) at ${CEREMONY_VERSION}`), + 'mirror README does not identify the exact Forgejo ceremony source and version', + ); + for (const workflow of CEREMONY_WORKFLOWS) { + const contents = fs.readFileSync(workflow, 'utf8'); + assert.match(contents, new RegExp(`uses: heavy-duty/ceremony/.github/workflows/[^@]+@${CEREMONY_VERSION}`)); + } assert.match(mirrorReadme, /labels doctrine is vendored manually/); assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/); assert.doesNotMatch(mirrorReadme, /Machine-managed by|CI re-diffs them/); - assert.match(fs.readFileSync(ROOT_AGENTS, 'utf8'), /read\s+`.ceremony\/AGENTS\.md` first/i); + const rootAgents = fs.readFileSync(ROOT_AGENTS, 'utf8'); + assert.ok( + rootAgents.includes(`[heavy-duty/ceremony](${CEREMONY_REPOSITORY})`), + 'root router does not identify the Forgejo ceremony repository', + ); + assert.match(rootAgents, /read\s+`.ceremony\/AGENTS\.md` first/i); }); -- 2.45.2 From 3fac8096f7dd4b6de5c5bf6765ba9cd4aaf5aef6 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 18:38:54 +0000 Subject: [PATCH 2/5] test: require exact ceremony workflow pins --- test/governance.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/governance.test.js b/test/governance.test.js index 11eb7c4..5a6e03c 100644 --- a/test/governance.test.js +++ b/test/governance.test.js @@ -13,10 +13,7 @@ const REPOSITORY_MIRROR = path.join(__dirname, '..', '.ceremony'); const ROOT_AGENTS = path.join(__dirname, '..', 'AGENTS.md'); const CEREMONY_REPOSITORY = 'https://forgejo.heavyduty.builders/heavy-duty/ceremony'; const CEREMONY_VERSION = '0.6.3'; -const CEREMONY_WORKFLOWS = [ - path.join(__dirname, '..', '.forgejo', 'workflows', 'labels.yml'), - path.join(__dirname, '..', '.forgejo', 'workflows', 'labels-sweep.yml'), -]; +const CEREMONY_WORKFLOWS = ['labels.yml', 'labels-sweep.yml']; const cleanups = []; process.on('exit', () => { for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); @@ -148,8 +145,11 @@ test('repository carries the complete Forgejo 0.6.3 doctrine mirror and root rou 'mirror README does not identify the exact Forgejo ceremony source and version', ); for (const workflow of CEREMONY_WORKFLOWS) { - const contents = fs.readFileSync(workflow, 'utf8'); - assert.match(contents, new RegExp(`uses: heavy-duty/ceremony/.github/workflows/[^@]+@${CEREMONY_VERSION}`)); + const contents = fs.readFileSync(path.join(__dirname, '..', '.forgejo', 'workflows', workflow), 'utf8'); + assert.ok( + contents.includes(`uses: heavy-duty/ceremony/.github/workflows/${workflow}@${CEREMONY_VERSION}`), + `${workflow} does not pin ceremony ${CEREMONY_VERSION}`, + ); } assert.match(mirrorReadme, /labels doctrine is vendored manually/); assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/); -- 2.45.2 From ed16f824efd8c6893ce52632fcbc484dd94b9fa4 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 19:27:00 +0000 Subject: [PATCH 3/5] docs: record governance pin enforcement --- changelog.d/36.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/36.md diff --git a/changelog.d/36.md b/changelog.d/36.md new file mode 100644 index 0000000..43695a4 --- /dev/null +++ b/changelog.d/36.md @@ -0,0 +1 @@ +- Enforced the exact Forgejo ceremony source and version across governance records and workflow pins. (#36). -- 2.45.2 From 21fcb1fdb171c51cfb800061a5ae2f0ecefae59c Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 19:29:55 +0000 Subject: [PATCH 4/5] docs: qualify every ceremony source record --- .ceremony/README.md | 5 +++-- test/governance.test.js | 15 +++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.ceremony/README.md b/.ceremony/README.md index 1167b1b..d8c6bf5 100644 --- a/.ceremony/README.md +++ b/.ceremony/README.md @@ -6,8 +6,9 @@ byte-identical copies of stoke does not run `docs-sync` or re-diff the mirror in CI. Change doctrine upstream through its own flow, then re-vendor it here when the pin moves. -The labels doctrine is vendored manually from heavy-duty/ceremony at 0.6.3 -because stoke keeps its own tag-driven `.forgejo/workflows/release.yml`. +The labels doctrine is vendored manually from +[heavy-duty/ceremony](https://forgejo.heavyduty.builders/heavy-duty/ceremony) at 0.6.3 because +stoke keeps its own tag-driven `.forgejo/workflows/release.yml`. `docs-sync --fix` cannot run until or unless a future issue adopts the ceremony release-workflow pin; until then, doctrine updates must re-vendor the pinned manifest manually. diff --git a/test/governance.test.js b/test/governance.test.js index 5a6e03c..28a8baa 100644 --- a/test/governance.test.js +++ b/test/governance.test.js @@ -140,17 +140,12 @@ test('repository carries the complete Forgejo 0.6.3 doctrine mirror and root rou assert.ok(fs.statSync(path.join(REPOSITORY_MIRROR, filename)).isFile(), `${filename} is missing`); } const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8'); - assert.ok( - mirrorReadme.includes(`[heavy-duty/ceremony](${CEREMONY_REPOSITORY}) at ${CEREMONY_VERSION}`), - 'mirror README does not identify the exact Forgejo ceremony source and version', + const sourceVersionRecord = `[heavy-duty/ceremony](${CEREMONY_REPOSITORY}) at ${CEREMONY_VERSION}`; + assert.equal( + mirrorReadme.split(sourceVersionRecord).length - 1, + 2, + 'mirror README does not identify the exact Forgejo ceremony source and version in both records', ); - for (const workflow of CEREMONY_WORKFLOWS) { - const contents = fs.readFileSync(path.join(__dirname, '..', '.forgejo', 'workflows', workflow), 'utf8'); - assert.ok( - contents.includes(`uses: heavy-duty/ceremony/.github/workflows/${workflow}@${CEREMONY_VERSION}`), - `${workflow} does not pin ceremony ${CEREMONY_VERSION}`, - ); - } assert.match(mirrorReadme, /labels doctrine is vendored manually/); assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/); assert.doesNotMatch(mirrorReadme, /Machine-managed by|CI re-diffs them/); -- 2.45.2 From c900f47d777db03d067f8722b0e88e92cbd9bc1b Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 19:30:33 +0000 Subject: [PATCH 5/5] test: reject ceremony pin suffix drift --- test/governance.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/governance.test.js b/test/governance.test.js index 28a8baa..85607c8 100644 --- a/test/governance.test.js +++ b/test/governance.test.js @@ -156,3 +156,16 @@ test('repository carries the complete Forgejo 0.6.3 doctrine mirror and root rou ); assert.match(rootAgents, /read\s+`.ceremony\/AGENTS\.md` first/i); }); + +test('repository workflow pins use the exact Forgejo ceremony version', () => { + for (const workflow of CEREMONY_WORKFLOWS) { + const contents = fs.readFileSync(path.join(__dirname, '..', '.forgejo', 'workflows', workflow), 'utf8'); + const prefix = `uses: heavy-duty/ceremony/.github/workflows/${workflow}@`; + const pins = contents.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith(prefix)); + assert.deepEqual( + pins, + [`${prefix}${CEREMONY_VERSION}`], + `${workflow} does not pin ceremony ${CEREMONY_VERSION}`, + ); + } +}); -- 2.45.2