Merge pull request 'docs: pin governance records to Forgejo ceremony 0.6.3' (#42) from build/36-ceremony-pin-proof into main
All checks were successful
ci / test (push) Successful in 16s

Reviewed-on: #42
Reviewed-by: claude-bot-andresmgsl <andres+1@heavyduty.builders>
Reviewed-by: kimi-bot-andresmgsl <andres+4@heavyduty.builders>
Reviewed-by: glm-bot-andresmgsl <andres+5@heavyduty.builders>
This commit is contained in:
andres 2026-08-31 19:46:36 +00:00
commit 21b91e870e
4 changed files with 35 additions and 6 deletions

View file

@ -2,12 +2,13 @@
Never edit these files ad hoc. The six manifest-listed doctrine files are Never edit these files ad hoc. The six manifest-listed doctrine files are
byte-identical copies of 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 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. 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 The labels doctrine is vendored manually from
because stoke keeps its own tag-driven `.forgejo/workflows/release.yml`. [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 `docs-sync --fix` cannot run until or unless a future issue adopts the
ceremony release-workflow pin; until then, doctrine updates must re-vendor ceremony release-workflow pin; until then, doctrine updates must re-vendor
the pinned manifest manually. the pinned manifest manually.

View file

@ -1,7 +1,7 @@
# AGENTS.md — start at .ceremony/ # AGENTS.md — start at .ceremony/
This repository is governed by 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 `.ceremony/AGENTS.md` first — it routes you to your role file, vendored
beside it. Repo specifics (the review panel roster, the scope labels, what beside it. Repo specifics (the review panel roster, the scope labels, what
a drill means here, code conventions) live in CONTRIBUTING.md. a drill means here, code conventions) live in CONTRIBUTING.md.

1
changelog.d/36.md Normal file
View file

@ -0,0 +1 @@
- Enforced the exact Forgejo ceremony source and version across governance records and workflow pins. (#36).

View file

@ -11,6 +11,9 @@ const REPOSITORY_CONFIG = path.join(__dirname, '..', '.github', 'labels.conf');
const REPOSITORY_LABELER = path.join(__dirname, '..', '.github', 'labeler.yml'); const REPOSITORY_LABELER = path.join(__dirname, '..', '.github', 'labeler.yml');
const REPOSITORY_MIRROR = path.join(__dirname, '..', '.ceremony'); const REPOSITORY_MIRROR = path.join(__dirname, '..', '.ceremony');
const ROOT_AGENTS = path.join(__dirname, '..', 'AGENTS.md'); 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 = ['labels.yml', 'labels-sweep.yml'];
const cleanups = []; const cleanups = [];
process.on('exit', () => { process.on('exit', () => {
for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true });
@ -131,14 +134,38 @@ 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']; const vendored = ['AGENTS.md', 'TRIAGE.md', 'BUILDER.md', 'REVIEWER.md', 'LABELS.md', 'RELEASES.md'];
for (const filename of vendored) { for (const filename of vendored) {
assert.ok(fs.statSync(path.join(REPOSITORY_MIRROR, filename)).isFile(), `${filename} is missing`); assert.ok(fs.statSync(path.join(REPOSITORY_MIRROR, filename)).isFile(), `${filename} is missing`);
} }
const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8'); const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8');
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',
);
assert.match(mirrorReadme, /labels doctrine is vendored manually/); assert.match(mirrorReadme, /labels doctrine is vendored manually/);
assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/); assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/);
assert.doesNotMatch(mirrorReadme, /Machine-managed by|CI re-diffs them/); 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);
});
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}`,
);
}
}); });