@archigravity/gate
The ArchiGravity deploy gate, in your CI.
Your repo declares the part of the system it owns. On every pull request this runs that declaration against the rest of the system and answers one question: does this change break something downstream that nobody in this PR can see?
It computes nothing itself. It posts your declaration, renders the verdict the server returned, and exits with a code your branch protection understands.
Use it
# .github/workflows/archigravity-gate.yml — blocks the merge
name: ArchiGravity gate
on: pull_request
permissions:
contents: read
pull-requests: write
issues: write
jobs:
gate:
name: Merge gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx --yes @archigravity/gate@<version> check
env:
AG_GATE_KEY: ${{ secrets.AG_GATE_CHECK_KEY }}
AG_SYSTEM_ID: ${{ vars.AG_SYSTEM_ID }}
AG_FRAGMENT_KEY: ${{ vars.AG_FRAGMENT_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# .github/workflows/archigravity-ratify.yml — records what actually landed
name: ArchiGravity ratify
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
ratify:
name: Push ratify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx --yes @archigravity/gate@<version> ratify
env:
AG_GATE_KEY: ${{ secrets.AG_GATE_RATIFY_KEY }}
AG_SYSTEM_ID: ${{ vars.AG_SYSTEM_ID }}
AG_FRAGMENT_KEY: ${{ vars.AG_FRAGMENT_KEY }}
Two keys, and it matters which is where. Mint a gate:check key for the pull-request job and a
separate one with gate:ratify for the push job. Ratify is the call that records what shipped; a
pull request never needs it, and a key is easiest to keep narrow if it was never wide.
The secrets are set per STEP, not at the top of the file. A workflow-level env hands every secret
to every step of every job — including checkout and any third-party action you add later. Same
reason permissions sits on each job rather than on the file: the pull-request job is the only one
that can write comments, the ratify job the only one that can mint an identity token, and neither
can do the other's work.
That is also why check carries if: github.event_name == 'pull_request'. It has nothing to say
after a merge — the verdict comment it writes only exists on a pull request — so running it on push
would grant comment permissions to a job that cannot use them. The merge is ratify's to report.
Then mark Merge gate as a required check in branch protection. A red verdict now blocks the merge instead of arguing with it.
Two files rather than one with two jobs, on purpose: a single file means each lane is filtered out on the other's event, and GitHub lists a filtered job as Skipped — so every pull request would carry a skipped check forever, and the first thing anyone asks is which one was skipped and whether that is bad. Split, each fires only where it belongs and there is nothing to explain.
They are not interchangeable. Merge gate is the one that blocks. Push ratify keeps the model honest about what is actually deployed; leave it out and the gate slowly starts checking against a system that no longer exists.
Pin the exact version — replace <version> with the one at the top of this page. We would rather
you upgrade on a line you reviewed than have a new release arrive unannounced in a job that holds
your key. (connect and init write the pin for you, always the version that generated it.)
Setting it up
npx --yes @archigravity/gate connect
It asks for a setup token — minted on your system's page, short-lived, and the only value you
ever copy. Then it shows one plan and, on your yes: mints the two gate keys, puts them straight
into GitHub through your own gh (they never appear on screen), writes the workflows, the hook and
the marker, and tells you the one thing left to click. Which part of the system this repo is
becomes a numbered menu, not a field to mistype.
No gh on the machine? connect stops before creating anything and offers --print-secrets,
which prints the keys once for you to paste yourself.
The manual path underneath it stays available:
npx --yes @archigravity/gate init \
--system-id <your-system-id> \
--fragment-key <this repo's name in the system>
Writes the workflow above, a pre-push hook, and a marker. It prints the secrets and variables to set and never sets them — it has no business holding your credentials. Run it again any time; it changes nothing that is already right, and it will not overwrite a file you wrote (it tells you what to add instead). Once the marker exists you can drop both flags.
Both identifiers come from your system's page, and neither is guessed. The part name
(--fragment-key) in particular is what the system already knows this repository as — not the
folder name, which is often different. A wrong one is invisible rather than loud: everything agrees with itself and the
gate reads this repo as a second, unknown one.
Then, when something is not working:
npx --yes @archigravity/gate doctor
Exit code 2 from the gate covers "no key", "wrong key", "nothing declared", "not wired" and "we are
unreachable". doctor says which, and never prints a secret's value.
Commands
check | the pull-request verdict — posts this repo's part, renders the result, writes one sticky PR comment |
ratify | run after a merge to main: records what actually landed. Once configured, it never fails your pipeline |
pre-push | the freshness check, for a git pre-push hook |
init | wire this repo: workflow, hook, marker |
doctor | check the wiring and name what is missing |
login | on your computer: sign in with GitHub once — your pushes are then checked as yours |
whoami | who this computer is signed in as |
logout | sign out here and revoke the key on the server |
login shows a code to enter at github.com/login/device and keeps your sign-in in
~/.config/archigravity/credentials.json, readable only by you. What it keeps is your personal key: it lets your
agents and the gate act for you in your workspace — read and propose changes — for 90 days. You see it, and can
revoke it, under Settings → Agent access.
Configuration
All through the environment — never arguments, because arguments show up in ps output and in CI
logs.
AG_GATE_KEY | the gate key for this repo. A CI secret |
AG_SYSTEM_ID | which system this repo belongs to |
AG_FRAGMENT_KEY | which part of that system this repo declares |
AG_GATE_URL | your host, if you are not on the hosted app |
AG_FRAGMENT_PATH | defaults to .archigravity/fragment.tdl |
Exit codes
They mean different things to branch protection, so they are never merged together.
0 | the change is clear |
1 | red — this change breaks something downstream. The PR comment says what and why |
2 | the gate could not run: the key, the configuration, or the service being unreachable |
A 2 is our problem, not a verdict about your architecture. It blocks the merge anyway, because a
gate that waves changes through when it could not check them is not a gate.
What leaves your repo
No analysis runs here, and no telemetry, auto-update or runtime dependencies ship with it.
check and ratify send the hosted gate one thing: this repo's part — the declaration you
wrote and can read in your own repo. Your source is never read and never uploaded by them.
If you install this package in a JS repo that is already wired, it seeds the pre-push hook for you.
It does that only there: not in CI, not in a repo without a marker, and never over a hook you wrote.
Set AG_GATE_SKIP_POSTINSTALL=1 to turn it off entirely.
The optional pre-push hook is different, and worth reading before you install it. When a push
touches the code your part declares, it hands a versioned recipe to the CLI agent already on
your machine (claude or codex) and that agent reads your local diff, the way it does for any
other task you give it. That is your agent, on your machine, under your existing trust decision —
nothing about it is sent here. If you do not want it, do not install the hook: check is the
authority and it is complete without it.
Signed in (login), the hook also checks your part at each pushed commit and sends this repo's part — the
declaration in .archigravity/fragment.tdl at that commit, never your source — so the pull request can say "checked
locally by you". It never stops a push: signed out, offline or anything else, it prints one line and the push goes on.
Requirements
Node 20+, and nothing else. check and ratify are plain Node: they call the gate, render the
verdict and set the exit code, with no shell and no other tools on the runner.
The one exception is the optional pre-push hook, which is a shell script — it reads your git
history and hands the diff to a local agent, so it wants bash on your machine. It is optional by
design: check is the authority and it is complete without it.