Files
cc-ci-orchestrator/cc-ci-plan/plan-phase-settings-ci-server-config.md
autonomic-bot f7825f8494 plan(settings): add release-tag-first no-canonical fallback; bump to opus
Operator 2026-06-17: (1) no-canonical upgrade base should prefer the most recent
release TAG < head (a real published predecessor, reusing samever's helper),
with raw main-tip only as a last resort, then skip — always-on, improves this
server too. (2) SKIP_CANONICALS_FOR_UPGRADE=true feeds that same release-tag-first
fallback (so it swaps canonical->latest-release base without losing upgrade
coverage). (3) model bumped sonnet->opus.
2026-06-17 09:48:34 +00:00

115 lines
8.6 KiB
Markdown

# Phase `settings` — CI-server settings.toml + `SKIP_CANONICALS_FOR_UPGRADE` + release-tag-first fallback
**Mission (operator-specified 2026-06-17):** two cohesive `resolve_upgrade_base` changes for the cc-ci
server:
1. A **minimal, extensible server-level `settings.toml`** with its first value
**`SKIP_CANONICALS_FOR_UPGRADE`** (bool, **default `false`**, `false` on this server). When `true`, the
upgrade tier resolves its base **without canonicals** — codifying that canonicals are an optional
optimization (an operator switch). The file is structured to hold other CI-server configs later; ship
it minimal.
2. An **improved no-canonical fallback (always-on, not flag-gated):** when no canonical is used — none
exists, its promote failed, or the flag is `true` — the base should be the **most recent release TAG on
`main` older than the PR head** (a real published predecessor), with the **raw `main`-tip only as a
further fallback** if the recipe has no prior release, then skip. Today the no-canonical path jumps
straight to `main`-tip, which may be an untagged WIP commit.
These compose: `SKIP_CANONICALS_FOR_UPGRADE=true` always takes the improved release-tag-first fallback.
State files: `STATUS-settings.md`, `BACKLOG-settings.md`, `REVIEW-settings.md`, `JOURNAL-settings.md`. DECISIONS.md shared.
## 1. Background
Canonicals are an optimization + robustness aid for the upgrade base, **not** a requirement: the upgrade
tier already falls back to main-tip when no canonical exists (and the install/backup/restore/custom tiers
never use canonicals). This flag makes that explicit and operator-controllable: a server can run the
upgrade tier purely off main-tip (real predecessor), ignoring the warm-canonical layer entirely — useful
for a deployment that doesn't run the canonical sweep, for debugging, or for a simpler setup.
This server keeps `false` (canonicals on — the optimized/robust path); the flag is the documented escape
hatch.
## 2. Design
**A. A minimal server settings file + loader.**
- A server-level TOML (today cc-ci server config is scattered env vars like `MAX_TESTS`, `CCCI_RUNS_DIR`).
Add a small settings layer that reads a TOML once. Suggested home: a host path the harness reads (e.g.
`/srv/cc-ci/settings.toml`), with a tracked `settings.toml.example` documenting the keys + defaults — OR
extend any existing cc-ci config module if one fits. **Builder: check for an existing cc-ci config
mechanism first and extend it rather than spawn a parallel one.**
- **Defaults baked into the loader** → an absent file, or an absent key, yields the default (so this server
needs no file to behave as today). Stdlib only (`tomllib`). Validate: unknown keys warn-and-ignore;
wrong type errors clearly. Per-server: the file is a host override, not committed config (a tracked
`.example` is fine; the live file is operator-managed and must carry **no secrets** — secrets stay in
sops).
- Keep it **minimal + extensible**: one `[ci]`/`[upgrade]` table with the single key now, shaped so future
CI-server configs slot in without a redesign.
**B. `SKIP_CANONICALS_FOR_UPGRADE` (bool, default `false`).**
- Wire into `resolve_upgrade_base` (`run_recipe_ci.py`): guard the canonical (`version`) branch — when the
flag is `true`, **skip the canonical lookup entirely** and fall through to the **improved no-canonical
fallback (§2.C)**. Effectively: behave as if no canonical exists.
- **Scope it narrowly to the upgrade BASE** (its name says so). Do NOT change canonical *promotion* or the
`--quick` warm-reattach with this flag — those are separate optimizations (a future `SKIP_CANONICAL_SWEEP`
/ `SKIP_QUICK` could gate them; out of scope here — note in DECISIONS).
**C. Improved no-canonical fallback — release tag before main-tip (always-on).** Replace the current
"jump straight to `main`-tip" no-canonical path with:
1. **most recent release TAG with version strictly older than the PR head** — reuse `samever`'s existing
helper (newest published version `< head`, from `recipe_tags`, version-ordered); deploy that tag (a
clean published predecessor) as the base.
2. **raw `main`-tip** (the target-branch tip) — only if the recipe has **no** prior release tag at all.
3. **skip** — if neither (no predecessor / head == main-tip with no older tag).
This applies whenever no canonical is used (none exists, promote failed, or the flag is `true`), so it
improves this server too (false flag, but un-promoted recipes get a real release base instead of a WIP
commit). The unified chain becomes: `canonical (unless flag/none/==head) → newest release tag < head →
main-tip → skip`, with `samever`'s step-back and this fallback sharing the same release-tag helper.
- **Sweep interaction (now better):** with the flag `true`, a cold-on-latest sweep run resolves
base = newest release tag `< latest` = the **previous release** → the upgrade tier still runs a **real**
`previous-release → latest` upgrade (not a skip, not a no-op). So `SKIP_CANONICALS_FOR_UPGRADE` swaps the
base from *last-green canonical* to *latest published predecessor* without losing upgrade coverage. PR
runs likewise get a real release base. This server runs `false`, so the canonical path is unchanged.
## 3. Gates
**M1 — implemented + unit-tested.** Settings loader (TOML, stdlib, defaults, validation, graceful on
absent/malformed file); `SKIP_CANONICALS_FOR_UPGRADE` wired into `resolve_upgrade_base`; the release-tag-first
no-canonical fallback (§2.C) implemented by reusing `samever`'s newest-release-tag-`<`-head helper. Unit
tests for the **full resolution matrix**: flag `false` + canonical present → canonical (unchanged); flag
`false` + no canonical → **newest release tag `< head`** (NOT main-tip); no canonical AND no older release
tag → main-tip; none → skip; flag `true` → canonical skipped → same release-tag-first fallback; absent
file / absent key → default false; malformed file handled. Adversary cold-verifies: **default is false**
(this server byte-for-byte unchanged); the no-canonical fallback returns a real older **release tag**
(main-tip only as last resort); `true` genuinely bypasses the canonical; the loader can't crash the harness
on a bad/absent file; scope is the upgrade base only (promote/`--quick` untouched); the fallback reuses the
`samever` helper (no divergent version-ordering).
**M2 — verified on the server.** The live server reads the settings (file present with
`SKIP_CANONICALS_FOR_UPGRADE = false`, or absent → default false) and the upgrade path **still uses
canonicals — unchanged**. Demonstrate, with evidence: (a) a recipe **without** a canonical resolves its
base to the **newest release tag `< head`** (a real published predecessor), not the raw main-tip; (b)
flipping `SKIP_CANONICALS_FOR_UPGRADE = true` (test/scratch, not permanent) makes a canonical-bearing
recipe ALSO resolve to that release-tag base (canonical bypassed), then restore `false`. Confirm the
harness picks up the file on the server. Fresh Adversary PASS on both milestones → `## DONE`.
## 4. Guardrails
- **Default `false`; this server stays `false`** — the change must be a no-op for current behavior. A
regression to the default upgrade-base path fails the gate.
- **Minimal + extensible** — one setting now, structured for more; do not over-build a config framework.
- **Stdlib only** for the loader; **no secrets** in settings.toml (config only; secrets stay in sops).
- **Narrow scope** — the flag affects only the upgrade-base resolver, not promotion or `--quick`.
- Never weaken a test. Commit author `autonomic-bot <autonomic-bot@noreply.git.autonomic.zone>`; push every
commit. If the settings file lives at a host path requiring a deploy, treat it as a host change (loops
may deploy if clean + verify health, else file for the orchestrator).
## 5. Definition of Done
A minimal, extensible CI-server `settings.toml` (+ tracked `.example`, defaults in the loader) is read by
the harness; `SKIP_CANONICALS_FOR_UPGRADE` (default `false`, `false` on this server) is wired into the
upgrade-base resolver; AND the no-canonical fallback now prefers the **most recent release tag `< head`**
(reusing `samever`'s helper) with raw `main`-tip only as a last resort. `false`/absent leaves current
behavior byte-for-byte unchanged; `true` bypasses the canonical into the same release-tag-first fallback.
Unit-tested across the full resolution matrix; verified live on the server (false = unchanged; no-canonical
recipe → release-tag base; true = canonical bypassed, then restored). Scope limited to the upgrade base
(promote/`--quick` separate). M1 + M2 fresh Adversary PASSes in REVIEW-settings.md.