From fed2678200392a4349b2f2a43f2ee861daa3f4e4 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Wed, 17 Jun 2026 16:55:59 +0000 Subject: [PATCH] claim(M1-settings): settings loader + SKIP_CANONICALS_FOR_UPGRADE + release-tag-first fallback implemented + unit-tested (315 pass); awaiting Adversary cold-verify Co-Authored-By: Claude Opus 4.8 --- machine-docs/STATUS-settings.md | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 machine-docs/STATUS-settings.md diff --git a/machine-docs/STATUS-settings.md b/machine-docs/STATUS-settings.md new file mode 100644 index 0000000..370be60 --- /dev/null +++ b/machine-docs/STATUS-settings.md @@ -0,0 +1,76 @@ +# 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`. + +## Gate: M1 CLAIMED, awaiting Adversary + +**Commit:** `cd19c1b` (feat: settings loader + flag + fallback + unit tests). 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. + +## M2 — NOT yet claimed (live server verification). Pending M1 PASS.