Skip to content

fix(stores): uniform deleted-entity listing + non-poisoning Redis appends (ARN-192)#350

Open
rita-aga wants to merge 1 commit into
mainfrom
claude/arn-192-store-divergence
Open

fix(stores): uniform deleted-entity listing + non-poisoning Redis appends (ARN-192)#350
rita-aga wants to merge 1 commit into
mainfrom
claude/arn-192-store-divergence

Conversation

@rita-aga

@rita-aga rita-aga commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Closes ARN-192 (High, Bug) under epic ARN-165. Draft for review — do not merge.

Two store-backend divergences

(a) Deleted-entity visibility. Turso list_entity_ids filtered tombstoned entities at the DB layer, but Postgres/Redis/Sim did not — so after a restart, deleted entities reappeared in GET /EntitySet on those backends. Fix: deletion is now decided once, in the shared cold path populate_index_from_store, via latest_event_is_tombstone applying the canonical is_deleted_envelope predicate (identical across all four backends, payload-action-aware); the Turso-specific SQL subquery is dropped.

(b) Redis reported committed writes as failures. Redis append committed the event + advanced seq atomically via Lua, then did non-atomic post-commit segment bookkeeping that ?-propagated transient errors — turning a durable append into an Err → wedged actor (permanent ConcurrencyViolation). Fix: removed the post-commit bookkeeping; the Lua commit is the sole authority.

Red→green

  • Sim deleted_entity_absent_from_list_after_restart_sim: RED (deleted entity reappeared) → GREEN. Redis variant GREEN against live Redis. Turso restart-delete test stays GREEN with the subquery dropped.
  • Redis committed_append_survives_broken_segment_metadata: RED (append returned Err for a committed event) → GREEN.
  • Scoped: store-turso 63, store-redis 6, store-sim 12, full temper-server suite green (28 binaries). ADR-0161. Code review PASS + DST-READY (both genuine).

Beneficial side effect

Dropping the Turso subquery makes migrate_turso_to_postgres migrate deleted entities' full history (previously silently skipped) — documented in the ADR.

🤖 Generated with Claude Code

…Redis append (ARN-192)

Two EventStore-backend divergences implemented the same operation with different
semantics.

(a) Deleted-entity visibility. Whole-tenant `list_entity_ids` filtered tombstoned
entities only on Turso, so after a restart a deleted entity reappeared in
`GET /EntitySet` on Postgres/Redis/Sim. Deletion is now decided once, in the shared
cold-path index population (`populate_index_from_store` /
`populate_index_from_store_by_type`), via the canonical `is_deleted_envelope`
predicate — the same check the hot-path `ensure_entity_loaded` already applies — so
all four backends exclude deleted entities identically, including the
`payload.action == "Deleted"` case a plain SQL filter misses. The Turso-specific
whole-tenant `event_type='Deleted'` subquery is dropped; the store returns raw
distinct pairs and the server layer is the single deletion authority.

(b) Redis reported failure for a committed write. `append` committed the events and
advanced the sequence atomically inside the Lua script, then ran non-atomic
post-commit segment bookkeeping (each step `?`-propagating). A transient error there
returned `Err` for an already-durable write, so the actor kept a stale sequence and
every later append hit a permanent `ConcurrencyViolation` (wedged actor), while a
retry re-applied the command and duplicated events. The segment bookkeeping — never
read back for the Redis backend — is removed; the Lua commit is the sole authority
on whether an append succeeded.

Starts with ADR-0155.

Tests (red-green):
- deleted_entity_index_parity: a deleted entity is absent from a listing after a
  restart on Sim (deterministic) and Redis (gated on REDIS_URL).
- Redis committed_append_survives_broken_segment_metadata: a forced post-commit
  failure no longer wedges the actor or duplicates events.
- Turso store-contract and replay-parity tests updated to the now-uniform behavior.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@rita-aga rita-aga marked this pull request as ready for review July 9, 2026 23:59
@rita-aga

rita-aga commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile review

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rita-aga has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rita-aga has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@rita-aga

Copy link
Copy Markdown
Collaborator Author

ARN-165 principle audit — request changes

The Redis Lua-append change correctly avoids returning failure after a successful commit. Deleted-entity parity and the broader store contract remain incomplete.

Blocking findings

  1. Cold listing is an N+1 full-history scan. latest_event_is_tombstone calls read_events(pid, 0) for every entity, sequentially, loading all historical events.
  2. Read errors become false truth. An error is treated as “not deleted,” the entity is inserted, and the type is still marked hydrated. Normal lazy listing will not automatically repair it.
  3. Delete→recreate remains backend-divergent. Postgres/Turso typed listings hide an entity if it has ever had a Deleted event, before the shared latest-event check can recover it.
  4. Tests do not prove the contract. They cover delete but not resurrection; only Sim is always exercised, while Redis silently skips without REDIS_URL.
  5. The ADR punts active divergence. It defers remaining list methods, contradicting the durable no-punt acceptance criterion.
  6. Segment metadata is not demonstrably dead. Snapshot code still reads/updates it, while Turso append paths disagree about segment updates.

Required direction

Add a bounded canonical read_latest_event/latest-tombstone projection, make hydration fail and retry on uncertainty, define delete→recreate semantics once, and run one shared contract/fault suite against every backend. Fold append/segment/snapshot truth into ARN-201's atomic store contract.

The Integrity failure is also real: this branch raises the production println! ratchet from 247 to 248. Please remove it rather than updating the baseline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant