Skip to content

fix(reconcile): make the prune plan describe what apply actually does - #57

Merged
ProtocolWarden merged 1 commit into
mainfrom
fix/prune-plan-reports-age-trims
Aug 4, 2026
Merged

fix(reconcile): make the prune plan describe what apply actually does#57
ProtocolWarden merged 1 commit into
mainfrom
fix/prune-plan-reports-age-trims

Conversation

@ProtocolWarden

Copy link
Copy Markdown
Owner

Closes #54.

Summary

prune --dry-run printed N moves and --apply archived N plus every unclaimed section past recent_n. The extra sections were computed inside _apply_plan_locked and never reached plan.moves — which is all format_plan renders — so nothing reported them, before or after. The operator approved one thing and got another.

What makes it bite rather than merely surprise: those sections are claimed by no item and cleared by no gate. The DOC GAP gate only inspects done items (check.py), so anything still partial is never examined — and owned_done excludes it too, so its sections are never claimed and land in the age-swept bucket. Incomplete, undocumented work is therefore the most likely thing to be archived by age, which inverts what the gate is for. Destination is the private manifest, so on a public repo the content also changes repos on its way out.

Changes — no policy change

  • build_plan records the swept sections as PlannedTrim, and format_plan prints them under their own heading. A separate type from PlannedMove on purpose: a move was released by the gate, a trim was swept by a number, and conflating them in the thing an operator approves is how this stayed invisible.
  • PrunePlan carries its recent_n, and apply_plan defaults to it. The two defaulted independently, so planning with --recent 20 swept to 10 at apply time — a second, quieter divergence. An explicit recent_n= still overrides.
  • is_noop still ignores trims. Age-sweeping is a side effect of an otherwise-legitimate prune, never a reason for one; counting trims there would start pruning repos that are today correctly left alone. Pinned by a test.

Testing

493 → 498 passed; the 17 failures are unchanged and identical (test_committed.py / test_signing.py still fail to import for lack of cryptography locally — pre-existing, and CI installs it).

All three changes were reverted in turn to confirm a named test fails. The end-to-end test — which diffs the dry-run text against the headings that actually land in the archive file — catches two of the three on its own, which is the property the issue is really about.

Two of the five tests are contract tests rather than regression tests, and I'd rather say so: test_explicit_recent_n_still_overrides_the_plan and test_trims_alone_do_not_make_a_noop_plan_act pass against the pre-fix code too. The first documents that the override survives; the second guards a future regression, since folding trims into is_noop is the obvious wrong next step.

Deliberately not addressed

#54 lists four possible directions. This PR implements only the first — make the plan honest — because the other three are policy calls that belong to the spec owner:

  • whether recent_n should sweep unclaimed sections at all
  • whether reconcile and trim should be separable operations (--no-trim-unclaimed)

Both remain open. This change makes the current behaviour visible without deciding what it ought to be.

🤖 Generated with Claude Code

Closes #54.

`prune --dry-run` printed N moves and `--apply` archived N plus every unclaimed
section past `recent_n`. The extra sections were computed inside
`_apply_plan_locked` and never reached `plan.moves`, which is all `format_plan`
renders — so nothing reported them, before or after. The operator approved one
thing and got another.

What makes it bite rather than merely surprise: those sections are claimed by
no item AND cleared by no gate. The DOC GAP gate only inspects *done* items, so
anything still `partial` is never examined — and `owned_done` excludes it too,
so its sections are never claimed and land in the age-swept bucket. Incomplete,
undocumented work is therefore the MOST likely thing to be archived by age,
which inverts what the gate is for. Destination is the private manifest, so on
a public repo the content also changes repos on its way out.

Three changes, no policy change:

- `build_plan` records the age-swept sections as `PlannedTrim`, and
  `format_plan` prints them under their own heading, named for what they are.
  A separate type from `PlannedMove` on purpose: a move was released by the
  gate, a trim was swept by a number, and conflating them in the thing an
  operator approves is how this stayed invisible.
- `PrunePlan` carries the `recent_n` it was built with, and `apply_plan`
  defaults to it. The two defaulted independently, so planning with
  `--recent 20` sweeps to 10 at apply time — a second, quieter divergence.
  An explicit `recent_n=` still overrides.
- `is_noop` deliberately keeps ignoring `trims`: age-sweeping is a side effect
  of an otherwise-legitimate prune, never a reason for one. Counting trims
  there would start pruning repos that are today correctly left alone. There
  is a test pinning that.

Not addressed here, because they are policy and belong to the spec owner:
whether `recent_n` should sweep unclaimed sections at all, and whether the
reconcile and trim operations should be separable. #54 lists both; this change
only makes the current behaviour honest.

5 tests. Each of the three changes was reverted in turn to confirm a named test
fails — the end-to-end one, which diffs the dry-run text against the headings
that actually land in the archive, catches two of them on its own.

Suite 493 -> 498 passed; the 17 failures are unchanged and identical.

No narrative log entry, following #53 and #55: this repo's log is at 400/400,
so adding one would trip RC1 and force a prune to land a commit about pruning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ProtocolWarden
ProtocolWarden merged commit 23ade5d into main Aug 4, 2026
7 checks passed
@ProtocolWarden
ProtocolWarden deleted the fix/prune-plan-reports-age-trims branch August 4, 2026 20:10
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.

reconcile prune: dry-run under-reports — apply also archives unclaimed log sections by age

1 participant