# M4-D2f — multi-subnet ceremony inputs + seed transfer on join (JIT plan)

**Status:** not started. D2-wire (ceremony driver over `SPT_PAIR_ALPN`) shipped & CI-green
at `08c6c9b`; REQ-PAIR-1 `[impl,unit]`. D2f closes the two pieces D2-wire deferred and
activates the multi-subnet pairing requirements.

## Goal

A successful pairing makes the joiner a **full subnet member** (it holds the seed, per
CONTEXT "every trusted node holds the seed"), and the relay-rendezvous token derivation
exists as a pure, tested function ready for the D9 relay wiring. Acceptance: a node that
ran `create_subnet("home")` pairs a second node via one code; the joiner ends with the
**same** named subnet record (seed + epoch) in its `SubnetStore`, not just a trust pin.

## Pieces (build order)

1. **`pairing/rendezvous.rs`** — pure rendezvous-token derivation (**REQ-PAIR-5**).
   `rendezvous_token(subnet_name, totp_step) -> [u8; 32]` = `SHA-256(domain ‖ lp(name) ‖
   step_be)` (length-prefixed name, domain-separated, versioned). Plus
   `rendezvous_window(name, step) -> [[u8;32]; 3]` for the ±1 skew (the joiner subscribes to
   all three; the responder advertises its own). This is the `H(subnet-name ‖ TOTP-epoch)`
   of ADR-0006 / CONTEXT:481 — **`TOTP-epoch` = the clock-derived time-step** (the D2-wire
   Q2 resolution), NOT the seed-rotation epoch. The relay *consumer* of this token is D9
   (Q1 deferral stands); D2f delivers the derivation + its tests.

2. **Wire seed transfer (closes D2-wire Q3).** Add an `F7 Seed{ seed, epoch }` frame the
   responder sends **after** `Done{ok}` (over the iroh-authenticated, TLS-encrypted stream,
   to the now-trust-pinned peer), and the joiner writes via `SubnetStore::add_joined`.
   - `run_responder` already holds the seed (`Admitted.seed`); on confirm-success send it.
   - `run_initiator` gains `subnets: &mut SubnetStore`; after `Done{ok}` + `trust.record`,
     read `Seed` and `add_joined(name, seed, epoch)` — **ignore `Exists`** (idempotent
     re-pair: an existing member just keeps its record).
   - Seed travels only on success (never on `Done{false}` / abort). No extra crypto layer:
     the QUIC stream is already E2E-encrypted + authenticated to the pinned node key, and
     the peer just proved code-knowledge via the PAKE.

3. **Create-new-names-up-front (REQ-PAIR-4).** `SubnetStore::create_subnet(name)` already
   mints the seed at epoch 1 and names the subnet at creation (the sole-seed-holder
   "create-new" path; ADR-0006 "naming moves to link-start"). Tag it `[impl->REQ-PAIR-4]`
   and add the first-pairing naming evidence: a wire test where the responder `create_subnet`s
   and the joiner learns the **named** subnet through piece 2.

4. **Activate REQ-PAIR-4, REQ-PAIR-5** in `traceable-reqs.toml` (`[impl,unit]`; `int` at D9
   with the relay rendezvous + two-host run).

## Tests
- `rendezvous`: token is deterministic, domain-separated, changes with name AND step;
  length-prefix prevents `("ab",1)` vs `("a",…)` boundary collisions; window = 3 adjacent.
- wire `joiner_becomes_seed_holder`: happy path → joiner's `SubnetStore` has the subnet
  (seed + epoch match the responder's record).
- wire `repair_member_keeps_seed`: a joiner that already holds the subnet re-pairs cleanly
  (`AlreadyTrusted`, `add_joined` `Exists` ignored, record unchanged).
- wire `failed_pairing_transfers_no_seed`: wrong code → joiner's `SubnetStore` stays empty.
- store: `create_subnet` names + seeds at epoch 1 (REQ-PAIR-4 unit).

## NOT in D2f (carried)
- Relay rendezvous *routing* (the token's consumer) → D9 (Q1).
- `spt pair` CLI orchestration / naming **prompt** → needs the daemon/net wiring (D4+); the
  library mechanism (create_subnet + named ceremony + seed transfer) is what D2f delivers.
- Elevation-gated code fetch (`spt pair show-totp`) → D2g (REQ-PAIR-6, REQ-PAIR-3).

## Conventions (carried)
- NO `cargo fmt`. Tag `[impl->REQ-PAIR-{4,5}]` on real evidence, `[unit->…]` on tests.
- Loopback tests mirror `wire.rs` (RelayPolicy::Disabled, injected `now`).
- Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.
