Why: gitea 1.27.1 fixed CVE-2026-60004 + CVE-2026-59774 (both CVSS 9.8). The
2026-08-03 report printed gitea's CVE count as '1', the 2026-08-07 report as
'none'. Cause chain: the upgrade subagent read the GitHub release notes, which
name NEITHER cve (they are announced only in the vendor blog's security section),
so it recorded one unrelated minor item; the report then derived security content
from those notes plus model knowledge, and the model's training predates the CVEs.
Nothing in the pipeline ever queried an advisory source.
cc-ci-plan/advisory-scan.py — deterministic, per recipe, per upgrade window:
1. GitHub Security Advisories API for every source repo in the upstream registry.
PRIMARY: CVE + GHSA + severity + vulnerable/patched ranges, so 'fixed by THIS
upgrade' is computed. Needs no new per-recipe config (134 registry URLs are
already github.com).
2. Vendor release/security pages — every registry URL, fetched + regex-scanned.
This is the source that actually had the gitea CVEs.
3. OSV where a package mapping exists — supplementary.
Each source reports its own status so 'checked, none found' is never confused with
'not checked'. Source selection was measured, not assumed: for these two CVEs OSV
404'd and NVD's API had them by neither CPE, id, nor keyword — advisory DBs lag the
vendor, hence 1+2 lead.
Wiring is strictly ADDITIVE:
- /recipe-upgrade gains step 2a: run the scan, paste the block into the per-recipe
log, and report the UNION of it and the existing release-note reading. The scan
may never lower a count established by reading.
- /recipe-report treats the block as a FURTHER source, prefers its advisory ids /
severities / fixed-in versions for citation, and must render '?' (not 'none')
when a scan is absent or has failed sources — the false-clean 'none' is exactly
what happened on 2026-08-07.
- upstream/gitea.md records blog.gitea.com as the security-announcement URL.
Verified on the real regression: 1.27.0 -> 1.27.1 now yields exactly the 2 missed
criticals with their GHSA ids; the wider 1.26.2 -> 1.27.1 window yields 62.
8.0 KiB
name: recipe-report description: Generate the weekly public "Recipe Report" after the cc-ci /upgrade-all run. Reviews how the upgrade went and the state of every recipe + open PR, and publishes a self-contained HTML page to report.ci.commoninternet.net (one page per week + a home index). Page order: short lead → the full wire table (priority-sorted, CVE-count column, CVE recipes first) → Addendum of special issues → Security Bulletin → per-recipe "what changed". Read-only — reports, never merges or edits PRs. Runs as its own agent (model separately overridable via REPORT_BACKEND/REPORT_MODEL; defaults to the upgrader's backend+model, opencode-go/glm-5.2). Invoke as /recipe-report [YYYY-MM-DD].
recipe-report
Produce the weekly "The Recipe Report" — a public HTML page reviewing the latest /upgrade-all run
and the live state of every recipe + open PR. Served at https://report.ci.commoninternet.net (one
week-<date>.html per run + a home-page index). You are read-only: you report, you never merge,
edit, or comment on PRs. The page is public + unauthenticated — include only public-safe data
(recipe names, version bumps, PR titles/links, CI verdicts, concise error summaries); never secrets,
tokens, internal IPs, or raw logs.
Helper: python3 /srv/cc-ci/cc-ci-plan/recipe-report.py {survey|render|publish} (see its header).
Deterministic formatting — spec in, page out (NON-NEGOTIABLE). ALL layout, CSS, and HTML is owned by
recipe-report.py's render(). Your ONLY output artifact is the spec JSON (data + editorial text).
You never write, edit, post-process, or templatize HTML yourself; you never put markup/inline styles
in spec text fields (they are HTML-escaped on purpose); the page is produced solely by
render <spec> <out>. Same spec shape every week → a structurally identical page. If the layout itself
must change, edit recipe-report.py (and example-spec.json) — never the generated page. This is what
keeps every weekly edition looking the same regardless of which model writes the spec.
Procedure
-
Pick the date.
DATE= the argument, else today (UTC). The/upgrade-allsummary for that run is/srv/cc-ci/.cc-ci-logs/upgrades/upgrade-all-<DATE>.md. -
Survey.
python3 /srv/cc-ci/cc-ci-plan/recipe-report.py survey <DATE> > /tmp/survey.json. This gives you: the raw/upgrade-allsummary; for every recipe mirror its open PRs + thecc-ci/testmeverdict; and each recipe's per-recipe upgrade notes (upgrade_notes_md— the breaking-change / CVE / security analysis the upgrade did). Read it all. -
Review & classify. The page order is: short lead → the full wire table (priority-sorted) → Addendum → Security Bulletin → per-recipe "What changed". Same newspaper style; this order.
- Security analysis. Scan the per-recipe
upgrade_notes_md+ the summary (and use your own knowledge of the version bumps) for upgrades that fix CVEs / security issues. For each recipe, count the CVEs the PR fixes — this drives both the table'scvecolumn and the priority sort.- ADDITIONALLY, and never instead: each per-recipe log carries an
### Advisory scan (deterministic pre-step)block (fromcc-ci-plan/advisory-scan.py— GitHub Security Advisories + vendor security pages + OSV, with severities and fixed-in versions). Treat its CVE list as a further source and report the UNION of it and what you found by reading. Its entries are machine-derived with advisory IDs, so prefer them for CVE ids / severities / fixed-in versions, and cite the GHSA where present in the Security Bulletin. If a recipe has no scan block, or the block lists failed sources, the count is not authoritative: render the cve cell as?(unknown), nevernone— a blank that reads as "clean" is exactly how two CVSS-9.8 gitea RCEs were reported as "none" on 2026-08-07. Anything critical/high also gets asecuritybulletin entry (recipe · CVE id(s) + severity · what it fixes · PR link); be specific about severity and what's exposed if not merged.
- ADDITIONALLY, and never instead: each per-recipe log carries an
- Lead — ONE short paragraph. A tight, concrete opener in opus's voice: fleet state in a sentence (how healthy, what moved) and what to address first. Lead with the single most important thing; the table and sections carry all the detail. Do NOT write multiple paragraphs.
- Priority sort for the table. Order rows by recommended priority to address:
- recipes with CVEs first (more/higher-severity CVEs higher),
- then FAILED (RED) recipes,
- then STALE-test recipes,
- then routine GREEN bumps,
- then UPTODATE / SKIPPED last.
- Addendum — special issues to look into. A bullet list of real anomalies worth an operator's eye: a recipe carrying two open PRs to reconcile, a CI-run oddity (flaky tier, needed re-runs), drift between the summary and reality, a misleading branch name, a tooling snag that silently drops a recipe, leftover artifact PRs, or something in the test run that could be improved. Only real issues — if everything looks clean, leave it empty; do NOT invent items.
- What changed — per recipe with a PR. One short section per recipe that has a PR this week describing what the upgrade actually changes (versions, notable deps, migrations/gotchas, reconcile notes). Skip recipes with no PR.
- Security analysis. Scan the per-recipe
-
Write the spec
/tmp/report-spec.json(shape in the helper header):date,subtitle("Week of "),lead— one short paragraph,table[]— EVERY recipe, sorted by the priority above. Fields:recipe,change"a → b",statusGREEN/FAILED/STALE/SKIPPED/UPTODATE,cve(integer count of CVEs this PR fixes;0or omit for none),cias a level/result number+info string e.g.build 154 ✓orRED 200 · installwithci_url,pr/pr_url,notes. CI = link + number/info only; no images.- The table renders two status columns: TESTS (the
statusfield — the CI/test verdict) and STATUS (the PR's LIVE state, fetched client-side). You only supplystatus; the live STATUS column is derived automatically fromrecipe+prand needs no spec field — it showsopenvs a ✓ (any not-open state), refreshing ~30s via the same-origin/pr/<recipe>/<n>proxy (cc-cinix/modules/reports.nix); it degrades to a muted "?" if the proxy/repo is unreachable.
- The table renders two status columns: TESTS (the
addendum[]— list of bullet strings (may be empty),security[]— critical-CVE bulletin entries (may be empty),changes[]—{recipe, body, links[]}, one per recipe that has a PR.
Template — match this.
.claude/skills/recipe-report/example-spec.jsonis the gold-standard example (operator-approved style/format/voice). Match its editorial voice, field shapes, and level of specificity, following the section order above. Read it before writing your spec. -
Render & publish — via the script only; never hand-edit the HTML.
python3 .../recipe-report.py render /tmp/report-spec.json /tmp/week-<DATE>.htmlpython3 .../recipe-report.py publish /tmp/week-<DATE>.html <DATE>(publish copies the page tocc-ci:/var/lib/cc-ci-reports/and regenerates the index.) Ifrendererrors, fix the spec JSON (a malformed field) and re-run render — do NOT work around it by writing HTML by hand. The rendered file is an artifact of the spec, never edited directly. -
Verify & stop.
curl -fsS https://report.ci.commoninternet.net/week-<DATE>.htmlrenders and the index lists it. Print the report URL andRECIPE REPORT COMPLETE, then go idle (one-shot — do not loop).
Guardrails
- Read-only. Never merge/edit/comment on PRs or touch recipes/tests. You only describe + link.
- Public-safe. No secrets/tokens/internal hostnames/raw logs on the page. Summarize errors.
- One page per run; always regenerate the index so all weeks are listed (newest first).