Hangar

Docs · Business rules

The rules Hangar plays by

Who can do what, what a release has to prove, and what the clusters refuse, all in one list. Each rule says how strongly it holds, because a rule that only lives in a doc isn't the same as one a system enforces, and I'd rather you knew which is which.

213 rules · 179 held by a system · last checked against the code November 10, 2026 · source on GitHub ↗

How strongly each rule holds

Enforced 149
A system refuses the action.
Fails closed 30
Enforced, and if the check itself cannot run, the action is refused too.
Soft 8
The rule is checked, and breaking it is flagged, announced and recorded, but nothing stops it.
Display 12
Shown to help a person decide. Nothing is checked or blocked.
Convention 11
Written down. Nothing checks it.
Off here 3
Built and documented, but not in effect in this lab (usually the GitHub plan).
Building 0
Decided and in development. Not in effect until the release named in the row ships.

1 People and roles

  1. PPL-01

    You sign in with GitHub, and only if the catalog has a User whose name is your GitHub login. Guest sign-in works only in local development.

    Held by Backstage auth (usernameMatchingUserEntityName); the guest provider refuses outside NODE_ENV=development

    Enforced
  2. PPL-02

    Groups and memberships come from the catalog (examples/org.yaml, each User's memberOf). Nothing syncs them from GitHub: a personal account has no org to sync from.

    Held by backstage examples/org.yaml

    Convention
  3. PPL-03

    An app's owners are its catalog Component's spec.owner, which comes from the XR's terasky.backstage.io/owner annotation. You own an app if you, or a group you belong to, are its owner.

    Held by the catalog's isEntityOwner rule

    Enforced
  4. PPL-04

    Admins are the members of group:default/admins. Admin is a grant, and some actions need it whoever owns the app (ACC-04).

    Held by permissionPolicy.ts

    Enforced
  5. PPL-05

    Break-glass reviewers (group:default/breakglass-reviewers) may review a glass-broken session. They get nothing else from that group.

    Held by Tower's break-glass routes

    Enforced
  6. PPL-06

    Tower's write routes accept only a signed-in user. A service token is refused.

    Held by towerAuthz.ts

    Enforced

Today's groups are admins, team-passenger, team-flight-ops, team-skyport, team-platform and breakglass-reviewers.

2 Access in the portal

  1. ACC-01

    Anyone signed in can read everything: the catalog, every app's Tower tabs, TechDocs, release records, sign-off states and the release calendar.

    Held by permission policy (every read allowed)

    Enforced
  2. ACC-02

    Only admins can delete catalog entities, register or delete catalog locations, run software templates (create a scaffolder task, run an action) or manage templates.

    Held by permissionPolicy.ts WRITE_PERMISSIONS

    Enforced
  3. ACC-03

    Every Tower write needs the app's owners or an admin. The route finds the app's catalog entity and asks the permission framework for that user and that app.

    Held by towerPermissions.ts, towerAuthz.ts

    Enforced
  4. ACC-04

    Some writes need an admin even from an owner: changing an app's release mode, syncing or force-syncing a Flight environment, ending someone else's break-glass session, and breaking the glass.

    Held by towerGrantOnlyPermissions

    Enforced
  5. ACC-05

    A request can only act on the repository the catalog names for that app. The GitHub owner in the request must match the entity's github.com/project-slug.

    Held by towerAppFromEntity

    Enforced
  6. ACC-06

    The Kubernetes proxy is read-only. Anything but GET, HEAD or OPTIONS is refused, and so is a WebSocket upgrade (exec or attach). Every write goes through an owner-checked route.

    Held by kubernetesProxyGuard.ts

    Enforced
  7. ACC-07

    Tower greys out the actions you may not take and says why. That is a courtesy: every route checks again.

    Held by Tower /argo/capabilities

    Display
  8. ACC-08

    Every Tower write is audited (tower-write), refusals included. Severity is medium by default, high for Flight permissions, rollback, force sync and image pruning, and critical for a Flight analysis bypass or a broken glass.

    Held by Backstage auditor

    Enforced

Full action-by-action table: Tower security model.

3 Environments and promotion

  1. ENV-01

    Every environment is Ground (lower, on the dev cluster) or Flight (upper clusters). The tier comes from the app's cicd.yaml, read on the server, never from a request. An environment that can't be placed counts as Flight.

    Held by Backstage routes; Glidepath

    Enforced
  2. ENV-02

    Ground is self-service: a deploy is a direct commit to the app repo's glidepath/envs/<env>.yaml by the deploy bot. No release gates run on Ground.

    Held by Glidepath deploy-manifests

    Enforced
  3. ENV-03

    Flight changes only through a release PR to gitops-<app>. The release pipeline can open a PR but cannot touch a cluster; Argo CD syncing the merged PR is the only way in.

    Held by Glidepath ADR-0004

    Enforced
  4. ENV-04

    A promotion carries forward only what already runs in the source environment. The image and commit are read from the source environment's file on the server, never from the request, and never rebuilt.

    Held by glidepathPromote.ts resolveSource

    Enforced
  5. ENV-05

    No workload, no release. An environment whose values turn the service off (rollout.enabled: false) gets no release: Glidepath refuses before any PR or commit, Tower refuses Promote, and the chart refuses to render a tracked release with nothing to run.

    Held by Glidepath release.md

    Enforced
  6. ENV-06

    Every environment's Argo CD Application syncs automatically, heals drift and prunes what the chart no longer renders. Argo CD refuses a sync that would delete everything, and deleting a generator never deletes a running environment.

    Held by Argo CD (selfHeal, prune, allowEmpty: false, preserveResourcesOnDeletion)

    Enforced
  7. ENV-07

    No cluster holds a credential for another cluster's API. A Flight environment is created by a commit the target cluster's own Argo CD acts on. Separate lower and upper AppProjects keep an app's Ground identity away from Flight destinations.

    Held by Glidepath security.md

    Enforced
  8. ENV-08

    Each app environment has its own namespace (<type>-<app>-<env>), and pipelines run in the app's -cicd namespace. RBAC never spans them.

    Held by Glidepath, Airframe

    Enforced
  9. ENV-09

    A PR preview environment lives as long as its PR. Its namespace is deleted with its Application, and a sweep every 30 minutes deletes any preview namespace whose Application is gone.

    Held by Glidepath ephemeral-environments.md

    Enforced
  10. ENV-10

    A Flight destination must be a registered upper cluster marked crossplaneReady. A dev cluster is refused.

    Held by Airframe ApplicationEnvironment schema and cluster registry

    Enforced

Builds push to Ground (ghcr.io/jfillman); upper clusters run only what was promoted to Flight (ghcr.io/riley-roooo), copied by digest before the release merges. Detail: Glidepath registries.md, ADR-0029.

  1. PROM-01

    A Flight environment's release file pins the Flight repository and a digest (release.image.digest), with the Ground repository it came from as source, from the moment the release PR opens. A release that pins Ground, or no digest, is never promoted.

    Held by open-release-pr; promote refuses

    Enforced
  2. PROM-02

    Only the promoter writes Flight: the promote workflow in riley-roooo/flight-promoter, whose own short-lived GITHUB_TOKEN is the only credential with write access. Builds, clusters and Glidepath cannot write it; Glidepath can only start the workflow (the hangar-flight-promoter App, actions: write on that one repo, minted per call by the broker for the glidepath-promoter ServiceAccount only).

    Held by GHCR package permissions; broker /promoter-token

    Enforced
  3. PROM-03

    Promotion starts only when every required gate is green on the release PR's current head commit. The last gate to finish starts it (an event, not a polling pod), exactly once; promote re-checks every gate itself and refuses a red or missing one. image-promotion is then a required check on the PR, and it cannot be made advisory.

    Held by comment-pr-check-result fan-in; promote

    Enforced
  4. PROM-04

    The copy keeps every digest: the image index, every platform manifest, the .sig/.att tags and the OCI 1.1 referrers. Nothing is re-signed, so Rekor entries and the check ride's age stay valid. The copy is verified in Flight with a read-only credential before the check goes green: digest, .sig, .att, and a promotion attestation naming this request.

    Held by promote workflow; promote-image StepAction

    Enforced
  5. PROM-05

    Upper clusters run tenant images only from Flight, each with a promotion attestation (https://hangar.io/attestations/promotion/v1) signed by the promote workflow's GitHub Actions identity on public Sigstore and verified offline. Third-party images no build produces (CloudNativePG, OTel auto-instrumentation, bitnamilegacy) are allow-listed by prefix. Tenant pods pull with a read-only Flight credential; the Ground credential is not on the cluster. In force on hangar-prod since Oct 30, 2026 (Audit from Oct 29, 2026).

    Held by Kyverno flight-only-images (ValidatingPolicy) and flight-images (ImageValidatingPolicy), Deny, failurePolicy: Fail, rendered by Apron admission.flight (POL-11)

    Fails closed
  6. PROM-06

    Only the app's own Ground repository is promotable: never a PR preview build (<app>-pr) or another app's image.

    Held by promote

    Enforced
  7. PROM-07

    Promoting a digest already in Flight (a rollback, a second environment) copies nothing new and adds a new promotion attestation for that release.

    Held by promote workflow (digest copy is idempotent)

    Strength: Enforced (not yet exercised by a live rollback)

    Enforced
  8. PROM-08

    A release merged before its promotion is a bypass and is never promoted afterwards: the merged pin names a Flight digest that does not exist, so the new pods cannot start and the previous ReplicaSet keeps serving; the missing image-promotion check is reported as a bypass.

    Held by promote; detect-bypass-merge (GATE-04)

    Strength: Enforced (promote refuses a merged PR); the bypass alert is Soft

    Enforced
  9. PROM-09

    Flight retention never deletes a release that a release file pins, a Release Record names, or an open release PR pins, nor the 10 newest releases of a package, nor one younger than 30 days. The platform plans, and only the promote repo's prune workflow deletes. A package without a readable gitops repo is never pruned.

    Held by Glidepath flight-prune CronJob; prune workflow

    Strength: Off here (Flight only grows: promotion.retention.enabled is false; the prune workflow is in the promoter repo since Oct 30, 2026 but has not run)

    Off here
  10. PROM-10

    With promotion on, a cloud target (aws-ecs, aws-lambda, azure-container-apps) in a Flight environment is refused before it deploys: a cloud deploy copies from Ground, so nothing promoted it. Ground environments of the same app are not affected.

    Held by resolve-deploy-target

    Enforced
  11. PROM-11

    The registries and promotion settings are admin-owned: the catalog's registries and promotion values and the cluster's admission.flight, never cicd.yaml or Tower.

    Held by Glidepath catalog values; Apron cluster.yaml

    Enforced

4 Release gates

  1. GATE-01

    Every release PR to a gitops repo runs every registered gate: sast, image-scan, provenance, sbom, values, qa and policy-validation are real; itsm is real once the catalog names a change tool (itsm.provider) and a stub until then.

    Held by Glidepath release-guardrails.md

    Strength: Enforced (they run)

    Enforced
  2. GATE-02

    A stub never reports a pass. It logs that no check exists, returns stub, and shows as a grey STUB everywhere.

    Held by Glidepath governance-stubs.md, ADR-0003

    Enforced
  3. GATE-03

    A gate stops a merge only through the gitops repo's GitHub ruleset: every gate a required check from the Pipelines as Code app, 2 approvals, no bypass actors.

    Held by Glidepath github-ruleset.md. Private repos on GitHub Free cannot enforce rulesets, so anyone with write access can merge past a red gate.

    Off here
  4. GATE-04

    Merging past a failed or missing gate is a break-glass bypass. It is detected after the merge and alerted (not subject to notification settings), and the PR keeps its failed checks.

    Held by bypass-check (detect-bypass-merge)

    Soft
  5. GATE-05

    The commit being released must be proven one of two ways: gitsign-signed (public Sigstore) by an address in the app's allowedCommitSigners from the admin-owned release-signing policy (GATE-16), or a GitHub PR merge that passes GATE-15. A gitsign signature is judged by gitsign alone; failing it never falls through to GATE-15. An empty signer list fails closed. Changed Oct 20, 2026: the list was the app's own cicd.yaml.

    Held by provenance gate (verify-commit-signature); Glidepath commit-signing.md

    Fails closed
  6. GATE-06

    For an app without the GitHub-merge path, land app changes by fast-forwarding a gitsign-signed commit, never with GitHub's merge button: all three button options make a new commit GitHub signs, which fails GATE-05. With the path on, the merge button is fine when an allowed login presses it. The web editor, the contents API, GitHub Apps and "Update branch" never count.

    Held by commit-signing.md: merge strategy

    Strength: Convention (GATE-05 and GATE-15 catch it)

    Convention
  7. GATE-07

    The image must carry a valid SLSA provenance attestation from the platform's own Fulcio (the Tekton Chains identity), and the attestation must pass the Conforma policy.

    Held by provenance gate; Glidepath provenance-policy.md

    Enforced
  8. GATE-08

    Every changed values.yaml passes airframe-validate: no secret-looking literal (AF-SECRET-001), no release-owned key in a human file or the reverse (AF-OWNER-001), and no unknown component or output (AF-COMP-001/002).

    Held by values gate

    Strength: Enforced (subject to GATE-03)

    Enforced
  9. GATE-09

    A Release Record PR (release-record-* branch) is not a release, and the gates skip it.

    Held by gate triggers

    Enforced
  10. GATE-10

    /retest re-runs a gate. It never skips one.

    Held by Pipelines as Code

    Enforced
  11. GATE-11

    A new or removed gate goes in the releaseGuardrails registry, and the ruleset is regenerated from it in the same change.

    Held by hack/apply-guardrail-ruleset.sh

    Convention
  12. GATE-12

    qa passes only when the promoted image's own test provenance (TEST-06) carries a check ride that is verified, passed, with tests-failed: 0, from the shared check-rides Application (workflow-source: argocd:check-rides), and younger than qa.maxAge (72h). It names what was missing otherwise. An eligible rollback is advisory.

    Held by qa-gate; Glidepath testing.md

    Strength: Enforced as a check; Soft today (advisory, GATE-13)

    Enforced
  13. GATE-13

    Every registry entry has required (default true). A gate with required: false is advisory: it runs and posts what it found, its check stays green, a merge past it is not a bypass, and the ruleset leaves it out. Only a glidepath PR (the chart default) or a cluster's catalog values (releaseGuardrailOverrides) change it: never cicd.yaml, never Tower, never per release. On hangar-dev qa is advisory until every app has a check ride.

    Held by glidepath-catalog.guardrails; release-guardrails.md: Required and advisory gates

    Enforced
  14. GATE-14

    sast and provenance cannot be made advisory: a catalog that sets either to required: false fails to render.

    Held by glidepath-catalog chart render

    Enforced
  15. GATE-15

    GitHub-merge path (Oct 20, 2026, off unless the policy turns it on for the app). A commit signed with GitHub's web-flow key passes GATE-05 only when every check holds: the key is one of GitHub's pinned keys (GATE-17); the committer is GitHub; it is the merge_commit_sha of a PR merged into the default branch; it is on that branch, and a merge commit's second parent is the PR's head; merged_by is in allowedMergers (an empty list fails closed). mode: strict also requires every PR commit gitsign-signed by an allowed signer. requiredApprovals: N requires N approvals of the PR's final head by other allowed mergers. Defaults when on: merger mode, 0 approvals. The PR comment and the gate's results say which path verified the commit. Applies to tag-triggered releases too.

    Held by verify-commit-signature; commit-signing.md: the GitHub-merge path, ADR-0027

    Fails closed
  16. GATE-16

    The release-signing policy is admin-owned: signers, whether the GitHub-merge path is on, its mode, allowedMergers and the minimum approvals are set only in the catalog's releaseSigning values (a glidepath PR, or a cluster's 50-glidepath/glidepath-catalog values), never in cicd.yaml or Tower. A product owner asks for a change; a platform admin merges it. An app's cicd.yaml can only tighten it (governance.githubMerge.mode: strict, a higher requiredApprovals); governance.allowedCommitSigners there is ignored.

    Held by release-signing-policy ConfigMap; commit-signing.md: who sets the policy

    Strength: Enforced (the gate reads only the catalog's ConfigMap; the schema refuses enabled/allowedMergers in cicd.yaml)

    Enforced
  17. GATE-17

    GitHub's web-flow keys are pinned: checked in, baked into the toolbox image with a fingerprint assert, and pinned again in the gate. A signature by any other key, including a new GitHub key not yet pinned, fails closed; the retired 2017 key counts only for signatures made before Jan 16, 2024. The gate never fetches a key while verifying. A weekly workflow warns when GitHub publishes a different key set.

    Held by toolbox image, verify-commit-signature, web-flow-key-check workflow

    Fails closed
  18. GATE-18

    policy-validation renders every environment the release PR changes and evaluates it offline (Kyverno CLI) against the target cluster's own admission policies, 30-policy/ on main of its gitops-<cluster> repo. Only a Deny/Enforce failure fails the check; Audit/Warn findings are warnings. A render failure or an unreadable policy source fails it (fail closed). The policies are fetched by the token broker (POST /policy-bundle) with a read-only token the pipeline never sees.

    Held by Glidepath policy-validation.md (ADR-0028)

    Enforced

5 Testing and the check ride

The test stage, the shared check ride and the evidence a release carries: Glidepath testing.md, the check-rides repository's README, and the library paper A test contract for Glidepath, and a QA gate that reads it.

  1. TEST-01

    A test step runs the TestWorkflow it names with the platform's context as config (app, env, image, url, revision, gitUrl, chain, for the keys the workflow declares) and as execution tags. A workflow never hardcodes its target.

    Held by run-testworkflow

    Enforced
  2. TEST-02

    A test or execute step whose workflow does not exist fails, naming the file. Only test.enabled: false skips, and it says skipped.

    Held by run-testworkflow

    Enforced
  3. TEST-03

    A check-ride step runs <app>-check-ride exactly as Argo CD synced it from the check-rides repository, never a file from the app repo: it refuses a workflow without the sync annotation or with another app's hangar.io/app label. A flow has at most one, it takes no testName, and it is always required.

    Held by run-testworkflow, validateFlows

    Enforced
  4. TEST-04

    The standard (templates/) is owned by SDETs and product owners; an app's ride (apps/<app>/) by its team and an SDET. The repo's own check requires the name, the label, a template from templates/, the declared config and only the app's own Secret.

    Held by check-rides CODEOWNERS, tools/check.py

    Strength: Convention (code owners on a private repo on GitHub Free); Enforced with the ruleset

    Convention
  5. TEST-05

    In the shared testkube namespace a tenant pipeline may reference only its own <app>-app-secrets, and a workflow synced by Argo CD only <hangar.io/app label>-app-secrets, in container env and content.git.tokenFrom.

    Held by Kyverno testkube-secret-usage (ADR-0008)

    Enforced
  6. TEST-06

    Every completed test PipelineRun's SLSA provenance is signed by Tekton Chains onto the tested image's digest, carrying the run's results (outcome, role, test counts, workflow-source, workflow-digest, execution-id). Failed runs are attested too, so a reader selects by task name and reads the outcome, never presence.

    Held by Chains; test.yaml type hints

    Enforced
  7. TEST-07

    The environment's NetworkPolicy admits same-namespace pods only; an app lets the testkube namespace in through its own glidepath/base.yaml (networkPolicy.allowIngressFrom).

    Held by airframe-application

    Convention
  8. TEST-08

    Tower shows every attested test run on an image (the Tests step), the check ride first, each downloadable; the build provenance is the one whose pipeline ran build-image.

    Held by Tower v0.79.0 supplyChain.ts

    Display
  9. TEST-09

    A release record carries the check ride the qa gate read (outcome, counts, source, execution, verdict, how many runs were attested) frozen in when the record is sealed; before that Tower reads it live from provenance. The run shown is the newest verified pass, else the newest check ride, else the newest run of any role, so a failure is never hidden behind an older pass and "the team's tests ran, no check ride" reads differently from "nothing ran".

    Held by backend testEvidence.ts; Tower v0.80.0 findReleaseTestRun

    Strength: Enforced (record) / Display (live)

    Enforced
  10. TEST-10

    The Pipelines tab's Tests view lists every Testkube execution tagged with the app and downloads its JUnit report, read from glidepath-api (listTests, getTestReport) as Tower's read identity. An execution Testkube marked aborted although every step passed is shown as passed, with the note. Changed Oct 30, 2026: Tower read Testkube itself through the control plane's service proxy; that grant is gone.

    Held by backend /tests, /tests/report; glidepath-api

    Display

6 Change management (itsm)

The itsm gate wraps the company's change tool (ServiceNow first, a webhook adapter for any other): Glidepath change-management.md. Every rule below applies once the catalog names a provider; with none the gate is a loud stub and the hooks are no-ops. This lab runs the webhook provider against a mock change tool.

  1. CHG-01

    Every release PR creates a change request, or links one the caller or the PR body already names: a normal change for a promote, an emergency change for a rollback whose parent is the change it rolls back. The PR body and the release record carry it.

    Held by open-release-pr

    Enforced
  2. CHG-02

    The itsm gate passes only when the change exists and is approved or scheduled, and fails naming the change, its state and its approval.

    Held by itsm-gate

    Strength: Enforced (subject to GATE-03 and GATE-13)

    Enforced
  3. CHG-03

    The change follows the release: implementing on merge, closed successful when the release is healthy, closed unsuccessful when it is rolled back, with the rollback's emergency change related. A later rollback re-closes a successful change.

    Held by bypass-merge-check, release-outcome-notify, release-rollback-notify

    Enforced
  4. CHG-04

    A change tool that is down stops a release PR from opening and fails the gate; the later hooks tolerate it visibly (a red TaskRun, the release flow continues).

    Held by onError: continue on the hooks

    Enforced
  5. CHG-05

    Change-tool credentials come from a platform Secret in each -cicd namespace, fanned out from the platform's Infisical project; never a value.

    Held by itsm.credentials ClusterExternalSecret

    Enforced
  6. CHG-06

    Glidepath never approves, creates or moves a change on the gate's behalf. A release is held by leaving its change unapproved; a merge past an unapproved change is a bypass (GATE-04).

    Held by itsm-gate

    Enforced
  7. CHG-07

    A release record carries its change request (id, number, URL, state, approval, and the change that rolled it back), frozen in when sealed and read from the newest promote tracking record for the image before that; Tower shows it on the record and as a state chip in the list.

    Held by backend changeRequestOf; Tower v0.80.0 ReleaseRecordEvidence.tsx

    Display

7 Release records, modes and sign-off

  1. REL-01

    Each app has a release mode in gitops-cluster-dev-tenants/release-modes.yaml: Assisted (the default) or Hand-flown. Autoland has a name but isn't available. Only an admin can change a mode, and only through a PR.

    Held by tower.release.mode (admins only)

    Enforced
  2. REL-02

    The sign-off environment is the one cicd.yaml marks production: true, else the last Flight environment.

    Held by releaseFlow.ts

    Enforced
  3. REL-03

    For a Hand-flown app, a release PR to the sign-off environment needs a sign-off.

    Held by the hangar/release-signoff commit status; GitHub Free can't make it required

    Soft
  4. REL-04

    A sign-off counts only if it is the reviewer's latest review, it is an approval, and it came before the merge. The reviewer must not be the person who merged or opened the PR, and must be in the app's owning team or an admin (GitHub login matched to a catalog User). If the catalog can't be read, no approval counts.

    Held by releaseApprovers.ts, releaseFlow.ts

    Fails closed
  5. REL-05

    A release that merges without a sign-off that counts is announced at high severity. Its record says shipped without or self-approved, and keeps saying so.

    Held by release-signoff-poll

    Soft
  6. REL-06

    Assisted apps record who merged and who reviewed. They get no status and no alert.

    Held by release-signoff-poll

    Enforced
  7. REL-07

    The next release's draft (notes, summary, owner, risk, target date, milestone, GitHub Release opt-in) is glidepath/releases/next.yaml in the app repo. Owners and admins save it as a direct commit that names them and builds nothing. Unknown fields are dropped, and text is capped at 50 lines of 300 characters per list and 2,000 characters per field.

    Held by tower.release-record.edit; sanitizeDraft

    Enforced
  8. REL-08

    When a release is first healthy in a Flight environment, the backend opens the record PR (releases/<app>@<tag>.yaml in gitops-<app>) with the draft sealed into it, and clears next.yaml, within a few minutes. Nothing merges it. A record is history once a person merges it.

    Held by release-record-seal-poll (2 min), release-record-poll (hourly)

    Enforced
  9. REL-09

    Release names are tree names given in order (ASPEN, BIRCH, CEDAR...), skipping U, V and X, with numerals after the last (ASPEN II). A name is the release's place in the app's committed records, so it never changes.

    Held by releaseNames.ts (Tower and backend, kept identical)

    Enforced
  10. REL-10

    A release's scope is the PRs merged after the previous release's commit landed on main, up to its own. A PR is never in two releases.

    Held by Tower

    Enforced
  11. REL-11

    Readiness checks the leading candidate: changes merged, running on Ground, 24 hours on Ground, build and scans passed, signed and attested, check ride passed (a verified, passed ride from the shared repository attested on the image: what the qa gate reads), version bumped, and a healthy previous release to roll back to. A target date and closed planned issues are added when the draft has them.

    Held by Tower nextRelease.ts. Nothing is blocked.

    Display
  12. REL-12

    The suggested version is the last version plus the bump the merged PRs call for: breaking means major (minor before 1.0), a feature means minor, anything else patch. The real version is the image's tag.

    Held by Tower

    Display
  13. REL-13

    A release's outcome is decided in the sign-off environment. Clean means healthy, then replaced or healthy through the 7-day bake. Rolled back, replaced before healthy and stopped before production are the other outcomes. It is written back once, by PR.

    Held by releaseOutcome.ts (BAKE_DAYS = 7)

    Enforced
  14. REL-14

    A GitHub Release is opt-in per release, published only once the release is healthy in production, and tagged v<version> at the image's commit. An existing tag is reused only at that commit, and a tag is never moved or replaced.

    Held by releaseOutcome.ts

    Enforced
  15. REL-15

    Everyone hears when a release PR to production opens ("ready for prod") and when it merges ("released"). After a restart, nothing is announced twice.

    Held by release-signoff-poll

    Enforced
  16. REL-16

    The backend writes only PRs, commit statuses, GitHub Releases and the draft commit. It never merges.

    Held by Tower security.md: release records

    Enforced
  17. REL-17

    The Release Record names how the released commit was proven: the gitsign signer, or the GitHub merge (PR, merger, web-flow key, approvers). It is shown green for every passing path, with a hint on a merge that the proof is GitHub's key and record, not a personal key, and frozen into the record when it is sealed. It is read only from the provenance gate's own PR comment posted by the platform's GitHub App (glidepath.gateCommentApps); a person's comment is never trusted for it. Records sealed before Oct 20, 2026 say "not recorded" (no backfill). The same proof shows on the record rows, the release cards, the pending-promotion task and the Release matrix.

    Held by Tower commitProof.ts; backend parseGateComments; Glidepath commit-signing.md: Evidence

    Display
  18. REL-18

    The readiness score's Commit proof line weighs how the commit was proven: gitsign 6, GitHub merge strict 4, GitHub merge 2, nothing when not recorded or not required (cloud targets). It only adds points; no older record's score moves.

    Held by Tower computeConfidence / confidenceBreakdown

    Display

Full detail: Tower release records.

8 Rollback

  1. RB-01

    A rollback is an ordinary release of an earlier image: a PR, the gates and a merge. Owners start it (tower.release.rollback, audited high). It applies to Kubernetes Flight environments; a cloud target rolls back by a release pin PR (tower.release.pin, ADR-0020).

    Held by Glidepath rollback-release; Tower

    Enforced
  2. RB-02

    The target must be one of the five newest releases of that app, environment and cluster that reached healthy. The gates work this out from the release records, never from the PR's text.

    Held by extract-promoted-image rollback-eligibility

    Enforced
  3. RB-03

    On an eligible rollback, sast, image-scan and sbom are advisory, because that image already ran there. Provenance, values, commit signing and approvals still apply.

    Held by Glidepath release-state.md: rollback

    Enforced

9 Supply chain

  1. SC-01

    Every pod in an app's -cicd namespace, image builds included, meets Pod Security baseline: not privileged, no added capabilities, no host namespaces or mounts, no Unconfined seccomp/AppArmor. Builds run rootless BuildKit as UID 1000; they can't meet restricted because the builder needs a user namespace. Each -cicd namespace carries pod-security.kubernetes.io/enforce: baseline, so a pod that breaks it is refused at admission (since Oct 17, 2026). Until then this rule said restricted (kaniko), which never held: kaniko ran as root.

    Held by Pod Security admission, labels set by Glidepath's tenant-onboarding ApplicationSet; security.md, rootless-builds.md, ADR-0026

    Enforced
  2. SC-02

    Tekton Chains signs every image and its provenance with a short-lived certificate from the build cluster's own Fulcio (its own trust root, never the public one), and logs the signatures in the platform's Rekor. Rekor's signing key persists, is never replaced, and is never in git, Infisical or a terminal.

    Held by Glidepath image-signing.md

    Enforced
  3. SC-03

    Tower colours a supply-chain step by its verification. Green: verified. Amber: present but unverified, for a reason that isn't tampering. Red: contradicted, so don't promote it. Grey: missing. A signature with no Rekor entry counts as unsigned.

    Held by Tower supply-chain.md

    Display
  4. SC-04

    Readiness's "Signed and attested" passes only on a verified attestation, never on one that is merely present.

    Held by Tower isSignedAndAttested

    Display
  5. SC-05

    Build signatures and provenance are checked at release (GATE-07), not when a cluster admits an image. Since Oct 30, 2026 hangar-prod admits tenant images only with a promotion attestation (PROM-05), which records that the image went through the promoter after the gates; the Chains provenance itself is not re-checked at admission (it carries no Rekor inclusion proof, and the build cluster's Rekor is unreachable from prod). The Tower debug image is checked at admission too (BG-04). hangar-dev checks nothing at admission.

    Held by see section 21

    Strength: Enforced at release; at admission on hangar-prod through PROM-05

    Enforced

10 Live operations from Tower

  1. OPS-01

    Tower has no standing write access to any cluster. Sync, force sync, terminate, Rollout actions and pod restarts run through one Argo CD account, which can act only on app projects: deny rules cover the platform projects.

    Held by Argo CD RBAC

    Enforced
  2. OPS-02

    Through Argo CD, Tower deletes nothing but a single pod. It never rolls back to an Argo CD revision and never edits an Application's spec.

    Held by Argo CD role tower-pod (delete//Pod/* only)

    Enforced
  3. OPS-03

    Sync and force sync: owners on Ground, admins only on Flight.

    Held by ACC-04

    Enforced
  4. OPS-04

    Restarting a pod never takes the last ready pod of a Flight ReplicaSet. On Ground it needs an explicit "accept downtime".

    Held by podRestart.ts

    Enforced
  5. OPS-05

    Skipping canary analysis on Flight (promote full, skip step) is allowed to owners as a bypass: audited critical and announced to everyone. Every Rollout action is announced.

    Held by tower.rollout.bypass

    Soft
  6. OPS-06

    Re-running and cancelling a PipelineRun is for owners, and Tower does both through glidepath-api, never by writing the PipelineRun itself. The cluster refuses any PipelineRun write from tower-actor, and admits from glidepath-api only Glidepath's own run shape in an app's or InfraService's -cicd namespace, and on an existing run only a cancel. Changed Oct 30, 2026: tower-actor wrote the runs itself, held to the shape by tower-pipelinerun-shape.

    Held by glidepath-api-pipelinerun-shape policy (POL-01)

    Enforced
  7. OPS-07

    Configuration changes (values, env XR, ConfigMap files, cicd.yaml) are PRs, never direct edits.

    Held by tower.config.edit

    Enforced

11 Break-glass debug sessions

  1. BG-01

    Only the app's owners can ask for a session. On Ground it starts at once. On Flight a second person approves first: an owner who isn't the requester, or an admin.

    Held by Tower routes, and the cluster refuses a grant whose approver is the requester

    Fails closed
  2. BG-02

    An admin may break the glass: a Flight session with no second person. It is audited critical, announced to everyone, and must be reviewed afterwards by someone other than the requester (an owner, an admin or a break-glass reviewer).

    Held by tower.breakglass.self

    Strength: Soft (the review)

    Soft
  3. BG-03

    A session lasts 15, 30 or 60 minutes, with one 15-minute extension. It ends after 10 minutes idle or 2 minutes disconnected. Each person may have one open session.

    Held by backend timers; expiry labels the cluster acts on

    Enforced
  4. BG-04

    Never exec. A session is a separate debug container: the signed, digest-pinned Tower debug image, non-root, read-only, all capabilities dropped. On Flight the cluster refuses kubectl exec into app namespaces for everyone but operators.

    Held by tower-breakglass-* admission policies

    Fails closed
  5. BG-05

    Backstage has no standing debug rights. For each session it binds one of two debug roles to itself, in one namespace, with an expiry.

    Held by tower-breakglass-rolebinding

    Fails closed
  6. BG-06

    Every byte is recorded. A session that can't be recorded never starts. Recordings are object-locked for 365 days, their hashes anchored in Rekor, and the app's owners can read them.

    Held by Tower break-glass guardrails

    Fails closed
  7. BG-07

    Admins can end anyone's session, and on Flight so can the session's approver. Anyone can end their own.

    Held by tower.breakglass.terminate

    Enforced

12 Images and retention

  1. IMG-01

    Owners can prune only their app's own packages (<app>, <app>-pr, <app>/cache). Platform packages are refused whatever the app is called.

    Held by imagePrunePlan.ts PLATFORM_PACKAGE

    Enforced
  2. IMG-02

    A prune shows its plan first and deletes only that plan. If anything changed, it refuses and shows the new plan. At most 300 versions go per run, audited high and announced.

    Held by imagePrune.ts

    Enforced
  3. IMG-03

    Always kept: anything running or that a ReplicaSet can scale back to; anything named in git (default branch or an open PR); each environment's last five healthy releases; anything in flight; an open PR's head image; anything under 14 days old; the 10 newest builds. For the cache: the 20 newest layers and anything under 14 days. A release is deleted whole or not at all.

    Held by imagePrunePlan.ts

    Enforced
  4. RET-01

    Release-tracking records on the dev cluster are deleted after 14 days. Never deleted: a merged or progressing release, or the five newest healthy releases per app, environment and cluster. Outcomes are copied into the git record before the 14 days are up.

    Held by release-record-sweeper

    Enforced
  5. RET-02

    A release that is merged or progressing with no new fact for 45 minutes raises ReleaseStalled, once per state.

    Held by release-record-sweeper

    Enforced
  6. RET-03

    A pipeline stage that hasn't started its next stage 10 minutes after finishing is reported as stalled.

    Held by stalled pipeline detector

    Enforced

13 Service catalog (Airframe)

Airframe is where policy turns into code. Its own register is the authority on these rules: Airframe governance: what is enforced today. In short:

  1. AF-01

    You can ask only for the kinds and fields the XRDs offer, within their enums, bounds and cross-field rules. There is no "raw VM" kind, on purpose.

    Held by XRD schema and CEL at admission

    Enforced
  2. AF-02

    Composed resources get least-privilege roles, the baseline NetworkPolicy, dedicated data stores per environment, and hangar.io/* tags and labels.

    Held by compositions

    Enforced
  3. AF-03

    Removing a Kubernetes object never deletes developer data: scaffolded repo files and Infisical projects. Scaffold files and the deploy surfaces Glidepath owns are never overwritten.

    Held by management policies

    Enforced
  4. AF-04

    Upper-environment and catalog changes are PRs a person merges. A catalog change ships as a tag, pinned per cluster, dev first.

    Held by GitOps; Argo CD self-heal reverts hand edits

    Enforced
  5. AF-05

    Only control-plane clusters can be marked autopilotReady.

    Held by cluster registry chart

    Enforced
  6. AF-06

    extraManifests, networkPolicy and httpRoute changes need a person.

    Held by AGENTS.md

    Convention
  7. AF-07

    A security reviewer approves changes to XRDs and compositions.

    Held by not enforced

    Convention

14 Cluster admission policies

Rules the API servers themselves enforce, so a bug in Backstage, or a compromised Backstage, can't get around them. Every one is native ValidatingAdmissionPolicy that fails closed, and each is scoped to its own objects. The exceptions are the debug-image signature check and glidepath-cloud-deployer-pods, which run in Kyverno's webhook (also fail closed).

  1. POL-01

    Backstage (tower-actor) may not create or patch a PipelineRun; glidepath-api's have Glidepath's shape only (OPS-06). Kyverno ValidatingPolicy. Was tower-pipelinerun-shape until Oct 30, 2026, which held Backstage's own PipelineRuns to that shape.

    Policy glidepath-api-pipelinerun-shape on dev

    Fails closed
  2. POL-02

    Backstage may delete only tower-breakglass-* RoleBindings.

    Policy tower-rolebinding-delete on dev

    Fails closed
  3. POL-03

    The only grant Backstage may create. On Flight it must name an approver other than the requester, or carry the glass-broken mark.

    Policy tower-breakglass-rolebinding on both

    Fails closed
  4. POL-04

    The exact debug image digest and its cosign signature, hardened settings, and process access only for the approved session.

    Policy tower-breakglass-debug-container, -debug-image on both

    Fails closed
  5. POL-05

    Backstage deletes only debug or copy pods, and patches only the process-access annotation.

    Policy tower-breakglass-pod-delete, -pod-annotate on both

    Fails closed
  6. POL-06

    No exec into app pods for anyone but operators. Attach only to a debug container.

    Policy tower-breakglass-no-exec on prod

    Fails closed
  7. POL-07

    No long-lived ServiceAccount token Secrets in backstage-system.

    Policy no-long-lived-sa-tokens on dev

    Fails closed
  8. POL-08

    A tenant's TestWorkflow may reference only that tenant's own Secrets in the shared testkube namespace.

    Policy testkube-secret-usage on dev

    Fails closed
  9. POL-10

    Only a cloud deploy may run as glidepath-cloud-deployer, the one identity AWS deploy roles trust. Decided by who created the object, never by labels: pods only from Tekton's controller or the Job controller, TaskRuns only from Tekton, Jobs only from Crossplane, and only they may change a TaskRun's or Job's spec later (others may add metadata, as Chains, Results and the garbage collector do), and a PipelineRun may give it only to the catalog deploy Pipeline's deploy-aws-lambda and deploy-aws-ecs, not run-wide, with no pod-template env or volumes.

    Policy glidepath-cloud-deployer-pods, -taskruns, -pipelineruns, -jobs on dev

    Fails closed
  10. POL-11

    Tenant pods run only Flight images with a promotion attestation, or an allow-listed third-party prefix (PROM-05). Scoped to namespaces labelled hangar.io/managed-secrets. Kyverno, Deny, fails closed.

    Policy flight-only-images, flight-images on hangar-prod

    Fails closed
  11. POL-09

    Every optional field is guarded with has(), and every policy is scoped by match conditions, so a CEL error can only refuse its own objects. A policy change widens before it narrows.

    Policy (how policies are written) on both

    Fails closed

15 Credentials and secrets

  1. CRED-01

    Secrets are references to Infisical, never values in git or an XR.

    Held by AF-SECRET-001

    Strength: Enforced on gitops repos (GATE-08), convention elsewhere

    Enforced
  2. CRED-02

    People create credential material themselves, in Infisical. It is never pasted into a chat, a terminal transcript or a repo.

    Held by Glidepath secrets-management.md

    Convention
  3. CRED-03

    Tower holds no long-lived cluster credential. Reads use the pod's projected token (600 s). Writes on dev use a tower-actor token minted for each action.

    Held by Tower credentials; POL-07

    Enforced
  4. CRED-04

    The GitHub App's private key never leaves platform-system. A release gets an installation token for its own gitops repo only.

    Held by token-review-interceptor

    Enforced
  5. CRED-05

    The platform has one identity per external system (GitHub, Infisical, AWS, Azure), in crossplane-system. Apps never hold one. Each app reads only its own Infisical project. On AWS that identity is an IAM role assumed with a projected ServiceAccount token; nothing is stored (since Oct 16, 2026).

    Held by Airframe provider configs, External Secrets; Airframe cloud-credentials.md

    Enforced
  6. CRED-07

    A Lambda deploy reaches AWS as glidepath-deploy-<functionName>, a role made for that one function: push to its repository and update its code, nothing else. An ECS deploy reaches AWS as glidepath-deploy-ecs-<service>, made for that one service: push to its ECR repository, register task definitions, update that service and pass its execution role to ECS, nothing else. It is assumed with a one-hour token for glidepath-cloud-deployer; no other pipeline step has AWS access, and there is no stored-key path: a deploy that can't assume the role fails. The target's bootstrap Job uses the same role; an AwsLambdaTarget has no access-key mode, and on a cluster without web identity it stops at AwsLambdaTargetNeedsWebIdentity (AwsEcsTargetNeedsWebIdentity for ECS).

    Held by Glidepath cloud-deploy-identity.md; POL-10

    Strength: Enforced (Lambda since phase 4, Oct 16, 2026; ECS since Oct 17, 2026, glidepath#119 and airframe#95; aws_credentials_test.sh in catalog-ci; Airframe no-web-identity render cases)

    Enforced
  7. CRED-08

    Every IAM role a cloud-target composition creates carries the permissions boundary HangarCloudTargetBoundary. The Crossplane identity can't create a role, or add rules to one, without it, and nothing may remove it.

    Held by HangarCrossplaneCloudTargets v3; Airframe cloud-credentials.md

    Strength: Enforced (IAM)

    Enforced
  8. CRED-09

    A composition never removes something that already exists because a condition it was created under stopped holding, or because an observed value it was rendered from is missing for a reconcile. Conditions gate creation only; deleting the XR is the only way to delete its cloud resources.

    Held by Airframe v0.3.144 and v0.3.145; incident Oct 16, 2026 in cloud-credentials.md

    Strength: Enforced (render tests)

    Enforced
  9. CRED-06

    The image-prune token can only read and delete packages. The backend holds it, never the browser.

    Held by IMG-01

    Enforced

16 Notifications

  1. NOT-01

    SLO alerts go to the app's owners, or to everyone if the app has no owner. Budget exhausted fires above a burn of 1.0 and clears below 0.9. Burning fast fires when both 5-minute and 1-hour burn exceed 14.4x, and clears below 12.96x. No data fires after 15 empty polls (about 30 minutes); a failed query doesn't count.

    Held by Hangar SLO docs, sloTransitionPoll.ts

    Enforced
  2. NOT-02

    Release announcements, Rollout actions, analysis bypasses, broken glass, Flight pod restarts and image prunes go to everyone.

    Held by Backstage notifications

    Enforced
  3. NOT-03

    A bypass merge (GATE-04) is alerted however notifications are set.

    Held by detect-bypass-merge

    Enforced

17 AI agents

  1. AI-01

    An agent passes the same pre-merge checks a person does, and works within AGENTS.md.

    Held by Airframe

    Strength: Enforced (the checks), convention (AGENTS.md)

    Enforced
  2. AI-02

    Agent runs go only to clusters marked autopilotReady (AF-05).

    Held by AgentRun composition

    Enforced
  3. AI-03

    Every tool call, model call, session open and spawn an agent makes is decided by Clearance and recorded. A tool above the agent's tier ceiling (R007), a repo, path or field outside its allow lists (R014), and a spend over its budget are denied; a child run may only narrow what its parent has left; anything that fails to evaluate is denied (R000). A denial carries a rule id and a fix hint. Updated Nov 10, 2026: this row said the limits were built but not deployed.

    Held by Clearance index: the runtime boundary; core.md; gitops-hangar-dev 55-clearance/clearance/application.yaml (the registered autopilot-gateway provider and the hangar policy bundle)

    Strength: Fails closed (the decision); the Autopilot gateway, which asks first, enforces it

    Fails closed
  4. AI-04

    Every agent is denied, whatever its definition says, these paths: .tekton/**, CODEOWNERS, .github/**, AppProject files and release.yaml files; and these fields: /extraManifests, /networkPolicy, /httpRoute, /release, /releaseTracking and /rollout/image. For an agent this makes AF-06 a check, not a convention.

    Held by gitops-hangar-dev 55-clearance/clearance/application.yaml vocabulary.baselineDenyPaths, baselineDenyFields

    Enforced
  5. AI-05

    An agent's change to a protected environment is tiered again at the deployment gate on its own merits, whoever proposed it (CLR-02, CLR-07).

    Held by section 19

    Enforced

18 The CI/CD API (glidepath-api)

Every read and write of an app's CI/CD, from Tower, Clearance, a script or a person, goes through glidepath-api: runs, logs, release records, evidence, tests, environments, events, re-run, cancel, promote and roll back. Tower has no other path since Oct 30, 2026 (OPS-06). Detail: Glidepath api.md.

  1. API-01

    Every /v1 call needs a bearer token. Who the caller is comes from the cluster's TokenReview, or, for a token whose issuer is people-dex, from glidepath-api's own check of that ID token: signature, issuer, expiry, and an audience of glidepath-api (a token minted for Clearance is refused). A token that fails is GP-AUTH-001/002.

    Held by api.md: Who may read what

    Fails closed
  2. API-02

    What a caller may do is a SubjectAccessReview in the app's -cicd namespace on glidepath.hangar.io (runs, runs/logs, releases, evidence, tests, environments, events, cicd). Three roles hold the verbs: reader, operator (re-run, cancel) and releaser (promote, roll back). None includes another, and a fresh install binds nobody. On hangar-dev the readers are Tower's read identity and Clearance; operators and releasers are tower-actor and Clearance.

    Held by glidepath-control-plane api.readers, api.operators, api.releasers

    Enforced
  3. API-03

    An app that doesn't exist answers 403 to a caller who couldn't read it if it did, and 404 only to one who could, so the API never tells a stranger which apps exist.

    Held by glidepath-api

    Enforced
  4. API-04

    Only a listed delegate (Tower's two identities and Clearance on hangar-dev) may name a person with X-Glidepath-On-Behalf-Of (GP-AUTH-003). A delegate's write must name one (GP-AUTH-004). The API authorizes the delegate and records the person; it can't check the person, so it trusts the delegate's own check (ACC-03 in Tower).

    Held by api.delegates

    Strength: Enforced (the header); the person check is the delegate's

    Enforced
  5. API-05

    Writes are tiered. T1: re-run and cancel a PipelineRun, only in the app's own -cicd namespace, taken from the path, never the request. T2: promote and roll back a Flight environment, which only starts the app's release pipeline; the change is a gitops PR a person merges (ENV-03). A Ground target is GP-API-422. T3 (anything that changes production directly) is never offered. The tier of every operation is in the contract.

    Held by glidepath-api; api/openapi.yaml x-glidepath-tier

    Enforced
  6. API-06

    Ground promotion and cloud release pins are not offered by the API: both are commits to a repo, and the API holds no GitHub write identity. They stay in Tower.

    Held by api.md

    Strength: Enforced (no route)

    Enforced
  7. API-07

    Every write takes an Idempotency-Key, and the run's name is derived from it: the same key returns the first run (200, replayed), never a second one; a name held by something else is GP-API-409. Every write has a dry run.

    Held by glidepath-api

    Enforced
  8. API-08

    glidepath-api holds no GitHub credential. For each request the broker mints an installation token scoped to one of the app's own repos (matched to a Pipelines as Code Repository in the app's namespace) with contents, pull_requests and metadata read only, and only for glidepath-api's ServiceAccount. The token is never cached, logged, returned or put in an error.

    Held by broker POST /github-read-token (githubRead.callers)

    Enforced
  9. API-09

    Every call, allowed or refused, writes one audit line: operation, tier, app, caller, on-behalf-of, decision, status, time and trace id; a write adds its key's hash, never the key. No token is ever in it.

    Held by glidepath-api audit log

    Enforced
  10. API-10

    People call the API over the Gateway route (/v1 only) with a people-dex ID token, as people:<GitHub login>, and only if that login is in api.oidc.allowedUsers (GP-AUTH-005); the chart refuses people sign-in without the list. An allowed person is bound as a reader; writing needs the name under operators or releasers. A person is never a delegate.

    Held by api.oidc, api.route

    Enforced
  11. API-11

    An event on the stream is one the API re-verified when it arrived: the sender's token is checked again by TokenReview, and its ServiceAccount must be in the event's own app -cicd namespace (the relay excepted). A forged event is dropped. Replicas share events only with each other, under their own token audience.

    Held by /internal/events/tap, peer fan-out

    Enforced
  12. API-12

    Every PR to an onboarded app repo runs cicd-validate: when cicd.yaml changes, the schema, GP-ENV and GP-PIPE rules, and for an agent's PR the GP-SCOPE rules too; a PR that removes cicd.yaml fails. The same validator answers POST /v1/apps/{app}/cicd/validations before a PR exists. It stops a merge only as a required check (GATE-03).

    Held by Glidepath contract.md

    Strength: Enforced as a check; Off here as a blocker

    Enforced

19 Clearance: the deployment gate

Clearance decides whether a change may land in a protected environment and records the answer as evidence. It decides and records; Argo CD's plugin, the cluster's admission policy and GitHub do the holding. Live on hangar-dev (core, clearance-api, the evidence ledger, the GitHub App) and gating hangar-prod's gate-api through the Argo CD agent and plugin, and the Terraform lab workflow in jfillman/hangar-infra. Detail: Clearance gate.md, terraform-gate.md, Glidepath clearance-gate.md. The refusal codes (DG-*) are stable and each carries a fix hint, so a caller can correct itself. Rows marked Building are decided and in development: they are not in effect until the release named in the row ships.

  1. CLR-01

    Protected environments are gated: hangar-prod/*, */prod and */lab (<cluster>/<env> globs; a Terraform plan has no cluster, so */lab matches its workspace). Any other environment clears at tier 0 as unprotected and is recorded like any other change. An empty list protects every environment. A caller cannot opt a change out (protected: false is DG-PROTECTED). hangar-prod's own agent and plugin list hangar-prod/prod and cover one app today (CLR-20).

    Held by hangar-dev cluster.yaml clearance.protectedEnvironments; gate.md: protected environments

    Strength: Enforced (fails closed when the list is empty)

    Enforced
  2. CLR-02

    A change is tiered 0 to 3 from the rendered objects (or the Terraform plan), as the highest tier of anything it touches: 0 docs, tests and label-only edits; 1 an image bump with every check green; 2 runtime shape (replicas, resources, strategy, env, probes, NetworkPolicy, HTTPRoute); 3 Secrets and their references, RBAC, AppProject, and anything in clearance.yaml. Tier is a floor: one tier-3 object makes the whole change tier 3. The platform owns the baseline; a repo's clearance.yaml can only tighten it, and a file that loosens it denies the change (DG-POLICY).

    Held by gate.md: tiers

    Enforced
  3. CLR-03

    Tiers 0 and 1 clear by policy only when every check holds (CI green, provenance confirmed, the tree renders, admission policies pass offline). A tier-1 image bump from a registry outside ghcr.io/riley-roooo/ (Flight) is tier 2. A change whose checks are missing is not cleared by policy and holds for people.

    Held by gate.md: who clears; gate.trustedRegistries in gitops-hangar-dev 55-clearance/clearance/application.yaml

    Enforced
  4. CLR-04

    Tier 2 needs one person from the app's owner group, never the proposer.

    Held by DG-PROPOSER, DG-FILLED; gate.md

    Enforced
  5. CLR-05

    Tier 3 needs two people: one approver from the app's owner group and one from the outside groups (group:default/admins, group:default/breakglass-reviewers), never the proposer and never one person twice. A change with no owner uses the admins group as owner. Tier 3 is never cleared by policy.

    Held by hangar-dev cluster.yaml clearance.gateOutsideGroups; DG-TWICE, DG-FILLED

    Enforced
  6. CLR-06

    Approvers are people. A service account, GitHub Actions identity, bot or agent (*[bot], agent:*, a Kubernetes or ID-JAG issuer) cannot approve (DG-APPROVER-AUTOMATION), and one person under two names or sessions counts once (DG-APPROVER-SAME). A delegate (Tower's backend) naming a person is not refused; the person named is tested. An alias is known only if the deployment lists it.

    Held by gate.md: tier 3 approvers are people

    Enforced
  7. CLR-07

    A PR that looks like an agent's but has no receipt is denied (DG-PROVENANCE). A confirmed agent PR is never cleared at tier 0 by policy alone.

    Held by gate.md: tiers

    Enforced
  8. CLR-08

    Only a registered verifier, or a GitHub Actions workflow of the change's own repository, can register a change (DG-REPO). Clearance matches a workflow by repository name and ignores GitHub's immutable owner and repo ids, so repo:jfillman@7268337/hangar-infra@1433775017:... matches repo:jfillman/hangar-infra:*. The Hangar verifiers are app-*-cicd/pipeline-runner, hangar-prod's repo-server plugin, clearance-deploy-scratch and hangar-infra.

    Held by hangar-dev 55-clearance/clearance/application.yaml verifiers, githubActions.allowed; provenance-app.md

    Enforced
  9. CLR-09

    A change's policy is recorded on every deploy.* record: the baseline version, the repo's clearance.yaml commit and the offline admission verdict. The verdict is recorded, not enforced by Clearance; Glidepath's policy-validation gate (GATE-18) is the check that blocks, subject to GATE-03.

    Held by gate.md: the policy in the record

    Display
  1. CLR-10

    A promotion PR's change must be cleared before the PR merges. Glidepath's clearance-gate- check registers the change at the PR head and reports cleared, holding or denied; the Clearance check should be a required status check on the gitops repo (the owner is setting it on jfillman/gitops-gate-api). The check is also advisory in Glidepath's registry (required: false, no hangar-dev override), so the promotion fan-in does not wait on it (GATE-13).

    Held by clearance-gate- check; Glidepath clearance-gate.md; gate.md: enforcement

    Strength: Off here (GitHub Free cannot require a check on a private repo, so the check reports and nothing stops the merge; see the section 21 row)

    Off here
  2. CLR-11

    If the PR merges before it is cleared, Argo CD's plugin renders the merge commit, finds no cleared change for that tree, and registers a new one with every object treated as new. Its tier comes from the whole tree, its checks are missing so policy cannot clear it, and the sync holds for people.

    Held by clearedByTree, POST /v1/clearances with pr: 0; gate.md: the lookup by tree

    Strength: Enforced (the hold)

    Enforced
  3. CLR-12

    A cleared, unexpired change for the same app, environment, cluster, repository and manifest tree covers the merge commit (clearedByTree): the commit and the PR it was cleared under do not matter, nothing new is registered, and a push of an already-cleared tree supersedes nothing.

    Held by internal/gate/service.go clearedByTree; Glidepath's task renders as <app>-<env> like Argo CD (G-ARGO-8)

    Enforced
  4. CLR-13

    A new head supersedes the older holding and cleared changes of the same PR, or, for a pushed commit, of the same app, environment and cluster. The superseded change's token is revoked at once (deploy.revoke), and the old head cannot be rendered or deployed as cleared.

    Held by gate.md: the state machine

    Enforced
  5. CLR-14

    An applied change stays valid for the objects it stamped until its successor applies: a superseded change with a recorded applied outcome keeps its token in the signed feed (it is no clearance and cannot render), and it is revoked when a later change for the same app, environment and cluster records an applied outcome.

    Held by clearance#113

    Strength: Building (clearance#113, not released; the incident is in section 21)

    Building
  1. CLR-15

    A clearance is a signed token (DSSE, ECDSA P-256) bound to the tree, the object hashes, the cluster, the environment, the tier, the policy and the named approvers. It lives 15 minutes and is re-issued with the same nonce while its change stays cleared, up to the change's horizon. A token whose nonce is not the live one is a replay. Every move out of cleared revokes it in the same transaction.

    Held by CLEARANCE_TOKEN_TTL; gate.md: the clearance token, core.md

    Enforced
  2. CLR-16

    A change holding for people expires after 7 days (a repo's ttlDays can only shorten it); a saved Terraform plan expires after 24 hours. denied, expired, superseded, degraded and exception are final: a new head registers a new change.

    Held by gate.md: the state machine; terraform plan hold

    Enforced
  3. CLR-17

    When the core cannot be reached, tiers 0 and 1 are allowed and recorded, and tiers 2 and 3, and any tier a provider cannot know, are held. A repo can tighten tiers 0 and 1 to hold, never loosen 2 or 3 (DG-POLICY).

    Held by gate.md: the outage policy

    Strength: Fails closed (tiers 2 and 3)

    Fails closed
  4. CLR-18

    While the evidence ledger cannot be checkpointed (every witness failing, one rejecting the checkpoint, or records waiting too long), a tier-3 change whose approvals are all in stays holding. Tiers 0 to 2 are not held by it.

    Held by ledger.tier3Wait: true in hangar-dev's Application; core.md: the outage knob

    Fails closed
  5. CLR-19

    Break-glass is for admins only (admins: user:jfillman, or Tower's backend naming one). It opens one window on one protected environment, for at most 4 hours, with a reason, and the breakglass.open record is written before any change it covers. It clears nothing and applies nothing: a change made in the window is still an exception, tagged with the window. When the window ends the core records what it holds and says what it cannot know. A report that arrives after the window closed is not tagged with it.

    Held by breakglassMaxTTL: "4h"; gate.md: break-glass

    Enforced
  1. CLR-20

    On hangar-prod the gate covers one app, gate-api (clearance.gate.apps, protected namespace app-gate-api-prod). Other hangar-prod apps are not gated.

    Held by gitops-hangar-prod cluster.yaml clearance.gate

    Strength: Enforced for gate-api; Off here for the rest

    Enforced
  2. CLR-21

    Argo CD's Clearance plugin renders the chart, stamps every object with its hash, the tree and (once cleared) the grant, and replaces any grant annotation a chart wrote. A cleared tree syncs. A tree held for people syncs only after a PreSync Job (clearance-awaiting-approval) sees it cleared. A denied, expired, superseded, exception or degraded change, an outage hold, or a failed render is a ComparisonError and nothing syncs.

    Held by gate.md: the plugin and the PreSync wait

    Strength: Enforced (for Applications that use the plugin)

    Enforced
  3. CLR-22

    The PreSync wait Job is not a control. argocd app sync --strategy apply skips hooks and so skips the wait. Admission is what refuses (CLR-24).

    Held by gate.md: the PreSync wait

    Display
  4. CLR-23

    A cluster's grants come only from the core's signed grant feed, read by a listed feed reader (hangar-prod's Argo CD agent only). The agent checks the signature, the issuer, its own cluster, freshness (5 minutes), that the sequence number does not go back within an epoch, that the tokens are exactly the feed's entries, and each token. Any failure changes no grant. Grants past their own expiry are marked invalid on the agent's clock; a grant no entry names is deleted. The agent never creates, updates or deletes a workload.

    Held by feedReaders in hangar-dev cluster.yaml; gate.md: the signed grant feed, the agent

    Fails closed
  5. CLR-24

    The admission policy clearance-protected-writes admits a write to a protected namespace only if the object's stamp is a hash listed by the namespace's one active, valid grant (or another live cleared tree it carries). On hangar-prod it runs in Warn: it warns and audits and refuses nothing.

    Held by gitops-hangar-prod 56-clearance-gate/admission.yaml (validationActions: [Warn, Audit], clearance.gate.enforce)

    Strength: Soft (Warn); Deny not enabled

    Soft
  6. CLR-25

    Before the policy goes from Warn to Deny on hangar-prod, its false exceptions must be resolved, and accepting the flip is the owner's decision alone. With Deny, a protected namespace with no active, valid grant refuses every matched write.

    Held by owner; clearance.gate.enforce in gitops-hangar-prod cluster.yaml

    Strength: Convention (owner decision, Nov 9, 2026 and Nov 10, 2026)

    Convention
  7. CLR-26

    A change that lands outside the gate is an exception: an object with no stamp, a stamp no valid grant lists, or content whose hash is not the stamp. The agent scans every 60 seconds and records one deploy.exception per finding, naming the writer. A change it names moves to exception and its token is revoked.

    Held by gate.md: exceptions

    Strength: Soft (detected and recorded, not stopped)

    Soft
  8. CLR-27

    An applied sync is reported (deploy.outcome) with the applied tree's digest, and the core signs a change attestation for it with a short-lived certificate from the cluster's own Fulcio, logged in its Rekor.

    Held by hangar-dev clearance.attestation.keyless; core.md: change attestations

    Strength: Enforced (keyless is on in hangar-dev's config; no live attestation was read for this page)

    Enforced
  1. CLR-28

    A saved Terraform plan is a change. clearance hold sends the plan's digests, resource addresses, actions and changed attribute names, never a value; sensitive variables contribute their name only. clearance release lets the apply run only when the change is cleared and the plan file's digest is the cleared one. The apply's outcome is compared with the cleared digest, and a mismatch is a digest-mismatch exception that revokes the token.

    Held by terraform-gate.md: guardrails

    Fails closed
  2. CLR-29

    In a protected workspace, any destroy or replace is tier 3 and so is any change to an IAM, security-group, network-ACL, KMS or database resource (a tag included); a -target or -replace plan is one tier higher. A destroy in a protected workspace never clears by policy. hangar-infra's clearance.yaml protects network/* and observe/*.

    Held by gate.md: the terraform-plan subject; hangar-infra clearance.yaml

    Enforced
  3. CLR-30

    The observed account's collector infrastructure (observe/lab in hangar-infra) is applied only through the gated workflow (observe-lab.yaml): plan with a read-only role, hold in Clearance, apply with a separate role that only the lab environment job can assume.

    Held by hangar-infra docs/observe-aws.md

    Strength: Enforced by the workflow; the apply role's trust is read from the docs, not live

    Enforced
  4. CLR-31

    The logging account (observe/logging, the management account) is outside the gate. It is applied by hand with management credentials, because the point of that account is that production administrators and the gated workflow cannot write to it.

    Held by hangar-infra docs/observe-aws.md

    Convention
  5. CLR-32

    In the private hangar-infra repo the lab environment has no Clearance App protection rule (GitHub Free), so the apply job starts at once and fails with "holding, not cleared" until a person clears the plan in Tower. Then the failed job is re-run (gh run rerun <run> --failed) within a day, while the plan artifact is kept and before the plan's 24 hours end.

    Held by hangar-infra docs/observe-aws.md step 4; CLR-16, CLR-28

    Strength: Fails closed (the apply does not run); the protection rule is Off here

    Fails closed
  6. CLR-33

    GitHub's immutable OIDC subject is in force for hangar-infra: AWS role trust must name the repository's immutable subject, repo:jfillman@7268337/hangar-infra@1433775017:environment:lab. A trust on repo:jfillman/hangar-infra:... fails with "Not authorized to perform sts:AssumeRoleWithWebIdentity".

    Held by hangar-infra docs/observe-aws.md; gh api repos/jfillman/hangar-infra/actions/oidc/customization/sub

    Strength: Enforced (by AWS and GitHub; checked against GitHub's subject setting, not the role)

    Enforced
  7. CLR-34

    A GitHub Actions deployment to an environment that has Clearance's App as a protection rule is approved when its change is cleared, rejected when denied, expired, superseded, degraded or an exception, and rejected (DG-UNREGISTERED) when no change matches the repository, commit and environment. While the change holds for people the deployment waits.

    Held by provenance-app.md: the deployment protection rule

    Strength: Enforced where the rule is on (clearance-deploy-scratch); Off here on hangar-infra's private lab (CLR-32)

    Enforced
  1. CLR-35

    Clearance policy and tool changes ship as a Clearance release, then a version bump where they are consumed (hangar-dev pins the chart, targetRevision, and the image by digest; the Autopilot gateway bundle is vendored at its pinned version). They are never edited in a consumer.

    Held by Clearance CHANGELOG; gitops-hangar-dev 55-clearance/clearance/application.yaml

    Convention

20 Clearance: observation, evidence and the packet

Observation is what Clearance sees, whoever made the change: the Kubernetes audit collector (observe-k8s, on both clusters), the AWS collector (observe-aws) and the pipeline behind them. Seeing is not clearing: every observation is observed-not-attributed until an assessment says otherwise. Detail: evidence.md, observe-pipeline.md, observe-k8s.md, observe-aws.md.

  1. OBS-01

    A change made outside the gate (kubectl apply, a console change) is an exception: it records the actor, the diff and a resolution.

    Held by observe-pipeline.md

    Strength: Soft (seen and recorded, not stopped; the stop is CLR-24, in Warn)

    Soft
  2. OBS-02

    A stamp on an object proves nothing by itself. A change is cleared only when its grant annotation names a grant the store knows and the live object's hash is among that grant's own rendered hashes. A grant with another hash is out-of-band (high), a grant the store does not know is out-of-band, and Argo CD's controller writing with no grant is out-of-band.

    Held by observe-pipeline.md: attribute

    Enforced
  3. OBS-03

    A change to a Secret is recorded without its value. The shipped audit policy records Secrets at Metadata at most; the receiver cuts any body before the queue write; the pipeline never reads, hashes or diffs one; the collector's service account has no get, list or watch on Secrets; and the core refuses (400) any observation that carries a Secret body. A Secret change shows its namespace, name, actor, verb and time, and no key names.

    Held by observe-k8s.md: the guardrails; evidence.md

    Fails closed
  4. OBS-04

    A collector that stops makes a blind window, never a clean one. A health record holds for twice its interval (default 15 minutes), and after that the feed is blind until the next. The packet and the coverage statement print "no exceptions detected" only beside a window in which every observed scope was healthy throughout; a scope with no collector reads blind.

    Held by evidence.md: health; clearance-api.md: coverage

    Fails closed
  5. OBS-05

    The Argo CD agent's exception and the collector's observation of the same object are one exception: when they are within 15 minutes the row is the observation's (actor and diff) with the gate record's seq in also_reported_seq. Only out-of-band rows fold; a refusal, a monitoring operation and a break-glass window are never merged away. An apply followed within 15 minutes by a revert is one exception.

    Held by observe-k8s.md: one exception, not two

    Enforced
  6. OBS-06

    An operation on the controls that watch the environment (AWS StopLogging, DeleteTrail, UpdateTrail, PutEventSelectors, recorder and rule deletes; Kubernetes webhook configurations and the audit-policy ConfigMap) is tier 3 whether or not it succeeded, alerts at once, and flips the scope's health: blind when it succeeded, degraded when it was refused. Only the provider's next healthy record clears the flip. It is never noise and never folded away.

    Held by CLEARANCE_MONITORING_PROTECTION_OPS; evidence.md: operations on monitoring

    Enforced
  7. OBS-07

    A refused attempt (a 403, an errorCode) is recorded as attempted-refused and is never noise. Nothing landed, so a change it names is not moved to exception.

    Held by observe-pipeline.md

    Enforced
  1. OBS-08

    A noise rule is one exact identity plus the exact shape it may touch (exact kinds and verbs), with a stated reason. The loader refuses anything wider: a pattern, a service-account group, "every service account of a namespace", no kinds or verbs, an unconditional when, or a monitoring control. A rule that does not compile or is over the cost limit stops the whole bundle from loading; there is no partial rule set. The packet prints the count per rule.

    Held by observe-pipeline.md: noise (ErrBlanketNoise)

    Fails closed
  2. OBS-09

    Routine controller webhook writes are named exceptions (monitoring.kubernetes_exempt), each with a name, a why, one exact subject, exact verbs, the two webhook kinds only and name globs. Only a succeeded write counts. Anything else on a webhook, another verb or name, a person, a refused attempt, and any write to kube-system/audit-policy, stays monitoring protection (OBS-06). A compromised named controller can change its own webhooks without an alert.

    Held by observe-pipeline.md: named exceptions

    Enforced
  3. OBS-10

    Objects a controller creates for an object that a valid grant stamped (ReplicaSet, Pod, Job) take that grant. A controller write that leaves the rendered fields unchanged is attributed. A stamp naming a grant the collector does not know yet waits a settle window before it is judged.

    Held by owner decision, Nov 10, 2026

    Strength: Building (not in Clearance main at 4ccf3dd; no open PR found)

    Building
  4. OBS-11

    The AWS collector governs only its configured account (ruleset.governedAccounts, default the observed account). Attribution uses the exact session-issuer ARNs of the apply roles (ruleset.applyRoles, required) and the SSO admin role glob (ruleset.ssoAdminRoles). The root user is always an exception. A user-agent claim is self-reported, so a plan or grant named only there clears nothing: it needs the apply role and a recorded apply window. A Config item and the CloudTrail event that caused it are one change.

    Held by observe-aws.md: the rule set

    Strength: Enforced in the chart and pipeline (built; not yet registered on hangar-dev, see section 21)

    Enforced
  5. OBS-12

    A successful StopLogging (or any other monitoring operation) flips the AWS account's health to blind through the core, a refused one to degraded. The AWS collector's health record covers trail enabled, regions, event selectors, digests arriving and validating, recorder running, rules enabled and collector current, and states what scope was not evaluated.

    Held by observe-aws.md: health

    Strength: Enforced (built; see section 21)

    Enforced
  1. OBS-13

    Collectors are read-only to the systems they watch and are registered providers with fixed record kinds. observe-k8s patches only its own heartbeat ConfigMap and canary Secret; observe-aws consumes only its own queue. A provider cannot write a kind its registration does not name, and the core sets provider itself; an event cannot. observe-k8s is registered for observation, assessment, resolution and observation.health; the AWS collector also reads clearance-api's outcomes as an auditor.

    Held by providers in hangar-dev 55-clearance/clearance/application.yaml; provider-protocol.md

    Enforced
  2. OBS-14

    A resolution is how an observed change ended. The collector posts the first one (still-live, or reverted when it saw the revert). A person sets or changes it, to reverted (with one of the fixed seven causes: agent-caused, policy-triggered, human-initiated, automated-remediation, requirement-changed, external-dependency, unattributed), ratified, declared-break-glass or still-live. Only admins can (DG-RESOLUTION; a service, an agent or a non-admin gets 403). Resolutions are append-only: the newest wins when read, and every earlier one stays in the chain with who set it.

    Held by gate.md: a person's resolution

    Enforced
  3. OBS-15

    Admins can also resolve gate exceptions (deploy.exception), not only observations.

    Held by owner decision, Nov 10, 2026

    Strength: Building (today the route resolves observations only)

    Building
  4. OBS-16

    Evidence read access: auditors (api.auditors) read everything, including the chain check and observations, health and coverage, which are auditor-only. A person reads their own sessions and evidence, their agents', and records that target an app they own. Tower's backend reads for the person it names. A grant-sync agent reads its own clusters' feed and nothing else.

    Held by clearance-api.md: who sees what; api.auditors, api.feedReaders

    Enforced
  5. OBS-17

    Evidence is one append-only hash chain with one writer (the core, one replica), each record tagging which fields the writer verified and which something else claimed. Free text given as an intent is stored only as a SHA-256. The chain's hashes go into a Tessera log whose checkpoints hangar-prod witnesses and countersigns.

    Held by evidence.md; ledger in hangar-dev's Application

    Enforced
  6. OBS-18

    The evidence packet is built from the chain read from genesis and verified as it goes: a chain that does not verify makes no packet. It prints every coverage state, even at zero, and clearance verify rebuilds each scope's coverage statement offline, so a packet that calls a stopped collector's scope clean fails. The packet's window defaults to 30 days (at most 366). The 30-day re-run of the live packet is due Dec 9, 2026.

    Held by packet.md, verify.md

    Strength: Enforced (the build and the verify); Convention (the re-run: no scheduled job ships)

    Enforced

21 Rules that are not enforced yet

Every place where a rule above is weaker than it reads, in one list:

  • GitHub Free can't enforce rulesets or required checks on private repos.

    GATE-03REL-03API-12

    Gates and sign-off report but don't block. A bypass is detected and announced (GATE-04, REL-05).

  • A GitHub-merged release rests on a GitHub session.

    GATE-15

    Decided Oct 20, 2026 (ADR-0027): an allowed merger's GitHub session is enough to release a PR's code, and in merger mode nobody gitsign-signed that code. On hangar-dev the path is on for every onboarded app (backstage, boarding-api, flight-api, gate-api), in merger mode with 0 approvals (one person merges). Raise requiredApprovals once a second real person merges; use strict where every author can run gitsign.

  • The ruleset assumes every PR is a release PR.

    GATE-03

    Once enforced, non-release PRs would wait forever for gates that never run. Fix before enabling GATE-03 (ruleset audit).

  • App repos aren't gated.

    ENV-02

    By design: Ground is self-service. Only gitops repos get release gates. App repo PRs run cicd-validate (API-12) and the agent gates, which report but don't block (GATE-03).

  • itsm is real only with a provider.

    GATE-01

    This lab points it at a mock change tool (CHG-01 to CHG-06 hold against the mock).

  • policy-validation checks only validating policies.

    GATE-18

    ImageValidatingPolicy, mutating policies, parameterised ValidatingAdmissionPolicies and Pod Security Admission labels are not evaluated; admission still enforces them at sync. Every policy on hangar-prod today is a break-glass rule, so every current release passes until app-facing policies land in 30-policy/.

  • qa is advisory on hangar-dev.

    GATE-12GATE-13

    required: false until every app with a Flight environment has a check ride (boarding-api has one). The check runs and reports; a missing ride does not fail it.

  • The check-rides repository's owners are not enforced.

    TEST-04

    A private repo on GitHub Free: code-owner review is convention. The repo holds no secrets and can be made public.

  • An app opens its NetworkPolicy to tests by hand.

    TEST-07

    A platform default for the testkube namespace is on the test framework roadmap.

  • Production SLO burn isn't read during the bake.

    REL-13

    Production's Prometheus is on and canary analysis uses it, but the outcome is still decided from the rollout alone, so "clean" can't yet mean "no SLO burn during bake".

  • Build signatures aren't checked at admission.

    SC-05

    hangar-prod checks the promotion attestation (PROM-05), not the Chains provenance; hangar-dev checks nothing, so an unsigned image could run there if it got into git.

  • The promote workflow trusts whoever starts it.

    PROM-02PROM-03PROM-05

    The workflow checks its input's shape, not the gates. Anyone who can start it (the promoter App through the broker, or riley-roooo, who owns the repo) gets a valid promotion attestation for any Ground digest, and PROM-05 would admit it. Glidepath only starts it after PROM-03.

  • Flight admission depends on Kyverno being up.

    PROM-05

    failurePolicy: Fail: while hangar-prod's Kyverno admission controller is down, new pods in tenant namespaces are refused. It restarted 354 times between Oct 18, 2026 and Oct 30, 2026.

  • Flight retention is off.

    PROM-09

    Flight keeps every promoted image until promotion.retention.enabled is turned on (dry run first: neither the package listing with the read credential nor deletion with the workflow's token has been tried).

  • Cloud environments can't be Flight while promotion is on.

    PROM-10

    Refused rather than promoted. No app has a cloud Flight environment today; cloud promotion is a recorded Glidepath follow-up.

  • airframe-validate doesn't run on app repos or the tenants repo.

    GATE-08CRED-01

    Airframe roadmap AF-4b.

  • Security review and human extraManifests/networkPolicy/httpRoute changes are convention.

    AF-06AF-07

    Agents are held to AF-06 by Clearance's field deny list (AI-04), and tier 3 needs an outside approver for Secrets and RBAC in a protected environment (CLR-05), but nothing requires a security reviewer for XRD or composition changes, and a person's AF-06 change is not checked.

  • Azure deploys use a stored secret.

    CRED-07

    Lambda and ECS deploys are keyless (Oct 16, 2026/17). Azure Container Apps still logs in with a stored service principal secret; next is workload identity federation (Glidepath known gap 43).

  • Clearance's merge-time check cannot be required.

    CLR-10

    clearance-gate- reports on a release PR but GitHub Free cannot require it on a private repo (the rulesets and branch-protection APIs answer 403 "Upgrade to GitHub Pro" for the four gitops repos checked on Nov 10, 2026), and it is advisory in Glidepath's registry. The owner is setting it as a required status check on jfillman/gitops-gate-api; the other gitops repos have none. A PR merged before it is cleared is held at sync (CLR-11), not at merge.

  • Hangar-prod's admission policy is in Warn.

    CLR-24CLR-25

    clearance-protected-writes warns and audits and refuses nothing. The flip to Deny waits on resolving its false exceptions and on the owner's decision. Until then the gate holds only what Argo CD's plugin holds (CLR-21), and a sync that skips the plugin's hook is not stopped (CLR-22).

  • An applied change goes invalid when a newer head supersedes it.

    CLR-13CLR-14

    hangar-prod, Nov 10, 2026: change ch-1dda08661ae5b7b1 was cleared and applied, then superseded by a later pushed head; its token was revoked and its grant left the cluster, and the agent raised a false deploy.exception (seq 9723, as reported by the owner; not re-read for this page) for the live objects until the new head applied. The fix, clearance#113, is not released.

  • Controller-derived objects raise false exceptions.

    OBS-10

    A ReplicaSet, Pod or Job a controller creates for a stamped object, and a controller write that leaves the rendered fields unchanged, can read as out-of-band until OBS-10 ships.

  • Only gate-api is gated on hangar-prod.

    CLR-20

    clearance.gate.apps: [gate-api], protected namespace app-gate-api-prod. Other hangar-prod apps deploy ungated, and hangar-prod's own protected list is hangar-prod/prod, narrower than the core's.

  • Clearance traffic between clusters is plain HTTP.

    CLR-21CLR-23

    hangar-prod's plugin, wait Job and agent reach the core and clearance-api at http://clearance.hangar-dev.local and http://clearance-api.hangar-dev.local. The tokens are short-lived projected ServiceAccount tokens and the grant feed is signed, but the traffic is readable on the LAN.

  • An outage let-through leaves no record.

    CLR-17

    The Argo CD plugin keeps no outbox, so a tier 0 or 1 change allowed while the core was down is not followed by a deploy.degraded record.

  • The Terraform lab apply is held by a failing job, not a protection rule.

    CLR-32CLR-34

    lab in the private hangar-infra repo has no Clearance App protection rule, so a person clears the plan and re-runs the failed apply job within a day.

  • The AWS collector is built, not yet live.

    OBS-11OBS-12OBS-13

    observe-aws is not registered in hangar-dev's providers or auditors (gitops-hangar-dev 446ea51), its Terraform is in hangar-infra (observe/), and Clearance's own doc calls it unverified against a real trail, queue, Config recorder and AWS-signed digest. The rules hold in the code and tests; no live exception from AWS has been read for this page.

  • Kubernetes audit flags are node work.

    OBS-01OBS-04

    The collector is enabled on both clusters (clearance.observe.enabled), but the API server's audit flags are set by hand on each control-plane node (tools/observe-audit) and were not read for this page. Clearance's observe-k8s.md still says "not yet rolled out". A collector with no audit feed reads blind (OBS-04), never clean.

  • Approver aliases are only known if listed.

    CLR-06

    The gate has no directory of a person's other names. A person who signs in under a second identity that no alias names counts as two approvers.

  • The 30-day packet re-run is manual.

    OBS-18

    No scheduled job ships for clearance packet (due Dec 9, 2026).

  • Only admins can run software templates.

    ACC-02

    App teams can't scaffold a new service themselves. Intentional for the lab; revisit before onboarding other teams.

  • The API trusts the delegate's person check.

    API-04

    A write from Tower or Clearance names a person, but the API can only authorize the delegate. If Tower's owner check (ACC-03) were wrong, the API would still act; the audit line records both names.

  • People's route is plain HTTP in this lab.

    API-10

    The Gateway listener has no TLS, so an ID token crosses the LAN in the clear, as it does for Clearance and Dex (Clearance's sign-in issuer and gateway URL are http://). Use it on a trusted network until the Gateway has TLS.

  • CI/CD events are best effort.

    API-11

    Each replica keeps the last 2000 events, for 24 h at most, in memory. If both replicas restart at once, a subscriber that was away misses events. Durable fan-out over AMQP is designed and deferred (Glidepath known gap 44, ADR-0030).

22 Keeping this page true

  • A change that adds, changes or removes a rule updates this page in the same change: here when the change is in this repo, or with a linked Hangar PR when it isn't. The component's own docs keep the detail.
  • A rule that exists only in code is a bug in this page. Add it with the next free ID in its section.
  • A retired rule keeps its row, struck through, with the date and the reason. Its ID is never reused.
  • The commit list at the top says what this page was last checked against. Re-check it after any release of Tower, Glidepath, Airframe or Backstage that touches permissions, gates, release records or admission policies.
What these rules were last checked against

Last checked against the code on Oct 19, 2026: backstage e40008f (+ branch tests-route), tower 2228fbc (v0.80.0), glidepath 0e66378, airframe 4b3cde1 (v0.3.157), gitops-hangar-dev 6a6f055, gitops-hangar-prod d751f6e, check-rides 193afc2. REL-17 and REL-18 (commit proof on the Release Record and in the score) added Oct 20, 2026 with Tower v0.83.0 and glidepath 36eb223. GATE-05, GATE-06 and GATE-15 to GATE-17 (release-signing policy, GitHub-merge path) re-checked on Oct 20, 2026: glidepath 9c42d81 (ADR-0027), tower be8111b (v0.82.0), backstage 61634fb, gitops-hangar-dev 0d48da0, apron 24d23d0, airframe 8aea2f6. GATE-01 and GATE-18 (policy-validation real) checked on Oct 27, 2026 against glidepath 22cd9ff (ADR-0028). PROM-01 to PROM-11, POL-11 and the SC-05 change (Ground and Flight registries, ADR-0029) added Oct 30, 2026, checked against glidepath 983d2e0, apron 2493b02, gitops-hangar-dev 52b1223, gitops-hangar-prod fd9082a, and live on both clusters. OPS-06, POL-01 and TEST-10 changed Oct 30, 2026 with Glidepath API P6 (Tower on glidepath-api, no Tekton writes), checked against gitops-hangar-dev 52b1223 and live (kubectl auth can-i). API-01 to API-12 (the CI/CD API, glidepath-api, and the cicd-validate check) added Nov 1, 2026, checked against glidepath d48e1b7 (contract 0.11.0) and gitops-hangar-dev fd0ed74, and live on hangar-dev. CRED-07 to CRED-09, POL-10) re-checked on Oct 17, 2026: glidepath 1ad8dae, airframe 0710485 (v0.3.148), gitops-cluster-dev #108. CLR-01 to CLR-35, OBS-01 to OBS-18, AI-03 to AI-05 and the section 21 rows for Clearance added or changed Nov 10, 2026, checked against clearance 4ccf3dd (v0.39.3, docs and code at origin/main), gitops-hangar-dev 446ea51 (live clearance: block and 55-clearance/clearance/application.yaml, chart 0.39.0), gitops-hangar-prod d957792 (clearance: block, 56-clearance-gate/admission.yaml) and hangar-infra 737907c. Verified from those files and from GitHub's API for the gitops repos; nothing was read from a running cluster or AWS account for this change. Rows marked Building (CLR-14, OBS-10, OBS-15) are decisions of Nov 10, 2026 and describe code that has not shipped.

This page is built from docs/policy/business-rules.md in the Hangar repo every time the site builds (this build: e9feafe), so it says exactly what the document says. A change that adds or changes a rule updates the document in the same change as the code. You can also read it as a plain document with the rest of the Hangar docs.