Some checks failed
continuous-integration/drone/push Build is failing
Phase 3 complete. U5 gate PASS @2026-05-31T13:13Z: - R6 per-recipe badge endpoint live (custom-html/uptime-kuma level 4, keycloak unknown fallback) - R8 docs/results-ux.md §1-5 complete, no TODOs - R7 render-kill: exit 0, install pass, results.json intact, no card/screenshot (u5-renderkill3) - R7 broad leak scan: 0 real secret values in any artifact or PR comment All R1–R8 verified <24h; STATUS-3 flipped to ## DONE.
96 lines
7.3 KiB
Markdown
96 lines
7.3 KiB
Markdown
# Phase 3 — Beautiful YunoHost-style results — BACKLOG
|
||
|
||
Single source of truth: `/srv/cc-ci/cc-ci-plan/plan-phase3-results-ux.md`.
|
||
Milestones U0–U5 (plan §5); each ends with an Adversary gate. DoD items R1–R8 (plan §2).
|
||
|
||
## Build backlog
|
||
|
||
### U0 — Results schema + level (R1)
|
||
- [x] U0.1 — Pure `level()` function (harness/level.py): L0–L6 gap-caps semantics; 15 unit tests
|
||
(incl L4-pass + L2-cap); Adversary fuzz-clean 729/729 (REVIEW-3 @df54693).
|
||
- [x] U0.2 — Per-tier pytest emits JUnit XML (parsed by harness/results.py) → results.json per-stage
|
||
AND per-test ✔/✘ breakdown.
|
||
- [x] U0.3 — `run_recipe_ci.py` writes `results.json` per run (level, cap_reason, rungs, stages,
|
||
flags) to the run-scoped artifact dir; assembly wrapped so it NEVER changes the verdict (R7).
|
||
- [x] U0.4 — Artifact hosting path decided + recorded in DECISIONS (`${CCCI_RUNS_DIR:-/var/lib/cc-ci-runs}/
|
||
<run_id>/`; dashboard serves `/runs/<id>/` in U2/U4 via host bind-mount).
|
||
- GATE U0: **PASS** (Adversary REVIEW-3 @18d2bd1, 2026-05-31) — R1 cold-verified, no inflation, no VETO.
|
||
|
||
### U1 — App screenshot (R4)
|
||
- [x] U1.1 — Harness captures a real Playwright screenshot of the deployed app while it is up
|
||
(default landing page = secret-safe; recipes opt into a post-login view via a SCREENSHOT meta
|
||
hook, never shoot a credentials page). Wired into run_recipe_ci.py post-healthy, pre-teardown.
|
||
- [x] U1.2 — Screenshot saved to run artifact dir (`screenshot.png`); results.json `screenshot` field
|
||
set ONLY when capture succeeds; degrades gracefully (capture() swallows all errors → None →
|
||
field null → run/verdict unaffected, R7).
|
||
- GATE U1: **PASS** (Adversary REVIEW-3 @74a6993, 2026-05-31) — R4 cold-verified (real screenshot of
|
||
working UI, no secrets, R7-safe wiring, graceful degradation), no VETO.
|
||
|
||
### U2 — Summary card + badge (R3, R6)
|
||
- [x] U2.1 — HTML results-card (recipe+version, level badge, per-stage/per-test ✔/✘ table, embedded
|
||
app screenshot) → PNG via Playwright; wired into run_recipe_ci.py, R7-best-effort.
|
||
- [x] U2.2 — Per-run SVG level badge (`badge.svg`) generated per run (shields-style, colour by level).
|
||
- [x] U2.3 — Card + badge + screenshot + results.json served at stable URLs
|
||
`/runs/<id>/{summary.png,badge.svg,screenshot.png,results.json}` (allow-list + traversal-guarded;
|
||
runs dir bind-mounted RO into the dashboard swarm service). LIVE over HTTPS, verified.
|
||
- GATE U2: **PASS** (Adversary REVIEW-3 @324d84d, 2026-05-31) — card+badge render correct for pass &
|
||
fail, served traversal-guarded, never-greener, leak-clean, R7-safe, no VETO. (R3/R6 stay partial
|
||
until embedded in PR comment (U3) + dashboard (U4) + per-recipe badge (U5).)
|
||
- Adversary polish items to fold in (low-sev, not gates): (a) dashboard `/runs/` HEAD→501 (no do_HEAD)
|
||
→ add do_HEAD (also enables a cheap bridge existence-check for U3 fallback); (b) per-recipe
|
||
latest-level badge endpoint → U5.
|
||
|
||
### U3 — YunoHost-style PR comment (R2)
|
||
- [x] U3.1 — Bridge posts a placeholder comment on run start (⏳ + live-logs link). `start_comment_body`,
|
||
reuses the marked comment if present (re-`!testme` refreshes to placeholder).
|
||
- [x] U3.2 — On completion, update the SAME comment to 🌻 + level/status badge + summary card image,
|
||
both linking to the run/dashboard. Re-`!testme` refreshes it. Fallback to text on render failure
|
||
(`result_comment_body` + `artifact_available` HEAD check). Deployed (bridge img 6377f9571f3b).
|
||
- [ ] U3.3 — Fold Drone repo activation into the drone reconcile so a DB reset self-heals: `POST
|
||
/api/repos/recipe-maintainers/cc-ci` (idempotent) BEFORE the timeout PATCH in drone.nix. Found
|
||
during the U3 live demo — the Hetzner-migration DB reset left the repo inactive (bridge `drone
|
||
trigger failed 404`); I reactivated by hand to run the demo. Not a U3 DoD item (cosmetics/comment
|
||
shape is); robustness hardening — fold in at U5 or flag to operator.
|
||
- GATE U3: **PASS** (Adversary REVIEW-3 @778b577, 2026-05-31) — image-forward comment live on
|
||
custom-html PR#2 (comment 13792), update-in-place cold-reproduced (run 4→7, never stacked), card
|
||
== results.json (no inflation), no secrets, deployed bridge == source. R2 satisfied; no VETO.
|
||
|
||
### U4 — Dashboard polish (R5)
|
||
- [x] U4.1 — Overview grid like `ci-apps.yunohost.org`: per-recipe level badge, latest pass/fail,
|
||
last-tested version, app screenshot/thumbnail, link to history (`/recipe/<name>`). `render_overview`
|
||
+ `_card` (dashboard.py @e1d837e).
|
||
- [x] U4.2 — Regenerated on build completion; reads results.json artifacts (`_results_for`,
|
||
`_build_row`; 30s cache + live render over the RO-bind-mounted runs dir).
|
||
- GATE U4: **PASS** (Adversary REVIEW-3 @9ca39dc, 2026-05-31) — grid + history cold-verified
|
||
never-greener vs results.json; honest uptime-kuma #11 failure row; no secrets; deployed == source;
|
||
9 tests; no VETO. R5 satisfied, **R3 fully satisfied** (card in comment + dashboard).
|
||
|
||
### U5 — Badges + docs + hardening (R6, R7, R8)
|
||
- [x] U5.1 — Embeddable per-recipe latest-level badge endpoint `/badge/<recipe>.svg` (level-coloured,
|
||
status fallback; `render_level_badge`, dashboard.py @91a69b8) + README-embed snippet documented.
|
||
Built + unit-tested; pending live deploy+verify.
|
||
- [x] U5.2 — `docs/results-ux.md` §1-5 complete: level ladder + tier→rung mapping, results.json schema,
|
||
card/screenshot generation, PR-comment shape, badge endpoints + README embed snippet (R8).
|
||
- [x] U5.3 — Hardening: render failure degrades to text (comment `artifact_available` HEAD →
|
||
text, unit-covered) + cosmetic render-kill proven verdict-unaffected (`u5-renderkill3`: card +
|
||
screenshot forced to raise → exit 0, install pass, results.json intact, no card/screenshot) +
|
||
new defense-in-depth try/except on the screenshot call site (`799cceb`); broad secret scan over
|
||
ALL published text artifacts + PR comments → zero real secret values (only `no_secret_leak`
|
||
flag name/label).
|
||
- GATE U5: **PASS** (Adversary REVIEW-3 @15b3057, 2026-05-31T13:13Z) — R6 badge live (3 URLs verified),
|
||
R8 docs complete (§1-5, no TODOs), R7 render-kill artifacts confirmed + broad leak scan clean
|
||
(0 real secret values in any artifact/comment). All R1–R8 verified. STATUS-3 `## DONE` flipped.
|
||
|
||
## Adversary findings
|
||
(Adversary owns this section — Builder does not edit.)
|
||
|
||
- [x] **A3-1 [adversary] — `/runs/<id>/<file>` returned 501 to HEAD requests** (low severity, polish).
|
||
**CLOSED @2026-05-31T09:34Z — re-tested live, fixed.** The dashboard `BaseHTTP` handler implemented
|
||
only `do_GET`, so `HEAD /runs/u1-uk-shot/summary.png` → `HTTP 501 Unsupported method`. The Builder
|
||
added a `do_HEAD` in `9a47aa2`, now deployed live. Re-verify (cold, from VM):
|
||
`curl -sSI https://ci.commoninternet.net/runs/u1-uk-shot/summary.png` → **HTTP/2 200**,
|
||
`content-type: image/png`, `content-length: 69313`, and **0-byte body** (`curl -X HEAD | wc -c` = 0
|
||
— correct HEAD semantics, headers only). badge.svg HEAD → 200 image/svg+xml. GET still 200/69313.
|
||
**Guards still hold under HEAD:** `HEAD …/evil.sh` → 404, `HEAD …/runs/nonexist-xyz/results.json`
|
||
→ 404 (whitelist + run-id guard not bypassed by method). Resolved; no regression.
|