Skip to content

docs(api): move the CiliumFQDN/CalicoFQDN removal floor to v2.0.0 - #1867

Merged
karlkfi merged 6 commits into
mainfrom
claude/q452-29ed0a
Sep 8, 2026
Merged

docs(api): move the CiliumFQDN/CalicoFQDN removal floor to v2.0.0#1867
karlkfi merged 6 commits into
mainfrom
claude/q452-29ed0a

Conversation

@karlkfi

@karlkfi karlkfi commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

What

v2beta1 is no longer served past v2.0.0, and the deprecated
CiliumFQDN/CalicoFQDN aliases go with it.

This announces that removal; it does not perform one. Nothing on the
published API surface changes here — api-surface-since.sh v1.7.0 reports no
added fields, no changed defaults, and "Added or changed enum constraints:
(none)"
. The enum is still CIDR;FQDN;CiliumFQDN;CalicoFQDN, admission still
warns rather than rejects, and every object valid before this PR is valid after
it. The break lands at v2.0.0, two releases out. Typed docs(api) to match
30399a0b0,
which named the v3.0.0 floor for the same two values.

This PR was drafted pending the Q1082 decision, which is now made: v2.0.0 is a
major release, and dropping a served version is a breaking change a major
release may make. Q452 follows from it and lands with the opposite answer to
the one this branch previously argued — GA v2 omits the aliases.

Three pieces:

The decision, recorded. v2beta1-retirement.md
gets the outcome and keeps the pre-decision analysis as taken, so the cost that
was accepted stays legible. v2-ga.md § Phase 2 records
Q452's answer and Phase 3 gains v2beta1 as a fourth removal.

The published floor moves from v3.0.0 to v2.0.0. Q428's reasoning was
sound at every step — an API element is removed by incrementing the version, so
the aliases live exactly as long as v2beta1 does — and only its input changed.
Every site stating the derived floor now states v2.0.0: the enum godoc in both
v2 versions, the regenerated CRD descriptions and chart templates, the admission
warning and its test, and the operator, design and plan docs.
api-review.md keeps the worked example and
gains the lesson — state a floor with what it is derived from, so the next
reader can tell which half a change invalidates.

The operator instruction changes. Migrating stops being optional. v2 cannot
represent an object naming an alias, and the conversion contract cannot report
one object as absent: convertedObjects must match the input list in size and
order, Result is a single Status for the whole request, and on failure
convertedObjects is empty. ConversionRequest.Objects is a list, so one
unmigrated EgressProxy breaks kubectl get egressproxies at v2 for the
cluster rather than for itself.

Backlog

Q452 and Q1082 close. Q1085 is new and carries what the decision puts on the
critical path: the release-notes deprecation notice, an admission change from
warn to reject on new alias writes, and the pre-upgrade check. Q1068 is
corrected rather than closed — v2beta1 now goes at the same tag as v2alpha1,
so the un-matched rules leave v2 as the only served version, and the alias
warning that surfaced the defect dies with the aliases instead of riding into
the gap.

What this does not do

Rule 4b is untouched by semver: the release before v2.0.0 still has to serve
v2beta1 and v2 side by side. That overlap, the storage migration and Q1085's
notice all remain ahead, and none of them is in this PR.

The ladder gains a 1.9 rung

Raised while reviewing the above: the v2-ga plan could not deliver v2.0.0 as
written.
Phase 2 was "add v2 to each kind, mark it storage" and Phase 3
"storage migration, then drop", both inside the v2.0.0 cut, and the ladder
ran 1.8 → 2.0 with nothing between. That advances the storage version in the
same release that introduces v2, which Rule 4b does not allow.

The gap predates this PR's decision and is sharpened by it: before, an operator
upgrading to v2.0.0 still had v2beta1 served, and now they do not.

Rule 4b is a convention rather than something the apiserver enforces, so
release-ladder.md records what it is worth
here rather than citing it. Once stored objects are rewritten as v2, a cluster
cannot return to a release whose CRDs lack v2; with no overlap release that
destination is v1.8.0, making the v2.0.0 upgrade the one with no way back —
on the largest upgrade this project asks anyone to make. The overlap is also the
only place the v2beta1v2 conversion edge runs before it is mandatory,
which Phase 1's soak does not cover because that conversion does not exist yet.

So:

Release Carries
1.8 soak evidence (Q1059, Q1060) and the v2beta1 deprecation notice this PR ships
1.9 the overlap: v2 served beside v2beta1, storage unchanged. Phase 2 minus the storage marker, plus Q1085's reject and pre-upgrade check
2.0 storage advances to v2, objects migrate, then four removals

1.9-gate is declared and Q413 carries it, since Phase 2 is 1.9's whole
content. Q1085 moves from 2.0-gate to 1.9-gate: 1.9 is the first release
serving v2, so it is the first in which a conversion that cannot represent an
alias can be requested. 1.8 remains the better target for the reject and the
check, and the row says so rather than treating the deadline as the plan.

Q413 is deliberately not split. Its phases now land in different releases,
but the split is only useful once the soak closes and someone picks the work up.

Tracking audit: four gaps closed

An audit of what actually blocks each release (205 rows) found only three
carried a gate label, and the picture was incomplete in four places:

  • The storage migration had no row at all. v2-ga.md Phase 3 opens by
    marking v2 storage and rewriting stored objects, and the mechanism does not
    exist — StorageVersionMigration and storedVersions appear nowhere outside
    vendor/. It was one sentence in a plan doc, tracked nowhere. Now Q1086,
    sized L rather than the M the phase table implies, because the mechanism
    choice is unmade.
  • Q273 and Q264 blocked v2.0.0 without 2.0-gate. They are Phase 3's
    content, so a query for what blocks 2.0 returned one row of four. Being
    deferred was not the reason: Q413 is deferred and carries 1.9-gate.
  • 1.9 had no plan doc, though every rung from 1.0 to 1.8 has one. Now
    release-1.9.md, including a what this release
    must NOT do
    section: marking v2 storage is the one way to ship 1.9 and
    still not satisfy Rule 4b, and Phase 2's step 1 still carries that marker.
  • The v2-ga.md index row was stale — still recording Q452 as v2 defining
    both aliases, the answer this PR flips.

Q1087 is the gate that would have caught two of these: nothing binds a
gate-labelled row to its release plan in either direction. check-release-ladder
binds only the punted table to status:, and roadmap-check rule 7 fires only
for feature/security rows, leaving every debt, ci, tests and
milestone blocker unbound. The row names the hard part rather than assuming
it: the label-to-plan mapping is not a filename template, since 2.0's plan is
v2-ga.md, so it has to be read off the ladder table.

Testing

make check, make docs-gates and make docs-build green. make manifests,
make chart-crds and make api-reference regenerated; the warning test now
asserts v2.0.0 and asserts v3.0.0 is absent, since naming the next major
would understate the removal by a whole release.

karlkfi added a commit that referenced this pull request Sep 8, 2026
…estion

Review of #1867 refuted Q1068's one-line fix. Listing the request's own
version in a webhook rule stops the apiserver converting, so a v2beta1
object reaches a v2alpha1-typed handler and DecodeInto rejects it; under
failurePolicy: Fail that turns a silent loss of validation into every
write being rejected. The row now says so, sizes M, and names the envtest
that settles the apiserver half.

Q1076 records the option Q452 did not consider: a CEL transition rule on
the v2 schema alone, admitting an alias only where oldSelf carried one.
It has to be settled before the tag, and its feature-gate premise is
unmeasured.
karlkfi added a commit that referenced this pull request Sep 8, 2026
The third carriage bullet claimed the annotation carrier could not be
inert because the reconciler would have to read it. Both halves are
false: the GMC reconciles the spoke, so the conversion restores the
value into the typed field and no controller names the annotation, and
the RunnerSet annotations carry the same hand-settable hazard the bullet
ascribed uniquely to the hypothetical FQDN one.

The decision is unchanged and the real objection is stated instead:
extending a known unvalidated-annotation hazard to an egress-enforcement
backend, which is a weighting rather than a mechanical bar.

Also records the ratcheting question as considered (Q1076), pre-empts
the hub-versus-storage escape, states the carry-the-string cost as the
read-modify-write break it actually is, and replaces a forecast of
project intent in the operator notice with the committed fact.
karlkfi added a commit that referenced this pull request Sep 8, 2026
Q1076 claimed both transition rules exist in both v2 versions. Measured:
actionsgateway_types.go carries githubURL's in v2alpha1 and v2beta1;
runnerset_types.go carries acquisitionProtocol's in v2alpha1 only,
because v2beta1 dropped the field. That is the same fact the annotation
carrier exists for, so the row now says which is which.

Drops the clause naming an earlier draft of the alias decision. The repo
squash-merges, so the commit it points at does not reach main and a
reader of the merged doc cannot find it. The substance it introduced --
that the objection is a weighting rather than a mechanical bar -- stays.
@karlkfi
karlkfi marked this pull request as ready for review September 8, 2026 05:18
@karlkfi
karlkfi marked this pull request as draft September 8, 2026 05:44
@karlkfi karlkfi changed the title docs(plan): GA v2 defines the deprecated FQDN aliases (Q452) feat(api)!: v2beta1 and the FQDN aliases are removed at v2.0.0, not v3.0.0 Sep 8, 2026
@karlkfi
karlkfi marked this pull request as ready for review September 8, 2026 08:37
…itical path

Q1082 is decided: v2beta1 is not served past v2.0.0, because v2.0.0 is a
major release and a major release may make breaking changes. Q452 follows
from it and closes with the opposite answer to the one it was heading for:
v2 omits the deprecated FQDN aliases.

Q1085 carries what the decision puts on the critical path and Path 1 would
not have needed: the formal v2beta1 deprecation notice in the release notes,
an admission change from warn to reject on new alias writes, and the
pre-upgrade check for unmigrated objects. The check is the load-bearing
piece, because one object naming an alias fails the conversion request it is
batched into and breaks LIST at v2 cluster-wide.

Q1068 is corrected rather than closed. v2beta1 now goes at the same tag as
v2alpha1, so the un-matched rules leave v2 as the only served version, and
the alias warning that surfaced the defect dies with the aliases instead of
riding into the gap. The other four kinds' validators are what is at risk.

Q1083 keeps its own scope: the notice period is release-count only either
way.
Announces a removal; does not perform one. Nothing on the published API
surface changes here: api-surface-since.sh v1.7.0 reports no added fields, no
changed defaults, and "Added or changed enum constraints: (none)". The enum is
still CIDR;FQDN;CiliumFQDN;CalicoFQDN, admission still warns rather than
rejects, and every object valid before this commit is valid after it. The
break lands at v2.0.0, when the versions defining the aliases are removed.

Follows docs(api) at 30399a0, which named the v3.0.0 floor for the same two
values. This moves that floor rather than introducing a new kind of claim.

Q1082 decided that v2.0.0 stops serving v2beta1: it is a major release, and
dropping a served version is a breaking change a major release may make. Q452
follows. The aliases are enum members of v2alpha1 and v2beta1, v2.0.0 removes
both, and the GA v2 version does not define them, so they go at v2.0.0 and GA
is born clean.

The floor moves with the premise, not with the rule. Q428's reasoning was
sound at every step: an API element is removed by incrementing the version, so
the aliases live exactly as long as v2beta1 does. The input changed. Every site
stating the derived v3.0.0 floor now states v2.0.0: the enum godoc in both v2
versions, the generated CRD descriptions and chart templates, the admission
warning, and the operator, design and plan docs. api-review.md keeps the worked
example and gains the lesson: state a floor with what it is derived from, so a
reader can tell which half a change invalidates.

The operator instruction changes with it. Migrating stops being optional,
because v2 cannot represent an object naming an alias and the conversion
contract cannot report one object as absent: convertedObjects must match the
input list in size and order, Result is a single Status for the request, and on
failure convertedObjects is empty. One unmigrated EgressProxy therefore breaks
kubectl get egressproxies at v2 for the cluster, not for itself.

v2beta1 joins the v2.0.0 removal table for the cost reason the other three
share rather than a mechanism they share: one breaking migration instead of
two. Rule 4b is untouched by semver, so the release before v2.0.0 still serves
v2beta1 and v2 side by side.

The two records that stated the old floor as settled are annotated rather than
rewritten: what 1.3 announced is what 1.3 announced.
The v2 graduation now spans two releases: Phase 2 serves v2 beside v2beta1
in 1.9, and the storage advance plus the removals stay at v2.0.0. Q413 owns
Phase 2, so it blocks the 1.9 tag rather than the 2.0 one.

