Hangar

Docs · Hangar

This document also has its own page, with every rule searchable by strength: Business rules.

Business rules

Every rule Hangar applies to people, releases and clusters, in one place. Each component’s own docs explain how a rule works and why; this page lists every rule, how strongly it holds, and what holds it. If a rule is not on this page, it is not a rule yet.

Last checked against the code on 2026-10-19: 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 2026-10-20 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 2026-10-20: 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 2026-10-27 against glidepath 22cd9ff (ADR-0028). PROM-01 to PROM-11, POL-11 and the SC-05 change (Ground and Flight registries, ADR-0029) added 2026-10-30, 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 2026-10-30 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 2026-11-01, 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 2026-10-17: 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 2026-11-10, 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 2026-11-10 and describe code that has not shipped.

How to read it

Each rule has an ID that never changes and is never reused. Strength says what happens when someone tries to break the rule:

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

Source links to the doc that explains the rule in full, or to the code when no doc does.

Contents

  1. People and roles
  2. Access in the portal
  3. Environments and promotion
  4. Release gates
  5. Testing and the check ride
  6. Change management (itsm)
  7. Release records, modes and sign-off
  8. Rollback
  9. Supply chain
  10. Live operations from Tower
  11. Break-glass debug sessions
  12. Images and retention
  13. Service catalog (Airframe)
  14. Cluster admission policies
  15. Credentials and secrets
  16. Notifications
  17. AI agents
  18. The CI/CD API (glidepath-api)
  19. Clearance: the deployment gate
  20. Clearance: observation, evidence and the packet
  21. Rules that are not enforced yet
  22. Keeping this page true

1. People and roles

ID Rule Strength Held by
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. Enforced Backstage auth (usernameMatchingUserEntityName); the guest provider refuses outside NODE_ENV=development
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. Convention backstage examples/org.yaml
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. Enforced the catalog’s isEntityOwner rule
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). Enforced permissionPolicy.ts
PPL-05 Break-glass reviewers (group:default/breakglass-reviewers) may review a glass-broken session. They get nothing else from that group. Enforced Tower’s break-glass routes
PPL-06 Tower’s write routes accept only a signed-in user. A service token is refused. Enforced towerAuthz.ts

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

2. Access in the portal

ID Rule Strength Held by
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. Enforced permission policy (every read allowed)
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. Enforced permissionPolicy.ts WRITE_PERMISSIONS
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. Enforced towerPermissions.ts, towerAuthz.ts
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. Enforced towerGrantOnlyPermissions
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. Enforced towerAppFromEntity
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. Enforced kubernetesProxyGuard.ts
ACC-07 Tower greys out the actions you may not take and says why. That is a courtesy: every route checks again. Display Tower /argo/capabilities
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. Enforced Backstage auditor

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

3. Environments and promotion

ID Rule Strength Held by
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. Enforced Backstage routes; Glidepath
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. Enforced Glidepath deploy-manifests
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. Enforced Glidepath ADR-0004
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. Enforced glidepathPromote.ts resolveSource
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. Enforced Glidepath release.md
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. Enforced Argo CD (selfHeal, prune, allowEmpty: false, preserveResourcesOnDeletion)
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. Enforced Glidepath security.md
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. Enforced Glidepath, Airframe
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. Enforced Glidepath ephemeral-environments.md
ENV-10 A Flight destination must be a registered upper cluster marked crossplaneReady. A dev cluster is refused. Enforced Airframe ApplicationEnvironment schema and cluster registry

Ground and Flight registries (PROM)

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.

ID Rule Strength Held by
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. Enforced open-release-pr; promote refuses
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). Enforced GHCR package permissions; broker /promoter-token
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. Enforced comment-pr-check-result fan-in; promote
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. Enforced promote workflow; promote-image StepAction
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 2026-10-30 (Audit from 2026-10-29). Fails closed Kyverno flight-only-images (ValidatingPolicy) and flight-images (ImageValidatingPolicy), Deny, failurePolicy: Fail, rendered by Apron admission.flight (POL-11)
PROM-06 Only the app’s own Ground repository is promotable: never a PR preview build (<app>-pr) or another app’s image. Enforced promote
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. Enforced (not yet exercised by a live rollback) promote workflow (digest copy is idempotent)
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. Enforced (promote refuses a merged PR); the bypass alert is Soft promote; detect-bypass-merge (GATE-04)
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. Off here (Flight only grows: promotion.retention.enabled is false; the prune workflow is in the promoter repo since 2026-10-30 but has not run) Glidepath flight-prune CronJob; prune workflow
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. Enforced resolve-deploy-target
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. Enforced Glidepath catalog values; Apron cluster.yaml

4. Release gates

ID Rule Strength Held by
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. Enforced (they run) Glidepath release-guardrails.md
GATE-02 A stub never reports a pass. It logs that no check exists, returns stub, and shows as a grey STUB everywhere. Enforced Glidepath governance-stubs.md, ADR-0003
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. Off here Glidepath github-ruleset.md. Private repos on GitHub Free cannot enforce rulesets, so anyone with write access can merge past a red gate.
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. Soft bypass-check (detect-bypass-merge)
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 2026-10-20: the list was the app’s own cicd.yaml. Fails closed provenance gate (verify-commit-signature); Glidepath commit-signing.md
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. Convention (GATE-05 and GATE-15 catch it) commit-signing.md: merge strategy
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. Enforced provenance gate; Glidepath provenance-policy.md
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). Enforced (subject to GATE-03) values gate
GATE-09 A Release Record PR (release-record-* branch) is not a release, and the gates skip it. Enforced gate triggers
GATE-10 /retest re-runs a gate. It never skips one. Enforced Pipelines as Code
GATE-11 A new or removed gate goes in the releaseGuardrails registry, and the ruleset is regenerated from it in the same change. Convention hack/apply-guardrail-ruleset.sh
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. Enforced as a check; Soft today (advisory, GATE-13) qa-gate; Glidepath testing.md
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. Enforced glidepath-catalog.guardrails; release-guardrails.md: Required and advisory gates
GATE-14 sast and provenance cannot be made advisory: a catalog that sets either to required: false fails to render. Enforced glidepath-catalog chart render
GATE-15 GitHub-merge path (2026-10-20, 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. Fails closed verify-commit-signature; commit-signing.md: the GitHub-merge path, ADR-0027
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. Enforced (the gate reads only the catalog’s ConfigMap; the schema refuses enabled/allowedMergers in cicd.yaml) release-signing-policy ConfigMap; commit-signing.md: who sets the policy
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 2024-01-16. The gate never fetches a key while verifying. A weekly workflow warns when GitHub publishes a different key set. Fails closed toolbox image, verify-commit-signature, web-flow-key-check workflow
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. Enforced Glidepath policy-validation.md (ADR-0028)

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.

ID Rule Strength Held by
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. Enforced run-testworkflow
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. Enforced run-testworkflow
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. Enforced run-testworkflow, validateFlows
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. Convention (code owners on a private repo on GitHub Free); Enforced with the ruleset check-rides CODEOWNERS, tools/check.py
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. Enforced Kyverno testkube-secret-usage (ADR-0008)
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. Enforced Chains; test.yaml type hints
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). Convention airframe-application
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. Display Tower v0.79.0 supplyChain.ts
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”. Enforced (record) / Display (live) backend testEvidence.ts; Tower v0.80.0 findReleaseTestRun
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 2026-10-30: Tower read Testkube itself through the control plane’s service proxy; that grant is gone. Display backend /tests, /tests/report; glidepath-api

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.

ID Rule Strength Held by
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. Enforced open-release-pr
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. Enforced (subject to GATE-03 and GATE-13) itsm-gate
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. Enforced bypass-merge-check, release-outcome-notify, release-rollback-notify
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). Enforced onError: continue on the hooks
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. Enforced itsm.credentials ClusterExternalSecret
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). Enforced itsm-gate
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. Display backend changeRequestOf; Tower v0.80.0 ReleaseRecordEvidence.tsx

