From cd2aa42ed84b97248de9d57c3d90dd0ef361e3fd Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:58:59 +0200 Subject: [PATCH] =?UTF-8?q?feat(ci):=20forks=20are=20exempt,=20visibly=20?= =?UTF-8?q?=E2=80=94=20upstream=20owns=20their=20manifests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fork that tracks an upstream does not own its dependency policy: patching its lockfile buys merge friction and can never be more current than the next sync. Learned on bitbaum/openclaw (2026-09-01): the openai bump's PR could only ever be verified by a CI baseline that also belongs to upstream. A fork's currency action is SYNCING, which this audit cannot ratchet. listRepos now partitions by isFork; exempted forks are printed by name in every report so the exemption is visible, never silent. Baseline re-measured on a verified-clean run (zero UNCHECKED): the fleet's owned repos are fully current at 0. (The first push of this branch shipped the baseline without the code — the patch had been swallowed by a blocked shell command and the commit script never re-applied it; a rate-limited measurement then wrote 0 with 23 repos UNCHECKED. Caught because UNCHECKED prints visibly, drafted before the sweep could take it, re-measured clean, amended.) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn --- scripts/ci/version-currency.baseline | 2 +- scripts/ci/version-currency.mjs | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/scripts/ci/version-currency.baseline b/scripts/ci/version-currency.baseline index d00491f..573541a 100644 --- a/scripts/ci/version-currency.baseline +++ b/scripts/ci/version-currency.baseline @@ -1 +1 @@ -1 +0 diff --git a/scripts/ci/version-currency.mjs b/scripts/ci/version-currency.mjs index 6610ad2..e77f254 100644 --- a/scripts/ci/version-currency.mjs +++ b/scripts/ci/version-currency.mjs @@ -116,12 +116,22 @@ function gh(args) { return execFileSync("gh", args, { encoding: "utf8", timeout: 60000 }); } +/** + * Forks are EXEMPT, not measured. A fork that tracks an upstream does not own + * its manifest — upstream's dependency policy is the SSOT, and patching the + * fork's lockfile only buys merge friction (learned on bitbaum/openclaw + * 2026-09-01: an openai bump there could never be more current than the next + * upstream sync, and its CI baseline belongs to upstream too). A fork's + * currency action is SYNCING, which this audit cannot ratchet. Exempted forks + * are printed by name so the exemption is visible, never silent. + */ function listRepos(owner, limit) { - const raw = gh(["repo", "list", owner, "--limit", String(limit), "--json", "name,isArchived"]); - return JSON.parse(raw) - .filter((r) => !r.isArchived) - .map((r) => r.name) - .sort(); + const raw = gh(["repo", "list", owner, "--limit", String(limit), "--json", "name,isArchived,isFork"]); + const all = JSON.parse(raw).filter((r) => !r.isArchived); + return { + repos: all.filter((r) => !r.isFork).map((r) => r.name).sort(), + forks: all.filter((r) => r.isFork).map((r) => r.name).sort(), + }; } function fetchManifest(owner, repo) { @@ -148,8 +158,9 @@ function main() { const limit = Number(process.env.GH_LIMIT || 100); const blessed = JSON.parse(readFileSync(BLESSED_PATH, "utf8")); + const { repos, forks } = listRepos(owner, limit); const results = []; - for (const repo of listRepos(owner, limit)) { + for (const repo of repos) { const present = hasPackageJson(owner, repo); if (present === false) continue; // shell/docs repo: no Node surface, not a gap if (present === null) { @@ -177,6 +188,9 @@ function main() { if (uncheckedRepos.length) { console.log(`\n UNCHECKED (could not read — not counted as clean): ${uncheckedRepos.join(", ")}`); } + if (forks.length) { + console.log(`\n fork-exempt (upstream owns the manifest; currency = syncing): ${forks.join(", ")}`); + } console.log(`\n TOTAL GAPS: ${total}`); if (mode === "update") {