# ── v0.15.0: activity-gated delivery + per-message send-modifier axes (ADR-0028) ── [[requirements]] id = "REQ-MSG-DELIVERY-AXES" title = "Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally — its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active → spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition → deliver immediately: translation binary spt-hosted → relay-poll either topology → spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW — default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` — `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION — unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-method). Native flags do NOT respect the binary's idle-gating: the WINDOW says when, the native flag says through-what (so `--force-native --active-only` = binary injects during the active window, mid-turn-safe via the existing InjectFloor). (3) PERSISTENCE — durable (default; spool until delivered or TTL) | `--ephemeral` (drop if undeliverable in the accepted window — at window-open with no live carrier, or at TTL, whichever first). METADATA (orthogonal): `--json-payload ''` → a single attr-escaped `json=\"…\"` envelope attr ALONGSIDE (not replacing) the body, pure verbatim passthrough across spool/TCP/WAN/EVENT-PART, parsed only by the receiving adapter; collision-proof by construction (structured data lives INSIDE the one `json` value, can never forge `from`/`type`); available to ANY sender (confers no spt-core authority). HAZARD: `--ephemeral` is the ONLY path permitted to drop silently — the sender-opted-in carve-out to REQ-HAZARD-IDLE-SILENT-NONDELIVERY (that hazard gains a '…unless --ephemeral' clause in v0.15.0). (v0.15.0)" required_stages = ["doc"] # SEED→DOC: ADR-0028 + CONTEXT.md (activity-gated delivery / message delivery axes / message metadata `json`) landed at the design grill (2026-06-23). impl/unit/int activate when the v0.15.0 build starts (wire the discarded resolve_inject_methods into dispatch_endpoint_input + the active/idle two-window router; add the window/channel/persistence flags to `spt send` w/ mutual-exclusion per axis; render `json` attr; rename --deferred→--active-only + grep tests for the old flag; amend REQ-HAZARD-IDLE-SILENT-NONDELIVERY w/ the --ephemeral carve-out).