Docs · Hangar
Airframe A+ program: a service catalog an AI agent can operate
Goal: an agent (Claude Code, a workload, a client) can discover, configure, validate, change and
verify anything Airframe manages, using only machine-readable contracts and governed tools, and can
tell when it has failed and why. Success is measured, not felt: tools/airframe-scorecard/scorecard.py.
The test we are building toward is one sentence typed into a Claude Code session:
provision a new python application named parachute. give it a dev and test ground environment. a staging and prod flight environment. and configure it with 1 100% weight canary step, an URL=http://myendpoint.io env var.
Status labels used here: Built, Draft (written, not applied), Proposal, Unverified.
Progress (2026-09-26, end of M0): 41.3/100, 2 of 14 acceptance checks. Shipped in Airframe v0.3.91: the rollout guard (AF-10a), render tests and chart CI (AF-10b), AGENTS.md (AF-1a), tools/airframe-validate v0 (AF-4a). Validation moved 22 -> 76 and hygiene 46 -> 92. The scorecard’s new baseline is tools/airframe-scorecard/baseline-2026-09-26b.json. One dimension went down: component contracts 4.0 -> 3.4, because baggage-api’s env file added four hand-written derived names (bag-mq-connection, bag-mq-user-credentials), which is the gap AF-3 (fromComponent) closes. Note: the values schema still accepts unknown keys; strictness lives in airframe-validate until AF-2 generates a strict schema. Unknown fields in an XR are silently pruned by ArgoCD (U1), so XR files need the same check.
1. Baseline (2026-09-26)
Measured by the scorecard tool against the real repos. It scores readiness against the full A+ contract, so it is a progress meter: most of the gap is artifacts that do not exist yet, not things that are broken. It is stricter than a “does it work with a careful agent” judgment, which is why it reads lower than an eyeball grade would.
| Dimension | Score | What the measurement found |
|---|---|---|
| 1 Discoverability | 35 | Human docs and a Backstage catalog exist; 6 of 13 XRDs are added to the catalog. No AGENTS.md, no contract bundle, no llms.txt. |
| 2 Schema precision | 45 | values.schema.json covers all 29 top-level keys and a wrong type fails at render. But 12 of 234 nodes have a description (5%), 0 of 68 objects reject unknown keys, and 4 of 4 live values files still render with a typo injected. XRDs: 78 of 125 fields described, one CEL rule in 13 XRDs. |
| 3 Component contracts | 4 | 22 of 25 live env entries hard-code a derived name (cache-master, <name>-connection, svc.cluster.local). No component declares outputs. |
| 4 Pre-merge validation | 22 | No airframe validate, no required check on env, gitops or tenants repos (none found). 11 chart guards. Ground env files are committed straight to main. |
| 5 Write safety | 13 | 4 of 4 live values files mix human and machine-owned keys (releaseTracking and the image tag beside replicas and env). No base layer, no field ownership, no risk classes. Secrets are references, never values. |
| 6 Observe and verify | 21 | Crossplane’s Ready/Synced conditions and one custom condition (CicdOnboarded). No reason codes, no verify contracts, no describe. |
| 7 Docs for agents | 39 | Quickstarts are true (walked live) and all 14 YAML blocks parse. None is validated in CI; nothing is executable. |
| 8 Interaction surface | 27 | One GitOps write path. The only config editor is Tower’s UI; no API. |
| 9 Safety integration | 20 | Tiers reach paths, not fields. extraManifests is an arbitrary-manifest escape hatch with no special handling. |
| 10 Hygiene | 46 | helm lint passes and the fleet renders. No chart tests, no chart CI. |
| Overall | 27 / 100 | 1 of 14 acceptance checks pass. |
Re-run any time: python3 tools/airframe-scorecard/scorecard.py --json out.json. The committed
baseline is tools/airframe-scorecard/baseline-2026-09-26.json.
Two findings from running the chart, not reading it
- Typos pass.
rolout:,replcas: 5andtotallyMadeUprender with exit 0 and are ignored. A component withsize: gigantic(the Redis XRD allows small, medium, large) also renders; it fails only after merge, on the cluster, or is pruned silently. - Configuring before the first image renders a broken Rollout. Setting
rollout.stepsandenvwith no image rendersimage: ':'. The quickstart works around it withrollout: null, but then you cannot configure the rollout. An agent declaring everything up front, which is the natural thing to do, hits this on the first environment.autopilot/tests/test_airframe_plan.py::test_CANARY_the_chart_bug_the_workaround_exists_forpins the behaviour and will fail on purpose when the chart is fixed.
2. What A+ means
A+ = overall ≥ 97 and all 14 acceptance checks pass. Each check is automated by the scorecard.
| # | Acceptance check | Baseline |
|---|---|---|
| 1 | Typo acceptance is 0%: every injected unknown key, in every live values file, fails | 4/4 accepted |
| 2 | Schema description coverage ≥ 95% | 5% |
| 3 | Every non-passthrough object rejects unknown keys | 0 of 68 |
| 4 | A versioned contract bundle is published | no |
| 5 | AGENTS.md at the root and on every component |
no |
| 6 | All component XRDs declare outputs and verify checks | 0 of 6 |
| 7 | No derived-name literals in live env entries | 22 of 25 |
| 8 | airframe validate exists and is a required check |
no |
| 9 | No live file mixes human and machine-owned keys | 4 of 4 mixed |
| 10 | A shared base layer exists in the ApplicationSets | no |
| 11 | airframe.* tools exist |
no |
| 12 | An AppSpec schema exists | Draft (autopilot/schemas/appspec.schema.json) |
| 13 | Executable walkthroughs replace prose quickstarts | 0 |
| 14 | Every live values file renders cleanly | yes |
Beyond the scorecard, the end-to-end acceptance test is the parachute sentence, run as a Preflight
case with a seeded bad run, and a resume test (kill the session mid-plan, resume by task_id).
3. The design: the values contract is the API
A NodeJS or Python application’s ongoing configuration is its values files, not its XR. So “making Airframe AI-friendly” means making that contract a first-class, machine-readable, validated, ownership-aware API, and giving agents a small verb set over it: describe, explain, plan, validate, apply (as a PR), status, verify.
Glidepath’s cicd.schema.json is the model: additionalProperties: false, long descriptions,
explicit oneOf shapes. The Airframe schema should read like it.
One source, generated artifacts
XRD OpenAPI + chart values schema + sidecar meta + dead-end rules
│
contract generator (CI)
│
airframe-contract.json ← versioned with each Airframe tag
┌───────────┬──────────┼──────────┬──────────────┐
airframe Tower forms airframe.* docs, AGENTS.md,
validate + validator tools (MCP) reference (generated)
Diagram: diagrams/plan/05-contract-architecture.html.
Sidecar meta. A Kubernetes CRD’s structural schema rejects unknown x- keys, so the extra
vocabulary cannot live in an XRD. It lives in contract/<kind>.meta.yaml next to it, keyed by JSON
pointer, and the generator merges it. In the chart’s values.schema.json (not a CRD) the same keys
can be inline annotations; Unverified: that Helm’s validator ignores unknown keywords. Check with
helm lint before relying on it.
| Key | Meaning | Used by |
|---|---|---|
x-hangar-owner |
human, release (Glidepath), platform (compositions) |
ownership gate, Tower |
x-hangar-risk |
low, medium, high, critical |
Clearance tier mapping |
x-hangar-effects |
what changing it does: restart, new namespace, external resource | plan output |
x-hangar-outputs |
on a component: Secrets, ConfigMaps, keys, DNS, ports it produces | wiring, lint |
x-hangar-verify |
machine-checkable expectations | verify |
x-hangar-passthrough |
deliberately open (podSpec, extraManifests) |
strictness gate |
A sample is in drafts/airframe/contract/redis.meta.yaml.
4. The ten workstreams
Effort is for one person. Born-A+ rule: every component built from now on (MongoDB, OAuth, nginx,
the chart’s agent block) ships with outputs, verify checks, sidecar meta, AGENTS.md and a description
on every field. Nothing new adds to the retrofit.
AF-1 Discoverability (35 → A+)
airframe/AGENTS.md(root) andcompositions/<x>/AGENTS.md: where the contract lives, how to validate, what not to touch. Draft:drafts/airframe/AGENTS.md.airframe/llms.txt: an index of the contract and docs.hangar.io/agent-summaryannotation on every XRD;add-to-catalogreviewed for all 13.contract/airframe-contract.jsonand theairframe.capabilitiestool.- Accept: a cold-start Preflight case answers 10 capability questions from the contract alone.
- Effort: 2 days after AF-2’s generator exists.
AF-2 Schema precision (45 → A+)
- Schema-first: move the 548 lines of prose from
values.yamlcomments intovalues.schema.jsondescriptions, then generatevalues.yaml(defaults and doc comments) from the schema so they cannot drift. additionalProperties: falseon every object except marked passthroughs (podSpec,extraManifests,canaryAnalysis,blueGreen).- Enums, patterns (DNS names, quantities), examples, defaults. Coverage gates in CI: descriptions ≥ 95%, strictness 100%.
- XRDs: CEL rules for cross-field constraints (
x-kubernetes-validations), and required descriptions. - Discriminated union for
components[]ontype, each arm generated from that component’s XRD spec, sosize: giganticfails before merge. (Tower’s hand-rolled validator has nooneOf; validate server-side or adopt a real validator.) - Accept: a mutation test over every live values file (add a key, misspell a key, break an enum) is rejected 100%.
- Rollout: warn-only first, enforce after the fleet baseline sweep passes.
- Effort: 1 week. Risk: strictness can break an existing environment, hence the sweep.
AF-3 Component contracts (4 → A+)
- Each component declares outputs in its sidecar meta: Secret and ConfigMap names, keys, service DNS, ports.
- Chart feature
fromComponent:env: [{name: REDIS_URL, fromComponent: {name: cache, output: url}}], resolved to a literal or avalueFromreference. Agents stop guessingcache-master. - Components publish
status.outputsso it is discoverable at runtime. - Lint AF-COMP-002: every reference resolves to a declared component output.
- Accept: zero derived-name literals across the fleet; every reference resolves.
- Effort: 1 week for Redis, Postgres, RabbitMQ, SecretStore; MongoDB and OAuth are born with it.
AF-4 Pre-merge validation (22 → A+)
airframe validate is one tool with six layers. It runs as a required check on app repos
(platform/envs), gitops repos, and the tenants repo (xr-requests), and as an agent tool.
| Layer | Checks |
|---|---|
| L1 schema | values against the strict chart schema; XRs against the XRD schema |
| L2 render | helm template against the pinned chart version |
| L3 conformance | kubeconform against the CRDs |
| L4 policy | ownership, risk, secret-looking literals, extraManifests |
| L5 conventions | dead-end rules, below |
| L6 references | every valueFrom and fromComponent resolves |
Output is JSON and SARIF with a rule id, a JSON path and a fix hint, so an agent can retry against it.
Seed rules, each with a seeded failing fixture (the dead-ends list becomes lint):
| Id | Catches |
|---|---|
| AF-CLUSTER-001 | devCluster other than the registry’s canonical dev value (even when the physical cluster is named differently) |
| AF-ENV-001 | an environment named like a pipeline stage (test) |
| AF-ENV-002 | env used where envName is meant |
| AF-ROLLOUT-001 | rollout config with no image (until the chart guard lands) |
| AF-PROBE-001 | Spring Boot probe not on /actuator/health/liveness |
| AF-ARCH-001 | build.platforms pinned to one architecture |
| AF-RABBIT-001 | RabbitMQ 4.1 with the pinned operator |
| AF-COMP-001 / 002 | unknown component type; unresolved output reference |
| AF-OWN-001 | a machine-owned key changed by a non-owner |
| AF-RISK-001 | extraManifests or another critical field without human approval |
| AF-SECRET-001 | a secret-looking literal in env or configMaps |
- Accept: every dead end has a rule and a failing fixture; every live file passes; under 10 seconds.
- Effort: 1 week for L1 to L3 as a container and a Tekton task; the rules accrue.
AF-5 Write safety (13 → A+)
- One file, one owner. Ground:
platform/envs/<env>.yaml(human) and<env>.release.yaml(the deploy stage). Flight:values.yaml(human) andvalues.release.yaml(Glidepath’s release PR). ApplicationSets layerbase → env → release, later wins. - A shared base layer:
platform/base.yamlandgitops-<app>/base/values.yaml, so “set this everywhere” is one edit. - Field ownership and risk in the sidecar meta; an ownership gate (AF-OWN-001).
agent-scopebecomes field-aware (JSON-pointer allow and deny), not path-only.extraManifests,rollout.image,releaseTracking,networkPolicyandhttpRouteare denied to agents by default.- Canonical formatting and a comment-preserving patch engine, so agents do not drop the comments that carry meaning.
- Accept: no live file mixes owners; a property test shows a release PR and a config PR never conflict.
- Coordination: three repos change together (airframe, glidepath’s
open-release-prand deploy stage, Tower’s Config tab). Unverified: that ArgoCD’s git files generator can exclude*.release.yamlso it does not create a second Application; the generator supportsexclude, but test it on a scratch app first. - Effort: 2 weeks with migration.
AF-6 Observe and verify (21 → A+)
- A shared status helper: conditions
Ready,Syncedplus a closed list of reason codes, each with a hint string, andobservedGeneration, on every XRD. - Verify contracts per component and per app (
x-hangar-verify): XR ready, Secret exists, port open, Rollout healthy, SLO not burning. airframe.describe: effective config (defaults merged), XR conditions, Argo health, last release, recent failures. Tower’s backend already assembles most of this.- Accept: every XRD status uses the closed reason list;
verifypasses and fails correctly on seeded fixtures. - Effort: 1.5 weeks.
AF-7 Docs for agents (39 → A+)
- Convert the quickstarts to executable walkthroughs (
docs/walkthroughs/*.yaml): each step has a command, an expected result and a verify. Render them to markdown; the walkthrough runner replays them on the dev cluster. - CI extracts every YAML block from the docs and validates it against the contract.
- Reference docs generated from the contract.
- Accept: part 1 replays green; doc YAML is validated in CI.
- Effort: 1 week; rides along with each Skyport part.
AF-8 Interaction surface (27 → A+)
- AppSpec (
autopilot/schemas/appspec.schema.json, Draft): desired state for an app and its environments in one place. - Planner (
autopilot/src/clearance/airframe_plan.py, Built and tested): AppSpec to an ordered ChangeSet across the tenants, app and gitops repos, with gates, dependencies, assumptions and warnings. - Tools in Clearance:
airframe.capabilities,describe,explain,plan,apply(opens PRs),status,verify,diagnose(via Holmes). - Idempotent (same spec, same change set, a no-op when converged) and resumable (state on the
task_id, PR labels and the audit log). - Accept: the parachute sentence passes as a Preflight case, plus a resume test.
- Effort: 3 weeks, mostly adapters (GitHub, ArgoCD, Kubernetes).
AF-9 Safety integration (20 → A+)
- Risk classes map to Clearance tiers: ground writes T1, flight PRs T2, creating flight environments T2.
- Escape hatches denied by default; field-level policy tested with negative cases.
- Accept: every high or critical field has a negative test.
- Effort: 3 days after AF-5.
AF-10 Hygiene and determinism (46 → A+)
- Chart tests (helm-unittest or a script), chart CI, and a compatibility matrix (chart version by values fixtures). The fleet baseline runs on every schema change.
- Chart fixes: no Rollout without an image (the parachute bug), more guards with actionable messages,
devClustervalidated against the registry, anagent:block (below). - Accept: chart CI green; scorecard runs in CI and fails on regression.
- Effort: 1 week, and the first thing to do.
5. Chart changes, in one list
- Guard: render no Rollout, RolloutWatch or workload until
rollout.imageis set. Fixes theimage: ':'bug and lets an agent configure before the first deploy. Then flipFeatures.rollout_guard. - Base layer and release file support in the templates and ApplicationSets.
fromComponentwiring inenvandsecretsentries.agent:block for service agents (below).- More guards with actionable messages (11 today; the target is every invariant the docs state in prose).
devClustervalidated against the cluster registry.
The agent: block (service agents use the application path)
A service agent is an ordinary application, so the chart gives its pods what an AgentRun gets:
agent:
enabled: true
definition: passenger-assistant # name of the AgentDefinition in git
network: clearance+model # narrow-only against the definition
sandbox: hardened # rejected on a cluster with no runtime class
It renders audience-bound projected tokens (clearance, model-proxy), a default-deny egress policy
with the two exceptions, the runtime-contract environment, and the hangar.io/agent labels. Same
guarantees, one implementation of the contract in two places (function-agentrun and the chart), kept
in step by a shared conformance test.
6. AppSpec and the planner
The parachute sentence, as the agent writes it (autopilot/examples/parachute.appspec.yaml):
apiVersion: airframe/v1
kind: AppSpec
app: parachute
stack: python
environments:
ground: [dev, test]
flight: [{name: staging}, {name: prod}]
config:
rollout: {strategy: canary, steps: [{setWeight: 100}]}
env: [{name: URL, value: "http://myendpoint.io"}]
plan compiles it, today’s Airframe, into:
| Step | Repo | Gate | Risk |
|---|---|---|---|
tenants |
gitops-cluster-dev-tenants | human merge | T2 |
repos-ready (wait) |
|||
app-repo: cicd.yaml patch, platform/envs/dev.yaml, test.yaml |
parachute | auto-merge | T1 |
tekton-resync |
parachute | auto-merge | T1 |
gitops: staging and prod values patches |
gitops-parachute | human merge | T2 |
ground-rollout (only because of the chart bug) |
parachute | auto-merge | T1 |
verify |
T0 |
With the A+ features on, ground-rollout disappears and the config is written once into a base layer.
Assumptions and warnings the planner surfaces instead of hiding: the only upper cluster is prod
so both flight environments go there; two upper clusters and no choice is a question, not a guess;
test collides with the test pipeline stage; a single 100% step promotes immediately.
7. Migration and safety
- Never edit shared things in place. Test-via-copy: chart and schema changes go through a scratch app first; a new composition through one XR’s
compositionRef. - Worktrees. Airframe, glidepath and apron have all been on the user’s branches before. Check the branch, work in a worktree, and leave tags to the user.
- Strictness in stages: warn, sweep the fleet (dev and prod), enforce.
- Multi-arch: everything new builds for arm64 and amd64.
- Unverified, check first: how ArgoCD applies XRs (are unknown fields rejected or pruned?), the git-generator
exclude, Helm and unknownx-keywords, and whether chart and schema changes need a chart version bump per your release process.
8. Run it
python3 tools/airframe-scorecard/scorecard.py # the grade
cd ~/tech/autopilot && ./.venv/bin/python -m pytest -q # 255 tests incl. the parachute planner