---
name: join-truth-build-progress
description: "JOIN-TRUTH milestone (v0.28.0/counter 48) build ledger — BUILD COMPLETE: W1 @39c1eb9 + W2 @893f06d both BLESSED, deployah GO'd for release. NEXT-TOUCH: ceremony_offset_state one-lock cosmetic."
metadata: 
  node_type: memory
  type: project
  originSessionId: d2c001cf-62b5-48ea-9db6-ada702aadc74
---

**JOIN-TRUTH** (operator-called 2026-07-06, doyle-gated, drive to v0.28.0 / counter 48). todlando executes, doyle gates per-wave, deployah publishes. branch `join-truth` off main@47efdaf. Origin = enlyzeam BIGNET-join RCA (doyle /diagnose; see [[spt-core-findings-backlog]] seed #4): stopped w32time drifted clock +210s, a live `w32tm /resync` step defeated the REQ-PAIR-8 ceremony offset four ways (D1-D4) + one operator UX change.

**5 REQs minted** @2d380c6 (all `required_stages=[]`, activate per wave — never full end-state upfront):
- W1: REQ-PAIR-NTP-MULTIHOME, REQ-PAIR-NTP-LOUD-FAIL, REQ-HAZARD-CEREMONY-CLOCK-STEP
- W2: REQ-JOIN-VERBOSE-CLOCK, REQ-JOIN-DEFERRED-ELEVATION

**W1 SHIPPED to gate @39c1eb9** (activated [impl,unit]; traceable exit 0; clippy --workspace clean; 593 lib + 6 new nextest pass). Files: `crates/spt-net/src/net/pairing/ntp.rs`, `crates/spt-daemon/src/pairhost.rs`, `docs/KNOWN-HAZARDS.md` (7.35), `traceable-reqs.toml`.
- **D1 MULTIHOME** — `query_unix_secs` iterates `to_socket_addrs()` + per-family bind (v4→0.0.0.0, v6→[::]); split `query_first_reachable` (seam) + `query_one_addr`. unit `v6_first_ordering_does_not_starve`.
- **D2 LOUD-FAIL** — kept `Option` (not `unwrap_or(0)`); pure `ntp_transition(prev_uncorrected, outcome)→(bool,Transition{Silent,Uncorrected,Recovered})` logs edges only. unit `loud_fail_logs_only_on_transitions`.
- **D3 CLOCK-STEP** — `CachedOffset{when:Instant, wall_at:SystemTime, uncorrected}`; pure `refresh_needed(when,wall_at,mono_now,wall_now,ttl)` + `clock_stepped(mono_elapsed,wall_signed)` (>2s ⇒ step ⇒ requery regardless of TTL) + `signed_secs`; `force_refresh`/`invalidate` seam. pairhost `meet_seed_holder` now wraps generic `sweep_then_final_retry(probe,deadline,start,on_exhaust,gap)` — on exhaustion force_refresh + ONE final sweep before NO_SEED_HOLDER. units `clock_step_forces_refresh_via_injected_reads` (injected Instant+Duration / SystemTime=UNIX_EPOCH+dur, NO sleep) + `invalidate_clears_the_snapshot` + `meet_retries_once_after_refresh_on_exhaustion` + `meet_final_retry_failing_surrenders_once`.
- **W1 BLESSED @39c1eb9 (doyle 2026-07-06):** isolated worktree gate, full-workspace nextest 1654/1654, clippy --workspace --all-targets clean, traceable exit 0; design review grounded (KH 7.35 = RCA, v6-first unit reds on old code, REQ-PAIR-8 contract intact). Rig reaped (4 leaked gate daemons + 2 orphan conhosts). ONE non-blocking remark to todlando: sort resolved NTP addrs v4-first in query_first_reachable (blackholed-v6 costs ~1.5s/addr; latency-only). W2 GO sent.

**W2 SHIPPED to gate @893f06d** (activated; traceable exit 0; xtask check OK; clippy --workspace clean; 595 seam-sweep + 4 new units pass). Files: cli.rs, pairhost.rs, ntp.rs, docs/adr/0005 (deferred-elevation amendment), traceable-reqs.toml. Pinged doyle W2-READY.
- **VERBOSE-CLOCK [impl,unit]** — `ntp::ceremony_offset_state()→(offset,corrected)` under one lock; pairhost `JoinerClock{step,offset,corrected}.now()/render()` folded into `meet_failure_detail` verbose NO_SEED_HOLDER block (added `clock: JoinerClock` param — updated its 2 callers); CLI prints joiner same-box ceremony clock under `--verbose` at meet start. units `meet_failure_detail_concise_vs_verbose` (asserts step/offset/UNCORRECTED) + `joiner_clock_render`.
- **DEFERRED-ELEVATION [doc,impl,unit] — INT DEFERRED (doyle ruling: Fork D + he runs field-acceptance).** pure `join_gate_decision(JoinPhase{Entry,PostMeet}, has_code, Elevation)→{Proceed,Elevate}` + `run_join_gate` at 2 sites in cmd_subnet_join. --code gates Entry (unchanged); interactive DEFERS to PostMeet (Met arm). LEG 4b already correct (no code change): `run_join_gate` treats `try_auto_elevate()==None` uniformly → refusal + EXIT_NOT_ELEVATED; headless Linux `decide_elevation_path`→PrintHint→None→clean exit 3, returns BEFORE any mutation. doc = ADR-0005 amendment ([doc->] tagged) + REQ-SUBNET-4 note. unit `deferred_elevation_gate_matrix` (8 cases). Int earmarked for next [twohost] milestone; doyle's gate-time field acceptance: (4a) NOSUCHNET-never-prompts on HFENDULEAM, (4b) TESTNET-on-enlyzeam real WAN meet→refuse→unmutated on headless kitsubito.
- **W2 BLESSED @893f06d (doyle 2026-07-06): BUILD COMPLETE, deployah GO'd for v0.28.0/counter 48.** Local 1657/1657 + clippy + traceable + xtask (first xtask run tripped KNOWN #13 CARGO_TARGET_DIR bug — workaround rerun, not a defect). Linux leg kitsubito 1645/1645 + clippy 0. Field 4a PASS (unelevated NOSUCHNET: no elevation attempt, verbose clock line live, W1 final-retry sweep visible: 12 attempts/80s vs 75s deadline). Field 4b PASS (TESTNET real WAN meet → PostMeet refusal exit 3 → store unmutated; run against gate DAEMON on kitsubito). Branch pushed to origin. Cosmetic handed to todlando: ceremony_offset_state doc says one lock, takes two (TOCTOU sliver, diagnostic-only).
- **Gate-session gotchas:** (1) bg-task killer RECURRED on hfenduleam — both gate bg tasks externally killed mid-run; foreground staged fallback worked. (2) pkill -f "<path>" over ssh kills YOUR OWN ssh session when the pattern matches the remote command line — use explicit pids (the exit-255s). (3) kitsubito runs spt **0.6.0** under systemd user unit spt-daemon.service (auto-respawns; predates pair-meet, peer pump was 7-days stalled) — needs an update pass, flagged to operator. (4) piped `$?` trap again (JOIN_EXIT=0 lie) — capture exit unpiped. (5) enlyzeam completed the REAL BIGNET join mid-gate (BIGNET 2 nodes) — incident field-closed.

**W2 design detail (as built):** `crates/spt/src/cli.rs`.
- **DEFERRED-ELEVATION** — `cmd_subnet_join` (cli.rs:7482) gates at ENTRY today (line 7485 `join_elevation_refusal(elevation::current())`). Restructure: pure `join_gate_decision(JoinPhase{Entry,PostMeet}, has_code:bool, elevation:Elevation)→JoinGate{Proceed,Elevate}` — `--code` gates at Entry (one-shot unchanged, REQ-SUBNET-4); interactive DEFERS to PostMeet (inside the `Ok(MetOutcome::Met(m))` arm ~line 7553). `elevation::gate_for(el)→Gate{Show=Proceed,Fallback=needs}`. Elevated re-run: `try_auto_elevate` (6091) re-runs `current_exe_str()`+`invocation_args()` (argv sans --code) → skip-gate → re-meet (cheap, member present) → post-meet gate now elevated → proceeds to code prompt. doyle RULED OUT cross-elevation hold-session adoption (re-meet instead). Failed search NEVER reaches PostMeet gate → no UAC/sudo prompt. doc = REQ-SUBNET-4 registry comment + ADR-0005 amendment (gate moves command-entry→enrollment boundary; discovery read-only pre-trust). unit = join_gate_decision 8-case matrix. int (Linux leg) = declined elevation post-meet leaves NO store mutation + refusal exit.
- **VERBOSE-CLOCK** — `join --verbose` prints joiner's derived TOTP step + applied offset secs + NTP correction state per sweep; fold same into `meet_failure_detail` (pairhost.rs:489) NO_SEED_HOLDER block. Need a new ntp accessor exposing (offset_secs, uncorrected) + `time_step(ceremony_now_secs())`. Extends REQ-JOIN-DIAGNOSTICS --verbose (no new knob). clap help → xtask docs gen, NO REQ codes in ///. unit = verbose render carries step/offset/state.

**FIELD-CLOSED (enlyzeam):** operator completed the real BIGNET join mid-gate 2026-07-06 (enlyzeam shows BIGNET 2 nodes) — the seeding incident is done end-to-end.

**Protocol (doyle):** per-wave bless = isolated worktree `.worktrees/gate-<sha>` + fresh CARGO_TARGET_DIR + prebuild `cargo build -p spt --bins` + nextest + clippy --workspace + traceable exit 0. W2 gets a Linux leg (elevation = OS-semantics: sudo/pkexec/terminal-emulator vs UAC). No local gate suites while CI on hfenduleam (THIS box). Alarm every test run. No pushes to PR branch while [twohost] lives (none this milestone). Flow: ping W1-ready→gate→bless→roll W2→gate+Linux→bless→GO deployah (bump-in-PR, tag-on-merge; CHANGELOG census banked w/ deployah).
