# v0.13.2 — Adapter packaging + live update: JIT build plan

**Design (grill capture, operator-driven 2026-06-22):** ADR-0024 (multi-platform `.spt`), ADR-0025 (live daemon-coordinated adapter update). CONTEXT.md terms: multi-platform `.spt`, **resident vs ephemeral adapter binary**, live update, `gh` auto-transport.

**REQs** (registered; activate real stages per-wave, rule 5):
- `REQ-ADAPTER-MULTIPLATFORM-SPT` (doc-active = ADR-0024) → W1
- `REQ-ADAPTER-GH-TRANSPORT` (inactive) → W2
- `REQ-ADAPTER-LIVE-UPDATE` (doc-active = ADR-0025) → W3
- `REQ-ADAPTER-UPDATE-MESSAGE` (inactive) → W4
- `REQ-ADAPTER-PROOF-DIR-OVERRIDE` (inactive) → W5

**Motivating real-world cases:** F-014 (claude-spt v0.6.0 shipped per-OS `.spt` + a windows-copy default `adapter.spt` stopgap → W1 kills it); the field locked-binary update error + the stale in-memory manifest (→ W3); F-011 (the `*-proof` commands can't resolve a bare-file gh adapter → W5).

---

## W1 — Multi-platform `.spt`  (REQ-ADAPTER-MULTIPLATFORM-SPT, ADR-0024)
Foundational format change; everything else composes on it.
- **Scope:** fat archive = shared `manifest.toml` + `strings/` at root, binaries under per-target-triple subdirs. Install/update extracts shared root + **only `current_platform()`'s** triple, flattened into `install_dir` (flat `<install_dir>/<program>` resolution unchanged, REQ-INSTALL-11). One whole-archive sig. Name stays `adapter.spt` (plain/gzip); `--asset` optional default.
- **Mode detection:** archive contains a known-target-triple subdir → multi-platform (extract shared + that subdir; missing my triple → typed `NoArtifactForPlatform`). No triple subdirs → legacy flat (extract as today — free back-compat). `min_spt_core_version ≥ 0.13.2` is the forward gate.
- **Files:** `extract_release_archive` (cli.rs:2412) → platform-selective extract (shared + my-triple, flatten); reuse `current_platform()` (exists for spt-core update-sets); the `--asset` default → `adapter.spt`, flag optional, in `cmd_adapter_update` (cli.rs:5618) + `add --release`; `verify_staged_archive` (cli.rs:5786) unchanged (sig over the fat archive).
- **Gate:** unit (extract picks the right triple + flattens; missing-triple → `NoArtifactForPlatform`; legacy-flat stays flat; detection keys on a KNOWN triple set, never misfires on a flat archive). int (a real multi-platform `.spt` fixture: `add --release` → only my-triple binaries land at `install_dir` root, manifest+strings shared). doc = ADR-0024 + CONTEXT (active).

## W2 — `gh` transport for private repos  (REQ-ADAPTER-GH-TRANSPORT)
Additive; independent of W1/W3.
- **Scope:** `transport = "https" | "gh" | "auto"` (default `auto`) on the `gh_release` avenue + `add --release`. `gh` shells `gh release download` / `gh api` (private-repo path; honors OAuth + `GH_TOKEN`, spt custodies no token). `--gh`/`--https` force on `add`. `auto` = prefer `gh` if installed+authed, else HTTPS.
- **Files:** the fetch path (`http_get_bytes` cli.rs:2399, `gh_latest_release_version` 5715, `stage_gh_release_archive` 5759) → a `gh`-shell variant; `Update` struct (manifest.rs:399) → `transport` field; transport selection + a bounded `gh` availability probe.
- **Gate:** unit (auto picks gh-when-available else https; gh command construction; version query via `gh api`). int likely HITL/skip (real private repo + gh auth) — unit-cover the decision + command shape. doc = manifest.md + CONTEXT.

## W3 — Live daemon-coordinated update + CRC swap  (REQ-ADAPTER-LIVE-UPDATE, ADR-0025) — THE KEYSTONE
Riskiest wave; touches daemon lifecycle just hardened in v0.12.x/v0.13.0. Likely splits into sub-items.
- **W3a — resident-children registry:** formalize the per-endpoint translation-binary spawn/kill (broker, `TranslationChild::spawn`) into "the resident adapter children for endpoint E running adapter X." The structure an apply queries to know what to cycle.
- **W3b — CRC-gated swap:** replace on disk only files whose content hash differs from the staged archive (prefer std hashing, no new dep). Unchanged files + their running binaries untouched.
- **W3c — manifest refresh:** a `BrainLifecycle` method (lifecycle.rs:70/120-129) to re-clone the new on-disk manifest into the running endpoint (+ its `ManifestRuntime`), so binaries + manifest are on the same page.
- **W3d — daemon-apply IPC:** CLI keeps fetch+verify; for an adapter with running resident binaries it hands APPLY to the daemon over a new IPC command. Daemon apply per endpoint: **stop** resident binary (releases the OS file lock) → W3b swap → W3c refresh → **restart**. Endpoint not running → CLI swaps directly. Route in `cmd_adapter_update` (cli.rs:5679) + `adapter_update.rs` (today `gh_release` = `Skipped(GhReleaseManaged)`; now the running-endpoint apply routes to the daemon).
- **W3e — int keystone (NO mocks):** a real spt-hosted endpoint with a running translation binary → `spt adapter update` → binary stops (lock frees), only-changed files swap, in-memory manifest refreshes, binary restarts, **endpoint stays live and serves the new binary**. The **Windows locked-binary** case is the regression test (pre-fix: `Access denied (os error 5)`; post-fix: clean).
- **Hazards (binding):**
  - Gate vs a REAL dummy-harness fixture + real daemon, **NO mocks** (the v0.12.1 binding lesson — green-mocks ≠ green-real-harness).
  - The stop→restart must be **bounded** (kill-bounded if the binary won't die; never ride a 240s backstop — the `join_bounded` pattern).
  - **Brain-parity:** the endpoint (`BrainLifecycle`/broker) never restarts; only the resident child cycles.
  - **Partial-failure:** a mid-swap failure must not strand the endpoint (stopped binary + half-swapped files); restart-old / atomic-ish on failure.
  - The new int is heavy (real broker + PTY + resident child) — mind hfenduleam load; reuse the `SPT_ATTACH_IPC_DEADLINE_MS` / watchdog env-knob precedent if it flakes on the shared runner.

## W4 — `update-message`  (REQ-ADAPTER-UPDATE-MESSAGE)
Small, additive.
- **Scope:** `[update].message` (plain multi-line) surfaced to stdout, **markdown-rendered** (helpfmt prose path), **only when an update is actually applied** (version changed), not on a no-op. Read from the newly-installed manifest; avenue-agnostic.
- **Files:** `Update` struct (manifest.rs:399) → `message: Option<String>`; `cmd_adapter_update` (cli.rs:5699) → after a successful apply, `helpfmt::render` + print the new manifest's message.
- **Gate:** unit (rendered on applied update, NOT on no-op; markdown render). doc = manifest.md + CONTEXT. No int (CLI output, unit-covered).

## W5 — `*-proof --dir/--manifest` override  (REQ-ADAPTER-PROOF-DIR-OVERRIDE, F-011) — DX rider
May ship in v0.13.2 or a separate v0.13.x adapter-DX batch.
- **Scope:** `--dir <path>` / `--manifest <file>` on `digest-proof` + `translate-proof` → proof a DEV binary against an on-disk manifest+dir without staging a full extracted install (mirrors `digest-proof --sample`). Un-stales perri's bare-file digest-proof int.
- **Files:** `cmd_adapter_digest_proof` (cli.rs:5958) + `cmd_adapter_translate_proof` (cli.rs:6108) → accept `--dir`/`--manifest`, resolve from there instead of `registry::resolve_option`.
- **Gate:** unit (override resolves a dev manifest+dir); int (mirror the existing proof ints with `--dir`). doc = the proof `--help` + patterns.

---

## Build order, gating, ship
**Order:** W1 → W2 (parallel-able) → **W3** (keystone, after W1's extract) → W4 → W5. doyle gates each wave (full read + non-vacuous int; **W3 vs a real daemon+dummy-harness fixture, NO mocks**). Activate each REQ's real stages at its wave (rule 5). CI green BOTH runners per wave; the **W3 int + the Windows-lock regression are the ship-blockers**. Then deployah cuts **v0.13.2** (counter 29 — verify against spt-releases metadata at cut, the v0.13.1 lesson).

**Downstream:** perri drops the claude-spt F-014 per-OS stopgap when W1 lands; F-011 closed by W5. Coordinate the v0.13.2 publish ping to perri.

**Numbering note:** ADR-0023 (always-on endpoint) is now merged to main; 0024/0025 are stable.
