Airframe · Review · Crossplane and the agent API

How well does Airframe use Crossplane, and can an agent drive it?

Two weeks after the architecture review I asked for a narrower one: just Airframe, my service catalog, read the way an agent would read it. Is the Crossplane layer built well, and could an agent discover, change and verify things through it without a human translating? Twenty findings came back. Fifteen were fixed and live within two days.

Review October 8, 2026Scope airframe, with autopilot and hangar for context; both clusters checked read-onlyFollows the Sep 27, 2026 architecture reviewActed on Oct 8 and 9, 2026
15 of 20 fixed and live · Oct 9, 2026

A healthy fleet with structural debt, and an agent API that was half built.

Every catalog XR on both clusters was Synced and Ready, and the compositions in git matched their templates. The problems were structural: six near-identical app compositions with no tests, credentials passing through the function pipeline, readiness decided by hand in every template, and one-shot scaffolding reconciled forever.

On the agent side, the contract half was ahead of most platforms: a strict schema, rule ids with fix hints, and closed reason sets. The runtime half was missing. Verify steps were English sentences, the planner wrote an API group that no longer existed, and a typo in an XR was silently dropped.

Twenty findings, two days later

Twenty findings and their outcomeThirteen Crossplane findings and seven agent API findings. Fifteen are fixed and live, two are partly done (C9 and A3), one is deferred (A5), and two are open (A4 and A6).PART 1 · CROSSPLANE · 13 FINDINGSC1highC2highC3highC4highC5medC6medC7medC8medC9medC10lowC11lowC12lowC13lowPART 2 · THE AGENT-FACING API · 7 FINDINGSA1highA2highA3highA4medA5medA6medA7lowFixed in two days: 15 of 20.LEGENDfixed, livepartly donedeferredopen
Figure 1Every finding, coloured by what happened to it. The four still not done are about governance and vocabulary, not correctness.

What it found, and what happened

C1 · A Dex client secret the server doesn't hold

Fixed

If an app's Secret was deleted, the function wrote a fresh secret that Dex never accepted, and still reported Ready. Now it adopts the existing one; proved by deleting a Secret and watching it come back identical within a second.

C2 · Credentials rendered into managed-resource specs

Fixed

Two credentials travelled through the function pipeline into plain specs in etcd. The provider now takes a secret reference, and the copies happen server side, so no credential value enters a composed manifest.

C3 · Six cloned compositions, zero composition tests

Fixed

One build script with a check in CI regenerates every composition from shared templates, and a render harness compares 77 cases against committed expectations. Two renderers agree.

C4 · Reconcile forever for one-shot scaffolds

Fixed

"Scaffold once, reconcile never" became a ledger on the XR's status. GitHub file resources fell from 161 to 60, and every scaffolded file kept the same content, checked file by file.

C5, C6 · A condition that was always False, and a silent default

Fixed

WorkloadDeployed could never be True from where it was computed; it's gone. A misspelled app name now says AppNotFound instead of quietly pointing at a NodeJS app.

C7 · Readiness decided by hand in every template

Fixed

Readiness rules are declared once, as CEL per resource kind, and the per-template annotations are gone. One of them had been wrong the other way.

C8 · Operator webhooks flipping every XR

Fixed

The root cause was leader-election loss on single-replica operators. Two replicas, budgets and longer leases took webhook failures from one per restart to zero in 444 applies.

C9 · Security hygiene

Partly

Endpoints and the org id moved out of templates into the cluster registry. TLS on two internal links is skipped for now, by decision.

C10 to C13 · Event churn, a probe timeout, version pins, XRD API hygiene

Fixed

All four done: events only on change, one version pinned everywhere, default compositions, name-length bounds and printer columns on all twenty XRDs.

A1 · The planner was stale on both outputs

Fixed

Autopilot's planner wrote a retired API group and a cicd.yaml shape Glidepath no longer accepted, so the parachute sentence could not pass. Fixed, with a test against the live fleet file.

A2 · The XR write path accepted typos silently

Fixed

Server-side apply at sync, and a pre-merge --xr validator with did-you-mean hints. See figure 2.

A3 · The observe and verify half was prose

Partly

Verify steps are now structured checks that airframe-verify runs read-only, and every XRD has a machine-readable sidecar tested in CI. Status outputs on the XR wait until something needs them.

A4, A6 · Conditions and errors aren't one vocabulary

Open

Values-file errors come back with a rule id and a hint; XR-side failures still come back as template strings.

A5 · AI triage bypasses governance

Deferred

Routing HolmesGPT's fix PRs through Clearance needs Clearance to be running, and it exists only as code today. Triage is switched off meanwhile.

A7 · Discoverability

Fixed

An AGENTS.md for every kind, generated from the sidecars, and every chart field carries an owner and a risk class. The scorecard moved from 74 to 82.

One fix, drawn

Closing the XR typo holeAn XR committed to git is validated against the XRD schema in pre-merge CI, then Argo CD applies it with server-side apply, so the API server rejects any field the schema does not declare.AN XR REQUEST, FROM COMMIT TO CLUSTERXR in gittenants repoNEWvalidate --xrpre-merge CINEWArgo CDserver-side applyAPI serverrejects unknown fieldsBefore: client-side apply let the API server prune a misspelled field, and the XR reported healthy.After: the typo fails twice, once in the PR with a did-you-mean hint, and again at sync.
Figure 2A2: a scratch XR with a misspelled field failed its sync and created nothing. The same mistake is now caught before merge.

What I took from it

Lesson

Every bug was found live

Before this, every readiness, naming and policy bug in the git history was found on a cluster. The render harness is the change that moves them to a pull request.

Lesson

Root cause before the fix

The webhook outages looked like an Airframe problem. They were operators losing leadership on a busy node, and the fix was in the cluster, not the catalog.

Lesson

A contract isn't an API

A strict schema tells an agent what it may write. It also needs to know what happened afterwards, in a form it can check. That's the half still being built.