bluesky-pds 8 rows in exact host ts order (753 556 435 427 423 ab-* m2rr-* m2r-*), plausible 30 (capped from 33), ghost 24; overview+badges 200; service 1/1. Deploy via path: flake (git-flake drops secrets/ submodule). Retention: no trim job on /var/lib/cc-ci-runs (439 dirs / 17 days) — adequate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.3 KiB
STATUS — phase dash (per-recipe run history fix)
SSOT: /srv/cc-ci/cc-ci-plan/plan-phase-dash-recipe-history.md Gates: M1 (fix implemented + locally verified) · M2 (deployed + verified live)
Gate: M1 CLAIMED, awaiting Adversary
WHAT — history_for(recipe) in dashboard/dashboard.py now sources the FULL per-recipe run
history from the local run artifacts under /var/lib/cc-ci-runs (each run dir's results.json),
newest-first by the finished timestamp, display-capped at HISTORY_CAP (default 30). It no longer
reads the Drone …/builds?per_page=100 slice (the root cause: that window dropped a recipe's older
runs out of view, so most recipes showed 1 run). Overview (/), /badge/<recipe>.svg,
/runs/<id>/<file>, security guards, and stdlib-only constraint are unchanged.
WHERE —
- Commit: see
git logon origin/main for theclaim(M1)commit (this push). - Changed files:
dashboard/dashboard.py(new_run_status,_numeric_id,_local_history_row,_local_history; rewrittenhistory_for; newHISTORY_CAP; new_LOCALcache), andtests/unit/test_dashboard.py(newtest_history_sourced_from_local_artifacts). - Host artifacts the page reads:
/var/lib/cc-ci-runs/<id>/results.json(bind-mounted read-only into the dashboard container, unchanged from before).
HOW to verify (cold, from a fresh clone) —
- Unit suite (stdlib render + new local-sourcing test):
EXPECTED:
nix-shell -p 'python3.withPackages(ps:[ps.pytest])' --run \ 'DRONE_TOKEN_FILE=$(mktemp) python3 -m pytest tests/unit/test_dashboard.py -q'13 passed. - Verify against the REAL host artifacts. Build a fixture of every
results.jsonand runhistory_foragainst it (no Drone, no network):EXPECTED:FIX=/tmp/advfix; rm -rf $FIX; mkdir -p $FIX ssh cc-ci 'cd /var/lib/cc-ci-runs && tar -cf - */results.json 2>/dev/null' | tar -xf - -C $FIX printf x > /tmp/t.tok DRONE_TOKEN_FILE=/tmp/t.tok CCCI_RUNS_DIR=$FIX python3 -c ' import sys; sys.path.insert(0,"dashboard"); import dashboard as d r=d.history_for("bluesky-pds") print("count", len(r), [x["number"] for x in r]) print("total parseable", sum(len(v) for v in d._local_history().values())) print("plausible cap", len(d.history_for("plausible")))'bluesky-pdscount 8, order EXACTLY['753','556','435','427','423','ab-bluesky-pds-oldmain','m2rr-bluesky-pds','m2r-bluesky-pds'](newest-first byfinished; note 423 sorts BELOW 427 though id 423<427, and named ids land in their timestamp positions — the mixed numeric+named id trap).- total parseable grouped rows 308 (matches host: 432 dirs, 308 with parseable
results.json). plausiblecapped at 30 (of 33), newest kept.
EXPECTED — invariants the Adversary's break-tests should confirm hold
- The 124 run dirs with no/malformed
results.jsonare skipped (no 500, no garbage row):_results_forreturns{}on miss/malformed/non-dir,_local_historyskips any row with norecipe. - Security preserved (untouched code paths):
/recipe/<name>still gated by_RUN_ID_RE(^[A-Za-z0-9][A-Za-z0-9._-]*$→ rejects../..,foo/.., spaces,;);_results_for/serve_run_filestill realpath-guarded against escaping/var/lib/cc-ci-runs. - stdlib-only: no new imports (still
html,json,os,re,sys,time,urllib,http.server). - Overview (
/) and/badge/<recipe>.svgstill sourced from Drone latest-per-recipe (_custom_recipe_builds/latest_per_recipeunchanged) — only the history page changed source. - Run-link resolution: numeric id →
{DRONE_URL}/{CI_REPO}/<id>; named id (m2r-*,ab-*) →/runs/<id>/summary.html(local, since no Drone build number exists). - Status pill derived from the per-stage
resultsmap (results.jsonhas no top-level status): anyfail/error→ failure; allpass/skip→ success; else unknown.
Gate: M2 CLAIMED, awaiting Adversary
WHAT — the dashboard service is rebuilt + redeployed with the M1 fix; the LIVE per-recipe
history page now shows the full (display-capped) local-artifact history. Verified on bluesky-pds
(8 runs) + plausible (30, capped from 33) + ghost (24); overview + badges + host health intact.
WHERE —
- Deployed image:
cc-ci-dashboard:11ac2a1e6c07(content hash of the M1 dashboard.py; rolled FROM15addbc7bf45). Source built from commit84ac65f+ (origin/main; this push adds the M2 status). - Deploy: host flake clone
/etc/cc-cipulled, thennixos-rebuild switchfrom apath:flake of the synced working tree (path:/root/ccci-build#cc-ci) — a plain git-flake build drops thesecrets/submodule (gitlink), thepath:copy includes the on-disksecrets/secrets.yaml. Thedeploy-dashboardreconcile rolled the swarm service on the new content-hash tag. - Live:
https://ci.commoninternet.net/recipe/<recipe>.
HOW to verify (cold) —
- Deployed image + service health:
EXPECTED:
ssh cc-ci 'docker service ls --filter name=ccci-dashboard --format "{{.Replicas}} {{.Image}}"'1/1 cc-ci-dashboard:11ac2a1e6c07. - Live full history (count rows = run count on host):
EXPECTED:
for r in bluesky-pds plausible ghost; do echo -n "$r: "; curl -s https://ci.commoninternet.net/recipe/$r \ | grep -coE '<tr><td><a href'; donebluesky-pds 8,plausible 30(capped from 33),ghost 24— matching the host run counts (history_forcap = 30). - Live order matches host timestamp order (mixed numeric+named id trap):
EXPECTED exactly:
curl -s https://ci.commoninternet.net/recipe/bluesky-pds | grep -oE '>#[^<]+</a>' \ | sed 's/[>#<]//g; s|/a||'753 556 435 427 423 ab-bluesky-pds-oldmain m2rr-bluesky-pds m2r-bluesky-pds. - Other routes unaffected:
EXPECTED: both
curl -s -o /dev/null -w '%{http_code}\n' https://ci.commoninternet.net/ # 200 overview curl -s -o /dev/null -w '%{http_code}\n' https://ci.commoninternet.net/badge/bluesky-pds.svg # 200200; overview still latest-per-recipe (Drone-sourced, unchanged).
EXPECTED — retention confirmed adequate: no nix module/tmpfiles/cron trims /var/lib/cc-ci-runs
(grep -rn cc-ci-runs nix/ shows no rm/find-delete/prune/maxage). Host: 439 run dirs spanning
2026-05-31 → 2026-06-17 (17 days). No growth cap needed now (recorded in DECISIONS).
Blocked
(none)