---
name: v0140-endpoint-creation-flow
description: "v0.14.0 endpoint-creation-flow milestone — scope, branch, waves, the binding v0.13.2 prerequisite"
metadata: 
  node_type: memory
  type: project
  originSessionId: 8e154a5b-232c-4650-94ef-5b0ee8abd07f
---

v0.14.0 = **endpoint-creation-flow** milestone (post-v0.13.2). Operator started todlando 2026-06-22 while deployah finished the v0.13.2 cut. doyle = orchestrator/gater (same as v0.13.2, see [[v0132-build-drive]]). Dispatch todlando via `"$OWL" send todlando doyle` (legacy-owl live agent).

**Branch:** `design/endpoint-creation-flow` (NOT a v0.14 name). Carries the full JIT (`V0.14.0-ENDPOINT-CREATION-FLOW-JIT.md`) + ADR-0026/0027 + 2 REQs. Was 2-ahead/21-behind main at start.

**Two REQs (doc-active at start; impl/unit/int activate per-wave, rule 5):**
- **REQ-RUN-MULTISUBNET-HOME** (ADR-0026): `spt endpoint run` resolves home subnet at the skeleton-create step + pre-creates skeleton perch carrying it, so harness `bind` inherits home via establish_perch's IMMUTABLE prior-branch (home.rs:122; no hook/env change). Resolution: sole→auto; multi+no-`--subnet`+non-interactive→refuse early w/ MRU `--subnet` guidance (NOT the silent 25s timeout); multi+interactive→proposed-config + Y/n; `--subnet` overrides+validates membership. Two-level MRU home lists (per-project + node-global fallback). Fixes perri's LATENT multi-subnet bringup gap (NOT a regression — HOME_REFUSED is established ≥0.11.0 no-guess; exposed crossing 1→2 subnets w/ BIGNET).
- **REQ-ENDPOINT-UNBOUND-ATTACH** (ADR-0027): new `STATUS_UNBOUND` (liveness.rs:37) — attachable (live broker session) but NOT message-addressable (no bound session_id). Gate attach on broker-SESSION-exists not perch-STATUS_ONLINE (replaces await_endpoint_online); `cmd_endpoint_run`+`spt rc` attach regardless of perch status (headless bringups, clear bind-prompts). EpDisplay gains `Unbound`=HOLLOW (+hollow-controlled); NOT amber (amber=HarnessOnly=not-controllable, the opposite).

**BINDING PREREQUISITE:** start from main AFTER v0.13.2 merged — builds on v0.13.2's broker `sessions` map (ADR-0025 W3a: per-endpoint endpoint/adapter/install_dir = "is there a live session" source of truth; W2/W4/W5 read it). v0.13.2 IS on main @2bb9c8f → satisfied. todlando rebases design branch onto main first.

