| Field | Value |
|---|---|
| Decision ID | ADR-041 |
| Initiative | Gate A performance / proof-library lifecycle |
| Proposed By | unsorry maintainers |
| Date | 2026-06-14 |
| Status | Accepted |
In the context of an unsorry library that keeps accumulating solved proof modules in one active Lean package, where Gate A’s build, axiom audit, statement-binding regeneration, and kernel replay all become slower as the verified library grows,
facing the fact that most merged proofs are no longer active development targets but still sit in the same package as new work, causing every full validation path to scale with the historical proof count rather than with the current working set,
we decided for splitting proved work into immutable proof archive blocks: keep only the current working set in the active package, and when the active proved set reaches a target of 40 proved goals, cut a frozen archive package containing that block’s proofs, index entries, and any required metadata. The active package then depends on the archived block by a pinned Lake/Git revision instead of carrying all archived proof source files in its own active module set,
and neglected keeping one ever-growing package indefinitely (simple but makes Gate A’s long pole grow forever), archiving by individual proof (too much package/index churn), archiving only by moving files to a directory still enumerated by Gate A (does not reduce the active verification surface), and choosing 50+ as the initial block size (reasonable once proof inflow is steady, but too coarse for the current library size and operational learning loop),
to achieve a bounded active verification surface, faster normal proof PRs, clearer lifecycle boundaries between current work and frozen dependencies, and a path toward distributed problem-solving where solved blocks behave like pinned, previously certified dependencies rather than live source,
accepting that archive boundaries add dependency-management overhead; that archive blocks must be validated fully before freezing; that the initial block size of 40 is an operational target, not a mathematical invariant, and may be retuned later from CI telemetry; and that any change to an archive package or dependency pin is trust-bearing and must trigger full validation of the affected archive/pin boundary.
An archive block is created when the active package reaches roughly 40 proved goals. The default target remains 40 because it keeps blocks reviewable and gives CI feedback sooner than a 50- or 100-proof block.
Archive closed proof families only — never half-finished decomposition trees. A decomposition family (parent goal + its sublemmas, their proofs, the decomposition metadata, and proof-run telemetry) is archived as one unit, and only when every member is proved. A family with any open member is kept active. Two reasons:
parent+subs are atomic to
Gate B: a package is validated as its own tree, so a sublemma’s src≜decompositions/<D> must
resolve there (GB008) and the parent must be a known goal there (GB016); and an active sublemma
still referencing a moved decomposition fails GB008 on the active side. So a tree goes to the
package entirely or not at all (enforced by the cut; see SPEC-041-A §8 invariant A).unblockable, recompose-candidate, proved-deps) and the active Lake build see only the
active library/ — archive packages are not active Lake dependencies. So archiving the proved
sublemmas of a still-open parent would hide the very ingredients the parent’s recomposition
needs. This is not a real constraint in practice: an open parent whose sublemmas are all proved is
a transient recompose-candidate — the parent gets re-proved from the active sublemmas, the
family closes, and then it is archivable. The conservative rule simply waits for that, so it
never strands recomposition. Once archived, the family is correctly read-only history: its records
are status≜archived (excluded from selection) and its decomposition moved into the package
(invisible to the active recompose machinery). A later refactor/replace creates a new active
proof; it never edits an archived block.Before a block is frozen, it must pass the full Gate A soundness stack for the block:
lake build --wfailleanchecker kernel replayAfter freezing, normal active proof PRs do not replay or re-audit the archive source. They validate the active package plus the pinned archive dependency boundary. A PR that modifies an archive block, changes its pin, changes the archive packaging rules, or changes shared Gate A tooling falls back to full validation for the affected trust boundary.
The preferred first implementation is separate Lake packages, not necessarily separate GitHub repositories:
packages/
unsorry-active/
unsorry-archive-0001/
unsorry-archive-0002/
Separate repositories can come later if package size, release cadence, or external reuse makes that worthwhile. Starting in one repository keeps history, issue links, and migration tooling simpler while still letting Gate A distinguish active modules from frozen archive modules.
Each archive block should carry:
library/Unsorry/*.lean modules for that blocklibrary/index/*.aisp entriesThe first implementation should be conservative:
unsorry-archive-0001 from the oldest stable proved goals.The step-by-step cut procedure (as performed for 0001/0002) is the runbook in SPEC-041-A §8.
At scale. With a high, continuous inflow of proofs (many contributors / agents), the active set crosses the block target constantly, and the active package’s full-replay/audit cost — Gate A’s long pole, which on memory-bound runners cannot be parallelised away (ADR-047) — grows between cuts. So 40 is a ceiling, not a goal: cut early and often to keep full validation fast, and promote the report-only planner to a write mode plus a threshold-triggered retire PR so archiving keeps pace without manual effort (SPEC-041-A §9).
Today archived proofs are validated history, not reusable proof inputs: the active package does
not require archive packages, and dependency discovery (_proved_goals, proved-deps,
recompose-candidate, unblockable) scans only the active library/. The “closed families only”
policy above makes this a non-issue for the normal decompose→recompose lifecycle (recomposition
finishes before a family is archived). The only capability it forecloses is letting a new
active proof import an already-archived lemma instead of re-proving it — a dependency-reuse
optimization, not a correctness need. If we ever want it, it is a separate, cross-cutting change:
lakefile.toml require the archive packages (by pinned rev)._proved_goals (and proved-deps) to include packages/unsorry-archive-*/library/index/*.aisp.Because it touches Lake deps, dependency discovery, prompt generation, and CI scope, it must land as its own ADR/PR — not bundled with the archiver.
Related gap — retired families. The “closed families only” rule has no exit for a parent that is
hard enough to never recompose: its proved sublemmas then stay active forever, slowly bloating the
active set under high inflow. A retired parent status (e.g. after N failed recompose attempts, or
explicit operator retirement) should release the whole family to the archiver, so genuinely-stuck
trees don’t pin the active replay surface. Design this alongside the write-mode tool.
| Reference ID | Title | Type | Location |
|---|---|---|---|
| REF-1 | Gate A soundness enforcement | Decision | ADR-006-Gate-A-Soundness-Enforcement.md |
| REF-2 | Statement binding gate | Decision | ADR-011-Statement-Binding-Gate.md |
| REF-3 | Incremental kernel replay | Decision | ADR-033-Incremental-Kernel-Replay.md |
| REF-4 | Gate A workflow | Specification | specs/SPEC-006-B-Gate-A-Workflow.md |
| REF-5 | Distributed workload engine | Decision | ADR-030-Distributed-Workload-Engine.md |
| Status | Approver | Date |
|---|---|---|
| Proposed | unsorry maintainers | 2026-06-14 |
| Accepted | unsorry maintainers | 2026-06-14 |
| Amended | unsorry maintainers | 2026-06-15 |