Airframe

Why Airframe

Guardrails as code

Every change to a product's infrastructure and pipeline, whoever or whatever wrote it, is checked by tested rules in its merge request, and its plan again before anything is applied. People approve what a change is for; the rules check how it is done.

How a change is checked

  1. The rules are code, with tests

    The policy pack is a set of Open Policy Agent rules in the control-plane repository. Conftest runs them on a product's Terraform code and pipeline file in the merge request, and on the Terraform plan before every apply; from release 2026.10.01-1, the Dockerfiles a product declares for its builds are checked on every merge request that changes one named Dockerfile, Dockerfile.* or *.Dockerfile. By convention each rule ships a case that must pass and one that must fail, and a new deny rule usually spends its first release as a warning; the pack's own pipeline runs every test on each change to it.

  2. Validation a product cannot switch off

    GitLab pipeline execution policies in the security-policies project inject jobs that a repository's own pipeline file cannot remove or replace: the gate's integrity check into every pipeline in the group, and the validation and test evidence into every pipeline of a product repository.

  3. The message says what is wrong

    A failing rule names the resource it judged and what is wrong with it, and many also say what to do instead, in words written for whoever reads them next, a person or a coding agent.

  4. Only the platform's components deploy

    A product's pipeline file may include only the control-plane repository's own CI components, and none of its own jobs may declare a deployment environment. A component from anywhere else is denied, and the gate fails a pipeline that includes a revoked release of the platform's.

  5. People approve intent

    Every merge request to a default branch needs the approval of a named person from the catalog who neither wrote nor committed it, never a role. Today, in every repository but security-policies, which holds the approval rules themselves, it also needs the gate's approval, which the gate adds only once the owners the catalog names have approved the latest commit: a budget rise or a new service needs a domain lead, and where the lead has no account, as in the demo, the platform team stands in.

Try it

Pick a change a team might propose. The verdict, the rule and the message are the policy pack's own, judged at the platform release this site describes.

A proposed change
Denied

File: main.tf

resource "google_storage_bucket_iam_member" "public" {
  bucket = "orders-exports-k3d8"
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}
  • Denied iam.public-principal

    google_storage_bucket_iam_member.public: public principal "allUsers" is not allowed; expose through external-ingress behind IAP

    Nothing is ever granted to everyone, and the deny is enforced. A public route through the platform's external ingress behind IAP is designed; today a public service gets a route on the shared edge, behind single sign-on.

  • Warning naming.raw-resource

    google_storage_bucket_iam_member.public: a raw google_storage_bucket_iam_member in a product root needs a reason in the merge request (platform approval); compose the registry's modules where one covers it (skill airframe-naming)

    A resource the product declares itself, instead of composing a module, needs a reason in the merge request. The policy pack warns about it; the platform's approval of it is designed, and today a warning holds nothing back.

These rules run on your laptop in airframe validate, and again in the merge request's validate:policy job.

What makes it true

The policy pack's tests run on every change to it.
The control-plane repository's policy:test job runs Conftest's unit tests and the pass and fail fixtures on every merge request that changes the pack, and GitLab merges only a merge request whose pipeline succeeds. That a new rule brings its own tests is a convention held in review, not a check.
A product repository cannot skip validation.
The product pipeline execution policy in the security-policies project injects the validate and build-test components into every pipeline of the repositories in the journey groups, and GitLab lets no project's own file remove or replace an injected job.
A product's pipeline file includes only the platform's components, and none of its own jobs deploys.
The pack's pipeline rules deny an include that is not one of the control-plane repository's eight named components, a job that declares an environment and a job that triggers another pipeline; the injected validation runs them on the product's file.
A person's approval of a merge request to a default branch comes from a named person, not a role.
The approval policy lists its approvers by name and excludes the author and the committers. The security-policies project's policy:approvers job reports any role, group or name that differs from the catalog's active people with a GitLab account. What holds the list there is a named person's approval of each change, never its author's. The gate's service account is the one approver of a separate policy and cannot give the person's approval.
A rule that denies stops the change.
A denial fails the injected validation job, and GitLab merges a product's merge request only when its pipeline succeeds; on main, the tf-apply component's policy job runs the pack on the plan, and the apply needs that job to pass. The cost rules run in the executor instead: their refusal fails the merge request's Airframe cost status check, which must pass before GitLab merges, and stops the plan on main.