---
name: rt-a4b-tracing-retry
description: REQ-OPID-TRACING-RETRY (A-4b) — with_tracing_retry helper + frozen marker builder-fns, 3 wrapped sites all re-dial-in-run; 5 effect:: unit tests
metadata:
  type: project
---

REMOTE-TRUTH A-4b (sibling of [[rt-a4a-minter-namespace-blocker]]): the rest
family + rc attach stream-open auto-retry ONCE with a fresh same-minter op on the
broker "no longer held" reply. sync/update NOT wrapped (durable dedup load-bearing).

**Why:** a broker restart drops the conn/stream after the op journaled → typed
"…op already applied…no longer held (broker restarted); retry with a fresh op_id"
reached the user (WOKE_FAIL / rc attach fail). ADR-0034 Decision 2 + Addendum 2.

**How it landed (spt-daemon/src/effect.rs is the home):**
- `pub const OP_NO_LONGER_HELD_MARKER = "no longer held"` — FROZEN cross-IPC compat
  substring. Broker's two messages are built by `net_dial_no_longer_held_message()` /
  `stream_open_no_longer_held_message()` (pub(crate) builder-fns in effect.rs) that
  broker.rs calls via `ok_or_else(crate::effect::…)`. Builder-fn seam (not inline
  format!) is the single source a unit test can pin byte-identical to the old literal.
- `with_tracing_retry(mint: FnMut()->io::Result<MintedOp>, run: FnMut(MintedOp)->io::Result<T>)`
  — mint()? → run(op); on `is_no_longer_held` re-mint + run once; 2nd marker →
  `RETRY_EXHAUSTED_PUBLIC` (F-1, private const: "the connection to the target was
  reset during the request; try again" — passes the op/journal/broker/stream/"retry
  with a fresh" lingo grep). Other errors propagate verbatim. Re-exported from lib.rs.

**LANDMINE — all 3 sites re-dial INSIDE the run closure** (a broker restart drops the
CONN too, not just the stream — retrying a stream-open against the dead conn surfaces
a different error, never self-heals):
- wansend `wan_rest_with` (spt/src/wansend.rs): signature changed `open_op: MintedOp`
  → `mint: impl FnMut`; round_trip already re-dialed (`net_dial(addr,None)`), now
  `addr.clone()` per attempt. `wan_rest` builds a `move` mint closure yielding the
  pre-minted first_epoch once then `ops.next_epoch()?`. 4 test callers updated
  `MintedOp::new(..)` → `|| Ok(MintedOp::new(..))`.
- forward_wake (spt-daemon/src/shellwake.rs): moved `net_dial` INTO run; op_id tracing
  string rebuilt from `op.seq` each attempt.
- rc attach (spt/src/rc.rs run_attach_inner): `established: Option<u64>` take()-then-
  redial — first attempt uses the conn set up above (happy path byte-identical), retry
  re-dials (loopback singleton for local / resolve_and_dial_owner for remote). Preserves
  remote_node side-effect + honest-failure early-returns outside the retry.

**Tests (effect::tests, all GREEN, RED-first = didn't compile pre-helper):**
retries_once_on_no_longer_held (2 distinct seqs SAME minter), terminal_is_public_language
(== RETRY_EXHAUSTED_PUBLIC + lingo grep), no_retry_on_success, passes_through_other_errors
(NEGATIVE CONTROL + match-discrimination, doyle bind 2), marker_is_single_source_and_frozen
(both builder msgs byte-identical to pre-const literal + contains marker).

**Gates:** `cargo test -p spt-daemon --lib effect::` (17 ok, 5 new) + broker:: (22 ok);
`cargo test -p spt --bin spt wansend::`(8 ok, incl remote_rest e2e) + rc::(25 ok); clippy
-p spt-daemon -p spt --all-targets clean; traceable-reqs check exit 0 (REQ-OPID-TRACING-
RETRY +doc +impl +unit). Left dirty for todlando ground-truth review + commit.
