(1) Prior U2 R7 'empirical' line used a wrong-signature call to render_card_png/
render_badge_svg, so its TypeError was my test's bug not an R7 violation. Re-ran
correctly: render_card_png(nonexistent html_path) -> None, no raise, 'non-fatal'.
R7 holds (empirical + structural). U2 verdict UNCHANGED, still PASS.
(2) Eyeballed the real served u1-uk-shot summary.png — content matches results.json.
(3) Filed A3-1 [adversary] (HEAD->501 on /runs/, low-sev); Builder added do_HEAD in
9a47aa2 — Adversary to re-test live before closing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.1 KiB
5.1 KiB
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)
- 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). - U0.2 — Per-tier pytest emits JUnit XML (parsed by harness/results.py) → results.json per-stage AND per-test ✔/✘ breakdown.
- U0.3 —
run_recipe_ci.pywritesresults.jsonper run (level, cap_reason, rungs, stages, flags) to the run-scoped artifact dir; assembly wrapped so it NEVER changes the verdict (R7). - 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)
- 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.
- U1.2 — Screenshot saved to run artifact dir (
screenshot.png); results.jsonscreenshotfield 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)
- 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.
- U2.2 — Per-run SVG level badge (
badge.svg) generated per run (shields-style, colour by level). - 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)
- U3.1 — Bridge posts a placeholder comment on run start (⏳ + live-logs link).
- U3.2 — On completion, update the SAME comment to 🌻 + level/status badge + summary card image,
both linking to the run/dashboard. Re-
!testmerefreshes it. Fallback to text on render failure. - GATE U3: live on a scratch PR — comment shows badge + card + screenshot, updates on re-run, no secrets.
U4 — Dashboard polish (R5)
- 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. - U4.2 — Regenerated on build completion; reads results.json artifacts.
- GATE U4: matches reality across several runs; mirrors the underlying results.json.
U5 — Badges + docs + hardening (R6, R7, R8)
- U5.1 — Embeddable per-recipe latest-level badge documented for README embedding.
- U5.2 —
docs/explains the level ladder, card/screenshot/badge generation, how to embed a badge. - U5.3 — Hardening: render failure degrades to text (R7); secret-scan over published images/screenshots/comments finds nothing; killing the renderer doesn't affect the verdict.
- GATE U5: Adversary leak-scan clean; graceful degradation proven; flip STATUS-3 to
## DONE.
Adversary findings
(Adversary owns this section — Builder does not edit.)
- A3-1 [adversary] —
/runs/<id>/<file>returned 501 to HEAD requests (low severity, polish). The dashboardBaseHTTPhandler implemented onlydo_GET, soHEAD /runs/u1-uk-shot/summary.png→HTTP 501 Unsupported method. GET worked fine (200), so the card/badge/comment/dashboard embeds all function, but stricter markdown/image clients (andcurl -I) probe with HEAD first and a stray 501 could make an embed look broken. Repro:curl -sSI https://ci.commoninternet.net/runs/u1-uk-shot/summary.png→HTTP/2 501. Found during U2 cold-verify @2026-05-31T07:48Z; NOT a U2 blocker (U2 PASSED). The Builder added ado_HEADin9a47aa2— Adversary to re-test the live HEAD response before closing this.