Documentation
¶
Overview ¶
Package compat implements publish-side catalog compatibility logic: enhancement 0010 D27's additive-only comparison walk (Check, CheckAtLevel), the D34 contract-level ladder (Level, ParseLevel, CompareAPIVersions), D9 predecessor selection (HighestStable), and the D30 provenance strip (StripProvenance).
The package is pure logic: no I/O, no registry access, no schema-cache dependency, no state. Callers compose it with member enumeration, predecessor pulling, and gate policy. Its consumers are the 0011 publish gate (`opm catalog publish`), `opm catalog registry check --compat` (0011 D7), and library-matching's D34/D30 reads (contract-key ordering, unify-rung strip).
Index ¶
Constants ¶
const ( KindFieldRemoved = "field removed" KindFieldAddedStrict = "field added without optional or default" KindDefaultChanged = "default changed" KindDefaultRemoved = "default removed" KindDomainNarrowed = "domain narrowed" )
The violation kinds. KindDomainNarrowed carries the CUE subsumption diagnostic verbatim in New (no reformatting, consistent with UnifyError); the default kinds carry the rendered defaults in Old/New.
Variables ¶
var ( ErrUnparseableAPIVersion = errors.New("unparseable apiVersion") ErrNotStruct = errors.New("operand is not a struct") )
Sentinel errors for CheckAtLevel's error channel. A gate that cannot classify its input has not found an incompatibility — these are failures, never violations.
Functions ¶
func CompareAPIVersions ¶
CompareAPIVersions is a total, transitive ordering over apiVersion strings, following the Kubernetes kube-aware ordering: level first (alpha < beta < GA), then major, then the alpha/beta number. It returns a negative value if a sorts before b, zero if they tie, positive otherwise.
This exists because SemVer cannot order the ladder — measured against Masterminds v3, a per-pair rule switch makes v1alpha1 < v2, v2 < v10 and v10 < v1alpha1 all true at once (0010 D34). Strings outside the grammar sort before every valid one, lexically among themselves; that branch keeps the ordering total and is not part of the contract.
func HighestStable ¶
HighestStable returns the highest published stable (non-pre-release) version. published is the registry's `v`-prefixed, SemVer-ascending list. Pre-release tags (e.g. v0.6.0-dev.*) are skipped so selection lands on the latest *released* build. If no stable version exists, the highest overall is returned so a pre-release-only path still resolves. Unparseable entries are skipped.
This is the FLOAT selector — "give me the latest released build" — and it is deliberately NOT the compatibility gate's predecessor selection. An earlier revision of this comment claimed it was; 0011 D23 (amending D9) corrected that: the publish gate's predecessor is found by D9's literal rule — scan published versions strictly below the effective version, same major, prereleases included, newest first — implemented gate-side in the CLI, because a stable-preferring selector coincides with that rule only on a prerelease-only history and would miss breaks that prerelease pinners (0010 D14-blessed) can see. Selection here is pure — enumerating and fetching the candidate are the caller's. Moved verbatim from opm/materialize's since-deleted filterVersions path (0010 D14). Its first true caller is template resolution's version selection (cli-template-modules), which is why it stays.
func StripProvenance ¶
StripProvenance removes metadata.catalogVersion and metadata.description from v — the D30 strip both the compat gate and library-matching's unify rung apply before comparing values from different catalog builds. Without it every comparison reports a violation, because catalogVersion differs between any two releases by construction.
The mechanism is a syntax round-trip: Syntax(cue.All(), cue.InlineImports(true)) → delete the denylisted fields from every metadata block → rebuild in v's own context. The strip reaches the definition as well as the instance — removing only the instance's field would leave a required `catalogVersion!` nothing satisfies, a shape Validate(cue.Concrete(false)) cannot flag if missed. InlineImports makes the emitted syntax self-contained, so values whose definitions come from imported packages rebuild without their loader. Known cost, accepted by D30: the round-trip discards document positions, so downstream CUE messages on the stripped value lose file/line — the comparator's own violations are path-located by the walk, not by CUE positions.
Types ¶
type Level ¶
type Level int
Level is a contract apiVersion's position on the Kubernetes ladder (enhancement 0010 D34): vNalphaM → vNbetaM → vN. The level decides whether D27's additive-only promise binds — see Level.Enforced.
func ParseLevel ¶
ParseLevel classifies a contract's apiVersion on the D34 ladder and reports its major. ok is false when the string is outside #APIVersionType's grammar ("v1alpha", "V1", "1.2.0"); D34 keys enforcement to the primitive's own apiVersion, never to the catalog's release version, which is an independent axis.
type Violation ¶
type Violation struct {
Path string // dotted path from the compared root, "" for top-level
Kind string // one of the Kind* constants
Old string // rendered prior value; "" when not applicable
New string // rendered new value; "" when not applicable
}
Violation is one breach of 0010 D27's additive-only rule, located by the dotted path from the compared root. Violations are results, not errors (opm/errors doctrine): the walk reports every breach it finds and never fails. The primitive's name, apiVersion, and predecessor coordinate are caller-attached — the walk does not know them.
func Check ¶
Check reports every violation of D27's additive-only rule in next relative to prev: fields and options may be added and never removed; a newly added field must be optional or defaulted; an existing field's default is immutable. It is level-blind — see CheckAtLevel — and cannot fail given two valid values.
The comparison is a field-wise walk, deliberately not a single Subsume call in either direction: adding a struct field makes a value more specific while adding a disjunct makes it less specific, and D27 calls both "additive", so the rule spans both directions of the lattice while one subsume call tests one (measured 10/14 and 8/14 against the D27 change classes in enhancements/0011/experiments/03-d27-compat-gate; the walk is 14/14). Structs recurse; leaves get a forward subsume, where it is correct for the value domain; defaults are compared explicitly at every level, because subsume is blind to them in both directions.
Three rules keep the walk from reporting non-changes (measured on catalog_opm PR 51, cli issue 165):
- 0010 D30's provenance fields are skipped at every depth: catalogVersion and description directly under any field named metadata, so a member reference embedded in another member (appliesTo, composedResources) does not report the referenced member's per-release provenance.
- Closed lists of equal length are walked element-wise (paths name[i]), so those embedded references reach the rule above; any other list pair is a leaf.
- A leaf whose emitted syntax is byte-identical on both sides reports nothing: it cannot have narrowed, and the forward subsume false-positives on unchanged leaves carrying matchN or a pending comprehension.
func CheckAtLevel ¶
CheckAtLevel is the level-aware entry point (0010 D34): the additive-only promise binds at beta and GA only, so an alpha apiVersion returns (nil, nil) without evaluating the operands. The apiVersion is the primitive's own — a catalog's release version is an independent axis and must not be passed here. An apiVersion outside core's #APIVersionType grammar is an error, as are non-struct top-level operands.