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.
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
-
PPL-01
Enforced
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 outsideNODE_ENV=development -
PPL-02
Convention
Groups and memberships come from the catalog (
examples/org.yaml, each User'smemberOf). Nothing syncs them from GitHub: a personal account has no org to sync from.Held by backstage
examples/org.yaml -
PPL-03
Enforced
An app's owners are its catalog Component's
spec.owner, which comes from the XR'sterasky.backstage.io/ownerannotation. You own an app if you, or a group you belong to, are its owner.Held by the catalog's
isEntityOwnerrule -
PPL-04
Enforced
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 -
PPL-05
Enforced
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
-
PPL-06
Enforced
Tower's write routes accept only a signed-in user. A service token is refused.
Held by
towerAuthz.ts
Today's groups are admins, team-passenger, team-flight-ops, team-skyport, team-platform and breakglass-reviewers.
2 Access in the portal
-
ACC-01
Enforced
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)
-
ACC-02
Enforced
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.tsWRITE_PERMISSIONS -
ACC-03
Enforced
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 -
ACC-04
Enforced
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 -
ACC-05
Enforced
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 -
ACC-06
Enforced
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 -
ACC-07
Display
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 -
ACC-08
Enforced
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
Full action-by-action table: Tower security model.
3 Environments and promotion
-
ENV-01
Enforced
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
-
ENV-02
Enforced
Ground is self-service: a deploy is a direct commit to the app repo's
glidepath/envs/<env>.yamlby the deploy bot. No release gates run on Ground.Held by Glidepath
deploy-manifests -
ENV-03
Enforced
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
-
ENV-04
Enforced
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.tsresolveSource -
ENV-05
Enforced
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
-
ENV-06
Enforced
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) -
ENV-07
Enforced
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
-
ENV-08
Enforced
Each app environment has its own namespace (
<type>-<app>-<env>), and pipelines run in the app's-cicdnamespace. RBAC never spans them.Held by Glidepath, Airframe
-
ENV-09
Enforced
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.
-
ENV-10
Enforced
A Flight destination must be a registered upper cluster marked
crossplaneReady. A dev cluster is refused.Held by Airframe
ApplicationEnvironmentschema and cluster registry
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.
-
PROM-01
Enforced
A Flight environment's release file pins the Flight repository and a digest (
release.image.digest), with the Ground repository it came from assource, from the moment the release PR opens. A release that pins Ground, or no digest, is never promoted.Held by
open-release-pr;promoterefuses -
PROM-02
Enforced
Only the promoter writes Flight: the
promoteworkflow inriley-roooo/flight-promoter, whose own short-livedGITHUB_TOKENis the only credential with write access. Builds, clusters and Glidepath cannot write it; Glidepath can only start the workflow (thehangar-flight-promoterApp,actions: writeon that one repo, minted per call by the broker for theglidepath-promoterServiceAccount only).Held by GHCR package permissions; broker
/promoter-token -
PROM-03
Enforced
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;
promotere-checks every gate itself and refuses a red or missing one.image-promotionis then a required check on the PR, and it cannot be made advisory.Held by
comment-pr-check-resultfan-in;promote -
PROM-04
Enforced
The copy keeps every digest: the image index, every platform manifest, the
.sig/.atttags 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
promoteworkflow;promote-imageStepAction -
PROM-05
Fails closed
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) andflight-images(ImageValidatingPolicy), Deny,failurePolicy: Fail, rendered by Apronadmission.flight(POL-11) -
PROM-06
Enforced
Only the app's own Ground repository is promotable: never a PR preview build (
<app>-pr) or another app's image.Held by
promote -
PROM-07
Enforced
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
promoteworkflow (digest copy is idempotent)Strength: Enforced (not yet exercised by a live rollback)
-
PROM-08
Enforced
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-promotioncheck is reported as a bypass.Held by
promote;detect-bypass-merge(GATE-04)Strength: Enforced (
promoterefuses a merged PR); the bypass alert is Soft -
PROM-09
Off here
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
pruneworkflow deletes. A package without a readable gitops repo is never pruned.Held by Glidepath
flight-pruneCronJob;pruneworkflowStrength: Off here (Flight only grows:
promotion.retention.enabledis false; thepruneworkflow is in the promoter repo since Oct 30, 2026 but has not run) -
PROM-10
Enforced
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 -
PROM-11
Enforced
The registries and promotion settings are admin-owned: the catalog's
registriesandpromotionvalues and the cluster'sadmission.flight, nevercicd.yamlor Tower.Held by Glidepath catalog values; Apron
cluster.yaml
4 Release gates
-
GATE-01
Enforced
Every release PR to a gitops repo runs every registered gate:
sast,image-scan,provenance,sbom,values,qaandpolicy-validationare real;itsmis 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)
-
GATE-02
Enforced
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
-
GATE-03
Off here
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.
-
GATE-04
Soft
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) -
GATE-05
Fails closed
The commit being released must be proven one of two ways: gitsign-signed (public Sigstore) by an address in the app's
allowedCommitSignersfrom 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 owncicd.yaml.Held by
provenancegate (verify-commit-signature); Glidepath commit-signing.md -
GATE-06
Convention
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.
-
GATE-07
Enforced
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
provenancegate; Glidepath provenance-policy.md -
GATE-08
Enforced
Every changed
values.yamlpassesairframe-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
valuesgateStrength: Enforced (subject to GATE-03)
-
GATE-09
Enforced
A Release Record PR (
release-record-*branch) is not a release, and the gates skip it.Held by gate triggers
-
GATE-10
Enforced
/retestre-runs a gate. It never skips one.Held by Pipelines as Code
-
GATE-11
Convention
A new or removed gate goes in the
releaseGuardrailsregistry, and the ruleset is regenerated from it in the same change.Held by
hack/apply-guardrail-ruleset.sh -
GATE-12
Enforced
qapasses only when the promoted image's own test provenance (TEST-06) carries a check ride that is verified,passed, withtests-failed: 0, from the shared check-rides Application (workflow-source: argocd:check-rides), and younger thanqa.maxAge(72h). It names what was missing otherwise. An eligible rollback is advisory.Held by
qa-gate; Glidepath testing.mdStrength: Enforced as a check; Soft today (advisory, GATE-13)
-
GATE-13
Enforced
Every registry entry has
required(defaulttrue). A gate withrequired: falseis 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: nevercicd.yaml, never Tower, never per release. On hangar-devqais advisory until every app has a check ride.Held by
glidepath-catalog.guardrails; release-guardrails.md: Required and advisory gates -
GATE-14
Enforced
sastandprovenancecannot be made advisory: a catalog that sets either torequired: falsefails to render.Held by
glidepath-catalogchart render -
GATE-15
Fails closed
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_shaof 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_byis inallowedMergers(an empty list fails closed).mode: strictalso requires every PR commit gitsign-signed by an allowed signer.requiredApprovals: Nrequires N approvals of the PR's final head by other allowed mergers. Defaults when on:mergermode, 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 -
GATE-16
Enforced
The release-signing policy is admin-owned: signers, whether the GitHub-merge path is on, its mode,
allowedMergersand the minimum approvals are set only in the catalog'sreleaseSigningvalues (a glidepath PR, or a cluster's50-glidepath/glidepath-catalogvalues), never incicd.yamlor Tower. A product owner asks for a change; a platform admin merges it. An app'scicd.yamlcan only tighten it (governance.githubMerge.mode: strict, a higherrequiredApprovals);governance.allowedCommitSignersthere is ignored.Held by
release-signing-policyConfigMap; commit-signing.md: who sets the policyStrength: Enforced (the gate reads only the catalog's ConfigMap; the schema refuses
enabled/allowedMergersincicd.yaml) -
GATE-17
Fails closed
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-checkworkflow -
GATE-18
Enforced
policy-validationrenders every environment the release PR changes and evaluates it offline (Kyverno CLI) against the target cluster's own admission policies,30-policy/onmainof itsgitops-<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)
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.
-
TEST-01
Enforced
A
teststep 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 -
TEST-02
Enforced
A
testorexecutestep whose workflow does not exist fails, naming the file. Onlytest.enabled: falseskips, and it saysskipped.Held by
run-testworkflow -
TEST-03
Enforced
A
check-ridestep runs<app>-check-rideexactly 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'shangar.io/applabel. A flow has at most one, it takes notestName, and it is always required.Held by
run-testworkflow,validateFlows -
TEST-04
Convention
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 fromtemplates/, the declared config and only the app's own Secret.Held by check-rides
CODEOWNERS,tools/check.pyStrength: Convention (code owners on a private repo on GitHub Free); Enforced with the ruleset
-
TEST-05
Enforced
In the shared
testkubenamespace 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 andcontent.git.tokenFrom.Held by Kyverno
testkube-secret-usage(ADR-0008) -
TEST-06
Enforced
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.yamltype hints -
TEST-07
Convention
The environment's NetworkPolicy admits same-namespace pods only; an app lets the
testkubenamespace in through its ownglidepath/base.yaml(networkPolicy.allowIngressFrom).Held by airframe-application
-
TEST-08
Display
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 -
TEST-09
Enforced
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.0findReleaseTestRunStrength: Enforced (record) / Display (live)
-
TEST-10
Display
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
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.
-
CHG-01
Enforced
Every release PR creates a change request, or links one the caller or the PR body already names: a
normalchange for a promote, anemergencychange for a rollback whoseparentis the change it rolls back. The PR body and the release record carry it.Held by
open-release-pr -
CHG-02
Enforced
The
itsmgate 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 -
CHG-03
Enforced
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 -
CHG-04
Enforced
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: continueon the hooks -
CHG-05
Enforced
Change-tool credentials come from a platform Secret in each
-cicdnamespace, fanned out from the platform's Infisical project; never a value.Held by
itsm.credentialsClusterExternalSecret -
CHG-06
Enforced
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 -
CHG-07
Display
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.0ReleaseRecordEvidence.tsx
7 Release records, modes and sign-off
-
REL-01
Enforced
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) -
REL-02
Enforced
The sign-off environment is the one
cicd.yamlmarksproduction: true, else the last Flight environment.Held by
releaseFlow.ts -
REL-03
Soft
For a Hand-flown app, a release PR to the sign-off environment needs a sign-off.
Held by the
hangar/release-signoffcommit status; GitHub Free can't make it required -
REL-04
Fails closed
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 -
REL-05
Soft
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 -
REL-06
Enforced
Assisted apps record who merged and who reviewed. They get no status and no alert.
Held by
release-signoff-poll -
REL-07
Enforced
The next release's draft (notes, summary, owner, risk, target date, milestone, GitHub Release opt-in) is
glidepath/releases/next.yamlin 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 -
REL-08
Enforced
When a release is first healthy in a Flight environment, the backend opens the record PR (
releases/<app>@<tag>.yamlingitops-<app>) with the draft sealed into it, and clearsnext.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) -
REL-09
Enforced
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) -
REL-10
Enforced
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
-
REL-11
Display
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. -
REL-12
Display
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
-
REL-13
Enforced
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) -
REL-14
Enforced
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 -
REL-15
Enforced
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 -
REL-16
Enforced
The backend writes only PRs, commit statuses, GitHub Releases and the draft commit. It never merges.
-
REL-17
Display
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; backendparseGateComments; Glidepath commit-signing.md: Evidence -
REL-18
Display
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
Full detail: Tower release records.
8 Rollback
-
RB-01
Enforced
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 -
RB-02
Enforced
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-imagerollback-eligibility -
RB-03
Enforced
On an eligible rollback,
sast,image-scanandsbomare advisory, because that image already ran there. Provenance, values, commit signing and approvals still apply.
9 Supply chain
-
SC-01
Enforced
Every pod in an app's
-cicdnamespace, image builds included, meets Pod Securitybaseline: not privileged, no added capabilities, no host namespaces or mounts, noUnconfinedseccomp/AppArmor. Builds run rootless BuildKit as UID 1000; they can't meetrestrictedbecause the builder needs a user namespace. Each-cicdnamespace carriespod-security.kubernetes.io/enforce: baseline, so a pod that breaks it is refused at admission (since Oct 17, 2026). Until then this rule saidrestricted(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
-
SC-02
Enforced
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
-
SC-03
Display
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
-
SC-04
Display
Readiness's "Signed and attested" passes only on a verified attestation, never on one that is merely present.
Held by Tower
isSignedAndAttested -
SC-05
Enforced
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
10 Live operations from Tower
-
OPS-01
Enforced
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
-
OPS-02
Enforced
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) -
OPS-03
Enforced
Sync and force sync: owners on Ground, admins only on Flight.
Held by ACC-04
-
OPS-04
Enforced
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 -
OPS-05
Soft
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 -
OPS-06
Enforced
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-cicdnamespace, and on an existing run only a cancel. Changed Oct 30, 2026:tower-actorwrote the runs itself, held to the shape bytower-pipelinerun-shape.Held by
glidepath-api-pipelinerun-shapepolicy (POL-01) -
OPS-07
Enforced
Configuration changes (values, env XR, ConfigMap files,
cicd.yaml) are PRs, never direct edits.Held by
tower.config.edit
11 Break-glass debug sessions
-
BG-01
Fails closed
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
-
BG-02
Soft
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.selfStrength: Soft (the review)
-
BG-03
Enforced
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
-
BG-04
Fails closed
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 execinto app namespaces for everyone but operators.Held by
tower-breakglass-*admission policies -
BG-05
Fails closed
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 -
BG-06
Fails closed
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
-
BG-07
Enforced
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
12 Images and retention
-
IMG-01
Enforced
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.tsPLATFORM_PACKAGE -
IMG-02
Enforced
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 -
IMG-03
Enforced
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 -
RET-01
Enforced
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 -
RET-02
Enforced
A release that is merged or progressing with no new fact for 45 minutes raises
ReleaseStalled, once per state.Held by
release-record-sweeper -
RET-03
Enforced
A pipeline stage that hasn't started its next stage 10 minutes after finishing is reported as stalled.
Held by 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:
-
AF-01
Enforced
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
-
AF-02
Enforced
Composed resources get least-privilege roles, the baseline NetworkPolicy, dedicated data stores per environment, and
hangar.io/*tags and labels.Held by compositions
-
AF-03
Enforced
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
-
AF-04
Enforced
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
-
AF-05
Enforced
Only control-plane clusters can be marked
autopilotReady.Held by cluster registry chart
-
AF-06
Convention
extraManifests,networkPolicyandhttpRoutechanges need a person.Held by
AGENTS.md -
AF-07
Convention
A security reviewer approves changes to XRDs and compositions.
Held by 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).
-
POL-01
Fails closed
Backstage (
tower-actor) may not create or patch a PipelineRun; glidepath-api's have Glidepath's shape only (OPS-06). Kyverno ValidatingPolicy. Wastower-pipelinerun-shapeuntil Oct 30, 2026, which held Backstage's own PipelineRuns to that shape.Policy
glidepath-api-pipelinerun-shapeon dev -
POL-02
Fails closed
Backstage may delete only
tower-breakglass-*RoleBindings.Policy
tower-rolebinding-deleteon dev -
POL-03
Fails closed
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-rolebindingon both -
POL-04
Fails closed
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-imageon both -
POL-05
Fails closed
Backstage deletes only debug or copy pods, and patches only the process-access annotation.
Policy
tower-breakglass-pod-delete,-pod-annotateon both -
POL-06
Fails closed
No exec into app pods for anyone but operators. Attach only to a debug container.
Policy
tower-breakglass-no-execon prod -
POL-07
Fails closed
No long-lived ServiceAccount token Secrets in
backstage-system.Policy
no-long-lived-sa-tokenson dev -
POL-08
Fails closed
A tenant's TestWorkflow may reference only that tenant's own Secrets in the shared
testkubenamespace.Policy
testkube-secret-usageon dev -
POL-10
Fails closed
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 catalogdeployPipeline'sdeploy-aws-lambdaanddeploy-aws-ecs, not run-wide, with no pod-templateenvor volumes.Policy
glidepath-cloud-deployer-pods,-taskruns,-pipelineruns,-jobson dev -
POL-11
Fails closed
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-imageson hangar-prod -
POL-09
Fails closed
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
Full detail: Tower break-glass admission policies.
15 Credentials and secrets
-
CRED-01
Enforced
Secrets are references to Infisical, never values in git or an XR.
Held by
AF-SECRET-001Strength: Enforced on gitops repos (GATE-08), convention elsewhere
-
CRED-02
Convention
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
-
CRED-03
Enforced
Tower holds no long-lived cluster credential. Reads use the pod's projected token (600 s). Writes on dev use a
tower-actortoken minted for each action.Held by Tower credentials; POL-07
-
CRED-04
Enforced
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 -
CRED-05
Enforced
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
-
CRED-07
Enforced
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 asglidepath-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 forglidepath-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; anAwsLambdaTargethas no access-key mode, and on a cluster without web identity it stops atAwsLambdaTargetNeedsWebIdentity(AwsEcsTargetNeedsWebIdentityfor 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.shin catalog-ci; Airframeno-web-identityrender cases) -
CRED-08
Enforced
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
HangarCrossplaneCloudTargetsv3; Airframe cloud-credentials.mdStrength: Enforced (IAM)
-
CRED-09
Enforced
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)
-
CRED-06
Enforced
The image-prune token can only read and delete packages. The backend holds it, never the browser.
Held by IMG-01
16 Notifications
-
NOT-01
Enforced
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 -
NOT-02
Enforced
Release announcements, Rollout actions, analysis bypasses, broken glass, Flight pod restarts and image prunes go to everyone.
Held by Backstage notifications
-
NOT-03
Enforced
A bypass merge (GATE-04) is alerted however notifications are set.
Held by
detect-bypass-merge
17 AI agents
-
AI-01
Enforced
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) -
AI-02
Enforced
Agent runs go only to clusters marked
autopilotReady(AF-05).Held by AgentRun composition
-
AI-03
Fails closed
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 registeredautopilot-gatewayprovider and thehangarpolicy bundle)Strength: Fails closed (the decision); the Autopilot gateway, which asks first, enforces it
-
AI-04
Enforced
Every agent is denied, whatever its definition says, these paths:
.tekton/**,CODEOWNERS,.github/**, AppProject files andrelease.yamlfiles; and these fields:/extraManifests,/networkPolicy,/httpRoute,/release,/releaseTrackingand/rollout/image. For an agent this makes AF-06 a check, not a convention.Held by gitops-hangar-dev
55-clearance/clearance/application.yamlvocabulary.baselineDenyPaths,baselineDenyFields -
AI-05
Enforced
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
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.
-
API-01
Fails closed
Every
/v1call 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 ofglidepath-api(a token minted for Clearance is refused). A token that fails isGP-AUTH-001/002.Held by api.md: Who may read what
-
API-02
Enforced
What a caller may do is a SubjectAccessReview in the app's
-cicdnamespace onglidepath.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 aretower-actorand Clearance.Held by glidepath-control-plane
api.readers,api.operators,api.releasers -
API-03
Enforced
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
-
API-04
Enforced
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.delegatesStrength: Enforced (the header); the person check is the delegate's
-
API-05
Enforced
Writes are tiered. T1: re-run and cancel a PipelineRun, only in the app's own
-cicdnamespace, 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 isGP-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.yamlx-glidepath-tier -
API-06
Enforced
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)
-
API-07
Enforced
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 isGP-API-409. Every write has a dry run.Held by glidepath-api
-
API-08
Enforced
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_requestsandmetadataread 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) -
API-09
Enforced
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
-
API-10
Enforced
People call the API over the Gateway route (
/v1only) with a people-dex ID token, aspeople:<GitHub login>, and only if that login is inapi.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 -
API-11
Enforced
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
-cicdnamespace (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 -
API-12
Enforced
Every PR to an onboarded app repo runs
cicd-validate: whencicd.yamlchanges, the schema,GP-ENVandGP-PIPErules, and for an agent's PR theGP-SCOPErules too; a PR that removescicd.yamlfails. The same validator answersPOST /v1/apps/{app}/cicd/validationsbefore 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
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.
-
CLR-01
Enforced
Protected environments are gated:
hangar-prod/*,*/prodand*/lab(<cluster>/<env>globs; a Terraform plan has no cluster, so*/labmatches its workspace). Any other environment clears at tier 0 asunprotectedand is recorded like any other change. An empty list protects every environment. A caller cannot opt a change out (protected: falseisDG-PROTECTED). hangar-prod's own agent and plugin listhangar-prod/prodand cover one app today (CLR-20).Held by hangar-dev
cluster.yamlclearance.protectedEnvironments; gate.md: protected environmentsStrength: Enforced (fails closed when the list is empty)
-
CLR-02
Enforced
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'sclearance.yamlcan only tighten it, and a file that loosens it denies the change (DG-POLICY).Held by gate.md: tiers
-
CLR-03
Enforced
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.trustedRegistriesin gitops-hangar-dev55-clearance/clearance/application.yaml -
CLR-04
Enforced
Tier 2 needs one person from the app's owner group, never the proposer.
Held by
DG-PROPOSER,DG-FILLED; gate.md -
CLR-05
Enforced
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.yamlclearance.gateOutsideGroups;DG-TWICE,DG-FILLED -
CLR-06
Enforced
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. -
CLR-07
Enforced
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
-
CLR-08
Enforced
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, sorepo:jfillman@7268337/hangar-infra@1433775017:...matchesrepo:jfillman/hangar-infra:*. The Hangar verifiers areapp-*-cicd/pipeline-runner, hangar-prod's repo-server plugin,clearance-deploy-scratchandhangar-infra.Held by hangar-dev
55-clearance/clearance/application.yamlverifiers,githubActions.allowed; provenance-app.md -
CLR-09
Display
A change's policy is recorded on every
deploy.*record: the baseline version, the repo'sclearance.yamlcommit and the offline admission verdict. The verdict is recorded, not enforced by Clearance; Glidepath'spolicy-validationgate (GATE-18) is the check that blocks, subject to GATE-03.
-
CLR-10
Off here
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 onjfillman/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: enforcementStrength: 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)
-
CLR-11
Enforced
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/clearanceswithpr: 0; gate.md: the lookup by treeStrength: Enforced (the hold)
-
CLR-12
Enforced
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.goclearedByTree; Glidepath's task renders as<app>-<env>like Argo CD (G-ARGO-8) -
CLR-13
Enforced
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
-
CLR-14
Building
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)
-
CLR-15
Enforced
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
clearedrevokes it in the same transaction.Held by
CLEARANCE_TOKEN_TTL; gate.md: the clearance token, core.md -
CLR-16
Enforced
A change holding for people expires after 7 days (a repo's
ttlDayscan only shorten it); a saved Terraform plan expires after 24 hours.denied,expired,superseded,degradedandexceptionare final: a new head registers a new change. -
CLR-17
Fails closed
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)
-
CLR-18
Fails closed
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: truein hangar-dev's Application; core.md: the outage knob -
CLR-19
Enforced
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 thebreakglass.openrecord 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
-
CLR-20
Enforced
On hangar-prod the gate covers one app, gate-api (
clearance.gate.apps, protected namespaceapp-gate-api-prod). Other hangar-prod apps are not gated.Held by gitops-hangar-prod
cluster.yamlclearance.gateStrength: Enforced for gate-api; Off here for the rest
-
CLR-21
Enforced
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 aComparisonErrorand nothing syncs.Held by gate.md: the plugin and the PreSync wait
Strength: Enforced (for Applications that use the plugin)
-
CLR-22
Display
The PreSync wait Job is not a control.
argocd app sync --strategy applyskips hooks and so skips the wait. Admission is what refuses (CLR-24).Held by gate.md: the PreSync wait
-
CLR-23
Fails closed
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
feedReadersin hangar-devcluster.yaml; gate.md: the signed grant feed, the agent -
CLR-24
Soft
The admission policy
clearance-protected-writesadmits 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
-
CLR-25
Convention
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.enforcein gitops-hangar-prodcluster.yamlStrength: Convention (owner decision, Nov 9, 2026 and Nov 10, 2026)
-
CLR-26
Soft
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.exceptionper finding, naming the writer. A change it names moves toexceptionand its token is revoked.Held by gate.md: exceptions
Strength: Soft (detected and recorded, not stopped)
-
CLR-27
Enforced
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 attestationsStrength: Enforced (keyless is on in hangar-dev's config; no live attestation was read for this page)
-
CLR-28
Fails closed
A saved Terraform plan is a change.
clearance holdsends the plan's digests, resource addresses, actions and changed attribute names, never a value; sensitive variables contribute their name only.clearance releaselets 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 adigest-mismatchexception that revokes the token.Held by terraform-gate.md: guardrails
-
CLR-29
Enforced
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
-targetor-replaceplan is one tier higher. A destroy in a protected workspace never clears by policy. hangar-infra'sclearance.yamlprotectsnetwork/*andobserve/*.Held by gate.md: the terraform-plan subject; hangar-infra
clearance.yaml -
CLR-30
Enforced
The observed account's collector infrastructure (
observe/labin 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 thelabenvironment 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
-
CLR-31
Convention
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
-
CLR-32
Fails closed
In the private hangar-infra repo the
labenvironment 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
-
CLR-33
Enforced
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 onrepo: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/subStrength: Enforced (by AWS and GitHub; checked against GitHub's subject setting, not the role)
-
CLR-34
Enforced
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 privatelab(CLR-32)
-
CLR-35
Convention
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
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.
-
OBS-01
Soft
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)
-
OBS-02
Enforced
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
-
OBS-03
Fails closed
A change to a Secret is recorded without its value. The shipped audit policy records Secrets at
Metadataat 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. -
OBS-04
Fails closed
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.
-
OBS-05
Enforced
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. -
OBS-06
Enforced
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 -
OBS-07
Enforced
A refused attempt (a 403, an
errorCode) is recorded asattempted-refusedand is never noise. Nothing landed, so a change it names is not moved to exception.Held by observe-pipeline.md
-
OBS-08
Fails closed
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) -
OBS-09
Enforced
Routine controller webhook writes are named exceptions (
monitoring.kubernetes_exempt), each with a name, awhy, 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 tokube-system/audit-policy, stays monitoring protection (OBS-06). A compromised named controller can change its own webhooks without an alert. -
OBS-10
Building
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
mainat 4ccf3dd; no open PR found) -
OBS-11
Enforced
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)
-
OBS-12
Enforced
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)
-
OBS-13
Enforced
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
provideritself; an event cannot. observe-k8s is registered forobservation,assessment,resolutionandobservation.health; the AWS collector also reads clearance-api's outcomes as an auditor.Held by
providersin hangar-dev55-clearance/clearance/application.yaml; provider-protocol.md -
OBS-14
Enforced
A resolution is how an observed change ended. The collector posts the first one (
still-live, orrevertedwhen it saw the revert). A person sets or changes it, toreverted(with one of the fixed seven causes:agent-caused,policy-triggered,human-initiated,automated-remediation,requirement-changed,external-dependency,unattributed),ratified,declared-break-glassorstill-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
-
OBS-15
Building
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)
-
OBS-16
Enforced
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 -
OBS-17
Enforced
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;
ledgerin hangar-dev's Application -
OBS-18
Enforced
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 verifyrebuilds 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.Strength: Enforced (the build and the verify); Convention (the re-run: no scheduled job ships)
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.
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.
Decided Oct 20, 2026 (ADR-0027): an allowed merger's GitHub session is enough to release a PR's code, and in
mergermode nobody gitsign-signed that code. On hangar-dev the path is on for every onboarded app (backstage, boarding-api, flight-api, gate-api), inmergermode with 0 approvals (one person merges). RaiserequiredApprovalsonce a second real person merges; usestrictwhere every author can run gitsign. -
The ruleset assumes every PR is a release PR.
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.
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). -
itsmis real only with a provider.This lab points it at a mock change tool (CHG-01 to CHG-06 hold against the mock).
-
policy-validationchecks only validating policies.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/. -
qais advisory on hangar-dev.required: falseuntil 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.
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.
A platform default for the
testkubenamespace is on the test framework roadmap. -
Production SLO burn isn't read during the bake.
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.
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.
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.
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.
Flight keeps every promoted image until
promotion.retention.enabledis 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.
Refused rather than promoted. No app has a cloud Flight environment today; cloud promotion is a recorded Glidepath follow-up.
-
airframe-validatedoesn't run on app repos or the tenants repo.Airframe roadmap AF-4b.
-
Security review and human
extraManifests/networkPolicy/httpRoutechanges are convention.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.
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.
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 onjfillman/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.
clearance-protected-writeswarns 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.
hangar-prod, Nov 10, 2026: change
ch-1dda08661ae5b7b1was 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 falsedeploy.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.
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.
clearance.gate.apps: [gate-api], protected namespaceapp-gate-api-prod. Other hangar-prod apps deploy ungated, and hangar-prod's own protected list ishangar-prod/prod, narrower than the core's. -
Clearance traffic between clusters is plain HTTP.
hangar-prod's plugin, wait Job and agent reach the core and clearance-api at
http://clearance.hangar-dev.localandhttp://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.
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.degradedrecord. -
The Terraform lab apply is held by a failing job, not a protection rule.
labin 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.
observe-awsis not registered in hangar-dev's providers or auditors (gitops-hangar-dev446ea51), 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.
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'sobserve-k8s.mdstill says "not yet rolled out". A collector with no audit feed reads blind (OBS-04), never clean. -
Approver aliases are only known if listed.
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.
No scheduled job ships for
clearance packet(due Dec 9, 2026). -
Only admins can run software templates.
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.
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.
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.
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.