Do not overwrite an unrelated memory on a derived-slug collision - #132
Do not overwrite an unrelated memory on a derived-slug collision#132kratos0718 wants to merge 1 commit into
Conversation
When save derives a slug from the opening words of the content, two saves whose openings agree resolve to the same path and the second replaces the first -- no error, no warning, and a receipt identical to a fresh create. The displaced memories remain in git history but leave the queryable store entirely. A derived slug that collides now takes the next free suffixed path, so each memory keeps its own file. Re-saving identical content is still a single file: the incoming content hash is compared against the content_hash already in the target's frontmatter, so the same memory arriving twice does not accumulate duplicates. An explicitly passed slug is untouched. A collision there is an update of the same logical memory, which is what the existing created_at carry-forward assumes, and it keeps overwriting.
|
The main collision fix is right, but there is one idempotency case the current tests don't reach. If A owns Please make the suffix scan reuse an existing candidate whose
Everything else in the approach looks good, including keeping explicit-slug overwrites and failing |
|
One addition to the note above, and it's our sequencing rather than anything on your side: #131 So when you push the idempotency fix, update onto |
Fixes #129.
What was happening
When
slugis omitted, it is derived from the opening words of the content. Two saves whose openings agree resolve to the same path, and the second replaces the first — no error, no warning, and a receipt identical to a fresh create. Reproducing your three-save case in a test:The distinction the fix turns on
The existing-path branch is deliberate, and I did not want to disturb it. Re-saving an explicit slug is an update of the same logical memory — that is the case
created_atcarry-forward exists for, and it should keep overwriting.A derived slug colliding is a different thing: two unrelated memories that happen to start alike. So the save path now records whether the slug was derived, and only that case is disambiguated. Explicit-slug behaviour is byte-for-byte unchanged.
Behaviour
slug-2,slug-3, … Nothing is overwritten and the receipt reports the path actually written.content_hashalready in the target's frontmatter, so the same memory arriving twice does not litter the store.The suffix search is bounded, with a timestamp fallback so it always terminates.
Open question
I kept explicit-slug collisions silent, since that is the current contract. If you would rather they became loud too — a distinct receipt field, or a rejection when
update_policydoes not permit replacement — I am happy to follow up; it felt like your design call rather than mine, so I did not fold it into a bug fix.Tests
tests/test_save_derived_slug_collision.py:test_colliding_derived_slugs_do_not_overwritetest_identical_content_is_not_duplicatedtest_explicit_slug_still_overwritesThe third passes before and after on purpose — it pins the behaviour this change must not alter.