fix(inbox): retire the orphaned index when a directory's last segment goes
Retiring a directory's last concept left its index.md standing, so the directory survived a rebuild that never creates it -- diff -r reports it as 'Only in ...'. Found by S7 once its fixture was made to actually retire a segment: with round 2 a superset of round 1, S7 stayed green with retirement disabled entirely.
This commit is contained in:
parent
91efd92612
commit
894ccdf42f
1 changed files with 29 additions and 11 deletions
|
|
@ -310,21 +310,37 @@ def _owned_concepts_by_source(bundle: Path, profile: BundleProfile) -> dict[str,
|
||||||
return owned
|
return owned
|
||||||
|
|
||||||
|
|
||||||
def _retire_stale_segments(bundle: Path, stale: set[str]) -> None:
|
def _retire_stale_segments(bundle: Path, stale: set[str], profile: BundleProfile) -> None:
|
||||||
"""Delete concepts this round's plan no longer names, and prune what empties.
|
"""Delete concepts this round's plan no longer names, and prune what empties.
|
||||||
|
|
||||||
An emptied directory is deleted too: a scratch rebuild never creates it, so
|
An emptied directory is deleted too, and so is the index left standing in
|
||||||
leaving it behind is exactly the kind of one-sided difference `diff -r`
|
it. A scratch rebuild writes an index only where a concept lives, so an
|
||||||
reports and S7 refuses.
|
orphaned one is exactly the kind of one-sided difference `diff -r` reports
|
||||||
|
as `Only in ...` -- measured 2026-09-01, when retiring a directory's last
|
||||||
|
concept left its index behind and S7 caught the divergence.
|
||||||
|
|
||||||
|
The emptiness test is "holds no concept anywhere BENEATH it", not "holds no
|
||||||
|
files": a directory whose own concepts are gone may still be an ancestor of
|
||||||
|
ones that remain, and deleting its index would break the walk from the root.
|
||||||
"""
|
"""
|
||||||
for relative in sorted(stale):
|
for relative in sorted(stale):
|
||||||
target = bundle / relative
|
(bundle / relative).unlink(missing_ok=True)
|
||||||
target.unlink(missing_ok=True)
|
|
||||||
|
suffix = profile.paths.concept_suffix
|
||||||
|
# Deepest first, so a parent is judged only after its children are gone.
|
||||||
for relative in sorted(stale, key=lambda item: item.count("/"), reverse=True):
|
for relative in sorted(stale, key=lambda item: item.count("/"), reverse=True):
|
||||||
parent = (bundle / relative).parent
|
directory = (bundle / relative).parent
|
||||||
while parent != bundle and parent.is_dir() and not any(parent.iterdir()):
|
while directory != bundle and directory.is_dir():
|
||||||
parent.rmdir()
|
if any(
|
||||||
parent = parent.parent
|
path.is_file() and path.name != profile.index.name
|
||||||
|
for path in directory.rglob(f"*{suffix}")
|
||||||
|
):
|
||||||
|
break
|
||||||
|
(directory / profile.index.name).unlink(missing_ok=True)
|
||||||
|
if any(directory.iterdir()):
|
||||||
|
break
|
||||||
|
directory.rmdir()
|
||||||
|
directory = directory.parent
|
||||||
|
|
||||||
|
|
||||||
def _check_segment_path(path: str) -> str:
|
def _check_segment_path(path: str) -> str:
|
||||||
|
|
@ -711,7 +727,9 @@ def process_inbox(
|
||||||
# Round N owns exactly what round N's plan names. Everything this
|
# Round N owns exactly what round N's plan names. Everything this
|
||||||
# document owned before and does not now is retired HERE, after the
|
# document owned before and does not now is retired HERE, after the
|
||||||
# writes, so a failure above leaves the previous round intact.
|
# writes, so a failure above leaves the previous round intact.
|
||||||
_retire_stale_segments(bundle, owned_by_source.get(path.name, set()) - set(targets))
|
_retire_stale_segments(
|
||||||
|
bundle, owned_by_source.get(path.name, set()) - set(targets), profile
|
||||||
|
)
|
||||||
|
|
||||||
# §6 index — the last disk mutation, and only when something was written.
|
# §6 index — the last disk mutation, and only when something was written.
|
||||||
if persisted:
|
if persisted:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue