Skip to content

Policy rules

The policy pack is the platform’s rules written as code, and this page lists every one of them: what it checks, whether it stops a change or only warns, and what its message tells you to do instead. When a merge request fails on a rule, find the rule here, read the alternative, and change the code to match.

The policy page explains where the rules run and what their two verdicts do. In short, the same rules run through airframe validate on a laptop, in the merge request’s validation, in the executor’s runs at approval, and in policy:<root> on the plan before every apply.

Each row is one rule, with three facts and a source:

  • Level is one of three:
    • deny: the job fails, and GitLab merges a merge request only when its pipeline passes (enforced).
    • warn: the job prints the finding and passes. The design asks for the platform’s approval of every warning; that approval is designed, so today a warning holds nothing back.
    • warn in this release, deny in the next: a new rule that warns first, so teams can fix what it would refuse. That it then becomes a deny is a convention: nothing changes the level on its own, and some rules marked so are still warnings.
  • What it checks says what the rule looks at. On the Terraform code the pack judges only values written out literally; on the plan it sees every value.
  • What its message points to is the conforming alternative the message names, where it names one.
  • Source is the file of the pack that holds the rule. The pack’s own list is docs/policy-pack.md; a rule whose source says it is not listed there is in the code but missing from that list.

The tables describe the pack at release 2026.10.01-1, whose control-plane commit is c53c0bc.

No public principal

Level
Deny
What it checks
A grant to allUsers or allAuthenticatedUsers in any IAM member, binding or policy.
What its message points to
Expose the service through external ingress behind IAP, which is designed; the built path is a route on the shared edge, behind single sign-on.
Source
control-plane policy/airframe/iam.rego

No owner or editor role

Level
Deny
What it checks
The roles roles/owner and roles/editor, in any IAM shape. The viewer role is allowed: it is the designed standing read.
What its message points to
Grant a predefined or custom role.
Source
control-plane policy/airframe/iam.rego

No service account key

Level
Deny
What it checks
Any service account key resource.
What its message points to
Use workload identity federation.
Source
control-plane policy/airframe/iam.rego

No IAM administration for an unlisted member

Level
Warn in this release, deny in the next
What it checks
A role that administers IAM, such as Project IAM Admin, bound at a project, folder or organization to a member the pack does not list for it.
What its message points to
None: a root's roles are the scope its README states, never IAM administration.
Source
control-plane policy/airframe/iam.rego, input.rego; not listed in docs/policy-pack.md

The platform's labels

Level
Deny
What it checks
A resource that declares labels must carry managed_by, tenant, domain, product, env and criticality. KMS keys, Secret Manager secrets and Cloud Identity groups are exempt.
What its message points to
The message lists the missing labels. The golden path's modules set them all.
Source
control-plane policy/airframe/naming.rego

Project ids follow the naming rules

Level
Deny
What it checks
A project id written out must be a product's lk-<domain>-<product>[-np]-<rnd>, a platform project's or a playground's.
What its message points to
The naming rules of the contracts, section 2.
Source
control-plane policy/airframe/naming.rego

A raw resource needs a reason

Level
Warn
What it checks
Every resource a product root declares itself rather than through a golden path module.
What its message points to
Give a reason in the merge request, and use the golden path's modules where one covers the resource.
Source
control-plane policy/airframe/naming.rego

A raw resource's name starts with the product's

Level
Warn in this release, deny in the next
What it checks
A raw resource whose name does not start with its product label and a hyphen.
What its message points to
Start the name with the product's slug, as the contracts' naming rules say.
Source
control-plane policy/airframe/naming.rego

One region

Level
Deny
What it checks
Any region or location written out that is not us-central1, a zone in it, or global.
What its message points to
The platform's region, us-central1, which the message names.
Source
control-plane policy/airframe/naming.rego

No project id or number written in the code

Level
Warn in this release, deny in the next
What it checks
Outside the project factories, any string with the shape of a project id or a project number, except the bootstrap project's own and the platform's bucket names of the same shape.
What its message points to
Take the project from the factory's outputs or the product's Ready output, because a rebuilt project changes both.
Source
control-plane policy/airframe/literal_ids.rego; not listed in docs/policy-pack.md

No external programs, web reads or provisioners

