1345	[[requirements]]
1346	id = "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP"
1347	title = "Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) — the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a))"
1348	required_stages = ["doc", "impl", "unit", "int"]  # doc = KNOWN-HAZARDS 7.22 + ADR-0025 amendment. impl/unit from W3 (a). int = W3e (live_adapt_psyche_lock_e2e: endpoint stop reaps a brain-less orphan psyche).
1349	[[requirements]]
1350	id = "REQ-ADAPTER-UPDATE-MESSAGE"
1351	title = "An adapter manifest may declare `[update].message` — a plain (multi-line) human notice surfaced to stdout, markdown-rendered (the v0.13.0 helpfmt prose path), ONLY when `spt adapter update` actually APPLIES an update (version changed), not on a no-op. Read from the newly-installed manifest; avenue-agnostic (gh_release/delegated/file_pull). No `{key}` substitution. Use: an adapter telling the operator a post-update action, e.g. spt-claude-code's \"run `/reload-plugins` in any ongoing sessions\". (v0.13.2)"
1352	required_stages = []  # additive; activate at the v0.13.2 build.
1353	[[requirements]]
1354	id = "REQ-ADAPTER-GH-TRANSPORT"
1355	title = "The `gh_release` avenue (and `spt adapter add --release`) gain a fetch `transport`: `https` (current reqwest direct, public), `gh` (shell the pre-authorized `gh` CLI — the private-repo path; `gh` honors OAuth and `GH_TOKEN`, so spt custodies no token), or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `--gh`/`--https` force it on `add`. Additive over the existing fetch path; verify->extract->register downstream is unchanged. (v0.13.2)"
1356	required_stages = ["doc", "impl", "unit"]  # doc = MANIFEST.md + CONTEXT transport option. impl/unit activated at the v0.13.2 W2 build. int = HITL (real private repo + gh auth), unit-covered not int.
1357	[[requirements]]
1358	id = "REQ-ADAPTER-PROOF-DIR-OVERRIDE"
1359	title = "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)"
1360	required_stages = []  # additive DX; activate when built.
1361

[[requirements]]
id = "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP"
title = "Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) — the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a))"
required_stages = ["doc", "impl", "unit", "int"]  # doc = KNOWN-HAZARDS 7.22 + ADR-0025 amendment. impl/unit from W3 (a). int = W3e (live_adapt_psyche_lock_e2e: endpoint stop reaps a brain-less orphan psyche).
[[requirements]]
id = "REQ-ADAPTER-UPDATE-MESSAGE"
title = "An adapter manifest may declare `[update].message` — a plain (multi-line) human notice surfaced to stdout, markdown-rendered (the v0.13.0 helpfmt prose path), ONLY when `spt adapter update` actually APPLIES an update (version changed), not on a no-op. Read from the newly-installed manifest; avenue-agnostic (gh_release/delegated/file_pull). No `{key}` substitution. Use: an adapter telling the operator a post-update action, e.g. spt-claude-code's \"run `/reload-plugins` in any ongoing sessions\". (v0.13.2)"
required_stages = []  # additive; activate at the v0.13.2 build.
[[requirements]]
id = "REQ-ADAPTER-GH-TRANSPORT"
title = "The `gh_release` avenue (and `spt adapter add --release`) gain a fetch `transport`: `https` (current reqwest direct, public), `gh` (shell the pre-authorized `gh` CLI — the private-repo path; `gh` honors OAuth and `GH_TOKEN`, so spt custodies no token), or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `--gh`/`--https` force it on `add`. Additive over the existing fetch path; verify->extract->register downstream is unchanged. (v0.13.2)"
required_stages = ["doc", "impl", "unit"]  # doc = MANIFEST.md + CONTEXT transport option. impl/unit activated at the v0.13.2 W2 build. int = HITL (real private repo + gh auth), unit-covered not int.
[[requirements]]
id = "REQ-ADAPTER-PROOF-DIR-OVERRIDE"
title = "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)"
required_stages = []  # additive DX; activate when built.


Now find the apply path + helpfmt + test literals.


