Skip to content

spec(007): interpolate required formula vars — the change formula drops {{request}} (nh-knk) #246

spec(007): interpolate required formula vars — the change formula drops {{request}} (nh-knk)

spec(007): interpolate required formula vars — the change formula drops {{request}} (nh-knk) #246

Workflow file for this run

# 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