---
name: v0170-published
description: "v0.17.0 published 2026-06-28 (counter 36); robust WAN join. Two publisher catches: third-party lock collision + latent [twohost]/[budget] CI gate bug."
metadata: 
  node_type: memory
  type: project
  originSessionId: 5a9b9e63-3899-4378-ad2c-8331f47cd507
---

v0.17.0 PUBLIC 2026-06-28 (counter 36, monotonic 35→36). Tag @a8baa23 (merge PR#39 on main, off branch v0.17.0-robust-join). Signed FRESH, key rel-primary-2026, seed len64. update-set v36 "0.17.0" both platforms. docs-publish GREEN on tag (networking/overview two-phase-join+troubleshooting, lifecycle/overview picker palette legend).

Hashes (SHA256SUMS = .release.json): linux `874486a62f237637660d7780e5cd953e5eb4271e433e775b774b83618ee2f762` / win `80053a2143b1af5c7c9662098c3500a7689e2cb349f067a941151706b67c9814`.

Contents (5 waves, doyle-gated): W1 7cddaba per-family bind gate (robust WAN join over half-broken IPv6, SPT_DISABLE_IPV6/IPV4) · W4 867c7ab cold perch→Suspended not Dormant (presence truth) · W2 a07322c two-phase meet-before-code join · W3 be1f4d6 diagnosable join (progress/--verbose) · W5 54a4540 picker subnet display parity + status palette. CHANGELOG 68688ed + public docs 38965cb (doyle-authored, rode the PR).

GOTCHA 1 (publisher catch — THIRD-PARTY LOCK COLLISION): on the 0.16.0→0.17.0 Cargo.lock bump, THIRTEEN entries were at `version = "0.16.0"` — 11 first-party PLUS third-party `netwatch` + `portmapper` (iroh deps, coincidentally at 0.16.0). A blind `replace('version = "0.16.0"')` would have dangled the lock (pointed netwatch/portmapper at a nonexistent 0.17.0). Caught by a name-anchored bump asserting exactly the 11 first-party names; netwatch/portmapper HELD. This is the [[release-cargo-lock-first-party]] hazard made real — ALWAYS name-anchor the lock bump, never blind-replace the version string.

GOTCHA 2 (publisher catch — LATENT CI GATE BUG, durably fixed): the `twohost-a/b` + dormancy-`budget` jobs gated ONLY on `contains(github.event.head_commit.message, '[twohost]')`. `head_commit` is a PUSH-event payload field → NULL on `pull_request` events → `contains(null,…)=false` → the ladder SILENTLY SKIPPED on every tagged release PR to date (false confidence; only ever fired on push to main/dev-freeform). FIX (commit 04dc7e9, chore(ci), no REQ — infra): add `|| contains(github.event.pull_request.title, '[twohost]')` to each gate + put `[twohost]` in the PR TITLE. The two os-scoped budget steps PARENTHESIZE the OR before `&& runner.os ==` (GHA evals && before || — un-parenthesized leaks the step cross-OS). Same-repo pull_request runs the workflow from the PR head, so the fix is live for its own PR. PROVEN: twohost-a/b fired + BOTH green on PR #39 att3 (first time ever on a PR). LESSON: `[twohost]`/`[budget]` now go in the PR TITLE (belt-and-suspenders: keep in bump HEAD msg too for the main-merge push).

GOTCHA 3 (flake, not code): att2 Linux red on ONE test `multi_subnet_bringup_e2e::fresh_unbound_is_attachable_before_bind` (panic rc.rs:421 "rc must attach the live pre-bind session", rc stderr RC_FAIL empty = attach missed window under kitsubito load). Confirmed FLAKE: same commit passed Linux-att1 AND Win-att2; only Linux-att2 red (cross-platform non-determinism). Seedmap-starvation class [[seedmap-test-collides-live-daemon]]. Cleared on att3 full-rerun. Also att1 Win-test red = disk-full os-error-112 [[hfenduleam-disk-full-ci]] (doyle freed 210GB). Gating took 3 attempts (FULL rerun each — `--failed` won't re-fire skipped needs:test dependents like twohost). Follow-up (doyle→backlog, post-cut): harden the timing-sensitive E2E set (CI isolation / retry-marking).

Next counter = 37. Supersedes ledger via [[v0121-published]]. perri GO = claude-spt v0.8.0, min_spt_core→0.17.0.
