fix(save): disambiguate derived slugs to prevent silent overwrites - #133
fix(save): disambiguate derived slugs to prevent silent overwrites#133Karunasagar12 wants to merge 1 commit into
Conversation
When slug is omitted, save_memory() derives one from the opening words of the content. Two saves whose openings agree resolved to the same file, silently overwriting the first with no error or warning. Fix: after deriving the slug, check if the target path already exists with different content. If so, append a sha256[:8] hash of the full content to the slug. This produces stable, deterministic filenames that won't collide unless content is genuinely identical (in which case the re-save is allowed through as idempotent). Design decisions per issue phasespace-labs#129: - Hash the FULL content, not a prefix (avoids the 100-char trap) - Loop until candidate filename is unused or byte-identical - Explicitly-passed slugs still overwrite (documented escape hatch) - Stays inside core/save.py (does not touch ingest/pipeline.py) - Uses frontmatter lib directly (already a project dependency) Tests: - Shared opening line → two distinct files, both retrievable - Identical first 100 chars, different after → still disambiguates - Explicit slug → still overwrites (escape hatch preserved) - Identical content re-save → idempotent (same file, no disambiguation) - Three collisions → three distinct files Fixes phasespace-labs#129
|
Thank you for this. I owe you an explanation for the overlap before asking anything else: kratos0718 I'm keeping this open as the fallback while #132 goes through a review round. Your implementation is |
|
An update on the follow-up I promised you, because "within the day" has come and gone and silence It isn't filed yet, and the reason is the same collision that closed this PR: the save-receipt issue #132 hasn't moved in about a day. If it stays that way I'll come back to you either way — the issue |
|
Correcting myself twice in one thread, which is not a great look but better than leaving either one First, small one: my note earlier today called this PR closed. It isn't — it's open, and it was Second, and more worth your time. I went back through this properly today rather than trusting how I Your four failing checks are all mechanical and none is in the logic: an unused I'm not asking you to push anything yet. #129 is assigned to kratos0718 and he gets the first move on |
Summary
Fixes #129 — when
slugis omitted,save_memory()derives one from the opening words of the content. Two saves whose openings agree resolved to the same file, silently overwriting the first.What changed
In
palinode/core/save.py, after deriving the slug (only when not explicitly passed):sha256(full_content)[:8]to the slugDesign decisions (per issue guidance)
core/save.py— does not touchingest/pipeline.py(separate issue, as noted)frontmatterlib directly to extract body for comparison (already a project dependency)Tests
Added
tests/test_save_slug_collision.py— 5 tests:test_two_saves_shared_opening_produce_distinct_filestest_content_identical_first_100_chars_still_disambiguatestest_explicit_slug_still_overwritestest_identical_content_resave_is_idempotenttest_three_collisions_produce_three_filesExisting save tests (29) also pass.