chore(release): sync dev to main for v1.0.37 #170
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: SpecGit Acceptance | |
| on: | |
| pull_request: | |
| # Delivery PRs target dev (fast-integration layer); the acceptance | |
| # verdict runs only on the dev→main promotion PR, where protect-main's | |
| # checks apply. Keep the trigger main-only (d6ce53a83): running it on | |
| # dev PRs duplicated the verdict against the lighter dev gate. | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| specgit-acceptance: | |
| name: SpecGit Acceptance | |
| runs-on: ubuntu-latest | |
| # Must exceed the slowest required sibling (Unit Tests (linux) runs | |
| # ~28min on PRs): the verdict waits for every policy check to reach a | |
| # terminal state before evaluating. | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # Check out the PR head branch by name so HEAD is on the branch | |
| # (not the detached merge ref): the execution context gate reads | |
| # live git. Falls back to the default ref on non-PR events. | |
| ref: ${{ github.head_ref || github.ref }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '22' | |
| # This repo is a bun workspace and does not vendor the SpecGit CLI; | |
| # install the published CLI instead of building from source. Pinned | |
| # with a caret floor (#366): the CLI releases multiple times a day and | |
| # an unpinned install would let an unnoticed upstream change flip CI | |
| # acceptance verdicts repo-wide. | |
| - name: Install specgit CLI | |
| run: npm install -g specgit@^0.5.0 | |
| - name: Wait for sibling checks | |
| # The verdict must see the OTHER required checks in a terminal | |
| # state. Sibling jobs start in parallel AND may not have registered | |
| # their check-runs yet, so an empty poll is not "done": wait until | |
| # every name in spec_git/policy.yaml is present with a terminal | |
| # conclusion. This job is not in the policy, so no self-deadlock. | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| WAIT_REPO: ${{ github.repository }} | |
| WAIT_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| node --input-type=module <<'EOF' | |
| import { readFileSync } from 'node:fs'; | |
| // Minimal parse of policy.yaml's required_checks block list — | |
| // avoids a yaml dependency in this bun-based repo. | |
| const policy = readFileSync('spec_git/policy.yaml', 'utf8'); | |
| const section = policy.slice(policy.indexOf('required_checks:')); | |
| const required = [...section.matchAll(/^\s*-\s*(.+)$/gm)].map((m) => m[1].trim()); | |
| const headers = { | |
| authorization: 'Bearer ' + process.env.GH_TOKEN, | |
| accept: 'application/vnd.github+json', | |
| }; | |
| const url = 'https://api.github.com/repos/' + process.env.WAIT_REPO | |
| + '/commits/' + process.env.WAIT_SHA + '/check-runs?per_page=100'; | |
| const terminal = new Set(['completed']); | |
| const terminalHas = (byName, name) => { | |
| if (byName.has(name)) return terminal.has(byName.get(name)); | |
| const retried = [...byName.keys()].find((k) => k.startsWith(name + ' (')); | |
| return retried !== undefined && terminal.has(byName.get(retried)); | |
| }; | |
| // Must outlast the slowest required sibling (Unit Tests (linux) | |
| // runs ~28min on PRs); the job timeout above bounds this too. | |
| const deadline = Date.now() + 40 * 60 * 1000; | |
| while (Date.now() < deadline) { | |
| const res = await fetch(url, { headers }); | |
| if (!res.ok) throw new Error('check-runs API ' + res.status); | |
| const payload = await res.json(); | |
| const byName = new Map(payload.check_runs.map((r) => [r.name, r.status])); | |
| const missing = required.filter((n) => !terminalHas(byName, n)); | |
| if (missing.length === 0) { | |
| console.log('All required checks are in a terminal state.'); | |
| process.exit(0); | |
| } | |
| console.log('Waiting for: ' + missing.join(', ')); | |
| await new Promise((r) => setTimeout(r, 10000)); | |
| } | |
| console.error('Timed out waiting for sibling checks.'); | |
| process.exit(1); | |
| EOF | |
| - name: specgit finish | |
| run: specgit finish --json | |
| env: | |
| GH_TOKEN: ${{ github.token }} |