feat(index): maintain existing indexes on re-materialization (spec §6)
TDD step 6: on an existing index, managed lines whose target is an ingest-owned file removed this run are dropped, a managed label is refreshed in place when the extraction title changes, and every other line — curated links, promoted-verdict links, foreign line endings — is preserved byte for byte. Load-bearing test: a promoted verdict and its index link survive re-ingest unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QeqhJpYQyghASjiJo5EhGg
This commit is contained in:
parent
0ff946696c
commit
f12fdc1dd0
2 changed files with 161 additions and 0 deletions
|
|
@ -101,6 +101,36 @@ def _write_bytes(bundle_dir: Path, name: str, content: str) -> Path:
|
|||
return resolved
|
||||
|
||||
|
||||
def _update_index_lines(
|
||||
index_path: Path, removed_targets: set[str], labels_by_target: dict[str, str]
|
||||
) -> None:
|
||||
"""§6 maintenance on an EXISTING index: drop managed lines whose target is
|
||||
an ingest file removed in this run; refresh in place a managed label that
|
||||
no longer equals the extraction title. Every other line is preserved
|
||||
verbatim, in order — including its own line ending.
|
||||
"""
|
||||
original = index_path.read_bytes().decode("utf-8")
|
||||
lines = original.splitlines(keepends=True)
|
||||
updated: list[str] = []
|
||||
changed = False
|
||||
for line in lines:
|
||||
content = line.rstrip("\r\n")
|
||||
ending = line[len(content) :]
|
||||
match = _MANAGED_LINE_RE.match(content)
|
||||
if match is not None:
|
||||
target = match.group("target")
|
||||
if target in removed_targets:
|
||||
changed = True
|
||||
continue
|
||||
new_label = labels_by_target.get(target)
|
||||
if new_label is not None and match.group("label") != new_label:
|
||||
line = f"- [{new_label}]({target})" + ending
|
||||
changed = True
|
||||
updated.append(line)
|
||||
if changed:
|
||||
index_path.write_bytes("".join(updated).encode("utf-8"))
|
||||
|
||||
|
||||
def _link_in_index(bundle_dir: Path, target_name: str, label: str) -> None:
|
||||
# §6: idempotent by target — a link whose target is already present in
|
||||
# the index is never added twice.
|
||||
|
|
@ -215,8 +245,15 @@ def materialize_bundle(
|
|||
# §6 index generation — the last disk mutation. A fresh index gets
|
||||
# bundle_summary as its body; links are appended in extraction order.
|
||||
index_path = bundle / _INDEX_NAME
|
||||
labels_by_target = {
|
||||
generated_filename(extraction.id): extraction.title for extraction in manifest.extractions
|
||||
}
|
||||
if not index_path.is_file():
|
||||
_write_bytes(bundle, _INDEX_NAME, manifest.bundle_summary + "\n")
|
||||
else:
|
||||
# Links whose target is an ingest-owned file removed this run MUST be
|
||||
# removed; all other links — curated and promoted — are preserved.
|
||||
_update_index_lines(index_path, owned - staged_names, labels_by_target)
|
||||
for extraction in manifest.extractions:
|
||||
_link_in_index(bundle, generated_filename(extraction.id), extraction.title)
|
||||
return IngestResult(written=written)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue