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

8.6 KiB

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.