Files
poimen/tasks/T0.2-kernel-attemptstate.md

114 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# T0.2 — Kernel `AttemptState`
| Field | Value |
|---|---|
| Phase | P0 — Foundations |
| Size | S — under 1 day |
| Status | ✅ Done |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
## Goal
The closed kernel attempt enum and its transition function, matching the
normative transition table arm for arm.
## Facts (inlined — no spec read needed)
```rust
/// Kernel. Closed. Users never extend this.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AttemptState {
Pending,
Running,
Succeeded,
Failed,
/// Crashed mid-side-effect; recovery could not determine what happened.
Indeterminate,
/// Stopped by decision, no dispatched intent outstanding.
Cancelled,
TimedOut,
}
```
Normative legal set — the match has exactly these arms and no others:
| From | To | When |
|---|---|---|
| `Pending` | `Running` | admitted |
| `Pending` | `Cancelled` | run cancelled before the attempt started |
| `Pending` | `TimedOut` | queue deadline passed before admission |
| `Running` | `Succeeded` | completed |
| `Running` | `Failed` | completed with an error |
| `Running` | `TimedOut` | step deadline passed |
| `Running` | `Cancelled` | run cancelled, **no intent was `Dispatched`** |
| `Running` | `Indeterminate` | an intent was `Dispatched` and did not resolve |
| terminal | — | nothing leaves a terminal state |
- `Cancelled` and `Indeterminate` are **separate states**. Cancellation is a
decision; indeterminacy is an unknown. The deciding fact is the intent record
(T2.1): no `Dispatched` intent means nothing external happened, so the attempt
is cleanly `Cancelled`.
- Why it matters beyond taxonomy: operations alerts on `Indeterminate` count and
expects near zero. Route ordinary cancellations there and the alert acquires a
noisy floor, which is the same as not having the alert.
- Domain states are the opposite — open, declared by the workflow as data
(`pub struct StepState(SmolStr)`), validated at load. Not this task.
## Steps
1. Declare `AttemptState` exactly as above.
2. Write `fn transition(from: AttemptState, cause: TransitionCause) -> Result<AttemptState, IllegalTransition>`
as an **exhaustive match** — no `_ =>` catch-all, so adding a state becomes a
compile error at this function.
3. Model the `Running → {Cancelled, Indeterminate}` split on an explicit input:
`TransitionCause::Cancel { intent_dispatched: bool }`. Never infer it from the
drop site.
4. Make every terminal state reject all outgoing transitions with
`IllegalTransition`.
5. Property test with `proptest`: enumerate all `(from, to)` pairs, assert the
accepted set equals the table above exactly — reject-set included.
## Acceptance
- Property test over all state pairs: legal set matches the table exactly.
- Targeted test: cancelling an attempt with **no** `Dispatched` intent yields
`Cancelled`; cancelling one **with** a `Dispatched` intent yields
`Indeterminate`.
## Verify
**Harness:** `proptest`, plus a recorded log fixture once P1 exists.
**Integration test**`tests/it_transition_table.rs`:
1. Build the **cartesian product** of all 7 states × all 7 states, written out by
hand from the table in this file — not generated by the code under test.
2. For each pair, assert accept/reject matches the table.
3. Replay a real recorded run's log (P1 fixture) and feed every observed state
change through `transition`; assert none is rejected. This proves the executor
and the table agree.
4. Two targeted cases: cancel with `intent_dispatched: false``Cancelled`;
cancel with `true``Indeterminate`.
**Command:** `cargo test -p kernel transition`
**False pass:**
- A property test whose generator produces states **by calling `transition`**.
It then agrees with itself and cannot see a missing arm. The pair list must be
written independently.
- Asserting only the accepted set. The rejected set is half the table, and a
permissive `_ => Ok(to)` passes every accept-only test.
## Traps
- Collapsing `Cancelled` into `Indeterminate` "because the tool might have been
mid-something". That "might" is exactly what the intent record answers.
- A `_ =>` arm. It compiles forever and silently absorbs a new state.
---
Background (not required to do this task):
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §2, §5.1, §15 ·
[rust-agentic-task.md](../../../rust-agentic-task.md)