Skip to content

The gate

The deterministic half of the delivery gate. One Go package that answers one question about a pull request: does this change what actually gets deployed, and is what it produces still valid?

The same engine ships in two forms, reaching the same verdict. The agent imports it and runs it in-cluster against the live ArgoCD inventory — the default since ADR 0008, and the reason onboarding no longer involves this directory at all. And it ships as a CLI (cmd/gitops-gate) whose exit code is the verdict, for running locally before a push and for the CI fallback; adapters in ../ci are thin wrappers that pass a workspace in and turn the exit code into a commit status. The CLI renders against a checked-in inventory snapshot, which is what clusters export maintains.

Status: shipped and judging every pull request on the platform it was built for, published as ghcr.io/jamesatintegratnio/gitops-gate. CHANGELOG.md records what has changed since. No model is involved at this layer: the AI half lives in the agent that calls this package.

CommandDoes
renderRenders every bootstrap ApplicationSet declared in .gitops-gate.yaml, for every cluster in the inventory, expanding the generators. Emits a normalized target table.
diffCompares two target tables. With -repo, also renders every chart whose version moved — at both versions — and diffs the resources, down to the fields that changed. Emits the report and render-diff.json.
validateSchema-validates every rendered stream.
clusters exportRegenerates the cluster inventory from live ArgoCD cluster Secrets. Workstation only — shells out to kubectl against a kubeconfig, and kubectl is not in the gate’s image.

helm and kubeconform, both pinned. Nothing else. Two paths therefore need a binary the image does not have, and both say so rather than failing obscurely:

PathNeedsWhere it runs
clusters exportkubectla workstation, against a kubeconfig. The in-cluster gate reads the same Secrets through the apiserver instead.
a kustomize source in .gitops-gate.yamlkustomize or kubectla workstation, or a CI runner that installs one. Not in-cluster.
FindingBlocksBecause
Cluster targeting changedyesa values-layer edit can add or remove a whole cluster from an addon’s scope without the text diff showing it — the selector did not change, the labels it matches did. Rendering both sides and diffing the expanded result is the only way to see it
Source, project or namespace changedyesno version bump can cause these, so nobody has explained them
An object’s apiVersion movedyesa migration wearing a version number — it renders perfectly and breaks at runtime
A CRD stops serving a version that manifests in the repository still declareyes, while any remainthose manifests break at apply. The report names the consumer kind, the surviving version and the declaring files — which is exactly the contract the agent’s deterministic repair executes, and the recount on the re-run is what verifies it. Counted at zero, the finding is reported and does not block
Resources added, removed, changed; versions movedno, reportedthat is what a version bump legitimately does — reported with per-field diffs so the reviewer judges evidence, not a count
CodeMeaning
0No blocking change.
1Blocking change — see the table above.
2The gate itself could not run (bad config, unreachable chart repo). Distinct from 1 so CI can tell “this change is bad” from “the gate is broken”.