Files
cc-ci/machine-docs/STATUS-settings.md
autonomic-bot dd6712c243
Some checks failed
continuous-integration/drone/push Build is failing
status(settings): ## DONE — M1+M2 fresh Adversary PASS (cd19c1b, 99d6bbc), no VETO
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:07:14 +00:00

10 KiB
Raw Blame History

STATUS — phase settings

Phase: server-level settings.toml + SKIP_CANONICALS_FOR_UPGRADE + release-tag-first no-canonical fallback. Plan: /srv/cc-ci/cc-ci-plan/plan-phase-settings-ci-server-config.md.

DONE

All Definition-of-Done items Adversary-verified with fresh PASSes (no standing VETO), 2026-06-17:

  • M1 PASS — REVIEW-settings.md @17:00Z (claim fed2678 / code cd19c1b): settings loader (stdlib tomllib, defaults, graceful absent/malformed, warn-and-ignore unknown, TypeError on wrong type) + SKIP_CANONICALS_FOR_UPGRADE wired into resolve_upgrade_base + release-tag-first no-canonical fallback reusing samever's helper; 32 + 315 unit pass; scope narrow; stdlib-only; no secrets.
  • M2 PASS — REVIEW-settings.md @17:35Z (claim a9ff941 / deployed /etc/cc-ci@99d6bbc, byte-identical runner to cd19c1b): live on cc-ci — keycloak (no canonical) → release tag 10.7.1+26.6.2 not main-tip; gitea (canonical) unchanged last-green under default false; scratch true bypasses gitea's canonical to the release-tag path; restored to false; harness file-pickup proven via the real /etc/cc-ci/settings.toml.

Server in steady state: /etc/cc-ci/settings.toml ABSENT (default false), checkout clean @99d6bbc.

Gate: M1 PASS (Adversary @2026-06-17T17:00Z, REVIEW-settings.md) · M2 CLAIMED (see below)

M1 commit: cd19c1b (feat: settings loader + flag + fallback + unit tests) — Adversary cold-PASS, no VETO. M2 deployed: 99d6bbc on /etc/cc-ci. Tree clean, pushed to origin/main.

WHAT is claimed (M1 — implemented + unit-tested)

  1. Settings loader runner/harness/settings.py — stdlib tomllib, one [upgrade] table with skip_canonicals_for_upgrade (bool, default false). _SCHEMA (table→{key:(type,default)}) is the single source of defaults + validation. Graceful on absent/unreadable/malformed file (WARN + all-defaults — never crashes); unknown table/key → warn-and-ignore; present known key of wrong type → TypeError. Path = $CCCI_SETTINGS else /etc/cc-ci/settings.toml. Tracked settings.toml.example documents the key (no secrets).
  2. SKIP_CANONICALS_FOR_UPGRADE wired into resolve_upgrade_base (runner/run_recipe_ci.py): when true, the canonical (version) branch is skipped entirely → no-canonical fallback. Guard: if rec and rec.get("version") and not skip_canonicals:. Scope = upgrade base only (promotion / --quick untouched).
  3. Release-tag-first no-canonical fallback _no_canonical_base (runner/run_recipe_ci.py), always-on: (1) newest release tag with version strictly older than head_version — reuses warm_reconcile.newest_older_version(warm_reconcile.recipe_tags(recipe), head_version); (2) raw main-tip — only if no prior release tag; (3) skip. (Guarded: tag lookup skipped when head_version is falsy → main-tip, preserving prevb behavior for that caller.)

HOW to verify (re-runnable from a fresh clone)

All commands from the repo root of a clone at cd19c1b (or later). Unit tests need pytest from nixpkgs:

# 1. Full upgrade-base matrix + settings loader tests
nix shell nixpkgs#python311Packages.pytest -c pytest tests/unit/test_upgrade_base.py tests/unit/test_settings.py -v
# 2. Whole unit suite (regression — nothing else broke)
nix shell nixpkgs#python311Packages.pytest -c pytest tests/unit/ -q
# 3. Lint (my files) — ruff check + format
nix shell nixpkgs#ruff -c ruff check runner/ tests/unit/test_settings.py tests/unit/test_upgrade_base.py
nix shell nixpkgs#ruff -c ruff format --check runner/harness/settings.py runner/run_recipe_ci.py tests/unit/test_settings.py tests/unit/test_upgrade_base.py settings.toml.example
# 4. Default is false WITHOUT any file (this server unchanged):
nix shell nixpkgs#python311Packages.pytest -c python3 -c "import sys; sys.path.insert(0,'runner'); from harness import settings; print(settings.load('/no/such/file.toml').skip_canonicals_for_upgrade)"
#   -> False

EXPECTED outcome

  • (1) → 32 passed. Key cases proving the matrix:
    • test_flag_false_canonical_present_unchanged — flag false + canonical (≠head) → canonical, last-green reason, tags/main NOT consulted (byte-for-byte prevb).
    • test_no_canonical_prefers_release_tag_over_main_tip — flag false + no canonical → version=10.7.1+26.6.2 (newest tag < head 10.8.0+26.6.3), main NOT consulted; reason contains no-canonical fallback/release tag.
    • test_no_canonical_no_older_tag_falls_back_to_main_tip — no older tag → ref=main-tip.
    • test_no_canonical_no_tag_no_main_skips — neither → skip.
    • test_flag_true_bypasses_canonical_into_release_tag_fallback — flag true + canonical present → resolves to release tag 10.7.1+26.6.2, NOT the canonical 10.6.0+26.5.0.
    • test_flag_true_canonical_present_no_older_tag_uses_main — flag true routes through the FULL chain → main-tip.
    • test_canonical_equals_head_steps_back_to_newest_older / ..._no_older_published_skips — samever step-back unchanged (older→version; none→skip, NOT main-tip).
    • loader: test_absent_file_yields_defaults, test_malformed_toml_degrades_to_defaults, test_wrong_type_errors_clearly, test_int_not_accepted_for_bool, test_unknown_key/table_warns_and_ignored, test_env_var_path_override, test_flag_true_read/_false_read.
  • (2) → 315 passed (full suite, no regression).
  • (3) → All checks passed! and ... already formatted.
  • (4) → False (default with no file → this server behaves as today).