**Waves (sequential — share broker-sessions-map + perch-status seam; doyle gates each, CI green BOTH runners per wave; contended Win host can't run lifecycle/daemon suite → CI is authority):**
W1 STATUS_UNBOUND primitive+lifecycle (foundational) → W2 home-resolve+skeleton-with-home (the multi-subnet UNBLOCK) → W3 two-level MRU lists → W4 attach-on-session-exists → W5 Unbound display (picker/list/whoami) → W6 int keystone + perri's multi-subnet regression.

**W3 GATE-PASS @a035512** (doyle verified: 6 store + 2 cli units, traceable+clippy+reference clean, CI green run 28005190141): recent_home upgraded single node-global value → two-level move-to-front MRU LISTs. `mru_preference(project_id)` = per-project list THEN node-global, deduped; `record_home` moves-to-front BOTH levels. Lists newline-sep + CAP=16; pre-W3 single-value file reads as 1-element (back-compat). `decide_run_home(mru: &[String])` + new `order_by_mru` orders the Ambiguous subnet list MRU-first (default=head) for BOTH the confirm AND the non-interactive `--subnet` guidance. cli wires `project_id_for_dir(project_cwd)`. No new CLI flag (reference.md no drift). Units: 6 store + 2 cli (matrix+ordering). clippy/xtask check/traceable all EXIT=0.

**W4 @21cdfba** (CI run 28009013867 watching, awaiting doyle gate). @9f726a9 CI-RED both runners (deterministic, NOT Win-flake) on dummy_harness_e2e `endpoint_run_attach_awaits_online_before_attaching` — green-units≠green-int AGAIN (I grep'd the token only in comments, MISSED the live e2e). ROOT: `await_endpoint_session` returns at session-exists (pre-bind), but a RESUMING perch stays OFFLINE in that window (W2 short-circuits existing perch → no UNBOUND re-stamp), so run_attach's (a) offline failfast bailed "offline—nothing to attach"; old await-ONLINE masked it by waiting past bind. FIX @21cdfba: extracted `run_attach_inner(.., session_confirmed)`; new `run_attach_session_confirmed` SKIPS the (a) offline failfast (caller proved a live session → stale offline must not block); standalone run_attach keeps it; (b)/(c) backstops still on chain. cmd_endpoint_run uses the confirmed variant. e2e GREEN locally (saw_tick=true, ran on this Win box 8s). LESSON: grep e2e/tests/ NOT just comments for behavior-change blast radius ([[behavior-change-grep-tests-not-comments]]). **W4 GATE-PASS @21cdfba** (doyle code-verified: split tight, (b)/(c) on chain both paths, no B1 regression, standalone failfast kept; he credited the CI-catch + said my session_confirmed split matches the REQ more literally than his ruling). Original W4 desc:
**attach-on-session-exists.** cmd_endpoint_run `await_endpoint_online` → `await_endpoint_session` (polls W3a broker sessions map via new `rc::SessionProbe` over brain.sessions() for endpoint==id; one broker connect reused across bounded poll). Combinator renamed `poll_online`→`poll_until_ready`; token `ENDPOINT_RUN_ONLINE_TIMEOUT`→`ENDPOINT_RUN_SESSION_TIMEOUT`. **rc.rs run_attach UNCHANGED** — already session-gated: UNBOUND (status≠offline) falls through failfast + attaches via resolve_session; only explicit-offline no-IPC short-circuit (REQ-HAZARD-RC-ATTACH-FAILFAST (a)) remains. doyle CODE-VERIFIED ruling: KEEP failfast (don't route through session query — (b) bounded first-event backstop already guards dead-not-offline). await hands off to run_attach (no bypass) so B1 (b)+(c) stay on chain. **KNOWN BOUNDARY (doyle, commented rc.rs):** RESUMING perch still OFFLINE pre-bind → standalone `spt rc` refuses "offline—start it" (W2 skeleton-write short-circuits existing perch → no UNBOUND stamp on resume); covered by `run --attach` (await_endpoint_session) + post-bind retry; accepted not contorted. Units: poll_until_ready fast/timeout/later (broker+clock-free). REQ already active [doc,impl,unit] since W1 — no toml change. **W6 must assert:** UNBOUND fresh-run skeleton → `spt rc` AND `run --attach` BOTH attach live pre-bind session → output flows → bind flips ONLINE. (doyle W6 note: the dummy_harness e2e ALREADY covers the RESUME-offline-attach face at int; W6 keystone ADDS the FRESH UNBOUND attach-before-bind face + multi-subnet regression — TWO faces.)

**W5 DONE @93a42fa** (CI run 28010292661 watching, awaiting doyle gate): Unbound display. EpDisplay += Unbound (green HOLLOW ▢) + UnboundControlled (blue HOLLOW ▢) — NOT amber (amber=HarnessOnly=not-controllable, opposite of attachable); FILL encodes bound/unbound, COLOUR the rest. EndpointRow.is_unbound (data.rs: local rows from info.json.status==STATUS_UNBOUND; subnet/remote=false→plain). display_status precedence: offline → unbound(+driven_by→UnboundControlled) → existing type-gate/amber. view square_span: hollow glyph for unbound variants (raw EpStatus reads online, so glyph from display_status not status.square()). roster::PerchEntry.unbound (is_perch_unbound) — KEY: unbound reads alive=FALSE (is_perch_alive bound-gated) so text list would look OFFLINE; render_local_section + whoami/list SELF pin now mark "UNBOUND". Units: display matrix + hollow render + local-section marker. doc=picker colour legend.

**W5 GATE HELD @93a42fa → FIXED @6b75cc7** (CI run 28011502497). doyle caught a REAL picker bug my unit MASKED (green-unit≠green-real, 3rd time this milestone): roster alive=is_perch_alive→FALSE for unbound (bound-gated) → data.rs status=if p.alive{Online}else{Offline} → live unbound got EpStatus::Offline → display_status TOP `if status==Offline` returned BEFORE the is_unbound branch → gray Offline, Unbound unreachable. My display unit HAND-SET EpStatus::Online (false premise vs real derivation) so stayed green; + asserted impossible (Offline+is_unbound)→Offline combo (B2 clears is_unbound on real offline). Text-list path was FINE (uses p.unbound directly). FIX (A, doyle-preferred): data.rs pure `row_status(alive,unbound)=Online when alive||unbound` — unbound reads Online at raw EpStatus (has live session). Ripple ALL correct + BETTER: resume_rows(gated ==Offline)→none for mid-bringup; confirm_options offers Attach not Start (Start=wrong re-bringup of live session); confirm_terminal→Attach{Control}; start_headless no-op. is_unbound single-sourced from p.unbound. view status_word="unbound". model.rs: removed impossible-combo assertion. **NEW real-derivation catch test** `data::row_status_drives_unbound_display` (drives alive=false,unbound=true→Online→display Unbound — the test that'd have caught it). doyle 3 REQUIREDs all met. **W6 keystone REQUIRED (doyle):** assert REAL picker render of live UNBOUND==hollow Unbound end-to-end via disk→data.rs→model.rs seam in a live bringup (the seam no unit reached).

**W6 GATE PASS @0760ec5 — MILESTONE BUILD COMPLETE (2026-06-23).** Both REQs now +doc+impl+unit+int; ADR-0026/0027 → accepted (evidence cited in the ADRs). New `crates/spt/tests/multi_subnet_bringup_e2e.rs` (dummy-harness, NO mocks) + `picker::data::gather_renders_live_unbound` + new mock-adapter `--mode hold-unbound` (sibling of `--mode dummy`, gates the bind behind `if !no_bind` → real live broker session that NEVER binds = race-free pre-bind hold). doyle design-ruled hold-unbound over todlando's --bind-delay-ms (never-bind = deterministic, no window to flake on the contended runner). 5 checklist items all NON-VACUOUS (doyle read the real asserts): (1+2) multi_subnet refuse exit≠0+MULTI_SUBNET_HOME+NO skeleton, then --subnet→ONLINE+no HOME_REFUSED+home_subnet STAYS "control"+sync_subnets==[control] single (the two W2 load-bearing premises proven E2E on a real ≥2-subnet node); (3) fresh_unbound rc attaches live pre-bind UNBOUND session, 4 asserts (is_unbound+rc_connected[PUMP_IPC_READER]+rc_saw_tick+STILL UNBOUND after attach); (4) gather_renders_live_unbound real seam→Unbound not Offline w/ alive=false precondition (doyle ran locally, green); control single_subnet auto-homes. CI run 28013034397 green both runners no-red. perri's latent multi-subnet gap CLOSED in code.

**v0.14.0 PUBLISHED ✅ 2026-06-23** (deployah, counter 30, Latest on spt-releases). Tag @6ba875e, signed rel-primary-2026, docs-publish green. CI attempt-2 green BOTH runners; attempt-1 RED = recurring Win netstream ring-redrive flake (sender/receiver_brain_restart exactly-once, same family W2 hit) cleared on rerun — NOT code. todlando ack to deployah undeliverable (NO_PERCH — deployah went offline post-cut, no retry needed). **NEXT (NOT todlando's leg): perri validates her exact multi-subnet repro on real claude-spt at THIS publish.** todlando executor leg DONE — standing by for perri findings. Counter ledger → v0.14.0=30 (update [[v0121-published]]).

**PRIOR RELEASE STATE (now satisfied):** PR#30 release-ready, v0.14.0 MINOR. Two gates: (1) OPERATOR GO (relayed); (2) doyle CHANGELOG-vet (PASSED 2026-06-23 09:17Z). Pending operator calls alongside: PR#31 (multi-platform .spt doc-gap fix, merge), F-018 (adapter-safety fast-follow). Milestone lesson: green-unit≠green-real bit 3× (W4 e2e-not-comments, W5 picker hand-set-premise) — each HOLD caught by reading the REAL pipeline + each fixed with a real-derivation test.

**W6 DONE @0760ec5 — MILESTONE BUILD COMPLETE** (CI run 28013034397 watching, awaiting doyle HARD-gate). The capstone, all 5 doyle checklist items:
- NEW `crates/spt/tests/multi_subnet_bringup_e2e.rs` (3 tests, real bringup, dummy fixture): `multi_subnet_refuses_without_subnet_then_homes_and_binds` (checklist 1+2: >=2 subnets → no-`--subnet` MULTI_SUBNET_HOME refuse + NO skeleton; `--subnet S` → bind INHERITS home=S STAYS + UNBOUND→ONLINE + no-double-seed sync_subnets==[S]); `single_subnet_control_auto_homes` (control); `fresh_unbound_is_attachable_before_bind` (checklist 3: `--mode hold-unbound` live-never-binds session → `spt rc` attaches pre-bind STATUS_UNBOUND, tick flows, STAYS unbound).
- NEW fixture: `adapters/mock/src/main.rs` `--mode hold-unbound` (sibling of dummy, minus bind; READY-before-conditional-bind; dummy unchanged, existing dummy_harness_e2e still green — shared-seam verified).
- checklist 4: `picker::data::tests::gather_renders_live_unbound` (REAL roster→gather_endpoints→display_status over an on-disk UNBOUND perch → hollow Unbound; the seam the W5 bug hid in; isolated SPT_HOME via testutil guard).
- traceable: both REQs activated int (+doc+impl+unit+int, EXIT=0). ADR-0026/0027 flipped proposed→accepted.
- LOCAL all green (this Win box): 3 e2e + render-seam unit + 2 existing dummy_harness e2e; clippy/xtask EXIT=0. doyle HARD-gates W6 vs perri's repro. perri validates on real claude-spt at v0.14.0 PUBLISH. After gate: PR#30 ready → release (MINOR v0.14.0, counter from published [[release-counter-from-published]], doyle CHANGELOG-vet first).

**W6 binding:** int reuses v0.12.1 dummy-harness fixture (NO mocks); the multi-subnet regression **MUST run on a multi-subnet node** (single-subnet auto-homes + HIDES the gap). Flip ADR-0026/0027 → accepted. perri validates her exact repro on real claude-spt at publish (she declined the temp `--subnet` stopgap; been multi-subnet-blocked by choice). MINOR bump. Shipped as ONE milestone (operator: no minimal-unblock fast-follow split).
