spec(007): interpolate required formula vars — the change formula drops {{request}} (nh-knk) #246
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
| # Fast governance checks (pipeline Phase 1+). Pure bash, no toolchain — | |
| # feedback in seconds, long before the build workflows finish. | |
| # | |
| # Jobs: | |
| # doctor — enforces "every pipeline asset registers a ./dev verb or carries | |
| # a reasoned exemption" (see scripts/doctor for the contract). | |
| # lint — the NeoHaskell dialect gate: hlint with the dialect-first | |
| # .hlint.yaml (vanilla-module restrictions, banned partials, | |
| # teaching hints). Standalone pinned binary — no nix needed. | |
| name: checks | |
| on: | |
| pull_request: | |
| # ready_for_review included so the spec job's drift gate fires the moment | |
| # a draft (parked spec) is promoted — default types would miss the flip | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: checks-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| doctor: | |
| name: dev doctor (verb registration) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - run: ./dev doctor | |
| # Static workflow wiring: hidden-artifact upload opt-in + retrospect | |
| # loop stays deterministic/report-only (pure stdlib, no toolchain). | |
| - run: ./dev workflow-check | |
| # ADR-0075 process docs are triplicated by design (formula/skill/ADR | |
| # each self-contained); this catches them drifting apart. | |
| - run: ./dev process-check | |
| secrets: | |
| name: secret scan (gitleaks, deterministic) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # Pinned binary, not gitleaks-action (the action needs an org license). | |
| # Scans the working tree, not git history: deterministic on the state | |
| # being merged, no permanent red from historical noise. --redact keeps | |
| # any finding out of public CI logs. ADR-0075: deterministic checks | |
| # live here; agents (verify V9) only check what needs judgment. | |
| - name: Install gitleaks (pinned + checksum) | |
| run: | | |
| curl -sSfL -o gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz | |
| echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks.tar.gz" | sha256sum -c - | |
| tar -xzf gitleaks.tar.gz gitleaks | |
| - run: ./gitleaks dir . --redact --no-banner --exit-code 1 | |
| codemap: | |
| name: codemap check (localization ontology) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # Same check.py as ./dev codemap-check; runner python + pinned pyyaml | |
| # (pulling the full nix dev shell for a YAML check is not worth 40m — | |
| # the PYTHON override is the sanctioned CI path, like lint's HLINT=). | |
| - run: python3 -c "import yaml" 2>/dev/null || pip3 install pyyaml==6.0.3 | |
| - run: PYTHON=python3 ./dev codemap-check | |
| adr-index: | |
| name: adr index (decisions README faithful) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # Pure-stdlib python check: every docs/decisions/NNNN-*.md is indexed | |
| # exactly once with a status matching its own `## Status` section. | |
| - run: ./dev adr-check | |
| # Drift gate: the website ADR pages are generated from that same | |
| # (now-audited) index by website/scripts/generate-adrs.mjs (Node, on the | |
| # runner's default toolchain — no pnpm install, the generator is zero-dep). | |
| # Regenerates the gitignored per-ADR pages (proving each still transcribes) | |
| # and fails if the tracked landing page was hand-edited or left stale. | |
| - run: ./dev adr-website --check | |
| spec: | |
| name: spec check (contract-delta gate) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # spec-drift --pr diffs against the merge base — needs history | |
| fetch-depth: 0 | |
| # Structure gate: every committed spec (incl. TEMPLATE.md, which is a | |
| # validated instance) parses and machine-joins. Runs on drafts too — | |
| # this is what the maintainer's gate decision reads. | |
| - run: ./dev spec-check | |
| # Drift gate: at PR-READY only, the specs this PR touches must be | |
| # honored by the generated API surface (codemap-sync proves committed | |
| # signatures == code; this proves signatures == promise). A draft is | |
| # still implementing its spec — drift there is expected, not an error. | |
| - name: Spec drift (PR-ready only) | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| env: | |
| # env-var indirection, not inline ${{ }} in run: — the standard | |
| # template-injection defense-in-depth for workflow expressions | |
| BASE_REF: ${{ github.base_ref }} | |
| run: ./dev spec-drift --pr "origin/${BASE_REF}" | |
| # Review-record gate: at PR-ready, every COMMITTED design review a changed | |
| # spec ROUTES (touches ∩ perf-sensitive) must have its committed record. | |
| # Security reviews are local-only (gitignored, never pushed — they map | |
| # attack surface; ADR-0069), enforced on the dev machine via | |
| # `spec-check --reviews-local`, so CI's --reviews-pr gates only perf. | |
| - name: Design-review records present (PR-ready only) | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: ./dev spec-check --reviews-pr "origin/${BASE_REF}" | |
| # Changelog gate: at PR-ready, every changed spec must have a CHANGELOG.md | |
| # entry (breaking ones with a migration note) — the changelog is generated, | |
| # not hand-written (Phase 6, task 3). | |
| - name: Changelog entries present (PR-ready only) | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: ./dev changelog --check "origin/${BASE_REF}" | |
| # DoD criterion-test gate: at PR-ready, every criterion in a changed spec | |
| # must name a proving test that RESOLVES (a real .hurl or *.hs spec module) | |
| # — a criterion can't cite a test that was never written (Phase 6, task 1). | |
| - name: Criterion proving-tests resolve (PR-ready only) | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: ./dev spec-check --criteria-tests "origin/${BASE_REF}" | |
| expectations: | |
| name: expectation guard (test-contract diff) | |
| # PRs only (drafts included — weakening a test is never OK); never on push. | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # census-diffs committed test files against the merge base | |
| fetch-depth: 0 | |
| - name: Expectation census (maintainer label overrides) | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| # a maintainer-only PR label — labels need write access, so the agent | |
| # cannot self-apply it (unlike the local, gitignored marker). This is | |
| # the load-bearing approval signal. | |
| EXPECTATIONS_APPROVED: ${{ contains(github.event.pull_request.labels.*.name, 'expectations-approved') }} | |
| run: python3 .claude/hooks/expectation-guard.py --pr-diff "origin/${BASE_REF}" | |
| lint: | |
| name: hlint (dialect gate) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install hlint 3.10 (pinned binary + checksum) | |
| run: | | |
| curl -sSfL -o hlint.tar.gz \ | |
| https://github.com/ndmitchell/hlint/releases/download/v3.10/hlint-3.10-x86_64-linux.tar.gz | |
| echo "ccabc8802a58154699a3583b8dddc5ea2e6d65753a62c45c0e80088ebb16b42b hlint.tar.gz" | sha256sum -c - | |
| tar xzf hlint.tar.gz | |
| - name: Dialect gate | |
| run: HLINT=./hlint-3.10/hlint ./dev lint |