Skip to content

operator: make v1alpha1 CRD lifecycle executable#136

Draft
usr-bin-roygbiv wants to merge 15 commits into
LycaonLLC:mainfrom
usr-bin-roygbiv:agent/t4-op02-crd-lifecycle
Draft

operator: make v1alpha1 CRD lifecycle executable#136
usr-bin-roygbiv wants to merge 15 commits into
LycaonLLC:mainfrom
usr-bin-roygbiv:agent/t4-op02-crd-lifecycle

Conversation

@usr-bin-roygbiv

@usr-bin-roygbiv usr-bin-roygbiv commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

OP line item

OP-02 — CRD lifecycle. Depends on open blocker #135. This PR remains draft until authoritative Woodpecker proof is green.

Observable contract

  • Keeps exactly three namespaced cluster.t4.dev/v1alpha1 CRDs, each with one served: true, storage: true structural version and a status subresource.
  • Before any cluster access or mutation, cmd/crd-preflight checks every full compatibility fixture against the proposed structural/OpenAPI schema, Kubernetes CEL under create semantics (oldObject=nil), Kubernetes CEL under unchanged persisted-update semantics (oldObject=fixture), and pruning detection. This includes spec and status transition rules and preserves precise fixture field paths in failures.
  • A failed proposed-schema or server dry-run preflight performs zero non-dry-run CRD, existing-object, or Helm/workload mutation.
  • After additive CRD apply, retained Established=True is insufficient: served OpenAPI must converge to the proposed schemas before admission validation, exact status.storedVersions == [v1alpha1], or Helm --skip-crds may proceed.
  • No broad RBAC was added; discovery, dry-run, stored-version, rollback, and read-only module contracts are unchanged.

Red/green ledger

Migration and rollback

The documented future v1beta1 procedure does not create a new API now. It keeps v1alpha1 served for rollback, flips storage only after dual-version conversion proof, rewrites every object, verifies counts and reads through both served versions, and retires the old stored version only after rewrite gates pass. Current rollback remains additive and fail-closed: schema or dry-run failure leaves existing CRDs, persisted objects, and workloads untouched, with no force replacement.

Residual proof

No live Kubernetes credentials were available in this worktree, so HA apiserver/load-balancer behavior remains a live-cluster proof item. Repository and GitHub CI proof is executable and fail-closed. Woodpecker 38/95 is the explicit infrastructure blocker above.

Non-goals

No v1beta1, conversion webhook, storage-version change, product/runtime status, Helm CRD upgrades, force replacement, unrelated OP-03 behavior, or broad RBAC.

Review-blocker repair

The fixture-only migration gate has been replaced with a fail-closed live-object gate. Before any server dry-run or non-dry-run mutation, the lifecycle runner reads the three exact CRD definitions, lists every installed T4 CR across namespaces, and validates complete live objects—including status—with the existing cmd/crd-preflight OpenAPI, CEL create, CEL unchanged-update, and pruning engine. Absent CRDs remain a valid fresh-install case; definition-read denial, list denial, malformed data, incompatible live data, or cancellation stops before CRD/object/Helm mutation. Kubernetes integer values retain exact int64 fidelity. Served OpenAPI convergence and exact stored-version gates are unchanged; RBAC was not broadened.

@usr-bin-roygbiv
usr-bin-roygbiv force-pushed the agent/t4-op02-crd-lifecycle branch 2 times, most recently from 6262874 to c063782 Compare July 21, 2026 19:42
@usr-bin-roygbiv
usr-bin-roygbiv force-pushed the agent/t4-op02-crd-lifecycle branch from 35cf631 to 9681091 Compare July 22, 2026 01:44

@wolfiesch wolfiesch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking migration-safety issue: the preflight validates only curated fixture files, then applies the CRDs. Kubernetes CRD update validation does not enumerate and revalidate stored custom resources, so a live object containing an old-valid value omitted from the fixtures can be stranded by a narrowed schema. Please validate every live CR, including status/CEL/pruning behavior, before non-dry-run apply, or add a sound no-narrowing compatibility gate. I reproduced this with an enum narrowed to legacy-a: the fixture-only gate passed while a simulated persisted legacy-b object failed validation.

@usr-bin-roygbiv
usr-bin-roygbiv force-pushed the agent/t4-op02-crd-lifecycle branch 2 times, most recently from 75d36d4 to c228370 Compare July 22, 2026 04:14
@usr-bin-roygbiv
usr-bin-roygbiv force-pushed the agent/t4-op02-crd-lifecycle branch from c228370 to de9bf6f Compare July 22, 2026 04:17
@usr-bin-roygbiv

Copy link
Copy Markdown
Contributor Author

@wolfiesch — agreed: fixture-only validation was not a sufficient migration-safety gate. This is fixed at de9bf6f118476f1630b005544aefb64bc5264170.

  1. Before any server dry-run or non-dry-run apply, the lifecycle runner now reads each of the three exact CRD definitions and lists every installed namespaced T4 resource across all namespaces. An absent definition is handled only as the fresh-install case; CRD-read denial, object-list denial, malformed list data, or an incompatible object fails closed.
  2. Every returned live object, including status, is passed to cmd/crd-preflight objects. That command reuses the existing candidate loader and validateObject engine, so live data receives the same proposed OpenAPI validation, CEL create validation, CEL unchanged-update validation, and pruning detection as the curated fixtures. Kubernetes integer decoding preserves exact int64 values.
  3. The regression suite includes fixture-omitted live-only spec and status values, create and unchanged-update CEL cases, pruning cases, CRD-read/list denial, fresh-install absence, and cancellation. It asserts zero non-dry-run CRD/object apply and zero Helm invocation on each preflight failure.
  4. The served-version/OpenAPI convergence loop and exact status.storedVersions == [v1alpha1] gates are unchanged, and no RBAC manifest was broadened.

The tests-only red is eee9844c1f9a167388edc0e4a433128daefdd012: run https://github.com/LycaonLLC/t4-code/actions/runs/29889342817, cluster job https://github.com/LycaonLLC/t4-code/actions/runs/29889342817/job/88826550288. It failed exactly because no live enumeration/validation occurred and list denial did not stop the rollout.

The security-main-rebased final head is fully green: run https://github.com/LycaonLLC/t4-code/actions/runs/29890665063, cluster job https://github.com/LycaonLLC/t4-code/actions/runs/29890665063/job/88830336202, aggregate verify https://github.com/LycaonLLC/t4-code/actions/runs/29890665063/job/88830921128.

This resolves the requested blocking migration-safety issue. Please re-review the exact head and clear the requested-changes state so the PR can proceed through the existing merge path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants