Size / Priority
Rationale
PersistentActor assumes journal events: arrive in-order, exactly-once, immutable. Real-world failures break these:
- Network glitch duplicates an event.
- Backend corruption skips events.
- Manual ops accidentally reorders entries.
A Replay-Mutation Recovery Fuzzer automatically mutates a journal during test-replay and verifies recovery behaviour:
- Delete a random event → does the actor surface a coherent error?
- Duplicate an event → does it idempotently handle?
- Reorder events → does sequence-validation catch it?
- Corrupt an event payload → does deserialisation fail safely?
Catches silent corruption assumptions.
Design sketch
// src/testkit/ReplayFuzzer.ts (new)
export type MutationStrategy = 'delete' | 'duplicate' | 'reorder' | 'corrupt' | 'truncate';
export interface ReplayFuzzerOptions {
readonly strategies: ReadonlyArray<MutationStrategy>;
readonly mutationsPerRun?: number;
readonly seed: bigint;
readonly numRuns: number;
}
export function runReplayFuzzer<Cmd, Event, State>(
actorClass: new () => PersistentActor<Cmd, Event, State>,
baselineJournal: PersistentEvent<Event>[],
options: ReplayFuzzerOptions,
): Promise<FuzzerReport>;
interface FuzzerReport {
readonly totalRuns: number;
readonly succeeded: number;
readonly failedWithErrorMessage: number;
readonly silentlySucceeded: number; // RED FLAG — recovery passed despite mutation
readonly examples: ReadonlyArray<{ seed: bigint; mutation: string; result: 'ok' | 'error' | 'silent' }>;
}
Integration
Out of scope / non-goals
- Production use — testkit only.
- Cross-actor fuzzing — single actor at a time.
Open design questions
- Mutation policy: random subset (sketch) vs exhaustive. Random suits long fuzz runs.
- Detection of "silent success": tricky — recovery passed; was the state wrong? Compare against baseline final state.
Test plan
- Baseline replay → succeeds.
- Delete event → either errors clearly OR state matches "as if event was never persisted" (legitimate).
- Duplicate event → idempotent handler OR error.
- Reorder → sequence-validation catches.
- Corrupt payload → deserialise fails clearly.
- Report flags "silent success" as needs-investigation.
Acceptance criteria
Pre-implementation checklist
Size / Priority
Rationale
PersistentActor assumes journal events: arrive in-order, exactly-once, immutable. Real-world failures break these:
A Replay-Mutation Recovery Fuzzer automatically mutates a journal during test-replay and verifies recovery behaviour:
Catches silent corruption assumptions.
Design sketch
Integration
PersistentActor: replay through mutated journal.Out of scope / non-goals
Open design questions
Test plan
Acceptance criteria
runReplayFuzzerfunction.Pre-implementation checklist