Files
amethyst/quartz
Claude c54f34ef08 refactor(quartz): make EventStoreProjection store-agnostic, NIP-aware
The projection now sits on top of any IEventStore, not just SQLite.
Stores publish a simple `inserts: SharedFlow<Event>` (one event per
successful insert), and the projection itself replays the NIP rules
against that stream:

- NIP-01 supersession with the lexical-id tiebreaker, for both
  replaceables (kind 0/3/10000-19999) and addressables (30000-39999).
  Both now share a single in-place update path.
- NIP-09 deletions, with the original-author check (cross-author
  kind-5s are inert, matching the store).
- NIP-62 right-to-vanish, scoped by the projection's relay arg.
- NIP-40 expiration via a per-projection ticker that drops slots whose
  expiration tag has lapsed.

Out-of-band store mutations (`delete(id)`, `clearDB()`, the periodic
`deleteExpiredEvents()` sweep) are no longer visible to projections —
they're maintenance ops; projections re-seed when their scope is
restarted.

Removed from SQLiteEventStore:
- ChangeLogModule + temp-table + AFTER DELETE trigger.
- StoreChange sealed type and the per-mutation drain/publish path.

Both SQLiteEventStore and FsEventStore now satisfy
`IEventStore.inserts`. FsEventStore.insertLocked returns Boolean so
no-op idempotent retries (canonical already on disk) don't re-publish.

Tests:
- EventStoreProjectionTest moves to `store/projection/`, gains four
  new cases: NIP-01 out-of-order rejection, NIP-09 cross-author
  inertness, NIP-62 cross-author no-op, NIP-40 ticker-driven removal.
- 13/13 projection tests + 232/232 total store tests pass.

https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5
2026-04-29 19:56:00 +00:00
..