Proof

Four claims are proven on a real cluster: the marker itself, the sweep that finds a deleted block’s object by it, the admission policy that fences a write by it (through which claim 13’s Kubernetes cell is proven too), and a custom resource bound by the natural key inside its manifest, carrying the label and swept by it. The rest are stated per claim in the claims data rather than left implicit. The table below shows only the claims whose Kubernetes cell is not still open; hover a cell for its note.

#ClaimRun it~minAWSKUBERNETES
1Owned resources cannot fall out of plans unnoticedjust smoke no-silent-orphans2provenproven
7Identity is a tag you can read and movejust smoke identity-is-a-tag3provenrestated
11A count pool is a fungible setjust smoke count-is-a-fungible-set2provenn/a
12Carve by retagjust smoke carve-by-retag needs Go6provenproven
13The tag is the boundaryjust smoke the-tag-is-the-boundary4provenproven
14A plan costs its estate, not its accountjust smoke plan-cost-tracks-the-estate2provenrestated
21The marker is a label: one tofu-estate label on a real clusterjust smoke k8s-greenfield2n/aproven
22No silent orphans on Kubernetes: a deleted block's object is found by its label and a controller's copies are never touchedjust smoke k8s-no-silent-orphans3n/aproven
23The label is the boundary: one admission policy fences every write to an estate's objects, and live-mv -from-estate is the governed relabeljust smoke k8s-the-label-is-the-boundary4n/aproven
24A custom resource binds by its natural key, carries the estate label and is swept by it, a block whose CRD the cluster does not serve is refused by name, and the plan carries the API server's own dry-run verdict on every planned object: a kubernetes_manifest block is found again by the apiVersion, kind, namespace and name inside its manifest, its object is created with tofu-estate in metadata.labels, an object whose block is removed is found by the sweep and proposed for removal, before the CRD is installed the plan refuses the block naming the kind, the apiVersion and the CRD to install, and every planned create or update is sent to the server with dryRun=All and its acceptance printed above the planjust smoke k8s-custom-resource3n/aproven

In CI

