name: CI on: pull_request: push: branches: [main] permissions: contents: read jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Shellcheck run: bash .github/scripts/shellcheck-all.sh - name: Install actionlint env: ACTIONLINT_VERSION: 1.7.12 run: | curl -fsSLo actionlint.tar.gz \ "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" tar -xzf actionlint.tar.gz actionlint sudo install actionlint /usr/local/bin/actionlint - name: Actionlint run: bash .github/scripts/actionlint-all.sh - name: Self-ref pin # The pin rules (issue #9; #1 D3): a stale CEREMONY_SELF_REF fails # CI here, not a consumer's release. run: bash .github/scripts/self-ref-check.sh - name: Tests env: # The npm-backed version_write case may skip locally when npm is # absent; in CI a skip must be a failure, or the case could # quietly stop running (issue #3's test contract). CEREMONY_REQUIRE_NPM: 1 run: bash test/run.sh # The release exercise (issue #9's scratch caller) on every PR, so the # parse proof and the merge door's step-replay are standing, reviewable # evidence — not a dispatch someone must remember to run. PR-ONLY, and # the gate is load-bearing: this CI also runs on push to main, and a # workflow_call from THAT context would hand release.yml a genuine # push+refs/heads/main event — the merge door's exact gate — opening a # live door from CI. A pull_request event can never satisfy either # door's `if:`. release-exercise: if: github.event_name == 'pull_request' uses: ./.github/workflows/release-exercise.yml # The self-guards (issue #11): this repo eats exactly what it serves. The # guard actions run against the REAL tree — VERSION, CHANGELOG.md, # drills/, .github/workflows/ — through the same `uses:` steps every # consumer's CI carries. # These steps are also the composite-action wiring proof (issue #5's # acceptance criterion: action.yml resolving, $GITHUB_ACTION_PATH, the # relative lib sourcing) that action-exercise carried with scratch files # while this repo had no tree of its own to guard; the armed and # drill-recorded scratch steps moved here per the armed step's own # eviction note — the file backend hardcodes the VERSION name, so a # scratch write would SHADOW the real file, not sit beside it. self-guards: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # The monotonic guard compares HEAD against the merge base; a # shallow checkout cannot resolve it, and in CI that is a hard # failure, not a skip (the action's description). fetch-depth: 0 - uses: ./actions/changelog-armed - uses: ./actions/changelog-monotonic - uses: ./actions/drill-recorded - uses: ./actions/runner-isolated # Exercises changelog-monotonic the way a consumer does, against a # CONSTRUCTED history. The self-guards job above runs the same action on # the real tree, but there its containment half is only as interesting as # the PR's own diff; this job commits a known base and an insert-above # edit on top, so a real, non-vacuous containment run is standing # evidence on every PR. (Armed and drill-recorded moved to self-guards — # the real tree now exercises them; monotonic stays because it reads no # version source, so it is immune to the VERSION-shadowing problem that # evicted the other two.) action-exercise: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Construct a scratch history for the monotonic guard # The monotonic guard's input is a DIFF, so its exercise needs # history, not just a file: commit a scratch changelog, mark that # commit as the fixture base, then commit an insert-above edit on # top — a real containment run, not just an action.yml parse. The # base ref is the in-job branch, passed explicitly, because this # job's shallow PR checkout carries no origin/main for the input's # default to resolve (consumers get that via fetch-depth: 0, per # the action's description). Scratch-named file so the real # CHANGELOG.md is never shadowed; the commits live only in this # job's checkout and are never pushed. run: | git config user.name ceremony-ci git config user.email ceremony-ci@users.noreply.github.com printf '# Changelog\n\n## Unreleased\n\n## 0.1.0 — 2026-07-01\n\n- Shipped entry.\n' > CHANGELOG.monotonic.scratch.md git add CHANGELOG.monotonic.scratch.md git commit -m 'fixture: monotonic base' git branch monotonic-fixture-base printf '# Changelog\n\n## Unreleased\n\n- Entry inserted above.\n\n## 0.1.0 — 2026-07-01\n\n- Shipped entry.\n' > CHANGELOG.monotonic.scratch.md git commit -am 'fixture: insert above' - uses: ./actions/changelog-monotonic with: changelog: CHANGELOG.monotonic.scratch.md base-ref: monotonic-fixture-base # Exercises actions/docs-sync the way a consumer does (issue #19's # acceptance criterion). Its own job, unlike the exercises above: the # composite reads the CONSUMER's tree at the workspace root, and a # `uses:` step cannot change directory — so the fixture consumer must BE # the workspace root, with ceremony itself checked out to a subdirectory # (that path also serves as the action reference and the --source # override; no ref carrying docs/VENDORED.txt exists to fetch until this # lands, and the exercised bytes should be THIS PR's anyway). docs-sync-exercise: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: path: ceremony-src - name: Construct a fixture consumer at the workspace root # The pin ref is scratch — --source overrides the fetch, but the # pin line itself is still parsed and required (one pin governs # machinery and doctrine; a consumer without one has nothing for # the mirror to be verified against). run: | mkdir -p .github/workflows printf '%s\n' \ 'name: release' \ 'on:' \ ' push:' \ ' branches: [main]' \ 'jobs:' \ ' release:' \ ' uses: heavy-duty/ceremony/.github/workflows/release.yml@0.0.0-fixture' \ > .github/workflows/release.yml - name: Bootstrap the mirror (--fix) uses: ./ceremony-src/actions/docs-sync with: mode: fix source: ceremony-src - name: Verify the mirror (--check, the mode consumers run) uses: ./ceremony-src/actions/docs-sync with: source: ceremony-src