feat(send): delivery-window + persistence axes — CLI flags + spool schema (v0.15.0 W2) ADR-0028 W2 (doyle-ratified drain-surface split + deferred-mirror). Expose the delivery-WINDOW axis on `spt send` and tag it onto the spool so the right drain surface picks each row up. The broker / EndpointInputReq is untouched — the window is a caller-side spool tag, orthogonal to the send-time `.idle` inject-vs-spool gate (Q3: broker-inject-at-send-time stays the sole idle gate). CLI (`spt send`): - `--idle-only` / `--active-only` — mutually exclusive delivery-window group. `--active-only` RENAMES `--deferred` (pure rename; the internal `deferred=1` column + `api poll --include-deferred` keep their names). default = both windows. - `--ephemeral` — persistence axis; the flag + column land now (stored), the evaporation trigger wires in W3 alongside the channel axis (force-native + ephemeral is the concrete no-carrier-at-window case). - cmd_send maps the flag → window and spools via deliver::send_windowed; the broker's active/idle hint is superseded by the explicit window. Spool schema (additive, mirrors how `deferred` was added): - New columns `"window"` (default|idle_only|active_only), `channel` (any; prefer/force_native wire in W3), `ephemeral`. `"window"` is double-quoted in all SQL (SQLite keyword). - `deferred` is RETAINED as a back-compat MIRROR — derived from `window` at the SINGLE insert chokepoint (deferred=1 IFF window='active_only'), so the ~40 deferred-keyed reads stay correct at zero churn. A unit test pins the invariant across all three window values. Drain-surface split (Q1 — only the ACTIVE-hook side re-points): - New `drain_active_window_at(perch, include_deferred)`: include_deferred ? window != 'idle_only' ({default,active_only}) : window = 'default'. Re-points poll_drain (api poll) + cmd_worker_poll — both now SUPPRESS idle_only (it reaches the receiver only via the idle/wake relay). The resting-gate narrowing (REQ-INST-6) flows through unchanged. - The idle/wake side is UNCHANGED: deferred=0 already == {default, idle_only} == the relay/idle-drain target set, so the relay (ready.rs) + the idle-transition drain need no edit. No second drain fn. deliver::send_windowed: active_only = spool-only (no live wake); default/idle_only try a live TCP handoff first (the relay IS the idle wake), then spool tagged with the window. send / send_deferred are untouched (other callers). Tests: spool deferred-mirror invariant (3 windows) + active-window idle_only- suppression/include_deferred; delivery poll_drain suppresses idle_only while it reaches the relay; CLI parse for --active-only/--idle-only/--ephemeral + mutually-exclusive group. Full nextest -p spt-daemon (483) + -p spt (292) green; clippy --workspace 0; traceable EXIT0; xtask gen+check OK (reference.md regen for the new flags, no internal codes). [impl->REQ-MSG-DELIVERY-AXES] Co-authored by: todlando Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_015e2dT1NAirnsahCJs4BX3Y