WHERE

  • runner/harness/settings.py (loader, DEFAULT_PATH = /etc/cc-ci/settings.toml, Settings, load(), get()).
  • settings.toml.example (repo root, tracked).
  • runner/run_recipe_ci.py: resolve_upgrade_base (flag guard) + new _no_canonical_base helper; import settings as settings_mod.
  • tests/unit/test_settings.py (loader, 13 tests), tests/unit/test_upgrade_base.py (matrix, +8 new tests).

NOTE (pre-existing, out of scope — see DECISIONS)

scripts/lint.sh (pinned ruff) flags dashboard/dashboard.py + tests/unit/test_dashboard.py as needing reformat — confirmed present at HEAD f68f1c5, NOT in this phase's diff. Not fixed here (narrow scope). My 5 phase files are ruff-clean + format-clean.


Gate: M2 CLAIMED, awaiting Adversary @2026-06-17T17:25Z

M1 is Adversary-PASS (REVIEW-settings.md verdict @17:00Z, fed2678/cd19c1b). M2 = verified live on cc-ci.

Deployed: /etc/cc-ci at 99d6bbc (pushed to origin/main; the deployed checkout the nightly sweep runs from, and the absolute path the Drone recipe-CI runner reads). No nixos-rebuild needed — the change is pure runner Python loaded at runtime from the checkout. Live settings file /etc/cc-ci/settings.toml is ABSENT → default false (server steady state restored after the test).

WHAT is claimed (M2)

  • The live server harness reads the settings file from the host path /etc/cc-ci/settings.toml (absent → default false), confirmed by the flag value flipping with the file's presence.
  • (a) A recipe without a canonical (keycloak, no canonical.json) resolves its upgrade base to the newest release tag < head (10.7.1+26.6.2), NOT the raw main-tip.
  • (b) With SKIP_CANONICALS_FOR_UPGRADE = true (scratch file), a canonical-bearing recipe (gitea, canonical 3.5.3+1.24.2-rootless) resolves to the release-tag base (canonical BYPASSED) — proven by the reason changing from last-green (warm canonical, status=idle) to no-canonical fallback: newest release tag older than head 3.6.0+1.24.2-rootless. Scratch file then removed → restored to false (reason back to last-green (warm canonical)).
  • Default false ⇒ this server's canonical-bearing path is unchanged (gitea false → last-green base).

HOW to verify (cold, on the server, from /etc/cc-ci or your own clone)

The probe runs the EXACT deployed resolve_upgrade_base against live settings + live canonical registry (/var/lib/ci-warm/<r>/canonical.json) + live recipe tags (~/.abra/recipes/<r>). Faithful, no deploy/teardown.

ssh cc-ci
cd /etc/cc-ci && git rev-parse --short HEAD          # 99d6bbc (or later)
ls /etc/cc-ci/settings.toml                          # ABSENT -> default false

# CASE 1 — flag false (default, no file): (a) keycloak, plus gitea unchanged
HOME=/root cc-ci-run scripts/show-upgrade-base.py keycloak gitea

# CASE 2 — flag true (scratch), then RESTORE
printf '[upgrade]\nskip_canonicals_for_upgrade = true\n' > /etc/cc-ci/settings.toml
HOME=/root cc-ci-run scripts/show-upgrade-base.py gitea keycloak
rm -f /etc/cc-ci/settings.toml                       # restore default false
HOME=/root cc-ci-run scripts/show-upgrade-base.py gitea

EXPECTED (verbatim BasePlan lines observed @17:2017:25Z)

  • CASE 1 (false):
    • keycloakBasePlan(kind='version', version='10.7.1+26.6.2', ref='', reason='no-canonical fallback: newest release tag older than head 10.8.0+26.6.3') (canonical=None; newest_release_tag<head=10.7.1+26.6.2; NOT main-tip 12ac6db8…)
    • giteaBasePlan(kind='version', version='3.5.3+1.24.2-rootless', ref='', reason='last-green (warm canonical, status=idle)') (canonical=3.5.3 present → used)
  • CASE 2 (true):
    • giteaBasePlan(kind='version', version='3.5.3+1.24.2-rootless', ref='', reason='no-canonical fallback: newest release tag older than head 3.6.0+1.24.2-rootless') (canonical 3.5.3 present but BYPASSED — reason is the release-tag path)
    • keycloak → same as CASE 1 (no canonical either way)
  • RESTORE (file removed → false):
    • gitea → reason back to last-green (warm canonical, status=idle); flag reads False.

WHERE

  • Deployed code: /etc/cc-ci @ 99d6bbc (origin/main). Probe: scripts/show-upgrade-base.py.
  • Live registry: /var/lib/ci-warm/{keycloak (none),gitea}/canonical.json. Recipe tags: ~/.abra/recipes/{keycloak,gitea}. Settings path: /etc/cc-ci/settings.toml (absent now).

Server left in steady state: /etc/cc-ci/settings.toml ABSENT (default false), checkout clean @99d6bbc.

→ On a fresh Adversary PASS of M2 (with M1 PASS standing), I will write ## DONE.