# V0.7.4 build plan — gh_release [update] avenue + adapter-binary install-dir resolution

> JIT plan (doyle, 2026-06-15). Operator GO. Build on the **spt-core-doyle worktree** (main), cut as **v0.7.4** (deployah, counter 16). NO code written yet — this is the design + investigation so the next session resumes without re-deriving.
>
> **2026-06-16 update:** Operator added **Feature D** (Windows at-logon visible-window fix). Build status this session (doyle, spt-core-doyle worktree, all committed local-only — batched, NOT pushed):
> - **D — Win at-logon window fix (REQ-INSTALL-10):** DONE @ `ac4dad5`.
> - **B — adapter-binary install-dir resolution (REQ-INSTALL-11):** DONE @ `6306d33` (built by a background agent, doyle-gated + verified).
> - **A — gh_release [update] avenue, optional signing (REQ-UPD-9):** DONE @ `2d6bcdf` (built by a background agent, doyle hard-gated the trust path + verified). Driven CLI-side via new `spt adapter update`; conduct_ripple has no prod driver (auto-ripple = REQ-UPD-5 production activation, separate).
> - **C — `update apply` no-staged UX nit:** DEFERRED pending root-cause. perri saw "Updated to v0.7.2" on a no-staged apply, but the code's `NoUpdate` arm prints "NO_UPDATE" — her case likely hit a *different* `ApplyStagedOutcome` (re-apply of an already-staged version rendering "Updated"). Don't guess a message change on a user-facing apply path ([[update-apply-confident-message]]); confirm which outcome fires (perri repro / read apply_staged) first. Fast-follow.
> - **E — api manifest-from-adapter resolution (REQ-API-4):** DONE @ (this commit). `api` resolves manifest+profile+install_dir from `--adapter name:profile` via the registry when `--manifest` is omitted (`--manifest` = override); precise `source_dir` install_dir also closes Feature B's copy-mode psyche edge. Built by bg agent, doyle-gated (`:profile` overlay confirmed via shared apply_option_overlay).
>
> **CONSOLIDATION (operator 2026-06-16): NO separate v0.7.4 — everything ships as ONE `v0.8.0` (counter 16, minor).** M11 (PR #16, W5-gated) + v0.7.4 features (D+B+A) + REQ-API-4 (E) all roll into v0.8.0. "No sense waiting for two big builds when it can all be in one." perri + todlando notified (perri sweeping v0.7.4→v0.8.0 refs + int psyche-gate keys <0.8.0; todlando holding PR #16 for the merge-ordering ping).
> - **REMAINING before push:** docs batch — CONTEXT.md (gh_release avenue + install-dir resolution + the api manifest-from-adapter resolution), MANIFEST.md + docs-site manifest.md (`[update] gh_release` + `<asset>.sig` keyed-verify convention + install-dir resolution note), docs-site install-on-demand.md (gh_release auto-update; DROP the PATH-placement caveat), **how-to-live topic** (HOW_TO_LIVE: live bringup = persistent `api listen <id>` via Monitor [heir to `$LIVE start`; ready-vs-live; NOT `--once`] + the seed→listen-child→probe→assert acceptance recipe + the WINPID/bind-before-send gotchas + registry + v1-lock bump — REQ-DOCS-6, closes perri F-007 item-1). Activate `doc` stage on REQ-UPD-9/REQ-INSTALL-11/REQ-API-4 + tag sections. version-refs-not-milestone-codes ([[public-docs-version-not-milestone]]).
> - **ASSEMBLY:** get everything on main (v0.7.4+E commits already on local main; merge PR #16/M11 in) → CI green BOTH runners → deployah bumps **0.8.0** + CHANGELOG + Cargo.lock first-party + tag + sign + publish (operator-gated). Then ping perri (v0.8.0).

## Scope — four deliverables

**A. NEW `gh_release` `[update]` avenue (OPTIONAL signing).** An adapter declares `[update] avenue = "gh_release", repo = "user/repo"`; spt-core checks the repo's latest GitHub release version vs the installed adapter version and, if newer, auto-updates via the `--release` fetch primitive. Trust = HTTPS+GitHub by default; verify the `.spt` against an Ed25519 `signing_key` if the adapter declares one. Removes perri's `[update]` block (file_pull needs absent signing tooling + deferred transport REQ-UPD-1; delegated needs CC-updater wiring + self_verifies).

**B. Adapter-binary template resolution against the install dir BEFORE PATH.** The "proper" fix for the `--release` bundled-binary gap perri CONFIRMED: `.spt` ships binaries to `adapters/_github/<safe>/` but bare-name templates (`claude-spt-digest …`) resolve from PATH and miss. Resolve an adapter command template's program against the adapter's install dir first → `.spt` becomes self-contained, no PATH placement.

**C. (if cheap) UX nit.** Bare `spt update apply` with nothing staged is a silent no-op ("Updated to v0.7.2"); make it say "nothing staged — run `spt update fetch` first" (perri found this).

**D. Windows at-logon visible-window fix (operator-reported, REQ-INSTALL-10). DONE.** The install.ps1 at-logon scheduled task ran `spt daemon run` — a foreground console process; Task Scheduler launches the action in the interactive logon session, so the long-lived console held a VISIBLE window for the daemon's whole lifetime. Fix: task action → `spt daemon start` (spawn_detaches a console-less DETACHED_PROCESS daemon via CREATE_NO_WINDOW, launcher exits) → daemon runs in the background, no persistent window. Operator chose the robust `daemon start` over a fully-windowless WSH/VBS shim (declined to avoid a WSH-disabled fragility on hardened boxes; residual ~1-2s launcher console flash at logon accepted).

## Feature D — DONE (2026-06-16, doyle, this session)
- **REQ-INSTALL-10** minted in `traceable-reqs.toml` (`required_stages = ["impl","unit"]`; int N/A — at-logon registration can't be hermetically driven in CI, same constraint as REQ-INSTALL-8's impl-only).
- **impl:** `installer/install.ps1` at-logon schtasks `/TR` → `daemon start` (both the `/Create` and the manual-fallback hint), tagged `[impl->REQ-INSTALL-10]` with the rationale comment.
- **unit:** `crates/spt/tests/oneliner_e2e.rs::at_logon_task_launches_daemon_in_background_not_foreground` — non-gated content-assertion over the real install.ps1, pins the ONLOGON task action to `daemon start`, forbids `daemon run`. `[unit->REQ-INSTALL-10]`. PASSES.
- **preflight green:** `traceable-reqs check` EXIT0 (REQ-INSTALL-10 [OK] impl+unit); `cargo clippy --workspace --all-targets -D warnings` clean; the unit test passes. No CLI/docs-site change (installer-internal bugfix, no new surface). CHANGELOG line at release (deployah).

## Feature B — DONE (2026-06-16, doyle, built by a background agent + doyle gate/verify)
- **REQ-INSTALL-11** minted (`["impl","unit"]`; doc added with the docs batch).
- **helper:** `spt_runtime::resolve_program_in_dir(program, install_dir) -> String` (install-dir-first, Windows `.exe` suffix, PATH fallback; pure) + `run_bounded_command_in` variant + `ManifestRuntime::with_install_dir`. 3 unit tests (present/absent/exe-suffix).
- **exec sites:** `[digest]` extractor (`spt-daemon/digest.rs` + `spt-live/digest.rs::extract_digest` gains `install_dir`) uses the registry record's `source_dir` (precise); `[session.psyche_init]` (`lifecycle.rs::with_config_in` → `ManifestRuntime::with_install_dir`); `adapter digest-proof` CLI (`cli.rs`) uses `source_dir` too (consistency bonus — proof resolves like the daemon).
- **api seam threading:** `Ctx.install_dir` = the `--manifest` file's PARENT dir (api/mod.rs), threaded through `cmd_listen`/`LiveHost::new_opt`/`BrainLifecycle::new_opt`/`cmd_shutdown`. **DECISION:** the api/psyche seam uses manifest-parent (not a registry `source_dir` lookup) — CORRECT for pointer-mode (`--release`/`--github`, perri's exact case; manifest+binaries co-located), and the registry record isn't unambiguously available at the `--manifest` seam (the manifest may be an override). **KNOWN EDGE (follow-on):** copy-mode + psyche_init + shipped binary would miss (binary stays in source_dir, not the copy dir); the digest path is precise via the registry. Documented, not built this rev.
- **verified by doyle:** `cargo check --workspace --all-targets` clean; `clippy --workspace --all-targets -D warnings` clean; `traceable-reqs check` EXIT0 (REQ-INSTALL-11 [OK]); spt-runtime (3 helper units) + spt-live digest + spt-daemon `--lib` (286) all green. (The background builder finished the edits then hung ~1h on the heavy spt-daemon INTEGRATION suite during its own preflight — not a deadlock; doyle stopped it + verified with targeted tests, CI runs the full E2E.)

## Investigation findings — DO NOT re-investigate

### Feature B (binary-resolution)
- Adapter command templates run via the **`ManifestRuntime` trait** (`crates/spt-runtime/src/runtime.rs`): `spawn_session` (~176), `run_bounded_stdin` (~186), `run_bounded` (~195); actual exec `Command::new(program)` (~240); plus the free `run_bounded_command` (384, `Command::new` at 393).
- Install dir = **`AdapterRecord.source_dir`** (`registry.rs:57`). For `--release`/`--github` = durable `adapters/_github/<safe>`. `manifest_dir(Pointer)=source_dir`, `manifest_dir(Copy)=adapters/<name>`. **NOTE:** copy-mode copies only manifest+`strings/` to `adapters/<name>`; **binaries stay in `source_dir`** → resolve against `source_dir`, NOT manifest_dir.
- **DESIGN:** a resolution helper in `spt-runtime` (next to `run_bounded_command`): given program (first token) + install_dir, if `<install_dir>/<program>` (or `+".exe"` on Windows) exists → rewrite the template's program token to that absolute path; else leave bare (PATH fallback). Reusable by the daemon exec sites + CLI.
- **EXEC SITES TO PATCH (find exact run lines next session):**
  - `[digest]` extractor: `digest.rs` activity backbone (~157–173, the `run_bounded` call); `digesthub.rs:180` thread::spawn wraps it.
  - `[session.psyche_init]` runner: detached spawn in `livehost.rs` / `brainproc.rs` (`brainproc.rs` has `Command::new` at 920; the psyche spawn is the live-agent companion launch).
- **v0.7.4 SCOPE:** digest + psyche sites (perri's two). Other templates (`[session.self]` bringup = the harness binary, usually on PATH; `[history]` fetcher/normalize; shell `spawn`/`wake_command`) = extend later, document the scope.
- install_dir is resolved by adapter_name → `registry` record → `source_dir`.

### Feature A (gh_release avenue)
- `UpdateAvenue` enum (`manifest.rs:427`): today `Delegated | FilePull`. **ADD `GhRelease`.**
- `Update` struct (`manifest.rs:388`): has `avenue, command, repo: Option, path_regex, signing_key: Option (Ed25519), self_verifies`. gh_release **reuses `repo`** + optional `asset` (likely a NEW field; default `adapter.spt`) + optional `signing_key` (verify `.spt` if present).
- Validation (`manifest.rs:728`): avenue↔required-fields. **ADD: `GhRelease` requires `repo`.**
- `adapter_update.rs`: `plan_adapter_update` (84) is PURE (decision+verification, no fetch). Ripple = `conduct_ripple` (135) + `ripple_registered` (155, over the registered set).
- **gh_release update logic:** check the repo's LATEST GitHub release version vs installed `manifest.adapter.version` → if newer, run the `--release` fetch primitive → if `signing_key` present, verify the `.spt` (reuse `release.rs` adapter-content-signing verify) → re-extract + re-register. This is a side-effecting layer ABOVE `plan_adapter_update` (like file_pull's deferred transport), wired into the conduct/ripple path.
- **LIFT `fetch_release_adapter` + `extract_release_archive`** (currently `crates/spt/src/cli.rs`, tagged `[impl->REQ-INSTALL-9]`) to a shared location (spt-runtime or spt-daemon) so the update path reuses them.
- Version compare: parse the GH release tag (e.g. `v0.1.0`) as semver vs installed version (spt-core has semver handling for `min_spt_core_version` — find the util).
- **Trust:** operator chose **optional signing** — unsigned HTTPS+GitHub default; verify `.spt` if `signing_key` declared.

### Feature C
- `cmd_update` apply path in `cli.rs` — the no-staged branch. Make it diagnostic, not a silent success.

## Build order
1. **Feature B** (smaller, unblocks perri runtime): resolution helper in spt-runtime + apply at digest + psyche sites + units.
2. **Feature A**: enum + validation + lift fetch/extract to shared + version-check + optional-verify + ripple wiring + units.
3. **Feature C**: small UX fix.
4. **Docs:** CONTEXT.md (gh_release avenue + install-dir resolution), `docs/MANIFEST.md` + docs-site `manifest.md` (`[update] gh_release` + the resolution note), docs-site `install-on-demand.md` (gh_release auto-update; DROP the "PATH placement" caveat — binaries now resolve from the install dir), CLI ref regen. Positive framing ([[docs-positive-framing]]).
5. **REQ:** mint in `traceable-reqs.toml` FIRST. Free INSTALL id = **REQ-INSTALL-10** (1–9 taken: 6/7/8=M8, 9=--release). The gh_release avenue may instead fit a **REQ-UPD-*** id (it's an update avenue) — check free UPD ids. Binary-resolution can ride REQ-INSTALL-9 (follow-on) or REQ-INSTALL-10.
6. **Pre-flight (CI invocation):** `cargo clippy --workspace --all-targets -- -D warnings`; `traceable-reqs check` (EXIT 0); `cargo run -p xtask -- check` + `gen` (CLI ref); `cargo test -p spt --bins` + `-p spt-daemon`; schema regen if manifest changed: `SPT_BLESS=1 cargo test -p spt-runtime checked_in_schema_is_current`. Pre-flight the EXACT `--workspace` clippy ([[ci-clippy-preflight-workspace]]); no M#/REQ codes in clap help ([[cli-command-docs-drift]]).
7. **Ship:** commit to main (spt-core-doyle worktree), push, confirm ci.yml GREEN both runners, THEN deployah cuts v0.7.4 (counter 16): bump 0.7.3→0.7.4, CHANGELOG, Cargo.lock first-party lines (watch third-party version collisions — loom/spki bit v0.7.3, [[v073-published]]), tag, sign, publish. Ping perri.

## Open threads (NOT part of v0.7.4)
- **W4 GATE PASS — TOP IMMEDIATE NEXT STEP** — todlando's M11-W4 (Gateway-owner capstone, REQ-SHELL-5). Code-read PASS done. The real block was PR #16 being **unmergeable** (GitHub skips PR CI for unmergeable PRs — NOT a webhook drop); todlando merged main in → **gate HEAD = 63a5d3e** (CI run 27599789430 queued both runners). I INDEPENDENTLY verified the W4 deltas are byte-unchanged across the merge (`git diff c4dbeb4 63a5d3e -- gateway_owner_shell_e2e.rs linkhost.rs` = EMPTY; the merge brought only my --release+docs main content, already CI-green). So **my code-read PASS holds verbatim on 63a5d3e — no re-read; STAMP W4 GATE PASS the moment 63a5d3e CI is green both runners** (attach_survives flake #7 → rerun --failed). todlando told to HOLD (not merge PR#16) until I stamp. Then todlando → **W5** (rig [twohost] + docs: drive + tunnel [twohost] rungs, how-to subnet, ADR-0020 enumeration, on-LAN docs). M11 waves on **PR #16** (branch m11-shell-substrate) — ship a LATER release, NOT v0.7.4.
- **perri** — running live int under a disposable id; v0.7.4 to-do queued (drop interim PATH-copy, declare `gh_release`, confirm UX nit), fires on v0.7.4 publish ping.

See memory: [[adapter-add-release-archive]], [[v073-published]], [[broker-is-daemon-state-anchor]], [[perri-question-triage-protocol]], [[docs-positive-framing]].
