@archigravity/gate
v1.2.2
Published
ArchiGravity deploy gate — run the architecture check in CI and on push. Posts your declared fragment, renders the verdict the server computed; holds no secrets and decides nothing itself.
Maintainers
Readme
@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 connectIt 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 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 doctorExit 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 your fragment, renders the result, writes one sticky PR comment |
| ratify | run after a merge to main: records what actually landed. 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 |
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: your fragment — 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 fragment describes, 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.
Requirements
Node 20+. This version shells out, so it also wants bash and jq on the runner — both are present
on GitHub's hosted images. A future release drops that requirement — and it is being proven rather
than promised: every check run also computes the next-generation runner's answer from the same
server response and compares, byte for byte. If they ever disagree you will see a clearly marked
[shadow] block in the CI log; it changes nothing about your verdict, and pasting it into an issue
is the whole favour we ask. AG_SHADOW=off turns the comparison off.
