# M5 — PACER 0.5.0: the one live board, wait checkpoints, extend retired

> JIT plan for request [#11](https://github.com/BigscreenVR/spt-pacer-tool/issues/11)
> ("stretches auto-extend"). Version 0.5.0; `min_spt_core_version` stays `0.44.0` —
> nothing here needs newer core. Rulings grilled and answered by the operator
> 2026-07-27 (posted verbatim to #11).

## Scope

1. **One live board.** Exactly one stretch (board) exists at a time, born at a
   board-clear (classification) or shell-link (resident bind) event — that event marks
   **checkpoint-0** — and it keeps extending until classified. The old close/reopen/
   bridge machinery dissolves: a stretch is the interval between classifications.
2. **`wait` mints checkpoints.** Each consumed wait (verb or `!!wait=m!!`, identical)
   mints the next sub-id checkpoint on the live board, besides deferring the next ping
   as ever. No separate extend step.
3. **Board payloads.** Each idle→busy resume ending a ≥60s idle segment sends the board:
   ONE stretch id + its checkpoints, each with its wall-clock delta from this resume
   (spans overlap — `:0` contains `:1` contains `:2`…). 10 checkpoints max displayed,
   oldest rolls off.
4. **Classify takes a sub-id designator.** `!!classify=x7au34-2:<name>!!` /
   `classify x7au34-2 <name>` folds exactly the delta the board showed for `:2`
   (checkpoint→latest-resume wall-clock) into the class's running mean, then clears the
   board — one classification consumes it; remaining sub-ids die.
5. **The extend surface is retired** (verb + `!!extend=m!!` code) — breaking, flagged.
   The verb answers a pointer-refusal; the code goes inert.

## Rulings (operator, 2026-07-27)

- **(1)** Board numbers = wall-clock from each checkpoint's creation to the latest
  resume; classifying `id-N` folds exactly the number shown for `:N`. (First reading
  confirmed; not non-overlapping segments.)
- **(2)** One classification consumes the whole board. Confirmed.
- **(3)** Retire both the verb and the code. Confirmed.
- **(4)** No pending window of boards — **only 1 board at a time**, extending until
  classified. Each board-clear or shell-link event marks checkpoint-0 of the new
  stretch. 10 checkpoints max displayed in the payload, oldest rolls off.

## Ruled-by-me details (posted to #11 with the record)

- Checkpoints number from **0** (`:0` = the board's birth); waits mint `:1, :2, …`.
  A never-waited stretch classifies as `<id>-0` — the designator is always required.
- Storage = display: the board keeps the last 10 checkpoints; a rolled-off sub-id is
  dead (classify of it: refusal on the verb, inert on the code). `:0` rolls off like
  any other.
- The board is **in-memory** — a shell-link event starts a fresh board by ruling, so a
  restart resets it by design. Durable state keeps only the registry + the watermark
  rider; the 0.4.0 `pending` field is dropped and ignored on load (no migration).
- The stretch id mints at board birth. The fold value is measured to the **latest
  resume** (the instant the last payload reported); classify before any resume has
  reported is refused (`no reported checkpoint yet`).
- Checkpoint instant = the wait's consumption instant (verb arrival / code ingest), for
  held pre-idle waits too. Several waits in one entry: steering stays latest-wins — one
  consumed wait, one checkpoint.
- A wait always mints (idle or busy, held or immediate) — there is always a live board.
- Pacing (latch, timer, backoff, ping, pause, delay deferral) is untouched.
- Report floor unchanged: payloads fire only on a resume ending a ≥60s idle segment.
- #11 is driven directly — no milestone wrapper (none exists; operator did not ask
  for one).

## Tasks

1. `traceable-reqs.toml` + `CONTEXT.md` [doc]:
   - Retitle `REQ-PACER-DELAY` (wait = delay + checkpoint mint on the live board).
   - Retitle `REQ-PACER-EXTEND` to the retirement contract (verb pointer-refusal, code
     inert, capability gone) — stages doc/impl/unit; evidence = refusal + dead-parse tests.
   - Add `REQ-PACER-BOARD` (one live board; birth at clear/link = checkpoint-0; 10-cap
     roll-off; in-memory by ruling).
   - Retitle `REQ-PACER-CLASSIFY` (sub-id designator; folds the shown delta; consumes
     the board).
   - Retitle `REQ-PACER-STRETCH-REPORT` (board payload: one id + checkpoint deltas from
     this resume).
   - Glossary: **board**, **checkpoint**, **sub-id**; rewrite **extend** (retired),
     **stretch id**, **classify**, **stretch report**, **delay**; delete **pending
     window** semantics (registry cap prose stays).
2. Machine (`machine.rs`): delete `stretch_start` / `bridge_until` / `last_closed_start`
   / `reopened` / `extend()` / `ExtendOutcome` / `Code::Extend` / `Ingested.extend_m`;
   `on_activity` Busy returns the ended idle segment (duration since idle edge) only;
   the Delay path reports the consumption instant so the caller mints a checkpoint
   (held waits included). Unit: rework extend-era tests to segment semantics; checkpoint
   signal on idle-wait / held-wait / busy-wait. [impl+unit]
3. Board (`machine.rs` or new `board.rs`): `Board { id, birth, checkpoints (sub, at),
   next_sub, last_resume }` — mint at birth (checkpoint-0), `mint_checkpoint(at)` with
   10-cap roll-off, `on_resume(at)`, `fold_value(sub)` = last_resume − checkpoint,
   `classify → clear + fresh board`. Unit: numbering from 0, roll-off kills, fold
   values, no-resume-yet refusal, clear births checkpoint-0. [impl+unit]
4. State (`state.rs`): delete `PendingStretch` / `pending` / `push_pending` /
   `upsert_pending`; `classify(name, secs)` folds directly (registry + cap unchanged);
   old files load clean with `pending` ignored. Unit: fold, load-ignores-pending,
   retire untouched. [impl+unit]
5. Frames + digest (`frame.rs`, `digest.rs`): classify parse takes the `id-N`
   designator on both surfaces (malformed: verb refusal, code inert); extend verb
   decodes to the pointer-refusal (`extend was retired in 0.5.0 — wait now extends the
   live stretch`); `scan_extends` deleted. Unit: designator forms, retired-verb
   refusal, extend code inert. [impl+unit]
6. Report (`report.rs`) + resident (`main.rs`): board payload rendering (issue format —
   header `id — resumed <stamp>`, `:N ~delta` lines newest-first, classify teaching with
   the designator form, registry listing); status suffix shows the live board;
   education + status text teach wait-checkpoints + designator, drop extend; resident
   owns the Board (fresh at bind), wires resume/classify/wait paths. [impl+unit]
7. Manifest: remove the `extend` capability; bump `manifest.toml` + `Cargo.toml` 0.5.0.
8. CHANGELOG `[0.5.0]`: one physical line per bullet; **Changed** (one live board,
   wait checkpoints, classify designator, board payloads) + **Removed** (extend verb +
   code — flagged breaking).
9. Gates: `cargo test`, `traceable-reqs check`, pack + schema-validate.
10. Release per `docs/RELEASE-RUNBOOK.md`: fresh 3 triples, tag `v0.5.0`, gh release,
    digest-verify; `spt adapter update PACER` + canary re-check (marker stays planted);
    restart instance; live E2E — a wait that checkpoints, a board payload, a sub-id
    classify that clears the board, a retired-extend refusal.
11. Issue states on #11 (driven directly): BACKLOG→greenlit (plan committed) → wip
    (build) → acceptance (release verified) → done (E2E); rulings posted before close.

## Gate

Build + `cargo test` + `traceable-reqs check` green; release digest-verified; canary
observed surviving the update; #11 at done.
