polygate wires the Polygraph engines into a GitLab merge pipeline. The design rests on one fact: only spec generation calls a paid model — replay, model checking, and the compat gates run locally, free. So the pipeline splits into three jobs with three trust levels, and the org API key touches exactly one of them.
Runs on every merge request and on main: a staleness gate (content hash — did the machine change without re-verification?), the exhaustive model check against the invariants, and trace replay. All deterministic, no API key anywhere near it. Any failure blocks the merge.
The single job that can spend money or leak a key. Manual trigger, main only, fed by a masked + protected group variable — so the key simply does not exist in MR pipelines from forks or feature branches. It re-runs generation and re-stamps the verification manifest under governance.
Publishes the whitelisted bundle — hashes, verdicts, provenance — to a control plane. Nothing secret in it, nothing executable from it.
# .gitlab-ci.yml — the shape (full version in the polygate repo) stages: [verify, generate, publish] variables: POLYGRAPH_REPO: https://github.com/cognitive-fab/polygraph.git POLYGRAPH_SHA: <pinned commit> # bump deliberately, never track main polygraph-check: # every MR + main — FREE, keyless stage: verify script: - node scripts/check-stale.mjs # hash gate - node /tmp/polygraph/scripts/check.mjs … # exhaustive model check - node /tmp/polygraph/scripts/verify.mjs … # trace replay rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == "main" regenerate-specs: # manual, main only — the ONE key-bearing job stage: generate when: manual variables: ANTHROPIC_API_KEY: $POLYGRAPH_ORG_KEY # masked + protected — absent on MRs push-artifacts: # main — keyless provenance stage: publish needs: [polygraph-check]
The engines run from a pinned public clone — the gate's own code is content-addressed too, so "the gate passed" always means "this exact gate passed."
Both demonstrated end-to-end on a self-hosted GitLab CE mock, with the real engines and a real polygen-authored subscription-billing machine (12 states exhaustive, 0 violations, 65-window trace corpus).
Gates block unverified change. An MR edits the state machine without re-running verification. The staleness gate compares content hashes, fails the pipeline, and the merge is blocked — before any human review happens.
Gates survive a gamed hash. The adversarial case: an MR seeds a real bug and re-stamps the manifest so the hash gate passes. The exhaustive model check still fails it — with a shortest-path counterexample as the ready-made repro.
The polygate repo ships a complete self-hosted GitLab CE mock — Docker Compose, scripted configuration, both demo projects — so you can watch the gates fire without touching a production GitLab.
git clone https://github.com/cognitive-fab/polygate cd polygate docker compose -f ci-mock/docker-compose.yml up -d # wait for GitLab CE to boot (see §2 of docs/CI-MOCK-SETUP.md), then: pwsh ci-mock/setup-gitlab.ps1
On your own GitLab, the recipe is the skeleton above: copy the three-job split, pin the Polygraph clone, put the org key in a masked + protected group variable, and make polygraph-check a required check on merge requests.