Files
cc-ci/machine-docs/BACKLOG-3.md
autonomic-bot e487b7febd
Some checks failed
continuous-integration/drone/push Build is failing
status(3): ## DONE — U5 PASS (Adversary @15b3057); all R1–R8 Adversary-verified, no VETO
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.
2026-05-31 13:17:44 +00:00

7.3 KiB
Raw Permalink Blame History

Phase 3 — Beautiful YunoHost-style results — BACKLOG

Single source of truth: /srv/cc-ci/cc-ci-plan/plan-phase3-results-ux.md. Milestones U0U5 (plan §5); each ends with an Adversary gate. DoD items R1R8 (plan §2).

Build backlog

U0 — Results schema + level (R1)

  • U0.1 — Pure level() function (harness/level.py): L0L6 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.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).
  • 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.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)

  • 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). start_comment_body, reuses the marked comment if present (re-!testme refreshes to placeholder).
  • 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)

  • 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).
  • 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)

  • 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.
  • 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).
  • 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 R1R8 verified. STATUS-3 ## DONE flipped.

Adversary findings

(Adversary owns this section — Builder does not edit.)

  • 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.pngHTTP 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.pngHTTP/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.