Files
cc-ci-orchestrator/cc-ci-plan/plan-report-pr-status-column.md

121 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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) vs a **✓** for **any state that is not open** (merged OR closed-without-merge both show ✓; no separate "closed unmerged" treatment — operator-decided),
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).
> **Operator decisions (locked):** (a) STATUS is binary — `open` vs ✓ (not-open). (b) The realtime
> proxy goes on the **cc-ci server via nix**, in the SAME stack/way the report itself is served
> (`reports.nix`) — NOT a separate service. (c) Exclusion set for the public flip confirmed: make the
> recipe mirrors public; keep `cc-ci-secrets`, `cc-ci-orchestrator`, `archived-cc-ci-orchestrator`,
> and `cc-ci` private.
## 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 — in `reports.nix`, via nix (cc-ci repo, /ci-dev-workflow)
The proxy lives in the SAME place and is shipped the SAME way as the report serving itself: in the
cc-ci repo's `reports.nix` (the `ccci-reports` nginx stack), deployed via the nix rebuild — NOT a
separate service or a hand-edited container.
1. Locate how `reports.nix` defines the nginx config for `ccci-reports` (the static `/var/lib/cc-ci-reports`
server). Add the `/pr/<recipe>/<n>` `location` (above) into that same nginx config, so it's built
and deployed by the same mechanism. Resolver/SNI as needed for the `git.autonomic.zone` https upstream.
2. Ship via `/ci-dev-workflow` (branch cc-ci → adversary verify → merge → `nixos-rebuild` deploy).
Verify `curl https://report.ci.commoninternet.net/pr/cryptpad/5` returns the PR JSON same-origin.
3. (Optional hardening) a small `proxy_cache` (e.g. 3060 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 **binary**: `state === 'open'` → an **`open`** badge; **any other state** (closed/merged) →
a green **✓** (title shows merged vs closed for hover detail, but the glyph is ✓ either way);
network/parse error → a muted "?" (never break the page).
5. Add CSS for the two states (`.pr-open` badge, `.pr-done` ✓) + 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 / notes
- **Public exposure is one-way in practice** (mirrors get cloned/indexed). The secret-scan (A.1/A.2)
is the hard gate before any flip — do not flip a repo that doesn't pass. Excluded set locked:
`cc-ci-secrets`, `cc-ci-orchestrator`, `archived-cc-ci-orchestrator`, `cc-ci` stay private.
- **Load:** ~20 unauth PR reads per page view. With proxy caching (B.3) this is negligible.
- **Order of ops:** the STATUS column is useless until the proxy is live AND the repos are public, but
it degrades gracefully (muted "?") if either is missing — so the generator change can ship first and
light up once B + A land. Recommended order: A (public) → B (proxy) → C (generator) → D (verify).