5.2 KiB
STATUS — phase samever (step-back to older base when canonical == head version)
SSOT: /srv/cc-ci/cc-ci-plan/plan-phase-samever-older-base-fallback.md.
State files: this + BACKLOG-samever.md, REVIEW-samever.md (Adversary), JOURNAL-samever.md. DECISIONS.md shared.
Phase
Started 2026-06-17. Gates: M1 (implemented + unit-tested), M2 (proven in real CI).
Current status
M1: PASS (REVIEW-samever.md @2026-06-17T04:27Z — cold-verified, teeth hold, no regression). M2: IN PROGRESS — real-CI demonstration on cc-ci. Plan: custom-html cold-on-latest twice (run A promotes canonical→latest 1.13.0+1.31.1; run B = nightly steady state → step-back to 1.11.0+1.29.0, base<latest); PR form (head==canonical → step-back); version-bump UNAFFECTED (canonical older→head); discourse #4 unaffected; spot-check.
M1 — WHAT is claimed
resolve_upgrade_base now reads the head's published version and steps back to a genuinely older
published base when the last-green warm-canonical version equals the head version — never a
same-version no-op, never a needless skip when an older base exists.
Resolution chain (override / EXPECTED_NA / upgrade∉stages short-circuits unchanged):
- explicit
UPGRADE_BASE_VERSIONoverride → unchanged. - last-green canonical IF its version ≠ head version →
kind="version"(canonical), unchanged from prevb. - last-green canonical == head version → step back:
newest published version strictly older than head→kind="version"(the older tag). Reason starts"step-back: …". - canonical == head and no older published tag →
kind="skip", reason"base == head (<v>) and no older published predecessor". - no canonical → main-tip ref / skip paths unchanged.
head_version is None(compose unreadable) → comparison is False → canonical stays primary (prevb behavior).
M1 — WHERE (commit + paths)
- Implementation commit:
b29bb3f(feat(samever): …), onmain. runner/run_recipe_ci.py—resolve_upgrade_base(..., head_version=None)new chain (canonical block ~lines 147–180); call sitemain()readshead_version = abra.head_compose_version(recipe)(~line 1023) and passes it.runner/harness/abra.py—head_compose_version(recipe)(regexcoop-cloud\.[^.\s]*\.version=([^\s"']+)over the head checkout'scompose.yml; matches quoted + unquoted labels; does NOT match.chaos-version).runner/warm_reconcile.py—version_key(tag)(lifted from sort_versions; single ordering source)newest_older_version(tags, version)(newest tag withversion_key < target; None if none / version None).
tests/unit/test_upgrade_base.py— 5 new tests (13 total).
M1 — HOW to verify (cold, from a clean clone)
-
Unit suite (the gate):
nix shell nixpkgs#python311Packages.pytest -c pytest tests/unit/test_upgrade_base.py -vEXPECTED: 13 passed. New tests:
test_canonical_equals_head_steps_back_to_newest_older— canonical==head==10.8.0+26.6.3, tags[10.6.0+26.5.0, 10.8.0+26.6.3, 10.7.1+26.6.2, 10.7.0+26.6.0, not-a-version]→plan.version == "10.7.1+26.6.2"(strictly older; assertsversion_key(plan.version) < version_key(head)),kind=="version", reason contains"step-back". main never consulted.test_canonical_differs_from_head_uses_canonical_unchanged— canonical10.7.1+26.6.2≠ head10.8.0+26.6.3→version==10.7.1+26.6.2, reason"last-green"; recipe_tags NOT consulted.test_canonical_equals_head_no_older_published_skips— canonical==head==1.0.0+3.5.3, tags[1.0.0+3.5.3]only →kind=="skip", reason contains"no older published predecessor".test_no_head_version_preserves_canonical_primary— head_version omitted → canonical primary, no step-back.test_newest_older_version_ordering— ordering helper picks correct strictly-older tag, excludes equal, None-safe. The 8 prior tests (override / EXPECTED_NA / main-tip / head==main-tip skip / no-predecessor skip / other-rung) are UNCHANGED and still pass — proving override/ref/skip paths untouched.
-
Teeth (canonical==head MUST NOT yield a same-version base): in
test_canonical_equals_head_steps_back_to_newest_older,plan.version != head_versionand theversion_key(plan.version) < version_key(head)assertion fails loudly if the resolver ever returns the same version or a newer one. -
Compose-label parse (the head-version reader): the regex extracts
10.8.0+26.6.3from a quoted label and3.5.3+1.24.2-rootlessfrom an unquoted one, and returns no match for a.chaos-versionlabel (verified — see JOURNAL). Real labels confirmed on cc-ci: keycloak10.8.0+26.6.3, gitea3.5.3+1.24.2-rootless, discourse1.0.0+3.5.3. -
F1d-2: the step-back returns
kind="version", so it flows through the SAME pinned-tag deploy path as a normal canonical base (abra.recipe_checkoutpins the tag on disk) — no new deploy code.
Note (pre-existing, NOT introduced by this gate): tests/unit/test_meta.py::test_generated_doc_table_in_sync
and tests/unit/test_warm_reconcile.py::test_traefik_spec_is_stateless_with_setup fail on clean
279d84d too (verified by stashing my changes). Out of scope for samever.
Blocked
(none)