Skip to content

Automate NOTICE generation, Mathlib bumps, and import-PR rebasing - #290

Merged
Vilin97 merged 2 commits into
mainfrom
claude/lean-pool-automation-3ba499
Jul 28, 2026
Merged

Automate NOTICE generation, Mathlib bumps, and import-PR rebasing#290
Vilin97 merged 2 commits into
mainfrom
claude/lean-pool-automation-3ba499

Conversation

@Vilin97

@Vilin97 Vilin97 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Three pieces of maintenance automation, all infrastructure-only (no LeanPool/** content touched).

NOTICE is now generated, not hand-maintained

NOTICE had drifted badly: 75 of 141 projects had no attribution entry, an Apache-2.0 §4(d) and MIT notice gap for each. Every input was already in projects.yml (all 141 cards carry license and source.github_repo), so python/lean_pool/notice.py derives the list sections and NOTICE.extra.yml holds only the prose that cannot be derived — MIT copyright lines, relicensing statements, upstream citation requests.

Verified against the previous file: all 141 present, 75 added, nothing dropped and no prose lost. Regenerating also corrected four upstream URLs whose repositories had been renamed — BrauerGroup_new, FLDutchmann/selberg-sieve4, rmt4, axiommath — each confirmed against the GitHub API.

notice.yml regenerates after merge, so drift cannot persist. It is deliberately not a PR gate: a content PR may not touch NOTICE under content-pr-guard, so gating there would be unsatisfiable. Hand-edits are still caught by test_repository_notice_is_current.

Automated Mathlib bumps

mathlib-bump.yml runs nightly: detect → pin → probe → triage → repair → assemble. Only repair costs anything (Claude subscription quota via CLAUDE_CODE_OAUTH_TOKEN); everything else is free runner minutes. The probe shards across 10 parallel jobs and buckets failures per project, which becomes the repair fan-out's matrix — pool projects never import each other, so a bump decomposes into independent per-project repairs. assemble still rebuilds the whole pool, which is what catches the cross-project effects per-project repair cannot see.

Bumps target the newest available release, candidates included, so the current target is v4.33.0-rc1 rather than v4.32.1. Repair jobs upload patches instead of pushing, so parallel jobs cannot race the branch. The PR it opens is always a draft.

Statement-level changes are deliberately not gated: a lemma absorbed by Mathlib is a legitimate loss during a bump, so agents report absorbed declarations for a human to judge instead.

Auto-rebase for the import queue

auto-rebase.yml keeps open import PRs mergeable when a content PR lands. Both conflict points are mechanical: LeanPool.lean is regenerated from the file tree (it reproduces the committed 2,885-line index byte-for-byte, so no Lean toolchain is needed), and projects.yml takes the merged base plus the branch's added cards as verbatim text blocks. Anything else in conflict is a real content overlap — the merge is abandoned and the PR labelled needs-manual-rebase.

Validated against real PR data by simulating #285 landing, which makes #287 conflict exactly as predicted: the regenerated index is the exact sorted union of both branches with no conflict markers, and the merged registry is 143 cards, no duplicates, valid YAML, required fields intact, 142 pre-existing cards byte-identical.

Verification

  • 244 Python tests pass (64 new), ruff clean, actionlint clean on all workflows, every action SHA-pinned
  • detect smoke-tested against the live Mathlib remote; pin run end-to-end against v4.33.0-rc1 with all three upstream tags confirmed to exist and [leanOptions] preserved
  • Shard planning verified to cover all 142 modules across 10 balanced shards

Not yet exercised: the bump workflow has never run a real build or repair job — workflow_dispatch requires the file on the default branch, so that can only happen after this merges.

Before the automation is live

  • CLAUDE_CODE_OAUTH_TOKEN — from claude setup-token; without it the free stages still work and only repair fails
  • REBASE_TOKEN — only needed to rebase fork PRs; GITHUB_TOKEN cannot push to forks even with "Allow edits by maintainers". Same-repo PRs work without it.

See .github/BUMP_AUTOMATION.md.

Vilin97 and others added 2 commits July 27, 2026 22:59
NOTICE had drifted badly: 75 of 141 projects had no attribution entry at
all, which is an Apache-2.0 section 4(d) and MIT notice gap for every one
of them. Rather than lint a hand-maintained file, generate it.

  - python/lean_pool/notice.py builds NOTICE from LeanPool/projects.yml
    (which already carries `license` and `source.github_repo` for all 141)
    plus NOTICE.extra.yml for the prose that cannot be derived: MIT
    copyright lines, relicensing statements, upstream citation requests.
  - notice.yml regenerates after merge, so drift cannot persist. It is not
    a PR gate on purpose: a content PR may not touch NOTICE under
    content-pr-guard, so gating there would be unsatisfiable.
  - Regenerating also corrected four stale upstream URLs whose repositories
    had been renamed (BrauerGroup_new, FLDutchmann/selberg-sieve4, RMT4,
    AxiomMath/fel-polynomial).

mathlib-bump.yml migrates the pool to a new release in stages, only the
last of which needs a human: detect a newer tag, move the four pins and
plan shards, probe-build every project in parallel, triage the logs into a
per-project breakage map, fan out one Claude repair job per broken project,
then reassemble and open a draft PR. Pool projects never import each other,
so a bump decomposes into independent per-project repairs; the assemble
stage still rebuilds the whole pool, which is what catches the
cross-project effects per-project repair cannot see.

Repair jobs authenticate with a Claude subscription token and upload
patches rather than pushing, so parallel jobs cannot race the branch.
Probing is free and runs nightly regardless, so a release never lands as a
surprise; `repair: auto` spends quota on final releases only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bumps now target the newest available release, candidates included: mid
release-cycle that is what "latest Lean and Mathlib" means, and the pool
tracks the latest. `detect` therefore reports v4.33.0-rc1 rather than
v4.32.1, `--stable-only` opts out, and the repair fan-out no longer skips
rc targets (it would otherwise never have run).

auto-rebase.yml keeps the import queue mergeable without hand-holding.
When a content PR lands, every other open import PR conflicts in exactly
two files, and in both the resolution is mechanical:

  - LeanPool.lean is a sorted list of imports regenerated from the file
    tree. It reproduces the committed 2,885-line index byte-for-byte, so
    the job needs no Lean toolchain and runs in seconds.
  - LeanPool/projects.yml takes the merged base's registry plus the cards
    the branch adds, moved as verbatim text blocks. Round-tripping 141
    cards through a YAML dumper would reformat every one and bury the
    real change.

Any other conflicted path is a genuine content overlap: the merge is
abandoned and the PR labelled needs-manual-rebase rather than guessed at.

Verified against real pull request data by simulating #285 landing, which
makes #287 conflict exactly as predicted. The regenerated index is the
exact sorted union of both branches' modules with no conflict markers, and
the merged registry is 143 cards with no duplicates, valid YAML, required
fields intact, and the 142 pre-existing cards byte-identical.

Pushing to a fork branch needs REBASE_TOKEN (a GitHub App installation
token or a PAT); GITHUB_TOKEN has no write access to forks even with
"Allow edits by maintainers" set. Without it, fork PRs are labelled
instead of rebased and same-repo PRs still work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread python/tests/test_bump.py
Comment on lines +97 to +104
def test_newer_versions_ignores_unparseable_tags() -> None:
"""Only well-formed release tags are considered."""
tags = ["v4.33.0", "nightly-2026-07-01"]
assert newer_versions(
"v4.32.0",
[t for t in tags if t != "nightly-2026-07-01"],
stable_only=True,
) == ["v4.33.0"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Misleading test: newer_versions does not ignore unparseable tags

The test constructs tags = ["v4.33.0", "nightly-2026-07-01"] but then filters the nightly tag before calling newer_versions, so the function is only ever called with ["v4.33.0"]. The name and docstring claim that newer_versions itself ignores unparseable tags, but it doesn't — version_key raises ValueError inside the list comprehension if given a non-release tag. The protection is in remote_tags(), which pre-filters via VERSION_RE.match. Any future caller who passes an unfiltered list expecting newer_versions to be resilient would hit a runtime crash.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +82 to +95
added = [
(slug, block)
for slug, block in split_cards(theirs)[1]
if slug not in base_slugs and slug not in our_slugs
]
if not added:
return ours

merged = header + "".join(block for _, block in our_cards)
# A registry whose last card lacks a trailing newline would otherwise
# run into the first appended card.
if merged and not merged.endswith("\n"):
merged += "\n"
return merged + "".join(block for _, block in added)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Same-slug conflict silently drops one PR's card

If two PRs both add a card with the same slug, and the first one merges before the second, merge_registry silently drops the second PR's version: the condition slug not in our_slugs is False once the first card is in main, so the block is never appended. Meanwhile the Lean files from the second PR are already in the tree (the rebase succeeds), so LeanPool.lean will contain import LeanPool.SomeProject but projects.yml will carry the first PR's card — a silent registry/tree mismatch. Since this is a genuine content conflict (two independent authors claiming the same slug), it might be safer to bail out and apply the needs-manual-rebase label when theirs adds a slug that appears in our_slugs but not in base_slugs.

Comment on lines +367 to +372
PROJECT: ${{ matrix.project }}
CLEAN: ${{ steps.verify.outputs.clean }}
run: |
set -euo pipefail
# Patches, not pushes: parallel jobs writing the same branch would
# race. `assemble` applies them in a defined order instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security elan-init.sh fetched from master with no hash verification

The installer is downloaded from https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh and piped straight to sh in three separate jobs (pin, probe, assemble). Because this points at a mutable master ref with no checksum, a compromised push to the upstream repo (or a MITM) could execute arbitrary code inside a runner that holds contents: write access. Pinning to a specific commit hash or a tagged release URL (e.g. refs/tags/v4.1.2) and verifying a checksum would close this window.

Comment thread python/lean_pool/bump.py
Comment on lines +134 to +138
root / "lakefile.toml",
re.compile(r'rev\s*=\s*"v[\d.]+(?:-rc\d+)?"'),
f'rev = "{version}"',
),
(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regex bumps all release-style revs in the file, not only Mathlib's

re.compile(r'rev\s*=\s*"v[\d.]+(?:-rc\d+)?"') matches every rev = "v…" line in lakefile.toml, not just Mathlib's. Right now the main lakefile.toml has only one such entry, so this is harmless. But if a future [[require]] dependency is pinned to a release tag (e.g. an Alloy or Batteries rev = "v1.0.0"), it would silently get overwritten to the new Mathlib version during a bump. The fix is to match only the Mathlib block, for example by anchoring the pattern to scope = "leanprover-community" on the preceding line.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Greptile Summary

Adds three infrastructure-only automation pieces: generated NOTICE (replacing a hand-maintained file that had drifted to 75 missing entries), a nightly Mathlib-bump pipeline (detect → pin → probe → triage → repair → assemble), and an auto-rebase workflow that mechanically resolves the two predictable conflicts every import PR faces after a content PR lands.

  • notice.py + notice.yml: NOTICE is now derived from projects.yml + NOTICE.extra.yml, with a post-merge commit that prevents drift. A --check mode is tested against the live file in CI.
  • mathlib-bump.yml: A six-stage pipeline using parallel probe shards and per-project Claude repair jobs; patches are uploaded as artifacts rather than pushed, so parallel repair jobs cannot race the branch. The assembled result opens as a draft PR.
  • auto-rebase.yml: Serialised (one job at a time) rebase of open import PRs; resolves LeanPool.lean by regenerating from the file tree and projects.yml by a verbatim three-way card append. Anything else is left for a human and labelled accordingly.

Confidence Score: 4/5

Safe to merge as infrastructure-only automation; no Lean content is touched and all workflows open draft PRs or label rather than self-merging.

The three new Python modules are well-tested (244 tests, live round-trips against committed files). The main workflow concerns are: the elan installer is fetched from an unpinned master ref in three jobs that hold write permissions; pin_files regex would silently over-match if a future lakefile dependency adopts a release-style rev; merge_registry drops a card silently when two PRs claim the same slug rather than flagging it for manual review; and the exit-code-5 pytest workaround should be cleaned up now that tests exist.

Files Needing Attention: mathlib-bump.yml for the unpinned elan installer and rebase.py / test_rebase.py for the duplicate-slug edge case in merge_registry.

Security Review

  • elan-init.sh supply chain risk (mathlib-bump.yml lines 367–372, 427–432, 659–663): The Lean toolchain installer is downloaded from https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh — a mutable master ref — and executed directly via sh in three jobs that hold contents: write or contents: read permissions. A compromised push to the upstream repo (or a MITM) would execute arbitrary code inside the runner. Pinning to a specific commit hash or a tagged release URL with a checksum check would eliminate this window.

Important Files Changed

Filename Overview
python/lean_pool/bump.py New module: version detection, pin rewriting, and build-log triage for Mathlib bumps. Solid design with one concern: the version-rev regex in pin_files would over-match if a future lakefile dependency uses a release-style rev.
python/lean_pool/rebase.py New module: deterministic conflict resolution for import PRs. Index regeneration and three-way registry merge look correct; edge case where two PRs claim the same slug results in silent card drop rather than a manual-rebase flag.
.github/workflows/mathlib-bump.yml New 521-line workflow: detect → pin → probe → triage → repair → assemble pipeline. All GitHub Actions SHAs are pinned; however, elan-init.sh is fetched from an unpinned master ref and executed directly in three jobs that hold write permissions.
.github/workflows/auto-rebase.yml New workflow: serialised rebase of open import PRs after a content PR merges. Concurrency group prevents races; fork vs. same-repo token logic is correct; fall-through to needs-manual-rebase is appropriately conservative.
.github/workflows/python_ci.yml Adds NOTICE and NOTICE.extra.yml to the path trigger; the exit-code-5 workaround comment says to remove it once tests exist, which they now do.
python/tests/test_bump.py 39 new tests covering version ordering, pin rewriting, and build-log parsing. One test (test_newer_versions_ignores_unparseable_tags) pre-filters before calling the function and does not test the stated behaviour.

Comments Outside Diff (1)

  1. .github/workflows/python_ci.yml, line 72-86 (link)

    P2 Now that this PR adds 64 tests, the exit-code-5 workaround should be retired per the inline comment ("Remove this once tests exist"). Keeping it around means a future misconfiguration that produces zero collected tests would silently pass CI.

Reviews (1): Last reviewed commit: "Bump to the newest release; auto-rebase ..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

LLM review skipped: PR touches no .lean files. Push a Lean change or comment /review after one lands to re-trigger.

@Vilin97
Vilin97 merged commit 191f7f6 into main Jul 28, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant