# Slot State Machine Specification

## Overview

The slot module uses a single entry-level `ParkingMutex<EventState>` that holds
all per-entry state including waker registration. This design eliminates
stale-completion races by construction — there is no separate lock or atomic
guard whose ordering could allow stale results to leak across generations.

## State Variables

All fields are protected by a single `parking_lot::Mutex`:

```rust
struct EventState {
    last_triggered: Generation,              // highest completed generation
    active_generation: Option<Generation>,   // currently pending generation
    wakers: Vec<Waker>,                      // registered waiter wakers
    poisoned: BTreeMap<Generation, PoisonArc>, // poison history per generation
    retired: bool,                           // permanently unusable
}
```

## Lifecycle Phases

```
    begin_generation()
Idle ──────────────────> Active
  ^                        |
  |   finalize_completion()|
  |                        v
  +──────────────────── Completing
```

All transitions happen under a single lock acquisition.

### Idle

- `active_generation = None`
- `wakers` is empty (drained by prior `finalize_completion`)
- Entry is available for reuse via the free list

### Active

- `active_generation = Some(gen)`
- Waiters may register wakers via `poll_waiter`
- Only one generation can be active at a time

### Completing

- `finalize_completion` sets `last_triggered`, clears `active_generation`,
  stores poison (if applicable), drains wakers, then wakes them
- Transitions back to Idle

## Operations

### begin_generation

1. Acquire lock
2. Validate: no active generation, not retired, not overflowed
3. Compute `next = last_triggered + 1`
4. Drain stale wakers (`std::mem::take`)
5. Set `active_generation = Some(next)`
6. Release lock
7. Wake stale wakers (outside lock)

### finalize_completion(generation, completion)

1. Acquire lock
2. Validate: `active_generation == Some(generation)`
3. Set `last_triggered = generation`
4. Clear `active_generation`
5. Insert/remove from poison map
6. Drain wakers
7. Release lock
8. Wake all drained wakers (outside lock)

### register_local_waiter(generation)

1. Acquire lock
2. If `generation <= last_triggered`: return Ready or Poisoned
3. If `active_generation == Some(generation)`: return Pending
4. Otherwise: return InvalidGeneration error

### poll_waiter(observed_generation, cx)

1. Acquire lock
2. If `observed_generation <= last_triggered`: return completion result
3. If `active_generation.is_none()`: return "generation expired" error
4. Register waker with deduplication (`will_wake` check)
5. Return Pending

### try_to_poison(generation, poison)

1. Acquire lock
2. If `generation <= last_triggered`:
   - If `poisoned.contains_key(generation)`: return `AlreadyPoisoned`
   - Else: return `AlreadyCompleted` error
3. Validate: `active_generation == Some(generation)`
4. Set `last_triggered = generation`
5. Clear `active_generation`
6. Insert into poison map
7. Drain wakers
8. Release lock
9. Wake all drained wakers (outside lock)
10. Return `Poisoned`

This is equivalent to an atomic `status_for` + `finalize_completion(Poisoned)`,
eliminating the TOCTOU window when the two are called separately.

### retire

1. Acquire lock
2. Debug-assert: wakers list is empty (callers should ensure all waiters resolved before retirement)
3. Set `retired = true`, clear `active_generation`
4. Drain wakers (defensive, prevents silent hangs if invariant violated)
5. Release lock
6. Wake drained wakers (outside lock)

## Invariants

- **I1: Generation monotonicity** — `last_triggered` only increases. Each
  `begin_generation` computes `last_triggered + 1`.

- **I2: Single completion per generation** — `active_generation` guard ensures
  only one generation is active. `finalize_completion` validates
  `active_generation == Some(generation)`.

- **I3: Completion visibility** — Waiter resolution is determined by
  `observed_generation <= last_triggered` (success) plus the `poisoned`
  BTreeMap (error). Both are set under the same lock that the waiter reads.

- **I4: No stale completion leakage** — There is no stored completion value
  that could leak. Waiters resolve via generation comparison + poison map.
  `begin_generation` unconditionally drains stale wakers.

- **I5: No lost wakeups** — `finalize_completion` sets `last_triggered` and
  drains wakers in the same lock scope. Any waiter that registered before
  the drain will be woken. Any waiter that polls after the drain will see
  `observed_generation <= last_triggered` and resolve immediately.

- **I6: Stale waiter resolution** — Waiters from generation N check
  `observed_generation <= last_triggered` on every poll. After generation N
  completes, this check succeeds regardless of what generation is currently
  active. `begin_generation` also flushes stale wakers defensively.

## Concurrency Rules

A single `parking_lot::Mutex` per entry serializes all state mutations. This
eliminates the need for:

- Atomic `waiter_count` (no conditional clearing)
- Atomic `completed` flag (redundant with lock-protected state)
- Separate slot-level `generation` counter (entry-level suffices)
- Manual waker deduplication across lock boundaries

The only concurrency pattern is: acquire lock, read/write state, release lock,
then wake drained wakers outside the lock (waker invocation only enqueues
tasks on the runtime, it does not poll them synchronously).