Level
Deny
What it checks
The external data source; an http data source to an address not on the allow-list, which is empty; and a provisioner on any resource.
What its message points to
None.
Source
control-plane policy/airframe/code.rego, data.json

Allowed providers and a pinned Terraform version

Level
Deny
What it checks
On the code, a provider from outside the allowed namespaces (hashicorp/, gitlabhq/, grafana/, integrations/), and a root with no required_version. That backend.tf holds only the backend is in the pack's list but no rule checks it, so that part is designed.
What its message points to
Pin the platform's Terraform version.
Source
control-plane policy/airframe/code.rego, data.json

Remote state only between declared pairs

Level
Deny
What it checks
In a product root, reading another root's state, except a preview root reading its product's non-production state.
What its message points to
None beyond the rule: reading state is only for the declared producer and consumer pairs.
Source
control-plane policy/airframe/code.rego, data.json

Ingress only through single sign-on or the edge

Level
Deny
What it checks
A Cloud Run service open to all traffic without IAP, unless it is a preview's service or a named self-authenticating platform service; and load-balancer-only ingress on a service that is neither on the shared edge nor registered for external ingress.
What its message points to
A preview is reached through its router with the invoker check on; other services go on the shared edge.
Source
control-plane policy/airframe/cloud_run.rego

The invoker check stays on

Level
Deny
What it checks
A Cloud Run service with its invoker check turned off, except a named self-authenticating service in a platform root or a service on the shared edge.
What its message points to
Only the named self-authenticating services, today the gate and the telemetry gateway, or a service on the edge may turn it off.
Source
control-plane policy/airframe/cloud_run.rego, data.json

An edge service keeps its invoker check

Level
Warn in this release, deny in the next
What it checks
A service on the shared edge with its invoker check off, other than this website.
What its message points to
Keep the check on with IAP's service agent as the only invoker, by rendering the product again at the current golden path modules.
Source
control-plane policy/airframe/cloud_run.rego; not listed in docs/policy-pack.md

A product backend has single sign-on

Level
Deny
What it checks
In a product root, a load balancer backend service without IAP.
What its message points to
A public route uses the external-ingress building block, which is designed.
Source
control-plane policy/airframe/cloud_run.rego; not listed in docs/policy-pack.md

No public database address

Level
Deny
What it checks
A Cloud SQL instance with a public IPv4 address.
What its message points to
None.
Source
control-plane policy/airframe/cloud_run.rego

Minimum instances above zero

Level
Warn
What it checks
A Cloud Run service that keeps instances running, apart from the sandbox's one warm instance.
What its message points to
Platform approval, outside criticality 1; the approval is designed.
Source
control-plane policy/airframe/cloud_run.rego

A sidecar database is a preview's alone

Level
Warn in this release, deny in the next
What it checks
A container named database beside a service that is not a preview's.
What its message points to
Production and non-production use Cloud SQL.
Source
control-plane policy/airframe/cloud_run.rego; not listed in docs/policy-pack.md

A service with a sidecar database runs one instance

Level
Warn in this release, deny in the next
What it checks
A service with a database container that may scale to more than one instance.
What its message points to
Run one instance at most, since its data lives in the instance.
Source
control-plane policy/airframe/cloud_run.rego; not listed in docs/policy-pack.md

A preview creates no database instance

Level
Warn in this release, deny in the next
What it checks
A Cloud SQL, Redis or AlloyDB instance labeled with a preview's environment.
What its message points to
Use the shared instance of the preview substrate or a sidecar.
Source
control-plane policy/airframe/cloud_run.rego; not listed in docs/policy-pack.md

No platform-owned resource in a product root

Level
Deny
What it checks
In a product root, any of the types the platform owns, such as networks, subnets, firewalls, DNS zones, certificates, projects, folders, enabled APIs, organization policies, deny policies, perimeters, identity pools, routers and NAT.
What its message points to
None: these belong to the platform's roots.
Source
control-plane policy/airframe/platform_owned.rego

Secret Manager is the only store, and no value passes through Terraform

Level
Deny
What it checks
A secret version with its value in secret_data, and a secret in Vault, AWS or Azure. A version whose value is write-only, secret_data_wo, passes, because it stays out of the plan and the state.
What its message points to
A maintainer adds the value in Secret Manager, or the value is write-only.
Source
control-plane policy/airframe/platform_owned.rego

No project, database, bucket or repository is destroyed outside a removal

Level
Warn in this release, deny in the next
What it checks
A plan that destroys or replaces a project, a Cloud SQL instance, a bucket or a GitLab repository, unless it belongs to a product the catalog says is removed with its date passed. The project factories' deletion protection on an active product's projects is the floor beneath it.
What its message points to
Set the product's lifecycle to removed, after retire_after, in the catalog.
Source
control-plane policy/airframe/lifecycle.rego

The trusted runner is not replaced from its own pipeline

Level
Deny
What it checks
A plan that deletes or replaces the trusted runner's instance, which would end the job applying it.
What its message points to
Apply that plan from a laptop under the platform-apply grant.
Source
control-plane policy/airframe/runner.rego; not listed in docs/policy-pack.md

These rules read a repository’s .gitlab-ci.yml. Platform repositories are exempt from those about a product’s file.

A product's file keeps the platform's includes

Level
Warn in this release, deny in the next
What it checks
A product's file must include tf-apply and drift-report, must not include the injected validate or build-test again, must not name a job like a platform job, such as plan: or gate:, and must not declare the policy stages.
What its message points to
Keep the includes the template rendered, remove an injected include or a policy stage, and give your own job another name.
Source
control-plane policy/airframe/pipeline.rego

Components are pinned to a commit

Level
Warn in this release, deny in the next
What it checks
In every repository, a control-plane component included at a branch, a tag or a short commit.
What its message points to
Pin it to a full control-plane commit, as airframe pin <sha> does.
Source
control-plane policy/airframe/pipeline.rego

A product's own job never deploys

Level
Deny
What it checks
A product's job that declares an environment, directly or through extends; an include that is not a component; and a job that triggers another pipeline.
What its message points to
Remove the environment and deploy through the tf-apply component.
Source
control-plane policy/airframe/pipeline.rego, docs/adr/0026-a-product-job-never-deploys.md

Only the platform's components

Level
Deny
What it checks
A product's include of a component that is not one of control-plane's own eight: tf-apply, drift-report, image-build, promote-artifact, catalog-publish, reconcile, validate and build-test.
What its message points to
The message lists the eight components a product may include.
Source
control-plane policy/airframe/pipeline.rego

Images start from a platform base image

Level
Deny
What it checks
In each Dockerfile .airframe/images.yaml lists, every FROM and every COPY --from or ADD --from that is not a stage of the same file must name a platform base image, pinned by a tag other than latest or by a digest, and never computed from a build argument.
What its message points to
Build from the platform's base image for the stack, named by its tag or its digest.
Source
control-plane policy/airframe/dockerfile.rego, docs/adr/0014-approved-base-images-built-in-project-and-checked-on-every-from.md

These rules run in the executor, on the priced plans of an approved merge request, and decide the Airframe cost status check. A plan that has no price, for example without an Infracost key, is not judged.

A large cost increase

Level
Warn
What it checks
A change that adds more than 10 US dollars a month, or more than 25 percent.
What its message points to
Platform approval, which is designed.
Source
control-plane policy/airframe/cost.rego; not listed in docs/policy-pack.md

The budget holds

Level
Deny
What it checks
A change whose projected monthly cost for the product is above its monthly_budget in the catalog.
What its message points to
Raise the budget by a catalog merge request that the domain's leads approve, before this change can merge.
Source
control-plane policy/airframe/cost.rego; not listed in docs/policy-pack.md

These limits are open at this release, and the claims and mechanisms page lists them with the rest:

  • A raw resource warns, never denies. A product may declare resources outside the golden path’s modules; the deny rules still apply to them, and so do the rules on platform-owned types.
  • A resource with no labels attribute passes the label rule, which checks only the labels a resource declares.
  • Dockerfiles are matched by file name. The merge request’s validation judges a change to a listed Dockerfile alone when the file is named Dockerfile, Dockerfile.*, *.Dockerfile or *.dockerfile. A listed file under another name, such as Containerfile, is judged only when the merge request also changes another file the validation watches.
  • A warning holds nothing back, and nothing applies the warn label or opens an exception: the approval and the exception register are designed.

Where it lives

In the control-plane repository:

MIT license. Copyright Airframe contributors.

This site describes platform release 2026.10.20-1.