# M1-BUILD-PLAN — build the non-core rebound shell (JIT)

> The immediate body of work: code everything in M1 that does **not** need the spt-core activity
> seam (ADR-0001). Design is authoritative in `REBOUND-PLAN.md` + `CONTEXT.md`; this plan is the
> build order, the mock-seam contract, and the phase gate. Model the binary on
> `../spt-shell-notify` (the reference `kind = "shell"` adapter) — reuse its patterns verbatim
> where they fit.

## Scope

**In:** repo skeleton, the swappable activity-source seam + a mock, the full state machine
(latch / timer / backoff), ping build+emit, digest read+parse (codes), the `pause`/`delay`
capability drain, education, and all three hazard tests. Every one is testable against the mock —
no live spt-core edges required.

**Out (blocked on ADR-0001):** the *real* `ActivitySource` impl and the live idle-edge→ping E2E.
Those land when doyle's seam ships; keep the seam boundary a one-adapter swap.

## The mock-seam contract (the swap point)

The single interface that isolates the core dependency. Real impl arrives via ADR-0001; until
then a mock drives edges:

```rust
/// Activity of the bound endpoint. Both directions; a redundant same-state
/// item is a harmless no-op (matches the seam's no-op tolerance, ADR-0001).
#[derive(Clone, Copy, PartialEq)]
enum Activity { Busy, Idle }

/// Yields the bound endpoint's activity transitions. The ONLY thing blocked on
/// spt-core — everything else is driven from this trait.
trait ActivitySource {
    /// Next transition, or None when the source ends (shutdown).
    fn next(&mut self) -> Option<Activity>;
}

/// Test/bringup driver: a scripted transition sequence (optionally timed).
struct MockActivitySource { /* queue of (delay, Activity) */ }
```

The state machine consumes `Activity` items and knows nothing about their origin. Real seam =
a new `ActivitySource` that reads doyle's push channel; drop it in, delete nothing else.

## Build order (each step compiles + tests green before the next)

1. **Skeleton.** `Cargo.toml` (`[[bin]] name = "rebound-shell"`, empty `[workspace]` guard,
   `serde_json`, a time crate or std), `manifest.toml` (exactly `REBOUND-PLAN.md`'s block),
   `src/main.rs` resident shell: `api --adapter rebound bind-shell --link`, then the drain loop.
   Copy `spt_cmd` (CREATE_NO_WINDOW) + the EVENT-decode helpers from `../spt-shell-notify`.
2. **ActivitySource trait + MockActivitySource** (step above). Unit: a scripted sequence yields
   the expected edges.
3. **State machine** — `latch`/`activity`/`timer(deadline)`/`backoff_step`/`acted_seq`. Drive it
   from `ActivitySource`. This is the heart; test the whole truth table from `REBOUND-PLAN.md`
   (arm on busy→idle+raised, disarm+re-raise on idle→busy, elapse→ping, backoff widen/reset).
   → `REQ-REBOUND-LATCH`, `-TIMER`, `-BACKOFF`.
4. **Ping** — build the body (relative ~elapsed + absolute `HH:MM YYYY-MM-DD` local tz; the exact
   Q10 text), emit via `spt send --idle-only --ephemeral <owner>` (subprocess). Unit: body shape,
   and **no signoff directive** (`REQ-HAZARD-REBOUND-NO-SIGNOFF`). → `REQ-REBOUND-PING`.
5. **Digest read + parse** — `spt endpoint digest <owner> --last N --json`; serde structs for
   `turns[].entries[].Agent.text` + `seq`; parser applies: seq-dedup watermark, quotation-guard
   (ignore backticked/fenced), recognized forms only, `m` clamp `[1,120]`, highest-seq wins.
   Test against **fixture JSON** (no live endpoint). → `REQ-REBOUND-CODE-DIGEST`, `-PAUSE`,
   `-DELAY`, `REQ-HAZARD-REBOUND-CODE-FALSE-TRIGGER`, `-CODE-REPLAY`.
6. **Capability drain** — parse `op="pause"` / `op="delay"` frames off `api poll --link` (the
   notify parser, retargeted), dispatch into the state machine. Same two operations as the codes.
7. **Education** — `spt send --active-only <owner>` on first online + on each new digest
   `Boundary` (kind clear|compact|boot) seen while polling. → `REQ-REBOUND-EDUCATION`.
8. **Hazard tests** — ensure all three `REQ-HAZARD-*` have a tagged `unit` test (some land with
   steps 4/5 above; confirm coverage).

## Requirement activation (this phase)

Activate to `["doc","impl","unit"]` and tag evidence in the same commit:
`REQ-REBOUND-{LATCH,TIMER,PING,BACKOFF,PAUSE,DELAY,CODE-DIGEST,EDUCATION}` + the three
`REQ-HAZARD-*`. Leave `REQ-REBOUND-ACTIVITY-SEAM` at `["doc"]` (ADR is its doc evidence) — its
`impl`/`int` wait for the real seam. Add `"src"` and `"tests"` to `[scan].roots`.

## Gate

`cargo build` + `cargo test` green **and** `traceable-reqs check` green (activated reqs covered).
The live idle-edge→ping E2E is explicitly deferred to the seam (note it in the commit, don't fake
it).

## Reference

- `../spt-shell-notify` — the shell adapter to model (skeleton, `spt_cmd`, EVENT decode, tests).
- spt shell docs: http://localhost:5474 (`/llms-full.txt` is the one-file dump).
- `REBOUND-PLAN.md` / `CONTEXT.md` / `docs/adr/0001` — design + vocabulary + the dependency.
