Some checks failed
continuous-integration/drone/push Build is failing
history_for() now enumerates run dirs' results.json, groups by recipe, sorts newest-first by finished timestamp (mixed numeric+named ids — timestamp is the only correct key), caps at HISTORY_CAP=30, skips malformed/empty/no-recipe dirs. Overview + badges + /runs + security guards + stdlib-only unchanged. Local verify: 13/13 unit tests; full-fixture vs 308 real results.json → bluesky-pds=8 in exact ts order, plausible capped 30 newest, edge dirs skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
4.0 KiB
Markdown
69 lines
4.0 KiB
Markdown
# 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 log` on origin/main for the `claim(M1)` commit (this push).
|
|
- Changed files: `dashboard/dashboard.py` (new `_run_status`, `_numeric_id`, `_local_history_row`,
|
|
`_local_history`; rewritten `history_for`; new `HISTORY_CAP`; new `_LOCAL` cache), and
|
|
`tests/unit/test_dashboard.py` (new `test_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)** —
|
|
1. Unit suite (stdlib render + new local-sourcing test):
|
|
```
|
|
nix-shell -p 'python3.withPackages(ps:[ps.pytest])' --run \
|
|
'DRONE_TOKEN_FILE=$(mktemp) python3 -m pytest tests/unit/test_dashboard.py -q'
|
|
```
|
|
EXPECTED: `13 passed`.
|
|
2. Verify against the REAL host artifacts. Build a fixture of every `results.json` and run
|
|
`history_for` against it (no Drone, no network):
|
|
```
|
|
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")))'
|
|
```
|
|
EXPECTED:
|
|
- `bluesky-pds` count **8**, order EXACTLY
|
|
`['753','556','435','427','423','ab-bluesky-pds-oldmain','m2rr-bluesky-pds','m2r-bluesky-pds']`
|
|
(newest-first by `finished`; 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`).
|
|
- `plausible` capped at **30** (of 33), newest kept.
|
|
|
|
**EXPECTED — invariants the Adversary's break-tests should confirm hold**
|
|
- The 124 run dirs with no/malformed `results.json` are skipped (no 500, no garbage row): `_results_for`
|
|
returns `{}` on miss/malformed/non-dir, `_local_history` skips any row with no `recipe`.
|
|
- 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_file` still 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>.svg` still sourced from Drone latest-per-recipe (`_custom_recipe_builds`
|
|
/ `latest_per_recipe` unchanged) — 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 `results` map (`results.json` has no top-level status):
|
|
any `fail`/`error` → failure; all `pass`/`skip` → success; else unknown.
|
|
|
|
## Gate: M2 — NOT STARTED (deploy + live verify; begins after M1 PASS)
|
|
|
|
## Blocked
|
|
(none)
|