# THE-FORKENING — milestone triage

Operator-called 2026-07-14 (grill-with-docs session, doyle). Design authority:
ADR-0036 (supersedes ADR-0014) + the CONTEXT.md amendments landing with this
commit. One release target: **v0.32.0, counter 59 — the first cut published
only to `BigscreenVR/spt-bs-releases`**. todlando builds, doyle gates; doyle
also drives the release this milestone (deployah down, operator-authorized
cross-role exception).

Non-goals: no transition release (hard cutover, operator hand-updates every
node); no public docs of any kind; no adapter-repo work (claude-spt-bs is
perri's lane, coordinated separately).

## W0 — repo + identity groundwork (operator + doyle, no code)

1. Create `BigscreenVR/spt-bs-core` + `BigscreenVR/spt-bs-releases` (private). [operator/org]
2. Flip local checkout `origin` → bs-core; add `mirror` → SaberMage/spt-core.
   Flip-first ruled: build work happens under the new project identity.
3. Copy+rename tracked project-context dirs for ALL involved agents
   (`github-com-sabermage-spt-core` → `github-com-bigscreenvr-spt-bs-core`).
4. Register self-hosted runners (kitsubito, hfenduleam) on bs-core. [operator]
5. Release-publish secret with org access on bs-core. [operator]
6. Actions OFF (repo settings) on SaberMage/spt-core + SaberMage/spt-releases
   at cutover. [operator]
7. Mirror discipline live from first bs-core merge: merging agent runs
   `git push mirror main --follow-tags`.

## W1 — gh transport + bootstrap (REQ-UPDATE-GH-TRANSPORT, REQ-INSTALL-BOOTSTRAP-VERB) — ACTIVATED

- Release discovery (`releases/latest`) + asset download move from
  unauthenticated HTTPS (cli.rs:4861, cli.rs:9717) to `gh` subprocess calls
  (`gh api`, `gh release download`), deadline-wrapped like `run_git`
  (spt-store gitrun.rs pattern). Private-repo gotcha driving this:
  `browser_download_url` 404s on private repos even with a valid token.
- Default repo → `BigscreenVR/spt-bs-releases` (`SPT_INSTALL_REPO` seam,
  cli.rs:5363; xtask REPO const, xtask main.rs:729;
  notif.rs CONSENT_CHANGELOG_URL rider).
- Loud failure tokens: gh missing → `UPDATE_FETCH_REJECTED:GhCliRequired`
  with OS-specific install hints (winget/apt/brew); gh unauthed → distinct
  `GhAuthRequired` pointing at `gh auth login`.
- Self-install verb (bootstrap path b): `gh release download` the platform
  binary, then one `spt` verb places it at the canonical install path,
  registers PATH, first-run identity — replaces the hosted one-liner
  scripts. Downloaded exe keeps the `spt-*` name (UAC-740 installer
  detection: no "install/setup/update" in exe names).
- `release_verify_e2e` (fetches public URLs today) reworked to the gh
  carrier; signature verification unchanged (carrier-independent).

## W2 — docs release pipeline (REQ-DOCS-RELEASE-ASSET, REQ-RELEASE-CHANNEL-PRIVATE) — queued

- release.yml assemble builds the mdbook and packs `spt-docs.tar.gz`
  (platform-independent: built HTML + llms.txt + llms-full.txt + raw .md +
  manifest.schema.json) as a release asset with an entry in the SIGNED
  update-set (sha256, same chain as binaries).
- Apply lands/refreshes `$SPT_HOME/docs` (single current copy — docs match
  the installed version). Docs failure NEVER fails the binary update:
  `UPDATE_DOCS_SKIPPED` loud, retry next fetch.
- release.yml publish target → bs-releases; docs-publish.yml retired; the
  book build stays CI-gated against drift (CLAUDE.md mandate) as a ci.yml
  step.

## W3 — local docs server (REQ-DOCS-LOCAL-SERVER) — queued

- hyper static server in the broker (hyper/tokio already in-tree via
  iroh/reqwest — zero new deps; operator ruled battle-tested crate over
  hand-rolled). Loopback ONLY (127.0.0.1, + ::1 where available), default
  port **5474** (547 rejected: privileged + IANA dhcpv6-server), config +
  env override. GET-only, strict path-sanitize under `$SPT_HOME/docs`.
- Published URL surface preserved verbatim (llms.txt contract: /llms-full.txt,
  append-.md raw markdown, /manifest.schema.json).
- `spt docs url` prints resolved URL; bare `spt docs` opens the system
  browser (cfg(windows) creation_flags — no console window).

## W4 — update UX (REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-DAEMON-REFRESH) — queued

Grounding fact (operator-corrected, code-confirmed): `update fetch --apply` /
`update apply` cycle the BRAIN only — the broker and every PTY survive
(apply_staged, applyhost.rs:303); "restart required" is a printed notice
(cli.rs:4615), not behavior. Broker restarts only under `apply --finish`.

- `spt update adapters [<a>[,<b>...]]` — thin alias over `spt adapter update`
  (cli.rs:748; keep the old verb — published surface) + comma-list on both.
  Fail-fast name validation BEFORE any update starts; per-adapter failure
  isolation with a summary line each; nonzero exit if any failed; local-path
  registrations skip loud.
- Plain `spt update` = `fetch --apply` then `update adapters` (core→adapters
  order). Core already current → adapters only. `--core-only`/`-c` skips the
  adapters leg. Invoker survives by construction (brain-only cycle) — no
  re-run machinery.
- `spt update --restart` = fetch → **adapters** → `apply --finish` LAST
  (lethal-leg-last, ruled: completes from any invoking context; a finish
  failure leaving new adapters on old core is loud + operator-attended).
  Retires findings-backlog seed #12 (REQ-UPDATE-ONE-STEP-SAFE-SWAP class).
- `spt daemon refresh` — restart the brain WITHOUT a binary swap and WITHOUT
  touching the broker (the apply_staged brain-cycle path minus the swap).
  Recovery verb for wedged brain-held state (e.g. broken endpoint bringup)
  that today needs a full daemon bounce.

## Cutover (operator, after v0.32.0 publishes)

Hand-update every node (gh + org membership per box) → private
SaberMage/spt-releases + SaberMage/spt-core → Actions off (W0.6) → comms:
perri + flynn get the docs relocation note (http://localhost:5474,
`spt docs url`).

## Dispatch order

W1 now (this dispatch) → gate → W2 → W3 → W4 → gate → release. REQs minted
registry-first below this commit; per-wave activation (W1 active, rest
`required_stages = []` until their dispatch).
