Repository navigation
ci(hygiene): measure branch drift and enforce the two pull-request rules - #285
Conversation
main's remote held fifteen heads on 2026-10-25: 'main', the orphan data branch the badge publisher force-pushes, and thirteen branches cut from snapshots between 5 and 119 commits old. Every one of the thirteen had an open pull request, so a reviewer saw thirteen blocked pull requests and no way to tell which was alive, and each started a full CI run that could not go green. CONTRIBUTING.md already required a branch to start at the tip of main (rule 1) and to carry no merge commits from it (rule 2). Nothing enforced either sentence. This adds the enforcement and the measurement, and nothing that deletes anything: - scripts/maintenance/check-pr-freshness.sh answers rules 1 and 2 from the merge base and the commit graph, annotating the failure for the checks API; pr-freshness.yml runs it on every pull request. - scripts/maintenance/triage-branches.sh classes every remote head as base, unrelated, contained, fresh, salvage or stale, so cleanup is decided on measurement. branch-hygiene.yml runs it weekly into ONE rolling issue that closes itself when no drifted branch remains. - Both refuse a shallow clone instead of reporting its graft point as the merge base, which would pass every branch. - Both are tested against fixture repositories: merged, drifted with nothing new, drifted with a file the base no longer has, orphan, unrelated history and shallow, plus the Actions annotation path. - docs/maintenance/repository-hygiene.md records the classes, the cleanup order and the measurement; CONTRIBUTING.md points at it. The work already done with these scripts is on the remote rather than in this diff: the thirteen superseded pull requests are closed and their branches deleted, each with its own measured rationale, and each recoverable from refs/pull/<n>/head. governance-badges is untouched: it is an output, not a spare branch. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 25 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Fork governance
🟣 critical · commit
Hypatia scan: 76 finding(s)
panic-attack assail: 8 finding(s)
rhodibot (RSR compliance): not_run — enrolled, but no report-back was received
panicbot (panic recovery, error handling): not_run — enrolled, but no report-back was received
echidnabot (formal verification: proofs/): not_run — enrolled, but no report-back was received
seambot (integration health: API routes): not_run — enrolled, but no report-back was received
finishingbot (release readiness): not_run — enrolled, but no report-back was received
accessibilitybot (WCAG on the React UI): not_run — enrolled, but no report-back was received
glambot (presentation quality): not_run — enrolled, but no report-back was received
cipherbot: not_applicable — no cryptography is implemented in this repository
sustainabot: not_applicable — fleet adapter slot reserved; no adapter exists yet (gitbot-fleet README)
Scope: static findings reported by each row's tool at the named commit. Purple, red, yellow and blue grade the worst finding present; orange is evidence missing, malformed or a scanner that did not run; grey is an explicit scope exclusion with its reason. No row is a security certification. report.json, badge.json and the raw findings are in the verdict-grid artifact on the linked run. 🟢 conformant · commit
Scope: K9 components L0–L2 against contract v1.0.0 at the named standards commit. L3 signature verification and routed coordination-dialect files are not certified. Green is limited to this scope. Orange is missing evidence; yellow is grandfathered debt or warnings; grey is an explicit scope exclusion. report.json, badge.json and the validator logs are in the k9-grid artifact on the linked run. |
The first run of the new freshness check failed on its own pull request: actions/checkout checks out refs/pull/<n>/merge for a pull_request event, and that commit is a merge by construction, so rule 2 failed on every branch. Check out github.event.pull_request.head.sha instead, and have the script name the case it cannot distinguish from a real violation: the head being a merge means either the author merged the base in, or CI checked out the merge ref. The hint says how to tell, and a test covers it. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
|
## Change and reason Three follow-ups to #285. One of them is a **correction of a mistake #285 made**, which is the main reason this exists. ### 1. Declared lanes are not drift — and #285 got this wrong The deed (`MetaManifold-WebUI_chora.deed`, routing `:upstream-candidates "upstream/*"`) declares `upstream/*` a lane: work destined for the upstream remote, squash-merged there (`notes/commit-signing.md` records #192 landing that way). `CONTRIBUTING.md` declares `archive/*` and `governance-badges`. The triage in #285 classed branches **by commit graph alone**, so it reported `upstream/placement-evidence` as `salvage` — a class whose documented remedy is "close it, or re-apply it elsewhere" — and #270 was closed with it. Deleting a laned branch discards a routing decision, not a commit. The content is recoverable (`refs/pull/270/head`), but the lane is gone and re-cutting it is the owner's call. So: - lanes are read from the **deed** (with `upstream/*` as the fallback for a bare clone that has no deed checked out) plus the two documented in `CONTRIBUTING.md`; - the triage prints a `LANE` column and counts laned branches separately; - **a laned branch does not raise `--fail-on`** unless `--include-lanes` is passed; - `branch-hygiene.yml` now takes its counts from the triage's own summary lines instead of grepping table rows (which would have broken the moment a column was added, and would have counted lanes as drift), and the issue body says how many laned branches it is deliberately not counting. Corrected in the docs under *Declared lanes are not spare branches*, with decision 5 recording what to do about that candidate. ### 2. Dependabot PRs would have gone red for the wrong reason Dependabot does not rebase by default. With two open updates, the second goes behind `main` the moment the first merges, and the new **Branch freshness** check fails a one-line dependency bump for a rule the bump did not break. Both ecosystems now set `rebase-strategy: auto`, so red on that check means what it says: a conflict a person has to resolve. ### 3. Pins and template - `actions/checkout` was `v4.4.0` in the `standards` job and `v7.0.1` in five others; the `occupancy` job's copy carried **no version comment at all**, which makes Dependabot's diff for it unreadable. All six now match. - The PR template names the **Branch freshness** check and the local command. ### Also: a new "Looks wrong, is not" section `JoshuaJewell/MetaManifold-WebUI` appears in `README.md` badges, `CITATION.cff`, the Codecov `slug`, `CONTRIBUTING.md`, `frontend/src/about.ts` and `SECURITY.md`. In the last pass I reported that as a stale owner to be "fixed" to `hyperpolymath`. That was **wrong**: it is the upstream this repository forks, recorded in `0-AI-MANIFEST.a2ml`, the deed (`:fork-of`, `:upstream`) and `must.k9.ncl` (`upstream_repository`). Repointing the Codecov slug would send uploads at a project that may not have the token — and with `fail_ci_if_error: false` nobody would notice. Nothing in this PR touches those references; the doc now says why not, next to the standards-pin note and the `docs/` whitelist. ## Validation Run here, on this branch, against the merged tree: ``` bash scripts/tests/test-triage-branches.sh # 24 passed, 0 failed bash scripts/tests/test-check-pr-freshness.sh # 12 passed, 0 failed bash scripts/check-governance-contract.sh # 70 checks passed bash scripts/tests/test-governance-contract.sh # all mutations rejected bash scripts/tests/test-scanner-boundaries.sh # ok bash scripts/tests/test-ci-logging.sh # ok bash scripts/lint-sonar-reliability.sh # OK: no S2871 or S7758 in frontend/src ``` The triage tests now also cover: a lane declared in the deed is recognised; the deed pattern **replaces** the built-in fallback; without a deed the fallback still lanes; laned branches alone do not raise `--fail-on` (and `--include-lanes` reproduces the match); the JSON carries `lane` as a string or `null`. Every workflow was parsed as YAML, its embedded shell extracted and checked with `bash -n`, and the rolling-issue upsert exercised against a stub `gh` (create, update, close, and now the laned-branch paragraph). **Not run:** the Julia and frontend suites — this diff touches no Julia or TypeScript, and neither runtime exists in the sandbox. `scripts/ci/check-shell-secrets.sh` refuses on a shallow clone, so it was not run here either; CI runs it on a full clone. ## Scope - The **standards SHA pins** stay as they are. Some are contract-bound (`c6d78606…` for K9/secret-scanner/Scorecard, `01c9b0c9…` for the verdict-grid renderer), so reconciling them has to move `scripts/check-governance-contract.sh`'s constants and the allowlist digest together. Recorded as decision 6. - The **`Branch freshness` ruleset entry** is still an owner action. - No lockfile, dependency, generated or vendored file changes; no `renv.lock`, `Manifest.toml` or `tool_versions.yml` edits. ## Contribution checks - [x] I followed [CONTRIBUTING.md](../CONTRIBUTING.md). - [x] This branch starts at current upstream main and contains no merges from fork main (`merge base 4b05b4b`). - [x] CI has run and passed for this branch (see checks). - [x] Existing licensing, citation and acknowledgement text is unchanged. Co-authored-by: Arena Agent <agent@arena.ai> Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>



Change and reason
main's remote held fifteen heads:main, the orphan data branch the badge publisher force-pushes, and thirteen branches cut from snapshots between 5 and 119 commits old — every one of them carrying an open pull request. Six required contexts (Julia tests,Agda proofs,Code relevance,Occupancy relevance,Occupancy disagreement test,Occupancy typed peak) mean a branch in that state reportsBLOCKEDindefinitely, so a reviewer saw thirteen blocked pull requests and no signal about which of them was alive. Each also started a full CI run that could never go green, which is the mechanism that made the pile stable rather than self-correcting.CONTRIBUTING.mdalready required a branch to start at the tip ofmain(rule 1) and to carry no merge commits from it (rule 2). Nothing enforced either sentence. This PR adds the enforcement, the measurement, and the record — and nothing that deletes anything:scripts/maintenance/check-pr-freshness.shscripts/maintenance/triage-branches.shbase,unrelated,contained,fresh,salvageorstale. Read-only:--format table|markdown|json,--fail-on..github/workflows/pr-freshness.yml.github/workflows/branch-hygiene.ymlscripts/tests/test-*.shAdvisory scan contractjob beside the other shell tests.docs/maintenance/repository-hygiene.mdCONTRIBUTING.md,.gitignore!docs/maintenance/follows the existing whitelist pattern (docs/*is ignored except published notes).Two things both scripts refuse to do, because a wrong answer is worse than none: report on a shallow clone (it reports its graft point as the merge base, which makes every branch look
fresh), and delete anything.governance-badgesis classedunrelatedand left alone — it is an output, not a spare branch:README.mdreads it throughimg.shields.io/endpoint.Work already done on the remote with these scripts
The cleanup is not in this diff, because it is not a file change. Measured, then closed, then the branch deleted — in that order, so each pull request keeps its head reachable at
refs/pull/<n>/head:ahead0, nothing to merge): Arena/00d742e3 metamanifold webui #283, Archive/pre sync 2026 10 17 #281.mainlacks): Work/glmgampoi r shrinkage #284, Arena/b38170ca metamanifold webui #280, Arena/bb2c7f54 metamanifold webui #279, Arena/1cf75265 metamanifold webui #278, Arena/57ce87e1 metamanifold webui #274.main, and it arrived through a merge commit, which rule 2 forbids.mainlacks — closed, not merged, because a merge would revert the 119 commits in between): Arena/3bcf0318 metamanifold webui #276 (Full Evidence Mode, parked by the absorption register per feat(ui): Full Evidence Mode — epistemic editor, fiber visualizer #7), Prepare/lwr 63 lean #272, Upstream/placement evidence #270, Wip/pr62 theta #269 (placement-LWRComputingNote.tsx+ threshold test), Review/evidence 27 contract #271 (evidence-27 review docs).Every close comment carries its own branch's numbers and the
git fetch origin pull/<n>/headrecovery command. The remote is nowmain+governance-badges;triage-branches.shreports 2 heads, 0 stale.Validation
Run here, on this branch:
Both new workflows were parsed as YAML and their embedded shell extracted and checked with
bash -n; the rolling-issue upsert was exercised against a stubghfor all three paths (create, update, close). Not run: the Julia suite and the frontend suite — neither is touched by this diff, and neither can run in this sandbox; CI is the first execution of anything in this PR on a runner.Scope
Deliberately not done, each recorded in
docs/maintenance/repository-hygiene.mdunder Decisions for the owner:Branch freshnessis not a required context yet. It cannot be: a required check that no workflow produces blocks every pull request, including the one that would add it. Add it to theBranch-Floorruleset after this merges.mainis untouched.cancel-in-progress: falsefor pushes serialises the queue — at the time of measurement the run fora3a4a8ewas still going and the run forf3816e5ahad not started 25 minutes later. Changing it trades a per-commit result for latency; that is the owner's call, not a CI fix I should make silently.C Security Rating on New Code, is not a required context, and is already documented indocs/sonar-quality-gate.md. It was a symptom of the huge stale diffs, not a defect in this repository.docs/milestones/02-deferred-issues.mdis referenced by issue feat(analysis): Symbolic engine for formula manipulation #4 and absent frommain. The pre-sync archive had it. Restoring it re-adds content that overlapsdocs/statistics/method-catalogue-v1.md; editing the issue's reference may be the better answer.No lockfile, dependency, generated or vendored file changes.
Contribution checks
merge base f3816e5a=maintip).