# STUB-INJECT — stub-based idle delivery + digest parity

Operator sprint 2026-07-10; design RESOLVED in the grill session same date — decisions live in
ADR-0007 (stub-based idle delivery) and CONTEXT.md (message stub / stub turn / body park /
full-inject override). This plan is now execution only.

## Resolved shape (summary — ADR-0007 is authoritative)

- Idle delivery types a **message stub** (`<msg from="<id>[@<node>]" id="<short-id>"/>` /
  `<wake/>`), body **parks adapter-side**; the **stub turn** (whole-prompt-exact match, shared
  `is_stub()` between hook and digest) injects ALL parked bodies as additionalContext; trailing
  empty-park stub turns get a one-line coalesced note.
- Wake: arm/fire loopback unchanged; fired sequence = rename-half + `<wake/>` stub; parked-wake
  file presence decides stub-vs-silent (plain /clear stays silent).
- Mid-turn PreToolUse drains + internal control envelopes: untouched. **Full-inject override**:
  undocumented json-payload key → legacy full typing.
- End-state (core short-ids): stub `id` fetched via `spt endpoint msg <short-id>`; checkpoint
  clear becomes `/clear <short-id>` (arg rides into the new transcript's first entry — the id
  crosses the boundary in-band); park dissolves.
- Digest parity (REQUIRED, ships before/with stubs): extractor surfaces our `<EVENT>`/
  `<sptc_messages>` frames from hook_success attachment entries (both shapes: raw
  `attachment.content` + the `attachment.stdout` hookSpecificOutput envelope; hookName-filtered
  UPS/PreToolUse), collapses stub-input + forward-adjacent attachment frames into one input
  record (one-lookahead buffer; no-frames ⇒ flush stub as-is), and in the same pass filters
  transcript noise (`<command-name>` triple → bare command line; `<local-command-caveat>` +
  boundary `<system-reminder>` spans stripped). Also surfaces mid-turn PreToolUse deliveries —
  digest-invisible today.

## Core asks (file to doyle, one wave)

1. Per-message **short id** minted on send, exposed on receipts + poll frames.
2. **`spt endpoint msg <short-id>`** fetch verb (UPS stub lookup).

## Slices

- **A — digest parity + noise filter** (no core dep, no protocol dep; lands the required-anyway
  visibility layer first): attachment branch + span filter + stub collapse (inert until stubs
  exist) + noise filter. REQ-DIGEST-NOISE-FILTER + REQ-DIGEST-RELAY-SURFACE. Pure extract() unit
  tests over byte streams.
- **B — wake stub**: park wake at checkpoint detect (PostToolUse); translate fire-half types
  rename + `<wake/>`; UPS `<wake/>` handler reads+clears park → additionalContext.
  REQ-STUB-WAKE. Field-verify a full checkpoint cycle on a live endpoint before cut.
- **C — msg stubs** *(BUILT 2026-07-10, pending field verify)*: translate parks peer bodies +
  types `<msg from/>` (id attr once core mints it; from verbatim off the envelope, `@node` only
  cross-machine); UPS `<msg…/>` handler drain-all + coalesced note; full-inject override key
  (`{"full_inject":"v1"}`) in translate. REQ-STUB-MSG-DELIVERY + REQ-STUB-FULL-INJECT-OVERRIDE.
  Custody RESOLVED: msg park is HOME-anchored (`~/.spt-claude/msgpark/<eid>/`, one
  millis+seq-named file per body, temp+rename) — writer is the resident translate, reader the
  fresh hook, so exe-relative would skew silently across an install-dir migration/own-copy;
  empirically today translate runs from the install dir ({adapter_dir} command, verified live),
  so the anchor buys the failure class, not an instance. See ADR-0007 custody bullet.
- **D — short-id adoption** (post-core-wave): stub id attr live, `spt endpoint msg` fetch,
  `/clear <short-id>`, park retired.

Skew: safe by construction (hook always fresh from disk; a stale RUNNING translate keeps
full-typing, which the new hook reads as a normal prompt). No version gate needed.

## Gate

Per slice: build + unit + traceable-reqs + gates green; B and C additionally field-verified live
(this pipeline's failure mode is silent delivery loss). Digest parity (A) must be released
BEFORE or WITH C — stubs without the collapse regress the operator's RC view.
