|
| 1 | +# ADR-022: Per-Kind Artifact Maturity and the v1 Deprecation Policy |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Proposed. Amends [ADR-011](011-artifact-apiversion-policy.md): §4 (transition |
| 6 | +window) is replaced, §3 (compatibility gate) is extended to the catalog loader, |
| 7 | +and §1–§2 stand unchanged. Builds on [ADR-015](015-recipe-configuration-profiles.md), |
| 8 | +which already introduced kind-scoped version evolution as an amendment to ADR-011. |
| 9 | + |
| 10 | +## Problem |
| 11 | + |
| 12 | +Every AICR artifact carries an alpha `apiVersion`. ROADMAP |
| 13 | +[§2](../../ROADMAP.md#2-stability) promises a frozen, diff-gated surface at v1, |
| 14 | +and the Kubernetes convention that `v1alpha2` invokes — may be dropped or changed |
| 15 | +without notice — is the opposite of that promise. Two alpha versions coexist |
| 16 | +(`aicr.run/v1alpha2` for most kinds, `aicr.run/v1alpha3` for the profile-bearing |
| 17 | +`RecipeMetadata` and `RecipeResult`), which compounds it. |
| 18 | + |
| 19 | +Three questions have no recorded answer: |
| 20 | + |
| 21 | +1. **What `apiVersion` do artifacts carry at v1 GA?** |
| 22 | +2. **What does a bump owe the previous version?** ADR-011 §4 says dual-accept with |
| 23 | + a transition window. The `pkg/header` godoc says the opposite — a hard break |
| 24 | + with no window — and `IsSupportedAPIVersion` implements the godoc while |
| 25 | + `IsSupportedRecipeResultAPIVersion` implements the ADR. Both statements are |
| 26 | + unconditional, which is the actual gap: neither says *when* each applies. |
| 27 | +3. **What `apiVersion` does a newly introduced kind start at?** Nothing answers |
| 28 | + this, so new kinds guess. |
| 29 | + |
| 30 | +## Non-Goals |
| 31 | + |
| 32 | +- **REST path versioning.** ADR-011 scopes non-artifact `apiVersion`s as a |
| 33 | + non-goal and that stands. Whether the stable REST family is `/v1` or `/v2` is |
| 34 | + recorded separately under #2112. |
| 35 | +- **Tagging v1.0.0.** This ADR is prerequisite work, not the release. |
| 36 | +- **The CLI, REST, Go SDK, and bundle-layout freezes.** Those are #2111, #2112, |
| 37 | + #2113 under epic #2370. This ADR settles only what the artifacts are called and |
| 38 | + what a change to them owes consumers. |
| 39 | + |
| 40 | +## Decision |
| 41 | + |
| 42 | +### 1. Project v1 and artifact `v1` are separate axes |
| 43 | + |
| 44 | +AICR reaching v1.0.0 does not require every artifact kind to reach |
| 45 | +`aicr.run/v1`. ROADMAP §2 asks for a committed baseline, a CI diff-gate, and a |
| 46 | +deprecation channel. A gate over a `v1beta1` schema is a real gate: it catches |
| 47 | +*unintended* breakage, which is what the freeze promises. The maturity string |
| 48 | +governs *intended* breakage, and it is set per kind. |
| 49 | + |
| 50 | +This mirrors Kubernetes, which shipped 1.0 with beta APIs and serves beta |
| 51 | +alongside GA in every release since. |
| 52 | + |
| 53 | +### 2. Per-kind maturity map |
| 54 | + |
| 55 | +| Kind | Today | Target | Rationale | |
| 56 | +|---|---|---|---| |
| 57 | +| `Snapshot` | `v1alpha2` | `aicr.run/v1` | Settled shape; the first artifact an integrator reads | |
| 58 | +| `Recipe`, `RecipeCriteria` | `v1alpha2` | `aicr.run/v1` | 105 shipped overlays of exercise | |
| 59 | +| Bundle provenance (`localformat.ProvenanceAPIVersion`) | `v1alpha2` | `aicr.run/v1` | Rides in the bundle, which is what downstream integrates against | |
| 60 | +| `AICRConfig` | `v1alpha2` | `aicr.run/v1beta1` | Actively growing: #2026 bound 2 of 5 spec sections, #2245 binds the rest. Do not freeze a schema mid-expansion | |
| 61 | +| `RecipeMetadata`, `RecipeResult` | `v1alpha3` | `aicr.run/v1beta1` | Newest (ADR-015), 2 overlays, opt-in via profiles | |
| 62 | + |
| 63 | +The line: **whatever an integrator's code binds to must be at `v1`.** That is |
| 64 | +`pkg/client/v1`, the REST surface, `Snapshot`, `Recipe`, and the bundle. Local |
| 65 | +configuration and the profile-bearing kinds can sit at beta honestly. |
| 66 | + |
| 67 | +`v1alpha3` does **not** converge into the promoted version. `RecipeResult` stays |
| 68 | +distinct at beta, so `RecipeProfileAPIVersion` survives as the discriminator and |
| 69 | +the bidirectional profile/apiVersion validation in `pkg/recipe/profile.go` is |
| 70 | +unchanged. |
| 71 | + |
| 72 | +### 3. The bump happens now, as a hard break |
| 73 | + |
| 74 | +While every kind is alpha, a bump owes nothing (see §4). The 256 files carrying a |
| 75 | +version literal are regenerated in lockstep, exactly as |
| 76 | +[ADR-013](013-aicr-run-domain-migration.md) did, and no dual-accept path is |
| 77 | +carried forward. ADR-013 made this argument already: doing it before v1 "is the |
| 78 | +cheapest possible moment — there is no stable contract to honor yet." That |
| 79 | +argument expires at GA, which is why it is spent now. |
| 80 | + |
| 81 | +### 4. The deprecation window is conditional on the level being retired |
| 82 | + |
| 83 | +This **replaces ADR-011 §4**, whose dual-accept rule was stated unconditionally. |
| 84 | +The window owed is a function of the maturity of the version being removed: |
| 85 | + |
| 86 | +| Level being retired | Obligation | |
| 87 | +|---|---| |
| 88 | +| Alpha | None. May be removed in any release, no prior notice | |
| 89 | +| Beta | Readable for **2 releases** after deprecation | |
| 90 | +| GA | Not removed within a major version | |
| 91 | + |
| 92 | +The `pkg/header` godoc gains this condition. Its hard-break language is correct |
| 93 | +*for alpha* and must not be read as the general rule. |
| 94 | + |
| 95 | +**Two releases is deliberate, not inherited.** The Kubernetes equivalent is 9 |
| 96 | +months or 3 minor releases; at AICR's ~2-week cadence that would be ~18 releases. |
| 97 | +A short window means the read gate carries at most one retired version, which |
| 98 | +keeps the accept-known logic trivial. The cost is that a consumer who upgrades |
| 99 | +less than monthly can miss a window. |
| 100 | + |
| 101 | +### 5. Never deprecate toward a less stable version |
| 102 | + |
| 103 | +GA may replace beta and alpha. Beta may replace beta and alpha, never GA. Alpha |
| 104 | +may replace only alpha. This is what makes a mixed-maturity map safe: promoting |
| 105 | +`Snapshot` to `v1` is a one-way door for `Snapshot` and binds no other kind. |
| 106 | + |
| 107 | +### 6. Split each bump across two releases |
| 108 | + |
| 109 | +Release N adds the new version to the read gate. Release N+1 flips the emitter. |
| 110 | +A consumer who rolls back one release can still read what they generated. |
| 111 | + |
| 112 | +AICR has no conversion layer — a file is one version, take it or leave it — so |
| 113 | +this matters more here than in Kubernetes, where the apiserver converts between |
| 114 | +served versions. |
| 115 | + |
| 116 | +**This composes with §4 rather than duplicating it.** For a beta kind: N adds |
| 117 | +read support, N+1 emits the new version and deprecates the old, and the old stays |
| 118 | +readable through N+3. Three releases of overlap total. The two "2 releases" are |
| 119 | +different clocks and should not be conflated. |
| 120 | + |
| 121 | +### 7. A new kind starts on the current track |
| 122 | + |
| 123 | +A kind introduced today is stamped `aicr.run/v1alpha2` (or the post-bump |
| 124 | +equivalent once §3 lands), never a version the tree does not already accept. |
| 125 | +`aicr.run/v1alpha1` in particular has never been valid: ADR-013 moved the version |
| 126 | +to `v1alpha2` *at* the domain rename, so the legacy pairing was |
| 127 | +`aicr.nvidia.com/v1alpha1`. |
| 128 | + |
| 129 | +Every new kind gets a row in the §2 map and an entry in the read gate in the same |
| 130 | +change that introduces it. |
| 131 | + |
| 132 | +### 8. External data must be compatible with the binary reading it, and fails closed |
| 133 | + |
| 134 | +This **extends ADR-011 §3** to the catalog loader, which today checks `kind` only |
| 135 | +and never `apiVersion`. ADR-015 documented the consequence: a released binary |
| 136 | +pointed at a newer catalog silently resolves an unspecialized recipe. |
| 137 | + |
| 138 | +Silent divergence is the dangerous direction. The accept-known / reject-unknown |
| 139 | +gate applies per kind at every external-data boundary, including |
| 140 | +`--data` catalogs. An unknown `apiVersion` is an `ErrCodeInvalidRequest` naming |
| 141 | +the value, the expected value, and the remediation — never a silent downgrade. |
| 142 | + |
| 143 | +Consequence: catalog authors need a published statement of which binary versions |
| 144 | +accept which catalog versions. Loud breakage is the intent. |
| 145 | + |
| 146 | +## Consequences |
| 147 | + |
| 148 | +- Two version tracks exist at v1 — `aicr.run/v1` and `aicr.run/v1beta1` — instead |
| 149 | + of today's two alpha tracks. |
| 150 | +- `pkg/header` grows from two gates to roughly four, one per track, following the |
| 151 | + `IsSupportedRecipeResultAPIVersion` pattern ADR-015 established. |
| 152 | +- A one-time regeneration touches the 256 files carrying a version literal. No |
| 153 | + transition window is offered, and artifacts stamped with a prior group/version |
| 154 | + must be regenerated. This is the last release in which that is free. |
| 155 | +- `AICRConfig` and the profile-bearing kinds ship at beta and are *expected* to |
| 156 | + break after v1.0.0, which makes the deprecation channel (#2115) load-bearing |
| 157 | + rather than a documentation task. |
| 158 | +- A catalog that declares a kind an older binary does not know now fails loudly |
| 159 | + where it previously resolved something plausible and wrong. |
| 160 | + |
| 161 | +## References |
| 162 | + |
| 163 | +- [ADR-011](011-artifact-apiversion-policy.md) — artifact `apiVersion` policy and compatibility gate |
| 164 | +- [ADR-013](013-aicr-run-domain-migration.md) — `aicr.run` domain migration, the precedent for a pre-v1 hard break |
| 165 | +- [ADR-015](015-recipe-configuration-profiles.md) — recipe configuration profiles, which introduced kind-scoped evolution |
| 166 | +- [ROADMAP §2 Stability](../../ROADMAP.md#2-stability) |
| 167 | +- [Kubernetes deprecation policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) |
0 commit comments