crates\spt\src\cli.rs:6160:fn cmd_adapter_update(adapters: &std::path::Path, name: Option<&str>) -> i32 {


crates\spt-daemon\src\adapter_update.rs:217:    fn file_pull_update(signing_key: Option<String>) -> Update {
crates\spt-daemon\src\adapter_update.rs:218:        Update {
crates\spt-daemon\src\adapter_update.rs:232:    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {
crates\spt-daemon\src\adapter_update.rs:233:        Update {
crates\spt-daemon\src\adapter_update.rs:247:    fn gh_release_update(repo: Option<String>) -> Update {
crates\spt-daemon\src\adapter_update.rs:248:        Update {


crates\xtask\src\main.rs:1132:fn render_converge_table(
crates\spt\src\wansend.rs:446:pub fn render_refusal(
crates\spt\src\helpfmt.rs:83:pub fn render(input: &str, color: bool) -> String {
crates\spt-daemon\src\digesthub.rs:349:pub fn render_digest(digest: &Digest) -> String {
crates\spt-daemon\src\digesthub.rs:355:pub fn render_update(update: &DigestUpdate) -> String {
crates\spt-daemon\src\digesthub.rs:370:fn render_turns(turns: &[Turn]) -> String {
crates\spt-daemon\src\digesthub.rs:505:    fn render_is_glanceable() {
crates\spt\src\cli.rs:1014:            print!("{}", crate::helpfmt::render(&raw, crate::helpfmt::stdout_color()));
crates\spt\src\cli.rs:1419:            crate::helpfmt::render(
crates\spt\src\cli.rs:2116:fn render_connection_lines(
crates\spt\src\cli.rs:2188:            crate::helpfmt::render(
crates\spt\src\cli.rs:2471:fn render_applied_message(version: u64, product_version: &str) -> String {
crates\spt\src\cli.rs:3471:fn render_local_section(perches: &[roster::PerchEntry]) -> String {
crates\spt\src\cli.rs:3528:fn render_self_pin(
crates\spt\src\cli.rs:4172:fn render_subnet_status(rows: &[SubnetStatusRow], daemon_running: bool, hints: bool) -> String {
crates\spt\src\cli.rs:4430:fn render_node_rows(subnet: &str, rows: &[(NodeRow, bool)]) -> String {
crates\spt\src\cli.rs:4572:fn render_subnets_joined(rows: &[(String, &'static str)]) -> String {
crates\spt\src\cli.rs:4705:        print!("{}", crate::helpfmt::render(&out, crate::helpfmt::stdout_color()));
crates\spt\src\cli.rs:4803:    print!("{}", crate::helpfmt::render(&out, crate::helpfmt::stdout_color()));
crates\spt\src\cli.rs:7399:fn render_link_err(verb: &str, shell_ref: &str, owner: &str, e: spt_daemon::ShellLinkError) -> i32 {
crates\spt\src\cli.rs:11206:        // sites route it through helpfmt::render — so STRIP mode (a piped adapter)
crates\spt\src\cli.rs:11210:        let stripped = crate::helpfmt::render(&empty, false);
crates\spt\src\picker\view.rs:58:pub fn render(model: &PickerModel, f: &mut Frame) {
crates\spt\src\picker\view.rs:87:fn render_kind(model: &PickerModel, f: &mut Frame, area: Rect) {
crates\spt\src\picker\view.rs:97:fn render_create_adapter(model: &PickerModel, f: &mut Frame, area: Rect) {
crates\spt\src\picker\view.rs:127:fn render_create_id(model: &PickerModel, f: &mut Frame, area: Rect) {
crates\spt\src\picker\view.rs:197:fn render_pick(model: &PickerModel, f: &mut Frame, area: Rect) {
crates\spt\src\picker\view.rs:328:fn render_confirm(model: &PickerModel, f: &mut Frame, area: Rect) {
crates\spt\src\picker\view.rs:392:fn render_resume(model: &PickerModel, f: &mut Frame, area: Rect) {
crates\spt\src\picker\view.rs:422:    fn rendered(model: &PickerModel) -> String {
crates\spt\src\picker\shortcut.rs:80:pub fn render_script(
crates\spt-daemon\src\notif.rs:327:pub fn render_via_shell_notif_templates(owlery: &Path, endpoint: &str, row: &NotifRow) -> usize {
crates\spt-msg\src\emit.rs:48:pub fn render_event_lines(from: &str, body: &str) -> Vec<String> {
crates\spt-msg\src\emit.rs:59:pub fn render_event_whole(from: &str, body: &str) -> String {
crates\spt-store\src\contextmark.rs:76:    pub fn render(&self) -> String {
crates\spt-store\src\contextmark.rs:166:    pub fn render(&self) -> String {
crates\spt-daemon\src\shelldisc.rs:193:fn render_context(owner: &str, d: &ShellDiscovery) -> Option<String> {
crates\spt-daemon\tests\attach.rs:199:fn render_until(


6160	fn cmd_adapter_update(adapters: &std::path::Path, name: Option<&str>) -> i32 {
6161	    use spt_runtime::manifest::UpdateAvenue;
6162	    use spt_runtime::registry;
6163
6164	    let targets: Vec<_> = registry::registered(adapters)
6165	        .into_iter()
6166	        .filter(|(r, _)| name.is_none_or(|n| r.name == n))
6167	        .filter(|(_, m)| {
6168	            m.update
6169	                .as_ref()
6170	                .is_some_and(|u| u.avenue == UpdateAvenue::GhRelease)
6171	        })
6172	        .collect();
6173
6174	    if let Some(n) = name {
6175	        if targets.is_empty() {
6176	            eprintln!(
6177	                "ADAPTER_UPDATE_SKIP:{n}: not a registered gh_release adapter (no \
6178	                 [update] avenue = \"gh_release\")"
6179	            );
6180	            return 1;
6181	        }
6182	    } else if targets.is_empty() {
6183	        eprintln!("no gh_release adapters registered");
6184	        return 0;
6185	    }
6186
6187	    let mut failed = false;
6188	    for (record, manifest) in &targets {
6189	        let update = manifest.update.as_ref().expect("filtered to gh_release");
6190	        let repo = match update.repo.as_deref() {
6191	            Some(r) => r,
6192	            None => {
6193	                // Validation requires `repo`; a record reaching here without one
6194	                // is corrupt — never silently fetch from nowhere.
6195	                eprintln!("ADAPTER_UPDATE_FAIL:{}: gh_release missing repo", record.name);
6196	                failed = true;
6197	                continue;
6198	            }
6199	        };
6200	        let asset = update.asset.as_deref().unwrap_or("adapter.spt");
6201	        let installed = manifest.adapter.version.as_str();
6202	        // Resolve the fetch transport once (probes `gh` at most once for `auto`).
6203	        // [impl->REQ-ADAPTER-GH-TRANSPORT]
6204	        let transport = resolve_transport(update.transport, gh_available);
6205
6206	        // 1. Latest release version off the GitHub API (no download yet).
6207	        let latest = match gh_latest_release_version(repo, transport) {
6208	            Ok(v) => v,
6209	            Err(e) => {
6210	                eprintln!("ADAPTER_UPDATE_FAIL:{}: latest release: {e}", record.name);
6211	                failed = true;
6212	                continue;
6213	            }
6214	        };
6215
6216	        // 2. Version-compare decision: only newer ripples.
6217	        if !version_is_newer(&latest, installed) {
6218	            eprintln!(
6219	                "ADAPTER_UPDATE_UPTODATE:{}: installed {installed}, latest {latest}",
6220	                record.name
6221	            );
6222	            continue;
6223	        }
6224	        eprintln!(
6225	            "ADAPTER_UPDATE:{}: {installed} -> {latest} (fetching {asset} from {repo})",
6226	            record.name
6227	        );
6228
6229	        // 3. Fetch the release .spt (reuse the REQ-INSTALL-9 primitive). The
6230	        //    fetched archive lands under adapters/_github/<safe> as raw bytes we
6231	        //    can verify BEFORE it is extracted+registered.
6232	        let staged = match stage_gh_release_archive(adapters, repo, &latest, asset, transport) {
6233	            Ok(s) => s,
6234	            Err(e) => {
6235	                eprintln!("ADAPTER_UPDATE_FAIL:{}: fetch: {e}", record.name);
6236	                failed = true;
6237	                continue;
6238	            }
6239	        };
6240
6241	        // 4. Optional fail-closed verify: if the adapter declared a signing_key,
6242	        //    the fetched .spt MUST verify against a detached signature published
6243	        //    beside it, or the update is REFUSED (bytes never extracted). With no
6244	        //    key, proceed on HTTPS + GitHub trust (the --release acquisition model).
6245	        if let Some(key_hex) = update.signing_key.as_deref() {
6246	            if let Err(e) =
6247	                verify_staged_archive(adapters, repo, &latest, asset, &staged, key_hex, transport)
6248	            {
6249	                let _ = std::fs::remove_file(&staged);
6250	                eprintln!(
6251	                    "ADAPTER_UPDATE_REJECTED:{}: signature verification failed: {e}",
6252	                    record.name
6253	                );
6254	                failed = true;
6255	                continue;
6256	            }
6257	            eprintln!("ADAPTER_UPDATE_VERIFIED:{}: signature OK", record.name);
6258	        } else {
6259	            eprintln!(
6260	                "ADAPTER_UPDATE_UNSIGNED:{}: no signing_key — trusting HTTPS + GitHub",
6261	                record.name
6262	            );
6263	        }
6264
6265	        // 5. CRC-swap + re-register (the verified/trusted bytes become the new
6266	        //    install in the durable _github home; pointer-mode re-reads it live).
6267	        //    W3d: a CONTENT-HASH swap (only changed files, never-strand) replaces
6268	        //    the legacy remove_dir_all + blanket-extract — which on Windows fails
6269	        //    the WHOLE update the moment one install-dir binary is still running.
6270	        let safe = repo.replace(['/', '\\'], "-");
6271	        let dest = adapters.join("_github").join(&safe);
6272	        if let Err(e) = std::fs::create_dir_all(&dest) {
6273	            let _ = std::fs::remove_file(&staged);
6274	            eprintln!("ADAPTER_UPDATE_FAIL:{}: create dest: {e}", record.name);
6275	            failed = true;
6276	            continue;
6277	        }
6278	        // W3d routing: a live endpoint on this adapter may hold a resident
6279	        // translation binary that locks the install dir → hand the apply to the

fn cmd_adapter_update(adapters: &std::path::Path, name: Option<&str>) -> i32 {
    use spt_runtime::manifest::UpdateAvenue;
    use spt_runtime::registry;

    let targets: Vec<_> = registry::registered(adapters)
        .into_iter()
        .filter(|(r, _)| name.is_none_or(|n| r.name == n))
        .filter(|(_, m)| {
            m.update
                .as_ref()
                .is_some_and(|u| u.avenue == UpdateAvenue::GhRelease)
        })
        .collect();

    if let Some(n) = name {
        if targets.is_empty() {
            eprintln!(
                "ADAPTER_UPDATE_SKIP:{n}: not a registered gh_release adapter (no \
                 [update] avenue = \"gh_release\")"
            );
            return 1;
        }
    } else if targets.is_empty() {
        eprintln!("no gh_release adapters registered");
        return 0;
    }

    let mut failed = false;
    for (record, manifest) in &targets {
        let update = manifest.update.as_ref().expect("filtered to gh_release");
        let repo = match update.repo.as_deref() {
            Some(r) => r,
            None => {
                // Validation requires `repo`; a record reaching here without one
                // is corrupt — never silently fetch from nowhere.
                eprintln!("ADAPTER_UPDATE_FAIL:{}: gh_release missing repo", record.name);
                failed = true;
                continue;
            }
        };
        let asset = update.asset.as_deref().unwrap_or("adapter.spt");
        let installed = manifest.adapter.version.as_str();
        // Resolve the fetch transport once (probes `gh` at most once for `auto`).
        // [impl->REQ-ADAPTER-GH-TRANSPORT]
        let transport = resolve_transport(update.transport, gh_available);

        // 1. Latest release version off the GitHub API (no download yet).
        let latest = match gh_latest_release_version(repo, transport) {
            Ok(v) => v,
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: latest release: {e}", record.name);
                failed = true;
                continue;
            }
        };

        // 2. Version-compare decision: only newer ripples.
        if !version_is_newer(&latest, installed) {
            eprintln!(
                "ADAPTER_UPDATE_UPTODATE:{}: installed {installed}, latest {latest}",
                record.name
            );
            continue;
        }
        eprintln!(
            "ADAPTER_UPDATE:{}: {installed} -> {latest} (fetching {asset} from {repo})",
            record.name
        );

        // 3. Fetch the release .spt (reuse the REQ-INSTALL-9 primitive). The
        //    fetched archive lands under adapters/_github/<safe> as raw bytes we
        //    can verify BEFORE it is extracted+registered.
        let staged = match stage_gh_release_archive(adapters, repo, &latest, asset, transport) {
            Ok(s) => s,
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: fetch: {e}", record.name);
                failed = true;
                continue;
            }
        };

        // 4. Optional fail-closed verify: if the adapter declared a signing_key,
        //    the fetched .spt MUST verify against a detached signature published
        //    beside it, or the update is REFUSED (bytes never extracted). With no
        //    key, proceed on HTTPS + GitHub trust (the --release acquisition model).
        if let Some(key_hex) = update.signing_key.as_deref() {
            if let Err(e) =
                verify_staged_archive(adapters, repo, &latest, asset, &staged, key_hex, transport)
            {
                let _ = std::fs::remove_file(&staged);
                eprintln!(
                    "ADAPTER_UPDATE_REJECTED:{}: signature verification failed: {e}",
                    record.name
                );
                failed = true;
                continue;
            }
            eprintln!("ADAPTER_UPDATE_VERIFIED:{}: signature OK", record.name);
        } else {
            eprintln!(
                "ADAPTER_UPDATE_UNSIGNED:{}: no signing_key — trusting HTTPS + GitHub",
                record.name
            );
        }

        // 5. CRC-swap + re-register (the verified/trusted bytes become the new
        //    install in the durable _github home; pointer-mode re-reads it live).
        //    W3d: a CONTENT-HASH swap (only changed files, never-strand) replaces
        //    the legacy remove_dir_all + blanket-extract — which on Windows fails
        //    the WHOLE update the moment one install-dir binary is still running.
        let safe = repo.replace(['/', '\\'], "-");
        let dest = adapters.join("_github").join(&safe);
        if let Err(e) = std::fs::create_dir_all(&dest) {
            let _ = std::fs::remove_file(&staged);
            eprintln!("ADAPTER_UPDATE_FAIL:{}: create dest: {e}", record.name);
            failed = true;
            continue;
        }
        // W3d routing: a live endpoint on this adapter may hold a resident
        // translation binary that locks the install dir → hand the apply to the


6280	        // daemon (stop→swap→restart). No live endpoint → swap directly.
6281	        let applied = if adapter_has_live_endpoint(&record.name) {
6282	            eprintln!(
6283	                "ADAPTER_UPDATE_LIVE:{}: live endpoint(s) — daemon-coordinated apply",
6284	                record.name
6285	            );
6286	            apply_release_via_daemon(&staged, &dest, &record.name)
6287	        } else {
6288	            apply_release_crc_swap(&staged, &dest)
6289	        };
6290	        let _ = std::fs::remove_file(&staged);
6291	        if let Err(e) = applied {
6292	            eprintln!("ADAPTER_UPDATE_FAIL:{}: apply: {e}", record.name);
6293	            failed = true;
6294	            continue;
6295	        }
6296	        match registry::register(adapters, &dest, now_ms()) {
6297	            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
6298	            Err(e) => {
6299	                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);
6300	                failed = true;
6301	            }
6302	        }
6303	    }
6304
6305	    i32::from(failed)
6306	}
6307
6308	/// `repo`'s latest release `tag_name`, leading `v` stripped (e.g. `v0.2.0` →
6309	/// `0.2.0`). HTTPS reads the public GitHub API; `gh` runs `gh api …/releases/latest
6310	/// --jq .tag_name` (the private-repo path). (REQ-UPD-9, REQ-ADAPTER-GH-TRANSPORT)
6311	// [impl->REQ-UPD-9]
6312	// [impl->REQ-ADAPTER-GH-TRANSPORT]
6313	fn gh_latest_release_version(
6314	    repo: &str,
6315	    transport: EffectiveTransport,
6316	) -> Result<String, String> {
6317	    let tag = match transport {
6318	        EffectiveTransport::Https => {
6319	            let url = format!("https://api.github.com/repos/{repo}/releases/latest");
6320	            let body = http_get_text(&release_http_client()?, &url)?;
6321	            let v: serde_json::Value = serde_json::from_str(&body).map_err(|e| e.to_string())?;
6322	            v.get("tag_name")
6323	                .and_then(|t| t.as_str())
6324	                .ok_or("release JSON had no tag_name")?
6325	                .to_string()
6326	        }
6327	        EffectiveTransport::Gh => {
6328	            let (template, keys) = gh_version_command(repo);
6329	            match spt_runtime::run_bounded_command(
6330	                &template,
6331	                &keys,
6332	                Duration::from_secs(60),
6333	                None,
6334	            ) {
6335	                Ok(out) if out.success() => out.stdout.trim().to_string(),
6336	                Ok(out) => return Err(format!("gh api exit {:?}", out.status_code)),
6337	                Err(e) => return Err(e.to_string()),
6338	            }
6339	        }
6340	    };
6341	    Ok(tag.strip_prefix('v').unwrap_or(&tag).to_string())
6342	}
6343
6344	/// Compare two dotted-numeric versions (`a` newer than `b`?). Hand-rolled rather
6345	/// than pulling a semver dependency: splits on `.`, compares each numeric
6346	/// component, treats a missing component as 0, and a non-numeric component as 0
6347	/// (best-effort — a release tag spt-core can't order is treated as not-newer, so
6348	/// an unparseable tag never triggers an unrequested update). (REQ-UPD-9)
6349	// [impl->REQ-UPD-9]
6350	fn version_is_newer(a: &str, b: &str) -> bool {
6351	    let part = |s: &str| -> Vec<u64> {
6352	        s.split('.')
6353	            .map(|c| c.trim().parse::<u64>().unwrap_or(0))
6354	            .collect()
6355	    };
6356	    let (av, bv) = (part(a), part(b));
6357	    let n = av.len().max(bv.len());
6358	    for i in 0..n {
6359	        let x = av.get(i).copied().unwrap_or(0);
6360	        let y = bv.get(i).copied().unwrap_or(0);
6361	        if x != y {
6362	            return x > y;
6363	        }
6364	    }
6365	    false
6366	}
6367
6368	/// Fetch a release `.spt` asset's raw bytes to a staging file under the durable
6369	/// `_github` home (NOT yet extracted/registered), so they can be verified before
6370	/// trust. Returns the staged archive path. `transport` selects HTTPS vs the `gh`
6371	/// CLI (private-repo path). (REQ-UPD-9, REQ-ADAPTER-GH-TRANSPORT)
6372	// [impl->REQ-UPD-9]
6373	// [impl->REQ-ADAPTER-GH-TRANSPORT]
6374	fn stage_gh_release_archive(
6375	    adapters: &std::path::Path,
6376	    repo: &str,
6377	    tag: &str,
6378	    asset: &str,
6379	    transport: EffectiveTransport,
6380	) -> Result<std::path::PathBuf, String> {
6381	    let safe = repo.replace(['/', '\\'], "-");
6382	    let github = adapters.join("_github");
6383	    std::fs::create_dir_all(&github).map_err(|e| e.to_string())?;
6384	    let tag_v = format!("v{tag}");
6385	    let bytes = fetch_release_asset_bytes(repo, Some(&tag_v), asset, transport, &github)?;
6386	    let staged = github.join(format!("{safe}.update.spt"));
6387	    std::fs::write(&staged, &bytes).map_err(|e| e.to_string())?;
6388	    Ok(staged)
6389	}
6390
6391	/// Fail-closed verification of a staged `.spt` against the adapter's declared
6392	/// Ed25519 `signing_key`: fetch the detached signature published beside the asset
6393	/// (`<asset>.sig`, lowercase-hex) and verify the staged bytes against it. The
6394	/// signature rides the SAME `transport` as the archive, so a private-repo `.spt`
6395	/// stays verifiable (its `.sig` is fetched via `gh` too). Any fetch/parse/
6396	/// signature failure is an error (the caller refuses the update).
6397	/// (REQ-UPD-9, REQ-ADAPTER-GH-TRANSPORT)
6398	// [impl->REQ-UPD-9]
6399	// [impl->REQ-ADAPTER-GH-TRANSPORT]
6400	fn verify_staged_archive(
6401	    adapters: &std::path::Path,
6402	    repo: &str,
6403	    tag: &str,
6404	    asset: &str,
6405	    staged: &std::path::Path,
6406	    key_hex: &str,
6407	    transport: EffectiveTransport,
6408	) -> Result<(), String> {
6409	    let key = spt_daemon::parse_verifying_key(key_hex).map_err(|e| e.to_string())?;

        // daemon (stop→swap→restart). No live endpoint → swap directly.
        let applied = if adapter_has_live_endpoint(&record.name) {
            eprintln!(
                "ADAPTER_UPDATE_LIVE:{}: live endpoint(s) — daemon-coordinated apply",
                record.name
            );
            apply_release_via_daemon(&staged, &dest, &record.name)
        } else {
            apply_release_crc_swap(&staged, &dest)
        };
        let _ = std::fs::remove_file(&staged);
        if let Err(e) = applied {
            eprintln!("ADAPTER_UPDATE_FAIL:{}: apply: {e}", record.name);
            failed = true;
            continue;
        }
        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);
                failed = true;
            }
        }
    }

    i32::from(failed)
}

/// `repo`'s latest release `tag_name`, leading `v` stripped (e.g. `v0.2.0` →
/// `0.2.0`). HTTPS reads the public GitHub API; `gh` runs `gh api …/releases/latest
/// --jq .tag_name` (the private-repo path). (REQ-UPD-9, REQ-ADAPTER-GH-TRANSPORT)
// [impl->REQ-UPD-9]
// [impl->REQ-ADAPTER-GH-TRANSPORT]
fn gh_latest_release_version(
    repo: &str,
    transport: EffectiveTransport,
) -> Result<String, String> {
    let tag = match transport {
        EffectiveTransport::Https => {
            let url = format!("https://api.github.com/repos/{repo}/releases/latest");
            let body = http_get_text(&release_http_client()?, &url)?;
            let v: serde_json::Value = serde_json::from_str(&body).map_err(|e| e.to_string())?;
            v.get("tag_name")
                .and_then(|t| t.as_str())
                .ok_or("release JSON had no tag_name")?
                .to_string()
        }
        EffectiveTransport::Gh => {
            let (template, keys) = gh_version_command(repo);
            match spt_runtime::run_bounded_command(
                &template,
                &keys,
                Duration::from_secs(60),
                None,
            ) {
                Ok(out) if out.success() => out.stdout.trim().to_string(),
                Ok(out) => return Err(format!("gh api exit {:?}", out.status_code)),
                Err(e) => return Err(e.to_string()),
            }
        }
    };
    Ok(tag.strip_prefix('v').unwrap_or(&tag).to_string())
}

/// Compare two dotted-numeric versions (`a` newer than `b`?). Hand-rolled rather
/// than pulling a semver dependency: splits on `.`, compares each numeric
/// component, treats a missing component as 0, and a non-numeric component as 0
/// (best-effort — a release tag spt-core can't order is treated as not-newer, so
/// an unparseable tag never triggers an unrequested update). (REQ-UPD-9)
// [impl->REQ-UPD-9]
fn version_is_newer(a: &str, b: &str) -> bool {
    let part = |s: &str| -> Vec<u64> {
        s.split('.')
            .map(|c| c.trim().parse::<u64>().unwrap_or(0))
            .collect()
    };
    let (av, bv) = (part(a), part(b));
    let n = av.len().max(bv.len());
    for i in 0..n {
        let x = av.get(i).copied().unwrap_or(0);
        let y = bv.get(i).copied().unwrap_or(0);
        if x != y {
            return x > y;
        }
    }
    false
}

/// Fetch a release `.spt` asset's raw bytes to a staging file under the durable
/// `_github` home (NOT yet extracted/registered), so they can be verified before
/// trust. Returns the staged archive path. `transport` selects HTTPS vs the `gh`
/// CLI (private-repo path). (REQ-UPD-9, REQ-ADAPTER-GH-TRANSPORT)
// [impl->REQ-UPD-9]
// [impl->REQ-ADAPTER-GH-TRANSPORT]
fn stage_gh_release_archive(
    adapters: &std::path::Path,
    repo: &str,
    tag: &str,
    asset: &str,
    transport: EffectiveTransport,
) -> Result<std::path::PathBuf, String> {
    let safe = repo.replace(['/', '\\'], "-");
    let github = adapters.join("_github");
    std::fs::create_dir_all(&github).map_err(|e| e.to_string())?;
    let tag_v = format!("v{tag}");
    let bytes = fetch_release_asset_bytes(repo, Some(&tag_v), asset, transport, &github)?;
    let staged = github.join(format!("{safe}.update.spt"));
    std::fs::write(&staged, &bytes).map_err(|e| e.to_string())?;
    Ok(staged)
}

/// Fail-closed verification of a staged `.spt` against the adapter's declared
/// Ed25519 `signing_key`: fetch the detached signature published beside the asset
/// (`<asset>.sig`, lowercase-hex) and verify the staged bytes against it. The
/// signature rides the SAME `transport` as the archive, so a private-repo `.spt`
/// stays verifiable (its `.sig` is fetched via `gh` too). Any fetch/parse/
/// signature failure is an error (the caller refuses the update).
/// (REQ-UPD-9, REQ-ADAPTER-GH-TRANSPORT)
// [impl->REQ-UPD-9]
// [impl->REQ-ADAPTER-GH-TRANSPORT]
fn verify_staged_archive(
    adapters: &std::path::Path,
    repo: &str,
    tag: &str,
    asset: &str,
    staged: &std::path::Path,
    key_hex: &str,
    transport: EffectiveTransport,
) -> Result<(), String> {
    let key = spt_daemon::parse_verifying_key(key_hex).map_err(|e| e.to_string())?;


crates\spt-runtime\src\registry.rs:289:pub fn register(
crates\spt-runtime\src\registry.rs-290-    adapters_dir: &Path,
crates\spt-runtime\src\registry.rs-291-    source: &Path,
crates\spt-runtime\src\registry.rs-292-    now_ms: u64,
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-293-) -> Result<(AdapterRecord, Manifest), RegistryError> {
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-294-    let manifest_file = source_manifest_file(source);
crates\spt-runtime\src\registry.rs-295-    let toml_src = std::fs::read_to_string(&manifest_file)?;
--
crates\spt-runtime\src\registry.rs:435:pub fn registered(adapters_dir: &Path) -> Vec<(AdapterRecord, Manifest)> {
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-436-    all_records(adapters_dir)
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-437-        .into_iter()
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-438-        .filter(|r| r.active)
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-439-        .filter_map(|r| match load_manifest(adapters_dir, &r) {
C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs-440-            Ok(m) => Some((r, m)),
crates\spt-runtime\src\registry.rs-441-            \ Skip-diagnostics, never a silent drop (REQ-HAZARD-DEFERRED-MANIFEST):


2471	fn render_applied_message(version: u64, product_version: &str) -> String {
2472	    let head = if product_version.is_empty() {
2473	        format!("Updated spt-core (release {version}).")
2474	    } else {
2475	        format!("Updated spt-core to v{product_version}.")
2476	    };
2477	    format!("{head}\nChangelog: {RELEASES_URL}")
2478	}
2479
2480	/// `spt update apply` — the explicit ack named by the update-consent notif.
2481	// [impl->REQ-UPD-4]
2482	// [impl->REQ-UPD-3]
2483	fn cmd_update_apply() -> i32 {
2484	    if let Err(code) = ensure_daemon_announced() {
2485	        return code;
2486	    }
2487	    let exe = match std::env::current_exe() {
2488	        Ok(p) => p,
2489	        Err(e) => {
2490	            eprintln!("CURRENT_EXE_FAIL: {e}");

fn render_applied_message(version: u64, product_version: &str) -> String {
    let head = if product_version.is_empty() {
        format!("Updated spt-core (release {version}).")
    } else {
        format!("Updated spt-core to v{product_version}.")
    };
    format!("{head}\nChangelog: {RELEASES_URL}")
}

/// `spt update apply` — the explicit ack named by the update-consent notif.
// [impl->REQ-UPD-4]
// [impl->REQ-UPD-3]
fn cmd_update_apply() -> i32 {
    if let Err(code) = ensure_daemon_announced() {
        return code;
    }
    let exe = match std::env::current_exe() {
        Ok(p) => p,
        Err(e) => {
            eprintln!("CURRENT_EXE_FAIL: {e}");


60	            return true;
61	        }
62	    }
63	    if no_color {
64	        return false;
65	    }
66	    if clicolor == Some("0") {
67	        return false;
68	    }
69	    stdout_tty
70	}
71
72	/// Rewrite the inline Markdown markers in `input` to terminal styling, or strip
73	/// the markers when `color` is false. Pure. Guarantees:
74	/// - `**bold**` → bold (or bare text); `` `code` `` → cyan (or bare text);
75	///   `[text](url)` → `text`.
76	/// - A marker run may cross a clap SOFT wrap (`\n` + continuation indent) so a
77	///   span `wrap_help` split mid-text still renders, but NEVER a hard/paragraph
78	///   break — a `**` with no partner before the block ends stays literal prose.
79	/// - An unmatched or empty marker (`**`, `` ` ``, `****`) is left verbatim.
80	/// - Pre-existing ANSI CSI escape sequences (`\x1b[…m`) are copied through
81	///   byte-for-byte and never re-interpreted (so their `[` can't seed a link).
82	// [impl->REQ-CLI-HELP-MARKDOWN]
83	pub fn render(input: &str, color: bool) -> String {
84	    let b = input.as_bytes();
85	    let mut out = String::with_capacity(input.len() + 16);
86	    let mut i = 0usize;
87	    while i < b.len() {
88	        // ANSI CSI escape: copy `\x1b[ … <final-byte>` through untouched.
89	        if b[i] == 0x1b {
90	            let start = i;
91	            i += 1;
92	            if i < b.len() && b[i] == b'[' {
93	                i += 1;
94	                while i < b.len() && !b[i].is_ascii_alphabetic() {
95	                    i += 1;
96	                }
97	                if i < b.len() {
98	                    i += 1; // include the final letter terminator
99	                }
100	            }
101	            out.push_str(&input[start..i]);
102	            continue;
103	        }
104
105	        // ATX header at line start: `#{1,6} ` → strip the marker; in color mode
106	        // bold the header line (command output like `spt how-to` opens with a
107	        // `# how-to: …` header). The header text still gets inline rendering.
108	        if (out.is_empty() || out.ends_with('\n')) && b[i] == b'#' {
109	            let mut h = i;
110	            while h < b.len() && b[h] == b'#' {
111	                h += 1;
112	            }
113	            if h - i <= 6 && h < b.len() && (b[h] == b' ' || b[h] == b'\t') {
114	                let mut t = h;
115	                while t < b.len() && (b[t] == b' ' || b[t] == b'\t') {
116	                    t += 1;
117	                }
118	                let line_end = input[t..].find('\n').map(|p| t + p).unwrap_or(b.len());
119	                let inner = render(&input[t..line_end], color);

            return true;
        }
    }
    if no_color {
        return false;
    }
    if clicolor == Some("0") {
        return false;
    }
    stdout_tty
}

/// Rewrite the inline Markdown markers in `input` to terminal styling, or strip
/// the markers when `color` is false. Pure. Guarantees:
/// - `**bold**` → bold (or bare text); `` `code` `` → cyan (or bare text);
///   `[text](url)` → `text`.
/// - A marker run may cross a clap SOFT wrap (`\n` + continuation indent) so a
///   span `wrap_help` split mid-text still renders, but NEVER a hard/paragraph
///   break — a `**` with no partner before the block ends stays literal prose.
/// - An unmatched or empty marker (`**`, `` ` ``, `****`) is left verbatim.
/// - Pre-existing ANSI CSI escape sequences (`\x1b[…m`) are copied through
///   byte-for-byte and never re-interpreted (so their `[` can't seed a link).
// [impl->REQ-CLI-HELP-MARKDOWN]
pub fn render(input: &str, color: bool) -> String {
    let b = input.as_bytes();
    let mut out = String::with_capacity(input.len() + 16);
    let mut i = 0usize;
    while i < b.len() {
        // ANSI CSI escape: copy `\x1b[ … <final-byte>` through untouched.
        if b[i] == 0x1b {
            let start = i;
            i += 1;
            if i < b.len() && b[i] == b'[' {
                i += 1;
                while i < b.len() && !b[i].is_ascii_alphabetic() {
                    i += 1;
                }
                if i < b.len() {
                    i += 1; // include the final letter terminator
                }
            }
            out.push_str(&input[start..i]);
            continue;
        }

        // ATX header at line start: `#{1,6} ` → strip the marker; in color mode
        // bold the header line (command output like `spt how-to` opens with a
        // `# how-to: …` header). The header text still gets inline rendering.
        if (out.is_empty() || out.ends_with('\n')) && b[i] == b'#' {
            let mut h = i;
            while h < b.len() && b[h] == b'#' {
                h += 1;
            }
            if h - i <= 6 && h < b.len() && (b[h] == b' ' || b[h] == b'\t') {
                let mut t = h;
                while t < b.len() && (b[t] == b' ' || b[t] == b'\t') {
                    t += 1;
                }
                let line_end = input[t..].find('\n').map(|p| t + p).unwrap_or(b.len());
                let inner = render(&input[t..line_end], color);


Now make edits. 1) Add `message` field to `Update` struct (after `uninstall`):


Edit

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// `gh_release` avenue: the fetch **transport**

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// Optional plain (multi-line) human notice surfaced to stdout — markdown-
    /// rendered (the helpfmt prose path) — **only when `spt adapter update`
    /// actually APPLIES an update** (version changed), never on a no-op. Read
    /// from the newly-installed manifest; avenue-agnostic. No `{key}`
    /// substitution. Use: an adapter telling the operator a post-update action
    /// (e.g. "run `/reload-plugins` in any ongoing sessions").
    // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    /// `gh_release` avenue: the fetch **transport**


<tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error>

Error: File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.


483	    pub version_check: bool,
484	    /// Optional inverse of install — run by `spt adapter remove` once the adapter
485	    /// is quiesced (the mirror of `spt adapter add`, which reuses this section as
486	    /// the install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in
487	    /// M2a; conducted with adapter-registration later.)
488	    #[serde(default, skip_serializing_if = "Option::is_none")]
489	    pub uninstall: Option<String>,
490	}
491
492	#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
493	#[serde(rename_all = "snake_case")]
494	pub enum UpdateAvenue {
495	    /// Delegate to the adapter's own updater (e.g. `claude plugin update`).
496	    Delegated,
497	    /// spt-core pulls files from a repo.
498	    FilePull,
499	    /// spt-core ships updates from the adapter's own **GitHub releases**
500	    /// (REQ-UPD-9): compare the repo's latest release version against the

    pub version_check: bool,
    /// Optional inverse of install — run by `spt adapter remove` once the adapter
    /// is quiesced (the mirror of `spt adapter add`, which reuses this section as
    /// the install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in
    /// M2a; conducted with adapter-registration later.)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum UpdateAvenue {
    /// Delegate to the adapter's own updater (e.g. `claude plugin update`).
    Delegated,
    /// spt-core pulls files from a repo.
    FilePull,
    /// spt-core ships updates from the adapter's own **GitHub releases**
    /// (REQ-UPD-9): compare the repo's latest release version against the


File changed — `transport` field gone? Re-check struct.


443	pub struct Update {
444	    pub avenue: UpdateAvenue,
445	    /// `delegated` avenue: the command spt-core delegates to.
446	    #[serde(default, skip_serializing_if = "Option::is_none")]
447	    pub command: Option<String>,
448	    /// `file_pull` / `gh_release` avenue: source repo. For `gh_release` this is
449	    /// the `user/repo` whose **GitHub releases** the adapter ships updates from.
450	    #[serde(default, skip_serializing_if = "Option::is_none")]
451	    pub repo: Option<String>,
452	    /// `file_pull` avenue: path selector.
453	    #[serde(default, skip_serializing_if = "Option::is_none")]
454	    pub path_regex: Option<String>,
455	    /// `gh_release` avenue: the release **asset** name to fetch (the adapter
456	    /// `.spt` archive). Absent ⇒ the default `adapter.spt`, matching the
457	    /// `spt adapter add --release` acquisition primitive (REQ-INSTALL-9). Not
458	    /// applicable to `delegated` / `file_pull`.
459	    // [impl->REQ-UPD-9]
460	    #[serde(default, skip_serializing_if = "Option::is_none")]
461	    pub asset: Option<String>,
462	    /// The adapter's Ed25519 **content-signing public key** (64 hex chars / 32
463	    /// bytes). spt-core verifies a pulled payload against this per-adapter key
464	    /// before applying it (REQ-UPD-5 adapter content signing, ADR-0004 §D) — the
465	    /// adapter author signs their own releases; spt-core's release key stays
466	    /// scoped to spt-core. **Required for `file_pull`** (there are bytes to
467	    /// verify); **optional for `gh_release`** (absent ⇒ HTTPS+GitHub
468	    /// first-acquisition trust, the same trust `spt adapter add --release` and
469	    /// the installer first-fetch place; present ⇒ the fetched `.spt` is verified
470	    /// fail-closed against this key, REQ-UPD-9); not applicable to `delegated`
471	    /// (opaque updater).
472	    #[serde(default, skip_serializing_if = "Option::is_none")]
473	    pub signing_key: Option<String>,
474	    /// `delegated` avenue: the adapter attests its own updater verifies the
475	    /// content it installs (e.g. `claude.exe plugin update` checks its own
476	    /// signatures). spt-core cannot see a delegated updater's bytes, so it
477	    /// delegates the trust **only** when this is set; an unattested delegated
478	    /// update is skipped as unverifiable (REQ-UPD-5).
479	    #[serde(default)]
480	    pub self_verifies: bool,
481	    /// Verify spt-core satisfies `min_spt_core_version` before/after.
482	    #[serde(default)]
483	    pub version_check: bool,
484	    /// Optional inverse of install — run by `spt adapter remove` once the adapter
485	    /// is quiesced (the mirror of `spt adapter add`, which reuses this section as
486	    /// the install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in
487	    /// M2a; conducted with adapter-registration later.)
488	    #[serde(default, skip_serializing_if = "Option::is_none")]
489	    pub uninstall: Option<String>,
490	}
491
492	#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]

pub struct Update {
    pub avenue: UpdateAvenue,
    /// `delegated` avenue: the command spt-core delegates to.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub command: Option<String>,
    /// `file_pull` / `gh_release` avenue: source repo. For `gh_release` this is
    /// the `user/repo` whose **GitHub releases** the adapter ships updates from.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub repo: Option<String>,
    /// `file_pull` avenue: path selector.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub path_regex: Option<String>,
    /// `gh_release` avenue: the release **asset** name to fetch (the adapter
    /// `.spt` archive). Absent ⇒ the default `adapter.spt`, matching the
    /// `spt adapter add --release` acquisition primitive (REQ-INSTALL-9). Not
    /// applicable to `delegated` / `file_pull`.
    // [impl->REQ-UPD-9]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub asset: Option<String>,
    /// The adapter's Ed25519 **content-signing public key** (64 hex chars / 32
    /// bytes). spt-core verifies a pulled payload against this per-adapter key
    /// before applying it (REQ-UPD-5 adapter content signing, ADR-0004 §D) — the
    /// adapter author signs their own releases; spt-core's release key stays
    /// scoped to spt-core. **Required for `file_pull`** (there are bytes to
    /// verify); **optional for `gh_release`** (absent ⇒ HTTPS+GitHub
    /// first-acquisition trust, the same trust `spt adapter add --release` and
    /// the installer first-fetch place; present ⇒ the fetched `.spt` is verified
    /// fail-closed against this key, REQ-UPD-9); not applicable to `delegated`
    /// (opaque updater).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signing_key: Option<String>,
    /// `delegated` avenue: the adapter attests its own updater verifies the
    /// content it installs (e.g. `claude.exe plugin update` checks its own
    /// signatures). spt-core cannot see a delegated updater's bytes, so it
    /// delegates the trust **only** when this is set; an unattested delegated
    /// update is skipped as unverifiable (REQ-UPD-5).
    #[serde(default)]
    pub self_verifies: bool,
    /// Verify spt-core satisfies `min_spt_core_version` before/after.
    #[serde(default)]
    pub version_check: bool,
    /// Optional inverse of install — run by `spt adapter remove` once the adapter
    /// is quiesced (the mirror of `spt adapter add`, which reuses this section as
    /// the install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in
    /// M2a; conducted with adapter-registration later.)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]


Transport field vanished between reads. Check what changed.


Bash

git status --short && echo "---DIFF---" && git diff -- crates/spt-runtime/src/manifest.rs | head -60

Check git diff on manifest.rs


---DIFF---


git clean but field gone, yet cli.rs uses `update.transport`. Check HEAD truth + grep transport.


git show HEAD:crates/spt-runtime/src/manifest.rs | grep -n "transport\|pub uninstall\|struct Update" | head

Check transport in HEAD manifest


443:pub struct Update {
489:    pub uninstall: Option<String>,


No matches found


Stale first-read. HEAD has no transport. Check if cli.rs references transport (would break build).


crates\spt\src\cli.rs:58

Found 58 total occurrences across 1 file.


1	# v0.13.2 — Adapter packaging + live update: JIT build plan
2
3	**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.
4
5	**REQs** (registered; activate real stages per-wave, rule 5):
6	- `REQ-ADAPTER-MULTIPLATFORM-SPT` (doc-active = ADR-0024) → W1
7	- `REQ-ADAPTER-GH-TRANSPORT` (inactive) → W2
8	- `REQ-ADAPTER-LIVE-UPDATE` (doc-active = ADR-0025) → W3
9	- `REQ-ADAPTER-UPDATE-MESSAGE` (inactive) → W4
10	- `REQ-ADAPTER-PROOF-DIR-OVERRIDE` (inactive) → W5
11
12	**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).
13
14	---
15
16	## W1 — Multi-platform `.spt`  (REQ-ADAPTER-MULTIPLATFORM-SPT, ADR-0024)
17	Foundational format change; everything else composes on it.
18	- **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.
19	- **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.
20	- **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).
21	- **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).
22
23	## W2 — `gh` transport for private repos  (REQ-ADAPTER-GH-TRANSPORT)
24	Additive; independent of W1/W3.
25	- **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.
26	- **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.
27	- **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.
28
29	## W3 — Live daemon-coordinated update + CRC swap  (REQ-ADAPTER-LIVE-UPDATE, ADR-0025) — THE KEYSTONE
30	Riskiest wave; touches daemon lifecycle just hardened in v0.12.x/v0.13.0. Likely splits into sub-items.
31	- **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.
32	- **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.
33	- **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.
34	- **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).
35	- **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).
36	- **Hazards (binding):**
37	  - Gate vs a REAL dummy-harness fixture + real daemon, **NO mocks** (the v0.12.1 binding lesson — green-mocks ≠ green-real-harness).
38	  - The stop→restart must be **bounded** (kill-bounded if the binary won't die; never ride a 240s backstop — the `join_bounded` pattern).
39	  - **Brain-parity:** the endpoint (`BrainLifecycle`/broker) never restarts; only the resident child cycles.
40	  - **Partial-failure:** a mid-swap failure must not strand the endpoint (stopped binary + half-swapped files); restart-old / atomic-ish on failure.
41	  - 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.
42
43	## W4 — `update-message`  (REQ-ADAPTER-UPDATE-MESSAGE)
44	Small, additive.
45	- **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.
46	- **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.
47	- **Gate:** unit (rendered on applied update, NOT on no-op; markdown render). doc = manifest.md + CONTEXT. No int (CLI output, unit-covered).
48

# 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).


cli.rs has 58 transport refs but manifest.rs Update has none. Possible broken build / concurrent edit. Check the `.transport` field access + resolve_transport.


Grep

update\.transport|\.transport\b|fn resolve_transport|enum EffectiveTransport|enum Transport|update_transport


crates\spt\src\cli.rs:2582:enum EffectiveTransport {
crates\spt\src\cli.rs:2592:fn resolve_transport(
crates\spt\src\cli.rs:6204:        let transport = resolve_transport(update.transport, gh_available);
crates\spt\src\cli.rs:7979:    fn resolve_transport_explicit_never_probes() {
crates\spt\src\cli.rs:7997:    fn resolve_transport_auto_follows_probe() {


Bash

git show HEAD:crates/spt-runtime/src/manifest.rs | grep -ni "transport" | head; echo "---LOCAL grep -i---"; git -C . grep -ni "transport" -- crates/spt-runtime/src/manifest.rs | head

Case-insensitive transport search in HEAD + worktree manifest


490:    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,
496:    // [impl->REQ-ADAPTER-GH-TRANSPORT]
497:    #[serde(default, skip_serializing_if = "Transport::is_auto")]
498:    pub transport: Transport,
501:/// Fetch transport for the `gh_release` avenue (and `spt adapter add --release`):
503:/// (REQ-ADAPTER-GH-TRANSPORT)
506:pub enum Transport {
519:impl Transport {
521:    /// serialized manifest (an absent `transport` deserializes back to `Auto`,
523:    // [impl->REQ-ADAPTER-GH-TRANSPORT]
---LOCAL grep -i---
crates/spt-runtime/src/manifest.rs:490:    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,
crates/spt-runtime/src/manifest.rs:496:    // [impl->REQ-ADAPTER-GH-TRANSPORT]
crates/spt-runtime/src/manifest.rs:497:    #[serde(default, skip_serializing_if = "Transport::is_auto")]
crates/spt-runtime/src/manifest.rs:498:    pub transport: Transport,
crates/spt-runtime/src/manifest.rs:501:/// Fetch transport for the `gh_release` avenue (and `spt adapter add --release`):
crates/spt-runtime/src/manifest.rs:503:/// (REQ-ADAPTER-GH-TRANSPORT)
crates/spt-runtime/src/manifest.rs:506:pub enum Transport {
crates/spt-runtime/src/manifest.rs:519:impl Transport {
crates/spt-runtime/src/manifest.rs:521:    /// serialized manifest (an absent `transport` deserializes back to `Auto`,
crates/spt-runtime/src/manifest.rs:523:    // [impl->REQ-ADAPTER-GH-TRANSPORT]


Tool gave stale content — worktree actually HAS transport (git grep confirms). Fresh read to get true bytes.


484	    /// Optional inverse of install — run by `spt adapter remove` once the adapter
485	    /// is quiesced (the mirror of `spt adapter add`, which reuses this section as
486	    /// the install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in
487	    /// M2a; conducted with adapter-registration later.)
488	    #[serde(default, skip_serializing_if = "Option::is_none")]
489	    pub uninstall: Option<String>,
490	    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,
491	    /// public), `gh` (shell the pre-authorized `gh` CLI, the private-repo path),
492	    /// or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `gh`
493	    /// honors OAuth + `GH_TOKEN`, so spt-core custodies no token. Additive over
494	    /// the existing fetch path; not applicable to `delegated` / `file_pull`.
495	    /// Absent ⇒ `auto` (N-1-safe).
496	    // [impl->REQ-ADAPTER-GH-TRANSPORT]
497	    #[serde(default, skip_serializing_if = "Transport::is_auto")]
498	    pub transport: Transport,
499	}
500
501	/// Fetch transport for the `gh_release` avenue (and `spt adapter add --release`):
502	/// how spt-core retrieves the release asset bytes + the latest-release version.
503	/// (REQ-ADAPTER-GH-TRANSPORT)
504	#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
505	#[serde(rename_all = "snake_case")]
506	pub enum Transport {
507	    /// Direct HTTPS via reqwest — the public-repo path (the original behavior).
508	    Https,
509	    /// Shell the pre-authorized `gh` CLI (`gh release download` for the asset,
510	    /// `gh api` for the version) — the private-repo path. `gh` honors OAuth +
511	    /// `GH_TOKEN`, so spt-core never custodies a token.
512	    Gh,
513	    /// Prefer `gh` when it is installed and authenticated, else fall back to
514	    /// HTTPS. The default.
515	    #[default]
516	    Auto,
517	}
518
519	impl Transport {
520	    /// True for the default (`Auto`) — lets the field be omitted from a
521	    /// serialized manifest (an absent `transport` deserializes back to `Auto`,
522	    /// N-1-safe), matching the other optional `[update]` fields.
523	    // [impl->REQ-ADAPTER-GH-TRANSPORT]

    /// Optional inverse of install — run by `spt adapter remove` once the adapter
    /// is quiesced (the mirror of `spt adapter add`, which reuses this section as
    /// the install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in
    /// M2a; conducted with adapter-registration later.)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,
    /// public), `gh` (shell the pre-authorized `gh` CLI, the private-repo path),
    /// or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `gh`
    /// honors OAuth + `GH_TOKEN`, so spt-core custodies no token. Additive over
    /// the existing fetch path; not applicable to `delegated` / `file_pull`.
    /// Absent ⇒ `auto` (N-1-safe).
    // [impl->REQ-ADAPTER-GH-TRANSPORT]
    #[serde(default, skip_serializing_if = "Transport::is_auto")]
    pub transport: Transport,
}

/// Fetch transport for the `gh_release` avenue (and `spt adapter add --release`):
/// how spt-core retrieves the release asset bytes + the latest-release version.
/// (REQ-ADAPTER-GH-TRANSPORT)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum Transport {
    /// Direct HTTPS via reqwest — the public-repo path (the original behavior).
    Https,
    /// Shell the pre-authorized `gh` CLI (`gh release download` for the asset,
    /// `gh api` for the version) — the private-repo path. `gh` honors OAuth +
    /// `GH_TOKEN`, so spt-core never custodies a token.
    Gh,
    /// Prefer `gh` when it is installed and authenticated, else fall back to
    /// HTTPS. The default.
    #[default]
    Auto,
}

impl Transport {
    /// True for the default (`Auto`) — lets the field be omitted from a
    /// serialized manifest (an absent `transport` deserializes back to `Auto`,
    /// N-1-safe), matching the other optional `[update]` fields.
    // [impl->REQ-ADAPTER-GH-TRANSPORT]


Now true bytes. Insert `message` after `uninstall`, before transport:


Edit

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// Optional plain (multi-line) human notice surfaced to stdout —
    /// markdown-rendered (the helpfmt prose path) — **only when `spt adapter
    /// update` actually APPLIES an update** (version changed), never on a
    /// no-op. Read from the newly-installed manifest; avenue-agnostic
    /// (`gh_release` / `delegated` / `file_pull`). No `{key}` substitution.
    /// Use: an adapter telling the operator a post-update action (e.g. "run
    /// `/reload-plugins` in any ongoing sessions").
    // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\manifest.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uninstall: Option<String>,
    /// Optional plain (multi-line) human notice surfaced to stdout —
    /// markdown-rendered (the helpfmt prose path) — **only when `spt adapter
    /// update` actually APPLIES an update** (version changed), never on a
    /// no-op. Read from the newly-installed manifest; avenue-agnostic
    /// (`gh_release` / `delegated` / `file_pull`). No `{key}` substitution.
    /// Use: an adapter telling the operator a post-update action (e.g. "run
    /// `/reload-plugins` in any ongoing sessions").
    // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    /// `gh_release` avenue: the fetch **transport** — `https` (direct reqwest,

/// M2a; conducted with adapter-registration later.)

+    /// Optional plain (multi-line) human notice surfaced to stdout —

+    /// markdown-rendered (the helpfmt prose path) — **only when `spt adapter

+    /// update` actually APPLIES an update** (version changed), never on a

+    /// no-op. Read from the newly-installed manifest; avenue-agnostic

+    /// (`gh_release` / `delegated` / `file_pull`). No `{key}` substitution.

+    /// Use: an adapter telling the operator a post-update action (e.g. "run

+    /// `/reload-plugins` in any ongoing sessions").

+    // [impl->REQ-ADAPTER-UPDATE-MESSAGE]

+    #[serde(default, skip_serializing_if = "Option::is_none")]

/// public), `gh` (shell the pre-authorized `gh` CLI, the private-repo path),

/// or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `gh`


Now the print on applied update. Edit `cmd_adapter_update` register-success branch:


        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
            Err(e) => {

        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, new_manifest)) => {
                eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name);
                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(msg) = new_manifest
                    .update
                    .as_ref()
                    .and_then(|u| u.message.as_deref())
                {
                    let rendered =
                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());
                    println!("{}", rendered.trim_end());
                }
            }
            Err(e) => {


Concurrent process bumping mtimes again. Re-read region to confirm content unchanged, then edit.


6296	        match registry::register(adapters, &dest, now_ms()) {
6297	            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
6298	            Err(e) => {
6299	                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);
6300	                failed = true;
6301	            }
6302	        }
6303	    }
6304
6305	    i32::from(failed)
6306	}
6307

        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);
                failed = true;
            }
        }
    }

    i32::from(failed)
}


        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);

        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, new_manifest)) => {
                eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name);
                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(msg) = new_manifest
                    .update
                    .as_ref()
                    .and_then(|u| u.message.as_deref())
                {
                    let rendered =
                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());
                    println!("{}", rendered.trim_end());
                }
            }
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);

        match registry::register(adapters, &dest, now_ms()) {
            Ok((r, new_manifest)) => {
                eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name);
                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(msg) = new_manifest
                    .update
                    .as_ref()
                    .and_then(|u| u.message.as_deref())
                {
                    let rendered =
                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());
                    println!("{}", rendered.trim_end());
                }
            }
            Err(e) => {
                eprintln!("ADAPTER_UPDATE_FAIL:{}: re-register: {e}", record.name);

-            Ok((r, _)) => eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name),

+                eprintln!("ADAPTER_UPDATE_DONE:{} (re-registered)", r.name);

+                // Surface the newly-installed manifest's post-update notice —

+                // ONLY here, on an APPLIED update (a no-op `continue`s above and

+                // never reaches this). Markdown-rendered via the helpfmt prose

+                // path; goes to STDOUT (the protocol lines above are stderr).

+                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]

+                if let Some(msg) = new_manifest

+                    .and_then(|u| u.message.as_deref())

+                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());

+                    println!("{}", rendered.trim_end());


Now fix Update test literals in adapter_update.rs. Read them.


215	    }
216
217	    fn file_pull_update(signing_key: Option<String>) -> Update {
218	        Update {
219	            avenue: UpdateAvenue::FilePull,
220	            command: None,
221	            repo: Some("https://x/y".to_string()),
222	            path_regex: None,
223	            asset: None,
224	            signing_key,
225	            self_verifies: false,
226	            version_check: false,
227	            uninstall: None,
228	            transport: Default::default(),
229	        }
230	    }
231
232	    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {
233	        Update {
234	            avenue: UpdateAvenue::Delegated,
235	            command,
236	            repo: None,
237	            path_regex: None,
238	            asset: None,
239	            signing_key: None,
240	            self_verifies,
241	            version_check: false,
242	            uninstall: None,
243	            transport: Default::default(),
244	        }
245	    }
246
247	    fn gh_release_update(repo: Option<String>) -> Update {
248	        Update {
249	            avenue: UpdateAvenue::GhRelease,
250	            command: None,
251	            repo,
252	            path_regex: None,
253	            asset: None,
254	            signing_key: None,
255	            self_verifies: false,
256	            version_check: false,
257	            uninstall: None,
258	            transport: Default::default(),
259	        }
260	    }
261
262	    // [unit->REQ-UPD-9] gh_release stays out of the pure decision module: the
263	    // network fetch + optional-verify + re-register run CLI-side (REQ-UPD-7 keeps
264	    // HTTP out of the daemon), so plan_adapter_update defers it as GhReleaseManaged
265	    // rather than fetching here.
266	    #[test]
267	    fn gh_release_is_managed_cli_side_not_in_pure_plan() {
268	        let update = gh_release_update(Some("user/repo".to_string()));
269	        assert_eq!(

    }

    fn file_pull_update(signing_key: Option<String>) -> Update {
        Update {
            avenue: UpdateAvenue::FilePull,
            command: None,
            repo: Some("https://x/y".to_string()),
            path_regex: None,
            asset: None,
            signing_key,
            self_verifies: false,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {
        Update {
            avenue: UpdateAvenue::Delegated,
            command,
            repo: None,
            path_regex: None,
            asset: None,
            signing_key: None,
            self_verifies,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    fn gh_release_update(repo: Option<String>) -> Update {
        Update {
            avenue: UpdateAvenue::GhRelease,
            command: None,
            repo,
            path_regex: None,
            asset: None,
            signing_key: None,
            self_verifies: false,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    // [unit->REQ-UPD-9] gh_release stays out of the pure decision module: the
    // network fetch + optional-verify + re-register run CLI-side (REQ-UPD-7 keeps
    // HTTP out of the daemon), so plan_adapter_update defers it as GhReleaseManaged
    // rather than fetching here.
    #[test]
    fn gh_release_is_managed_cli_side_not_in_pure_plan() {
        let update = gh_release_update(Some("user/repo".to_string()));
        assert_eq!(


Three literals — add `message: None` to each (after `uninstall: None,`):


            self_verifies: false,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {

            self_verifies: false,
            version_check: false,
            uninstall: None,
            message: None,
            transport: Default::default(),
        }
    }

    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\adapter_update.rs has been updated successfully. (file state is current in your context — no need to Read it back)

            self_verifies: false,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {

            self_verifies: false,
            version_check: false,
            uninstall: None,
            message: None,
            transport: Default::default(),
        }
    }

    fn delegated_update(self_verifies: bool, command: Option<String>) -> Update {


            self_verifies,
            version_check: false,
            uninstall: None,
            transport: Default::default(),

            self_verifies,
            version_check: false,
            uninstall: None,
            message: None,
            transport: Default::default(),


            self_verifies,
            version_check: false,
            uninstall: None,
            transport: Default::default(),

            self_verifies,
            version_check: false,
            uninstall: None,
            message: None,
            transport: Default::default(),


            self_verifies: false,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    // [unit->REQ-UPD-9]

            self_verifies: false,
            version_check: false,
            uninstall: None,
            message: None,
            transport: Default::default(),
        }
    }

    // [unit->REQ-UPD-9]


            self_verifies: false,
            version_check: false,
            uninstall: None,
            transport: Default::default(),
        }
    }

    // [unit->REQ-UPD-9]

            self_verifies: false,
            version_check: false,
            uninstall: None,
            message: None,
            transport: Default::default(),
        }
    }

    // [unit->REQ-UPD-9]


Hunt for other `Update {...}` literals needing the field (build breaks otherwise):


crates\spt-runtime\src\manifest.rs:489:    pub uninstall: Option<String>,
crates\spt-daemon\src\adapter_update.rs:227:            uninstall: None,
crates\spt-daemon\src\adapter_update.rs:243:            uninstall: None,
crates\spt-daemon\src\adapter_update.rs:259:            uninstall: None,


Only 3 literals, all fixed. Now refactor the print to a testable helper + add unit. First revise cli.rs to call a pure helper:


                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(msg) = new_manifest
                    .update
                    .as_ref()
                    .and_then(|u| u.message.as_deref())
                {
                    let rendered =
                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());
                    println!("{}", rendered.trim_end());
                }

                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(notice) =
                    adapter_update_notice(&new_manifest, crate::helpfmt::stdout_color())
                {
                    println!("{notice}");
                }


                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(msg) = new_manifest
                    .update
                    .as_ref()
                    .and_then(|u| u.message.as_deref())
                {
                    let rendered =
                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());
                    println!("{}", rendered.trim_end());
                }

                // Surface the newly-installed manifest's post-update notice —
                // ONLY here, on an APPLIED update (a no-op `continue`s above and
                // never reaches this). Markdown-rendered via the helpfmt prose
                // path; goes to STDOUT (the protocol lines above are stderr).
                // [impl->REQ-ADAPTER-UPDATE-MESSAGE]
                if let Some(notice) =
                    adapter_update_notice(&new_manifest, crate::helpfmt::stdout_color())
                {
                    println!("{notice}");
                }

-                if let Some(msg) = new_manifest

-                    .and_then(|u| u.message.as_deref())

+                    adapter_update_notice(&new_manifest, crate::helpfmt::stdout_color())

-                        crate::helpfmt::render(msg, crate::helpfmt::stdout_color());

-                    println!("{}", rendered.trim_end());

+                    println!("{notice}");


Add the helper right above `cmd_adapter_update`:


fn cmd_adapter_update(adapters: &std::path::Path, name: Option<&str>) -> i32 {

/// Render an adapter's post-update `[update].message` for stdout, or `None` when
/// the manifest declares none. Markdown-rendered via the helpfmt prose path and
/// trailing-trimmed (the caller adds one newline). Pure (modulo `color`); the
/// caller invokes it ONLY after an update is actually applied — never on a no-op.
// [impl->REQ-ADAPTER-UPDATE-MESSAGE]
fn adapter_update_notice(manifest: &spt_runtime::manifest::Manifest, color: bool) -> Option<String> {
    let msg = manifest.update.as_ref()?.message.as_deref()?;
    Some(crate::helpfmt::render(msg, color).trim_end().to_string())
}

fn cmd_adapter_update(adapters: &std::path::Path, name: Option<&str>) -> i32 {


/// Render an adapter's post-update `[update].message` for stdout, or `None` when
/// the manifest declares none. Markdown-rendered via the helpfmt prose path and
/// trailing-trimmed (the caller adds one newline). Pure (modulo `color`); the
/// caller invokes it ONLY after an update is actually applied — never on a no-op.
// [impl->REQ-ADAPTER-UPDATE-MESSAGE]
fn adapter_update_notice(manifest: &spt_runtime::manifest::Manifest, color: bool) -> Option<String> {
    let msg = manifest.update.as_ref()?.message.as_deref()?;
    Some(crate::helpfmt::render(msg, color).trim_end().to_string())
}

fn cmd_adapter_update(adapters: &std::path::Path, name: Option<&str>) -> i32 {

/// `Skipped(GhReleaseManaged)` for this avenue), reusing the REQ-INSTALL-9

+/// Render an adapter's post-update `[update].message` for stdout, or `None` when

+/// the manifest declares none. Markdown-rendered via the helpfmt prose path and

+/// trailing-trimmed (the caller adds one newline). Pure (modulo `color`); the

+/// caller invokes it ONLY after an update is actually applied — never on a no-op.

+fn adapter_update_notice(manifest: &spt_runtime::manifest::Manifest, color: bool) -> Option<String> {

+    let msg = manifest.update.as_ref()?.message.as_deref()?;

+    Some(crate::helpfmt::render(msg, color).trim_end().to_string())


Now the unit test. Find test module + how Manifest is built in cli.rs tests:


7983
7984	#[cfg(test)]
7985	mod tests {
7986	    use super::*;
7987
7988	    fn parse(args: &[&str]) -> Result<Cli, clap::Error> {
7989	        Cli::try_parse_from(args)
7990	    }
7991
7992	    // ── REQ-ADAPTER-GH-TRANSPORT (v0.13.2 W2): gh transport for private adapter
7993	    // release repos. Covers the DECISION (resolve_transport) and the COMMAND
7994	    // SHAPE (the gh command builders) only — the real `gh`/network path is HITL
7995	    // (int), explicitly unit-covered-not-int per doyle.
7996
7997	    // [unit->REQ-ADAPTER-GH-TRANSPORT] An EXPLICIT transport is honored verbatim
7998	    // and NEVER consults the probe: a `gh_available` closure that panics if
7999	    // called proves `Https`→Https and `Gh`→Gh short-circuit before any probe.
8000	    #[test]
8001	    fn resolve_transport_explicit_never_probes() {
8002	        use spt_runtime::manifest::Transport;
8003	        assert_eq!(
8004	            resolve_transport(Transport::Https, || unreachable!("Https must not probe gh")),
8005	            EffectiveTransport::Https,
8006	            "explicit Https resolves to Https without probing",
8007	        );
8008	        assert_eq!(
8009	            resolve_transport(Transport::Gh, || unreachable!("Gh must not probe gh")),
8010	            EffectiveTransport::Gh,
8011	            "explicit Gh resolves to Gh without probing",
8012	        );
8013	    }
8014
8015	    // [unit->REQ-ADAPTER-GH-TRANSPORT] AUTO consults the probe: gh-when-available
8016	    // (probe→true ⇒ Gh) else HTTPS (probe→false ⇒ Https). This is the default
8017	    // (`Transport::Auto`) avenue selection.
8018	    #[test]
8019	    fn resolve_transport_auto_follows_probe() {
8020	        use spt_runtime::manifest::Transport;
8021	        assert_eq!(
8022	            resolve_transport(Transport::Auto, || true),
8023	            EffectiveTransport::Gh,
8024	            "auto picks gh when the probe reports it available",
8025	        );
8026	        assert_eq!(
8027	            resolve_transport(Transport::Auto, || false),
8028	            EffectiveTransport::Https,
8029	            "auto falls back to https when gh is unavailable",
8030	        );
8031	    }
8032

#[cfg(test)]
mod tests {
    use super::*;

    fn parse(args: &[&str]) -> Result<Cli, clap::Error> {
        Cli::try_parse_from(args)
    }

    // ── REQ-ADAPTER-GH-TRANSPORT (v0.13.2 W2): gh transport for private adapter
    // release repos. Covers the DECISION (resolve_transport) and the COMMAND
    // SHAPE (the gh command builders) only — the real `gh`/network path is HITL
    // (int), explicitly unit-covered-not-int per doyle.

    // [unit->REQ-ADAPTER-GH-TRANSPORT] An EXPLICIT transport is honored verbatim
    // and NEVER consults the probe: a `gh_available` closure that panics if
    // called proves `Https`→Https and `Gh`→Gh short-circuit before any probe.
    #[test]
    fn resolve_transport_explicit_never_probes() {
        use spt_runtime::manifest::Transport;
        assert_eq!(
            resolve_transport(Transport::Https, || unreachable!("Https must not probe gh")),
            EffectiveTransport::Https,
            "explicit Https resolves to Https without probing",
        );
        assert_eq!(
            resolve_transport(Transport::Gh, || unreachable!("Gh must not probe gh")),
            EffectiveTransport::Gh,
            "explicit Gh resolves to Gh without probing",
        );
    }

    // [unit->REQ-ADAPTER-GH-TRANSPORT] AUTO consults the probe: gh-when-available
    // (probe→true ⇒ Gh) else HTTPS (probe→false ⇒ Https). This is the default
    // (`Transport::Auto`) avenue selection.
    #[test]
    fn resolve_transport_auto_follows_probe() {
        use spt_runtime::manifest::Transport;
        assert_eq!(
            resolve_transport(Transport::Auto, || true),
            EffectiveTransport::Gh,
            "auto picks gh when the probe reports it available",
        );
        assert_eq!(
            resolve_transport(Transport::Auto, || false),
            EffectiveTransport::Https,
            "auto falls back to https when gh is unavailable",
        );
    }


Grep

pub fn from_toml|pub fn parse|fn sample_manifest|Manifest \{|from_str::<Manifest>|toml::from_str


crates\spt-runtime\src\runtime.rs:308:    pub fn manifest(&self) -> &Manifest {
crates\spt-daemon\tests\daemon_e2e.rs:98:fn live_manifest(drops: &std::path::Path) -> Manifest {
crates\spt-runtime\src\resolve.rs:110:    toml::from_str(&s).unwrap_or_default()
adapters\mock\tests\manifest_valid.rs:7:fn load() -> Manifest {
crates\spt-runtime\src\registry.rs:95:    DeferredManifest { adapter: String, dir: String },
crates\spt-runtime\src\registry.rs:118:            RegistryError::DeferredManifest { adapter, dir } => write!(
crates\spt-runtime\src\registry.rs:374:    toml::from_str(&s).map_err(|_| RegistryError::BadRecord(name.to_string()))
crates\spt-runtime\src\registry.rs:404:            RegistryError::DeferredManifest {
crates\spt-runtime\src\registry.rs:502:    toml::from_str(&s).map_err(|_| RegistryError::BadRecord(format!("profiles/{profile}")))
crates\spt-runtime\src\registry.rs:540:    let overlay: Value = toml::from_str(overlay_toml)
crates\spt-runtime\src\registry.rs:928:            Err(RegistryError::DeferredManifest { adapter, dir }) => {
crates\spt-runtime\src\registry.rs:932:                let msg = RegistryError::DeferredManifest { adapter, dir }.to_string();
crates\spt-runtime\src\profile.rs:207:        toml::from_str(s).expect("test TOML parses")
crates\spt-runtime\src\profile.rs:374:        let mut t: toml::Table = toml::from_str(
crates\spt-runtime\src\profile.rs:408:        let ptr: Value = toml::from_str("k = { file = \"body.md\" }").unwrap();
crates\spt-runtime\src\profile.rs:415:        let multi: Value = toml::from_str("k = { file = \"x\", extra = 1 }").unwrap();
crates\spt-runtime\src\profile.rs:419:        let other: Value = toml::from_str("k = { path = \"x\" }").unwrap();
crates\spt-runtime\src\profile.rs:423:        let nonstr: Value = toml::from_str("k = { file = 7 }").unwrap();
crates\spt-live\src\resume.rs:170:    fn resume_manifest(cmd: &str) -> Manifest {
crates\spt-runtime\src\manifest.rs:29:pub struct Manifest {
crates\spt-runtime\src\manifest.rs:820:impl Manifest {
crates\spt-runtime\src\manifest.rs:824:    pub fn from_toml_str(s: &str) -> Result<Manifest, ManifestError> {
crates\spt-runtime\src\manifest.rs:825:        let manifest: Manifest = toml::from_str(s)?;
crates\spt-proto\src\event.rs:274:pub fn parse_event(line: &str) -> Option<ParsedEvent> {
crates\spt-proto\src\chunk.rs:168:pub fn parse_event_part(line: &str) -> Option<EventPart> {

[Showing results with pagination = limit: 25]


Bash

find . -name "manifest.toml" -not -path "*/target/*" | head; echo "---"; find . -name "*.toml" -path "*mock*" -not -path "*/target/*" | head

Find manifest.toml fixtures


./adapters/mock/manifest.toml
./adapters/mock-gateway/manifest.toml
---
./adapters/mock/Cargo.toml
./adapters/mock/manifest.toml
./adapters/mock-gateway/manifest.toml