Glidepath · Tower · Autopilot · Spec · The Glidepath API

A Glidepath API for people, Tower and agents

Airframe has a front end an agent can use cold. It has a schema, a contract that says what every kind returns and how it fails, a validator, a verifier, and a gateway that decides what an agent may touch. Glidepath has none of that. What it has is about sixty routes inside Tower's backend that answer every CI/CD question and take every CI/CD action, for Tower alone. This spec moves that surface into Glidepath, builds it the way Airframe is built, and lets Tower and agents share it.

Spec October 25, 2026Status proposed Oct 25, 2026; P6, the last phase, done and live-verified Oct 30, 2026Basis airframe, glidepath, autopilot, tower and backstage, read from the code on Oct 25, 2026; the P6 notes checked live on Oct 30, 2026Related Airframe as an agent API · Autopilot architecture map
Proposed · Oct 25, 2026

Glidepath gets a contract, a validator and an API, and Tower becomes one of its clients.

Why: Glidepath answers to Tower, and only to Tower

Airframe's API is its schema. Twenty kinds in catalog.hangar.io/v1alpha1, a versioning policy, and a sidecar per kind that lists what the kind returns, the conditions it reports with a fixed set of reasons, how to verify it and how it is known to fail. All of that is generated into one contract file. An agent asks airframe-capabilities what it can do, runs airframe-validate before it opens a pull request, and runs airframe-verify afterwards. Clearance decides which of those writes the agent may make at all.

Glidepath has the schema half: cicd.yaml and cicd.schema.json. Everything an agent needs at run time is spread over six stores: PipelineRuns, Tekton Results, the release records, Testkube, Chains and Rekor, and GitHub. The only thing that joins them is Tower's backend: glidepathProvenance.ts alone is 2,994 lines with 60 routes, and four browser hooks read Tekton directly through the Kubernetes proxy. Some of that is CI/CD logic, such as working out a flow's chain slug or which releases a rollback may target, and it lives in a Backstage plugin. Clearance offers agents exactly one CI/CD tool, pipeline.rerun.

That has three costs. An agent can't ask Glidepath why a flow failed. Glidepath, which has to stay installable on its own, can't be operated without Tower. And every rule about who may promote or roll back is written in Tower's code, not in Glidepath's.

The Airframe pattern, applied to GlidepathAirframe has four pieces: a generated contract, a validator with rule ids, a write path through git, and a verifier with closed reason sets. Glidepath gets the same four: a generated contract, glidepath validate, glidepath-api, and explain.AIRFRAME · TODAYGLIDEPATH · THIS SPECContractairframe-contract.jsonairframe-validateAF-* rule idsXR in gitArgo CD applies itairframe-verifyclosed reason setsContractglidepath-contract.jsonglidepath validateGP-* rule idsglidepath-apigit or a short-lived callexplainknownFailures ids
Figure 1Blue pieces are new. The API is the one piece Airframe doesn't need. A run is short-lived and has no desired state for Crossplane to reconcile.

Layer 1: the contract

A new sidecar, schemas/cicd.meta.yaml, does for cicd.yaml what xrds/<kind>.meta.yaml does for an XRD. A generator folds it, the schema, the gate registry and Glidepath's state vocabularies into contract/glidepath-contract.json. CI runs the generator with --check, the same way Airframe's does.

SectionWhat it saysWhere it comes from
fieldsEvery cicd.yaml field with its type, description, owner (team or admin), risk and agent scope: open, tighten or humancicd.schema.json + cicd.meta.yaml
gatesEach governance gate: what it verifies, whether it is required, which status check it poststhe gate registry
statesFixed sets of stage, gate and release states with their meanings. The release set already exists: proposed, merged, progressing, healthy, superseded, rolled-backADR-0021, ADR-0022
knownFailuresStable ids with a match and a hint, e.g. SourceVolumeFull (raise build.sourceVolume.size), ProvenanceSignerNotAllowed, ItsmChangeNotApproved, BuildKitOOMknown-gaps.md and incidents, curated
actionsEvery API action with its tier, arguments, effects and dry-run outputthe OpenAPI spec

glidepath validate checks a cicd.yaml in one pass: the schema, then GP-* rules for what a schema can't say (an environment named after a stage, a Flight environment with no release stage). With --base <ref> it also checks the GP-SCOPE rules on what changed, so the field scope below holds in CI even for a pull request that never went through Clearance. Glidepath gets an AGENTS.md and an llms.txt, as Airframe has.

Layer 2: the API

Where glidepath-api sitsFour clients, Tower, Clearance, the glidepath CLI and pipelines, call glidepath-api on the dev cluster over https. It checks who the caller is with TokenReview or an OIDC token, checks what they may do with a SubjectAccessReview, and reads and drives Tekton and Tekton Results, the release records, Testkube, Chains and Rekor, GitHub, and the CDEvents broker.CLIENTSDEV CLUSTER · CONTROL PLANEWHAT IT READS AND DRIVESToweras tower-actor + userClearanceagents, over MCPglidepath CLIpeople, OIDC tokenPipelinestheir own SA tokenHTTPSAuthnTokenReview · OIDCglidepath-apiv1 · OpenAPI · auditedAuthzSubjectAccessReviewWHOMAY THEYTekton + Resultsruns, logs, 30-day archiveRelease recordsstate per release-idTestkubetest runs and reportsChains + Rekorprovenance, SBOMGitHubPRs, statuses, mergesCDEvents brokerthe event stream
Figure 2Nothing here reaches a Flight cluster. A release is still a pull request on gitops-<app> that Argo CD syncs (ADR-0004).

One service, glidepath-api, deployed by glidepath-control-plane on the dev cluster where the pipelines already run. Its nouns use identities Glidepath already has: a flow is one chain-id (and one Tempo trace), a release is one releaseId (<chain-id>:<cluster>/<env>), and an image is a digest. Custom verbs follow the resource:verb style.

CallTierWhat it does
GET /v1/contractT0The contract, so a client never hard-codes a field or a state
GET /v1/apps/{app}/flowsT0Flows by state and time, live and archived (Results)
GET /v1/flows/{chainId}T0Stages, gates, runs, the trace link
GET /v1/runs/{run}/logsT0Step logs, live or archived
GET /v1/flows/{chainId}:explainT0The failing step classified against knownFailures, with the hint and the field to change
GET /v1/apps/{app}/releases, /v1/releases/{releaseId}T0Release state, PR, change request, rollback eligibility
GET /v1/images/{digest}/evidenceT0Provenance, SBOM, test evidence, signer
POST /v1/apps/{app}/cicd:validateT0glidepath validate as a call
POST /v1/apps/{app}/cicd:proposeT1Opens a pull request to cicd.yaml, after the scope check
POST /v1/runs/{run}:rerun, :cancelT1What Tower's Re-run and Cancel do today
POST /v1/apps/{app}/buildsT1Builds a branch
POST|DELETE /v1/apps/{app}/previewsT1Creates, extends or deletes an ephemeral environment
POST /v1/apps/{app}/environments/{env}:promoteT1 Ground · T2 FlightGround: the deploy commit. Flight: runs the app's release pipeline, which opens the gitops PR
POST …/environments/{env}:rollback, :pinT2A release of an eligible earlier image, or a cloud pin PR
GET /v1/eventsT0The CDEvents stream, filtered by app (server-sent events; AMQP topics for Autopilot)
Identity

Reuse what verifies

Pipelines and agents present a ServiceAccount token, checked with TokenReview the way the broker checks them (ADR-0002). People present an OIDC token. Tower presents tower-actor's minted token and names the user it acts for; only that subject may name someone else.

Authorization

The app's namespace is the boundary

Each call is a SubjectAccessReview against app-<name>-cicd on a virtual resource (glidepath.hangar.io flows, releases, environments) with verbs such as rerun or promote. glidepath-app renders the RoleBindings from the app's owners. No second permission model.

Every write

Plan first, once, on the record

Every write takes dryRun and an idempotency key, and records an audit event with the caller, who it acts for, the tier and the decision. A denial returns a rule id and a hint, in the shape Clearance's decisions already use.

Layer 3: tiers, and where agents come in

The four tiers of a Glidepath actionT0 reads. T1 makes reversible changes in Ground only, such as a re-run or a preview environment. T2 proposes a Flight change as a pull request that a human merges. T3 is never offered to any identity.T0Readflows · logs · gates · releases · evidence · explainT1Reversible, Ground onlyre-run · cancel · build a branch · preview env · cicd.yaml PRT2Propose for Flightpromote · roll back · pin: a PR a human mergesT3Never offeredbypass a gate · edit signing policy · merge · approveAPI CALLGIT PRNONET1 and T2 accept dryRun: true and return the plan without acting.
Figure 3The same tiers Clearance already uses. T2 is the line that matters: past it, nothing applies until a human merges.

Agents don't get a second MCP server. Clearance registers glidepath.* tools that call the API and keeps doing what it does for every tool: profile limits, CEL rules and its hash-chained audit. The API checks permissions again itself, so a tool that bypasses Clearance is refused there too.

