M9 S4: sync adopts serialization/parse_dt toolkit + normalizes push oracle
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 32s

DRY:
- serialize.py: serialize_label_sync(label) = base serialize_label + the
  delta-only fields (sync_revision/purged_at/created_at via iso()). sync's
  changes() adopts it; the local _serialize_label_row near-dup is gone.
- sync adopts common.parse_dt (drops the byte-identical _parse_client_dt;
  4 call sites) and common.iso for the note delta augmentation. (Manual-label
  reconciliation was already shared in S3.) Fully folding the note
  re-augmentation into the serializer waits on the notes.py split.
- test_sync: drops the now-redundant _parse_client_dt test (parse_dt is
  covered in test_notes) + its dead import.

Security (issue — push existence-oracle): a foreign-owned id on push was
rejected with "not yours", distinguishing "another user's note" from a free
id. A legit client only pushes ids of notes it created, so that branch is
only hit by a probe (or ~0-prob UUID collision) — now a GENERIC "cannot apply"
rejection that doesn't confirm the id exists. The residual create-vs-reject
status difference is inherent to client-chosen ids over a global PK and is
practically unexploitable (a shared note already exposes its id to recipients).

Sync behavior operator-verified on deploy (no Postgres CI lane).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-23 21:45:36 -04:00
co-authored by Claude Opus 4.8
parent e1cf63e875
commit be34fe8619
3 changed files with 31 additions and 38 deletions
-9
View File
@@ -8,7 +8,6 @@ from thoughtsync.sync import (
MAX_LIMIT,
_clamp_limit,
_page_cursor,
_parse_client_dt,
_parse_since,
client_wins,
)
@@ -42,14 +41,6 @@ def test_client_wins():
assert client_wins(None, None) is True
def test_parse_client_dt():
assert _parse_client_dt("2026-07-22T00:00:00Z").year == 2026
assert _parse_client_dt("2026-07-22T00:00:00+00:00").tzinfo is not None
assert _parse_client_dt("garbage") is None
assert _parse_client_dt(None) is None
assert _parse_client_dt(123) is None
def test_parse_since():
assert _parse_since(None) == 0
assert _parse_since("42") == 42