sync: owner-generation fencing for people_scan_dedupe (lever stays 0)

Merged
GI
2d ago
+984-83

Summary

Make people_scan_dedupe usable for sync spelling hints without cross-request staleness. Every People mutation atomically increments an owner generation document; each cached hint read validates that document once. Fill snapshots capture the generation before scanning, and metadata recreation is fenced by document update time.

Wire the sync attempt opener and known_spellings._people_spellings only. Keep vocabulary and authority lookups live. Keep the lever at 0; no rollout/config/deploy change.

Writer audit includes CRUD, speech samples/profiles, pinning, learning/jobs, auto-enrollment, assignment evidence, chat and Review undo/redo, backfill, and recursive account deletion. Existing accounts without metadata remain live until their next People mutation initializes the generation. Generation maintenance adds one atomic write per People mutation even while reuse is off.

Validation

  • WQ20 fix r2: the failed-Undo canary retry exposed target.parent.id in the new generic Undo generation check. The strict fixture lacks collection .id; the swallowed failure was AttributeError at the undo stage, with no People generation document created. Carry the validated journal collection with the Undo row instead. The canary test and fixture remain unchanged; cleanup round-trip coverage now asserts exact People generation increments and no non-People bumps.

  • R2 preflight: make preflight and scripts/pr-preflight --pr-body-file body.md each passed all 38 selected checks; draft metadata passed all 4 checks.

  • R2 regression: 749 passed across all 39 test_dream_*.py files plus test_fsr_people_scan_dedupe.py, test_fsr_scope_opener.py, and test_fsr_people_generation.py (42 isolated files; no skips/deselections). Canary: 12; other Dream: 665; scan dedupe: 39; scope opener: 20; generation writers: 13. See WQ20-fix-r2.md for mechanism and commands.

  • Review: zcode glm-5.3 VERDICT SAFE, no P1/P2 (exec-leopard-title-logic-blur-merge-profit, 2026-10-26); writer-fence audit of all 23 write sites, reader-admission and cost checks independently verified; reviewer re-ran 91 core + 142 regression + 9 import-lightness via backend/.venv. P3s accepted: generation .get() under memo lock (latency nit), +1 write per People mutation while lever is 0 (standing cost until a separate measured enablement PR), same-uid People transaction serialization (Firestore retry absorbs).

  • Worktree-local Python 3.11 environment installed with make setup.

  • Full make preflight: 38 checks passed; no check exceptions or skips.

  • Core selected suite: 91 passed; existing chat-emulator suite: 4 passed.

  • Targeted backend/test.sh runs cover attempt opener/retry/cancellation, generation and cross-context invalidation, off-path reads, and all touched writer families.

  • Local loopback Firestore emulator: 31 checks pass, including external writers, concurrent increments, failed atomic commits, and metadata recreation.

  • Counted reads: ten 100-document hint scans cost 1,000 reads off and 110 on, including generation lookups.

Product invariants affected

none

Failure class

Failure-Class: none

This is a new guarded optimization behind an existing default-off lever. The prior unsafe wiring attempt was closed without production rollout; this change supplies its required concurrency contract and regression proofs.

R2 fixes an internal journal/fixture API dependency exposed by the existing canary regression; no generation or memo leak was reproduced. Failure-Class: none remains applicable.