The four Kubernetes claims run on a kind cluster in GitHub Actions on every pull request that touches the Kubernetes surface, each with its BREAK=1 control, and the nightly gauntlet re-measures the kubernetes lane’s estates on the same cadence as the AWS rows (#1080). A Kubernetes verdict on this site is no longer only a laptop’s word.

The harness

just smoke k8s-greenfield

The AWS claims run against a local emulator in Docker. This runs against a kind cluster in Docker, which is a real API server, so the same scenario shape carries over without the emulator-fidelity question that stopped a second cloud: a verdict line per step, exit 0 only when every claim held, BREAK=1 manufacturing the fault. The scenario applies a namespace and a ConfigMap under a live block with no AWS provider anywhere, reads the tofu-estate label back with kubectl, replans empty, loses its cache without consequence, and destroys exactly; its BREAK=1 strips the label and requires the replan to propose restoring it (claim 21). Claim 22 runs the sweep on the same harness, and claim 23 runs the gate: two ServiceAccounts, two estates, a plain kubectl label refused by the API server across the boundary, and a carve by relabel the policy governs, with BREAK=1 removing the policy. Claim 24 runs a custom resource through the whole of it on a CRD the scenario installs: refused by name while the CRD is missing, bound by the key inside its manifest, labelled on create, dry-run against the server before the apply, restored when the label is stripped, and swept when its block is removed.

The gauntlet lane

kubernetes lane
2 of 2 estates behave like OpenTofu
  • Cold deploy: 2 pass, 0 fail, 0 not run
  • Migrate: 2 pass, 0 fail, 0 not run
  • Replan from nothing: 2 pass, 0 fail, 0 not run
  • No-op apply: 2 pass, 0 fail, 0 not run
  • Drift and reconverge: 2 pass, 0 fail, 0 not run
  • Rename: 2 pass, 0 fail, 0 not run
  • Remove a block: 2 pass, 0 fail, 0 not run
  • Change count: 2 pass, 0 fail, 0 not run
  • Replace with create_before_destroy: 0 pass, 0 fail, 0 not run, 2 n/a on this substrate
  • Crash between create and destroy: 0 pass, 0 fail, 0 not run, 2 n/a on this substrate
  • Teardown: 2 pass, 0 fail, 0 not run
  • Plan, review, apply: 2 pass, 0 fail, 0 not run
  • Greenfield apply: 2 pass, 0 fail, 0 not run
  • Strict profile: 2 pass, 0 fail, 0 not run

Behaviors proven: 1 of 14 stages have their representative-set fixtures passing against the tier-1 behavior matrix - a development-loop signal for contributors, not a coverage claim; it neither substitutes for the bars above nor gates anything on this page.

Estates above were last measured against different emulator pins: 25 against ghcr.io/lex00/floci@sha256:9ec3fa649177f64c17c299e3fd799cc774cc1e1b67db2bc20e27d1bc98d7c264, 1 against ghcr.io/lex00/floci@sha256:a39185cc3971d0188663d61043cb038dff1260d8a975b1aa72c4e2bb1feac3cb, 3 against ghcr.io/lex00/floci@sha256:0bbeb43075c9df9c7e06311cd4eec99a354594d304faa4fe5899b494a009d23d. The current pin is ghcr.io/lex00/floci@sha256:0bbeb43075c9df9c7e06311cd4eec99a354594d304faa4fe5899b494a009d23d; a row not measured against it is stale evidence, not a failure.

The kubernetes lane (#1067) runs the same fourteen stages as the AWS lanes against a kind cluster created for the run, and counts toward its own bar, never toward the AWS ones. Two stages do not apply on Kubernetes and read n/a rather than being skipped silently: a replacement under create_before_destroy, and the crash between its create and its destroy, because a Kubernetes name is unique within its namespace and nothing can be created before the object it replaces is gone. Every other stage says in live/GAUNTLET.md how it reads on the kind substrate. Two estates run in it: reference-k8s, a hand-written shape kept in this repository, and corpus-quickpizza, Grafana Labs’ own published deployment root for their QuickPizza demo application at a pinned tag - 26 objects over eight kinds, real images, no cloud provider - crossed with the same deltas every AWS estate gets for its emulator and one more for the Grafana Cloud token the run does not have. The published root found a real gap on the way in: every one of its namespaced objects reads the namespace’s id, which identity resolution refused until the object-metadata rule learned that the provider’s id is the object’s own import id.

What it would cost

What follows is the shape, from the API’s own properties, and it is what every Kubernetes claim and the lane’s estates run through; the call counts have not been tabulated the way the AWS scale page tabulates them.

The sweep

On AWS the estate sweep is a single GetResources call, filtered server-side on the marker, covering the whole admission table at once. Kubernetes has no cross-kind label-filtered list. A sweep there is API discovery (/api and /apis, which say every kind the cluster serves) and then one cluster-wide, label-selected list per kind the cluster serves with list and delete verbs, custom kinds included - not one per kind per namespace, as the design first estimated: a namespaced kind lists across every namespace in one call.

Two things survive. A label-selected list returns only the estate’s objects and does not grow with the cluster, so “a plan costs its estate, not its account” holds in weakened form. And because the universe of kinds is asked rather than tabulated, an admitted type the generated table did not know about cannot be owned, orphaned and unreachable. live-ls DIR is the same listing printed as an inventory, each object joined to the block that declares it (claim 21).

What does not survive is “one call”, and the claims page marks claim 14 restated rather than pretending otherwise.

The read pass

Reading each declared object is one GET per object, as it is for stock. Server-side dry run validates, defaults and runs admission without persisting, which no AWS plan can do; the plan sends every planned kubernetes_manifest create or update that way and prints the server’s answer above the plan, one more request per such object (Adopt, “What Kubernetes does better”).