Post-merge audit of #6883 at main 448c4b8. The same code is on hotfix/1.3.1 (cherry-picked in #6905, patch-identical), so this is in v1.3.1-rc.2.
1. The protection looks one level down (major)
#6883 says a sweep never deletes the root, container or gate of work that is still in progress. That holds for a two-level hierarchy only.
SweepLiveDependentTargets (internal/workapi/sweep.go:337) protects a candidate when the source of a protecting edge is in live, and live is built from the dependents' not-done statuses (BuildSweepLiveDependentScanFilter). A closed candidate that is itself protected is not in live, so it does not protect its own parent. The callers (internal/storage/issueops/sweep.go:97, internal/storage/uow/sweeper.go:151) run the check once.
Reproduced on a scratch embedded workspace with a bd built at 448c4b8:
- Create ephemeral R, then S1 with
--parent R, then S2 with --parent S1.
- Close S1 and R. S2 stays open.
bd purge --force --json reports purged_count: 1, live_dependent_skipped: 1.
Afterwards R is gone, S1 is still there (closed, protected by S2) with no parent, and the open S2 hangs off an orphaned S1. --dry-run reports the same deletion, so the two modes agree with each other; both are wrong about R.
A fix that fits the current shape: iterate to a fixed point, adding each protected candidate to live until the protected set stops growing.
2. --wisps-plane deletes cited no-history beads that bd prune keeps (minor)
cmd/bd/purge.go:103-114 switches the tier to SweepWispsPlane and keeps purge's scope otherwise, so the reference protection that bd prune applies is off. --wisps-plane reaches --no-history beads, which are durable-tier.
With a closed --no-history bead N and an open bead whose text cites N:
bd prune --pattern N --dry-run → referenced_skipped: 1, pruned_count: 0
bd purge --wisps-plane --pattern N --dry-run → purge_count: 1
If that difference is intended, the help text should say so; if not, the wisps-plane scope wants protectReferenced.
3. Test gap (nit)
Custom statuses count as live in the code, and nothing pins it: passing nil for the custom statuses in the live-dependent filter leaves the embedded TestSweeperContract green.
What was checked and found sound
Edges consulted (parent-child, tracks, blocks, from both dependency tables, direction "points at the candidate"); the check and the delete share one transaction in all three backends; negative --limit is refused; --wisps-plane requires --older-than or --pattern in both the CLI and the role; --force does not bypass the protection; JSON counts come from the actual delete. Mutation: turning protectLiveDependents off in the CLI turns TestEmbeddedPurgeWispsPlaneRetention red. Not executed: the dolt-server and uow TestSweeperContract variants and TestProxiedServerPurgeWispsPlaneRetention.
bee, beads-lane steward
Post-merge audit of #6883 at main 448c4b8. The same code is on
hotfix/1.3.1(cherry-picked in #6905, patch-identical), so this is inv1.3.1-rc.2.1. The protection looks one level down (major)
#6883 says a sweep never deletes the root, container or gate of work that is still in progress. That holds for a two-level hierarchy only.
SweepLiveDependentTargets(internal/workapi/sweep.go:337) protects a candidate when the source of a protecting edge is inlive, andliveis built from the dependents' not-done statuses (BuildSweepLiveDependentScanFilter). A closed candidate that is itself protected is not inlive, so it does not protect its own parent. The callers (internal/storage/issueops/sweep.go:97,internal/storage/uow/sweeper.go:151) run the check once.Reproduced on a scratch embedded workspace with a
bdbuilt at 448c4b8:--parent R, then S2 with--parent S1.bd purge --force --jsonreportspurged_count: 1, live_dependent_skipped: 1.Afterwards R is gone, S1 is still there (closed, protected by S2) with no parent, and the open S2 hangs off an orphaned S1.
--dry-runreports the same deletion, so the two modes agree with each other; both are wrong about R.A fix that fits the current shape: iterate to a fixed point, adding each protected candidate to
liveuntil the protected set stops growing.2.
--wisps-planedeletes cited no-history beads thatbd prunekeeps (minor)cmd/bd/purge.go:103-114switches the tier toSweepWispsPlaneand keeps purge's scope otherwise, so the reference protection thatbd pruneapplies is off.--wisps-planereaches--no-historybeads, which are durable-tier.With a closed
--no-historybead N and an open bead whose text cites N:bd prune --pattern N --dry-run→referenced_skipped: 1,pruned_count: 0bd purge --wisps-plane --pattern N --dry-run→purge_count: 1If that difference is intended, the help text should say so; if not, the wisps-plane scope wants
protectReferenced.3. Test gap (nit)
Custom statuses count as live in the code, and nothing pins it: passing
nilfor the custom statuses in the live-dependent filter leaves the embeddedTestSweeperContractgreen.What was checked and found sound
Edges consulted (parent-child, tracks, blocks, from both dependency tables, direction "points at the candidate"); the check and the delete share one transaction in all three backends; negative
--limitis refused;--wisps-planerequires--older-thanor--patternin both the CLI and the role;--forcedoes not bypass the protection; JSON counts come from the actual delete. Mutation: turningprotectLiveDependentsoff in the CLI turnsTestEmbeddedPurgeWispsPlaneRetentionred. Not executed: the dolt-server and uowTestSweeperContractvariants andTestProxiedServerPurgeWispsPlaneRetention.bee, beads-lane steward