fix(service): make expected_version mandatory on update_note and edit

Both guarded with `if expected_version and ...`, so an empty string
skipped the version check entirely and silently overwrote the note.
Empty/missing expected_version is now a structured INVALID error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139vHnNpS2sLNELjrAHX5Cu
This commit is contained in:
MarkusandClaude Opus 5 committed 2026-09-12 14:33:13 +00:00
1 parent de752d126b
commit dbe9acf155
2 files changed
+27 -2

No files matched your search

+6 -2
View File
@@ -426,7 +426,9 @@ class MemoryService:
"""Active notes are never edited in place by an agent: the edit becomes a
proposed revision in the inbox (§2.4, §9)."""
note = self.get_note(note_id)
if expected_version and expected_version != note.file_hash:
if not expected_version:
raise Invalid("expected_version is required; read the note first and pass its version", id=note_id)
if expected_version != note.file_hash:
raise VersionConflict(note_id, expected_version, note.file_hash)
if note.state == "archive":
raise Archived(note_id)
@@ -587,7 +589,9 @@ class MemoryService:
) -> dict[str, Any]:
"""Direct edit from the UI: the owner may change an active note in place."""
note = self.get_note(note_id)
if expected_version and expected_version != note.file_hash:
if not expected_version:
raise Invalid("expected_version is required; read the note first and pass its version", id=note_id)
if expected_version != note.file_hash:
raise VersionConflict(note_id, expected_version, note.file_hash)
if frontmatter:
for k, v in frontmatter.items():
+21
View File
@@ -32,3 +32,24 @@ async def test_update_note_on_a_rejected_archived_note_is_refused(svc):
unchanged = svc.get_note(w["id"])
assert unchanged.body.strip() == "alt"
assert unchanged.get("rejected") is True
# -- finding 4: expected_version must be mandatory, not just "truthy" ---------
async def test_update_note_with_empty_expected_version_is_rejected(svc):
w = await svc.write_note(title="T", body="original", type="note")
with pytest.raises(Invalid):
await svc.update_note(w["id"], "", body="entfuehrt")
unchanged = svc.get_note(w["id"])
assert unchanged.body.strip() == "original"
async def test_edit_with_empty_expected_version_is_rejected(svc):
w = await svc.write_note(title="T", body="original", type="note")
with pytest.raises(Invalid):
await svc.edit(w["id"], body="entfuehrt", expected_version="")
unchanged = svc.get_note(w["id"])
assert unchanged.body.strip() == "original"