Claims and mechanisms
This page lists every claim Airframe makes about its security, cost and records, with the mechanism behind each one and an honest status. A claim is only as strong as what enforces it, so each row says whether something refuses a violation today, whether the claim rests on people following a rule, or whether it is designed and not built yet. The trust boundaries page explains how the pieces fit together.
How to read a row
Section titled “How to read a row”Each row has a claim, its mechanism, a status and a source: the repository and the files where the mechanism lives. The status is one of three words:
- Enforced: a mechanism refuses the violation. For a claim that the platform provides something, such as a dashboard, it means the platform’s own code provides it every time, without anyone choosing to.
- Convention: written down and taught, but nothing refuses a change that ignores it.
- Designed: specified in the platform’s design or records, and not built.
The examples come from a demo organization, whose projects sit under one folder in Google Cloud. Today one person runs the platform. Wherever a row names a second person, such as an approver, that person is the maintainer’s second account: the mechanism is enforced, but the separation between two people is not yet real.
Every claim of the “why” pages appears here with the same mechanism, and so does every gap the platform’s own reviews and records list.
Identities and access
Section titled “Identities and access”No person's everyday account holds a standing role that can change production infrastructure.
- Mechanism
- The folders root and the project factories grant people viewer and log viewer roles, and the one write a team holds is adding a version of its production secrets, never reading one. Administrative roles reach people only through Privileged Access Manager grants. A daily Policy Analyzer job fails, and alerts, if that one administrator account holds any role but Organization Administrator at the organization node, or any other person or group holds a standing role there.
- Status
- enforced
- Source
- landing-zone
roots/folders/main.tf,roots/project-factory/,.gitlab-ci.yml(audit:policy-analyzer)
Only a job of the product's own repository, on a protected branch or tag, in the right environment, can obtain its apply identity.
- Mechanism
- The project factory binds each apply identity to one value that combines the repository's path, the environment and whether the ref is protected, with the protected value only. The identity provider refuses any token from outside the projectairframe group in GitLab.
- Status
- enforced
- Source
- landing-zone
roots/wif/main.tf,roots/project-factory/
Nothing that runs before a merge can change production.
- Mechanism
- The executor may use every product's plan identity, which reads and changes nothing, and every non-production apply identity, and no production apply identity. A merge request's own pipeline gets no identity at all.
- Status
- enforced
- Source
- landing-zone
roots/project-factory/(grants to the executor)
No service account key can be created or uploaded under the demo organization's folder.
- Mechanism
- Organization policies at the folder disable key creation and upload for every principal, and the deny policy blocks key creation for everyone but the break-glass group, the bootstrap administrator account and the Privileged Access Manager service agent. The policy pack also refuses a key declared in Terraform.
- Status
- enforced
- Source
- landing-zone
roots/organisation/policies.tf,deny.tf; control-planepolicy/airframe/iam.rego
A maintainer's administrative access is temporary, and another member of the platform group approves it.
- Mechanism
- The Privileged Access Manager entitlements that grant administrative roles last at most two hours and need the approval of another member of the platform group; a requester never approves their own request. Today that member is the maintainer's second account.
- Status
- enforced
- Source
- landing-zone
roots/pam/main.tf
A billed product's non-production project has a level 1 grant, to read its logs and state for an hour.
- Mechanism
- The one level 1 entitlement, self-approved for an hour, is on the platform's non-production project,
lk-runtime-np. No project factory creates one for a product yet. - Status
- designed
- Source
- landing-zone
roots/pam/variables.tf,docs/guides/identity.md
Google Cloud itself refuses the owner and editor roles anywhere below the folder, even outside a pipeline.
- Mechanism
- A custom organization policy constraint, enforced at the folder since 2026-10-01, refuses a grant of either role from any path, the console included. It allows two cases: a project still in its factory's bootstrap window, and Google's own APIs service agent, which Google grants editor when a project's APIs are turned on. The project factories also remove the owner role Google gives them on each project they create. The policy pack refuses both roles in every Terraform change as well (Policy, below).
- Status
- enforced
- Source
- landing-zone
roots/organisation/constraints.tf,terraform.tfvars,docs/adr/0026-the-floor-enforced-and-the-audit-record-locked.md
The deny policy also protects the firewall policy and Binary Authorization.
- Mechanism
- Architecture 9.3 lists both among the deny policy's permissions. The deny policy as built covers key creation, organization policy changes, folder IAM changes and the folder's log sinks, and neither of the two.
- Status
- designed
- Source
- landing-zone
docs/architecture.md9.3,roots/organisation/deny.tf
Every product hostname is behind single sign-on.
- Mechanism
- Identity-Aware Proxy is on for every user-facing service, for the product's users group: the golden path's Cloud Run module turns it on for every product backend, and the policy pack refuses open ingress, or a product backend, without it. Cloud Run's invoker check stays on, with IAP's service agent as an edge service's only invoker, so a call around IAP is refused. The gate and this website are the platform's public exceptions. A route an entry declares public, with external ingress, is designed: nothing builds one yet.
- Status
- enforced
- Source
- control-plane
policy/airframe/cloud_run.rego; golden-pathmodules/cloud-run-service/main.tf; landing-zonedocs/contracts.md8
Applications check the signed header IAP sends, and a service's default address is off.
- Mechanism
- The review of 2026-09-25 recorded both as the next layers after the invoker check. Today the invoker check alone keeps a caller from going around IAP: the golden path's Cloud Run module keeps it on (
invoker_iam_disabled = false), and the policy pack only warns when an edge service turns it off. - Status
- designed
- Source
- landing-zone
docs/reviews/2026-09-25-implementation-review.md(F06); control-planepolicy/airframe/cloud_run.rego
Each preview has its own hostname, behind single sign-on, and no other way in.
- Mechanism
- The domain's wildcard certificate and record cover every preview hostname, so nothing is issued per preview. The golden path's preview-substrate module gives each product one router on its non-production project, behind IAP for the product's users group; the router reads the merge request's number from the hostname, and each preview service names the router as its only invoker.
- Status
- enforced
- Source
- golden-path
modules/preview-substrate/
A removed name is never reused.
- Mechanism
- The catalog keeps a tombstone for each retired name, and its lint refuses any other product with that name in the domain. An identity is bound to the repository's path, so a new repository cannot inherit an old one's access.
- Status
- enforced
- Source
- catalog
scripts/lint.mjs,retired-names/
Change control
Section titled “Change control”A merge request's pipeline holds no cloud credential.
- Mechanism
- It runs on the untrusted runner, outside the perimeters, in a network with no route to the platform's ranges; its jobs cannot reach the metadata server, and no identity binding accepts an unprotected branch.
- Status
- enforced
- Source
- control-plane
roots/runner-untrusted/,docs/adr/0007-an-untrusted-runner-of-our-own.md; landing-zoneroots/wif/main.tf
Only the current owners can approve.
- Mechanism
- The gate reads the ownership model from the catalog's protected main, never from the change it judges, counts no approval by the author or a committer, and binds its own approval to the commit it checked; an approval policy makes that approval a condition of merging.
- Status
- enforced
- Source
- control-plane
gate/src/core/ownership.ts; security-policies.gitlab/security-policies/policy.yml
Only a named person's approval lets a change merge, never its author's.
- Mechanism
- The approval policy lists its approvers by name, the catalog's active people with a GitLab account, and excludes the author and the committers. A coding agent with its own account is on no list; one that acts through a person's account or token is that person to GitLab, and nothing tells the two apart. The gate's service account is the one approver of a separate policy and cannot give the person's approval. Today every named approver is one of the maintainer's own accounts.
- Status
- enforced
- Source
- security-policies
.gitlab/security-policies/policy.yml
Every catalog file is checked before it merges.
- Mechanism
- control-plane's validate component runs the catalog's lint,
validate:catalog, on every merge request and on main: each file against its JSON Schema, each level's parent present, every lead and team member a person who has not departed, and each team's lead one of its members with the team lead role. Each finding names its file. The catalog project lets a merge request merge only when its pipeline has succeeded, so a finding stops the merge; on main, a failed lint stops the chain of reconcilers before it starts. - Status
- enforced
- Source
- control-plane
templates/validate/template.yml(validate:catalog),roots/gitlab/projects.tf; catalogscripts/lint.mjs,schema/,.gitlab-ci.yml
Only the current owners can approve a change to the catalog.
- Mechanism
- The gate reads the ownership rules, the teams and the people from the catalog's protected main, never from the change it judges, and counts only active people with a GitLab account, never the author or the authors of its commits. The required ownership gate policy in security-policies makes the gate's approval a condition of merging in the catalog, as in every other repository the gate guards.
- Status
- enforced
- Source
- control-plane
gate/src/core/ownership.ts,gate/src/adapters/gitlab-ownership.ts,gate/infra/terraform.tfvars(ownership_enforced); security-policies.gitlab/security-policies/policy.yml; catalogownership/protected-paths.yaml
A catalog merge reaches the readers with no one running a command.
- Mechanism
- The catalog's pipeline on its protected main triggers landing-zone's, operations' and control-plane's pipelines in turn, then landing-zone's again, each waiting for the one before; a failure stops the ones after it. In them the tf-apply component runs only the roots that declare
reads_catalog, against the catalog's main, each with its own pipeline identity. - Status
- enforced
- Source
- catalog
.gitlab-ci.yml(reconcile:landing-zone,reconcile:operations,reconcile:control-plane,reconcile:landing-zone-last); control-planetemplates/tf-apply/template.yml(reads_catalog,catalog_ref)
Every reader derives memberships and approvals from active people alone.
- Mechanism
- The groups root syncs memberships for active people alone; the GitLab roots give memberships and merge rights only to active people with a GitLab username, and admit them to the platform repositories and security-policies through one group,
projectairframe/contributors, so a departure takes a person out of all of them on the chain's next run; the Grafana root adds only active people with a Grafana username; the gate counts only active people. The lint refuses a departed person in any team or lead list, so the merge request that marks someone departed also takes them out of every team and lead list. - Status
- enforced
- Source
- landing-zone
roots/groups/main.tf; control-planeroots/gitlab-config/locals.tf,roots/gitlab-config/memberships.tf,modules/product-repositories/main.tf,gate/src/core/ownership.ts; operationsroots/grafana/members.tf; catalogscripts/lint.mjs
The approval policy's named approvers follow the catalog.
- Mechanism
- The
policy:approversjob compares the list with the catalog's active people with a GitLab account. In security-policies it runs on every merge request and on main, and a failure shows on the merge request. In control-plane it runs after every catalog merge, where a person to add or remove only warns, so the catalog's chain goes on, and every day, where it fails andpolicy:approvers:reportkeeps one issue with the exact change to make. A role, a group or an id among the approvers, or any approver but the gate's account on the gate's own rule, fails every run. Until the list follows, a person not yet on it cannot approve, and a departed person still on it reaches no platform repository, since people reach those through theprojectairframe/contributorsgroup alone, whose members come from the catalog. What holds the list is a named person's approval of each change in security-policies, never its author's. - Status
- convention
- Source
- security-policies
.gitlab-ci.yml(policy:approvers),scripts/approvers.py; control-planecli/approvers_lag.py,.gitlab-ci.yml(policy:approvers,policy:approvers:report),roots/gitlab-config/memberships.tf,roots/gitlab/modules/ultimate/policies.tf
The gate's approval is required in every repository but one.
- Mechanism
- The required ownership gate policy covers the repositories the gate enforces, today every one but security-policies, which holds the approval rules. A new repository joins by two merge requests, one in control-plane and one in security-policies; until then it needs only a named person's approval.
- Status
- enforced
- Source
- security-policies
.gitlab/security-policies/policy.yml; control-planegate/
A change can still merge when the gate is running but cannot approve it.
- Mechanism
- A merge request in security-policies, approved by a named person who did not write it, takes the blocked repository out of the gate policy's scope, and a second one puts it back. This was rehearsed: the rule left a product repository 11 seconds after the first merge and returned 35 seconds after the reversal.
- Status
- enforced
- Source
- control-plane
docs/security-policies.md; security-policies.gitlab/security-policies/policy.yml
A change to a platform repository can still merge when the gate cannot be reached.
- Mechanism
- security-policies is outside the group-wide pipeline execution policy, so its own pipelines carry no
gate:integrityand its merge requests do not wait on the gate. One merge request there, approved by a named person who did not write it, takes one repository out of the required ownership gate policy's scope and adds it to that execution policy's exclusions; that repository then merges on the non-author approval alone, and a reverse merge request puts both back. In the window the executor plans, builds and promotes nothing for it. The rehearsal of 2026-10-02 took a platform repository out and back with the gate up: the rule left in 10 seconds and returned 16 seconds after the reverse merge. It then made the gate unreachable for two minutes: a pipeline in another repository failed closed 10 seconds after it started, and security-policies' pipeline passed. A product repository cannot merge this way, because its merge also waits for the Airframe cost status check, which only the gate sets; whether an urgent product change waits for the gate or gets a way through the check is not decided. What an outage loses: GitLab does not send again an audit event the gate could not receive. On 2026-10-02, 34 of the 35 pipelines that failed in the group between 09:00 and 15:30 UTC have their failure event in the gate's log; the one without failed during the outage. Since that evening a second streaming destination sends every event to the audit receiver, a separate Cloud Run service running the gate's image with only the audit route, so the locked audit bucket loses an event only when both are unreachable; the detections read the gate's copy alone, so a gate outage still hides its events from them. - Status
- enforced
- Source
- security-policies
.gitlab/security-policies/policy.yml; control-planedocs/adr/0032-a-gate-that-cannot-be-reached.md(owed 5 and 7),docs/security-policies.md; landing-zonedocs/rehearsals/2026-10-02-the-gate-unreachable.md; control-planegate/infra/audit-receiver.tf,docs/adr/0021-gitlab-ultimate-deployments-vulnerabilities-compliance-and-audit-events.md
A gate outage is handled by a written procedure, the gate's own recovery first.
- Mechanism
- control-plane's ADR-0032 sets two stages. First the gate is recovered in Google Cloud, because it cannot redeploy itself through its own pipeline while it is down: under a level 2 Privileged Access Manager grant that another member of the platform group approves, Cloud Run's traffic goes back to the last revision that served, about 22 seconds each way in the rehearsal. Only when that has not restored the gate within 30 minutes, and a change cannot wait, does one repository leave both of the gate's controls, for at most two hours, as above. The approvals are enforced: Privileged Access Manager refuses a requester's own approval, and the approval policy an author's. The order, the 30 minutes and the issue that keeps the timeline are written down and rehearsed, and nothing refuses a break-glass merge request that skips them.
- Status
- convention
- Source
- control-plane
docs/adr/0032-a-gate-that-cannot-be-reached.md; landing-zoneroots/pam/main.tf,docs/rehearsals/2026-10-02-the-gate-unreachable.md
A break-glass window for one repository ends after two hours.
- Mechanism
- Nothing closes it: ending the window is a person's reverse merge request. What checks the bound is control-plane's scheduled pipeline, which runs once a day, so an overrun shows at the next day's run.
breakglass:checkcompares security-policies' policy file with the steady state, warns while a difference is under two hours old and fails after, naming the repositories, andbreakglass:reportkeeps one issue labeledaudit::misconfigurationopen until the policies match again. - Status
- convention
- Source
- control-plane
cli/breakglass_bound.py,.gitlab-ci.yml(breakglass:check,breakglass:report),docs/adr/0032-a-gate-that-cannot-be-reached.md
No project, group or pipeline variable can point the gate's integrity job elsewhere or empty it.
- Mechanism
- The job runs wherever the group-wide policy injects it: every repository but security-policies and one taken out for a break-glass window. It defines the gate's address itself, and an empty address fails closed like an unreachable gate. Both pipeline execution policies disallow variable overrides, so no project, group, instance or pipeline variable reaches the jobs they inject. Until 2026-10-02 the job read its address from a group variable and recorded a pass when it was empty; that fallback is gone.
- Status
- enforced
- Source
- security-policies
templates/airframe.yml,.gitlab/security-policies/policy.yml; control-planedocs/adr/0032-a-gate-that-cannot-be-reached.md
A product is created without anyone running a command.
- Mechanism
- The catalog's pipeline starts the chain of Terraform roots after the merge, and each root applies with a pipeline identity that only a job of its own repository, on a protected ref in its environment, can obtain.
- Status
- enforced
- Source
- catalog
.gitlab-ci.yml; landing-zoneroots/wif/
Only an approved change gets a preview, and never with production's identity.
- Mechanism
- The gate starts the executor's preview run only when it approves a product's merge request, and not for a retiring product; the run applies the product's
infra/ephroot with the non-production apply identity, after the policy pack has checked the plan. Someone who can only open merge requests cannot run up the bill. - Status
- enforced
- Source
- control-plane
executor/scripts/preview.sh,docs/adr/0016-the-preview-lifecycle-through-the-executor.md
The merge request shows how far along the product is.
- Mechanism
- After the merge, the catalog's progress job follows the chain, and the gate keeps one note on the merged merge request until the product answers at its hostname, a job fails, or three hours have passed.
- Status
- enforced
- Source
- control-plane
templates/progress/,gate/src/adapters/gitlab-progress.ts
Retiring a product takes two approvals besides the author's.
- Mechanism
- The gate's lifecycle rule asks for two distinct approvers, neither of them the author: one from the product's team and one from the domain's leads or the platform team. The catalog's lifecycle job requires the lifecycle label and the states in order. Today both can be the maintainer's own accounts.
- Status
- enforced
- Source
- control-plane
gate/src/core/ownership.ts; catalogscripts/lifecycle-check.mjs
A product's Terraform cannot read the pipeline's credentials.
- Mechanism
- Before any plan, the executor refuses in a product root every template function and every file read that is not a file the commit carries or a pinned module's, and it runs the product's Terraform as a separate user with a scrubbed environment and one token. That second layer is tested in the pipeline and not yet exercised by a live product plan.
- Status
- enforced
- Source
- control-plane
executor/scripts/security.py,executor/scripts/worker.py,docs/adr/0028-a-products-terraform-runs-as-a-worker-with-one-token.md
A product cannot claim another product's hostname, backend or identity on the shared network.
- Mechanism
- The network root takes each publication's owner and edition from the object's name, checks every claim in it against them, and rejects an invalid or contested publication on its own: a route's hostname and backend, and a reach's or an egress object's identity, must be the owner's. Keeping the last valid configuration is owed.
- Status
- enforced
- Source
- landing-zone
docs/adr/0024-the-edge-publications-are-checked-against-their-owner.md,docs/reviews/2026-09-26-implementation-review.md(R02)
A reach's address belongs to its owner's own service.
- Mechanism
- The network root checks only that a reach's address lies in the edition network's private service access range, not that it is the owner's own database or cache. A product can therefore open its own identity's path to another product's database or cache address there, behind that service's own authentication. A check of the address's owner is owed.
- Status
- designed
- Source
- landing-zone
docs/adr/0024-the-edge-publications-are-checked-against-their-owner.md,docs/reviews/2026-09-26-implementation-review.md(R02)
An approval is a fresh sign-in, not a session.
- Mechanism
- The design requires re-authentication at each approval. It is turned off, a recorded departure from the design: until it is on, an approval given through a person's session or token counts as that person's.
- Status
- designed
- Source
- landing-zone
docs/adaptations.md(row 32); security-policies.gitlab/security-policies/policy.yml
A criticality 1 product is released through verification, a soak and a canary.
- Mechanism
- Criticality 2 and 3 follow their designed order. Criticality 1 takes criticality 2's order, because the deployment component for the later stages is not built; the demo organization has no criticality 1 product.
- Status
- designed
- Source
- landing-zone
docs/adaptations.md(row 48)
A product spread over several repositories, each deploying its own part.
- Mechanism
- The catalog accepts an entry's repositories and the contracts name their state, identities and hostnames, but nothing reads them yet, and the design decision is still a proposal.
- Status
- designed
- Source
- landing-zone
docs/adr/0017-multi-repository-products-and-adoption-by-moving-the-group.md
Policy
Section titled “Policy”The policy pack's tests run on every change to it.
- Mechanism
- control-plane's
policy:testjob 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. - Status
- enforced
- Source
- control-plane
.gitlab-ci.yml(policy:test),policy/airframe/
Every rule comes with a case it must accept and one it must refuse.
- Mechanism
- The working conventions and the pack's documentation ask for both. Review holds it; no job checks that a new rule brings its tests.
- Status
- convention
- Source
- control-plane
docs/policy-pack.md
A product repository cannot skip validation.
- Mechanism
- The product pipeline execution policy 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.
- Status
- enforced
- Source
- security-policies
.gitlab/security-policies/policy.yml,templates/product.yml
A product's pipeline file includes only the platform's components, and none of its own jobs deploys.
- Mechanism
- The pack's pipeline rules deny an include that is not one of control-plane'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.
- Status
- enforced
- Source
- control-plane
policy/airframe/pipeline.rego,docs/adr/0026-a-product-job-never-deploys.md
A rule that denies stops the change.
- Mechanism
- A denial fails the injected validation job, and GitLab merges a product's merge request only when its pipeline succeeds. On main,
policy:<root>runs the pack on the plan, and the apply needs it to pass. The cost rules run in the executor instead and decide the Airframe cost status check. - Status
- enforced
- Source
- control-plane
templates/validate/template.yml,templates/tf-apply/template.yml
A warning needs the platform's approval and a row in the exception register.
- Mechanism
- Today a warning appears in the job's log and holds nothing back: Conftest runs without failing on warnings, although the pack's documentation and some messages say approval is required.
- Status
- designed
- Source
- control-plane
docs/policy-pack.md,templates/validate/template.yml
A new deny rule spends one release as a warning, then denies.
- Mechanism
- The working conventions ask for it, and nothing turns a warning into a denial. Several rules marked as warnings for one release, such as the sidecar database rules, the edge invoker check and a product file's required includes, are still warnings at release 2026.10.01-1.
- Status
- convention
- Source
- control-plane
policy/airframe/cloud_run.rego,pipeline.rego,docs/policy-pack.md
Raw resources are allowed, with a warning.
- Mechanism
- The pack warns on every managed resource a product root declares outside a module, asking for a reason in the merge request, and a warning fails nothing. Its deny rules apply to every resource, raw or not, and the types the platform owns, such as networks, projects and perimeters, are denied in a product root.
- Status
- enforced
- Source
- control-plane
policy/airframe/naming.rego,platform_owned.rego
No Terraform change grants the owner or editor role, or anything to the public.
- Mechanism
- The pack's IAM rules deny owner and editor in any shape, and any grant to
allUsersorallAuthenticatedUsers, on the code and on the plan. - Status
- enforced
- Source
- control-plane
policy/airframe/iam.rego
Every labeled resource carries the platform's full set of labels.
- Mechanism
- The label rule denies a resource whose labels miss any of the six the contracts name, on the code and on the plan.
- Status
- enforced
- Source
- control-plane
policy/airframe/naming.rego; landing-zonedocs/contracts.md7
Every resource carries the platform's labels, even one that declares none.
- Mechanism
- The contracts ask for labels on every project and resource. The label rule checks the labels a resource declares, so a resource with no labels attribute at all passes it.
- Status
- designed
- Source
- landing-zone
docs/contracts.md7; control-planepolicy/airframe/naming.rego
The laptop runs the merge request's validation.
- Mechanism
airframe validateruns the validate component's formatting, terraform validate and policy pack commands with no backend and no credentials; OpenTofu and tflint run only where they are installed. Running it is a convention, and the merge request runs the checks anyway. It reads the pack from the control-plane clone it is installed from, not from the product's release, and it does not run the catalog lint, the gate, the application's tests, the plan or its price; where Infracost and its key are present, it shows an estimate from the code.- Status
- convention
- Source
- control-plane
cli/airframe-validate.sh
airframe validate runs the module tests, the API specification lint and the API compatibility check.
- Mechanism
- The workspace's working conventions list all three; the script runs none of them.
- Status
- designed
- Source
- control-plane
cli/airframe-validate.sh
Supply chain
Section titled “Supply chain”Your own Dockerfile builds on a platform base image.
- Mechanism
- The pack's Dockerfile rules deny a
FROMor aCOPY --fromoutside the platform's base images, one not pinned and one computed from a build argument, in the Dockerfiles.airframe/images.yamllists. From release 2026.10.01-1 the injected validation runs them on every merge request that changes a listed Dockerfile namedDockerfile,Dockerfile.*,*.Dockerfileor*.dockerfile, andairframe validateruns them locally. The trusted builder builds and attests exactly that list. - Status
- enforced
- Source
- control-plane
policy/airframe/dockerfile.rego,templates/validate/template.yml
A merge request that changes only a listed Dockerfile is judged, whatever its name.
- Mechanism
- A Dockerfile listed under another name, such as
Containerfile, is judged only when the merge request also changes another file the validation watches. - Status
- designed
- Source
- control-plane
templates/validate/template.yml,docs/adr/0014-approved-base-images-built-in-project-and-checked-on-every-from.md
Each stack is built and tested when its part of the template changes.
- Mechanism
- golden-path's pipeline re-renders the three examples and requires them to match the committed ones whenever the template changes, and builds and tests a stack's example when that stack's files change. The demo catalog has an active product on each stack.
- Status
- enforced
- Source
- golden-path
.gitlab-ci.yml(template:render,template:test)
A stack the template does not offer builds on a platform base image, without the platform's tests.
- Mechanism
- The base image rules apply to every listed Dockerfile. The injected build and test jobs know the template's three stacks only, so another stack runs none of them and fails the coverage gate until its entry lowers
quality.minimum_coverage. - Status
- enforced
- Source
- control-plane
templates/build-test/template.yml
A product service deployed through the golden path runs only an image the platform built and promoted.
- Mechanism
- The golden path's Cloud Run module opts every service into the Binary Authorization policy its project carries, which in a billed product's production project is enforced and requires the build and promote attestations; the trusted-builder root lets only the build attestor and the artifact promoter sign.
- Status
- enforced
- Source
- golden-path
modules/service-project/main.tf,modules/cloud-run-service/main.tf; control-planeroots/trusted-builder/
Binary Authorization holds for every Cloud Run service, as part of the floor.
- Mechanism
- Architecture 9.3 counts it among the controls that hold if a pipeline is bypassed. What is built is opt-in per service, written only for billed products: no organization policy requires a service to opt in, a raw Cloud Run service passes the pack with a warning, and the platform's telemetry gateway runs without it.
- Status
- designed
- Source
- landing-zone
docs/architecture.md9.3,roots/organisation/policies.tf; golden-pathmodules/cloud-run-service/main.tf
Every image is scanned for known vulnerabilities in the trusted build.
- Mechanism
- The trusted builder runs grype on every image it builds, against a vulnerability database the platform prepares once a day and uses only when it is at most 48 hours old and matches its checksum; otherwise grype fetches the vendor's database for that build. The scan and the image's software bill of materials are kept with the image's digest as evidence, with a record of which database was used, and the findings reach GitLab's vulnerability report at promotion. A finding warns: it does not block a deploy.
- Status
- enforced
- Source
- control-plane
executor/build/cloudbuild.yaml,docs/adr/0034-the-scan-database-prepared-once-a-day.md,docs/adr/0021-gitlab-ultimate-deployments-vulnerabilities-compliance-and-audit-events.md
The build attestation is signed apart from the build.
- Mechanism
- The trusted builder builds and scans;
build-attestor@, which only the executor may impersonate, signs the build attestation after the executor has checked the build against Cloud Build's own record. Promotion to production adds a second attestation. - Status
- enforced
- Source
- control-plane
roots/trusted-builder/main.tf,docs/adr/0029-the-build-attestation-is-signed-apart-from-the-build.md
Each image is scanned again, apart from the build.
- Mechanism
- The builder's scan runs inside the build, and its result reaches GitLab's vulnerability report at promotion. An independent scan of each digest outside the build is recorded as owed.
- Status
- designed
- Source
- landing-zone
docs/reviews/2026-09-26-implementation-review.md(R03)
A revoked release runs no new pipeline.
- Mechanism
- The gate's integrity job, which the group's pipeline execution policy injects into every pipeline, reads the permitted releases on control-plane's main and fails a pipeline that includes a revoked one; the executor makes the same check before a merge request or release run holds a product's credentials.
- Status
- enforced
- Source
- control-plane
gate/src/core/releases.ts,executor/scripts/release_check.py,releases/permitted.yaml
A release is a fixed set of commits, verified together.
- Mechanism
- A release is a manifest of every part's commit, never changed once merged, verified at those commits before it is permitted; a component fetches its policy pack at its own commit and names every image by digest. A pipeline file that includes a component by branch or tag is warned, not refused.
- Status
- enforced
- Source
- control-plane
releases/,docs/adr/0030-shared-references-are-pinned-to-commits-and-digests.md,docs/adr/0031-the-platform-release-a-manifest-and-a-permitted-list.md
A change over the budget cannot merge.
- Mechanism
- The gate sets each product merge request's Airframe cost status check from the executor's priced plans of every root at its head: failed when the roots' total or any root is above the entry's
monthly_budget, or when a root has no price. The product project's rule requires status checks to pass. A plan on main stops on a recorded refusal too. Previews and usage-based resources are outside the figure. - Status
- enforced
- Source
- control-plane
gate/src/adapters/gitlab-status-checks.ts,executor/scripts/cost.sh,docs/adr/0025-the-merge-waits-for-the-cost-verdict.md
Raising a budget takes a domain lead.
- Mechanism
- The gate's ownership rule requires a domain lead's approval of a catalog change that raises
monthly_budget; where the domain's leads have no GitLab account, as in the demo, the platform team stands in. - Status
- enforced
- Source
- control-plane
gate/src/core/ownership.ts
Every product has a cost dashboard against its budget.
- Mechanism
- operations' Grafana root, which runs when the catalog changes, creates a cost dashboard in each product's folder: month to date and the forecast against
monthly_budget, orange at 80 percent and red at 100, from the billing export through the dailycost:reportjob. - Status
- enforced
- Source
- operations
roots/grafana/pack.tf,roots/cost/
The Google Cloud budgets follow the same number.
- Mechanism
- The domain's project factory creates a budget on each billed product project from
monthly_budget, 70 percent for production and 30 for non-production, with alerts at 80 and 100 percent. No demo product is billed today, so none has its project budgets yet. - Status
- enforced
- Source
- landing-zone
roots/project-factory/
A budget alert reaches the product's team.
- Mechanism
- When a product is billed, its budgets' alerts go by email to the billing account's administrators and users, Google's default, not to the team.
- Status
- designed
- Source
- landing-zone
roots/project-factory/
A cost rise above USD 10 or 25 percent a month needs the platform's approval.
- Mechanism
- Today such a rise is a warning in the root's note on the merge request and holds nothing back.
- Status
- designed
- Source
- control-plane
policy/airframe/cost.rego
A preview is destroyed when its merge request ends.
- Mechanism
- The gate learns of the merge or the close from GitLab's webhook and starts the executor's destroy run, which checks the merge request's state itself. Each preview's record carries a lock, so an apply and a destroy never run at once, and a reopened merge request starts a new generation, so a late destroy cannot remove the new preview.
- Status
- enforced
- Source
- control-plane
executor/scripts/preview.sh,executor/scripts/preview_record.py
An expired or missed preview is collected.
- Mechanism
preview:collect, in control-plane's daily scheduled pipeline, lists the previews the executor's records say are still up and destroys the merged, the closed and those pastpreview_ttl_hours(72 by default, at most 168, counted from the latest apply), with a note on each merge request.- Status
- enforced
- Source
- control-plane
executor/scripts/preview.sh
A preview costs next to nothing while idle, and never blocks a merge.
- Mechanism
- The router and the preview's services scale to zero, the default sidecar database lives in the service's own revision, and the golden path's Cloud Run module runs a preview's service as one instance at most. The executor prices each preview's plan and shows it in its note, and the cost check that holds a merge leaves previews out.
- Status
- enforced
- Source
- golden-path
modules/preview-substrate/,modules/cloud-run-service/; control-planeexecutor/scripts/preview.sh
Audit and observability
Section titled “Audit and observability”Every grant, and every change made under it, is recorded.
- Mechanism
- Privileged Access Manager keeps each grant with its requester, justification, approver and reason, and Google Cloud's audit log keeps every change; Data Access logs at the folder also record reads of Secret Manager, Cloud SQL, Cloud Storage, IAM and Cloud KMS.
- Status
- enforced
- Source
- landing-zone
roots/pam/,roots/audit/main.tf
Each use of a grant has a row in the bootstrap log.
- Mechanism
- The row names the grant, who requested and approved it, the root and commit applied, and the empty plan after it. It is written down and taught; nothing refuses a grant without one.
- Status
- convention
- Source
- landing-zone
docs/bootstrap-log.md
Every Google Cloud audit log reaches one bucket, and no pipeline can reroute it.
- Mechanism
- An aggregated sink at the folder routes every project's audit logs, and GitLab's audit events through the gate and the audit receiver, into a bucket in
lk-auditencrypted with a key the platform holds. The deny policy refuses any change to the folder's sinks to everyone but the break-glass group, the bootstrap administrator account and the Privileged Access Manager service agent. The folder's IAM policy, which holds the audit configuration, has the same exceptions and a fourth, the identity that configures the grants; it acts only from reviewed code, and the condition on its access requires a change to modify at least one grant and only grants of the roles the entitlements give, so a call that changes only the audit configuration is refused. A call that changes a permitted grant and the audit configuration together still passes: at the folder, a rule in Grafana pages when an audit configuration changes, and at the organization there is no sink. The sink leaves out VPC Service Controls' dry-run entries, which record what a perimeter would have refused and not a refusal; each project's own log keeps them. GitLab's audit events reach the bucket through two streaming destinations, the gate and the audit receiver, each writing every event; GitLab does not send an event again, so an event created while both are unreachable is missing from the bucket, and if its type is one GitLab only streams it is recorded nowhere. - Status
- enforced
- Source
- landing-zone
roots/audit/main.tf,roots/organisation/deny.tf,roots/wif/grants.tf,docs/adr/0007-pam-companion-condition.md; operationsroots/grafana/detections.tf; control-planedocs/adr/0032-a-gate-that-cannot-be-reached.md(owed 7),gate/infra/audit-receiver.tf
The audit bucket's retention is locked.
- Mechanism
- A retention policy keeps every object for 365 days, and none can be deleted or overwritten before then. The policy is locked since 2026-10-01, which Google makes irreversible: nobody, an administrator under a grant included, can shorten or remove it, and the audit project cannot be deleted while the bucket holds objects.
- Status
- enforced
- Source
- landing-zone
roots/audit/main.tf,roots/audit/terraform.tfvars,docs/bootstrap-log.md
Data cannot leave the platform's projects through Google's APIs, even with valid credentials.
- Mechanism
- Three VPC Service Controls perimeters, production, non-production and audit, are applied as dry-run specifications: a would-be violation is logged, and nothing is blocked. Enforcing them is one variable. The organization root's daily reads that a perimeter would refuse come from inside the production perimeter, from the trusted runner on the production network's VPC, because the perimeter's list of services reachable from that VPC does not include those services; they are not calls from outside.
- Status
- designed
- Source
- landing-zone
roots/perimeters/
Suspicious use raises an alert: a key creation attempt, a deny policy hit, a grant, an identity used outside its pipeline.
- Mechanism
- A second, narrow sink at the folder sends only these entries to Loki: a key creation attempt, a deny or organization policy refusal, a refused token, a change to the folder's sinks, deny policies or audit configuration, a grant, and GitLab's audit events. Rules in Grafana page the platform on the first five and open a ticket for a grant or a burst of GitLab changes. An event at the organization itself, such as an organization-level grant, is outside the folder's sinks; the approver's notification is its only record besides the audit log. GitLab's events arrive through the gate, so those created while it cannot be reached never arrive, and no rule sees them.
- Status
- enforced
- Source
- landing-zone
roots/audit/main.tf,docs/adr/0027-the-audit-detections-alert-in-grafana.md; operationsroots/grafana/detections.tf
Drift is reported.
- Mechanism
- The tf-apply component's drift job plans each root on the repository's daily schedule, and the drift-report component, as the audit identity, keeps one issue labeled
audit::driftper drifting root, rewrites it and closes it when the plan is empty again. - Status
- enforced
- Source
- control-plane
templates/tf-apply/,templates/drift-report/
A coding agent's work is labeled.
- Mechanism
- The instructions for a coding agent and the airframe-platform skill ask for a
Generated-bytrailer on every commit and theagentlabel on the merge request, and the platform's GitLab configuration defines the label. No job or rule checks either, so they record what a coding agent or its user declares. - Status
- convention
- Source
- golden-path
template/; platform-skillsskills/
An audit can tell a coding agent's work from a person's, and measure its share.
- Mechanism
- That is what the trailer and the label are designed for; with nothing checking them, they cannot prove it today.
- Status
- designed
- Source
- landing-zone
docs/architecture.md9.7
Every product has its dashboards and alert rules from its entry.
- Mechanism
- operations' Grafana root, which runs when the catalog changes, creates each product's folder, its service and SLO dashboards and its baseline alert rules, with thresholds from the entry's
slo; the rules select production by theenvlabel, and a missing series is not an alert. - Status
- enforced
- Source
- operations
roots/grafana/pack.tf,alerts.tf
An alert reaches the product's team, and today that means the maintainer.
- Mechanism
- Each rule routes by the team label to the team's contact point, which emails the team's members who have a Grafana account, and a page repeats every four hours until it is resolved. Grafana Cloud accepts only its own members' addresses, and today those members are the maintainer's own accounts, so every team's alerts, and the platform's, reach the maintainer.
- Status
- enforced
- Source
- operations
roots/grafana/alerts.tf,members.tf
An application needs no setup and holds no Grafana credential.
- Mechanism
- The project factory's Ready output carries the address of the gateway of the product's edition, and the golden path's Cloud Run module injects it into every service. Traces and metrics reach the OTLP gateway with the service's identity token; logs reach the logs gateway through the project's sink and a Pub/Sub buffer. The gateways hold the edition's write-only token, the only Grafana credential on that side.
- Status
- enforced
- Source
- operations
roots/telemetry/; golden-pathmodules/cloud-run-service/main.tf
Every signal says which edition it came from.
- Mechanism
- The OTLP gateway keeps a signal's
envonly when it is valid for its own edition and replaces a missing or foreign value withunknown; the logs gateway keeps a validenvlabel and otherwise gives the line its perimeter's, so only audit entries stayunknown. Both stamp the edition asperimeter. The golden path's Cloud Run module gives a service's traces and metrics its service name, its domain and its environment, and the platform agent labels each billed product project's Cloud resource metrics with the product's path and its journey and domain. - Status
- enforced
- Source
- operations
roots/telemetry/config/
An application's signals, the platform agent's resource metrics and the platform's scheduled jobs' series carry the environment they came from.
- Mechanism
- The OTLP gateway gives a resource without
envthe value ofdeployment.environment.name, then ofdeployment.environment, the attribute the golden path's Cloud Run module sets from the deployment (prod,np,sandboxoreph-<n>); a datapoint keeps its own validenv, and a value that is not valid for the edition becomesunknown. The platform agent labels each project's series withenv:platformfor the platform's projects, the edition for a product's, and in non-production the Cloud Run service's name narrows it tosandboxoreph-<n>. The logs gateway gives a line without a validenvits perimeter's, narrowed the same way; a platform project's lines areplatform, and audit entries keepunknown. The platform's own scheduled jobs (the cost, delivery and coverage reports and the daily Policy Analyzer check) declaredeployment.environment=platformon their resource, so their series carryenvplatform; the cost report keeps the environment of the project a cost belongs to in a separate label,project_env(contracts section 7). The drift report's series still arrive asunknownuntil the next platform release, because each repository runs the drift-report component of its release. The pipeline applies the telemetry root from operations' main, and itstelemetry:testjob renders the real configurations and runs them in the pinned Alloy whenever they change, failing rather than skipping when the binary is missing. - Status
- enforced
- Source
- operations commit
872681a:roots/telemetry/config/otlp.alloy.tftpl,logs.alloy.tftpl,agent.alloy.tftpl,roots/telemetry/agent.tf,tests/telemetry_test.py,.gitlab-ci.yml,jobs/delivery_report.py,jobs/coverage-report.sh,jobs/cost_report.py; control-planetemplates/drift-report/template.yml; landing-zone.gitlab-ci.yml,docs/contracts.md7; golden-pathmodules/airframe-context/main.tf,modules/cloud-run-service/main.tf
An application's signals carry its team and criticality.
- Mechanism
- The design gives every signal the catalog's labels, team and criticality among them. They carry no such label today: a service's traces and metrics have
service.name,service.namespace, which is its domain, anddeployment.environment, and the logs gateway keeps a line's project, service, resource type, log name and severity. Only some of the platform's own series carry a team, such as the delivery and coverage reports'. - Status
- designed
- Source
- landing-zone
docs/guides/observability.md(Labels); golden-pathmodules/cloud-run-service/main.tf; operationsroots/telemetry/config/logs.alloy.tftpl
An alert pages an on-call person, under a schedule and an escalation chain.
- Mechanism
- Grafana's on-call and incident tool would hold both for each team, from the catalog. Today an alert is an email.
- Status
- designed
- Source
- landing-zone
docs/architecture.md13.5
An outage of the gate is noticed without waiting for red pipelines.
- Mechanism
- A Grafana Cloud Synthetic Monitoring HTTP check,
airframe-gate, requestshttps://gate.projectairframe.dev/healthevery 60 seconds from two probes. A rule in theairframe-platformgroup pages the platform's contact point when more than one probe fails for two minutes, or when the check reports nothing. The check covers the gate only; no other hostname has one. Today a page is an email to the maintainer. - Status
- enforced
- Source
- operations
roots/grafana/synthetic.tf,alerts.tf,docs/adr/0012-the-gate-uptime-check-in-synthetic-monitoring.md
The baseline covers every failure the design names.
- Mechanism
- Cloud Run at its maximum instances, restarts or out of memory, a contract check blocked on main, and uptime checks through each product's production hostname are designed; the Grafana root does not create these rules yet. The gate's uptime check, above, is the only one built.
- Status
- designed
- Source
- landing-zone
docs/architecture.md13.4, 13.6; operationsroots/grafana/pack.tf
An operations agent investigates an alert, and an audit agent looks for waste and orphans.
- Mechanism
- The first would act within what the entry's
agentic_opsallows; the second would look across the organization, and find a preview with no record or a failed destroy. Neither exists today. - Status
- designed
- Source
- landing-zone
docs/architecture.md14.3
Documentation
Section titled “Documentation”A skill says what the platform's checks do.
- Mechanism
- The platform-skills pipeline's
skills:policy,skills:validate,skills:catalogandskills:referencesjobs check each skill's examples against the policy pack at the control-plane commit the skills pin, terraform validate and the catalog's lint, and check the paths a skill names; GitLab merges a change only when they pass. control-plane'srelease:checkrequires that pin to be the release's. Whether a coding agent loads a skill is up to the agent and its user. - Status
- enforced
- Source
- platform-skills
.gitlab-ci.yml; control-plane.gitlab-ci.yml(release:check)
Every skill ships examples that the checks must accept or refuse.
- Mechanism
- platform-skills' README says each skill comes with its tests and a skill without tests is not merged, and four of the five skills ship pass and fail examples. The overview skill, airframe-platform, ships none: its one test,
tests/references, checks that the paths it names exist. Nothing refuses a skill without examples. - Status
- convention
- Source
- platform-skills
README.md,skills/airframe-platform/
Each product publishes its own part of the map, and only its own.
- Mechanism
- The catalog-publish component's
validate:fragmentjob builds the fragment on the merge request with no credential; on the protected main,release:catalogpublishes it with the identity the repository already holds. A product's production apply identity may write only under its product's prefix. The four platform repositories share one identity,platform-docs@, whose grant covers the whole platform prefix, so nothing stops one of them from writing another's part. - Status
- enforced
- Source
- control-plane
templates/catalog-publish/template.yml,roots/executor/variables.tf
The map rebuilds itself from the latest fragments.
- Mechanism
- The aggregator, a Cloud Run job, runs when a publishing identity asks after a publication and hourly on a schedule. It reads the catalog's list of products, skips a broken fragment with its reasons in the run's record and keeps the last three versions.
- Status
- enforced
- Source
- operations
eventcatalog/,roots/eventcatalog/
A coding agent can query the map through the catalog's MCP server.
- Mechanism
- The server is designed; the machine-readable index it would read is published today beside every build.
- Status
- designed
- Source
- landing-zone
docs/guides/architecture-catalog.md
The platform's documents match its code.
- Mechanism
- A change carries its documents in the same merge request and review holds it; no job compares the two. Known differences today: the security guide puts Binary Authorization on every project; the observability guide gives each team an on-call schedule; ADR-0016 says a preview's destroy removes its state, which the executor keeps; and the contracts count a preview's time to live from its last push, the executor from its latest apply.
- Status
- convention
- Source
- landing-zone
docs/guides/security.md,docs/guides/identity.md,docs/guides/observability.md,docs/contracts.md8; control-planedocs/adr/0016-the-preview-lifecycle-through-the-executor.md,executor/scripts/preview.sh
Where it lives
In the landing-zone repository:
- docs/reviews/: the implementation reviews and what answers them
- docs/adaptations.md: where the build departs from the design
- docs/guides/security.md: the platform's security guide
In the control-plane repository:
