Glidepath · Spec · Testing and the QA gate

A test contract for Glidepath, and a QA gate that reads it

Glidepath already ran a TestWorkflow and the release stage already had a slot for a QA gate. The question was whether that is enough: give the business a standard test, give developers the implementation, and let the gate read the result. Reading the code and the live cluster, the answer was nearly yes. Three things were missing, and none of them is a test runner.

Spec October 17, 2026Status proposed Oct 17, 2026; phases 0 to 4 built and live by Oct 19, 2026Basis glidepath, tower, backstage, hangar and four Skyport app repos, read from the code; the dev cluster checked liveDocs Glidepath testing

Since then. Phases 0 to 3 were built in Glidepath and verified live on boarding-api by October 19, 2026, with the real qa gate reading the check ride from the image's test provenance. Phase 4 shipped the same day in Tower v0.79 and v0.80: test evidence on the image and the Release Record, check-ride readiness and a Tests view under Pipelines. The reference for what is built is Glidepath's testing doc; this page is the proposal as written.

Keep the wrapper · add the contract · attest the result

Glidepath should stay a thin wrapper around Testkube. What it must own is the contract, the evidence and the fence.

Found live · Oct 17, 2026

No Skyport app runs a test. The whole path from test step to Release Record has had no consumer since the demo apps were retired.

baggage-api, boarding-api, gate-api and sky-marshall all declare build → deploy test → deploy dev with no test step and no TestWorkflow file. The only TestWorkflows in the testkube namespace are two one-step curl smoke checks from demo apps that no longer exist, last executed 42 days ago. Tower's Release Record reads test results from Tekton Results and finds none, the qa gate is a declared stub, and Autoland is parked on "needs real test results". Every finding below was read in source, then checked against that cluster.

What is there today

The wrapper is better than its reputation. run-testworkflow is careful about secrets, names, the CLI's log-streaming bug and Testkube's false-abort race, and it already emits a result and an event. What it lacks is a reason to exist: no app calls it, nothing it produces is addressed to the release, and the gate that should read it reads nothing.

The test path todayThree rows. The app repo: cicd.yaml names a test step and a TestWorkflow file, with a phase one fallback script; none of the four Skyport apps has either, and a missing file counts as skipped, which passes. The Glidepath test stage: copy secrets in, rename and apply the workflow, run and poll with the testkube CLI for at most ten minutes, blank the secrets; it writes two Task results and a CDEvent, and passes no image, digest, environment or URL into the workflow. Where the result goes: Tekton Results feeds Tower's Release Record as a flat list that is empty for every app; the Testkube execution record with tags, reports and steps has no reader; the broker chains the next stage on the outcome, which holds; the qa gate on the release PR is a stub that reads nothing, and Autoland is parked on test results.THE APP REPO · WHAT A TEAM WRITEScicd.yamlstage: test · testNameglidepath/*.yamlthe team's own workflowintegration-test.shphase 1 fallbackNothingwhat Skyport apps have0 OF 4 SKYPORT APPS HAVE A TEST STEPNO FILE AT ALL = SKIPPED = PASSTHE NAMED WORKFLOWGLIDEPATH · THE TEST STAGE · RUN-TESTWORKFLOWCopy secrets inapp-secrets → testkubeRename, apply<app>-<name>Run and polltestkube CLI · 60 × 10sBlank secretsdata: nullTask resultsoutcome · step-summaryCDEventtestcaserun.finishedNothing passed inimage, digest, env, URL never reach the workflowA 10 MINUTE CAP, ONE RETRY BUDGET: NONEWHERE THE RESULT GOES · WHO READS ITTekton Resultsarchived 30 daysRelease RecordTower · testResultsTestkube recordtags · reports · stepsNobodyno reader existsBrokernext stage onlyNext stageoutcome == Succeededqa gaterelease PR · stubAutolandparked on test resultsFLAT NAME/VALUE, NO TALLY,EMPTY FOR EVERY APPREADS NOTHING · ALWAYS PASSESHOLDS: CHAIN ON OUTCOMEThe wrapper works. Nothing downstream of it has ever had a result to read.
Figure 1Amber marks where the path is thin or empty today. The only part that holds is the chain: the next stage starts only on a succeeded test.
F1

No app exercises the test stage

Gap

All four Skyport apps: build → deploy test → deploy dev. No test: block, no glidepath/*.yaml workflow, no integration-test.sh. The live testkube namespace holds two orphan workflows from the retired demo apps.

Evidencecicd.yaml in baggage-api, boarding-api, gate-api, sky-marshall · kubectl get testworkflows -n testkube

F2

A missing test passes

Gap

run-testworkflow reports skipped when neither the workflow file nor the fallback script exists, and the stage succeeds. For an ops action that is right; for the test the release depends on it is a hole the SDLC review already flagged.

Evidencerun-testworkflow.yaml, fallback-script step

F3

Nothing is passed into the workflow

Gap

The Task receives image-ref, env and app-name and uses them only for logging and the fallback script. The TestWorkflow is applied as written: it cannot know which digest it is testing, which environment or URL to hit, or which commit it belongs to. Tags are never set, so the execution cannot be found by image later.

Evidencerun-testworkflow.yaml, execute-testworkflow step; Testkube execution.tags and config unused

F4

The qa gate is a stub that reads nothing

Gap

qa-gate.yaml calls governance-stub. It has repo-url and revision threaded through for "reading the test stage's recorded outcome", but no recorded outcome exists that is keyed by image. Business rule GATE-01 lists it as a stub; GATE-03 means that on GitHub Free even a real gate would only advise.

Evidenceqa-gate.yaml · qa-check.yaml · pull-request-qa.yaml · business-rules.md §4, §16

F5

Tower has nowhere to put a test result

Partial

useReleaseRecords.ts flattens every TaskResult of a test PipelineRun into name/value pairs and notes, in its own comment, that no source produces a pass/total tally. The comment is one release behind: run-testworkflow does write outcome and step-summary now. The record would show two strings, if any app produced them. Readiness (REL-11) has no test check; Autoland (REL-01) is parked on exactly this.

Evidencetower src/useReleaseRecords.ts buildTestResults · tower docs/release-records/index.md

F6

Several test steps per flow already work, by index

Holds

The schema allows any number of test steps with a per-step testName, and the flow triggers key each step's CEL filter on the exact previous step (<flow>-<index>-<stage>-) after a live infinite-loop bug. What is missing is meaning: every test step is the same kind of thing, with the same cap and the same pass-or-skip rule.

Evidenceflow-triggers.yaml · cicd.schema.json pipelines.steps[].testName · _helpers.tpl validateFlows

F7

The free agent is enough, with two known seams

Holds

Testkube 2.12.2 standalone: one shared namespace (ADR-0007), a Kyverno policy that pins a workflow to its own app's secret (ADR-0008), and a false-abort race the wrapper already works around by trusting the steps. The agent also emits webhooks and CDEvents, parses JUnit from artifacts, supports templates with typed config, execution tags, retries, services and matrix runs. None of that is used yet.

Evidencethe dev cluster testkube-api-server 2.12.2 · docs.testkube.io open-source, execution-tags, test-workflow-templates, test-workflows-artifacts

F8

Evidence has no durable, portable home

Partial

A test result lives in three places that each expire or stay on the dev cluster: the Tekton Results archive (30 days), the Testkube database, and a CDEvent the broker consumes once. The image that ships to prod carries SLSA provenance, a SAST attestation and an SBOM, and no record that it was ever tested.

Evidencetekton-results.md · image-signing.md · verify-sast-attestation.yaml

Is a wrapper plus a contract enough?

Yes, with one addition. A wrapper decides what goes in and what comes out; that is the contract. But a gate that reads a contract from a database has to trust the database, the namespace it lives in, and whoever can write to it. The release stage already solved that problem once for SAST: the build attests, the gate verifies the signature against the platform's own Fulcio and Rekor. The test stage should do the same. Then the contract has two halves, and the gate needs no new trust.

Glidepath owns

The contract

A fixed set of inputs every workflow can rely on (config and tags: app, image, digest, env, URL, revision, chain) and a fixed set of outputs the platform records (outcome, execution id, workflow revision, JUnit counts, report link). Three step roles: execute, test, check ride.

Glidepath owns

The evidence

After a passed check ride, the wrapper signs a check-ride attestation onto the image digest, keyless, as the app's own pipeline-runner. The attestation travels with the image to every cluster and outlives every archive. The qa gate verifies it.

Glidepath owns

The fence

A shared check-rides repository: templates owned by SDETs and product, per-app workflows owned by the team and SDETs, synced by Argo CD into the testkube namespace, and admitted by the existing Kyverno policy extended to cover synced workflows.

Testkube owns

Running tests

Templates with typed config, step retries and timeouts, services and matrix runs, JUnit parsing, artifacts, schedules, webhooks and CDEvents, the execution record and a viewer. All in the free agent. Glidepath calls these; it does not rebuild them.

The design

Four changes to Glidepath, one new repository, one new attestation type, and a Tower strip. No new stage, no new CRD, no new service.

The designFive zones. A shared check-rides repository with templates owned by SDETs and product owners and per-app workflows owned by the team and SDETs, with CODEOWNERS and a ruleset so both review every change. The app repo, where the team alone writes cicd.yaml with any number of execute, test and check-ride steps and its own workflows. The testkube namespace, where Argo CD syncs the templates and workflows and Kyverno admits only a workflow that references its own app's secret. The Glidepath test stage, which runs the named workflow with config and tags for image, environment and URL, polls and reads the execution record, and attests the result onto the image digest with cosign. Evidence: the check-ride attestation on the digest in GHCR and Rekor, which is portable; Task results and the testcaserun.finished CDEvent, which stay on the dev cluster. Readers: the qa gate verifies the attestation, the Release Record shows a primary test strip and a readiness check, and Autoland has one prerequisite met.CHECK-RIDES REPO · SHARED OWNERSHIPtemplates/SDET + product ownersapps/<app>/team + SDETCODEOWNERS · rulesetboth owners review every changeUSETHE APP REPO · THE TEAM ALONEcicd.yamlexecute · test · check rideglidepath/*.yamlown workflowsAny number of test stepswarm-cache · integration · check-rideTESTKUBE NAMESPACE · SHARED, FENCEDArgo CD synctemplates + workflowsKyvernoown secret only<app>-check-ride · label hangar.io/appstoreGIT → CLUSTERGLIDEPATH · TEST STAGERun with configimage · env · url · tagsPoll, read recordsteps · reportsAttest onto the digest · cosign keylessfocalSTEPRUNSEVIDENCE · THREE RECORDS, ONE OF THEM PORTABLEcheck-ride attestationon the digest · GHCR + RekorTask resultsoutcome · id · counts · revtestcaserun.finishedCDEvent with the same fieldsThe attestation ships with the image to every cluster and survives every archive. The other two stay on the dev cluster.READERSqa gateverifies the attestationRelease Recordprimary test strip · readinessAutolandone prerequisite metThe business writes the template, the team writes the steps, the image carries the proof.
Figure 2The target. Amber-filled boxes are the new pieces; everything else exists today and keeps its shape.

Test and execute steps

Keep stage: test as the only DAG slot; the fixed superset DAG is a design rule worth keeping. Give the step a role that says what kind of thing it is, and pass the step's context into the workflow instead of applying the file blind.

role: execute

An action, not a test

Clear a cache, warm a pool, seed a fixture, run a migration dry-run. Pass or fail still chains the next stage. No attestation, no JUnit expected, no QA evidence. A missing workflow is a failure, since the step was written on purpose. required: false turns a failure into a warning for best-effort actions.

role: test (default)

The team's own tests

Integration, contract, smoke, load. The workflow lives in the app repo as today. Results are recorded and shown, and the next stage chains on them, but the qa gate does not read them. A missing workflow is a failure.

role: check-ride

The standard the release depends on

The workflow is <app>-check-ride from the shared repository, never from the app repo. Its passing run is attested onto the digest. Exactly one per flow, and validateFlows requires one after the last Ground deploy in any flow that ends in a release, once the catalog switches qa.required on.

cicd.yamlWhat it means
pipelines: ci: trigger: { source: git, event: push, branch: main } steps: - stage: build - stage: test env: dev testName: flush-cache role: execute required: false - stage: deploy env: dev - stage: test env: dev testName: integration # glidepath/integration.yaml timeout: 20m - stage: test env: dev role: check-ride # <app>-check-ride, shared repo - stage: release env: staging

One flow, three test steps with three meanings. The execute step runs before the deploy and may fail without stopping the chain. The test step is the team's own suite, with its own time budget (the fixed 60 × 10 s loop becomes a parameter). The check-ride step takes no testName: the platform knows the name. Each step's trigger is already distinct by index, so nothing changes in the flow triggers.

Every step, whatever its role, runs the workflow with the same config and tags: app, image, digest, env, url (the Ground service address, http://<app>.app-<app>-<env>.svc), revision, chain, step. A workflow reads them as {{ config.url }} and never hardcodes an environment.

Unchanged: the env requirement, the chain on outcome == Succeeded, the secrets materialization, the name rewrite to <app>-<name>, and the false-abort workaround. Ground only for now: a check ride against a Flight environment needs a trigger on release healthy, which does not exist yet (roadmap phase 6).

The check-rides repository

A check ride is the flight test a pilot passes to be certified for a rating. Here it is the one workflow a release must pass, and the name separates it from the team's own tests. The repository is the shared ground: the business sets the standard in a template, the team implements it in a workflow that uses the template, and GitHub code owners make both signatures necessary.

PathWhat it holdsOwned byHow that holds
templates/check-ride-http-service.yamlThe standard for an HTTP service: required config keys, a health step, a contract step that must produce JUnit, a time budget, tags, an artifacts step. Typed config with defaults.SDETs and product ownersBoth groups are code owners; a change needs one review from each.
templates/check-ride-worker.yaml, …-function.yamlOne template per service class Airframe already knows (Container, Function, Worker), so the standard matches the shape of the app.SDETs and product ownersSame rule.
apps/<app>/check-ride.yamlThe app's implementation: use: [check-ride-http-service], its config (suite path, test image, timeouts within the template's budget), extra steps. Content comes from the app repo at the revision under test, via content.git.The owning team and SDETsThe team writes it; an SDET reviews it. Scaffolded by onboarding as a PR, like the .tekton files.
apps/<app>/waivers.yaml (later)A time-boxed waiver of one named template step, with a reason and an expiry. The gate honours an unexpired waiver and says so in the PR comment and the record.Product owner + SDETA waiver is the only way to ship past a failing check ride without a bypass alert.
CODEOWNERS, the rulesetWhat makes the ownership real. On GitHub Free a private repo does not enforce it (GATE-03); a public repo or a Team plan does. The check-rides repo holds no secrets, so it can be public.Platform adminsGenerated by the same hack/apply-guardrail-ruleset.sh pattern.
Sync

Argo CD, not the pipeline

One Application in gitops-cluster-dev syncs templates/ and apps/ into the testkube namespace. The pipeline no longer applies a check-ride workflow; it runs the one that is there, and records the synced revision. A developer cannot run a hollow local copy and call it the check ride.

Fence

Kyverno, extended

ADR-0008's policy keys the allowed secret on the caller's namespace, which works for pipeline-runner and not for Argo CD. Add a rule for the sync identity: a workflow it applies may reference only the secret of the app named in its hangar.io/app label, and the label must match the file's apps/<app>/ folder. Same policy file, one more rule, live-verified with kubectl --as as before.

Shape

Template sets the floor

The template declares the steps every service must pass and marks them as not optional. The app's workflow adds steps and fills config. Testkube merges them, so the team cannot remove a required step without changing the template, which needs the product owner.

The contract

Three records, written in one place (run-testworkflow) with the same fields. The attestation is the one the gate reads. The Task results are the ones Tower reads today. The CDEvent is the one the broker and any future subscriber reads. Keeping them identical is the whole point: one definition, three carriers.

FieldSourceTask resultAttestation predicateCDEvent content
outcomeExecution result.status, steps-trusted on abort✓ (exists)✓✓ (exists)
workflow, roleStep✓✓✓ (test-name exists)
execution-id, started, finishedExecution record✓✓✓
workflow-source, workflow-revisionArgo CD sync annotations on the TestWorkflow (check ride) or the app repo revision (own tests)✓✓✓
template, template-revisionThe resolved workflow's use✓
image, digest, env, url, revision, chainThe config the step passed in; also set as execution tags✓✓ (digest is the subject)✓ (image, env exist)
tests: total, passed, failed, skippedJUnit reports Testkube parsed from artifacts (reports)✓✓✓
steps[]: name, status, durationresult.stepsstep-summary (exists)✓
report-urlTestkube execution URL, and the JUnit artifact path✓✓
Predicate type

https://hangar.io/attestations/check-ride/v1

An in-toto statement whose subject is the image digest. Signed keyless by the app's pipeline-runner through the in-cluster Fulcio, uploaded to Rekor, the same path and the same verification identity verify-sast-attestation already checks. Written only for role: check-ride and only on a pass; a failed check ride leaves no attestation and the gate fails for want of one.

Tags

Found by image, later

Every execution carries app, image, digest, env, role and chain as Testkube execution tags. Tower can list an app's runs with one tagSelector query, and the gate's PR comment can link to the execution it verified.

Fail, don't skip

Skipped is for disabled, only

outcome: skipped stays for test.enabled: false. A step whose workflow does not exist fails. A check-ride step whose synced workflow is missing fails with "no check ride for <app> in the check-rides repo", which is the onboarding message a new app needs.

The qa gate

Landing the real gate is the one-file change qa-gate.yaml was shaped for: replace the stub step with verify-and-check, flip the registry entry to real, add the check to the ruleset. The PR comment carries the counts and the link, and the Release Record freezes the same evidence.

The qa gate, made realA release PR opens on the gitops repo. The gate resolves the promoted digest with resolve-promoted-image, downloads the check-ride attestations for that digest, and verifies each signature against the platform Fulcio identity and Rekor. Every verified predicate is checked against a policy with six rows that must all hold: the workflow name is the app's check ride, the source is the check-rides repo, the revision is in the allowed set, the environment is the last Ground environment, the age is under the configured maximum, and the failed count is zero. A waiver in the app's waivers file is applied here and named. Three outcomes: pass with counts and an execution link, fail with a named reason such as no run for this digest or a stale run, or fail because no attestation exists and the check ride never ran.Release PR opensgitops-<app> · release-*Promoted digestresolve-promoted-imageAttestationscosign download · typeVerify signatureFulcio id + RekorVERIFIED PREDICATE(S)POLICY · EVERY ROW MUST HOLDworkflow<app>-check-ridesourcecheck-rides reporevisionallowed setenvlast Ground envage< qa.maxAgefailed== 0Any waiver in apps/<app>/waivers.yaml is applied here, named in the comment, and recorded.Passcounts + execution linkFail · named reasonno run for this digest, stale, envFail · no attestationcheck ride never ranGREEN CHECK · RECORD STRIPRED CHECK · /RETEST RE-READSRED · RUN THE STEPWhat the gate never does: run a test, read the Testkube database, or trust a ConfigMap. It reads a signed statement about a digest.The same shape as the sast gate, with a different predicate.
Figure 3The gate reads a signed statement about a digest. It runs nothing and trusts no database.
Knob · glidepath-catalog valuesDefaultMeaning
qa.requiredfalse until phase 3When true, validateFlows refuses a flow that ends in a release step without a check-ride step after its last Ground deploy, and the gate fails on a missing attestation. When false the gate reports and the step is optional.
qa.workflowNamecheck-rideThe suffix of the standard workflow, <app>-<suffix>.
qa.maxAge72hHow old the passing run may be, measured from the attestation to the PR. A digest tested on Monday may ship on Thursday; a month-old run is not evidence.
qa.sourceRepo, qa.allowedRevisionsthe check-rides repo · mainWhich repository and revisions count. Pinning to main at sync time means a merged template change applies to the next run, not to runs already attested.
qa.envlast Ground environmentWhich environment's run counts. A later phase adds a Flight run before production.

GitHub Free still applies (GATE-03): the gate reports and the bypass alert fires after a merge past it. That is true of every gate here and is the production roadmap's T1, not this design's problem to solve.

What Tower shows

Release Record

A primary test strip

Beside the certificate-style verification strip: Check ride with passed/failed/skipped counts, duration, the template and its revision, the execution link, and the attestation's verification state read the same way the provenance panel reads SLSA (fresh from Rekor by log index). The team's own role: test runs appear below it as rows from the Task results, replacing today's flat list. Frozen into the committed record at seal.

Readiness (REL-11)

One more check

"Check ride passed for the leading candidate" joins the readiness list, display only like the others. It reads the Task results via Tekton Results for the candidate's chain, so it works before any release PR exists.

Tests view

Runs per app

A Tests sub-tab under Pipelines, from one backend route that lists Testkube executions by tagSelector app=<app> through the in-cluster API, read-only, with JUnit summaries and artifact links. The backend's read identity gains get on the Testkube service, nothing on secrets.

Autoland (REL-01)

One prerequisite met

Autoland's eligibility list names "tests that report results". The check ride is that. Canary metrics on prod remain the other blocker.

What Testkube gives, and where the free tier stops

Checked against docs.testkube.io and the 2.12.2 agent on the dev cluster. The third diagram in Hangar's testing set records how the first three seams were found.

CapabilityUsed forAvailability
TestWorkflowTemplate with typed configThe business standard; {{ config.url }} in stepsFree agent
use: at the top levelTemplate sets pod, service account, required stepsFree agent
Execution tags, tagSelector on the list APIFind a run by app, digest, envFree agent
JUnit parsing from artifacts (reports)The counts in the contractFree agent
Step retry, timeout, optional, conditionFlaky-step budget inside the workflow, not in TektonFree agent
services, parallel, matrix and shardingBrowsers, databases, big suitesFree agent
Schedules in the workflow, TestTriggers on eventsNightly soak without a Glidepath cronFree agent
Webhooks and CDEvents on execution finishedReplace polling with an event into the broker (phase 6)Free agent; needs the cdeventsTarget value
executionNamespaces, per-tenant runsWould remove the shared namespace (ADR-0007)Pro only, confirmed in source
config.*.sensitive per-run secretsWould replace the Kyverno fence (ADR-0008)Not in this CE build
Running context, actor identity on an executionWho or what started a runPro only
DashboardA place to browse runsCommercial; the OSS viewer uploads to view.testkube.io, so not for private results. Tower's Tests view instead.
Result status on a fast podThe false-abort race2.12.2 bug, worked around by trusting steps; re-test on upgrade

Risks and open decisions

Decision

Ground evidence for a Flight release

Phase 3 accepts a check ride run on the last Ground environment as evidence for the first Flight PR. That is the honest floor today: nothing can trigger a test on release healthy. The production PR should eventually require a run tagged env=staging. Decide whether that is a gate rule (fail) or a readiness row (display) when phase 6 lands.

Decision

Where check-ride content comes from

A check ride tests the running service, so its steps should come from the shared repo and its target from config. If a team's suite lives in the app repo, the workflow pulls it with content.git at the revision under test. The template decides which is allowed; recommend: steps from the shared repo, suites from the app repo.

Risk

GitHub Free

CODEOWNERS and the qa check both report without blocking on a private repo. The check-rides repo can be public (no secrets), which makes its ownership real now. The gate waits on T1 like every other gate.

Risk

Attestation sprawl

Every passing check ride adds an attestation to the digest; a digest rebuilt and retested ten times carries ten. cosign handles many; the gate takes the newest that verifies. Prune with the image prune Tower already runs.

Risk

The shared namespace is still shared

Synced check-ride workflows and team workflows sit in one namespace. The Kyverno rule keeps secrets apart; nothing keeps a team's workflow from naming itself other-app-check-ride except the name rewrite in the wrapper and the folder-to-label rule in the sync policy. Acceptable for one owner's apps, as ADR-0007 says; revisit with Pro or a second Testkube.

Risk

Testkube version

2.12.2 is from early September. The false-abort race, the reports field and CDEvents emission should all be re-checked on the version phase 0 pins. Upgrade first, then build on the fields.

Roadmap

Six phases in order, each with a test that says when it is done. Phase 0 is the cheapest and the most important: nothing in this design should be built before one real test has been seen to run and fail on the cluster. Phases 1 to 3 are Glidepath; phase 4 is Tower; phase 5 is rollout. Effort is a single-engineer estimate.

Phase 0 · Prove the path

S · days

Give boarding-api a real test step and a real workflow (Playwright or pytest against the Ground URL, JUnit to artifacts). Upgrade Testkube to a current agent on the dev cluster. Make a missing workflow fail for role: test. Make the poll budget a step parameter.

Done when one green test PipelineRun on the dev cluster whose Testkube record shows tags and a parsed JUnit report, and one red run from a deliberately failing suite that stops the chain.

run-testworkflow.yaml · test.yaml · boarding-api cicd.yaml · gitops-cluster-dev testkube/application.yaml

Phase 1 · The contract

M · one to two weeks

Pass config and tags into every run. Define the result set and write it to Task results and the CDEvent. Add role, required, timeout to the schema and validateFlows. Emit the check-ride attestation on a passed check-ride run, keyless, like sast-scan. Rebuild the toolbox (schema is baked in). Document in cicd-yaml-reference.md and a new docs/admin/testing.md.

Done when cosign verify-attestation --type succeeds for the predicate on a real digest; Tower's existing flat list shows the new results without a Tower change.

cicd.schema.json · _helpers.tpl · run-testworkflow.yaml · stage-debrief.yaml · toolbox image

Phase 2 · The check-rides repo

M · one to two weeks

Create the repo with one HTTP-service template and boarding-api's workflow. CODEOWNERS with two groups. Argo CD Application into testkube. Extend the Kyverno policy for the sync identity and the folder-to-label rule. Onboarding scaffolds apps/<app>/check-ride.yaml as a PR. Add the repo to the rename map and the business rules register.

Done when a template change needs both reviews; a workflow referencing another app's secret is refused at admission with kubectl --as the Argo CD identity; testkube get tw shows the synced revision annotation the attestation records.

new repo · gitops-cluster-dev 30-policy + 50-platform-cicd · deliver-onboarding-files.yaml

Phase 3 · The gate

S · days

Replace the stub step in qa-gate.yaml with verify-and-policy. Registry entry to real, ruleset regenerated, GATE-01 updated. PR comment with counts and the execution link. Flip qa.required once every app with a Flight environment has a check ride.

Done when a release PR for a digest with a passing check ride goes green with counts; the same PR for an untested digest goes red and names the missing step; /retest qa re-reads.

qa-gate.yaml · values.yaml releaseGuardrails · hack/apply-guardrail-ruleset.sh · business-rules.md

Phase 4 · Tower

M · one to two weeks

Primary test strip in the Release Record from the verified attestation, frozen at seal. Readiness row. Tests sub-tab from one backend route over the Testkube list API by tag. Autoland eligibility reads the new readiness row. Docs in tower docs/release-records.

Done when the record for a release shows the check ride's counts and a verified state; the Tests view lists boarding-api's runs with JUnit summaries in the local harness.

tower useReleaseRecords.ts · ReleaseRecordDetail.tsx · nextRelease.ts · backstage glidepathProvenance.ts

Phase 5 · Roll out to Skyport

S · days

Check rides for baggage-api, gate-api and sky-marshall. Waivers file and the gate's waiver rule. Retire the integration-test.sh fallback once no app uses it.

Done when every Skyport app with a Flight environment has a green qa check on its next release PR; the business rules register lists TEST-01 to TEST-05 and GATE-12 as enforced or soft with evidence.

four app repos · check-rides repo · release-guardrails.md

Phase 6 · Later

L

A Flight check ride on release healthy (a new trigger kind from the relay's facts), required for the production PR. Testkube CDEvents into the broker to replace polling. Nightly soak from a Testkube schedule, attested with role: soak for the record only. Matrix and sharded check rides from the template.

Done when the production release PR shows a staging check ride; the test stage no longer polls.

flow-triggers.yaml · glidepath-relay · testkube cdeventsTarget

Rules this adds to the register

Proposed IDs for docs/policy/business-rules.md, to be written with their strength and evidence as each phase lands. Nothing below is a rule yet.

IDRuleStrength when builtHeld by
TEST-01A test step runs the named TestWorkflow with the platform's config and tags (app, image, digest, env, url, revision, chain). A workflow never hardcodes its target.Enforcedrun-testworkflow
TEST-02A test or execute step whose workflow does not exist fails. Only test.enabled: false skips.Enforcedrun-testworkflow, validateFlows
TEST-03The check ride for an app is apps/<app>/check-ride.yaml in the check-rides repo, using a template from templates/. A change to a template needs an SDET and a product owner; a change to an app's workflow needs the team and an SDET.Convention on GitHub Free; Enforced with code ownersCODEOWNERS, ruleset
TEST-04A synced workflow may reference only the secret of the app in its label, and the label must match its folder.EnforcedKyverno testkube-secret-usage
TEST-05A passed check ride is attested onto the image digest by the app's pipeline identity. A failed one leaves nothing.Enforcedrun-testworkflow, Fulcio, Rekor
GATE-12A release PR passes qa only if a verified check-ride attestation for the promoted digest meets the policy: the app's workflow, from the check-rides repo at an allowed revision, on the required environment, younger than qa.maxAge, with no failed tests, or an unexpired waiver that is named in the comment and the record.Soft on GitHub Free (GATE-03); Enforced with the rulesetqa-gate

How this was read

The Glidepath catalog (run-testworkflow, test, qa-gate, qa-check, resolve-promoted-image, verify-sast-attestation, the flow triggers and validateFlows), the cicd.yaml schema and reference, ADR-0007 and ADR-0008, the release-guardrails and release-state docs, Tower's useReleaseRecords.ts and release-records doc, the business rules register on its branch, the SDLC review and production roadmap, and the four Skyport apps' cicd.yaml. Live on the dev cluster: the Testkube deployment version, the installed CRDs, the two remaining TestWorkflows and their executions. Testkube capabilities come from docs.testkube.io for the current agent; the 2.12.2 agent on the cluster predates some of them and phase 0 upgrades it before anything depends on a field.

Nothing was changed in any repository or cluster.