# ADR-0042: RC attach truth — honest-session authority, resume UNBOUND, truthful refusals, canonical wire target

- Status: accepted (doyle triage 2026-07-18, RC-RENDER-TRUTH milestone, W1)
- Source: hertz perri offline/running-contradiction RCA (2026-07-17/18, doyle seam-verified
  same day; operator field confirmation of the wedged-TUI recovery) + hertz elevated-endpoints
  RCA core legs (2026-07-16, doyle seam-verified: harness-only refusal copy, qualified-target
  leakage). KNOWN-HAZARDS 7.46. Kin: ADR-0041 (endpoint lifecycle truth — this ADR extends its
  single-liveness-authority ruling to the `spt rc` reader), ADR-0027 (unbound endpoint state).

## Context

Four proven defects share one root shape: **the `spt rc` surface answers from stale
persisted projections and imprecise refusal copy instead of the live authorities that
already exist.**

1. **Authority split (perri contradiction).** The broker hosted an honest live session for
   perri (client tree alive, `SessionProbe::has_live_session_honest` = true) while the
   persisted perch row said `offline` (the resumed harness never bound). `spt rc perri`
   trusted the offline row and refused pre-IPC ("offline — nothing to attach to",
   `rc.rs` offline fast-fail); `endpoint run --resume` consulted broker truth first
   (ADR-0041 authority) and reattached to the same session. Decisive A/B repro. The
   fast-fail's own doc-comment invariant — "`offline` is stamped only on session DEATH, so
   it never co-occurs with a live session" — is falsified in the field.
2. **Resume pre-bind lies offline (second root seam, operator-spotted).** The W2
   skeleton-write short-circuits on an existing perch, so a RESUMING endpoint gets no
   UNBOUND stamp: broker session up, harness not yet bound, row still `offline`. The
   lifecycle meaning of UNBOUND is "session exists + harness has not bound" — fresh vs
   resume is irrelevant. The prior "accepted boundary" note reasoned only about a
   transient pre-bind window; the field showed the window can be PERMANENT (stuck native
   resume → SessionStart never fires → never binds), and the truthful UNBOUND stamp is
   exactly what let the operator `spt rc` in and see the wedged TUI to recover it.
3. **Harness-only refusal lies.** Direct `spt rc <id>` never preflights the already-known
   `harness_only`/`controllable=Some(false)` fact (local and gossiped rows both carry it);
   it dials, gets an empty refused attach, and maps every no-output refusal to stale-row
   copy ("it may have ended, or the registry row is stale"). Reproduced deterministically
   on an `ONLINE - HARNESS ONLY` endpoint.
4. **Qualified-target leakage.** `establish_attach` resolves and dials via
   `Address::parse` (`[subnet:]id[@node]`) but passes the ORIGINAL qualified string as the
   wire `AttachRequest.endpoint_id`; the target compares verbatim against the bare
   `HostedSession.endpoint` → `spt rc id@node` / `subnet:id` dials the right node then
   gets a false no-live-session refusal.

## Decisions

<!-- [doc->REQ-RC-HONEST-SESSION-AUTHORITY] -->
1. **`spt rc` consults the honest-session authority before the offline fast-fail.**
   Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest`
   gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session
   exists → attach via the session-confirmed path regardless of persisted status. No
   honest session → the existing offline refusal stands. A claimed session with a dead
   client tree → refusal/reap, never attach. Reuse `SessionProbe`; no new liveness
   heuristic.
   <!-- [doc->REQ-RESUME-UNBOUND-STAMP] -->
2. **Resume stamps UNBOUND.** A resume launch transitions an existing `offline` perch to
   `UNBOUND` before/with the broker spawn, with generation/session-safe rollback to
   `offline` on spawn failure or session death. Bind owns `UNBOUND → ONLINE`. UNBOUND
   semantics are fresh/resume-invariant: broker session exists + harness not bound =
   UNBOUND. This supersedes the "resume gets no UNBOUND stamp — accepted" boundary note.
   Both decisions 1 and 2 land: decision 1 is reader-truth, decision 2 is writer-truth;
   neither substitutes for the other.
   <!-- [doc->REQ-RC-HARNESS-ONLY-REFUSAL] -->
3. **Harness-only refusal is truthful and pre-stream.** `spt rc` preflights the known
   `harness_only` fact and refuses BEFORE attach with copy that names the actual state
   ("online but harness-hosted; spt does not own its terminal") — never the stale-row
   guess. RC support for harness-hosted endpoints is not claimed until an elevated
   PTY-host satellite exists (standing ruling).
   <!-- [doc->REQ-RC-QUALIFIED-TARGET-CANONICAL] -->
4. **The wire carries the canonical bare id.** The resolver's canonical bare endpoint id
   is carried separately from the user-facing qualified target; `AttachRequest.endpoint_id`
   is always the bare id. Bare, `id@node`, and `subnet:id` forms all attach.

## Consequences

- The rc reader can no longer be stranded by a stale projection while the broker holds an
  honest session — the operator-recovery path (attach and SEE the wedge) always exists.
- A permanently-offline row over a live session becomes impossible to mint via resume;
  transient UNBOUND is truthful the whole pre-bind window.
- Refusal copy states facts the node already knows; "stale row" guesses are reserved for
  genuinely unknown states.
- N-1 wire discipline: the bare-id carry is field-additive on the existing request shape;
  older brokers see the same bare ids current bare-form callers already send.
