diff --git a/nix/modules/nightly-sweep.nix b/nix/modules/nightly-sweep.nix index c31b70a..7a91e03 100644 --- a/nix/modules/nightly-sweep.nix +++ b/nix/modules/nightly-sweep.nix @@ -1,12 +1,18 @@ -# Phase 2w / WC6 — nightly full-cold sweep. A systemd TIMER fires nightly and runs -# `runner/nightly_sweep.py`: roll warm/infra (keycloak+traefik) to latest health-gated (WC1.1) THEN -# a SERIAL full-cold run across enrolled (WARM_CANONICAL) recipes on latest — each green run -# promotes/refreshes that recipe's canonical (WC5), serving as the daily authoritative regression. -# Serial = MAX_TESTS honored (one at a time); skips itself if a test is already in flight. Declarative -# + reproducible (runner/ packaged in the nix store, D8-clean). +# Phase 2w / WC6 + phase canon — WEEKLY full-cold canonical sweep. A systemd TIMER fires weekly and +# runs `runner/nightly_sweep.py`: faithfully mirror-sync each recipe to upstream, then a SERIAL +# full-cold run across enrolled (WARM_CANONICAL) recipes — but only for those with a NEW RELEASE TAG +# newer than their canonical (canon §2.D), cold-testing that tagged version and promoting its +# canonical on green (canon §2.A tagged-promote). Serial = MAX_TESTS honored (one at a time); skips +# itself if a test is already in flight. +# +# canon M1.4 (hollow-sweep fix): the sweep runs FROM the deployed checkout at $CCCI_REPO=/etc/cc-ci +# (the nixos flake source, kept current by the deploy `git pull` + nixos-rebuild), NOT from a +# nix-store copy of runner/. The store copy carried no tests/, so enrolled_recipes() resolved +# TESTS_DIR to a nonexistent dir → [] → the timer was a no-op ("hollow sweep"). Running from +# /etc/cc-ci (which has runner/ AND tests/) is the same checkout run_recipe_ci already executes from, +# and lets sweep-logic changes ship via a checkout pull without a store rebuild. { pkgs, ... }: let - runnerSrc = ../../runner; # The sweep drives run_recipe_ci.py (pytest/playwright) — needs the full harness env like cc-ci-run. pyEnv = pkgs.python3.withPackages (ps: with ps; [ pytest playwright ]); sweep = pkgs.writeShellApplication { @@ -17,13 +23,16 @@ let export HOME=/root export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 - exec ${pyEnv}/bin/python3 ${runnerSrc}/nightly_sweep.py + # canon M1.4: read enrollment + run the harness from the deployed checkout (has tests/). + export CCCI_REPO=/etc/cc-ci + cd "$CCCI_REPO" + exec ${pyEnv}/bin/python3 "$CCCI_REPO/runner/nightly_sweep.py" ''; }; in { systemd.services.nightly-sweep = { - description = "Phase-2w nightly: roll warm/infra (health-gated) + full-cold sweep over canonicals"; + description = "Weekly canonical sweep: mirror-sync + new-release-tag-gated full-cold runs that promote canonicals"; after = [ "deploy-proxy.service" "warm-keycloak.service" "docker.service" ]; environment.HOME = "/root"; serviceConfig = { @@ -35,11 +44,14 @@ in }; systemd.timers.nightly-sweep = { - description = "Nightly trigger for the Phase-2w full-cold canonical sweep (WC6)"; + description = "Weekly trigger for the full-cold canonical sweep (canon §2.F)"; wantedBy = [ "timers.target" ]; timerConfig = { - OnCalendar = "*-*-* 03:00:00"; - Persistent = true; # catch up a missed nightly after downtime + # canon §2.F: WEEKLY (operator preference) — a full-cold sweep over ~21 recipes is heavy; + # weekly is the chosen cadence. Sunday 03:00 UTC = a low-traffic slot (exact day/time not + # critical). Persistent catches up a missed run after downtime. + OnCalendar = "Sun *-*-* 03:00:00"; + Persistent = true; RandomizedDelaySec = "600"; }; };