Q1085's deadline is 1.9 for the same reason: that is the first release in
which v2 is served, so it is the first in which a conversion that cannot
represent an alias can be asked for. 1.8 is the better target for the reject
and the check, and the row says so rather than treating the deadline as the
plan.

Q413 is not split. Phase 2 and Phase 4 land in different releases now, but
the split is only useful once the soak closes and someone picks the work
up.
… Phase 2

v2-ga.md had Phase 2 add v2 and mark it storage, with Phase 3 migrating and
dropping the superseded versions, both inside the v2.0.0 cut. That advances
the storage version in the same release that introduces v2, which Rule 4b
does not allow, and the ladder ran 1.8 to 2.0 with no release in between.

The gap predates the v2beta1 retirement decision and is sharpened by it:
before, an operator upgrading to 2.0 still had v2beta1 served, and now they
do not.

Rule 4b is a convention rather than something the apiserver enforces, so the
ladder records what it is worth here instead of citing it. Once stored
objects are rewritten as v2, a cluster cannot return to a release whose CRDs
lack v2. With no overlap release that destination is v1.8.0, which would
make the v2.0.0 upgrade the one with no way back, on the largest upgrade the
project asks anyone to make. The overlap is also the only place the
v2beta1-to-v2 conversion edge runs before it is mandatory; Phase 1's soak
validates v2beta1's shape and says nothing about a conversion that does not
exist yet.

So 1.9 serves both and moves no storage version, and the storage marker
moves from Phase 2 to Phase 3.
An audit of what blocks each release found three tracking gaps and one
missing row.

Q1086 is the missing row. v2-ga.md Phase 3 opens by marking v2 storage and
rewriting stored objects, and Phase 4 cuts on top of that, but the mechanism
does not exist: StorageVersionMigration and storedVersions appear nowhere
outside vendor/, measured across the tree. It was recorded in one sentence of
a plan doc and tracked nowhere. Sized L rather than the M the phase table
implies, because the mechanism choice is unmade and the evidence half is
unbuilt.

Q273 and Q264 gain 2.0-gate. They are Phase 3's content, so a query for what
blocks v2.0.0 returned one row of four. Being deferred was not the reason
they lacked the label: Q413 is deferred and carries 1.9-gate.

Q1087 is the gate that would have caught this. Nothing binds a gate-labelled
row to its release plan in either direction; check-release-ladder binds only
the punted table to status, and roadmap-check rule 7 fires only for feature
and security rows. The row names the hard part rather than assuming it: the
label-to-plan mapping is not a filename template, since 2.0's plan is
v2-ga.md, and it must be read off the ladder table.
…x row

The 1.9 rung existed as one ladder row pointing at v2-ga.md Phase 2, so the
overlap release had nowhere stating its scope, its Definition of Done or its
gating rows, which every rung from 1.0 to 1.8 has.

release-1.9.md carries the rollback argument for why the rung exists, the
scope ledger, and a section on what the release must NOT do: marking v2 the
storage version is the one way to ship 1.9 and still not satisfy Rule 4b, and
Phase 2's step 1 still carries that marker.

Its Definition of Done makes the Rule 4b evidence a deliverable of this tag
rather than something v2.0.0's pre-flight is expected to re-derive, since a
later release cannot establish that an earlier one served both versions.

The v2-ga.md index row was stale: it still recorded Q452 as v2 defining both
aliases, the answer that flipped. It now records the omission, names the
1.9 split, and lists all four Phase 3 rows rather than two.

release-1.8.md gains Q1085's notice in its scope ledger. The docs half
shipped with Q452, which is what makes 1.8 the one-release-ahead
announcement; what it still owes is the release-notes line.
@karlkfi karlkfi changed the title feat(api)!: v2beta1 and the FQDN aliases are removed at v2.0.0, not v3.0.0 docs(api): move the CiliumFQDN/CalicoFQDN removal floor to v2.0.0 Sep 8, 2026
@karlkfi
karlkfi added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 3612faf Sep 8, 2026
75 checks passed
@karlkfi
karlkfi deleted the claude/q452-29ed0a branch September 8, 2026 22:43
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.

1 participant