docs(plans): scope Step 20a's per-call row ceiling, record its MR, and widen its Files and Tests entries
What this delivers
Three corrections to docs/plans/2026-08-11-s20a-lifecycle-closed-beta.md, all in that one file.
Step 20a stated a per-call row ceiling of 2*limit - 1 for every call of the two remote package reapers.
The merged code does not hold to that.
Its Scope and Acceptance text now puts 2*limit on every call, and 2*limit - 1 on the call that takes the cache package row.
The second commit fills the MR cell of Status row 20a with !2162 (merged).
Row 20b stays empty, because that step has no merge request.
The third commit widens Step 20a's Files and Tests entries.
!2162 (merged) changes thirteen paths, and the step named neither internal/datastore/lifecycle_scan.go nor internal/datastore/lifecycle_reap_maven_remote_test.go.
The Files entry gains both.
The Tests entry gains the new unit-test file alone, because lifecycle_scan.go is production code and carries no Tests-entry claim.
Why the old text was wrong
The error was a missing scope, not a wrong number.
Both 2*limit and 2*limit - 1 are real bounds of this arm.
The plan applied the tighter one to every call instead of to the parent-taking call alone.
The old Scope text also contradicted itself: it read "unchanged at 2*limit, in fact 2*limit - 1".
The reasoning after the colon is byte-identical in the new text, and it now attaches to the clause it scopes.
The old Acceptance line read "no call reports Rows above 2*limit - 1", which quantifies over every call and is false.
Why the file lists were short
Guardrail 4 in AGENTS.md keeps a step merge request out of the plan file, so !2162 (merged) cannot widen its own step's entries.
!2190 (merged) widened the same Files entry for two other paths and did not carry these two.
The gap was raised in review of !2162 (merged), in note 3780459940.
!2189 (merged) is this project's precedent for one docs(plans) merge request recording a step's MR and widening its Files list together, which is why the widening lands here rather than on a third merge request.
Evidence
docs/dev/storage-accounting.md carries the contract, and it already separates the two bounds:
- Line 2170: "Three conditions put one call's ceiling at
2*limitrows: the page spans every leg, each artifact row frees at most one attachment row, and the collateral reached by walking an artifact row is itself capped per artifact." - Line 2171: "The maximum comes from the filled-page branch, the one that leaves the parent row in place, and the short-page branch that deletes the parent cannot reach it."
- Line 2184: "a filled page selects
limitchildren and no parent, and a short page selects at mostlimit-1children plus the parent."
Two merged integration tests assert the wider bound as an equality on a filled page:
internal/datastore/lifecycle_reap_maven_remote_integration_test.go:1001, insideTestMavenRemotePackageReaper_Reap, assertsReapTotals{Rows: int64(2 * limit)}.internal/datastore/lifecycle_reap_npm_remote_integration_test.go:919, insideTestNpmRemotePackageReaper_Reap_BoundsEachChunk, assertsint64(2*packageChunkLimit).
These line numbers are read at origin/main aea5b9d30.
!2162 (merged) asserts the pair per arm, one bound per branch.
It asserts 2*limit unconditionally, and 2*limit - 1 only inside the branch that established the cache package row is gone.
Those tests arrive with !2162 (merged) and are not on main today.
The correction was raised in review of !2162 (merged).
The relationship to !2162 (merged)
!2162 (merged) needs no change from this merge request, and this merge request blocks nothing. It brings the plan text and the Status cell into agreement with what !2162 (merged)'s own tests assert.
e2e catalog
No scenario is added or affected. This change edits plan prose and one status-table cell, and it changes no behavior.
This is a bot message