1750	        }
1751	        Err(e) => {
1752	            eprintln!("UPDATE_APPLY_FAIL:{e}");
1753	            1
1754	        }
1755	    }
1756	}
1757
1758	/// The release-asset name for THIS platform — matches the release-publish
1759	/// assets on spt-releases. Pure for tests.
1760	fn platform_release_asset() -> &'static str {
1761	    if cfg!(windows) {
1762	        "spt-x86_64-windows.exe"
1763	    } else {
1764	        "spt-x86_64-linux"
1765	    }
1766	}
1767
1768	/// The two origin URLs (artifact, then its `.release.json` metadata) for a
1769	/// release on the GitHub origin. `tag = None` ⇒ the `latest` release. Pure.
1770	fn origin_release_urls(repo: &str, asset: &str, tag: Option<&str>) -> (String, String) {
1771	    let base = match tag {
1772	        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),
1773	        None => format!("https://github.com/{repo}/releases/latest/download"),
1774	    };
1775	    (
1776	        format!("{base}/{asset}"),
1777	        format!("{base}/{asset}.release.json"),
1778	    )
1779	}
1780
1781	fn http_get_text(client: &reqwest::blocking::Client, url: &str) -> Result<String, String> {
1782	    let resp = client.get(url).send().map_err(|e| e.to_string())?;
1783	    if !resp.status().is_success() {
1784	        return Err(format!("HTTP {}", resp.status()));
1785	    }
1786	    resp.text().map_err(|e| e.to_string())
1787	}
1788
1789	fn http_get_bytes(client: &reqwest::blocking::Client, url: &str) -> Result<Vec<u8>, String> {
1790	    let resp = client.get(url).send().map_err(|e| e.to_string())?;
1791	    if !resp.status().is_success() {
1792	        return Err(format!("HTTP {}", resp.status()));
1793	    }
1794	    resp.bytes().map(|b| b.to_vec()).map_err(|e| e.to_string())
1795	}
1796
1797	/// `spt update fetch` (REQ-UPD-7) — pull the signed release from the GitHub
1798	/// origin and stage it through the SAME verify gate the P2P pull uses
1799	/// (`plan_verified`: two-key signature + channel + monotonic rollback floor +
1800	/// SHA-256), after which the existing `spt update apply` flow takes over. Closes
1801	/// the peer-only discovery gap: a first-in-fleet / isolated node can update with
1802	/// no peer to pull from. The signed-release anchor keeps the GitHub transport
1803	/// untrusted-but-verified.
1804	// [impl->REQ-UPD-7]
1805	fn cmd_update_fetch(channel: Option<String>, tag: Option<String>) -> i32 {
1806	    let repo =
1807	        std::env::var("SPT_INSTALL_REPO").unwrap_or_else(|_| "SaberMage/spt-releases".to_string());
1808	    let asset = platform_release_asset();
1809	    let (art_url, meta_url) = origin_release_urls(&repo, asset, tag.as_deref());
1810
1811	    // Verify policy: the two-key anchor + the node's release-keys overlay; the
1812	    // rollback floor is the staged version on the pinned channel (mirrors the
1813	    // P2P path, peerloop.rs). `--channel` overrides the pin to deliberately
1814	    // accept another channel's release.
1815	    let cache = spt_daemon::ReleaseCache::open(&perch::spt_home().join("releases"));
1816	    let keys = perch::identity_dir().join("release-keys.json");
1817	    let mut policy = spt_daemon::release::VerifyPolicy::load_from(&keys, 0, now_ms());
1818	    let pinned = policy.pinned_channel.clone();
1819	    policy.current_version = if cache.staged_channel().as_deref() == Some(pinned.as_str()) {
1820	        cache.staged_version().unwrap_or(0)
1821	    } else {
1822	        0
1823	    };
1824	    if let Some(c) = channel {
1825	        policy.pinned_channel = c;
1826	    }
1827	    if policy.trusted_keys.is_empty() {
1828	        eprintln!("UPDATE_FETCH_FAIL: no trusted release keys");
1829	        return 1;
1830	    }
1831
1832	    eprintln!("Fetching {asset} from {repo}…");
1833	    let client = match reqwest::blocking::Client::builder()
1834	        .user_agent(concat!("spt/", env!("CARGO_PKG_VERSION")))
1835	        .build()
1836	    {
1837	        Ok(c) => c,
1838	        Err(e) => {
1839	            eprintln!("UPDATE_FETCH_FAIL: http client: {e}");
1840	            return 1;
1841	        }
1842	    };
1843	    let meta_text = match http_get_text(&client, &meta_url) {
1844	        Ok(t) => t,
1845	        Err(e) => {
1846	            eprintln!("UPDATE_FETCH_FAIL: GET {meta_url}: {e}");
1847	            return 1;
1848	        }
1849	    };
1850	    let signed: spt_daemon::release::SignedRelease = match serde_json::from_str(&meta_text) {
1851	        Ok(s) => s,
1852	        Err(e) => {
1853	            eprintln!("UPDATE_FETCH_FAIL: bad release.json: {e}");
1854	            return 1;
1855	        }
1856	    };
1857	    let artifact = match http_get_bytes(&client, &art_url) {
1858	        Ok(b) => b,
1859	        Err(e) => {
1860	            eprintln!("UPDATE_FETCH_FAIL: GET {art_url}: {e}");
1861	            return 1;
1862	        }
1863	    };
1864
1865	    // The SAME gate the daemon's peer pull runs — no inherited trust from the
1866	    // GitHub transport.
1867	    match spt_daemon::update::plan_verified(
1868	        &spt_daemon::update::BrokerAbi::current(),
1869	        &signed,
1870	        &artifact,
1871	        &policy,
1872	    ) {
1873	        Ok(_plan) => {
1874	            if let Err(e) = cache.stage(&signed, &artifact) {
1875	                eprintln!("UPDATE_FETCH_FAIL: stage: {e}");
1876	                return 1;
1877	            }
1878	            let v = cache
1879	                .staged_version()
1880	                .map(|v| v.to_string())
1881	                .unwrap_or_else(|| "?".to_string());
1882	            println!("UPDATE_STAGED:{v} (from {repo}) — run `spt update apply` to apply");
1883	            0
1884	        }

        }
        Err(e) => {
            eprintln!("UPDATE_APPLY_FAIL:{e}");
            1
        }
    }
}

/// The release-asset name for THIS platform — matches the release-publish
/// assets on spt-releases. Pure for tests.
fn platform_release_asset() -> &'static str {
    if cfg!(windows) {
        "spt-x86_64-windows.exe"
    } else {
        "spt-x86_64-linux"
    }
}

/// The two origin URLs (artifact, then its `.release.json` metadata) for a
/// release on the GitHub origin. `tag = None` ⇒ the `latest` release. Pure.
fn origin_release_urls(repo: &str, asset: &str, tag: Option<&str>) -> (String, String) {
    let base = match tag {
        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),
        None => format!("https://github.com/{repo}/releases/latest/download"),
    };
    (
        format!("{base}/{asset}"),
        format!("{base}/{asset}.release.json"),
    )
}

fn http_get_text(client: &reqwest::blocking::Client, url: &str) -> Result<String, String> {
    let resp = client.get(url).send().map_err(|e| e.to_string())?;
    if !resp.status().is_success() {
        return Err(format!("HTTP {}", resp.status()));
    }
    resp.text().map_err(|e| e.to_string())
}

fn http_get_bytes(client: &reqwest::blocking::Client, url: &str) -> Result<Vec<u8>, String> {
    let resp = client.get(url).send().map_err(|e| e.to_string())?;
    if !resp.status().is_success() {
        return Err(format!("HTTP {}", resp.status()));
    }
    resp.bytes().map(|b| b.to_vec()).map_err(|e| e.to_string())
}

/// `spt update fetch` (REQ-UPD-7) — pull the signed release from the GitHub
/// origin and stage it through the SAME verify gate the P2P pull uses
/// (`plan_verified`: two-key signature + channel + monotonic rollback floor +
/// SHA-256), after which the existing `spt update apply` flow takes over. Closes
/// the peer-only discovery gap: a first-in-fleet / isolated node can update with
/// no peer to pull from. The signed-release anchor keeps the GitHub transport
/// untrusted-but-verified.
// [impl->REQ-UPD-7]
fn cmd_update_fetch(channel: Option<String>, tag: Option<String>) -> i32 {
    let repo =
        std::env::var("SPT_INSTALL_REPO").unwrap_or_else(|_| "SaberMage/spt-releases".to_string());
    let asset = platform_release_asset();
    let (art_url, meta_url) = origin_release_urls(&repo, asset, tag.as_deref());

    // Verify policy: the two-key anchor + the node's release-keys overlay; the
    // rollback floor is the staged version on the pinned channel (mirrors the
    // P2P path, peerloop.rs). `--channel` overrides the pin to deliberately
    // accept another channel's release.
    let cache = spt_daemon::ReleaseCache::open(&perch::spt_home().join("releases"));
    let keys = perch::identity_dir().join("release-keys.json");
    let mut policy = spt_daemon::release::VerifyPolicy::load_from(&keys, 0, now_ms());
    let pinned = policy.pinned_channel.clone();
    policy.current_version = if cache.staged_channel().as_deref() == Some(pinned.as_str()) {
        cache.staged_version().unwrap_or(0)
    } else {
        0
    };
    if let Some(c) = channel {
        policy.pinned_channel = c;
    }
    if policy.trusted_keys.is_empty() {
        eprintln!("UPDATE_FETCH_FAIL: no trusted release keys");
        return 1;
    }

    eprintln!("Fetching {asset} from {repo}…");
    let client = match reqwest::blocking::Client::builder()
        .user_agent(concat!("spt/", env!("CARGO_PKG_VERSION")))
        .build()
    {
        Ok(c) => c,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: http client: {e}");
            return 1;
        }
    };
    let meta_text = match http_get_text(&client, &meta_url) {
        Ok(t) => t,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: GET {meta_url}: {e}");
            return 1;
        }
    };
    let signed: spt_daemon::release::SignedRelease = match serde_json::from_str(&meta_text) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: bad release.json: {e}");
            return 1;
        }
    };
    let artifact = match http_get_bytes(&client, &art_url) {
        Ok(b) => b,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: GET {art_url}: {e}");
            return 1;
        }
    };

    // The SAME gate the daemon's peer pull runs — no inherited trust from the
    // GitHub transport.
    match spt_daemon::update::plan_verified(
        &spt_daemon::update::BrokerAbi::current(),
        &signed,
        &artifact,
        &policy,
    ) {
        Ok(_plan) => {
            if let Err(e) = cache.stage(&signed, &artifact) {
                eprintln!("UPDATE_FETCH_FAIL: stage: {e}");
                return 1;
            }
            let v = cache
                .staged_version()
                .map(|v| v.to_string())
                .unwrap_or_else(|| "?".to_string());
            println!("UPDATE_STAGED:{v} (from {repo}) — run `spt update apply` to apply");
            0
        }


1884	        }
1885	        Err(reason) => {
1886	            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
1887	            1
1888	        }
1889	    }
1890	}
1891
1892	/// Epoch milliseconds (the notif surfacing clock).
1893	fn now_ms() -> u64 {
1894	    std::time::SystemTime::now()
1895	        .duration_since(std::time::UNIX_EPOCH)

        }
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            1
        }
    }
}

/// Epoch milliseconds (the notif surfacing clock).
fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)


Replace the URL helpers:


Edit

/// The release-asset name for THIS platform — matches the release-publish
/// assets on spt-releases. Pure for tests.
fn platform_release_asset() -> &'static str {
    if cfg!(windows) {
        "spt-x86_64-windows.exe"
    } else {
        "spt-x86_64-linux"
    }
}

/// The two origin URLs (artifact, then its `.release.json` metadata) for a
/// release on the GitHub origin. `tag = None` ⇒ the `latest` release. Pure.
fn origin_release_urls(repo: &str, asset: &str, tag: Option<&str>) -> (String, String) {
    let base = match tag {
        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),
        None => format!("https://github.com/{repo}/releases/latest/download"),
    };
    (
        format!("{base}/{asset}"),
        format!("{base}/{asset}.release.json"),
    )
}

/// The release-download base URL on the GitHub origin. `tag = None` ⇒ the
/// `latest` release; otherwise the pinned tag. Pure.
// [impl->REQ-UPD-8]
fn origin_download_base(repo: &str, tag: Option<&str>) -> String {
    match tag {
        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),
        None => format!("https://github.com/{repo}/releases/latest/download"),
    }
}

/// URL of the signed multi-platform update set (`update-set.json`) — the
/// platform-safe origin fetch anchor (REQ-UPD-8). Pure.
// [impl->REQ-UPD-8]
fn origin_set_url(repo: &str, tag: Option<&str>) -> String {
    format!("{}/update-set.json", origin_download_base(repo, tag))
}

/// URL of one named platform asset within a release (the `asset_name` the set
/// metadata records per target triple). Pure.
// [impl->REQ-UPD-8]
fn origin_asset_url(repo: &str, tag: Option<&str>, asset: &str) -> String {
    format!("{}/{asset}", origin_download_base(repo, tag))
}


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)

/// The release-asset name for THIS platform — matches the release-publish
/// assets on spt-releases. Pure for tests.
fn platform_release_asset() -> &'static str {
    if cfg!(windows) {
        "spt-x86_64-windows.exe"
    } else {
        "spt-x86_64-linux"
    }
}

/// The two origin URLs (artifact, then its `.release.json` metadata) for a
/// release on the GitHub origin. `tag = None` ⇒ the `latest` release. Pure.
fn origin_release_urls(repo: &str, asset: &str, tag: Option<&str>) -> (String, String) {
    let base = match tag {
        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),
        None => format!("https://github.com/{repo}/releases/latest/download"),
    };
    (
        format!("{base}/{asset}"),
        format!("{base}/{asset}.release.json"),
    )
}

/// The release-download base URL on the GitHub origin. `tag = None` ⇒ the
/// `latest` release; otherwise the pinned tag. Pure.
// [impl->REQ-UPD-8]
fn origin_download_base(repo: &str, tag: Option<&str>) -> String {
    match tag {
        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),
        None => format!("https://github.com/{repo}/releases/latest/download"),
    }
}

/// URL of the signed multi-platform update set (`update-set.json`) — the
/// platform-safe origin fetch anchor (REQ-UPD-8). Pure.
// [impl->REQ-UPD-8]
fn origin_set_url(repo: &str, tag: Option<&str>) -> String {
    format!("{}/update-set.json", origin_download_base(repo, tag))
}

/// URL of one named platform asset within a release (the `asset_name` the set
/// metadata records per target triple). Pure.
// [impl->REQ-UPD-8]
fn origin_asset_url(repo: &str, tag: Option<&str>, asset: &str) -> String {
    format!("{}/{asset}", origin_download_base(repo, tag))
}

-/// The release-asset name for THIS platform — matches the release-publish

-/// assets on spt-releases. Pure for tests.

-fn platform_release_asset() -> &'static str {

+/// The release-download base URL on the GitHub origin. `tag = None` ⇒ the

+/// `latest` release; otherwise the pinned tag. Pure.

+fn origin_download_base(repo: &str, tag: Option<&str>) -> String {

+        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),

+        None => format!("https://github.com/{repo}/releases/latest/download"),

-/// The two origin URLs (artifact, then its `.release.json` metadata) for a

-/// release on the GitHub origin. `tag = None` ⇒ the `latest` release. Pure.

-fn origin_release_urls(repo: &str, asset: &str, tag: Option<&str>) -> (String, String) {

-        Some(t) => format!("https://github.com/{repo}/releases/download/{t}"),

-        None => format!("https://github.com/{repo}/releases/latest/download"),

-        format!("{base}/{asset}.release.json"),

+/// URL of the signed multi-platform update set (`update-set.json`) — the

+/// platform-safe origin fetch anchor (REQ-UPD-8). Pure.

+fn origin_set_url(repo: &str, tag: Option<&str>) -> String {

+    format!("{}/update-set.json", origin_download_base(repo, tag))

+/// URL of one named platform asset within a release (the `asset_name` the set

+/// metadata records per target triple). Pure.

+fn origin_asset_url(repo: &str, tag: Option<&str>, asset: &str) -> String {

+    format!("{}/{asset}", origin_download_base(repo, tag))

fn http_get_text(client: &reqwest::blocking::Client, url: &str) -> Result<String, String> {

let resp = client.get(url).send().map_err(|e| e.to_string())?;


Now rewrite the `cmd_update_fetch` body:


/// `spt update fetch` (REQ-UPD-7) — pull the signed release from the GitHub
/// origin and stage it through the SAME verify gate the P2P pull uses
/// (`plan_verified`: two-key signature + channel + monotonic rollback floor +
/// SHA-256), after which the existing `spt update apply` flow takes over. Closes
/// the peer-only discovery gap: a first-in-fleet / isolated node can update with
/// no peer to pull from. The signed-release anchor keeps the GitHub transport
/// untrusted-but-verified.
// [impl->REQ-UPD-7]
fn cmd_update_fetch(channel: Option<String>, tag: Option<String>) -> i32 {
    let repo =
        std::env::var("SPT_INSTALL_REPO").unwrap_or_else(|_| "SaberMage/spt-releases".to_string());
    let asset = platform_release_asset();
    let (art_url, meta_url) = origin_release_urls(&repo, asset, tag.as_deref());

    // Verify policy: the two-key anchor + the node's release-keys overlay; the
    // rollback floor is the staged version on the pinned channel (mirrors the
    // P2P path, peerloop.rs). `--channel` overrides the pin to deliberately
    // accept another channel's release.
    let cache = spt_daemon::ReleaseCache::open(&perch::spt_home().join("releases"));
    let keys = perch::identity_dir().join("release-keys.json");
    let mut policy = spt_daemon::release::VerifyPolicy::load_from(&keys, 0, now_ms());
    let pinned = policy.pinned_channel.clone();
    policy.current_version = if cache.staged_channel().as_deref() == Some(pinned.as_str()) {
        cache.staged_version().unwrap_or(0)
    } else {
        0
    };
    if let Some(c) = channel {
        policy.pinned_channel = c;
    }
    if policy.trusted_keys.is_empty() {
        eprintln!("UPDATE_FETCH_FAIL: no trusted release keys");
        return 1;
    }

    eprintln!("Fetching {asset} from {repo}…");
    let client = match reqwest::blocking::Client::builder()
        .user_agent(concat!("spt/", env!("CARGO_PKG_VERSION")))
        .build()
    {
        Ok(c) => c,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: http client: {e}");
            return 1;
        }
    };
    let meta_text = match http_get_text(&client, &meta_url) {
        Ok(t) => t,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: GET {meta_url}: {e}");
            return 1;
        }
    };
    let signed: spt_daemon::release::SignedRelease = match serde_json::from_str(&meta_text) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: bad release.json: {e}");
            return 1;
        }
    };
    let artifact = match http_get_bytes(&client, &art_url) {
        Ok(b) => b,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: GET {art_url}: {e}");
            return 1;
        }
    };

    // The SAME gate the daemon's peer pull runs — no inherited trust from the
    // GitHub transport.
    match spt_daemon::update::plan_verified(
        &spt_daemon::update::BrokerAbi::current(),
        &signed,
        &artifact,
        &policy,
    ) {
        Ok(_plan) => {
            if let Err(e) = cache.stage(&signed, &artifact) {
                eprintln!("UPDATE_FETCH_FAIL: stage: {e}");
                return 1;
            }
            let v = cache
                .staged_version()
                .map(|v| v.to_string())
                .unwrap_or_else(|| "?".to_string());
            println!("UPDATE_STAGED:{v} (from {repo}) — run `spt update apply` to apply");
            0
        }
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            1
        }
    }
}

/// `spt update fetch` (REQ-UPD-7, platform-safed by REQ-UPD-8) — pull the
/// signed multi-platform **update set** (`update-set.json` + every platform
/// artifact it names) from the GitHub origin and stage it through the SAME
/// verify gate the P2P pull uses (signature + channel + monotonic rollback
/// floor + per-platform SHA-256), after which the existing `spt update apply`
/// flow takes over.
///
/// Staging the SET (never a single, platform-blind `SignedRelease`) is the
/// v0.3.1 cross-OS brick fix: the cache holds a platform-keyed set, so the
/// local apply selects `current_platform()` and a P2P re-serve lets each peer
/// select ITS own platform. Fetching the whole set (not just this node's
/// artifact) lets this node re-serve every platform to its subnet peers.
/// Closes the peer-only discovery gap: a first-in-fleet / isolated node can
/// update with no peer to pull from. The signed set keeps the GitHub transport
/// untrusted-but-verified.
// [impl->REQ-UPD-7]
// [impl->REQ-UPD-8]
fn cmd_update_fetch(channel: Option<String>, tag: Option<String>) -> i32 {
    let repo =
        std::env::var("SPT_INSTALL_REPO").unwrap_or_else(|_| "SaberMage/spt-releases".to_string());
    let set_url = origin_set_url(&repo, tag.as_deref());

    // Verify policy: the two-key anchor + the node's release-keys overlay; the
    // rollback floor is the staged version on the pinned channel (mirrors the
    // P2P path, peerloop.rs). `--channel` overrides the pin to deliberately
    // accept another channel's release.
    let cache = spt_daemon::ReleaseCache::open(&perch::spt_home().join("releases"));
    let keys = perch::identity_dir().join("release-keys.json");
    let mut policy = spt_daemon::release::VerifyPolicy::load_from(&keys, 0, now_ms());
    let pinned = policy.pinned_channel.clone();
    policy.current_version = if cache.staged_channel().as_deref() == Some(pinned.as_str()) {
        cache.staged_version().unwrap_or(0)
    } else {
        0
    };
    if let Some(c) = channel {
        policy.pinned_channel = c;
    }
    if policy.trusted_keys.is_empty() {
        eprintln!("UPDATE_FETCH_FAIL: no trusted release keys");
        return 1;
    }

    eprintln!("Fetching update set from {repo}…");
    let client = match reqwest::blocking::Client::builder()
        .user_agent(concat!("spt/", env!("CARGO_PKG_VERSION")))
        .build()
    {
        Ok(c) => c,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: http client: {e}");
            return 1;
        }
    };

    // 1. The signed set metadata. A missing update-set.json means the release
    //    predates the platform-safe set (v0.3.2+) — refuse with a pointer to
    //    the installer rather than falling back to a platform-blind single.
    let set_text = match http_get_text(&client, &set_url) {
        Ok(t) => t,
        Err(e) => {
            eprintln!(
                "UPDATE_FETCH_FAIL: GET {set_url}: {e}\n\
                 (no platform-safe update set at this release — it may predate v0.3.2; \
                 use the installer for cross-version updates)"
            );
            return 1;
        }
    };
    let signed: spt_daemon::release::SignedUpdateSet = match serde_json::from_str(&set_text) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: bad update-set.json: {e}");
            return 1;
        }
    };

    // 2. Authenticate the set (signature + channel + rollback floor) — the same
    //    gate a peer pull runs, no inherited trust from the GitHub transport.
    let meta = match spt_daemon::release::verify_update_set_metadata(&signed, &policy) {
        Ok(m) => m,
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            return 1;
        }
    };

    // 3. Download EVERY platform artifact the set names, verifying each against
    //    its signed digest before it lands in the cache (so this node can later
    //    re-serve any platform to peers without serving poison).
    let mut artifacts: std::collections::BTreeMap<String, Vec<u8>> =
        std::collections::BTreeMap::new();
    for (triple, art) in &meta.artifacts {
        let Some(asset) = art.asset_name.as_deref() else {
            eprintln!("UPDATE_FETCH_FAIL: set artifact for {triple} has no asset_name");
            return 1;
        };
        let url = origin_asset_url(&repo, tag.as_deref(), asset);
        eprintln!("  fetching {asset} ({triple})…");
        let bytes = match http_get_bytes(&client, &url) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("UPDATE_FETCH_FAIL: GET {url}: {e}");
                return 1;
            }
        };
        if let Err(reason) =
            spt_daemon::release::verify_update_set_artifact(&meta, triple, &bytes)
        {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            return 1;
        }
        artifacts.insert(triple.clone(), bytes);
    }

    // 4. The full per-node front door for THIS platform (selects + classifies)
    //    — mirrors what apply will do, so a set with no artifact for this node
    //    fails loud here, not silently at apply.
    let local = spt_daemon::current_platform();
    let Some(local_bytes) = artifacts.get(local) else {
        eprintln!("UPDATE_FETCH_REJECTED:NoArtifactForPlatform({local:?})");
        return 1;
    };
    match spt_daemon::update::plan_verified_update_set(
        &spt_daemon::update::BrokerAbi::current(),
        &signed,
        local,
        local_bytes,
        &policy,
    ) {
        Ok(_plan) => {
            if let Err(e) = cache.stage_update_set(&signed, &artifacts) {
                eprintln!("UPDATE_FETCH_FAIL: stage: {e}");
                return 1;
            }
            let v = cache
                .staged_version()
                .map(|v| v.to_string())
                .unwrap_or_else(|| "?".to_string());
            println!(
                "UPDATE_STAGED:{v} (set, {} platforms, from {repo}) — run `spt update apply` to apply",
                meta.artifacts.len()
            );
            0
        }
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            1
        }
    }
}


/// `spt update fetch` (REQ-UPD-7) — pull the signed release from the GitHub
/// origin and stage it through the SAME verify gate the P2P pull uses
/// (`plan_verified`: two-key signature + channel + monotonic rollback floor +
/// SHA-256), after which the existing `spt update apply` flow takes over. Closes
/// the peer-only discovery gap: a first-in-fleet / isolated node can update with
/// no peer to pull from. The signed-release anchor keeps the GitHub transport
/// untrusted-but-verified.
// [impl->REQ-UPD-7]
fn cmd_update_fetch(channel: Option<String>, tag: Option<String>) -> i32 {
    let repo =
        std::env::var("SPT_INSTALL_REPO").unwrap_or_else(|_| "SaberMage/spt-releases".to_string());
    let asset = platform_release_asset();
    let (art_url, meta_url) = origin_release_urls(&repo, asset, tag.as_deref());

    // Verify policy: the two-key anchor + the node's release-keys overlay; the
    // rollback floor is the staged version on the pinned channel (mirrors the
    // P2P path, peerloop.rs). `--channel` overrides the pin to deliberately
    // accept another channel's release.
    let cache = spt_daemon::ReleaseCache::open(&perch::spt_home().join("releases"));
    let keys = perch::identity_dir().join("release-keys.json");
    let mut policy = spt_daemon::release::VerifyPolicy::load_from(&keys, 0, now_ms());
    let pinned = policy.pinned_channel.clone();
    policy.current_version = if cache.staged_channel().as_deref() == Some(pinned.as_str()) {
        cache.staged_version().unwrap_or(0)
    } else {
        0
    };
    if let Some(c) = channel {
        policy.pinned_channel = c;
    }
    if policy.trusted_keys.is_empty() {
        eprintln!("UPDATE_FETCH_FAIL: no trusted release keys");
        return 1;
    }

    eprintln!("Fetching {asset} from {repo}…");
    let client = match reqwest::blocking::Client::builder()
        .user_agent(concat!("spt/", env!("CARGO_PKG_VERSION")))
        .build()
    {
        Ok(c) => c,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: http client: {e}");
            return 1;
        }
    };
    let meta_text = match http_get_text(&client, &meta_url) {
        Ok(t) => t,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: GET {meta_url}: {e}");
            return 1;
        }
    };
    let signed: spt_daemon::release::SignedRelease = match serde_json::from_str(&meta_text) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: bad release.json: {e}");
            return 1;
        }
    };
    let artifact = match http_get_bytes(&client, &art_url) {
        Ok(b) => b,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: GET {art_url}: {e}");
            return 1;
        }
    };

    // The SAME gate the daemon's peer pull runs — no inherited trust from the
    // GitHub transport.
    match spt_daemon::update::plan_verified(
        &spt_daemon::update::BrokerAbi::current(),
        &signed,
        &artifact,
        &policy,
    ) {
        Ok(_plan) => {
            if let Err(e) = cache.stage(&signed, &artifact) {
                eprintln!("UPDATE_FETCH_FAIL: stage: {e}");
                return 1;
            }
            let v = cache
                .staged_version()
                .map(|v| v.to_string())
                .unwrap_or_else(|| "?".to_string());
            println!("UPDATE_STAGED:{v} (from {repo}) — run `spt update apply` to apply");
            0
        }
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            1
        }
    }
}

/// `spt update fetch` (REQ-UPD-7, platform-safed by REQ-UPD-8) — pull the
/// signed multi-platform **update set** (`update-set.json` + every platform
/// artifact it names) from the GitHub origin and stage it through the SAME
/// verify gate the P2P pull uses (signature + channel + monotonic rollback
/// floor + per-platform SHA-256), after which the existing `spt update apply`
/// flow takes over.
///
/// Staging the SET (never a single, platform-blind `SignedRelease`) is the
/// v0.3.1 cross-OS brick fix: the cache holds a platform-keyed set, so the
/// local apply selects `current_platform()` and a P2P re-serve lets each peer
/// select ITS own platform. Fetching the whole set (not just this node's
/// artifact) lets this node re-serve every platform to its subnet peers.
/// Closes the peer-only discovery gap: a first-in-fleet / isolated node can
/// update with no peer to pull from. The signed set keeps the GitHub transport
/// untrusted-but-verified.
// [impl->REQ-UPD-7]
// [impl->REQ-UPD-8]
fn cmd_update_fetch(channel: Option<String>, tag: Option<String>) -> i32 {
    let repo =
        std::env::var("SPT_INSTALL_REPO").unwrap_or_else(|_| "SaberMage/spt-releases".to_string());
    let set_url = origin_set_url(&repo, tag.as_deref());

    // Verify policy: the two-key anchor + the node's release-keys overlay; the
    // rollback floor is the staged version on the pinned channel (mirrors the
    // P2P path, peerloop.rs). `--channel` overrides the pin to deliberately
    // accept another channel's release.
    let cache = spt_daemon::ReleaseCache::open(&perch::spt_home().join("releases"));
    let keys = perch::identity_dir().join("release-keys.json");
    let mut policy = spt_daemon::release::VerifyPolicy::load_from(&keys, 0, now_ms());
    let pinned = policy.pinned_channel.clone();
    policy.current_version = if cache.staged_channel().as_deref() == Some(pinned.as_str()) {
        cache.staged_version().unwrap_or(0)
    } else {
        0
    };
    if let Some(c) = channel {
        policy.pinned_channel = c;
    }
    if policy.trusted_keys.is_empty() {
        eprintln!("UPDATE_FETCH_FAIL: no trusted release keys");
        return 1;
    }

    eprintln!("Fetching update set from {repo}…");
    let client = match reqwest::blocking::Client::builder()
        .user_agent(concat!("spt/", env!("CARGO_PKG_VERSION")))
        .build()
    {
        Ok(c) => c,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: http client: {e}");
            return 1;
        }
    };

    // 1. The signed set metadata. A missing update-set.json means the release
    //    predates the platform-safe set (v0.3.2+) — refuse with a pointer to
    //    the installer rather than falling back to a platform-blind single.
    let set_text = match http_get_text(&client, &set_url) {
        Ok(t) => t,
        Err(e) => {
            eprintln!(
                "UPDATE_FETCH_FAIL: GET {set_url}: {e}\n\
                 (no platform-safe update set at this release — it may predate v0.3.2; \
                 use the installer for cross-version updates)"
            );
            return 1;
        }
    };
    let signed: spt_daemon::release::SignedUpdateSet = match serde_json::from_str(&set_text) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("UPDATE_FETCH_FAIL: bad update-set.json: {e}");
            return 1;
        }
    };

    // 2. Authenticate the set (signature + channel + rollback floor) — the same
    //    gate a peer pull runs, no inherited trust from the GitHub transport.
    let meta = match spt_daemon::release::verify_update_set_metadata(&signed, &policy) {
        Ok(m) => m,
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            return 1;
        }
    };

    // 3. Download EVERY platform artifact the set names, verifying each against
    //    its signed digest before it lands in the cache (so this node can later
    //    re-serve any platform to peers without serving poison).
    let mut artifacts: std::collections::BTreeMap<String, Vec<u8>> =
        std::collections::BTreeMap::new();
    for (triple, art) in &meta.artifacts {
        let Some(asset) = art.asset_name.as_deref() else {
            eprintln!("UPDATE_FETCH_FAIL: set artifact for {triple} has no asset_name");
            return 1;
        };
        let url = origin_asset_url(&repo, tag.as_deref(), asset);
        eprintln!("  fetching {asset} ({triple})…");
        let bytes = match http_get_bytes(&client, &url) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("UPDATE_FETCH_FAIL: GET {url}: {e}");
                return 1;
            }
        };
        if let Err(reason) =
            spt_daemon::release::verify_update_set_artifact(&meta, triple, &bytes)
        {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            return 1;
        }
        artifacts.insert(triple.clone(), bytes);
    }

    // 4. The full per-node front door for THIS platform (selects + classifies)
    //    — mirrors what apply will do, so a set with no artifact for this node
    //    fails loud here, not silently at apply.
    let local = spt_daemon::current_platform();
    let Some(local_bytes) = artifacts.get(local) else {
        eprintln!("UPDATE_FETCH_REJECTED:NoArtifactForPlatform({local:?})");
        return 1;
    };
    match spt_daemon::update::plan_verified_update_set(
        &spt_daemon::update::BrokerAbi::current(),
        &signed,
        local,
        local_bytes,
        &policy,
    ) {
        Ok(_plan) => {
            if let Err(e) = cache.stage_update_set(&signed, &artifacts) {
                eprintln!("UPDATE_FETCH_FAIL: stage: {e}");
                return 1;
            }
            let v = cache
                .staged_version()
                .map(|v| v.to_string())
                .unwrap_or_else(|| "?".to_string());
            println!(
                "UPDATE_STAGED:{v} (set, {} platforms, from {repo}) — run `spt update apply` to apply",
                meta.artifacts.len()
            );
            0
        }
        Err(reason) => {
            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");
            1
        }
    }
}

-/// `spt update fetch` (REQ-UPD-7) — pull the signed release from the GitHub

-/// origin and stage it through the SAME verify gate the P2P pull uses

-/// (`plan_verified`: two-key signature + channel + monotonic rollback floor +

-/// SHA-256), after which the existing `spt update apply` flow takes over. Closes

-/// the peer-only discovery gap: a first-in-fleet / isolated node can update with

-/// no peer to pull from. The signed-release anchor keeps the GitHub transport

+/// `spt update fetch` (REQ-UPD-7, platform-safed by REQ-UPD-8) — pull the

+/// signed multi-platform **update set** (`update-set.json` + every platform

+/// artifact it names) from the GitHub origin and stage it through the SAME

+/// verify gate the P2P pull uses (signature + channel + monotonic rollback

+/// floor + per-platform SHA-256), after which the existing `spt update apply`

+/// Staging the SET (never a single, platform-blind `SignedRelease`) is the

+/// v0.3.1 cross-OS brick fix: the cache holds a platform-keyed set, so the

+/// local apply selects `current_platform()` and a P2P re-serve lets each peer

+/// select ITS own platform. Fetching the whole set (not just this node's

+/// artifact) lets this node re-serve every platform to its subnet peers.

+/// Closes the peer-only discovery gap: a first-in-fleet / isolated node can

+/// update with no peer to pull from. The signed set keeps the GitHub transport

-    let asset = platform_release_asset();

-    let (art_url, meta_url) = origin_release_urls(&repo, asset, tag.as_deref());

+    let set_url = origin_set_url(&repo, tag.as_deref());

-    eprintln!("Fetching {asset} from {repo}…");

+    eprintln!("Fetching update set from {repo}…");

-    let meta_text = match http_get_text(&client, &meta_url) {

+    // 1. The signed set metadata. A missing update-set.json means the release

+    //    predates the platform-safe set (v0.3.2+) — refuse with a pointer to

+    //    the installer rather than falling back to a platform-blind single.

+    let set_text = match http_get_text(&client, &set_url) {

-            eprintln!("UPDATE_FETCH_FAIL: GET {meta_url}: {e}");

+                "UPDATE_FETCH_FAIL: GET {set_url}: {e}\n\

+                 (no platform-safe update set at this release — it may predate v0.3.2; \

+                 use the installer for cross-version updates)"

-    let signed: spt_daemon::release::SignedRelease = match serde_json::from_str(&meta_text) {

+    let signed: spt_daemon::release::SignedUpdateSet = match serde_json::from_str(&set_text) {

-            eprintln!("UPDATE_FETCH_FAIL: bad release.json: {e}");

+            eprintln!("UPDATE_FETCH_FAIL: bad update-set.json: {e}");

-    let artifact = match http_get_bytes(&client, &art_url) {

-            eprintln!("UPDATE_FETCH_FAIL: GET {art_url}: {e}");

+    // 2. Authenticate the set (signature + channel + rollback floor) — the same

+    //    gate a peer pull runs, no inherited trust from the GitHub transport.

+    let meta = match spt_daemon::release::verify_update_set_metadata(&signed, &policy) {

+            eprintln!("UPDATE_FETCH_REJECTED:{reason:?}");

-    // The SAME gate the daemon's peer pull runs — no inherited trust from the

-    match spt_daemon::update::plan_verified(

+    // 3. Download EVERY platform artifact the set names, verifying each against

+    //    its signed digest before it lands in the cache (so this node can later

+    //    re-serve any platform to peers without serving poison).

+    let mut artifacts: std::collections::BTreeMap<String, Vec<u8>> =

+        std::collections::BTreeMap::new();

+    for (triple, art) in &meta.artifacts {

+        let Some(asset) = art.asset_name.as_deref() else {

+            eprintln!("UPDATE_FETCH_FAIL: set artifact for {triple} has no asset_name");

+        let url = origin_asset_url(&repo, tag.as_deref(), asset);

+        eprintln!("  fetching {asset} ({triple})…");

+        let bytes = match http_get_bytes(&client, &url) {

+                eprintln!("UPDATE_FETCH_FAIL: GET {url}: {e}");

+            spt_daemon::release::verify_update_set_artifact(&meta, triple, &bytes)

+        artifacts.insert(triple.clone(), bytes);

+    // 4. The full per-node front door for THIS platform (selects + classifies)

+    //    — mirrors what apply will do, so a set with no artifact for this node

+    //    fails loud here, not silently at apply.

+    let local = spt_daemon::current_platform();

+    let Some(local_bytes) = artifacts.get(local) else {

+        eprintln!("UPDATE_FETCH_REJECTED:NoArtifactForPlatform({local:?})");

+    match spt_daemon::update::plan_verified_update_set(

-            if let Err(e) = cache.stage(&signed, &artifact) {

+            if let Err(e) = cache.stage_update_set(&signed, &artifacts) {

-            println!("UPDATE_STAGED:{v} (from {repo}) — run `spt update apply` to apply");

+                "UPDATE_STAGED:{v} (set, {} platforms, from {repo}) — run `spt update apply` to apply",


Now update cli.rs tests. View the old URL test:


7355	            NodeLiveness::Online,
7356	            "recently heard ⇒ online; the label carrier does not force offline"
7357	        );
7358	        assert!(
7359	            !by.contains_key("ghost0cc"),
7360	            "a label-only non-member is not rendered"
7361	        );
7362	    }
7363
7364	    // [unit->REQ-SUBNET-1] the bounded probe seam: a fast op returns its value;
7365	    // an op that overruns the deadline yields the timeout default (so the
7366	    // --nodes view never hangs on a dead peer's full dial timeout).
7367	    #[test]
7368	    fn run_bounded_yields_value_or_timeout_default() {
7369	        use std::time::Duration;
7370	        assert_eq!(run_bounded(Duration::from_secs(5), 0, || 42), 42);
7371	        assert_eq!(
7372	            run_bounded(Duration::from_millis(50), -1, || {
7373	                std::thread::sleep(Duration::from_millis(800));
7374	                7
7375	            }),
7376	            -1,
7377	            "an op past the deadline reads as the timeout default"
7378	        );
7379	    }
7380
7381	    // [unit->REQ-UPD-7] the origin-fetch URL/asset derivation: latest vs a
7382	    // pinned tag, artifact + its .release.json, per the spt-releases layout.
7383	    #[test]
7384	    fn origin_release_urls_latest_and_tag() {
7385	        let asset = platform_release_asset();
7386	        assert!(asset == "spt-x86_64-linux" || asset == "spt-x86_64-windows.exe");
7387	        let (art, meta) = origin_release_urls("Owner/repo", "spt-x86_64-linux", None);
7388	        assert_eq!(art, "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux");
7389	        assert_eq!(
7390	            meta,
7391	            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux.release.json"
7392	        );
7393	        let (art_t, meta_t) =
7394	            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));

            NodeLiveness::Online,
            "recently heard ⇒ online; the label carrier does not force offline"
        );
        assert!(
            !by.contains_key("ghost0cc"),
            "a label-only non-member is not rendered"
        );
    }

    // [unit->REQ-SUBNET-1] the bounded probe seam: a fast op returns its value;
    // an op that overruns the deadline yields the timeout default (so the
    // --nodes view never hangs on a dead peer's full dial timeout).
    #[test]
    fn run_bounded_yields_value_or_timeout_default() {
        use std::time::Duration;
        assert_eq!(run_bounded(Duration::from_secs(5), 0, || 42), 42);
        assert_eq!(
            run_bounded(Duration::from_millis(50), -1, || {
                std::thread::sleep(Duration::from_millis(800));
                7
            }),
            -1,
            "an op past the deadline reads as the timeout default"
        );
    }

    // [unit->REQ-UPD-7] the origin-fetch URL/asset derivation: latest vs a
    // pinned tag, artifact + its .release.json, per the spt-releases layout.
    #[test]
    fn origin_release_urls_latest_and_tag() {
        let asset = platform_release_asset();
        assert!(asset == "spt-x86_64-linux" || asset == "spt-x86_64-windows.exe");
        let (art, meta) = origin_release_urls("Owner/repo", "spt-x86_64-linux", None);
        assert_eq!(art, "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux");
        assert_eq!(
            meta,
            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux.release.json"
        );
        let (art_t, meta_t) =
            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));


Mint REQ-UPD-8 in traceable-reqs.toml

Add REQ-UPD-8: platform-safe origin fetch + apply platform-guard + friendly apply message. required_stages = ["impl","unit"].

Add product_version field to metadata

Add additive serde `product_version: String` to ReleaseMetadata + UpdateSetMetadata in release.rs (default ""). Update test helpers.

Cache platform stamp + product_version helper

relcache.rs: platform sidecar (staged_platform, stage_single_with_platform, stage()/stage_update_set() clear stamp), StagedUpdate::product_version().

Apply platform guard + product_version outcome

applyhost.rs: Single path refuses unstamped/mismatched platform. Applied{version,product_version}. Update tests + add regression test.

Fetch stages update-set + apply message

cli.rs: cmd_update_fetch downloads update-set.json + all platform artifacts, verifies, stages set. Friendly apply message + render helper. Unit tests for URL/asset derivation + message.

xtask publish emits + uploads update-set.json

xtask main.rs release_publish: build+sign SignedUpdateSet over both platforms, stamp product_version from tag, write+upload update-set.json. Stamp product_version in sign_artifacts too.

Run gates

build, test, clippy, --no-default-features, traceable-reqs check, xtask check.


7393	        let (art_t, meta_t) =
7394	            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));
7395	        assert_eq!(
7396	            art_t,
7397	            "https://github.com/Owner/repo/releases/download/v0.3.1/spt-x86_64-windows.exe"
7398	        );
7399	        assert!(meta_t.ends_with("/download/v0.3.1/spt-x86_64-windows.exe.release.json"));
7400	    }
7401
7402	    // [unit->REQ-DAEMON-9] the status connection classifier: a net-LESS broker
7403	    // surfaces "no connection (retrying)" + a waiting pump and NEVER reads the
7404	    // heartbeat (so a stale pre-boot heartbeat can't render a bogus STALLED age
7405	    // — the kitsubito boot-race misreport). Net up ⇒ heartbeat drives the line.
7406	    #[test]
7407	    fn connection_lines_net_less_hides_stale_heartbeat() {
7408	        // Net-less: a wildly-stale (pre-boot) heartbeat must NOT surface as an age.
7409	        let netless = render_connection_lines(false, Some(0), 1_000_000, 30_000);
7410	        assert!(netless.contains("no connection"), "surfaces the real cause");
7411	        assert!(netless.contains("waiting on network"));
7412	        assert!(!netless.contains("STALLED"), "no false stall off a stale heartbeat");

        let (art_t, meta_t) =
            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));
        assert_eq!(
            art_t,
            "https://github.com/Owner/repo/releases/download/v0.3.1/spt-x86_64-windows.exe"
        );
        assert!(meta_t.ends_with("/download/v0.3.1/spt-x86_64-windows.exe.release.json"));
    }

    // [unit->REQ-DAEMON-9] the status connection classifier: a net-LESS broker
    // surfaces "no connection (retrying)" + a waiting pump and NEVER reads the
    // heartbeat (so a stale pre-boot heartbeat can't render a bogus STALLED age
    // — the kitsubito boot-race misreport). Net up ⇒ heartbeat drives the line.
    #[test]
    fn connection_lines_net_less_hides_stale_heartbeat() {
        // Net-less: a wildly-stale (pre-boot) heartbeat must NOT surface as an age.
        let netless = render_connection_lines(false, Some(0), 1_000_000, 30_000);
        assert!(netless.contains("no connection"), "surfaces the real cause");
        assert!(netless.contains("waiting on network"));
        assert!(!netless.contains("STALLED"), "no false stall off a stale heartbeat");


Replace the old URL test with new set/asset URL + message render tests:


    // [unit->REQ-UPD-7] the origin-fetch URL/asset derivation: latest vs a
    // pinned tag, artifact + its .release.json, per the spt-releases layout.
    #[test]
    fn origin_release_urls_latest_and_tag() {
        let asset = platform_release_asset();
        assert!(asset == "spt-x86_64-linux" || asset == "spt-x86_64-windows.exe");
        let (art, meta) = origin_release_urls("Owner/repo", "spt-x86_64-linux", None);
        assert_eq!(art, "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux");
        assert_eq!(
            meta,
            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux.release.json"
        );
        let (art_t, meta_t) =
            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));
        assert_eq!(
            art_t,
            "https://github.com/Owner/repo/releases/download/v0.3.1/spt-x86_64-windows.exe"
        );
        assert!(meta_t.ends_with("/download/v0.3.1/spt-x86_64-windows.exe.release.json"));
    }

    // [unit->REQ-UPD-8] the platform-safe origin-fetch URL derivation: the
    // signed update-set.json + each named platform asset, for `latest` vs a
    // pinned tag, per the spt-releases layout.
    #[test]
    fn origin_set_and_asset_urls_latest_and_tag() {
        assert_eq!(
            origin_set_url("Owner/repo", None),
            "https://github.com/Owner/repo/releases/latest/download/update-set.json"
        );
        assert_eq!(
            origin_set_url("Owner/repo", Some("v0.3.2")),
            "https://github.com/Owner/repo/releases/download/v0.3.2/update-set.json"
        );
        assert_eq!(
            origin_asset_url("Owner/repo", None, "spt-x86_64-linux"),
            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux"
        );
        assert_eq!(
            origin_asset_url("Owner/repo", Some("v0.3.2"), "spt-x86_64-windows.exe"),
            "https://github.com/Owner/repo/releases/download/v0.3.2/spt-x86_64-windows.exe"
        );
    }

    // [unit->REQ-UPD-8] the friendly post-apply message: a semver names the
    // version; an empty product_version (pre-v0.3.2 metadata) falls back to the
    // release counter; the changelog URL is always present.
    #[test]
    fn applied_message_renders_semver_or_counter_fallback() {
        let with = render_applied_message(6, "0.3.2");
        assert!(with.contains("Updated spt-core to v0.3.2."), "got {with}");
        assert!(with.contains(RELEASES_URL));

        let without = render_applied_message(6, "");
        assert!(
            without.contains("Updated spt-core (release 6)."),
            "got {without}"
        );
        assert!(without.contains(RELEASES_URL));
        assert!(
            !without.contains("v."),
            "no dangling 'v' when the semver is absent"
        );
    }


    // [unit->REQ-UPD-7] the origin-fetch URL/asset derivation: latest vs a
    // pinned tag, artifact + its .release.json, per the spt-releases layout.
    #[test]
    fn origin_release_urls_latest_and_tag() {
        let asset = platform_release_asset();
        assert!(asset == "spt-x86_64-linux" || asset == "spt-x86_64-windows.exe");
        let (art, meta) = origin_release_urls("Owner/repo", "spt-x86_64-linux", None);
        assert_eq!(art, "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux");
        assert_eq!(
            meta,
            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux.release.json"
        );
        let (art_t, meta_t) =
            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));
        assert_eq!(
            art_t,
            "https://github.com/Owner/repo/releases/download/v0.3.1/spt-x86_64-windows.exe"
        );
        assert!(meta_t.ends_with("/download/v0.3.1/spt-x86_64-windows.exe.release.json"));
    }

    // [unit->REQ-UPD-8] the platform-safe origin-fetch URL derivation: the
    // signed update-set.json + each named platform asset, for `latest` vs a
    // pinned tag, per the spt-releases layout.
    #[test]
    fn origin_set_and_asset_urls_latest_and_tag() {
        assert_eq!(
            origin_set_url("Owner/repo", None),
            "https://github.com/Owner/repo/releases/latest/download/update-set.json"
        );
        assert_eq!(
            origin_set_url("Owner/repo", Some("v0.3.2")),
            "https://github.com/Owner/repo/releases/download/v0.3.2/update-set.json"
        );
        assert_eq!(
            origin_asset_url("Owner/repo", None, "spt-x86_64-linux"),
            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux"
        );
        assert_eq!(
            origin_asset_url("Owner/repo", Some("v0.3.2"), "spt-x86_64-windows.exe"),
            "https://github.com/Owner/repo/releases/download/v0.3.2/spt-x86_64-windows.exe"
        );
    }

    // [unit->REQ-UPD-8] the friendly post-apply message: a semver names the
    // version; an empty product_version (pre-v0.3.2 metadata) falls back to the
    // release counter; the changelog URL is always present.
    #[test]
    fn applied_message_renders_semver_or_counter_fallback() {
        let with = render_applied_message(6, "0.3.2");
        assert!(with.contains("Updated spt-core to v0.3.2."), "got {with}");
        assert!(with.contains(RELEASES_URL));

        let without = render_applied_message(6, "");
        assert!(
            without.contains("Updated spt-core (release 6)."),
            "got {without}"
        );
        assert!(without.contains(RELEASES_URL));
        assert!(
            !without.contains("v."),
            "no dangling 'v' when the semver is absent"
        );
    }

-    // [unit->REQ-UPD-7] the origin-fetch URL/asset derivation: latest vs a

-    // pinned tag, artifact + its .release.json, per the spt-releases layout.

+    // [unit->REQ-UPD-8] the platform-safe origin-fetch URL derivation: the

+    // signed update-set.json + each named platform asset, for `latest` vs a

+    // pinned tag, per the spt-releases layout.

-    fn origin_release_urls_latest_and_tag() {

-        let asset = platform_release_asset();

-        assert!(asset == "spt-x86_64-linux" || asset == "spt-x86_64-windows.exe");

-        let (art, meta) = origin_release_urls("Owner/repo", "spt-x86_64-linux", None);

-        assert_eq!(art, "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux");

+    fn origin_set_and_asset_urls_latest_and_tag() {

-            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux.release.json"

+            origin_set_url("Owner/repo", None),

+            "https://github.com/Owner/repo/releases/latest/download/update-set.json"

-            origin_release_urls("Owner/repo", "spt-x86_64-windows.exe", Some("v0.3.1"));

-            "https://github.com/Owner/repo/releases/download/v0.3.1/spt-x86_64-windows.exe"

+            origin_set_url("Owner/repo", Some("v0.3.2")),

+            "https://github.com/Owner/repo/releases/download/v0.3.2/update-set.json"

-        assert!(meta_t.ends_with("/download/v0.3.1/spt-x86_64-windows.exe.release.json"));

+            origin_asset_url("Owner/repo", None, "spt-x86_64-linux"),

+            "https://github.com/Owner/repo/releases/latest/download/spt-x86_64-linux"

+            origin_asset_url("Owner/repo", Some("v0.3.2"), "spt-x86_64-windows.exe"),

+            "https://github.com/Owner/repo/releases/download/v0.3.2/spt-x86_64-windows.exe"

+    // [unit->REQ-UPD-8] the friendly post-apply message: a semver names the

+    // version; an empty product_version (pre-v0.3.2 metadata) falls back to the

+    // release counter; the changelog URL is always present.

+    fn applied_message_renders_semver_or_counter_fallback() {

+        let with = render_applied_message(6, "0.3.2");

+        assert!(with.contains("Updated spt-core to v0.3.2."), "got {with}");

+        assert!(with.contains(RELEASES_URL));

+        let without = render_applied_message(6, "");

+            without.contains("Updated spt-core (release 6)."),

+        assert!(without.contains(RELEASES_URL));

+            "no dangling 'v' when the semver is absent"

// [unit->REQ-DAEMON-9] the status connection classifier: a net-LESS broker

// surfaces "no connection (retrying)" + a waiting pump and NEVER reads the

// heartbeat (so a stale pre-boot heartbeat can't render a bogus STALLED age


i've put todlando in charge of signing release builds. just let them know when the fix is live, confirmed good, and changelog is up to date. todlando will sign it.


Task 5 done. Task 6: xtask. First `sign_artifacts` — add `product_version` param + stamp:


Updated task #5 status


Updated task #6 status


Prompt: i've put todlando in charge of signing release builds. just let them know when the fix is live, confirmed good, and changelog is up to date. todlando will sign it.


Noted — release signing delegated to **todlando**; I'll message them once the fix is live, confirmed green, and CHANGELOG is current. Continuing the implementation first.


fn sign_artifacts(
    identity: &spt_proto::identity::Identity,
    key_id: &str,
    version: u64,
    expires_days: u64,
    artifacts: &[PathBuf],
) -> Vec<PathBuf> {

fn sign_artifacts(
    identity: &spt_proto::identity::Identity,
    key_id: &str,
    version: u64,
    expires_days: u64,
    product_version: &str,
    artifacts: &[PathBuf],
) -> Vec<PathBuf> {


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

fn sign_artifacts(
    identity: &spt_proto::identity::Identity,
    key_id: &str,
    version: u64,
    expires_days: u64,
    artifacts: &[PathBuf],
) -> Vec<PathBuf> {

fn sign_artifacts(
    identity: &spt_proto::identity::Identity,
    key_id: &str,
    version: u64,
    expires_days: u64,
    product_version: &str,
    artifacts: &[PathBuf],
) -> Vec<PathBuf> {

let now_ms = std::time::SystemTime::now()


            artifact_sha256: spt_daemon::release::sha256_hex(&bytes),
            brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
            broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,
        };
        let metadata_json = serde_json::to_string(&meta).expect("serialize metadata");

            artifact_sha256: spt_daemon::release::sha256_hex(&bytes),
            brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
            broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,
            product_version: product_version.to_string(),
        };
        let metadata_json = serde_json::to_string(&meta).expect("serialize metadata");


            artifact_sha256: spt_daemon::release::sha256_hex(&bytes),
            brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
            broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,
        };
        let metadata_json = serde_json::to_string(&meta).expect("serialize metadata");

            artifact_sha256: spt_daemon::release::sha256_hex(&bytes),
            brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
            broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,
            product_version: product_version.to_string(),
        };
        let metadata_json = serde_json::to_string(&meta).expect("serialize metadata");

+            product_version: product_version.to_string(),

let sig = identity.sign(metadata_json.as_bytes());


Now update the two `sign_artifacts` callers (release_sign, release_publish) and debug_rollout's set literal:


    let key_id = key_id.expect("--key-id <id> (must match a BUILTIN_RELEASE_KEYS id)");
    let version = version.expect("--version <u64> (the monotonic release counter)");
    assert!(!artifacts.is_empty(), "at least one artifact to sign");

    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    sign_artifacts(&identity, &key_id, version, expires_days, &artifacts);
}

    let key_id = key_id.expect("--key-id <id> (must match a BUILTIN_RELEASE_KEYS id)");
    let version = version.expect("--version <u64> (the monotonic release counter)");
    assert!(!artifacts.is_empty(), "at least one artifact to sign");

    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    // The low-level manual signer carries no release tag, so no semver to
    // stamp — the post-apply message falls back to the release counter.
    sign_artifacts(&identity, &key_id, version, expires_days, "", &artifacts);
}


    let key_id = key_id.expect("--key-id <id> (must match a BUILTIN_RELEASE_KEYS id)");
    let version = version.expect("--version <u64> (the monotonic release counter)");
    assert!(!artifacts.is_empty(), "at least one artifact to sign");

    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    sign_artifacts(&identity, &key_id, version, expires_days, &artifacts);
}

    let key_id = key_id.expect("--key-id <id> (must match a BUILTIN_RELEASE_KEYS id)");
    let version = version.expect("--version <u64> (the monotonic release counter)");
    assert!(!artifacts.is_empty(), "at least one artifact to sign");

    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    // The low-level manual signer carries no release tag, so no semver to
    // stamp — the post-apply message falls back to the release counter.
    sign_artifacts(&identity, &key_id, version, expires_days, "", &artifacts);
}

-    sign_artifacts(&identity, &key_id, version, expires_days, &artifacts);

+    // The low-level manual signer carries no release tag, so no semver to

+    // stamp — the post-apply message falls back to the release counter.

+    sign_artifacts(&identity, &key_id, version, expires_days, "", &artifacts);

/// Resolve the signing identity. Preference order:


            asset_name: path
                    .file_name()
                    .and_then(|n| n.to_str())
                    .map(str::to_string),
            },
        );
        artifact_bytes.insert(target, bytes);
    }

    let meta = spt_daemon::UpdateSetMetadata {
        version,
        channel: "debug".to_string(),
        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,
        key_id: key_id.clone(),
        artifacts: artifact_meta,
        provenance: Some(spt_daemon::UpdateSetProvenance {
            git_commit: git_stdout(&root, &["rev-parse", "HEAD"]),
            git_dirty: Some(
                git_stdout(&root, &["status", "--porcelain"]).is_some_and(|s| !s.is_empty()),
            ),
            build_host: build_host(),
            build_timestamp_ms: Some(now),
        }),
    };

            asset_name: path
                    .file_name()
                    .and_then(|n| n.to_str())
                    .map(str::to_string),
            },
        );
        artifact_bytes.insert(target, bytes);
    }

    let meta = spt_daemon::UpdateSetMetadata {
        version,
        channel: "debug".to_string(),
        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,
        key_id: key_id.clone(),
        artifacts: artifact_meta,
        provenance: Some(spt_daemon::UpdateSetProvenance {
            git_commit: git_stdout(&root, &["rev-parse", "HEAD"]),
            git_dirty: Some(
                git_stdout(&root, &["status", "--porcelain"]).is_some_and(|s| !s.is_empty()),
            ),
            build_host: build_host(),
            build_timestamp_ms: Some(now),
        }),
        // Debug rollouts use the monotonic counter, not a semver — the apply
        // message falls back to "release N".
        product_version: String::new(),
    };


            asset_name: path
                    .file_name()
                    .and_then(|n| n.to_str())
                    .map(str::to_string),
            },
        );
        artifact_bytes.insert(target, bytes);
    }

    let meta = spt_daemon::UpdateSetMetadata {
        version,
        channel: "debug".to_string(),
        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,
        key_id: key_id.clone(),
        artifacts: artifact_meta,
        provenance: Some(spt_daemon::UpdateSetProvenance {
            git_commit: git_stdout(&root, &["rev-parse", "HEAD"]),
            git_dirty: Some(
                git_stdout(&root, &["status", "--porcelain"]).is_some_and(|s| !s.is_empty()),
            ),
            build_host: build_host(),
            build_timestamp_ms: Some(now),
        }),
    };

            asset_name: path
                    .file_name()
                    .and_then(|n| n.to_str())
                    .map(str::to_string),
            },
        );
        artifact_bytes.insert(target, bytes);
    }

    let meta = spt_daemon::UpdateSetMetadata {
        version,
        channel: "debug".to_string(),
        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,
        key_id: key_id.clone(),
        artifacts: artifact_meta,
        provenance: Some(spt_daemon::UpdateSetProvenance {
            git_commit: git_stdout(&root, &["rev-parse", "HEAD"]),
            git_dirty: Some(
                git_stdout(&root, &["status", "--porcelain"]).is_some_and(|s| !s.is_empty()),
            ),
            build_host: build_host(),
            build_timestamp_ms: Some(now),
        }),
        // Debug rollouts use the monotonic counter, not a semver — the apply
        // message falls back to "release N".
        product_version: String::new(),
    };

+        // Debug rollouts use the monotonic counter, not a semver — the apply

+        // message falls back to "release N".

+        product_version: String::new(),

let metadata_json = serde_json::to_string(&meta).expect("serialize update set");


Now the core of Task 6: build + sign + write + upload `update-set.json` in `release_publish`:


    // 3. Sign.
    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    let signed = sign_artifacts(&identity, &key_id, version, expires_days, &artifacts);

    // 4. Upload the SignedRelease assets.
    let mut upload = Command::new("gh");
    upload.args(["release", "upload", &tag, "--repo", REPO, "--clobber"]);
    for s in &signed {
        upload.arg(s);
    }
    run(&mut upload, "gh release upload");

    // 3. Sign. The human-facing semver is the tag without its `v` (the
    //    post-apply message reads it; REQ-UPD-8).
    let product_version = tag.strip_prefix('v').unwrap_or(&tag).to_string();
    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    let signed = sign_artifacts(
        &identity,
        &key_id,
        version,
        expires_days,
        &product_version,
        &artifacts,
    );

    // 3b. Build + sign the platform-safe update set (REQ-UPD-8): one signed
    //     document covering EVERY platform artifact, keyed by Rust target
    //     triple. `spt update fetch` consumes this (not the platform-blind
    //     singles), so a peer selects ITS own platform — the v0.3.1 cross-OS
    //     brick fix.
    let set_path = build_signed_update_set(
        &identity,
        &key_id,
        version,
        expires_days,
        &product_version,
        &artifacts,
        &work,
    );

    // 4. Upload the SignedRelease assets + the signed update set.
    let mut upload = Command::new("gh");
    upload.args(["release", "upload", &tag, "--repo", REPO, "--clobber"]);
    for s in &signed {
        upload.arg(s);
    }
    upload.arg(&set_path);
    run(&mut upload, "gh release upload");


    // 3. Sign.
    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    let signed = sign_artifacts(&identity, &key_id, version, expires_days, &artifacts);

    // 4. Upload the SignedRelease assets.
    let mut upload = Command::new("gh");
    upload.args(["release", "upload", &tag, "--repo", REPO, "--clobber"]);
    for s in &signed {
        upload.arg(s);
    }
    run(&mut upload, "gh release upload");

    // 3. Sign. The human-facing semver is the tag without its `v` (the
    //    post-apply message reads it; REQ-UPD-8).
    let product_version = tag.strip_prefix('v').unwrap_or(&tag).to_string();
    let identity = signing_identity();
    println!(
        "signing as: {} (key_id {key_id})",
        identity.public_key().to_hex()
    );
    let signed = sign_artifacts(
        &identity,
        &key_id,
        version,
        expires_days,
        &product_version,
        &artifacts,
    );

    // 3b. Build + sign the platform-safe update set (REQ-UPD-8): one signed
    //     document covering EVERY platform artifact, keyed by Rust target
    //     triple. `spt update fetch` consumes this (not the platform-blind
    //     singles), so a peer selects ITS own platform — the v0.3.1 cross-OS
    //     brick fix.
    let set_path = build_signed_update_set(
        &identity,
        &key_id,
        version,
        expires_days,
        &product_version,
        &artifacts,
        &work,
    );

    // 4. Upload the SignedRelease assets + the signed update set.
    let mut upload = Command::new("gh");
    upload.args(["release", "upload", &tag, "--repo", REPO, "--clobber"]);
    for s in &signed {
        upload.arg(s);
    }
    upload.arg(&set_path);
    run(&mut upload, "gh release upload");

"no binaries found in the draft release"

+    // 3. Sign. The human-facing semver is the tag without its `v` (the

+    //    post-apply message reads it; REQ-UPD-8).

+    let product_version = tag.strip_prefix('v').unwrap_or(&tag).to_string();

-    let signed = sign_artifacts(&identity, &key_id, version, expires_days, &artifacts);

-    // 4. Upload the SignedRelease assets.

+    // 3b. Build + sign the platform-safe update set (REQ-UPD-8): one signed

+    //     document covering EVERY platform artifact, keyed by Rust target

+    //     triple. `spt update fetch` consumes this (not the platform-blind

+    //     singles), so a peer selects ITS own platform — the v0.3.1 cross-OS

+    let set_path = build_signed_update_set(

+    // 4. Upload the SignedRelease assets + the signed update set.


Now add the `build_signed_update_set` + `asset_target_triple` helpers, before `release_publish`:


/// The whole maintainer half of a release in one local command (the manual
/// ceremony, automated — the seed still never leaves this machine):

/// Map a release asset file name to its Rust target triple — the key the
/// update set + recipient platform selection use (`release::current_platform`).
// [impl->REQ-UPD-8]
fn asset_target_triple(asset_name: &str) -> &'static str {
    match asset_name {
        "spt-x86_64-linux" => "x86_64-unknown-linux-gnu",
        "spt-x86_64-windows.exe" => "x86_64-pc-windows-msvc",
        other => panic!("unknown release asset {other:?} — no target-triple mapping"),
    }
}

/// Build, sign, verify, and write `update-set.json`: the signed multi-platform
/// [`spt_daemon::SignedUpdateSet`] covering every release artifact, keyed by
/// target triple. The signature covers all platform digests at once, so `spt
/// update fetch` stages a platform-safe set rather than a platform-blind single
/// (REQ-UPD-8). Returns the written path (uploaded as a release asset).
// [impl->REQ-UPD-8]
fn build_signed_update_set(
    identity: &spt_proto::identity::Identity,
    key_id: &str,
    version: u64,
    expires_days: u64,
    product_version: &str,
    artifacts: &[PathBuf],
    out_dir: &Path,
) -> PathBuf {
    let now = system_time_ms();
    let mut artifact_meta = std::collections::BTreeMap::new();
    let mut artifact_bytes = std::collections::BTreeMap::new();
    for path in artifacts {
        let name = path
            .file_name()
            .and_then(|n| n.to_str())
            .expect("artifact file name");
        let triple = asset_target_triple(name).to_string();
        let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
        artifact_meta.insert(
            triple.clone(),
            spt_daemon::UpdateArtifactMetadata {
                artifact_sha256: spt_daemon::sha256_hex(&bytes),
                brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
                broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,
                asset_name: Some(name.to_string()),
            },
        );
        artifact_bytes.insert(triple, bytes);
    }

    let meta = spt_daemon::UpdateSetMetadata {
        version,
        channel: spt_daemon::release::DEFAULT_CHANNEL.to_string(),
        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,
        key_id: key_id.to_string(),
        artifacts: artifact_meta,
        provenance: None,
        product_version: product_version.to_string(),
    };
    // Sign the EXACT bytes stored as `metadata_json` (compact) — verification
    // never re-serializes (release.rs §SignedUpdateSet).
    let metadata_json = serde_json::to_string(&meta).expect("serialize update set");
    let sig = identity.sign(metadata_json.as_bytes());
    let signed = spt_daemon::SignedUpdateSet {
        metadata_json,
        signature_hex: sig.iter().map(|b| format!("{b:02x}")).collect(),
    };

    // Sanity: the freshly-signed set must verify against its own key, and every
    // platform's bytes must match the signed digest — never publish a set the
    // node-side gate would reject.
    let pub_hex = identity.public_key().to_hex();
    let vk = spt_daemon::parse_verifying_key(&pub_hex).expect("public key parses");
    let policy = spt_daemon::VerifyPolicy {
        trusted_keys: std::collections::BTreeMap::from([(key_id.to_string(), vk)]),
        revoked_keys: std::collections::BTreeSet::new(),
        pinned_channel: spt_daemon::release::DEFAULT_CHANNEL.to_string(),
        current_version: 0,
        now_ms: now,
    };
    let verified = spt_daemon::verify_update_set_metadata(&signed, &policy)
        .expect("freshly signed update set verifies");
    for (triple, bytes) in &artifact_bytes {
        spt_daemon::verify_update_set_artifact(&verified, triple, bytes)
            .unwrap_or_else(|e| panic!("{triple}: update-set artifact verification failed: {e}"));
    }

    let out = out_dir.join("update-set.json");
    std::fs::write(&out, serde_json::to_string_pretty(&signed).unwrap())
        .expect("write update-set.json");
    println!(
        "built update-set.json (v{version} \"{product_version}\", platforms {})",
        verified
            .artifacts
            .keys()
            .cloned()
            .collect::<Vec<_>>()
            .join(",")
    );
    out
}

/// The whole maintainer half of a release in one local command (the manual
/// ceremony, automated — the seed still never leaves this machine):


/// Map a release asset file name to its Rust target triple — the key the
/// update set + recipient platform selection use (`release::current_platform`).
// [impl->REQ-UPD-8]
fn asset_target_triple(asset_name: &str) -> &'static str {
    match asset_name {
        "spt-x86_64-linux" => "x86_64-unknown-linux-gnu",
        "spt-x86_64-windows.exe" => "x86_64-pc-windows-msvc",
        other => panic!("unknown release asset {other:?} — no target-triple mapping"),
    }
}

/// Build, sign, verify, and write `update-set.json`: the signed multi-platform
/// [`spt_daemon::SignedUpdateSet`] covering every release artifact, keyed by
/// target triple. The signature covers all platform digests at once, so `spt
/// update fetch` stages a platform-safe set rather than a platform-blind single
/// (REQ-UPD-8). Returns the written path (uploaded as a release asset).
// [impl->REQ-UPD-8]
fn build_signed_update_set(
    identity: &spt_proto::identity::Identity,
    key_id: &str,
    version: u64,
    expires_days: u64,
    product_version: &str,
    artifacts: &[PathBuf],
    out_dir: &Path,
) -> PathBuf {
    let now = system_time_ms();
    let mut artifact_meta = std::collections::BTreeMap::new();
    let mut artifact_bytes = std::collections::BTreeMap::new();
    for path in artifacts {
        let name = path
            .file_name()
            .and_then(|n| n.to_str())
            .expect("artifact file name");
        let triple = asset_target_triple(name).to_string();
        let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
        artifact_meta.insert(
            triple.clone(),
            spt_daemon::UpdateArtifactMetadata {
                artifact_sha256: spt_daemon::sha256_hex(&bytes),
                brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
                broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,
                asset_name: Some(name.to_string()),
            },
        );
        artifact_bytes.insert(triple, bytes);
    }

    let meta = spt_daemon::UpdateSetMetadata {
        version,
        channel: spt_daemon::release::DEFAULT_CHANNEL.to_string(),
        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,
        key_id: key_id.to_string(),
        artifacts: artifact_meta,
        provenance: None,
        product_version: product_version.to_string(),
    };
    // Sign the EXACT bytes stored as `metadata_json` (compact) — verification
    // never re-serializes (release.rs §SignedUpdateSet).
    let metadata_json = serde_json::to_string(&meta).expect("serialize update set");
    let sig = identity.sign(metadata_json.as_bytes());
    let signed = spt_daemon::SignedUpdateSet {
        metadata_json,
        signature_hex: sig.iter().map(|b| format!("{b:02x}")).collect(),
    };

    // Sanity: the freshly-signed set must verify against its own key, and every
    // platform's bytes must match the signed digest — never publish a set the
    // node-side gate would reject.
    let pub_hex = identity.public_key().to_hex();
    let vk = spt_daemon::parse_verifying_key(&pub_hex).expect("public key parses");
    let policy = spt_daemon::VerifyPolicy {
        trusted_keys: std::collections::BTreeMap::from([(key_id.to_string(), vk)]),
        revoked_keys: std::collections::BTreeSet::new(),
        pinned_channel: spt_daemon::release::DEFAULT_CHANNEL.to_string(),
        current_version: 0,
        now_ms: now,
    };
    let verified = spt_daemon::verify_update_set_metadata(&signed, &policy)
        .expect("freshly signed update set verifies");
    for (triple, bytes) in &artifact_bytes {
        spt_daemon::verify_update_set_artifact(&verified, triple, bytes)
            .unwrap_or_else(|e| panic!("{triple}: update-set artifact verification failed: {e}"));
    }

    let out = out_dir.join("update-set.json");
    std::fs::write(&out, serde_json::to_string_pretty(&signed).unwrap())
        .expect("write update-set.json");
    println!(
        "built update-set.json (v{version} \"{product_version}\", platforms {})",
        verified
            .artifacts
            .keys()
            .cloned()
            .collect::<Vec<_>>()
            .join(",")
    );
    out
}

/// The whole maintainer half of a release in one local command (the manual
/// ceremony, automated — the seed still never leaves this machine):

+/// Map a release asset file name to its Rust target triple — the key the

+/// update set + recipient platform selection use (`release::current_platform`).

+fn asset_target_triple(asset_name: &str) -> &'static str {

+        "spt-x86_64-linux" => "x86_64-unknown-linux-gnu",

+        "spt-x86_64-windows.exe" => "x86_64-pc-windows-msvc",

+        other => panic!("unknown release asset {other:?} — no target-triple mapping"),

+/// Build, sign, verify, and write `update-set.json`: the signed multi-platform

+/// [`spt_daemon::SignedUpdateSet`] covering every release artifact, keyed by

+/// target triple. The signature covers all platform digests at once, so `spt

+/// update fetch` stages a platform-safe set rather than a platform-blind single

+/// (REQ-UPD-8). Returns the written path (uploaded as a release asset).

+    identity: &spt_proto::identity::Identity,

+    let mut artifact_meta = std::collections::BTreeMap::new();

+    let mut artifact_bytes = std::collections::BTreeMap::new();

+            .expect("artifact file name");

+        let triple = asset_target_triple(name).to_string();

+        let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));

+            spt_daemon::UpdateArtifactMetadata {

+                artifact_sha256: spt_daemon::sha256_hex(&bytes),

+                brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,

+                broker_resource_abi: spt_daemon::update::BROKER_RESOURCE_ABI,

+                asset_name: Some(name.to_string()),

+        artifact_bytes.insert(triple, bytes);

+    let meta = spt_daemon::UpdateSetMetadata {

+        channel: spt_daemon::release::DEFAULT_CHANNEL.to_string(),

+        expires_at_ms: now + expires_days * 24 * 60 * 60 * 1000,

+        product_version: product_version.to_string(),

+    // Sign the EXACT bytes stored as `metadata_json` (compact) — verification

+    // never re-serializes (release.rs §SignedUpdateSet).

+    let metadata_json = serde_json::to_string(&meta).expect("serialize update set");

+    let sig = identity.sign(metadata_json.as_bytes());

+    let signed = spt_daemon::SignedUpdateSet {

+        signature_hex: sig.iter().map(|b| format!("{b:02x}")).collect(),

+    // Sanity: the freshly-signed set must verify against its own key, and every

+    // platform's bytes must match the signed digest — never publish a set the

+    let pub_hex = identity.public_key().to_hex();

+    let vk = spt_daemon::parse_verifying_key(&pub_hex).expect("public key parses");

+    let policy = spt_daemon::VerifyPolicy {

+        trusted_keys: std::collections::BTreeMap::from([(key_id.to_string(), vk)]),

+        revoked_keys: std::collections::BTreeSet::new(),

+        pinned_channel: spt_daemon::release::DEFAULT_CHANNEL.to_string(),

+    let verified = spt_daemon::verify_update_set_metadata(&signed, &policy)

+        .expect("freshly signed update set verifies");

+    for (triple, bytes) in &artifact_bytes {

+        spt_daemon::verify_update_set_artifact(&verified, triple, bytes)

+            .unwrap_or_else(|e| panic!("{triple}: update-set artifact verification failed: {e}"));

+    let out = out_dir.join("update-set.json");

+    std::fs::write(&out, serde_json::to_string_pretty(&signed).unwrap())

+        .expect("write update-set.json");

+        "built update-set.json (v{version} \"{product_version}\", platforms {})",


Mint REQ-UPD-8 in traceable-reqs.toml

Add product_version field to metadata

Fetch stages update-set + apply message

Run gates


Verify `Path` is imported in xtask:


Bash

grep -n "use std::path" crates/xtask/src/main.rs | head

Check Path import in xtask


17:use std::path::{Path, PathBuf};


`Path` imported. Now compile + test. Building workspace first:


Build workspace


   Compiling ring v0.17.14
   Compiling rustls v0.23.40
   Compiling rustls-webpki v0.103.13
   Compiling tokio-rustls v0.26.4
   Compiling noq-proto v0.17.0
   Compiling rustls-platform-verifier v0.7.0
   Compiling hickory-net v0.26.0-beta.4
   Compiling hyper-rustls v0.27.9
   Compiling tokio-websockets v0.13.2
   Compiling reqwest v0.13.4
   Compiling hickory-resolver v0.26.0-beta.4
   Compiling noq v0.18.0
   Compiling iroh-relay v0.98.0
   Compiling iroh v0.98.2
   Compiling spt-net v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-daemon v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling xtask v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
   Compiling spt v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 35.11s


Workspace builds. Now tests for the affected crates + clippy + no-default-features:


cargo test -p spt-daemon --lib 2>&1 | tail -30

Run spt-daemon lib unit tests


test shellhost::tests::close_shell_ephemeral_tears_down_and_frees_the_slot ... ok
test transport::tests::local_socket_handshake_and_frame_round_trip ... ok
test transport::tests::local_socket_rejects_wrong_role ... ok
test update::tests::apply_refuses_non_brain_only_plan ... ok
test update::tests::classify_brain_only_when_broker_hosts_new_brain ... ok
test update::tests::classify_broker_breaking_on_resource_abi_change ... ok
test update::tests::classify_broker_compatible_when_brain_below_floor ... ok
test update::tests::plan_verified_produces_no_plan_for_a_rejected_release ... ok
test update::tests::plan_verified_update_set_uses_selected_platform_spec ... ok
test update::tests::plan_verified_yields_a_plan_for_a_valid_release ... ok
test relay::tests::respawn_loses_no_spooled_message ... ok
test xfer::tests::resolve_under_confines_to_root ... ok
test xfer::tests::recv_state_chunks_idempotent_and_commit_replays_safely ... ok
test shellwake::tests::reconcile_flips_watchers_with_instance_state ... ok
test shellhost::tests::close_shell_delivers_pre_close_then_kills_and_flips_offline ... ok
test relay::tests::run_honors_stop ... ok
test resting::tests::daemon_rest_event_arms_gate_at_rest_edge_only ... ok
test shellhost::tests::close_shell_dead_child_is_prompt_and_manifestless_never_erases ... ok
test resting::tests::rest_edges_cascade_shells_with_divergence ... ok
test seedmap::tests::put_refreshes_existing ... ok
test seedmap::tests::put_then_take_consumes_once ... ok
test seedmap::tests::stop_op_acks_then_serve_returns ... ok
test seedmap::tests::take_absent_is_none ... ok
test serveprobe::tests::is_serving_subnet_honors_membership_and_detach ... ok
test shellwake::tests::resolve_wake_revives_a_suspended_owner_without_double_launch ... ok
test shellwake::tests::watcher_crash_exits_respawn_then_give_up ... ok
test sync::tests::select_refs_scopes_two_tiers ... ok

test result: ok. 224 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.67s


224 pass, 0 fail. Now spt crate tests + clippy + no-default-features:


cargo test -p spt --lib 2>&1 | tail -8; echo "=== relcache/applyhost new tests ==="; cargo test -p spt-daemon --lib -- applyhost::tests::apply_staged_refuses_platform_blind relcache::tests::single_platform_stamp 2>&1 | tail -8

spt cli tests + targeted new tests


error: no library targets found in package `spt`
=== relcache/applyhost new tests ===
     Running unittests src\lib.rs (target\debug\deps\spt_daemon-2b5c964f7e15953b.exe)

running 2 tests
test relcache::tests::single_platform_stamp_is_explicit_and_cleared_on_restage ... ok
test applyhost::tests::apply_staged_refuses_platform_blind_or_mismatched_single ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 222 filtered out; finished in 0.02s


spt is a bin crate. Run its tests + clippy + no-default-features build:


cargo test -p spt --bins 2>&1 | tail -12

Run spt binary unit tests


test cli::tests::shell_channels_relay_sensory_and_text_file ... ok
test cli::tests::shell_owner_exclusivity_negatives ... ok
test cli::tests::shell_relink_relaunches_offline_instances_only ... ok
test cli::tests::shell_spawn_approval_floor_gates ... ok
test cli::tests::shell_spawn_cap_and_over_cap_gates ... ok
test cli::tests::shell_spawn_gates_on_the_registered_set ... ok
test cli::tests::shutdown_soft_stops_and_suspends ... ok
test wansend::tests::remote_rest_suspends_and_wakes_with_the_deferred_gate ... ok
test wansend::tests::wan_send_ships_to_the_resolved_node ... ok

test result: ok. 108 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 28.54s


108 pass. Now clippy + no-default-features + integration tests:


cargo clippy --workspace --all-targets 2>&1 | tail -25

Clippy workspace all targets


warning: useless use of `format!`
   --> crates\spt-daemon\src\applyhost.rs:120:60
    |
120 |                       return Ok(ApplyStagedOutcome::Rejected(format!(
    |  ____________________________________________________________^
121 | |                         "staged single release has no platform stamp (pre-v0.3.2 / peer-propagated) — \
122 | |                          refusing a platform-blind single; re-fetch with `spt update fetch` on v0.3.2+ \
123 | |                          (stages a platform-safe update set)"
124 | |                     )))
    | |_____________________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#useless_format
    = note: `#[warn(clippy::useless_format)]` on by default
help: consider using `.to_string()`
    |
120 ~                     return Ok(ApplyStagedOutcome::Rejected("staged single release has no platform stamp (pre-v0.3.2 / peer-propagated) — \
121 +                          refusing a platform-blind single; re-fetch with `spt update fetch` on v0.3.2+ \
122 ~                          (stages a platform-safe update set)".to_string()))
    |

warning: `spt-daemon` (lib) generated 1 warning (run `cargo clippy --fix --lib -p spt-daemon` to apply 1 suggestion)
    Checking spt v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
warning: `spt-daemon` (lib test) generated 1 warning (1 duplicate)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.45s


Clippy flags the no-arg `format!`. Fix:


Edit

                None => {
                    return Ok(ApplyStagedOutcome::Rejected(format!(
                        "staged single release has no platform stamp (pre-v0.3.2 / peer-propagated) — \
                         refusing a platform-blind single; re-fetch with `spt update fetch` on v0.3.2+ \
                         (stages a platform-safe update set)"
                    )))
                }

                None => {
                    return Ok(ApplyStagedOutcome::Rejected(
                        "staged single release has no platform stamp (pre-v0.3.2 / peer-propagated) — \
                         refusing a platform-blind single; re-fetch with `spt update fetch` on v0.3.2+ \
                         (stages a platform-safe update set)"
                            .to_string(),
                    ))
                }


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

                None => {
                    return Ok(ApplyStagedOutcome::Rejected(format!(
                        "staged single release has no platform stamp (pre-v0.3.2 / peer-propagated) — \
                         refusing a platform-blind single; re-fetch with `spt update fetch` on v0.3.2+ \
                         (stages a platform-safe update set)"
                    )))
                }

                None => {
                    return Ok(ApplyStagedOutcome::Rejected(
                        "staged single release has no platform stamp (pre-v0.3.2 / peer-propagated) — \
                         refusing a platform-blind single; re-fetch with `spt update fetch` on v0.3.2+ \
                         (stages a platform-safe update set)"
                            .to_string(),
                    ))
                }

-                    return Ok(ApplyStagedOutcome::Rejected(format!(

+                    return Ok(ApplyStagedOutcome::Rejected(

+                            .to_string(),


Bash

cargo clippy --workspace --all-targets 2>&1 | tail -6; echo "=== no-default-features ==="; cargo build --workspace --no-default-features 2>&1 | tail -8

Re-run clippy + no-default-features build


    Checking spt-daemon v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.39s
=== no-default-features ===
   Compiling spt-daemon v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling xtask v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
   Compiling spt v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.71s


Clippy clean. Now integration tests (propagate/peerloop/dispatch) + traceable-reqs + xtask check:


cargo test -p spt-daemon --test propagate --test peerloop --test dispatch 2>&1 | tail -20

Run update propagation integration tests


     Running tests\peerloop.rs (target\debug\deps\peerloop-e40f31414cb44bf5.exe)

running 1 test
test pump_and_dispatch_self_drive_the_subnet ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.18s

     Running tests\propagate.rs (target\debug\deps\propagate-3ce8698d5f714c2a.exe)

running 6 tests
test untrusted_origin_gets_no_offer ... ok
test rollback_offer_is_rejected_before_any_fetch ... ok
test tampered_relay_artifact_is_rejected_and_never_staged ... ok
test update_set_propagates_selected_platform_artifact ... ok
test status_query_drives_the_convergence_table_end_to_end ... ok
test chain_self_heal_propagates_with_gate_at_every_hop ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s


cargo run -p xtask -- check 2>&1 | tail -15; echo "=== traceable-reqs ==="; traceable-reqs check 2>&1 | tail -25

xtask gen-drift check + traceable-reqs check


   Compiling spt-daemon v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling xtask v0.3.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.01s
     Running `target\debug\xtask.exe check`
xtask check: OK
=== traceable-reqs ===
  [OK] REQ-TERM-2  required: [impl, unit]  stages: -doc +impl +unit -int
       session-surface abstraction; send-keys + send-line injection
  [OK] REQ-TERM-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Byte-stream remote terminal streaming for v1
  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: +doc +impl +unit +int
       Live activity buffer (PTY digest): adapter-supplied patterns over broker PTY, spt digest pull + delta-stream, opt-in Path-B log
  [OK] REQ-UPD-1  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Peer-propagated update over P2P
  [OK] REQ-UPD-2  required: [impl, unit]  stages: -doc +impl +unit -int
       All binaries signature-verified before handoff
  [OK] REQ-UPD-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       No endpoint process terminates/suspends during self-update
  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Update gated on user confirmation by default; opt-in full-auto
  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
       spt-core ripple-updates registered adapters
  [OK] REQ-UPD-6  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       Platform-targeted update sets and debug rollout: signed multi-platform update metadata, recipient platform selection, channel-scoped monotonic counters, debug-channel opt-in via release-key overlay, local staging plus pull-based peer propagation, and maintainer-only convergence tooling (ADR-0016)
  [--] REQ-UPD-7  required: [impl, unit]  stages: -doc +impl -unit -int
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.

Findings:
  [must] missing_stage REQ-UPD-7 stage=unit — Requirement is missing unit test evidence


REQ-UPD-8 OK, but REQ-UPD-7 lost its unit test (I replaced the old URL test). The new origin-fetch URL test satisfies REQ-UPD-7's URL-derivation unit too — tag it for both:


Edit

    // [unit->REQ-UPD-8] the platform-safe origin-fetch URL derivation: the
    // signed update-set.json + each named platform asset, for `latest` vs a
    // pinned tag, per the spt-releases layout.
    #[test]
    fn origin_set_and_asset_urls_latest_and_tag() {

    // [unit->REQ-UPD-7] the origin-source fetch URL derivation (latest vs a
    // pinned tag, per the spt-releases layout) — now in its platform-safe
    // form: [unit->REQ-UPD-8] the signed update-set.json + each named platform
    // asset rather than a single per-platform artifact.
    #[test]
    fn origin_set_and_asset_urls_latest_and_tag() {