Problem
Replay.replayState seeds sequenceNr = 0 and only advances it from a replayed snapshot or replayed events — it never consults journal.highestSeq (src/persistence/Replay.ts:72, 93-99). PersistentActor adopts that value as _seq (src/persistence/PersistentActor.ts:231-232) and persistAll passes it as expectedSeq.
After a compaction that removed every event with no surviving snapshot, every backend correctly preserves its high-water mark (RelationalJournal.readHead, InMemoryJournal.highWater, Cassandra max_sequence_nr, DynamoDB deletedTo) — so the very next persist() throws JournalConcurrencyError(expected 0, actual N). Permanently. The actor can never write again.
This is the direct consequence of the fix for #379 (the high-water mark deliberately never rewinds); the replay seed was not updated to match. The journal contract suite has "full delete preserves highestSeq" (tests/integration/brokers/lib/persistence-contract/journal.ts:204), but nothing exercises the actor-level recovery path.
Proposed behaviour
Seed sequenceNr from max(snapshot.sequenceNr, await journal.highestSeq(persistenceId)) in replayState. assertTrustworthySnapshot already reads highestSeq, so the snapshot path pays no extra round-trip.
Acceptance criteria
- Append 10 events,
journal.delete(pid, 10), restart the actor → persisting event 11 succeeds.
- Covered at the actor level (not only the journal contract), across the contract harnesses.
Related: #379 (closed — created this condition), companion issue on the deleteHistory footgun that triggers it: #629
Problem
Replay.replayStateseedssequenceNr = 0and only advances it from a replayed snapshot or replayed events — it never consultsjournal.highestSeq(src/persistence/Replay.ts:72,93-99).PersistentActoradopts that value as_seq(src/persistence/PersistentActor.ts:231-232) andpersistAllpasses it asexpectedSeq.After a compaction that removed every event with no surviving snapshot, every backend correctly preserves its high-water mark (
RelationalJournal.readHead,InMemoryJournal.highWater, Cassandramax_sequence_nr, DynamoDBdeletedTo) — so the very nextpersist()throwsJournalConcurrencyError(expected 0, actual N). Permanently. The actor can never write again.This is the direct consequence of the fix for #379 (the high-water mark deliberately never rewinds); the replay seed was not updated to match. The journal contract suite has "full delete preserves highestSeq" (
tests/integration/brokers/lib/persistence-contract/journal.ts:204), but nothing exercises the actor-level recovery path.Proposed behaviour
Seed
sequenceNrfrommax(snapshot.sequenceNr, await journal.highestSeq(persistenceId))inreplayState.assertTrustworthySnapshotalready readshighestSeq, so the snapshot path pays no extra round-trip.Acceptance criteria
journal.delete(pid, 10), restart the actor → persisting event 11 succeeds.Related: #379 (closed — created this condition), companion issue on the
deleteHistoryfootgun that triggers it: #629