One rule is specific to CI/CD: an agent never satisfies a gate on its own change. A Flight change is always a pull request; the human who merges it is also who satisfies the GitHub-merge provenance check (ADR-0027). The signing policy is admin-owned and never reachable from an agent.

New Clearance toolTierCalls
glidepath.flows.list, .flow.get, .logs.get, .explainT0flows, runs, logs, explain
glidepath.releases.get, .evidence.get, .contractT0releases, evidence, contract
glidepath.run.rerun, .run.cancel, .build, .previewT1replaces today's pipeline.rerun
glidepath.cicd.proposeT1cicd:propose, after the field-scope check
glidepath.promote, .rollback, .pinT2propose only; T2 tools aren't capped by the tier ceiling

Agents and cicd.yaml: pull requests, limited by field

Today Clearance's BASELINE_DENY_PATHS denies cicd.yaml for every agent. That keeps agents out of their own controls, but it also stops an agent from turning on a build cache, adding a Ground environment or fixing the source volume size it was just told is too small. Even Autopilot's own planner patches cicd.yaml. The file-level deny goes. In its place is the field-level scope Clearance already uses for values files (AF-9a), with each field's class read from the contract.

Which cicd.yaml fields an agent may changeOpen fields an agent may change in a pull request: build, test, ephemeral environments, notifications, deploy strategy, Ground environment entries and pipelines. Governance gates may only be tightened. Secrets, signers, the deploy target and its cloud blocks, the release file and the chart are human only.OPEN · AGENT PR/build/**/test/**/ephemeralEnvironments/**/notifications/**/deploy/strategy/deploy/environments(Ground entries only)/pipelines/**TIGHTEN ONLY/governance/sast/governance/imageScan/governance/policyCheck/governance/sbom/governance/githubMergemay turn on or narrow,never turn off or widenHUMAN ONLY/secrets/governance/allowedCommitSigners/deploy/target/deploy/lambda/deploy/ecs/deploy/azureContainerApps/deploy/releaseFile/deploy/chartEvery agent PR still needs a human merge. .tekton/** stays file-denied: Glidepath regenerates it.
Figure 4Replaces the whole-file deny on cicd.yaml. The rule that stays: an agent never edits a control that constrains it.
ChangeWhere
Drop cicd.yaml and **/cicd.yaml from BASELINE_DENY_PATHS; keep .tekton/**autopilot profile.py
Make the baseline field denies file-qualified (cicd.yaml#/secrets), since /release means something else in a values fileautopilot profile.py, scope.py
Generate the human-only list from the contract instead of hard-coding it; tighten-only fields need a before and after comparison, not a globautopilot, from glidepath-contract.json
GP-SCOPE-001 human-only field changed, GP-SCOPE-002 a gate loosened, GP-SCOPE-003 a Flight environment added or retieredglidepath glidepath validate --base, in each app's PR checks
Agent PRs need a human approval and a human mergethe app repo's ruleset, unchanged

The rule the old deny protected still holds: an agent never edits a control that constrains it. Secrets, signers, cloud deploy identity and the gates are those controls. A build flag isn't.

What changes in Tower

Tower keeps every route, every screen and every URL. What changes is where its backend gets each answer. Tower's backend plugin glidepath (/api/glidepath/*) gains one client for glidepath-api, and each group of routes gets a switch in config:

1

direct: today's code path, unchanged. The default for every group.

2

shadow: Tower serves the direct answer, calls the API in the background and records any difference as a metric (tower_glidepath_api_shadow_mismatch_total{group}) and a log line. Writes are never run twice. A shadowed write calls the API with dryRun and compares the plan with what Tower is about to do. This is the same live and shadow approach glidepath-relay used for ADR-0021.

3

api: the route calls the API and the direct code becomes the fallback. Going back is a config change.

Until P6 (Oct 30, 2026). P6 removed the switches, shadow mode and its mismatch metric from Tower's backend (backstage#215); only tower_glidepath_api_request_duration{group, op, outcome} remains. Every group now calls the API, and there is no direct code left to fall back to: an API error or timeout goes back to the person, a 4xx with its own status and anything else as a 502. InfraServices (infra-<name>-cicd) re-run, cancel, promote and roll back through the API too.

Tower before and during the moveToday the browser calls Tower's backend, which reads and drives Tekton, Results, Testkube, the release records, GitHub and Rekor itself, and the browser also reads PipelineRuns through the read-only Kubernetes proxy. Side by side, the browser is unchanged and Tower's backend keeps its routes; each group of routes is switched to glidepath-api or kept direct, and in shadow mode it calls both and records any difference.TODAY · TOWER HOLDS THE CI/CD LOGICBrowserTower UITower backend/api/glidepath · 60Kubernetes proxyread-only guardTekton · Results · Testkuberecords · GitHub · RekorPipelineRuns directruns, TaskRun logs, fleetTOWER-ACTORINGESTORSIDE BY SIDE · A SWITCH PER GROUP OF ROUTESBrowserunchangedTower backendsame routes, a clientglidepath-apigroups switched to apiDirect, as todaygroups still directAPIDIRECTshadow: both, then diffTower-only routes (Argo CD, Config, break-glass, Release Record) stay direct for good.
Figure 5Nothing changes for someone using Tower. What changes is where each answer comes from. Until P6 (Oct 30, 2026) the move was undone by changing config; since then every group is on the API and the switches are gone.
GroupTower todayAfterPhase
Runs and logs/pipeline-history, /runs, /run, /log; browser hooks useTektonPipelineRuns, useTaskRunLogs, useFleetPipelineRuns, useFleetPipelineHistory through the proxy; chain slugs worked out in chainSlug.tsflows, runs and logs from the API. Hooks call Tower's backend, not the proxy. The chain slug comes with the flow. Since P6 the hooks read live runs and step logs through new API reads (/v1/apps/{app}/live, /v1/live, /v1/runs); Tower's own Results cache is goneP1 shadow · P2 api · P6 hooks
Supply chain and tests/provenance, /head, /images, /tests, /tests/report (Rekor, GHCR, Testkube through the service proxy)/v1/images/{digest}/evidence, tests and test reports from the API. Since P6 Tower verifies no provenance itself. /head and /images stay Tower's own (GitHub)P1 shadow · P2 api · P6
Release state/release-records and the record reads behind /release-run and /deploy-history (release-tracking ConfigMaps, read as the ingestor)/v1/apps/{app}/releasesP1 shadow · P2 api
Run control/tekton/rerun, /tekton/cancel as tower-actor, held to shape by the tower-pipelinerun-shape policyruns:rerun, :cancel. Since P6 tower-actor can't write a PipelineRun at allP3 · P6
cicd.yaml editor/config/cicd, /config/cicd/schema: Tower checks the patch and opens the PRcicd:validate, cicd:propose; the editor shows each field's owner and scope from the contractP3
Promote, roll back, pin/promote, /release/rollback-plan, /release/rollback, /pin; eligibility worked out in Towerenvironments:promote, :rollback, :pin; eligibility from the API. A Ground promotion stays Tower's own, a direct commit to the gitops repo, because the API refuses Ground targets (GP-API-422)P4 · P6
Stays in Tower

Not Glidepath's

Argo CD actions (/argo/*, a delegated system). Config and values (/config, flight-base, env-xr, components, chart, platform-env: Airframe's contract). Break-glass, the cluster registry, pull requests, agent definitions.

Stays in Tower

Tower's own products

The Release Record and the release flow (/release-record*, /release-draft, /release-mode, /release-planned, /release-calendar, /release-signoff). They read release state from the API but are written by Tower. Image prune stays and reads "in use" from the API later.

Gone in P6 · Oct 30, 2026

Tower lost write access to Tekton

tower-actor's PipelineRun create and patch binding on the dev cluster (backstage-tekton-rerun), the reader's Rekor, Testkube and tekton-results-relay service-proxy grants, Tower's Results cache, its own provenance checks and rollback plan, and the four proxy hooks. The tower-pipelinerun-shape policy became glidepath-api-pipelinerun-shape: it refuses any PipelineRun write from tower-actor and holds glidepath-api to Glidepath's run shape. Tower writes nothing on the dev cluster itself except break-glass; its CI/CD actions are API calls as tower-actor on the person's behalf.

Still not through the API after P6. The Ops Wall's release records and release Events, which Tower reads through the Kubernetes proxy (useFleetReleases.ts). Argo CD, Rollouts, pod and Crossplane reads also go through the proxy; they aren't CI/CD and were never in scope. Also still Tower's own: a Ground promotion, the Release notification for a Flight promotion, following a rollback run to its PR, /head and /images, and break-glass.

RiskAnswer
The API is downUntil P6 every group fell back to direct. Since P6 (Oct 30, 2026) there is no fallback: Tower's CI/CD views depend on the API, the way they depended on Tekton Results, and an API error reaches the person as an error. The API runs two replicas by default.
The two authorization checks disagreeTower's owner check (towerAuthz.ts) keeps running in front of the API. Shadow mode also compares the API's decision with Tower's, and a mismatch blocks the cut-over for that group.
An extra hop is slowerThe Results cache Tower built in pipelineHistory.ts moves into the API: a finished run never changes. Shadow mode records how long each side takes. Since P6 Tower's cache is gone and tower_glidepath_api_request_duration times each call.
Two copies drift during the moveEach group is shadowed and then cut over on its own. Only groups that are switched to api are frozen in Tower; the rest keep shipping as usual.

Roadmap

Roadmap: seven phasesP0 contract, validator and field scope. P1 read API with Tower in shadow. P2 read cut-over and agent read tools. P3 T1 actions. P4 T2 propose actions. P5 event subscriptions. P6 retire Tower's duplicate paths and its write access to Tekton.P0Contractvalidate · field scopeP1Read APITower in shadowP2Read cut-overagent T0 toolsP3T1 actionsre-run · cicd PRP4T2 proposepromote · rollbackP5Eventstriage on failureP6RetireTower loses writesEach phase ends with a live check. Order is fixed; dates are not.
Figure 6P0 and P4 are amber: P0 is what agents need first, and P4 is the first point an agent can start a Flight change.

P0 · Contract, validator, field scope

Glidepath + Autopilot

cicd.meta.yaml, the contract generator, glidepath validate with GP-* and GP-SCOPE rules, AGENTS.md and llms.txt. Clearance swaps the cicd.yaml file deny for field scope from the contract. No service yet. Done when: an agent PR that turns on build.cache passes, and one that turns off governance.sast or touches secrets is refused by Clearance and fails CI.

P1 · Read API, Tower in shadow

Glidepath + Tower

glidepath-api with flows, runs, logs, releases and evidence, its OpenAPI spec, authentication and SubjectAccessReview. Tower's runs, supply chain and release-state groups go to shadow. Done when: seven days with no shadow mismatches on every app on the dev cluster.

P2 · Read cut-over, agent reads

Tower + Autopilot

Those three groups switch to api. Clearance gets the T0 glidepath.* tools. explain ships with the first knownFailures. Done when: an agent starting cold names the right failure id for a seeded failed flow, using only MCP.

P3 · T1 actions

Glidepath + Tower

Re-run, cancel, build a branch, previews, cicd:propose. Tower's run-control and cicd-editor groups go through dry-run shadow, then api. pipeline.rerun is retired. Done when: a re-run from Tower and one from an agent each create exactly one PipelineRun, and each is audited with both identities.

P4 · T2 propose

Glidepath + Tower

Promote, roll back and pin, each opened by Glidepath's release pipeline as today. Tower's promote group moves. Done when: an agent proposes a staging promotion, a human merges it, the release record reaches healthy, and the agent had no way to merge it.

P5 · Events

Glidepath + Autopilot

/v1/events and AMQP topics. An Autopilot event agent triages a failed flow and opens a cicd.yaml or code PR. Done when: a seeded failure produces a correct triage PR without anyone stepping in.

P6 · Retire the duplicates

Tower + clusters Done Oct 30, 2026

Remove Tower's direct paths, the proxy hooks, tower-actor's Tekton bindings and the shape policy. Done when: kubectl auth can-i shows Tower's identities can't create or patch a PipelineRun. Met Oct 30, 2026: kubectl auth can-i create|patch pipelineruns answers no for hangar:hangar-prod:tower-actor and hangar:hangar-prod:backstage-ingestor, in a namespace and with -A, and yes for glidepath-api's ServiceAccount. Shipped as P6a, the backend's direct paths (backstage#215); P6b, the hooks on new live reads (glidepath#188, backstage#216, tower v0.107.0); and P6c, the cluster grants and the policy (apron#108, gitops-hangar-dev#72).

Open decisions

Which GitHub identity opens an agent's pull request. This only matters for the content an agent writes, which is cicd.yaml and code. Promote, rollback and pin PRs are opened by Glidepath's release pipeline, as they are today.

OptionVerdictWhy
A separate GitHub App for agentsLeaningThe PR author shows it was an agent. It is installed only on the repos it needs, with contents and pull requests and never checks. Each call mints a one-hour installation token, so nothing is stored. It isn't on any ruleset bypass list, so it can't merge. This is Autopilot's D4.
Glidepath's existing bot or the Pipelines-as-Code appNoBlurs who wrote the change, and the Pipelines-as-Code app can write status checks, so an agent holding it could post a passing gate.
The person's own tokenNoCorrect attribution, but the agent inherits that person's merge rights and their token has to be held somewhere.

Not in scope