Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions docs/false-claims/FC-001-determinism-constant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Falsification Audit: FA-001

## Claim

**"DETERMINISM = TRUE"** - Expressed in dispatcher.ts constant and architectural documentation

**Where Expressed**:

- `packages/core/src/dispatcher.ts` line 19: `DETERMINISM = TRUE`
- README.md line 33: "ensures that your business logic remains pure...and your bugs are 100% reproducible via time-travel replay"
- ARCHITECTURE.md line 3: "designed to be deterministic, race-condition resistant"

## Enforcement Analysis

**Enforcement**: Partially enforced by code

- FIFO queue processing prevents race conditions
- Message logging enables replay
- Time/random providers capture entropy

**Missing Enforcement**:

- No verification that update functions are pure
- No detection of side effects in update functions
- Replay only validates final state, not intermediate states
- Effects are not replayed (only messages are)

## Mock/Test Double Insulation

**Critical Reality Amputation**:

- Tests use `vi.useFakeTimers()` - removes real timer behavior
- Mock fetch/worker implementations remove network and concurrency failures
- No tests with real I/O errors, timeouts, or partial failures
- Stress tests use deterministic message patterns, not chaotic real-world inputs

**What's NOT Tested**:

- Network timeouts and connection drops
- Worker crashes and memory limits
- Timer precision issues across browsers
- Concurrent access to shared resources
- Memory pressure during high throughput
- Browser event loop starvation

## Falsification Strategies

### 1. Property-Based Replay Testing

```typescript
// Generate chaotic message sequences with real timers
test("replay preserves state under random async timing", async () => {
const realTimers = true;
const chaosFactor = 0.1; // 10% random delays

// Generate messages with unpredictable timing
const log = await generateChaoticSession(chaosFactor, realTimers);

// Replay should match exactly
const replayed = replay({ initialModel, update, log });
expect(replayed).toEqual(finalSnapshot);
});
```

### 2. Effect Falsification

```typescript
// Test that effects don't break determinism
test("effects are purely data, not execution", () => {
let effectExecutionCount = 0;
const effectRunner = (effect, dispatch) => {
effectExecutionCount++;
// Real network calls, timers, etc.
};

// Same message log should produce same effects regardless of execution
const effects1 = extractEffects(log1);
const effects2 = extractEffects(log1);
expect(effects1).toEqual(effects2);
});
```

### 3. Concurrency Stress Testing

```typescript
// Real concurrent dispatch from multiple event sources
test("determinism under real concurrency", async () => {
const sources = [
networkEventSource(),
timerEventSource(),
userEventSource(),
workerMessageSource(),
];

// Run all sources concurrently with real timing
await Promise.all(sources.map((s) => s.start(dispatcher)));

// Verify replay produces identical state
const replayed = replay({ initialModel, update, log });
expect(replayed).toEqual(finalSnapshot);
});
```

### 4. Memory Pressure Testing

```typescript
// Test determinism under memory constraints
test("replay preserves state under memory pressure", async () => {
// Simulate memory pressure during replay
const memoryLimitedReplay = withMemoryLimit(() =>
replay({ initialModel, update, largeLog }),
);

expect(memoryLimitedReplay).toEqual(normalReplay);
});
```

### 5. Real Network Failure Injection

```typescript
// Test with real network failures, not mocks
test("determinism despite real network failures", async () => {
const flakyNetwork = new FlakyNetworkService({
failureRate: 0.1,
timeoutMs: 1000,
retryStrategy: "exponential-backoff",
});

// Run session with real network failures
await runSessionWithNetwork(dispatcher, flakyNetwork);

// Replay should be deterministic despite failures
const replayed = replay({ initialModel, update, log });
expect(replayed).toEqual(finalSnapshot);
});
```

## Classification

**Status**: Weakly Supported

**Evidence**:

- FIFO processing prevents race conditions
- Message logging enables basic replay
- Time/random capture preserves some entropy

**Contradictions**:

- Effects are not replayed, breaking full determinism
- No enforcement of update function purity
- Tests insulated from real-world failures
- Phantom pending bug class documented in ideas.md

**Falsification Risk**: HIGH - The claim overstates what's actually guaranteed. Real-world concurrency, network failures, and memory pressure are not tested or protected against.

## Recommendation

Replace "DETERMINISM = TRUE" with "MESSAGE_ORDERING_DETERMINISM = TRUE" and document that effect execution and external I/O are not deterministic.
181 changes: 181 additions & 0 deletions docs/false-claims/FC-002-atomic-processing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Falsification Audit: FA-002

## Claim

**"Atomic Processing: Messages are processed one at a time via a FIFO queue, eliminating race conditions by design"**

**Where Expressed**:

- README.md line 39
- ARCHITECTURE.md line 11: "Serialized Processing: Messages are processed one at a time via a FIFO queue in the Dispatcher. Re-entrancy is strictly forbidden."

## Enforcement Analysis

**Enforcement**: Strongly enforced by code

- `isProcessing` flag prevents concurrent processing
- Single `processQueue()` function with while loop
- Re-entrancy handled via queueing, not immediate execution

**Code Evidence**:

```typescript
const processQueue = () => {
if (isProcessing || isShutdown || queue.length === 0) return;
isProcessing = true;
try {
while (queue.length > 0) {
const msg = queue.shift()!;
// Process single message
}
} finally {
isProcessing = false;
}
};
```

## Mock/Test Double Insulation

**Minimal Insulation**:

- Tests use real dispatcher logic
- No mocks for core queue processing
- Stress tests use actual message bursts

**What's NOT Tested**:

- Effect execution concurrency (effects run outside queue)
- Subscription lifecycle during processing
- Memory allocation during high-frequency processing
- Event loop interruption during long-running updates

## Falsification Strategies

### 1. Concurrent Effect Execution Test

```typescript
// Test that effects don't break atomicity
test("effects run outside atomic processing", async () => {
let effectConcurrency = 0;
const effectRunner = async (effect, dispatch) => {
effectConcurrency++;
await simulateAsyncWork();
effectConcurrency--;
dispatch({ kind: "EFFECT_DONE" });
};

// Dispatch multiple messages that trigger effects
for (let i = 0; i < 100; i++) {
dispatcher.dispatch({ kind: "TRIGGER_EFFECT" });
}

// Effects should be able to run concurrently
expect(effectConcurrency).toBeGreaterThan(1);
// But message processing should remain atomic
expect(dispatcher.getSnapshot().processedCount).toBe(100);
});
```

### 2. Memory Allocation Stress Test

```typescript
// Test atomicity under memory pressure
test("atomic processing under memory pressure", async () => {
const memoryHog = () => {
// Allocate large objects during update
return new Array(1000000).fill(0).map(() => ({
data: new Array(1000).fill(Math.random()),
}));
};

const updateWithAllocation = (model, msg) => {
if (msg.kind === "ALLOCATE") {
const largeData = memoryHog();
return {
model: { ...model, largeData },
effects: [],
};
}
return { model, effects: [] };
};

// Should not break atomicity despite GC pressure
for (let i = 0; i < 1000; i++) {
dispatcher.dispatch({ kind: "ALLOCATE" });
}

expect(dispatcher.getSnapshot().largeData).toBeDefined();
});
```

### 3. Event Loop Starvation Test

```typescript
// Test that long updates don't break atomicity
test("atomic processing with blocking updates", async () => {
let processingOrder = [];
const blockingUpdate = (model, msg) => {
processingOrder.push(msg.id);
// Simulate blocking operation
const start = Date.now();
while (Date.now() - start < 10) {} // Block for 10ms
return { model: { ...model, lastId: msg.id }, effects: [] };
};

// Dispatch multiple messages rapidly
for (let i = 0; i < 10; i++) {
dispatcher.dispatch({ kind: "BLOCK", id: i });
}

// Processing order should match dispatch order
expect(processingOrder).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
```

### 4. Subscription Interference Test

```typescript
// Test subscription lifecycle during processing
test("subscription changes don't break atomicity", async () => {
let subscriptionOrder = [];
const subscriptionRunner = {
start: (sub, dispatch) => {
subscriptionOrder.push(`START_${sub.key}`);
},
stop: (key) => {
subscriptionOrder.push(`STOP_${key}`);
},
};

// Messages that change subscriptions
dispatcher.dispatch({ kind: "ADD_SUB", key: "sub1" });
dispatcher.dispatch({ kind: "ADD_SUB", key: "sub2" });
dispatcher.dispatch({ kind: "REMOVE_SUB", key: "sub1" });

// Subscription changes should be atomic
expect(subscriptionOrder).toEqual(["START_sub1", "START_sub2", "STOP_sub1"]);
});
```

## Classification

**Status**: Likely True

**Evidence**:

- Strong code enforcement with `isProcessing` flag
- Comprehensive stress testing validates FIFO behavior
- No evidence of race conditions in tests
- Architecture correctly identifies re-entrancy handling

**Residual Risks**:

- Effect execution happens outside atomic processing
- Long-running updates could cause event loop issues
- Memory pressure during processing not tested

**Falsification Risk**: LOW - The core claim of atomic message processing is strongly enforced and well-tested.

## Recommendation

Keep the claim but clarify: "Atomic Processing: Messages are processed one at a time via a FIFO queue, eliminating race conditions in message processing. Effect execution and subscription lifecycle happen outside the atomic processing loop."
Loading