7. Release records, modes and sign-off

ID Rule Strength Held by
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. Enforced tower.release.mode (admins only)
REL-02 The sign-off environment is the one cicd.yaml marks production: true, else the last Flight environment. Enforced releaseFlow.ts
REL-03 For a Hand-flown app, a release PR to the sign-off environment needs a sign-off. Soft the hangar/release-signoff commit status; GitHub Free can’t make it required
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. Fails closed releaseApprovers.ts, releaseFlow.ts
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. Soft release-signoff-poll
REL-06 Assisted apps record who merged and who reviewed. They get no status and no alert. Enforced release-signoff-poll
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. Enforced tower.release-record.edit; sanitizeDraft
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. Enforced release-record-seal-poll (2 min), release-record-poll (hourly)
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. Enforced releaseNames.ts (Tower and backend, kept identical)
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. Enforced Tower
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. Display Tower nextRelease.ts. Nothing is blocked.
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. Display Tower
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. Enforced releaseOutcome.ts (BAKE_DAYS = 7)
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. Enforced releaseOutcome.ts
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. Enforced release-signoff-poll
REL-16 The backend writes only PRs, commit statuses, GitHub Releases and the draft commit. It never merges. Enforced Tower security.md: release records
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 2026-10-20 say “not recorded” (no backfill). The same proof shows on the record rows, the release cards, the pending-promotion task and the Release matrix. Display Tower commitProof.ts; backend parseGateComments; Glidepath commit-signing.md: Evidence
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. Display Tower computeConfidence / confidenceBreakdown

Full detail: Tower release records.

8. Rollback

ID Rule Strength Held by
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). Enforced Glidepath rollback-release; Tower
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. Enforced extract-promoted-image rollback-eligibility
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. Enforced Glidepath release-state.md: rollback

9. Supply chain

ID Rule Strength Held by
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 2026-10-17). Until then this rule said restricted (kaniko), which never held: kaniko ran as root. Enforced Pod Security admission, labels set by Glidepath’s tenant-onboarding ApplicationSet; security.md, rootless-builds.md, ADR-0026
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. Enforced Glidepath image-signing.md
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. Display Tower supply-chain.md
SC-04 Readiness’s “Signed and attested” passes only on a verified attestation, never on one that is merely present. Display Tower isSignedAndAttested
SC-05 Build signatures and provenance are checked at release (GATE-07), not when a cluster admits an image. Since 2026-10-30 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. Enforced at release; at admission on hangar-prod through PROM-05 see section 21

10. Live operations from Tower

ID Rule Strength Held by
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. Enforced Argo CD RBAC
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. Enforced Argo CD role tower-pod (delete//Pod/* only)
OPS-03 Sync and force sync: owners on Ground, admins only on Flight. Enforced ACC-04
OPS-04 Restarting a pod never takes the last ready pod of a Flight ReplicaSet. On Ground it needs an explicit “accept downtime”. Enforced podRestart.ts
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. Soft tower.rollout.bypass
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 2026-10-30: tower-actor wrote the runs itself, held to the shape by tower-pipelinerun-shape. Enforced glidepath-api-pipelinerun-shape policy (POL-01)
OPS-07 Configuration changes (values, env XR, ConfigMap files, cicd.yaml) are PRs, never direct edits. Enforced tower.config.edit

11. Break-glass debug sessions

ID Rule Strength Held by
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. Fails closed Tower routes, and the cluster refuses a grant whose approver is the requester
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). Soft (the review) tower.breakglass.self
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. Enforced backend timers; expiry labels the cluster acts on
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. Fails closed tower-breakglass-* admission policies
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. Fails closed tower-breakglass-rolebinding
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. Fails closed Tower break-glass guardrails
BG-07 Admins can end anyone’s session, and on Flight so can the session’s approver. Anyone can end their own. Enforced tower.breakglass.terminate

12. Images and retention

ID Rule Strength Held by
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. Enforced imagePrunePlan.ts PLATFORM_PACKAGE
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. Enforced imagePrune.ts
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. Enforced imagePrunePlan.ts
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. Enforced release-record-sweeper
RET-02 A release that is merged or progressing with no new fact for 45 minutes raises ReleaseStalled, once per state. Enforced release-record-sweeper
RET-03 A pipeline stage that hasn’t started its next stage 10 minutes after finishing is reported as stalled. Enforced stalled pipeline detector

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:

ID Rule Strength Held by
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. Enforced XRD schema and CEL at admission
AF-02 Composed resources get least-privilege roles, the baseline NetworkPolicy, dedicated data stores per environment, and hangar.io/* tags and labels. Enforced compositions
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. Enforced management policies
AF-04 Upper-environment and catalog changes are PRs a person merges. A catalog change ships as a tag, pinned per cluster, dev first. Enforced GitOps; Argo CD self-heal reverts hand edits
AF-05 Only control-plane clusters can be marked autopilotReady. Enforced cluster registry chart
AF-06 extraManifests, networkPolicy and httpRoute changes need a person. Convention AGENTS.md
AF-07 A security reviewer approves changes to XRDs and compositions. Convention not enforced

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).

ID Policy Cluster Rule
POL-01 glidepath-api-pipelinerun-shape dev 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 2026-10-30, which held Backstage’s own PipelineRuns to that shape.
POL-02 tower-rolebinding-delete dev Backstage may delete only tower-breakglass-* RoleBindings.
POL-03 tower-breakglass-rolebinding both The only grant Backstage may create. On Flight it must name an approver other than the requester, or carry the glass-broken mark.
POL-04 tower-breakglass-debug-container, -debug-image both The exact debug image digest and its cosign signature, hardened settings, and process access only for the approved session.
POL-05 tower-breakglass-pod-delete, -pod-annotate both Backstage deletes only debug or copy pods, and patches only the process-access annotation.
POL-06 tower-breakglass-no-exec prod No exec into app pods for anyone but operators. Attach only to a debug container.
POL-07 no-long-lived-sa-tokens dev No long-lived ServiceAccount token Secrets in backstage-system.
POL-08 testkube-secret-usage dev A tenant’s TestWorkflow may reference only that tenant’s own Secrets in the shared testkube namespace.
POL-10 glidepath-cloud-deployer-pods, -taskruns, -pipelineruns, -jobs dev 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.
POL-11 flight-only-images, flight-images hangar-prod 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.
POL-09 (how policies are written) both 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.

Full detail: Tower break-glass admission policies.

15. Credentials and secrets

ID Rule Strength Held by
CRED-01 Secrets are references to Infisical, never values in git or an XR. Enforced on gitops repos (GATE-08), convention elsewhere AF-SECRET-001
CRED-02 People create credential material themselves, in Infisical. It is never pasted into a chat, a terminal transcript or a repo. Convention Glidepath secrets-management.md
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. Enforced Tower credentials; POL-07
CRED-04 The GitHub App’s private key never leaves platform-system. A release gets an installation token for its own gitops repo only. Enforced token-review-interceptor
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 2026-10-16). Enforced Airframe provider configs, External Secrets; Airframe cloud-credentials.md
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). Enforced (Lambda since phase 4, 2026-10-16; ECS since 2026-10-17, glidepath#119 and airframe#95; aws_credentials_test.sh in catalog-ci; Airframe no-web-identity render cases) Glidepath cloud-deploy-identity.md; POL-10
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. Enforced (IAM) HangarCrossplaneCloudTargets v3; Airframe cloud-credentials.md
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. Enforced (render tests) Airframe v0.3.144 and v0.3.145; incident 2026-10-16 in cloud-credentials.md
CRED-06 The image-prune token can only read and delete packages. The backend holds it, never the browser. Enforced IMG-01

16. Notifications

ID Rule Strength Held by
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. Enforced Hangar SLO docs, sloTransitionPoll.ts
NOT-02 Release announcements, Rollout actions, analysis bypasses, broken glass, Flight pod restarts and image prunes go to everyone. Enforced Backstage notifications
NOT-03 A bypass merge (GATE-04) is alerted however notifications are set. Enforced detect-bypass-merge

17. AI agents

ID Rule Strength Held by
AI-01 An agent passes the same pre-merge checks a person does, and works within AGENTS.md. Enforced (the checks), convention (AGENTS.md) Airframe
AI-02 Agent runs go only to clusters marked autopilotReady (AF-05). Enforced AgentRun composition
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 2026-11-10: this row said the limits were built but not deployed. Fails closed (the decision); the Autopilot gateway, which asks first, enforces it 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)
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. Enforced gitops-hangar-dev 55-clearance/clearance/application.yaml vocabulary.baselineDenyPaths, baselineDenyFields
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). Enforced section 19

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 2026-10-30 (OPS-06). Detail: Glidepath api.md.

ID Rule Strength Held by
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. Fails closed api.md: Who may read what
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. Enforced glidepath-control-plane api.readers, api.operators, api.releasers
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. Enforced glidepath-api
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). Enforced (the header); the person check is the delegate’s api.delegates
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. Enforced glidepath-api; api/openapi.yaml x-glidepath-tier
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. Enforced (no route) api.md
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. Enforced glidepath-api
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. Enforced broker POST /github-read-token (githubRead.callers)
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. Enforced glidepath-api audit log
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. Enforced api.oidc, api.route
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. Enforced /internal/events/tap, peer fan-out
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). Enforced as a check; Off here as a blocker Glidepath contract.md

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.

Which changes are gated, and who clears them

ID Rule Strength Held by
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). Enforced (fails closed when the list is empty) hangar-dev cluster.yaml clearance.protectedEnvironments; gate.md: protected environments
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). Enforced gate.md: tiers
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. Enforced gate.md: who clears; gate.trustedRegistries in gitops-hangar-dev 55-clearance/clearance/application.yaml
CLR-04 Tier 2 needs one person from the app’s owner group, never the proposer. Enforced DG-PROPOSER, DG-FILLED; gate.md
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. Enforced hangar-dev cluster.yaml clearance.gateOutsideGroups; DG-TWICE, DG-FILLED
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. Enforced gate.md: tier 3 approvers are people
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. Enforced gate.md: tiers
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. Enforced hangar-dev 55-clearance/clearance/application.yaml verifiers, githubActions.allowed; provenance-app.md
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. Display gate.md: the policy in the record

Promotion PRs, merge order and supersession

ID Rule Strength Held by
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). 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) clearance-gate- check; Glidepath clearance-gate.md; gate.md: enforcement
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. Enforced (the hold) clearedByTree, POST /v1/clearances with pr: 0; gate.md: the lookup by tree
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. Enforced internal/gate/service.go clearedByTree; Glidepath’s task renders as <app>-<env> like Argo CD (G-ARGO-8)
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. Enforced gate.md: the state machine
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. Building (clearance#113, not released; the incident is in section 21) clearance#113

Tokens, expiry, outage and break-glass

ID Rule Strength Held by
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. Enforced CLEARANCE_TOKEN_TTL; gate.md: the clearance token, core.md
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. Enforced gate.md: the state machine; terraform plan hold
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). Fails closed (tiers 2 and 3) gate.md: the outage policy
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. Fails closed ledger.tier3Wait: true in hangar-dev’s Application; core.md: the outage knob
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. Enforced breakglassMaxTTL: "4h"; gate.md: break-glass

The Argo CD path (hangar-prod)

ID Rule Strength Held by
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. Enforced for gate-api; Off here for the rest gitops-hangar-prod cluster.yaml clearance.gate
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. Enforced (for Applications that use the plugin) gate.md: the plugin and the PreSync wait
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). Display gate.md: the PreSync wait
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. Fails closed feedReaders in hangar-dev cluster.yaml; gate.md: the signed grant feed, the agent
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. Soft (Warn); Deny not enabled gitops-hangar-prod 56-clearance-gate/admission.yaml (validationActions: [Warn, Audit], clearance.gate.enforce)
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. Convention (owner decision, 2026-11-09 and 2026-11-10) owner; clearance.gate.enforce in gitops-hangar-prod cluster.yaml
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. Soft (detected and recorded, not stopped) gate.md: exceptions
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. Enforced (keyless is on in hangar-dev’s config; no live attestation was read for this page) hangar-dev clearance.attestation.keyless; core.md: change attestations

The Terraform path (hangar-infra) and GitHub deployments

ID Rule Strength Held by
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. Fails closed terraform-gate.md: guardrails
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/*. Enforced gate.md: the terraform-plan subject; hangar-infra clearance.yaml
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. Enforced by the workflow; the apply role’s trust is read from the docs, not live hangar-infra docs/observe-aws.md
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. Convention hangar-infra docs/observe-aws.md
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. Fails closed (the apply does not run); the protection rule is Off here hangar-infra docs/observe-aws.md step 4; CLR-16, CLR-28
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”. Enforced (by AWS and GitHub; checked against GitHub’s subject setting, not the role) hangar-infra docs/observe-aws.md; gh api repos/jfillman/hangar-infra/actions/oidc/customization/sub
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. Enforced where the rule is on (clearance-deploy-scratch); Off here on hangar-infra’s private lab (CLR-32) provenance-app.md: the deployment protection rule

How the gate ships

ID Rule Strength Held by
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. Convention Clearance CHANGELOG; gitops-hangar-dev 55-clearance/clearance/application.yaml

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.

What counts as an exception

ID Rule Strength Held by
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. Soft (seen and recorded, not stopped; the stop is CLR-24, in Warn) observe-pipeline.md
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. Enforced observe-pipeline.md: attribute
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. Fails closed observe-k8s.md: the guardrails; evidence.md
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. Fails closed evidence.md: health; clearance-api.md: coverage
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. Enforced observe-k8s.md: one exception, not two
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. Enforced CLEARANCE_MONITORING_PROTECTION_OPS; evidence.md: operations on monitoring
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. Enforced observe-pipeline.md

Noise and attribution

ID Rule Strength Held by
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. Fails closed observe-pipeline.md: noise (ErrBlanketNoise)
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. Enforced observe-pipeline.md: named exceptions
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. Building (not in Clearance main at 4ccf3dd; no open PR found) owner decision, 2026-11-10
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. Enforced in the chart and pipeline (built; not yet registered on hangar-dev, see section 21) observe-aws.md: the rule set
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. Enforced (built; see section 21) observe-aws.md: health

Providers, resolutions and who can read

ID Rule Strength Held by
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. Enforced providers in hangar-dev 55-clearance/clearance/application.yaml; provider-protocol.md
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. Enforced gate.md: a person’s resolution
OBS-15 Admins can also resolve gate exceptions (deploy.exception), not only observations. Building (today the route resolves observations only) owner decision, 2026-11-10
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. Enforced clearance-api.md: who sees what; api.auditors, api.feedReaders
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. Enforced evidence.md; ledger in hangar-dev’s Application
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 2026-12-09. Enforced (the build and the verify); Convention (the re-run: no scheduled job ships) packet.md, verify.md

21. Rules that are not enforced yet

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

Gap Rules What it means today
GitHub Free can’t enforce rulesets or required checks on private repos. GATE-03, REL-03, API-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 2026-10-20 (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-12, GATE-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-02, PROM-03, PROM-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 2026-10-18 and 2026-10-30.
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-08, CRED-01 Airframe roadmap AF-4b.
Security review and human extraManifests/networkPolicy/httpRoute changes are convention. AF-06, AF-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 (2026-10-16/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 2026-11-10), 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-24, CLR-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-13, CLR-14 hangar-prod, 2026-11-10: 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-21, CLR-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-32, CLR-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-11, OBS-12, OBS-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-01, OBS-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 2026-12-09).
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.