# F-035 CONFLICT — REQ-vs-REQ, escalation for re-ruling

> ## ⚠ REVERSED 2026-07-27 — the collapse this document argued AGAINST is now SHIPPED
>
> <!-- [doc->REQ-HAZARD-DELIVERY-STARVATION] -->
>
> **Read this banner before treating anything below as current.** The 2026-07-09 ruling
> recorded here — that the idle edge SHOULD carry `active_only` for the relay-less
> spt-hosted class, and that the active/idle asymmetry was *intentional* — was **reversed
> by operator ruling in the FIELD-TRUTH W1 roll-in.** The idle-edge and parked-re-offer
> claims now exclude `deferred = 1` **unconditionally**, on every endpoint class. That is
> precisely the "collapse" this document was written to block.
>
> **What changed was the evidence, not the reasoning.** In 2026-07-09 the collision was
> REQ-vs-REQ on paper, and the shipped green gate was the tiebreaker. In 2026-07-27 doyle's
> spool take-audit on a LIVE perch supplied the missing field fact: every boundary-spooled
> spt-shells shell-context row (`window=active_only`, `deferred=1`) carried
> `taken_leg=idle-inject` ~200ms after spooling — each injection **starting a turn on an
> idle agent**. The rescue this document protected WAS the operator-visible defect.
>
> Consequences, all live:
> - The keystone assertion quoted below (`pending_after == 0`) is the **revoked behaviour**.
>   `spool_while_active_then_idle_fires_injection` was rewritten to its inverse: the deferred
>   row stays pending across the idle edge, while a non-deferred row in the same test proves
>   the inject leg still fires.
> - `REQ-MSG-IDLE-EDGE-DRAIN` narrows to non-deferred; `REQ-HAZARD-DELIVERY-STARVATION`'s
>   guarantee is scoped to `default` + `idle_only` (KNOWN-HAZARDS 7.23 SCOPE);
>   `REQ-SEND-WINDOW-DRAIN-HONOR` gains guard 4. ADR-0028 carries the matching amendment.
> - The two REQs are **no longer incompatible** — the incompatibility was resolved by
>   revoking one side, not by reconciling them.
>
> Kept unedited below as the reasoning record: it is the reason the reversal needed field
> evidence rather than an argument, and it names every carrier that had to be checked.

**Raised by:** todlando, mid-build, 2026-07-09. **Build HELD** (WIP uncommitted on
`f-035-window-drain-honor`). doyle's COLLAPSE ruling collides with a **shipped**
acceptance gate my RCA (docs/F-035-RCA.md) missed. Need an A/B/C ruling before I proceed.

---

## The contradiction

My F-035 fix makes `claim_idle_edge_inner` exclude `active_only` on the window axis
(unconditional). But a **shipped, green** integration gate asserts the OPPOSITE:

**`crates/spt/tests/idle_edge_drain_e2e.rs:89`** — `fn spool_while_active_then_idle_fires_injection`
`// [int->REQ-MSG-IDLE-EDGE-DRAIN] [int->REQ-HAZARD-DELIVERY-STARVATION]`

- **Setup:** spools `spool_message_deferred_at(perch, "peer", …)` — which is
  `insert_message(…, WINDOW_ACTIVE_ONLY, …)` (spool.rs:363). So the row is
  **`active_only`**.
- **Trigger:** `spt api state idle <id>` — the real ACTIVE→IDLE edge.
- **Assertion (the keystone):** `assert_eq!(pending_after, 0, …)` — the `active_only`
  row **MUST be delivered** on the idle edge (spool cleared).

My fix → the `active_only` row is excluded from the idle claim → stays spooled →
`pending_after == 1` → **this shipped gate goes RED.**

`REQ-MSG-IDLE-EDGE-DRAIN` (F-023 leg 2) and `REQ-SEND-WINDOW-DRAIN-HONOR` (F-035) are
directly incompatible for the relay-less spt-hosted class. The active/idle asymmetry I
called a bug in the RCA is **intentional** for that class.

---

## Why F-023 did it — the decisive carrier analysis

For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the
idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:

| carrier | `active_only` to spt-hosted? |
|---|---|
| Send-time inject (`cli.rs:5716` `try_inject = prefer_native \|\| !active_only`) | **NO** — cli.rs:5713 verbatim: *"a non-native `--active-only` is the explicit hook channel: **spool-only, no inject attempt**."* |
| Send-time idle re-offer (`cli.rs:5734`) | **NO** — gated `&& !active_only`. |
| Hook poll `drain_active_window` (`api poll`) | **N/A** — that is the HARNESS-hosted hook channel; an spt-hosted endpoint has no `api poll` harness. |
| **Idle-edge / parked drain `drain_idle_spool`** | **YES** — the F-023 leg-2 behavior (`include_deferred = !resting`). **This is the only carrier.** |

So: `active_only` is designed as *"the active **hook** window"* — meant for
**harness-hosted** endpoints that `api poll`. An **spt-hosted** endpoint has no hook, so
F-023 leg 2 rescued its `active_only` rows on the idle edge — otherwise they starve with
zero carriers. **My F-035 fix removes that only carrier → permanent starvation for
`active_only`→spt-hosted.**

---

## The design question (your ruling)

What is correct for an `active_only` row destined to a relay-less **spt-hosted** endpoint,
which has no hook-poll carrier?

- **(A) never-wake wins** — F-035 fix stands; F-023's starvation guarantee for
  `active_only`+spt-hosted is knowingly revoked (that class starves, or gets an
  evaporate/refuse-at-send policy instead). Requires re-scoping `REQ-MSG-IDLE-EDGE-DRAIN`
  + rewriting its int.
- **(B) never-starve wins** — the idle edge *should* carry `active_only` to spt-hosted
  (F-023 correct). Then **F-035 is mis-scoped**: *"never wakes"* is the **harness** hook
  model (harness endpoints DO hook-poll, so they never need the idle edge for it); it was
  never a contract for the hook-less spt-hosted class. **RETIRE or NARROW
  REQ-SEND-WINDOW-DRAIN-HONOR; no code change.**
- **(C) class split** — spt-hosted delivers `active_only` at idle **without waking**
  (non-interrupting background inject) vs harness-hosted holds-for-poll. New behavior,
  larger; needs a "deliver-but-don't-wake" inject mode the translation binary honors.

**todlando lean:** the carrier table points at **(B)** — the field-doc *"never wakes an
idle target"* (cli.rs:85) describes the harness hook model, and F-023 already reasoned
through the spt-hosted no-hook gap deliberately. The original F-035 *symptom* (a
`--deferred` background row reaching an idle spt-hosted agent) may be **working as
designed** — it is the only way that class can ever be delivered. If the true concern is
*"it interrupts,"* that is **(C)**, not the drain-exclusion of **(A)**. But this is your call.

My WIP (spool.rs window-axis collapse + inject.rs + 2 units) is staged **uncommitted** — I
touched neither delivery.rs nor worker.rs. Standing by for A / B / C.
