plan: Recipe Report TESTS rename + live PR-STATUS column (public mirrors + same-origin proxy)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
110
cc-ci-plan/plan-report-pr-status-column.md
Normal file
110
cc-ci-plan/plan-report-pr-status-column.md
Normal file
@ -0,0 +1,110 @@
|
||||
# Plan — Recipe Report: TESTS + live PR-STATUS column
|
||||
|
||||
Goal (operator request): in the weekly Recipe Report table,
|
||||
1. **rename the current `Status` column → `TESTS`** (it shows the test/CI verdict: GREEN/FAILED/STALE/SKIPPED/UPTODATE — unchanged content, new label),
|
||||
2. **add a new `STATUS` column** showing the live state of the recipe's PR — **open** (still to review) or a **✓** when closed/merged,
|
||||
3. the PR state is fetched **client-side with JavaScript so it updates in realtime** (a visitor sees the current state, even on an archived past report),
|
||||
4. to support (3), **make the recipe repo mirrors public** (operator: this is fine).
|
||||
|
||||
## Findings that shape the plan
|
||||
- The table is built by `cc-ci-plan/recipe-report.py::_table`. Current columns:
|
||||
`Recipe | Change | Status | CVEs | CI | PR | Notes`. The `Status` cell carries a CSS class
|
||||
`s-GREEN`/`s-FAILED`/… (colour); the `CI` cell is the build number+link; `PR` is `#n`+link.
|
||||
The page is a **self-contained HTML** file (`_page`) served statically by the `ccci-reports`
|
||||
nginx stack at `https://report.ci.commoninternet.net`.
|
||||
- **Gitea CORS:** git.autonomic.zone has CORS middleware enabled, but it does **not** return
|
||||
`access-control-allow-origin` for `report.ci.commoninternet.net` (origin not in the instance's
|
||||
`[cors] ALLOW_DOMAIN`). That allow-list is autonomic.zone **instance config we don't control**, so a
|
||||
direct browser→Gitea API fetch will be **blocked by CORS**. → use a **same-origin proxy** (below).
|
||||
- **Mirror repos:** 25 repos under `recipe-maintainers`, **all private**. This set includes repos that
|
||||
must **NOT** be made public:
|
||||
- `cc-ci-secrets` (secrets!), `cc-ci-orchestrator` + `archived-cc-ci-orchestrator` (operational
|
||||
plans/journal), and arguably `cc-ci` (the harness/infra). **Exclude these.**
|
||||
- The **recipe mirrors** that SHOULD go public (they mirror public coopcloud recipes): bluesky-pds,
|
||||
cryptpad, custom-html, custom-html-tiny, discourse, ghost, immich, keycloak, lasuite-docs,
|
||||
lasuite-drive, lasuite-meet, mailu, matrix-synapse, mattermost-lts, mumble, n8n, plausible,
|
||||
uptime-kuma. (Decide on the test fixtures `custom-html-bkp-bad`/`custom-html-rst-bad` and
|
||||
`hedgedoc` — fine to include or skip; not in the 18-recipe fleet.)
|
||||
|
||||
## Realtime-status architecture (chosen: same-origin proxy)
|
||||
Browser JS calls a **same-origin** endpoint on the reports host; nginx proxies it to the Gitea API.
|
||||
No cross-origin request → no dependency on the co-op's CORS allow-list. Public mirrors mean the proxy
|
||||
needs no token (and the PRs are publicly viewable, which is the point).
|
||||
|
||||
- Add an nginx `location` to the `ccci-reports` stack (cc-ci repo, `reports.nix` / its nginx conf):
|
||||
```
|
||||
location ~ ^/pr/([a-z0-9._-]+)/([0-9]+)$ {
|
||||
resolver 127.0.0.11 ipv6=off; # docker DNS; or a public resolver
|
||||
proxy_ssl_server_name on;
|
||||
proxy_set_header Host git.autonomic.zone;
|
||||
proxy_pass https://git.autonomic.zone/api/v1/repos/recipe-maintainers/$1/pulls/$2;
|
||||
proxy_intercept_errors off;
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
```
|
||||
So `GET https://report.ci.commoninternet.net/pr/cryptpad/5` → the PR JSON (`{state, merged, …}`).
|
||||
(Restrict the regex to recipe-maintainers + a recipe-name charset; only proxies public PR reads.)
|
||||
- **Alternative (if the operator prefers no proxy):** ask the autonomic.zone Gitea admins to add
|
||||
`report.ci.commoninternet.net` (or `*`) to `[cors] ALLOW_DOMAIN`; then the JS can hit the Gitea API
|
||||
directly. Same JS, just a different base URL. Documented as a fallback; the proxy is the default
|
||||
because it has no external dependency.
|
||||
|
||||
## Work items
|
||||
|
||||
### A. Make the recipe mirrors public (scoped + safe)
|
||||
1. **Secret-scan** each recipe mirror before flipping: `git log -p`/`git grep` for tokens, real `.env`
|
||||
(not `.env.sample`), private keys; confirm `.drone.yml` uses secret *refs* not plaintext. (Low risk
|
||||
— they mirror public upstream — but verify; the flip is hard to walk back once indexed/cloned.)
|
||||
2. Confirm **no secrets in bot-authored PR comments / `!testme` output** posted on these repos (the PRs
|
||||
become publicly readable). Reports are already public-safe; spot-check a couple of PR threads.
|
||||
3. Flip visibility via the Gitea API (per repo): `PATCH /api/v1/repos/recipe-maintainers/<r>` with
|
||||
`{"private": false}`, for the recipe-mirror set ONLY (never the excluded repos above). Verify the
|
||||
`recipe-maintainers` org itself is visible enough that public repos are reachable.
|
||||
4. Verify an **unauthenticated** read now works: `curl https://git.autonomic.zone/api/v1/repos/recipe-maintainers/cryptpad/pulls/5` → 200 with `state`/`merged`.
|
||||
|
||||
### B. Reports nginx proxy (cc-ci repo — via /ci-dev-workflow)
|
||||
1. Add the `/pr/<recipe>/<n>` proxy location to the reports nginx config; rebuild/redeploy the
|
||||
`ccci-reports` stack. Verify `curl https://report.ci.commoninternet.net/pr/cryptpad/5` returns the
|
||||
PR JSON same-origin.
|
||||
2. (Optional hardening) small `proxy_cache` (e.g. 30–60 s) so many visitors don't hammer Gitea.
|
||||
|
||||
### C. Report generator (`cc-ci-plan/recipe-report.py` — orchestrator repo)
|
||||
1. `_table` header: rename `Status` → `TESTS`; add a `STATUS` column. Proposed order:
|
||||
`Recipe | Change | TESTS | CVEs | CI | PR | STATUS | Notes`.
|
||||
2. TESTS cell: unchanged (keep the `s-<STATUS>` colour class + the GREEN/FAILED/… text).
|
||||
3. STATUS cell: emit a JS hook carrying the repo + PR number from the existing row fields
|
||||
(`recipe`, `pr` like `#5`):
|
||||
`<td class="pr-status" data-repo="{recipe}" data-pr="{n}">…</td>` — blank cell when the row has no
|
||||
PR (e.g. merged-upstream custom-html, mumble). **No spec-shape change** — derives from existing
|
||||
`recipe` + `pr`.
|
||||
4. Add a small `<script>` (in `_page`, so every report + archive page gets it) that, on
|
||||
`DOMContentLoaded` and then on a ~30 s `setInterval` (true realtime while the tab is open), for each
|
||||
`.pr-status[data-pr]`:
|
||||
- `fetch('/pr/' + repo + '/' + pr)` → JSON;
|
||||
- render: `merged===true` → green **✓** (title "merged"); `state==='closed'` → grey **✗**
|
||||
(title "closed, not merged"); `state==='open'` → an **open** badge (amber/blue);
|
||||
network/parse error → a muted "?" (never break the page).
|
||||
5. Add CSS for the three states (`.pr-open`, `.pr-merged`, `.pr-closed`) + reuse the muted style.
|
||||
6. Keep it dependency-free (vanilla fetch), CSP-safe (inline script in the self-contained page), and
|
||||
resilient (try/catch per cell; the page renders fully even if the proxy is down).
|
||||
|
||||
### D. Verify + ship
|
||||
1. Render a report locally and publish to a **preview path** (`/badge-preview`-style, not the live
|
||||
index) to eyeball: TESTS column reads right, STATUS column populates live (open vs ✓) from real PRs.
|
||||
2. Cross-check a known-merged PR shows ✓ and a known-open PR shows "open"; flip one to confirm realtime
|
||||
(re-fetch updates without regenerating the page).
|
||||
3. Ship the generator change (orchestrator repo: commit + push). Ship the nginx proxy via
|
||||
`/ci-dev-workflow` (cc-ci PR → adversary verify → merge → deploy). Regenerate the current week's
|
||||
report so the live page gains the new columns.
|
||||
4. Update `recipe-report/SKILL.md` + the `_table`/page docstring to describe the new columns and the
|
||||
`/pr/<recipe>/<n>` dependency.
|
||||
|
||||
## Risks / decisions for the operator
|
||||
- **Public exposure is one-way in practice** (mirrors get cloned/indexed). The secret-scan (A.1/A.2)
|
||||
is the gate. Confirm the excluded set (cc-ci-secrets, cc-ci-orchestrator, archived, cc-ci) is right.
|
||||
- **Proxy vs CORS allow-list:** plan defaults to the same-origin proxy (no external dependency). If you
|
||||
can get `report.ci.commoninternet.net` added to the autonomic Gitea `[cors] ALLOW_DOMAIN`, we can
|
||||
drop the proxy and have the JS hit Gitea directly — say which you prefer.
|
||||
- **Load:** ~20 unauth PR reads per page view. With proxy caching (B.2) this is negligible.
|
||||
- **"closed not merged"** rendering: do you want ✓ only for merged, and a distinct ✗/grey for
|
||||
closed-without-merge? (Plan assumes yes.)
|
||||
Reference in New Issue
Block a user