plan(canon): canonical promotes only to TAGGED releases; sweep triggers on a new release tag (not a new commit)
Operator 2026-06-17. (1) should_promote_canonical also requires the tested version to have a release tag -> canonical is always a real published release, never an untagged main commit. (2) sweep trigger is version/tag-keyed not commit-keyed: skip a recipe unless its latest release tag is newer than its canonical (skip new-untagged-commits-without-a-version). This makes the sweep and samever ORTHOGONAL (samever never fires in the sweep; it's the PR-path same-version guard). Updated gates/DoD accordingly.
This commit is contained in:
@@ -5,11 +5,16 @@ actually doing anything** — confirmed live: `nightly-sweep.timer` is deployed
|
|||||||
(`nightly_sweep.py`, last run 2026-06-17 03:09 UTC exit 0), but **only `custom-html` is `WARM_CANONICAL`
|
(`nightly_sweep.py`, last run 2026-06-17 03:09 UTC exit 0), but **only `custom-html` is `WARM_CANONICAL`
|
||||||
-enrolled and ZERO `canonical.json` records exist** — i.e. the machinery has **never actually promoted a
|
-enrolled and ZERO `canonical.json` records exist** — i.e. the machinery has **never actually promoted a
|
||||||
canonical end-to-end**. This phase makes it **real and proven**, as the **substitute for** that hollow
|
canonical end-to-end**. This phase makes it **real and proven**, as the **substitute for** that hollow
|
||||||
nightly sweep, with two additions the operator wants:
|
nightly sweep, with the operator's refinements (2026-06-17):
|
||||||
|
|
||||||
1. **Sync each recipe mirror's `main`** on `git.autonomic.zone/recipe-maintainers/<recipe>` to its
|
1. **Sync each recipe mirror's `main`** on `git.autonomic.zone/recipe-maintainers/<recipe>` to its
|
||||||
**upstream** (`git.coopcloud.tech/coop-cloud/<recipe>`) first, so the sweep tests true upstream latest.
|
**upstream** (`git.coopcloud.tech/coop-cloud/<recipe>`) first, so the sweep sees true upstream
|
||||||
2. **Skip a recipe whose `main` is unchanged** vs its current canonical (no rerun needed).
|
tags/latest.
|
||||||
|
2. **Trigger on a new RELEASED VERSION, not a new commit.** Test a recipe only when its latest **release
|
||||||
|
tag** on the synced `main` is **newer** than its current canonical version — **skip when there is no
|
||||||
|
new version**, even if `main` has new *untagged* commits. The sweep tests releases, not arbitrary commits.
|
||||||
|
3. **Promote the canonical only to a TAGGED release.** A canonical advances only to a version that has a
|
||||||
|
real release tag (a published release) — never to an arbitrary untagged commit.
|
||||||
|
|
||||||
…then **run CI cold-on-`main` for each recipe and actually promote the canonical for any that pass** —
|
…then **run CI cold-on-`main` for each recipe and actually promote the canonical for any that pass** —
|
||||||
and **prove the whole thing works**. **The deliverable is correctness, verified end-to-end** — and the
|
and **prove the whole thing works**. **The deliverable is correctness, verified end-to-end** — and the
|
||||||
@@ -38,6 +43,11 @@ cold-on-latest run **actually write `canonical.json`** (recipe/version/commit/st
|
|||||||
subsequent `--quick` warm-reattach uses it (`deploy_canonical` reattaches the retained volume). If it
|
subsequent `--quick` warm-reattach uses it (`deploy_canonical` reattaches the retained volume). If it
|
||||||
doesn't happen today, find and fix why (this is the real defect behind the hollow sweep). A canonical
|
doesn't happen today, find and fix why (this is the real defect behind the hollow sweep). A canonical
|
||||||
must demonstrably exist and be reusable before anything else is meaningful.
|
must demonstrably exist and be reusable before anything else is meaningful.
|
||||||
|
- **Promote-gate addition (operator 2026-06-17): only promote to a TAGGED release.** Extend
|
||||||
|
`should_promote_canonical` so a promote ALSO requires the tested version to correspond to a published
|
||||||
|
**release tag** (`warm_reconcile.recipe_tags`): green + cold + latest + enrolled **+ tagged**. The
|
||||||
|
canonical must always be a real release — never an arbitrary untagged `main` commit. An untagged state
|
||||||
|
must never be written as a canonical.
|
||||||
|
|
||||||
**B. Enroll ALL recipes (operator decision 2026-06-17).** Set `WARM_CANONICAL = True` for **every** recipe
|
**B. Enroll ALL recipes (operator decision 2026-06-17).** Set `WARM_CANONICAL = True` for **every** recipe
|
||||||
cc-ci tracks (the `used-recipes.md` set) — the sweep promotes a canonical for each that passes, not just
|
cc-ci tracks (the `used-recipes.md` set) — the sweep promotes a canonical for each that passes, not just
|
||||||
@@ -55,9 +65,15 @@ to coopcloud upstream — reuse `recipe-upgrade`'s `open-recipe-pr.sh <recipe> -
|
|||||||
go-git private-mirror auth, fetches coopcloud via an `upstream` remote, closes already-merged-upstream
|
go-git private-mirror auth, fetches coopcloud via an `upstream` remote, closes already-merged-upstream
|
||||||
PRs, leaves unrelated PRs). This is a **faithful mirror sync, not a push of our own changes.**
|
PRs, leaves unrelated PRs). This is a **faithful mirror sync, not a push of our own changes.**
|
||||||
|
|
||||||
**D. Add skip-when-unchanged.** After sync, if the recipe's `main` commit == its canonical record's commit
|
**D. Trigger on a new RELEASED VERSION (skip when no new version).** After sync, compute the recipe's
|
||||||
(no change since the last promotion) → **skip** (log `SKIP unchanged`). This is the operator's efficiency
|
latest **release tag** version reachable on `main` and compare it to the canonical record's version:
|
||||||
ask and is also the determinism property (see M2 run-twice proof).
|
- latest release tag **== canonical version** → **skip** (`SKIP no-new-version`) — *even if `main` has
|
||||||
|
new untagged commits*. The sweep tests releases, not arbitrary commits.
|
||||||
|
- latest release tag **newer than** canonical → run CI **cold on that tagged version** → promote on green
|
||||||
|
(tagged, per §2.A).
|
||||||
|
- no release tag at all (recipe never released) → skip with a recorded reason.
|
||||||
|
This is the operator's trigger refinement (version/tag-keyed, **not** commit-keyed) and the determinism
|
||||||
|
property (M2 run-twice → everything skips).
|
||||||
|
|
||||||
**E. Keep it deterministic + AI-free at runtime** (it already is — a script + timer). The additions must
|
**E. Keep it deterministic + AI-free at runtime** (it already is — a script + timer). The additions must
|
||||||
stay pure code: no AI calls during the run. AI (the loops) only authors + verifies.
|
stay pure code: no AI calls during the run. AI (the loops) only authors + verifies.
|
||||||
@@ -66,46 +82,46 @@ stay pure code: no AI calls during the run. AI (the loops) only authors + verifi
|
|||||||
exact day/time is not critical — pick a low-traffic slot; it's a one-line tune. `Persistent = true` to
|
exact day/time is not critical — pick a low-traffic slot; it's a one-line tune. `Persistent = true` to
|
||||||
catch up a missed run. This is the only schedule work; do not over-invest in it.
|
catch up a missed run. This is the only schedule work; do not over-invest in it.
|
||||||
|
|
||||||
**Plays-nice-with-`samever` (operator wants this CONFIRMED, not assumed).** In the sweep, two distinct
|
**Plays-nice-with-`samever` (operator wants this CONFIRMED).** The release-tag trigger (D) makes the
|
||||||
guards keep the upgrade tier from a vacuous same-version run — and they use **deliberately different
|
sweep and `samever` **orthogonal** — confirm they don't interfere:
|
||||||
keys**, so verify them together:
|
- In the **sweep**, a recipe runs **only when a new release tag exists**, so the version under test is
|
||||||
- **skip-when-unchanged uses COMMIT equality** (`main` commit == canonical commit) → if literally
|
always *newer* than the canonical → the upgrade tier's base (previous canonical/released version) is
|
||||||
nothing changed, the recipe is skipped *before* the upgrade tier runs. This is the primary
|
strictly older → **`samever`'s same-version step-back never fires in the sweep** (the tag trigger
|
||||||
same-version avoider in the sweep.
|
already prevents a `vX→vX` run; no-new-version recipes are skipped outright).
|
||||||
- **`samever` uses VERSION equality** → for the case where `main` *changed* (new commit) but the version
|
- `samever` remains the guard for the **PR path** (`!testme`), where a PR can carry the same version
|
||||||
LABEL still equals the canonical's version (a non-version-bump recipe change), the upgrade tier's base
|
label as the canonical without cutting a release — that's where the step-back matters, and it's
|
||||||
would equal the head version, and `samever` steps back to the previous published version so a real
|
owned/proven by the `samever` phase, not here.
|
||||||
delta is still tested.
|
So in the sweep, verify only: (a) no new release tag → recipe SKIPPED (no upgrade-tier run, no promote);
|
||||||
These are complementary: commit-equality (skip) is the coarse filter; version-equality (`samever`) is
|
(b) new release tag → `canonical(older) → new tagged version`, a real delta, promote (tagged). The sweep
|
||||||
the backstop for "changed commit, same version label." Together the sweep must **never** run a vacuous
|
must never promote an untagged version and never run a same-version upgrade.
|
||||||
`vX → vX` upgrade **and never** wrongly skip a real change. M2 must prove all three sweep paths
|
|
||||||
explicitly (see Gates).
|
|
||||||
|
|
||||||
## 3. Gates
|
## 3. Gates
|
||||||
|
|
||||||
**M1 — machinery works locally, each piece proven.** (A) a real `canonical.json` is produced by a green
|
**M1 — machinery works locally, each piece proven.** (A) a real `canonical.json` is produced by a green
|
||||||
cold run on ≥1 recipe and reused by a warm reattach — **demonstrated, not assumed**. (C) mirror-sync and
|
cold run on ≥1 recipe and reused by a warm reattach — **demonstrated, not assumed** — and the promote gate
|
||||||
(D) skip-when-unchanged implemented, reusing the existing reconcile + sweep code, with unit tests
|
now also requires a **release tag** (untagged → no promote). (C) mirror-sync and (D) the **new-release-tag
|
||||||
(skip = commit-equality; sync invoked per recipe; promote still gated on green+cold+latest+enrolled).
|
trigger** implemented, reusing the existing reconcile + sweep code, with unit tests (trigger = latest
|
||||||
(B) enrollment scope decided + recorded (≥several recipes enrolled, or the decouple decision). Adversary
|
release tag vs canonical version, NOT commit; sync invoked per recipe; promote gated on
|
||||||
cold-verifies: a canonical actually exists + reattaches; skip-logic correct; sync is faithful-mirror-only;
|
green+cold+latest+enrolled+**tagged**). (B) all recipes enrolled. Adversary cold-verifies: a canonical
|
||||||
a RED recipe does NOT promote (prior known-good intact); no AI at runtime.
|
actually exists + reattaches; an **untagged** state never promotes; the trigger skips no-new-tag recipes
|
||||||
|
and runs new-tag ones; sync is faithful-mirror-only; a RED recipe does NOT promote; no AI at runtime.
|
||||||
|
|
||||||
**M2 — proven end-to-end in real CI (the heart of this phase).** A full sweep run across the enrolled set
|
**M2 — proven end-to-end in real CI (the heart of this phase).** A full sweep run across the enrolled set
|
||||||
on cc-ci: mirrors synced to upstream, **canonicals actually promoted for the green recipes** (records
|
on cc-ci: mirrors synced to upstream, **canonicals actually promoted for the green recipes** (records
|
||||||
exist with correct version+commit), red recipes left intact, unchanged recipes skipped — with a
|
exist with correct version+commit), red recipes left intact, unchanged recipes skipped — with a
|
||||||
per-recipe results log. **Determinism proof: run the sweep a SECOND time immediately → it SKIPS every
|
per-recipe results log. **Determinism proof: run the sweep a SECOND time immediately → it SKIPS every
|
||||||
recipe** (all `main` == the canonicals just promoted) = a clean no-op, no CI rerun. Confirm the **deployed
|
recipe** (latest release tag == canonical version for all → skip all) = a clean no-op, no CI rerun.
|
||||||
timer fires the real (non-hollow) job** — after a fire, canonicals have advanced (evidence), not exit-0
|
Confirm the **deployed timer fires the real (non-hollow) job** — after a fire, canonicals have advanced
|
||||||
on an empty set.
|
(evidence), not exit-0 on an empty set. **Tagged-promote proven:** show a green run on an untagged state
|
||||||
|
does NOT promote, and a green run on a tagged release DOES.
|
||||||
|
|
||||||
**`samever` interaction proven (operator-required).** Demonstrate, with evidence, all three sweep paths:
|
**`samever` orthogonality proven (operator-required).** Demonstrate, with evidence, the two sweep paths:
|
||||||
(1) `main` commit == canonical commit → recipe SKIPPED (no upgrade tier run); (2) `main` changed +
|
(1) **no new release tag** (latest tag == canonical version, even with new untagged commits on `main`) →
|
||||||
version bumped → upgrade tier runs `canonical(older) → head(new)`, a real delta; (3) `main` changed but
|
recipe SKIPPED — no upgrade-tier run, no promote; (2) **new release tag** → cold-test the new tagged
|
||||||
version label == canonical version → `samever` steps back to the previous published version (base version
|
version, upgrade `canonical(older) → new`, a real delta, promote (because tagged). Confirm `samever`'s
|
||||||
< head version), NOT a vacuous `vX→vX` and NOT skipped. Confirm the boundary explicitly: a
|
step-back **never fires inside the sweep** (the tag trigger prevents same-version runs) — its same-version
|
||||||
same-version-label-but-different-commit recipe is **not** skipped (commits differ) and **does** hit the
|
behavior is owned/proven by the `samever` phase on the PR path. Construct scenarios if the live recipe set
|
||||||
`samever` step-back. Construct the scenarios if the natural recipe set doesn't cover all three.
|
doesn't cover both.
|
||||||
|
|
||||||
No AI in the loop. Fresh Adversary PASS on both milestones → `## DONE`.
|
No AI in the loop. Fresh Adversary PASS on both milestones → `## DONE`.
|
||||||
|
|
||||||
@@ -127,10 +143,12 @@ No AI in the loop. Fresh Adversary PASS on both milestones → `## DONE`.
|
|||||||
|
|
||||||
## 5. Definition of Done
|
## 5. Definition of Done
|
||||||
|
|
||||||
The canonical sweep **actually works and is proven**: a green cold-on-latest run produces a real,
|
The canonical sweep **actually works and is proven**: a green cold run on a **tagged release** produces a
|
||||||
reusable `canonical.json`; the sweep reconciles each recipe mirror's `main` to upstream, skips recipes
|
real, reusable `canonical.json` (an untagged state never promotes); the sweep reconciles each recipe
|
||||||
whose `main` is unchanged vs canonical, runs CI on the rest, and promotes the canonical for any that pass
|
mirror's `main` to upstream, **skips recipes with no new release tag** (even if `main` has new untagged
|
||||||
— across a real multi-recipe set, demonstrated end-to-end in CI, including the run-twice no-op determinism
|
commits), runs CI cold on the new tagged version for the rest, and promotes the canonical only to that
|
||||||
proof and a real (non-hollow) timer fire. Enrollment scope + the warm-volume budget decided/recorded; the
|
tagged release — across all enrolled recipes, demonstrated end-to-end in CI, including the run-twice no-op
|
||||||
runtime job is AI-free; it is the substitute for the hollow nightly sweep (not a parallel job). M1 + M2
|
determinism proof, the tagged-promote proof, and a real (non-hollow) timer fire. `samever` confirmed
|
||||||
fresh Adversary PASSes in REVIEW-canon.md.
|
orthogonal (never fires in the sweep). All recipes enrolled + warm-volume budget recorded; the runtime job
|
||||||
|
is AI-free; it is the substitute for the hollow nightly sweep (not a parallel job). M1 + M2 fresh Adversary
|
||||||
|
PASSes in REVIEW-canon.md.
|
||||||
|
|||||||
Reference in New Issue
Block a user