From c7b5dc04ccf6b7fdcb9b883e5272a6eb89ba48d9 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Sun, 31 May 2026 09:47:00 +0000 Subject: [PATCH] =?UTF-8?q?claim(3=20U3):=20YunoHost-style=20PR=20comment?= =?UTF-8?q?=20LIVE=20on=20custom-html=20PR#2=20(comment=2013792)=20?= =?UTF-8?q?=E2=80=94=20=F0=9F=8C=BB=20+=20level=20badge=20+=20summary=20ca?= =?UTF-8?q?rd=20images=20linked,=20updates=20in=20place=20on=20re-!testme,?= =?UTF-8?q?=20no=20secrets;=20R2=20satisfied?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machine-docs/ADVERSARY-INBOX.md | 17 ++++++++ machine-docs/BACKLOG-3.md | 13 ++++-- machine-docs/STATUS-3.md | 71 +++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 machine-docs/ADVERSARY-INBOX.md diff --git a/machine-docs/ADVERSARY-INBOX.md b/machine-docs/ADVERSARY-INBOX.md new file mode 100644 index 0000000..9548cf1 --- /dev/null +++ b/machine-docs/ADVERSARY-INBOX.md @@ -0,0 +1,17 @@ +# ADVERSARY-INBOX (Builder → Adversary) + +## 2026-05-31T09:45Z — U3 CLAIMED (YunoHost-style PR comment); live demo artifact map + +U3 is claimed in STATUS-3 (`claim(3 U3)`). Everything you need to cold-verify is in the STATUS-3 U3 +section (WHAT/HOW/EXPECTED/WHERE). Quick pointers: + +- **Live demo:** `recipe-maintainers/custom-html` **PR #2**, bot comment **id 13792** (the one + marked ``). It cycled ⏳placeholder→result on the first `!testme` (build #3) + and again on a re-`!testme` (build #4) — same comment id, never stacked. +- **Served artifacts:** `https://ci.commoninternet.net/runs/{3,4}/{summary.png,badge.svg,screenshot.png,results.json}`. +- **Heads-up (not a verdict ask):** I had to **reactivate `recipe-maintainers/cc-ci` in Drone** — the + Hetzner-migration DB reset left it inactive (bridge `drone trigger failed 404`), so the build counter + reset to 1 (hence builds #1–#4, not the Phase-1 numbering). Repo is `active=true` now, so you can + post your own `!testme` on PR #2 to cold-reproduce; it refreshes comment 13792. Self-heal hardening + filed as BACKLOG-3 U3.3. +- The embedded app screenshot is custom-html's "Welcome to nginx!" page — no secret values. diff --git a/machine-docs/BACKLOG-3.md b/machine-docs/BACKLOG-3.md index d0044d6..b951fed 100644 --- a/machine-docs/BACKLOG-3.md +++ b/machine-docs/BACKLOG-3.md @@ -41,9 +41,16 @@ Milestones U0–U5 (plan §5); each ends with an Adversary gate. DoD items R1– 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-`!testme` refreshes it. Fallback to text on render failure. +- [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: live on a scratch PR — comment shows badge + card + screenshot, updates on re-run, no secrets. ### U4 — Dashboard polish (R5) diff --git a/machine-docs/STATUS-3.md b/machine-docs/STATUS-3.md index ba55aa8..b64e872 100644 --- a/machine-docs/STATUS-3.md +++ b/machine-docs/STATUS-3.md @@ -165,11 +165,74 @@ The cardinal U2 invariant: the rendered card/level/badge are a faithful, never-g results.json + the actual test outcomes, served at a stable URL, generated best-effort so a render failure never blocks the run. +## Gate: U3 — CLAIMED, awaiting Adversary (YunoHost-style PR comment; R2) + +**WHAT.** On a `!testme` run the bridge now posts/updates ONE Gitea PR comment in the YunoHost shape: +on run start a 🌻 + ⏳ **placeholder** ("level pending", live-logs link); on completion it edits the +**SAME** comment in place to 🌻 + a **level badge** image + a **summary card** image, BOTH linked to +the full run, plus full-logs/dashboard links. A re-`!testme` refreshes that same comment (back to ⏳, +then to the new result) — never stacks a new one (R2 "one comment per PR, updated in place"). Falls +back to a compact text verdict if the rendered card isn't served (R7). DoD **R2** satisfied; U3 +acceptance ("live on a scratch PR — comment shows badge + card + screenshot, updates on re-run, no +secrets") demonstrated on a real scratch PR. (This also lands R3's "embedded in the comment" +sub-requirement; R3 still needs "in dashboard" at U4.) + +**WHERE (commits / files).** +- `9a47aa2` `bridge/bridge.py` — `COMMENT_MARKER` (hidden HTML comment ``), + `start_comment_body` (⏳ placeholder), `result_comment_body` (🌻 + badge + card, linked; text + fallback), `find_existing_comment` (marker → update-in-place), `artifact_available` (HEAD existence + check → image-vs-text), `watch_and_reflect` now edits to `result_comment_body`. Card/badge URLs are + `${DASH_URL}/runs//{summary.png,badge.svg}` (run_id == Drone build number, see + `runner/harness/results.py::run_id`). +- `9a47aa2` `dashboard/dashboard.py` — `do_HEAD` (shared `_route` with GET) so HEAD existence-checks + + strict image clients get 200, not 501 (closes Adversary A3-1, already re-verified @8807240). +- `9a47aa2` `tests/unit/test_bridge_trigger.py` — covers placeholder shape, image-forward result, + **text fallback when card missing**, marker-based find/update-in-place. +- **Deployed:** bridge swarm image `cc-ci-bridge:6377f9571f3b` == `sha256(bridge.py)` first-12 (content + tag, confirmed live); dashboard image live with `do_HEAD`. + +**HOW to verify (cold, from your clone / the VM).** +1. **Unit tests** (on cc-ci): `cc-ci-run -m pytest tests/unit/test_bridge_trigger.py tests/unit/test_card.py -q` → `15 passed`. +2. **Deployed bridge == source:** `ssh cc-ci 'sha256sum /etc/cc-ci/bridge/bridge.py | cut -c1-12'` → + `6377f9571f3b`; `ssh cc-ci 'docker service ls | grep ccci-bridge'` shows image tag `6377f9571f3b`. +3. **LIVE demo on scratch PR** `recipe-maintainers/custom-html` **PR #2** (recipe == repo name; the + bridge poller, 30s, fires on a NEW `!testme`). The bot comment carrying the marker is **id 13792**: + `curl -s -u "$GITEA_USERNAME:$GITEA_PASSWORD" https://git.autonomic.zone/api/v1/repos/recipe-maintainers/custom-html/issues/comments/13792` + → body has ``, 🌻, `✅ passed`, `[![cc-ci result card](…/runs/4/summary.png)](…/4)`, + `[![level](…/runs/4/badge.svg)](…/4)`, full-logs+dashboard links. (You may post your own `!testme` + on PR #2 — the repo is active in Drone; it will refresh **the same** comment 13792.) +4. **Images render (served):** `for f in summary.png badge.svg screenshot.png results.json; do + curl -s -o /dev/null -w "$f %{http_code}\n" https://ci.commoninternet.net/runs/4/$f; done` → all 200. +5. **Updates in place / no stacking:** the marked-comment set on PR #2 stays exactly `[13792]` across + runs #3 (first `!testme`) and #4 (re-`!testme`); the comment cycled ⏳→result both times. (Filter + comments for `` — there is exactly one.) +6. **No secrets:** scan the comment body + `/var/lib/cc-ci-runs/{3,4}/{results.json,summary.html}` for + `password|secret|token|passwd|api_key|privkey|PRIVATE` → only the `no_secret_leak` flag-name matches; + the embedded app screenshot is custom-html's **"Welcome to nginx!"** page (no values). +7. **No inflation:** the card for run #4 shows `level 4` / `capped: L5 integration N/A`, all + install/upgrade/backup/restore/custom rows ✔ — matches `/runs/4/results.json` verbatim. + +**EXPECTED.** +1. `15 passed`. 2. tag `6377f9571f3b` both places. 3. comment 13792 body exactly as above (run 4). +4. all four `/runs/4/` files 200 (`summary.png` ~178 KB, `badge.svg` 342 B, `screenshot.png` 35707 B). +5. exactly one marked comment (`13792`); no new comment stacked on re-run. 6. zero real secret hits. +7. card level 4, all rows ✔, == results.json (`recipe=custom-html`, `level=4`, all tiers pass, + `flags.clean_teardown=true,no_secret_leak=true`). + +The cardinal U3 invariant: ONE comment per PR, refreshed in place; the embedded card/badge are a +faithful never-greener projection of the run; image-gen failure degrades to text and never blocks the +run or the verdict. + ## In flight -- U3 — YunoHost-style PR comment (IN PROGRESS): bridge posts 🌻 + ⏳ start comment, edits same comment - on completion to 🌻 + level badge image + summary card image (both linking to the run/dashboard), - text fallback on render failure. URLs: `/runs//{badge.svg,summary.png}`. Also - adding dashboard `do_HEAD` (Adversary polish item + cheap bridge existence-check). +(none — U3 claimed; U4 dashboard polish next, unblocked work can start while parked at the U3 gate) ## Blocked (none) + +## Note — Drone repo reactivation (infra, recorded for the Adversary) +The Hetzner-migration Drone DB reset left `recipe-maintainers/cc-ci` **inactive** (bridge log `drone +trigger failed 404`); the bridge can't trigger builds when the repo is inactive. I reactivated it +(in-scope reconfig of my own CI, reversible): `POST /api/user/repos?async=false` then `POST +/api/repos/recipe-maintainers/cc-ci` → `active=true`, config_path `.drone.yml`, timeout 60. This is +why builds #1–#4 above exist (counter reset to 1 by the DB reset). Self-heal hardening filed as +BACKLOG-3 U3.3 (fold activation into the drone reconcile) — not a U3 DoD item.