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

required_stages = ["impl", "unit"]  # activate v0.3.1. impl = `spt update fetch [--channel <ch>] [--tag vX.Y.Z]` (default latest on pinned channel); HTTPS GET of artifact + <asset>.release.json from releases/{latest,tag/<tag>}/download; feed both through plan_verified then cache.stage(); reuse cmd_update_apply machinery downstream. Add a direct HTTPS client (reqwest, blocking+rustls) on the `spt` CLI crate (fetch runs in the CLI, NOT the daemon — no new daemon HTTP surface); honor SPT_INSTALL_REPO override. unit = URL/asset-name derivation (platform × latest/tag) + verify-then-stage against a fixture release.json (reuse release.rs vectors); rollback floor + channel-pin rejection paths.

# ───────────────────────────── Terminal wrapper / frontend ──────────────────

required_stages = ["impl", "unit"]  # activate v0.3.1. impl = `spt update fetch [--channel <ch>] [--tag vX.Y.Z]` (default latest on pinned channel); HTTPS GET of artifact + <asset>.release.json from releases/{latest,tag/<tag>}/download; feed both through plan_verified then cache.stage(); reuse cmd_update_apply machinery downstream. Add a direct HTTPS client (reqwest, blocking+rustls) on the `spt` CLI crate (fetch runs in the CLI, NOT the daemon — no new daemon HTTP surface); honor SPT_INSTALL_REPO override. unit = URL/asset-name derivation (platform × latest/tag) + verify-then-stage against a fixture release.json (reuse release.rs vectors); rollback floor + channel-pin rejection paths.
[[requirements]]
id = "REQ-UPD-8"
title = "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."
required_stages = ["impl", "unit"]  # activate v0.3.2. impl = fetch set-staging (cli) + apply_staged single platform-guard (applyhost) + ReleaseCache platform stamp (relcache) + product_version metadata (release.rs) + xtask release-publish emits/uploads update-set.json stamped with product_version. unit = (a) fetch update-set.json + per-asset URL/triple derivation; (b) apply REFUSES a platform-mismatched / unstamped single (the regression that bricked hfenduleam) + a matching-stamp single applies; (c) the apply message render (product_version present → "Updated spt-core to vX.Y.Z." + URL; absent → release-counter fallback).

# ───────────────────────────── Terminal wrapper / frontend ──────────────────

title = "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."

+title = "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."

+required_stages = ["impl", "unit"]  # activate v0.3.2. impl = fetch set-staging (cli) + apply_staged single platform-guard (applyhost) + ReleaseCache platform stamp (relcache) + product_version metadata (release.rs) + xtask release-publish emits/uploads update-set.json stamped with product_version. unit = (a) fetch update-set.json + per-asset URL/triple derivation; (b) apply REFUSES a platform-mismatched / unstamped single (the regression that bricked hfenduleam) + a matching-stamp single applies; (c) the apply message render (product_version present → "Updated spt-core to vX.Y.Z." + URL; absent → release-counter fallback).


Task 1 done. Task 2: add `product_version` to release.rs. First find all struct literals to update.


Bash

grep -rn "ReleaseMetadata {\|UpdateSetMetadata {" --include=*.rs crates/ | grep -v "pub struct\|//"

Find all metadata struct literals


crates/spt-daemon/src/adapter_update.rs:184:        let meta = ReleaseMetadata {
crates/spt-daemon/src/applyhost.rs:282:        let meta = ReleaseMetadata {
crates/spt-daemon/src/applyhost.rs:327:        let meta = UpdateSetMetadata {
crates/spt-daemon/src/release.rs:64:impl ReleaseMetadata {
crates/spt-daemon/src/release.rs:146:impl UpdateSetMetadata {
crates/spt-daemon/src/release.rs:635:    fn meta_for(version: u64, artifact: &[u8]) -> ReleaseMetadata {
crates/spt-daemon/src/release.rs:636:        ReleaseMetadata {
crates/spt-daemon/src/release.rs:647:    fn set_meta(version: u64, win: &[u8], linux: &[u8]) -> UpdateSetMetadata {
crates/spt-daemon/src/release.rs:648:        UpdateSetMetadata {
crates/spt-daemon/src/update.rs:364:    fn gate_meta(version: u64, artifact: &[u8]) -> ReleaseMetadata {
crates/spt-daemon/src/update.rs:365:        ReleaseMetadata {
crates/spt-daemon/src/update.rs:393:    ) -> UpdateSetMetadata {
crates/spt-daemon/src/update.rs:394:        UpdateSetMetadata {
crates/spt-daemon/tests/dispatch.rs:224:    let meta = ReleaseMetadata {
crates/spt-daemon/tests/peerloop.rs:161:    let meta = ReleaseMetadata {
crates/spt-daemon/tests/propagate.rs:104:    let meta = ReleaseMetadata {
crates/spt-daemon/tests/propagate.rs:130:    let meta = UpdateSetMetadata {
crates/spt-daemon/tests/twohost.rs:492:    let meta = ReleaseMetadata {
crates/xtask/src/main.rs:439:        let meta = spt_daemon::release::ReleaseMetadata {
crates/xtask/src/main.rs:782:    let meta = spt_daemon::UpdateSetMetadata {


Task 2: add `product_version` to both structs in release.rs.


Edit

    /// The IPC protocol version the release's **brain** speaks (ADR-0004 §A axis,
    /// fed to [`crate::update::classify`] once the release is verified).
    pub brain_ipc_version: u32,
    /// The held-resource ABI the release's **broker** implements (ADR-0004 §A axis).
    pub broker_resource_abi: u32,
}

    /// The IPC protocol version the release's **brain** speaks (ADR-0004 §A axis,
    /// fed to [`crate::update::classify`] once the release is verified).
    pub brain_ipc_version: u32,
    /// The held-resource ABI the release's **broker** implements (ADR-0004 §A axis).
    pub broker_resource_abi: u32,
    /// Human-facing semantic version of this release (e.g. `"0.3.2"`), for the
    /// post-apply message. **Additive** (`#[serde(default)]` ⇒ pre-v0.3.2
    /// metadata parses to `""`); never a trust input — the monotonic `version`
    /// counter remains the only ordering authority.
    // [impl->REQ-UPD-8]
    #[serde(default)]
    pub product_version: String,
}


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

    /// The IPC protocol version the release's **brain** speaks (ADR-0004 §A axis,
    /// fed to [`crate::update::classify`] once the release is verified).
    pub brain_ipc_version: u32,
    /// The held-resource ABI the release's **broker** implements (ADR-0004 §A axis).
    pub broker_resource_abi: u32,
}

    /// The IPC protocol version the release's **brain** speaks (ADR-0004 §A axis,
    /// fed to [`crate::update::classify`] once the release is verified).
    pub brain_ipc_version: u32,
    /// The held-resource ABI the release's **broker** implements (ADR-0004 §A axis).
    pub broker_resource_abi: u32,
    /// Human-facing semantic version of this release (e.g. `"0.3.2"`), for the
    /// post-apply message. **Additive** (`#[serde(default)]` ⇒ pre-v0.3.2
    /// metadata parses to `""`); never a trust input — the monotonic `version`
    /// counter remains the only ordering authority.
    // [impl->REQ-UPD-8]
    #[serde(default)]
    pub product_version: String,
}

+    /// Human-facing semantic version of this release (e.g. `"0.3.2"`), for the

+    /// post-apply message. **Additive** (`#[serde(default)]` ⇒ pre-v0.3.2

+    /// metadata parses to `""`); never a trust input — the monotonic `version`

+    /// counter remains the only ordering authority.


    /// Platform artifacts keyed by Rust target triple.
    pub artifacts: BTreeMap<String, UpdateArtifactMetadata>,
    /// Optional signed diagnostics for debugging and fleet inspection.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provenance: Option<UpdateSetProvenance>,
}

    /// Platform artifacts keyed by Rust target triple.
    pub artifacts: BTreeMap<String, UpdateArtifactMetadata>,
    /// Optional signed diagnostics for debugging and fleet inspection.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provenance: Option<UpdateSetProvenance>,
    /// Human-facing semantic version of this release (e.g. `"0.3.2"`), for the
    /// post-apply message. **Additive** (`#[serde(default)]` ⇒ pre-v0.3.2 set
    /// metadata parses to `""`); never a trust input.
    // [impl->REQ-UPD-8]
    #[serde(default)]
    pub product_version: String,
}


    /// Platform artifacts keyed by Rust target triple.
    pub artifacts: BTreeMap<String, UpdateArtifactMetadata>,
    /// Optional signed diagnostics for debugging and fleet inspection.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provenance: Option<UpdateSetProvenance>,
}

    /// Platform artifacts keyed by Rust target triple.
    pub artifacts: BTreeMap<String, UpdateArtifactMetadata>,
    /// Optional signed diagnostics for debugging and fleet inspection.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provenance: Option<UpdateSetProvenance>,
    /// Human-facing semantic version of this release (e.g. `"0.3.2"`), for the
    /// post-apply message. **Additive** (`#[serde(default)]` ⇒ pre-v0.3.2 set
    /// metadata parses to `""`); never a trust input.
    // [impl->REQ-UPD-8]
    #[serde(default)]
    pub product_version: String,
}

+    /// post-apply message. **Additive** (`#[serde(default)]` ⇒ pre-v0.3.2 set

+    /// metadata parses to `""`); never a trust input.


Now the test helpers in release.rs:


            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: BROKER_RESOURCE_ABI,
        }
    }

    fn set_meta(version: u64, win: &[u8], linux: &[u8]) -> UpdateSetMetadata {

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: BROKER_RESOURCE_ABI,
            product_version: String::new(),
        }
    }

    fn set_meta(version: u64, win: &[u8], linux: &[u8]) -> UpdateSetMetadata {


            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: BROKER_RESOURCE_ABI,
        }
    }

    fn set_meta(version: u64, win: &[u8], linux: &[u8]) -> UpdateSetMetadata {

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: BROKER_RESOURCE_ABI,
            product_version: String::new(),
        }
    }

    fn set_meta(version: u64, win: &[u8], linux: &[u8]) -> UpdateSetMetadata {

+            product_version: String::new(),


            provenance: Some(UpdateSetProvenance {
                git_commit: Some("abc123".to_string()),
                git_dirty: Some(true),
                build_host: Some("fastbox".to_string()),
                build_timestamp_ms: Some(42),
            }),
        }
    }

            provenance: Some(UpdateSetProvenance {
                git_commit: Some("abc123".to_string()),
                git_dirty: Some(true),
                build_host: Some("fastbox".to_string()),
                build_timestamp_ms: Some(42),
            }),
            product_version: String::new(),
        }
    }


            provenance: Some(UpdateSetProvenance {
                git_commit: Some("abc123".to_string()),
                git_dirty: Some(true),
                build_host: Some("fastbox".to_string()),
                build_timestamp_ms: Some(42),
            }),
        }
    }

            provenance: Some(UpdateSetProvenance {
                git_commit: Some("abc123".to_string()),
                git_dirty: Some(true),
                build_host: Some("fastbox".to_string()),
                build_timestamp_ms: Some(42),
            }),
            product_version: String::new(),
        }
    }


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.


update.rs test helpers:


            artifact_sha256: crate::release::sha256_hex(artifact),
            brain_ipc_version: IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
        }
    }

    fn gate_policy(sk: &SigningKey) -> VerifyPolicy {

            artifact_sha256: crate::release::sha256_hex(artifact),
            brain_ipc_version: IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
            product_version: String::new(),
        }
    }

    fn gate_policy(sk: &SigningKey) -> VerifyPolicy {


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

            artifact_sha256: crate::release::sha256_hex(artifact),
            brain_ipc_version: IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
        }
    }

    fn gate_policy(sk: &SigningKey) -> VerifyPolicy {

            artifact_sha256: crate::release::sha256_hex(artifact),
            brain_ipc_version: IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
            product_version: String::new(),
        }
    }

    fn gate_policy(sk: &SigningKey) -> VerifyPolicy {


                    asset_name: None,
                },
            )]),
            provenance: None,
        }
    }

                    asset_name: None,
                },
            )]),
            provenance: None,
            product_version: String::new(),
        }
    }


                    asset_name: None,
                },
            )]),
            provenance: None,
        }
    }

                    asset_name: None,
                },
            )]),
            provenance: None,
            product_version: String::new(),
        }
    }


Now adapter_update.rs (production path):


178	            .iter()
179	            .map(|b| format!("{b:02x}"))
180	            .collect()
181	    }
182
183	    fn sign(sk: &SigningKey, artifact: &[u8]) -> FilePullPayload {
184	        let meta = ReleaseMetadata {
185	            version: 2,
186	            channel: "stable".to_string(),
187	            expires_at_ms: 9_999,
188	            key_id: "adapter-key".to_string(),
189	            artifact_sha256: sha256_hex(artifact),
190	            brain_ipc_version: 1,
191	            broker_resource_abi: 1,
192	        };
193	        let metadata_json = serde_json::to_string(&meta).unwrap();
194	        let sig = sk.sign(metadata_json.as_bytes());
195	        let signature_hex = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
196	        FilePullPayload {
197	            signed: SignedRelease {
198	                metadata_json,
199	                signature_hex,

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

    fn sign(sk: &SigningKey, artifact: &[u8]) -> FilePullPayload {
        let meta = ReleaseMetadata {
            version: 2,
            channel: "stable".to_string(),
            expires_at_ms: 9_999,
            key_id: "adapter-key".to_string(),
            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: 1,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signature_hex = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
        FilePullPayload {
            signed: SignedRelease {
                metadata_json,
                signature_hex,


            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: 1,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: 1,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();


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

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: 1,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: 1,
            broker_resource_abi: 1,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();

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


Now applyhost.rs test helpers (lines 282, 327):


            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,
            broker_resource_abi: resource_abi,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedRelease {

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,
            broker_resource_abi: resource_abi,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedRelease {


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)

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,
            broker_resource_abi: resource_abi,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedRelease {

            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,
            broker_resource_abi: resource_abi,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedRelease {


                    asset_name: None,
                },
            )]),
            provenance: None,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedUpdateSet {

                    asset_name: None,
                },
            )]),
            provenance: None,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedUpdateSet {


                    asset_name: None,
                },
            )]),
            provenance: None,
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedUpdateSet {

                    asset_name: None,
                },
            )]),
            provenance: None,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedUpdateSet {


Now integration test literals. Let me view each:


Bash

for f in dispatch:224 peerloop:161 twohost:492; do n=${f%:*}; l=${f#*:}; echo "=== $n.rs:$l ==="; sed -n "${l},$((l+10))p" crates/spt-daemon/tests/$n.rs; done; echo "=== propagate.rs:104 ==="; sed -n '104,114p' crates/spt-daemon/tests/propagate.rs; echo "=== propagate.rs:130 ==="; sed -n '130,148p' crates/spt-daemon/tests/propagate.rs

View integration test metadata literals


=== dispatch.rs:224 ===
    let meta = ReleaseMetadata {
        version: 6,
        channel: "stable".to_string(),
        expires_at_ms: u64::MAX,
        key_id: "k".to_string(),
        artifact_sha256: sha256_hex(&artifact),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = sk.sign(metadata_json.as_bytes());
=== peerloop.rs:161 ===
    let meta = ReleaseMetadata {
        version: 6,
        channel: "stable".to_string(),
        expires_at_ms: u64::MAX,
        key_id: "k".to_string(),
        artifact_sha256: sha256_hex(&artifact),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = sk.sign(metadata_json.as_bytes());
=== twohost.rs:492 ===
    let meta = ReleaseMetadata {
        version: 6,
        channel: "stable".to_string(),
        expires_at_ms: u64::MAX,
        key_id: "twohost".to_string(),
        artifact_sha256: sha256_hex(RELEASE_BYTES),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).expect("meta json");
    let sig = rig.release_sk.sign(metadata_json.as_bytes());
=== propagate.rs:104 ===
    let meta = ReleaseMetadata {
        version,
        channel: CHANNEL.to_string(),
        expires_at_ms: 10_000,
        key_id: KEY_ID.to_string(),
        artifact_sha256: spt_daemon::sha256_hex(artifact),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
=== propagate.rs:130 ===
    let meta = UpdateSetMetadata {
        version,
        channel: CHANNEL.to_string(),
        expires_at_ms: 10_000,
        key_id: KEY_ID.to_string(),
        artifacts: BTreeMap::from([
            (
                current_platform().to_string(),
                UpdateArtifactMetadata {
                    artifact_sha256: spt_daemon::sha256_hex(current_bytes),
                    brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
                    broker_resource_abi: BROKER_RESOURCE_ABI,
                    asset_name: Some("spt-current".to_string()),
                },
            ),
            (
                other.to_string(),
                UpdateArtifactMetadata {
                    artifact_sha256: spt_daemon::sha256_hex(other_bytes),


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

Add product_version field to metadata

Fetch stages update-set + apply message

Run gates


Editing each test file. The ReleaseMetadata blocks are identical per-file:


Edit

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


224	    let meta = ReleaseMetadata {
225	        version: 6,
226	        channel: "stable".to_string(),
227	        expires_at_ms: u64::MAX,
228	        key_id: "k".to_string(),
229	        artifact_sha256: sha256_hex(&artifact),
230	        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
231	        broker_resource_abi: BROKER_RESOURCE_ABI,
232	    };

    let meta = ReleaseMetadata {
        version: 6,
        channel: "stable".to_string(),
        expires_at_ms: u64::MAX,
        key_id: "k".to_string(),
        artifact_sha256: sha256_hex(&artifact),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };


        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };


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

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };

+        product_version: String::new(),


161	    let meta = ReleaseMetadata {
162	        version: 6,
163	        channel: "stable".to_string(),
164	        expires_at_ms: u64::MAX,
165	        key_id: "k".to_string(),
166	        artifact_sha256: sha256_hex(&artifact),
167	        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
168	        broker_resource_abi: BROKER_RESOURCE_ABI,
169	    };


        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };


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


492	    let meta = ReleaseMetadata {
493	        version: 6,
494	        channel: "stable".to_string(),
495	        expires_at_ms: u64::MAX,
496	        key_id: "twohost".to_string(),
497	        artifact_sha256: sha256_hex(RELEASE_BYTES),
498	        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
499	        broker_resource_abi: BROKER_RESOURCE_ABI,
500	    };

    let meta = ReleaseMetadata {
        version: 6,
        channel: "stable".to_string(),
        expires_at_ms: u64::MAX,
        key_id: "twohost".to_string(),
        artifact_sha256: sha256_hex(RELEASE_BYTES),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };


        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };


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

let metadata_json = serde_json::to_string(&meta).expect("meta json");

let sig = rig.release_sk.sign(metadata_json.as_bytes());


104	    let meta = ReleaseMetadata {
105	        version,
106	        channel: CHANNEL.to_string(),
107	        expires_at_ms: 10_000,
108	        key_id: KEY_ID.to_string(),
109	        artifact_sha256: spt_daemon::sha256_hex(artifact),
110	        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
111	        broker_resource_abi: BROKER_RESOURCE_ABI,
112	    };
113	    let metadata_json = serde_json::to_string(&meta).unwrap();
114	    let sig = release_key().sign(metadata_json.as_bytes());
115	    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
116	    SignedRelease {
117	        metadata_json,
118	        signature_hex,
119	    }
120	}
121
122	/// Build + sign update-set metadata with the current platform plus a second
123	/// opposite-platform artifact, proving set propagation does platform selection.
124	fn signed_update_set(version: u64, current_bytes: &[u8], other_bytes: &[u8]) -> SignedUpdateSet {
125	    let other = if current_platform() == "x86_64-pc-windows-msvc" {
126	        "x86_64-unknown-linux-gnu"
127	    } else {
128	        "x86_64-pc-windows-msvc"
129	    };
130	    let meta = UpdateSetMetadata {
131	        version,
132	        channel: CHANNEL.to_string(),
133	        expires_at_ms: 10_000,
134	        key_id: KEY_ID.to_string(),
135	        artifacts: BTreeMap::from([
136	            (
137	                current_platform().to_string(),
138	                UpdateArtifactMetadata {
139	                    artifact_sha256: spt_daemon::sha256_hex(current_bytes),
140	                    brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
141	                    broker_resource_abi: BROKER_RESOURCE_ABI,
142	                    asset_name: Some("spt-current".to_string()),
143	                },
144	            ),
145	            (
146	                other.to_string(),
147	                UpdateArtifactMetadata {
148	                    artifact_sha256: spt_daemon::sha256_hex(other_bytes),
149	                    brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
150	                    broker_resource_abi: BROKER_RESOURCE_ABI,
151	                    asset_name: Some("spt-other".to_string()),
152	                },
153	            ),
154	        ]),
155	        provenance: None,
156	    };
157	    let metadata_json = serde_json::to_string(&meta).unwrap();
158	    let sig = release_key().sign(metadata_json.as_bytes());
159	    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
160	    SignedUpdateSet {
161	        metadata_json,
162	        signature_hex,
163	    }
164	}
165
166	/// A node's verify policy: trusts the release key, pinned to stable, running
167	/// `current_version`, clock before expiry.
168	fn policy(current_version: u64) -> VerifyPolicy {
169	    let mut trusted = BTreeMap::new();

    let meta = ReleaseMetadata {
        version,
        channel: CHANNEL.to_string(),
        expires_at_ms: 10_000,
        key_id: KEY_ID.to_string(),
        artifact_sha256: spt_daemon::sha256_hex(artifact),
        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedRelease {
        metadata_json,
        signature_hex,
    }
}

/// Build + sign update-set metadata with the current platform plus a second
/// opposite-platform artifact, proving set propagation does platform selection.
fn signed_update_set(version: u64, current_bytes: &[u8], other_bytes: &[u8]) -> SignedUpdateSet {
    let other = if current_platform() == "x86_64-pc-windows-msvc" {
        "x86_64-unknown-linux-gnu"
    } else {
        "x86_64-pc-windows-msvc"
    };
    let meta = UpdateSetMetadata {
        version,
        channel: CHANNEL.to_string(),
        expires_at_ms: 10_000,
        key_id: KEY_ID.to_string(),
        artifacts: BTreeMap::from([
            (
                current_platform().to_string(),
                UpdateArtifactMetadata {
                    artifact_sha256: spt_daemon::sha256_hex(current_bytes),
                    brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
                    broker_resource_abi: BROKER_RESOURCE_ABI,
                    asset_name: Some("spt-current".to_string()),
                },
            ),
            (
                other.to_string(),
                UpdateArtifactMetadata {
                    artifact_sha256: spt_daemon::sha256_hex(other_bytes),
                    brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
                    broker_resource_abi: BROKER_RESOURCE_ABI,
                    asset_name: Some("spt-other".to_string()),
                },
            ),
        ]),
        provenance: None,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedUpdateSet {
        metadata_json,
        signature_hex,
    }
}

/// A node's verify policy: trusts the release key, pinned to stable, running
/// `current_version`, clock before expiry.
fn policy(current_version: u64) -> VerifyPolicy {
    let mut trusted = BTreeMap::new();


        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedRelease {

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedRelease {


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

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedRelease {

        brain_ipc_version: spt_daemon::IPC_PROTOCOL_VERSION,
        broker_resource_abi: BROKER_RESOURCE_ABI,
        product_version: String::new(),
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedRelease {


        ]),
        provenance: None,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedUpdateSet {

        ]),
        provenance: None,
        product_version: String::new(),
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedUpdateSet {


        ]),
        provenance: None,
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedUpdateSet {

        ]),
        provenance: None,
        product_version: String::new(),
    };
    let metadata_json = serde_json::to_string(&meta).unwrap();
    let sig = release_key().sign(metadata_json.as_bytes());
    let signature_hex: String = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
    SignedUpdateSet {


Task 2 mostly done (xtask literals come with Task 6). Task 3: relcache platform stamp + `product_version()` helper.


/// File name of the staged release's signed metadata (the commit point).
const RELEASE_FILE: &str = "release.json";
/// File name of the staged artifact bytes.
const ARTIFACT_FILE: &str = "artifact.bin";
/// Directory for platform-targeted update-set artifact bytes.
const ARTIFACTS_DIR: &str = "artifacts";

/// File name of the staged release's signed metadata (the commit point).
const RELEASE_FILE: &str = "release.json";
/// File name of the staged artifact bytes.
const ARTIFACT_FILE: &str = "artifact.bin";
/// Directory for platform-targeted update-set artifact bytes.
const ARTIFACTS_DIR: &str = "artifacts";
/// File name of the platform stamp for a staged **single** release — the
/// target triple the staging side asserts the single artifact is for. A legacy
/// [`SignedRelease`] carries no platform identity in its signed metadata
/// (the v0.3.1 cross-OS brick root cause), so the apply guard
/// ([`crate::applyhost::apply_staged`]) trusts this stamp only when a path that
/// genuinely KNOWS the artifact's platform wrote it ([`stage_single_with_platform`]).
/// A plain [`stage`](ReleaseCache::stage) leaves it absent ⇒ the apply
/// fail-safe refuses (REQ-UPD-8). Update sets self-describe their platforms in
/// the signed metadata and never use this stamp.
// [impl->REQ-UPD-8]
const PLATFORM_FILE: &str = "platform.json";


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

/// File name of the staged release's signed metadata (the commit point).
const RELEASE_FILE: &str = "release.json";
/// File name of the staged artifact bytes.
const ARTIFACT_FILE: &str = "artifact.bin";
/// Directory for platform-targeted update-set artifact bytes.
const ARTIFACTS_DIR: &str = "artifacts";
/// File name of the platform stamp for a staged **single** release — the
/// target triple the staging side asserts the single artifact is for. A legacy
/// [`SignedRelease`] carries no platform identity in its signed metadata
/// (the v0.3.1 cross-OS brick root cause), so the apply guard
/// ([`crate::applyhost::apply_staged`]) trusts this stamp only when a path that
/// genuinely KNOWS the artifact's platform wrote it ([`stage_single_with_platform`]).
/// A plain [`stage`](ReleaseCache::stage) leaves it absent ⇒ the apply
/// fail-safe refuses (REQ-UPD-8). Update sets self-describe their platforms in
/// the signed metadata and never use this stamp.
// [impl->REQ-UPD-8]
const PLATFORM_FILE: &str = "platform.json";

+/// File name of the platform stamp for a staged **single** release — the

+/// target triple the staging side asserts the single artifact is for. A legacy

+/// [`SignedRelease`] carries no platform identity in its signed metadata

+/// (the v0.3.1 cross-OS brick root cause), so the apply guard

+/// ([`crate::applyhost::apply_staged`]) trusts this stamp only when a path that

+/// genuinely KNOWS the artifact's platform wrote it ([`stage_single_with_platform`]).

+/// A plain [`stage`](ReleaseCache::stage) leaves it absent ⇒ the apply

+/// fail-safe refuses (REQ-UPD-8). Update sets self-describe their platforms in

+/// the signed metadata and never use this stamp.

+const PLATFORM_FILE: &str = "platform.json";

/// A staged update can be the legacy single-artifact shape or the newer

/// platform-targeted set shape. Both use the same detached-signature wire


    pub fn channel(&self) -> Option<String> {
        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;
        meta.get("channel")
            .and_then(|v| v.as_str())
            .map(str::to_string)
    }
}

    pub fn channel(&self) -> Option<String> {
        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;
        meta.get("channel")
            .and_then(|v| v.as_str())
            .map(str::to_string)
    }

    /// The human-facing semantic version stamped in the signed metadata
    /// ([`crate::release::ReleaseMetadata::product_version`]), or `None` when
    /// absent/empty (pre-v0.3.2 metadata) — the apply message falls back to the
    /// monotonic counter then.
    // [impl->REQ-UPD-8]
    pub fn product_version(&self) -> Option<String> {
        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;
        meta.get("product_version")
            .and_then(|v| v.as_str())
            .filter(|s| !s.is_empty())
            .map(str::to_string)
    }
}


    pub fn channel(&self) -> Option<String> {
        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;
        meta.get("channel")
            .and_then(|v| v.as_str())
            .map(str::to_string)
    }
}

    pub fn channel(&self) -> Option<String> {
        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;
        meta.get("channel")
            .and_then(|v| v.as_str())
            .map(str::to_string)
    }

    /// The human-facing semantic version stamped in the signed metadata
    /// ([`crate::release::ReleaseMetadata::product_version`]), or `None` when
    /// absent/empty (pre-v0.3.2 metadata) — the apply message falls back to the
    /// monotonic counter then.
    // [impl->REQ-UPD-8]
    pub fn product_version(&self) -> Option<String> {
        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;
        meta.get("product_version")
            .and_then(|v| v.as_str())
            .filter(|s| !s.is_empty())
            .map(str::to_string)
    }
}

+    /// The human-facing semantic version stamped in the signed metadata

+    /// ([`crate::release::ReleaseMetadata::product_version`]), or `None` when

+    /// absent/empty (pre-v0.3.2 metadata) — the apply message falls back to the

+    pub fn product_version(&self) -> Option<String> {

+        let meta: serde_json::Value = serde_json::from_str(self.metadata_json()).ok()?;

/// The on-disk staged-release cache: one directory holding at most one


Now the stamp paths + `stage()`/`stage_update_set()` clearing + `stage_single_with_platform` + `staged_platform()`:


    fn release_path(&self) -> PathBuf {
        self.dir.join(RELEASE_FILE)
    }

    fn release_path(&self) -> PathBuf {
        self.dir.join(RELEASE_FILE)
    }

    fn platform_path(&self) -> PathBuf {
        self.dir.join(PLATFORM_FILE)
    }


    fn release_path(&self) -> PathBuf {
        self.dir.join(RELEASE_FILE)
    }

    fn platform_path(&self) -> PathBuf {
        self.dir.join(PLATFORM_FILE)
    }

+    fn platform_path(&self) -> PathBuf {

/// Stage a verified release: artifact first, signed metadata last (the

/// commit point), both atomic. Restaging replaces the previous release —

/// the cache holds only the newest verified one.


    pub fn stage(&self, signed: &SignedRelease, artifact: &[u8]) -> io::Result<()> {
        std::fs::create_dir_all(&self.dir)?;
        let _ = std::fs::remove_dir_all(self.dir.join(ARTIFACTS_DIR));
        atomic_write_bytes(&self.artifact_path(), artifact)?;
        let json = serde_json::to_string_pretty(signed)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        atomic_write_string(&self.release_path(), &json)
    }

    pub fn stage(&self, signed: &SignedRelease, artifact: &[u8]) -> io::Result<()> {
        std::fs::create_dir_all(&self.dir)?;
        let _ = std::fs::remove_dir_all(self.dir.join(ARTIFACTS_DIR));
        // No truthful platform identity for a bare single — clear any stale
        // stamp so the apply guard fail-safe refuses (REQ-UPD-8). A caller that
        // genuinely knows the artifact's platform uses
        // [`stage_single_with_platform`] instead.
        // [impl->REQ-UPD-8]
        let _ = std::fs::remove_file(self.platform_path());
        atomic_write_bytes(&self.artifact_path(), artifact)?;
        let json = serde_json::to_string_pretty(signed)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        atomic_write_string(&self.release_path(), &json)
    }

    /// Stage a single release whose artifact is KNOWN to target `platform`
    /// (the origin-fetch path that downloaded THIS node's platform asset, or a
    /// test). Identical to [`stage`](Self::stage) but additionally writes the
    /// platform stamp the apply guard requires — apply proceeds only when the
    /// stamp equals the running node's [`current_platform`] (REQ-UPD-8). Update
    /// sets do not need this; they carry per-platform identity in their signed
    /// metadata.
    // [impl->REQ-UPD-8]
    pub fn stage_single_with_platform(
        &self,
        signed: &SignedRelease,
        artifact: &[u8],
        platform: &str,
    ) -> io::Result<()> {
        self.stage(signed, artifact)?;
        atomic_write_string(
            &self.platform_path(),
            &serde_json::json!({ "platform": platform }).to_string(),
        )
    }

    /// The platform stamp of the staged single release, if one was recorded by
    /// [`stage_single_with_platform`]. `None` for an unstamped single (a bare
    /// [`stage`](Self::stage), incl. every P2P-pulled single) or an update set.
    // [impl->REQ-UPD-8]
    pub fn staged_platform(&self) -> Option<String> {
        let s = std::fs::read_to_string(self.platform_path()).ok()?;
        serde_json::from_str::<serde_json::Value>(&s)
            .ok()?
            .get("platform")?
            .as_str()
            .map(str::to_string)
    }


    pub fn stage(&self, signed: &SignedRelease, artifact: &[u8]) -> io::Result<()> {
        std::fs::create_dir_all(&self.dir)?;
        let _ = std::fs::remove_dir_all(self.dir.join(ARTIFACTS_DIR));
        atomic_write_bytes(&self.artifact_path(), artifact)?;
        let json = serde_json::to_string_pretty(signed)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        atomic_write_string(&self.release_path(), &json)
    }

    pub fn stage(&self, signed: &SignedRelease, artifact: &[u8]) -> io::Result<()> {
        std::fs::create_dir_all(&self.dir)?;
        let _ = std::fs::remove_dir_all(self.dir.join(ARTIFACTS_DIR));
        // No truthful platform identity for a bare single — clear any stale
        // stamp so the apply guard fail-safe refuses (REQ-UPD-8). A caller that
        // genuinely knows the artifact's platform uses
        // [`stage_single_with_platform`] instead.
        // [impl->REQ-UPD-8]
        let _ = std::fs::remove_file(self.platform_path());
        atomic_write_bytes(&self.artifact_path(), artifact)?;
        let json = serde_json::to_string_pretty(signed)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        atomic_write_string(&self.release_path(), &json)
    }

    /// Stage a single release whose artifact is KNOWN to target `platform`
    /// (the origin-fetch path that downloaded THIS node's platform asset, or a
    /// test). Identical to [`stage`](Self::stage) but additionally writes the
    /// platform stamp the apply guard requires — apply proceeds only when the
    /// stamp equals the running node's [`current_platform`] (REQ-UPD-8). Update
    /// sets do not need this; they carry per-platform identity in their signed
    /// metadata.
    // [impl->REQ-UPD-8]
    pub fn stage_single_with_platform(
        &self,
        signed: &SignedRelease,
        artifact: &[u8],
        platform: &str,
    ) -> io::Result<()> {
        self.stage(signed, artifact)?;
        atomic_write_string(
            &self.platform_path(),
            &serde_json::json!({ "platform": platform }).to_string(),
        )
    }

    /// The platform stamp of the staged single release, if one was recorded by
    /// [`stage_single_with_platform`]. `None` for an unstamped single (a bare
    /// [`stage`](Self::stage), incl. every P2P-pulled single) or an update set.
    // [impl->REQ-UPD-8]
    pub fn staged_platform(&self) -> Option<String> {
        let s = std::fs::read_to_string(self.platform_path()).ok()?;
        serde_json::from_str::<serde_json::Value>(&s)
            .ok()?
            .get("platform")?
            .as_str()
            .map(str::to_string)
    }

+        // No truthful platform identity for a bare single — clear any stale

+        // stamp so the apply guard fail-safe refuses (REQ-UPD-8). A caller that

+        // genuinely knows the artifact's platform uses

+        // [`stage_single_with_platform`] instead.

+        let _ = std::fs::remove_file(self.platform_path());

+    /// Stage a single release whose artifact is KNOWN to target `platform`

+    /// (the origin-fetch path that downloaded THIS node's platform asset, or a

+    /// test). Identical to [`stage`](Self::stage) but additionally writes the

+    /// platform stamp the apply guard requires — apply proceeds only when the

+    /// stamp equals the running node's [`current_platform`] (REQ-UPD-8). Update

+    /// sets do not need this; they carry per-platform identity in their signed

+            &serde_json::json!({ "platform": platform }).to_string(),

+    /// The platform stamp of the staged single release, if one was recorded by

+    /// [`stage_single_with_platform`]. `None` for an unstamped single (a bare

+    /// [`stage`](Self::stage), incl. every P2P-pulled single) or an update set.

+    pub fn staged_platform(&self) -> Option<String> {

+        let s = std::fs::read_to_string(self.platform_path()).ok()?;

+        serde_json::from_str::<serde_json::Value>(&s)

/// Stage a platform-targeted update set: every artifact first, signed

/// metadata last (commit point). The caller MUST have verified that the

/// provided bytes match the signed digests.


        std::fs::create_dir_all(self.dir.join(ARTIFACTS_DIR))?;
        let _ = std::fs::remove_file(self.artifact_path());
        for (platform, bytes) in artifacts {

        std::fs::create_dir_all(self.dir.join(ARTIFACTS_DIR))?;
        let _ = std::fs::remove_file(self.artifact_path());
        // A set self-describes its platforms in signed metadata; the single
        // stamp must not linger from a prior single stage. [impl->REQ-UPD-8]
        let _ = std::fs::remove_file(self.platform_path());
        for (platform, bytes) in artifacts {


        std::fs::create_dir_all(self.dir.join(ARTIFACTS_DIR))?;
        let _ = std::fs::remove_file(self.artifact_path());
        for (platform, bytes) in artifacts {

        std::fs::create_dir_all(self.dir.join(ARTIFACTS_DIR))?;
        let _ = std::fs::remove_file(self.artifact_path());
        // A set self-describes its platforms in signed metadata; the single
        // stamp must not linger from a prior single stage. [impl->REQ-UPD-8]
        let _ = std::fs::remove_file(self.platform_path());
        for (platform, bytes) in artifacts {

+        // A set self-describes its platforms in signed metadata; the single

+        // stamp must not linger from a prior single stage. [impl->REQ-UPD-8]

+        let _ = std::fs::remove_file(self.platform_path());


Add relcache unit test for the stamp:


// [unit->REQ-UPD-1] a torn stage never offers: metadata without its

    // [unit->REQ-UPD-8] the single-release platform stamp: an explicit
    // stage_single_with_platform records the triple; a bare stage() leaves it
    // absent (the apply fail-safe), and restaging via stage()/stage_update_set
    // clears a prior stamp so it can never go stale.
    #[test]
    fn single_platform_stamp_is_explicit_and_cleared_on_restage() {
        let dir = tempfile::tempdir().unwrap();
        let cache = ReleaseCache::open(dir.path());

        // Bare single stage: no stamp (fail-safe — apply must refuse).
        cache.stage(&signed(6), b"v6").unwrap();
        assert_eq!(cache.staged_platform(), None, "bare stage leaves no stamp");

        // Explicit platform stage records it.
        cache
            .stage_single_with_platform(&signed(7), b"v7", "x86_64-pc-windows-msvc")
            .unwrap();
        assert_eq!(
            cache.staged_platform().as_deref(),
            Some("x86_64-pc-windows-msvc")
        );

        // Restaging a bare single clears the stamp (never inherits the prior).
        cache.stage(&signed(8), b"v8").unwrap();
        assert_eq!(cache.staged_platform(), None, "restage clears stale stamp");

        // A set stage also clears any prior single stamp.
        cache
            .stage_single_with_platform(&signed(9), b"v9", "x86_64-unknown-linux-gnu")
            .unwrap();
        let artifacts =
            BTreeMap::from([("x86_64-unknown-linux-gnu".to_string(), b"setbytes".to_vec())]);
        cache.stage_update_set(&signed_set(10), &artifacts).unwrap();
        assert_eq!(cache.staged_platform(), None, "set stage clears the stamp");
    }

    // [unit->REQ-UPD-1] a torn stage never offers: metadata without its


    // [unit->REQ-UPD-8] the single-release platform stamp: an explicit
    // stage_single_with_platform records the triple; a bare stage() leaves it
    // absent (the apply fail-safe), and restaging via stage()/stage_update_set
    // clears a prior stamp so it can never go stale.
    #[test]
    fn single_platform_stamp_is_explicit_and_cleared_on_restage() {
        let dir = tempfile::tempdir().unwrap();
        let cache = ReleaseCache::open(dir.path());

        // Bare single stage: no stamp (fail-safe — apply must refuse).
        cache.stage(&signed(6), b"v6").unwrap();
        assert_eq!(cache.staged_platform(), None, "bare stage leaves no stamp");

        // Explicit platform stage records it.
        cache
            .stage_single_with_platform(&signed(7), b"v7", "x86_64-pc-windows-msvc")
            .unwrap();
        assert_eq!(
            cache.staged_platform().as_deref(),
            Some("x86_64-pc-windows-msvc")
        );

        // Restaging a bare single clears the stamp (never inherits the prior).
        cache.stage(&signed(8), b"v8").unwrap();
        assert_eq!(cache.staged_platform(), None, "restage clears stale stamp");

        // A set stage also clears any prior single stamp.
        cache
            .stage_single_with_platform(&signed(9), b"v9", "x86_64-unknown-linux-gnu")
            .unwrap();
        let artifacts =
            BTreeMap::from([("x86_64-unknown-linux-gnu".to_string(), b"setbytes".to_vec())]);
        cache.stage_update_set(&signed_set(10), &artifacts).unwrap();
        assert_eq!(cache.staged_platform(), None, "set stage clears the stamp");
    }

    // [unit->REQ-UPD-1] a torn stage never offers: metadata without its

+    // [unit->REQ-UPD-8] the single-release platform stamp: an explicit

+    // stage_single_with_platform records the triple; a bare stage() leaves it

+    // absent (the apply fail-safe), and restaging via stage()/stage_update_set

+    // clears a prior stamp so it can never go stale.

+    fn single_platform_stamp_is_explicit_and_cleared_on_restage() {

+        let dir = tempfile::tempdir().unwrap();

+        let cache = ReleaseCache::open(dir.path());

+        // Bare single stage: no stamp (fail-safe — apply must refuse).

+        cache.stage(&signed(6), b"v6").unwrap();

+        assert_eq!(cache.staged_platform(), None, "bare stage leaves no stamp");

+        // Explicit platform stage records it.

+            .stage_single_with_platform(&signed(7), b"v7", "x86_64-pc-windows-msvc")

+            cache.staged_platform().as_deref(),

+            Some("x86_64-pc-windows-msvc")

+        // Restaging a bare single clears the stamp (never inherits the prior).

+        cache.stage(&signed(8), b"v8").unwrap();

+        assert_eq!(cache.staged_platform(), None, "restage clears stale stamp");

+        // A set stage also clears any prior single stamp.

+            .stage_single_with_platform(&signed(9), b"v9", "x86_64-unknown-linux-gnu")

+            BTreeMap::from([("x86_64-unknown-linux-gnu".to_string(), b"setbytes".to_vec())]);

+        cache.stage_update_set(&signed_set(10), &artifacts).unwrap();

+        assert_eq!(cache.staged_platform(), None, "set stage clears the stamp");

// artifact (or corrupt metadata) yields None rather than a poisoned offer.


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

Add product_version field to metadata

Fetch stages update-set + apply message

Run gates


Update task statuses and move to Task 4 (applyhost guard).


ToolSearch

Question: select:TaskUpdate


Question: select:TaskUpdate


Updated task #1 status


Updated task #3 status


Updated task #4 status


Task 4: applyhost guard. Outcome enum first:


Edit

    /// Verified but not brain-only — the M5 apply surface refuses the
    /// broker-touching classes typed; the artifact stays staged.
    RefusedClass(UpdateClass),
    /// Swapped + handed off. The version is the staged release's.
    Applied { version: u64 },
}

    /// Verified but not brain-only — the M5 apply surface refuses the
    /// broker-touching classes typed; the artifact stays staged.
    RefusedClass(UpdateClass),
    /// Swapped + handed off. `version` is the monotonic release counter;
    /// `product_version` is the human-facing semver (empty for pre-v0.3.2
    /// metadata, whence the counter-based fallback message).
    // [impl->REQ-UPD-8]
    Applied {
        version: u64,
        product_version: String,
    },
}


    /// Verified but not brain-only — the M5 apply surface refuses the
    /// broker-touching classes typed; the artifact stays staged.
    RefusedClass(UpdateClass),
    /// Swapped + handed off. The version is the staged release's.
    Applied { version: u64 },
}

    /// Verified but not brain-only — the M5 apply surface refuses the
    /// broker-touching classes typed; the artifact stays staged.
    RefusedClass(UpdateClass),
    /// Swapped + handed off. `version` is the monotonic release counter;
    /// `product_version` is the human-facing semver (empty for pre-v0.3.2
    /// metadata, whence the counter-based fallback message).
    // [impl->REQ-UPD-8]
    Applied {
        version: u64,
        product_version: String,
    },
}

-    /// Swapped + handed off. The version is the staged release's.

+    /// Swapped + handed off. `version` is the monotonic release counter;

+    /// `product_version` is the human-facing semver (empty for pre-v0.3.2

+    /// metadata, whence the counter-based fallback message).

/// Apply the staged release (module docs). `exe_path` is the executable to


Now the guard + product_version extraction in `apply_staged`:


    let Some(staged) = cache.staged_update() else {
        return Ok(ApplyStagedOutcome::NoUpdate);
    };
    let version = cache.staged_version().unwrap_or(0);

    let Some(staged) = cache.staged_update() else {
        return Ok(ApplyStagedOutcome::NoUpdate);
    };
    let version = cache.staged_version().unwrap_or(0);
    let product_version = staged.product_version().unwrap_or_default();


    let Some(staged) = cache.staged_update() else {
        return Ok(ApplyStagedOutcome::NoUpdate);
    };
    let version = cache.staged_version().unwrap_or(0);
    let product_version = staged.product_version().unwrap_or_default();

+    let product_version = staged.product_version().unwrap_or_default();

// At-rest re-verification. Version floor 0: monotonicity already gated

// the stage; this check is authenticity/integrity/expiry of the bytes on


    let (plan, artifact) = match staged {
        StagedUpdate::Single(signed) => {
            let artifact = cache
                .load_artifact()
                .map_err(|e| format!("staged artifact: {e}"))?;

    let (plan, artifact) = match staged {
        StagedUpdate::Single(signed) => {
            // Platform guard (REQ-UPD-8) — defense-in-depth against the v0.3.1
            // cross-OS brick. A legacy single carries NO platform identity in
            // its signed metadata, so apply trusts only the cache's platform
            // stamp: apply proceeds solely when the stage asserted THIS node's
            // platform. An unstamped single (every P2P-pulled single, and any
            // pre-v0.3.2 stage) or one stamped for another OS fail-safe refuses
            // — exactly the path that swapped a Linux ELF in as `spt.exe`.
            let here = current_platform();
            match cache.staged_platform() {
                Some(p) if p == here => {}
                Some(p) => {
                    return Ok(ApplyStagedOutcome::Rejected(format!(
                        "staged single targets platform {p}, not this node's {here} — \
                         re-fetch with `spt update fetch` on v0.3.2+ (platform-safe update set)"
                    )))
                }
                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)"
                    )))
                }
            }
            let artifact = cache
                .load_artifact()
                .map_err(|e| format!("staged artifact: {e}"))?;


    let (plan, artifact) = match staged {
        StagedUpdate::Single(signed) => {
            let artifact = cache
                .load_artifact()
                .map_err(|e| format!("staged artifact: {e}"))?;

    let (plan, artifact) = match staged {
        StagedUpdate::Single(signed) => {
            // Platform guard (REQ-UPD-8) — defense-in-depth against the v0.3.1
            // cross-OS brick. A legacy single carries NO platform identity in
            // its signed metadata, so apply trusts only the cache's platform
            // stamp: apply proceeds solely when the stage asserted THIS node's
            // platform. An unstamped single (every P2P-pulled single, and any
            // pre-v0.3.2 stage) or one stamped for another OS fail-safe refuses
            // — exactly the path that swapped a Linux ELF in as `spt.exe`.
            let here = current_platform();
            match cache.staged_platform() {
                Some(p) if p == here => {}
                Some(p) => {
                    return Ok(ApplyStagedOutcome::Rejected(format!(
                        "staged single targets platform {p}, not this node's {here} — \
                         re-fetch with `spt update fetch` on v0.3.2+ (platform-safe update set)"
                    )))
                }
                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)"
                    )))
                }
            }
            let artifact = cache
                .load_artifact()
                .map_err(|e| format!("staged artifact: {e}"))?;

+            // Platform guard (REQ-UPD-8) — defense-in-depth against the v0.3.1

+            // cross-OS brick. A legacy single carries NO platform identity in

+            // its signed metadata, so apply trusts only the cache's platform

+            // stamp: apply proceeds solely when the stage asserted THIS node's

+            // platform. An unstamped single (every P2P-pulled single, and any

+            // pre-v0.3.2 stage) or one stamped for another OS fail-safe refuses

+            // — exactly the path that swapped a Linux ELF in as `spt.exe`.

+            let here = current_platform();

+            match cache.staged_platform() {

+                Some(p) if p == here => {}

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

+                        "staged single targets platform {p}, not this node's {here} — \

+                         re-fetch with `spt update fetch` on v0.3.2+ (platform-safe update set)"

+                        "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)"


Now the two `Applied { version }` constructions in apply_staged:


            match apply_brain_only(&plan, brain, move |state| handoff_retry(&name, state)) {
                Ok(_new_brain) => Ok(ApplyStagedOutcome::Applied { version }),
                Err(e) => Err(format!(

            match apply_brain_only(&plan, brain, move |state| handoff_retry(&name, state)) {
                Ok(_new_brain) => Ok(ApplyStagedOutcome::Applied {
                    version,
                    product_version,
                }),
                Err(e) => Err(format!(


            match apply_brain_only(&plan, brain, move |state| handoff_retry(&name, state)) {
                Ok(_new_brain) => Ok(ApplyStagedOutcome::Applied { version }),
                Err(e) => Err(format!(

            match apply_brain_only(&plan, brain, move |state| handoff_retry(&name, state)) {
                Ok(_new_brain) => Ok(ApplyStagedOutcome::Applied {
                    version,
                    product_version,
                }),
                Err(e) => Err(format!(

-                Ok(_new_brain) => Ok(ApplyStagedOutcome::Applied { version }),

+                Ok(_new_brain) => Ok(ApplyStagedOutcome::Applied {

"binary swapped to v{version}, but the brain re-attach failed: {e} — the broker \

and every hosted endpoint are intact; retry `spt update apply` or restart the brain"


        None => Ok(ApplyStagedOutcome::Applied { version }),
    }
}

        None => Ok(ApplyStagedOutcome::Applied {
            version,
            product_version,
        }),
    }
}


        None => Ok(ApplyStagedOutcome::Applied { version }),
    }
}

        None => Ok(ApplyStagedOutcome::Applied {
            version,
            product_version,
        }),
    }
}

-        None => Ok(ApplyStagedOutcome::Applied { version }),

+        None => Ok(ApplyStagedOutcome::Applied {


Update applyhost tests: stamp local platform in the `stage()` helper, and `Applied { .. }` matches:


        let cache = ReleaseCache::open(&dir.join("releases"));
        cache.stage(&signed, artifact).expect("stage");

        let keys = dir.join("release-keys.json");
        let pub_hex: String = sk
            .verifying_key()
            .to_bytes()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (cache, keys)
    }

    /// Stage a signed update set + write matching release keys.

        let cache = ReleaseCache::open(&dir.join("releases"));
        // Stamp the local platform so the apply guard (REQ-UPD-8) lets these
        // single-release paths through to the class/tamper logic under test;
        // the unstamped/mismatched refusals get their own dedicated test.
        cache
            .stage_single_with_platform(&signed, artifact, current_platform())
            .expect("stage");

        let keys = dir.join("release-keys.json");
        let pub_hex: String = sk
            .verifying_key()
            .to_bytes()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (cache, keys)
    }

    /// Stage a signed update set + write matching release keys.


        let cache = ReleaseCache::open(&dir.join("releases"));
        cache.stage(&signed, artifact).expect("stage");

        let keys = dir.join("release-keys.json");
        let pub_hex: String = sk
            .verifying_key()
            .to_bytes()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (cache, keys)
    }

    /// Stage a signed update set + write matching release keys.

        let cache = ReleaseCache::open(&dir.join("releases"));
        // Stamp the local platform so the apply guard (REQ-UPD-8) lets these
        // single-release paths through to the class/tamper logic under test;
        // the unstamped/mismatched refusals get their own dedicated test.
        cache
            .stage_single_with_platform(&signed, artifact, current_platform())
            .expect("stage");

        let keys = dir.join("release-keys.json");
        let pub_hex: String = sk
            .verifying_key()
            .to_bytes()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (cache, keys)
    }

    /// Stage a signed update set + write matching release keys.

-        cache.stage(&signed, artifact).expect("stage");

+        // Stamp the local platform so the apply guard (REQ-UPD-8) lets these

+        // single-release paths through to the class/tamper logic under test;

+        // the unstamped/mismatched refusals get their own dedicated test.

+            .stage_single_with_platform(&signed, artifact, current_platform())


Now the three `Applied { version: N }` matches:


            matches!(out, ApplyStagedOutcome::Applied { version: 7 }),
            "got {out:?}"

            matches!(out, ApplyStagedOutcome::Applied { version: 7, .. }),
            "got {out:?}"


            matches!(out, ApplyStagedOutcome::Applied { version: 7 }),
            "got {out:?}"

            matches!(out, ApplyStagedOutcome::Applied { version: 7, .. }),
            "got {out:?}"

let out = apply_staged(&cache, &keys, &exe, &name).expect("apply ok");

-            matches!(out, ApplyStagedOutcome::Applied { version: 7 }),

+            matches!(out, ApplyStagedOutcome::Applied { version: 7, .. }),


            matches!(out, ApplyStagedOutcome::Applied { version: 11 }),
            "got {out:?}"

            matches!(out, ApplyStagedOutcome::Applied { version: 11, .. }),
            "got {out:?}"


            matches!(out, ApplyStagedOutcome::Applied { version: 11 }),
            "got {out:?}"

            matches!(out, ApplyStagedOutcome::Applied { version: 11, .. }),
            "got {out:?}"

-            matches!(out, ApplyStagedOutcome::Applied { version: 11 }),

+            matches!(out, ApplyStagedOutcome::Applied { version: 11, .. }),

assert_eq!(std::fs::read(&exe).unwrap(), b"SET BINARY BYTES");


            matches!(out, ApplyStagedOutcome::Applied { version: 13 }),
            "got {out:?}"

            matches!(out, ApplyStagedOutcome::Applied { version: 13, .. }),
            "got {out:?}"


            matches!(out, ApplyStagedOutcome::Applied { version: 13 }),
            "got {out:?}"

            matches!(out, ApplyStagedOutcome::Applied { version: 13, .. }),
            "got {out:?}"

-            matches!(out, ApplyStagedOutcome::Applied { version: 13 }),

+            matches!(out, ApplyStagedOutcome::Applied { version: 13, .. }),

assert_eq!(std::fs::read(&exe).unwrap(), b"NEW WITHOUT SESSIONS");


Now the regression test for the guard. Let me view the test region to place it:


408	            .iter()
409	            .map(|b| format!("{b:02x}"))
410	            .collect();
411	        std::fs::write(
412	            &keys,
413	            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
414	        )
415	        .unwrap();
416	        (cache, keys)
417	    }
418
419	    fn echo_spawn_req() -> SpawnReq {
420	        #[cfg(unix)]
421	        let (program, args) = ("cat".to_string(), Vec::<String>::new());
422	        #[cfg(windows)]
423	        let (program, args) = ("findstr".to_string(), vec![".".to_string()]);
424	        SpawnReq {
425	            program,
426	            args,
427	            rows: 24,
428	            cols: 80,
429	            endpoint: String::new(),
430	        }
431	    }
432
433	    // [unit->REQ-UPD-4] the ack-driven apply end to end: a staged + verified
434	    // brain-only release swaps the binary (artifact lands at the exe path,
435	    // the original steps aside) and triggers the brain handoff with the
436	    // hosted endpoint untouched — the production caller the M4-D9 residual
437	    // promised. [unit->REQ-UPD-3]

            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (cache, keys)
    }

    fn echo_spawn_req() -> SpawnReq {
        #[cfg(unix)]
        let (program, args) = ("cat".to_string(), Vec::<String>::new());
        #[cfg(windows)]
        let (program, args) = ("findstr".to_string(), vec![".".to_string()]);
        SpawnReq {
            program,
            args,
            rows: 24,
            cols: 80,
            endpoint: String::new(),
        }
    }

    // [unit->REQ-UPD-4] the ack-driven apply end to end: a staged + verified
    // brain-only release swaps the binary (artifact lands at the exe path,
    // the original steps aside) and triggers the brain handoff with the
    // hosted endpoint untouched — the production caller the M4-D9 residual
    // promised. [unit->REQ-UPD-3]


    /// Sign a single release (sk = [9u8;32], matching `stage`) and write the
    /// matching release-keys.json — WITHOUT staging, so a test controls the
    /// platform stamp itself.
    fn sign_single(dir: &Path, version: u64, artifact: &[u8]) -> (SignedRelease, std::path::PathBuf) {
        let sk = SigningKey::from_bytes(&[9u8; 32]);
        let meta = ReleaseMetadata {
            version,
            channel: "stable".to_string(),
            expires_at_ms: u64::MAX,
            key_id: "k".to_string(),
            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedRelease {
            metadata_json,
            signature_hex: sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect(),
        };
        let keys = dir.join("release-keys.json");
        let pub_hex: String = sk
            .verifying_key()
            .to_bytes()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (signed, keys)
    }

    // [unit->REQ-UPD-8] the platform guard — the v0.3.1 cross-OS brick
    // regression. A single staged WITHOUT a matching platform stamp must never
    // apply: an unstamped single (every peer-propagated / pre-v0.3.2 single)
    // refuses, a single stamped for ANOTHER OS refuses, and only a single
    // stamped for THIS node's platform proceeds. This is exactly what would
    // have stopped a Linux ELF from being swapped in as `spt.exe`.
    #[test]
    fn apply_staged_refuses_platform_blind_or_mismatched_single() {
        let dir = tempfile::tempdir().unwrap();
        let name = unique_name();
        let _broker = served_broker(&name);
        let exe = dir.path().join("spt-binary");
        std::fs::write(&exe, b"OLD").unwrap();

        let cache = ReleaseCache::open(&dir.path().join("releases"));
        let (signed, keys) = sign_single(dir.path(), 7, b"CROSS-OS BYTES");

        // 1. Unstamped single (a bare stage, i.e. every P2P-pulled single) —
        //    the exact shape that bricked hfenduleam — refuses, seat untouched.
        cache.stage(&signed, b"CROSS-OS BYTES").unwrap();
        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");
        assert!(
            matches!(out, ApplyStagedOutcome::Rejected(ref r) if r.contains("platform")),
            "unstamped single must refuse, got {out:?}"
        );
        assert_eq!(std::fs::read(&exe).unwrap(), b"OLD", "seat untouched");

        // 2. Single stamped for the OTHER OS refuses too.
        let other = if current_platform() == "x86_64-pc-windows-msvc" {
            "x86_64-unknown-linux-gnu"
        } else {
            "x86_64-pc-windows-msvc"
        };
        cache
            .stage_single_with_platform(&signed, b"CROSS-OS BYTES", other)
            .unwrap();
        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");
        assert!(
            matches!(out, ApplyStagedOutcome::Rejected(ref r) if r.contains(other)),
            "mismatched-platform single must refuse, got {out:?}"
        );
        assert_eq!(std::fs::read(&exe).unwrap(), b"OLD", "seat untouched");

        // 3. Stamped for THIS platform: the single applies (the legitimate
        //    origin-fetched-local path).
        cache
            .stage_single_with_platform(&signed, b"CROSS-OS BYTES", current_platform())
            .unwrap();
        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");
        assert!(
            matches!(out, ApplyStagedOutcome::Applied { version: 7, .. }),
            "matching-platform single applies, got {out:?}"
        );
        assert_eq!(std::fs::read(&exe).unwrap(), b"CROSS-OS BYTES");
    }

    fn echo_spawn_req() -> SpawnReq {


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)

    /// Sign a single release (sk = [9u8;32], matching `stage`) and write the
    /// matching release-keys.json — WITHOUT staging, so a test controls the
    /// platform stamp itself.
    fn sign_single(dir: &Path, version: u64, artifact: &[u8]) -> (SignedRelease, std::path::PathBuf) {
        let sk = SigningKey::from_bytes(&[9u8; 32]);
        let meta = ReleaseMetadata {
            version,
            channel: "stable".to_string(),
            expires_at_ms: u64::MAX,
            key_id: "k".to_string(),
            artifact_sha256: sha256_hex(artifact),
            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
            product_version: String::new(),
        };
        let metadata_json = serde_json::to_string(&meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signed = SignedRelease {
            metadata_json,
            signature_hex: sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect(),
        };
        let keys = dir.join("release-keys.json");
        let pub_hex: String = sk
            .verifying_key()
            .to_bytes()
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect();
        std::fs::write(
            &keys,
            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),
        )
        .unwrap();
        (signed, keys)
    }

    // [unit->REQ-UPD-8] the platform guard — the v0.3.1 cross-OS brick
    // regression. A single staged WITHOUT a matching platform stamp must never
    // apply: an unstamped single (every peer-propagated / pre-v0.3.2 single)
    // refuses, a single stamped for ANOTHER OS refuses, and only a single
    // stamped for THIS node's platform proceeds. This is exactly what would
    // have stopped a Linux ELF from being swapped in as `spt.exe`.
    #[test]
    fn apply_staged_refuses_platform_blind_or_mismatched_single() {
        let dir = tempfile::tempdir().unwrap();
        let name = unique_name();
        let _broker = served_broker(&name);
        let exe = dir.path().join("spt-binary");
        std::fs::write(&exe, b"OLD").unwrap();

        let cache = ReleaseCache::open(&dir.path().join("releases"));
        let (signed, keys) = sign_single(dir.path(), 7, b"CROSS-OS BYTES");

        // 1. Unstamped single (a bare stage, i.e. every P2P-pulled single) —
        //    the exact shape that bricked hfenduleam — refuses, seat untouched.
        cache.stage(&signed, b"CROSS-OS BYTES").unwrap();
        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");
        assert!(
            matches!(out, ApplyStagedOutcome::Rejected(ref r) if r.contains("platform")),
            "unstamped single must refuse, got {out:?}"
        );
        assert_eq!(std::fs::read(&exe).unwrap(), b"OLD", "seat untouched");

        // 2. Single stamped for the OTHER OS refuses too.
        let other = if current_platform() == "x86_64-pc-windows-msvc" {
            "x86_64-unknown-linux-gnu"
        } else {
            "x86_64-pc-windows-msvc"
        };
        cache
            .stage_single_with_platform(&signed, b"CROSS-OS BYTES", other)
            .unwrap();
        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");
        assert!(
            matches!(out, ApplyStagedOutcome::Rejected(ref r) if r.contains(other)),
            "mismatched-platform single must refuse, got {out:?}"
        );
        assert_eq!(std::fs::read(&exe).unwrap(), b"OLD", "seat untouched");

        // 3. Stamped for THIS platform: the single applies (the legitimate
        //    origin-fetched-local path).
        cache
            .stage_single_with_platform(&signed, b"CROSS-OS BYTES", current_platform())
            .unwrap();
        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");
        assert!(
            matches!(out, ApplyStagedOutcome::Applied { version: 7, .. }),
            "matching-platform single applies, got {out:?}"
        );
        assert_eq!(std::fs::read(&exe).unwrap(), b"CROSS-OS BYTES");
    }

    fn echo_spawn_req() -> SpawnReq {

+    /// Sign a single release (sk = [9u8;32], matching `stage`) and write the

+    /// matching release-keys.json — WITHOUT staging, so a test controls the

+    fn sign_single(dir: &Path, version: u64, artifact: &[u8]) -> (SignedRelease, std::path::PathBuf) {

+        let sk = SigningKey::from_bytes(&[9u8; 32]);

+            channel: "stable".to_string(),

+            artifact_sha256: sha256_hex(artifact),

+            brain_ipc_version: crate::IPC_PROTOCOL_VERSION,

+            broker_resource_abi: BROKER_RESOURCE_ABI,

+            product_version: String::new(),

+        let metadata_json = serde_json::to_string(&meta).unwrap();

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

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

+        let keys = dir.join("release-keys.json");

+            serde_json::json!({"keys": {"k": pub_hex}, "channel": "stable"}).to_string(),

+    // [unit->REQ-UPD-8] the platform guard — the v0.3.1 cross-OS brick

+    // regression. A single staged WITHOUT a matching platform stamp must never

+    // apply: an unstamped single (every peer-propagated / pre-v0.3.2 single)

+    // refuses, a single stamped for ANOTHER OS refuses, and only a single

+    // stamped for THIS node's platform proceeds. This is exactly what would

+    // have stopped a Linux ELF from being swapped in as `spt.exe`.

+    fn apply_staged_refuses_platform_blind_or_mismatched_single() {

+        let dir = tempfile::tempdir().unwrap();

+        let _broker = served_broker(&name);

+        let exe = dir.path().join("spt-binary");

+        std::fs::write(&exe, b"OLD").unwrap();

+        let cache = ReleaseCache::open(&dir.path().join("releases"));

+        let (signed, keys) = sign_single(dir.path(), 7, b"CROSS-OS BYTES");

+        // 1. Unstamped single (a bare stage, i.e. every P2P-pulled single) —

+        //    the exact shape that bricked hfenduleam — refuses, seat untouched.

+        cache.stage(&signed, b"CROSS-OS BYTES").unwrap();

+        let out = apply_staged(&cache, &keys, &exe, &name).expect("ok");

+            matches!(out, ApplyStagedOutcome::Rejected(ref r) if r.contains("platform")),

+            "unstamped single must refuse, got {out:?}"

+        assert_eq!(std::fs::read(&exe).unwrap(), b"OLD", "seat untouched");

+        // 2. Single stamped for the OTHER OS refuses too.

+        let other = if current_platform() == "x86_64-pc-windows-msvc" {

+            .stage_single_with_platform(&signed, b"CROSS-OS BYTES", other)

+            matches!(out, ApplyStagedOutcome::Rejected(ref r) if r.contains(other)),

+            "mismatched-platform single must refuse, got {out:?}"

+        // 3. Stamped for THIS platform: the single applies (the legitimate

+        //    origin-fetched-local path).

+            .stage_single_with_platform(&signed, b"CROSS-OS BYTES", current_platform())

+            matches!(out, ApplyStagedOutcome::Applied { version: 7, .. }),

+            "matching-platform single applies, got {out:?}"

+        assert_eq!(std::fs::read(&exe).unwrap(), b"CROSS-OS BYTES");

let (program, args) = ("cat".to_string(), Vec::<String>::new());


Task 4 done. Now cli.rs apply message (Task 5 part 1):


Updated task #4 status


Updated task #5 status


        Ok(spt_daemon::ApplyStagedOutcome::Applied { version }) => {
            println!("APPLIED:{version}");
            0
        }

        Ok(spt_daemon::ApplyStagedOutcome::Applied {
            version,
            product_version,
        }) => {
            println!("{}", render_applied_message(version, &product_version));
            0
        }


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)

        Ok(spt_daemon::ApplyStagedOutcome::Applied { version }) => {
            println!("APPLIED:{version}");
            0
        }

        Ok(spt_daemon::ApplyStagedOutcome::Applied {
            version,
            product_version,
        }) => {
            println!("{}", render_applied_message(version, &product_version));
            0
        }

-        Ok(spt_daemon::ApplyStagedOutcome::Applied { version }) => {

-            println!("APPLIED:{version}");

+        Ok(spt_daemon::ApplyStagedOutcome::Applied {

+            println!("{}", render_applied_message(version, &product_version));


/// `spt update apply` — the explicit ack named by the update-consent notif.
// [impl->REQ-UPD-4]
// [impl->REQ-UPD-3]
fn cmd_update_apply() -> i32 {

/// The changelog the post-apply message points users to.
const RELEASES_URL: &str = "https://www.github.com/SaberMage/spt-releases/releases";

/// Render the friendly post-apply message (REQ-UPD-8). With a semver
/// (`product_version` from the signed metadata) it names the version; absent
/// one (pre-v0.3.2 metadata) it falls back to the monotonic release counter.
/// The changelog line is always appended. Pure — unit-tested.
// [impl->REQ-UPD-8]
fn render_applied_message(version: u64, product_version: &str) -> String {
    let head = if product_version.is_empty() {
        format!("Updated spt-core (release {version}).")
    } else {
        format!("Updated spt-core to v{product_version}.")
    };
    format!("{head}\nCheck out the changelog at {RELEASES_URL}")
}

/// `spt update apply` — the explicit ack named by the update-consent notif.
// [impl->REQ-UPD-4]
// [impl->REQ-UPD-3]
fn cmd_update_apply() -> i32 {


/// The changelog the post-apply message points users to.
const RELEASES_URL: &str = "https://www.github.com/SaberMage/spt-releases/releases";

/// Render the friendly post-apply message (REQ-UPD-8). With a semver
/// (`product_version` from the signed metadata) it names the version; absent
/// one (pre-v0.3.2 metadata) it falls back to the monotonic release counter.
/// The changelog line is always appended. Pure — unit-tested.
// [impl->REQ-UPD-8]
fn render_applied_message(version: u64, product_version: &str) -> String {
    let head = if product_version.is_empty() {
        format!("Updated spt-core (release {version}).")
    } else {
        format!("Updated spt-core to v{product_version}.")
    };
    format!("{head}\nCheck out the changelog at {RELEASES_URL}")
}

/// `spt update apply` — the explicit ack named by the update-consent notif.
// [impl->REQ-UPD-4]
// [impl->REQ-UPD-3]
fn cmd_update_apply() -> i32 {

+/// The changelog the post-apply message points users to.

+const RELEASES_URL: &str = "https://www.github.com/SaberMage/spt-releases/releases";

+/// Render the friendly post-apply message (REQ-UPD-8). With a semver

+/// (`product_version` from the signed metadata) it names the version; absent

+/// one (pre-v0.3.2 metadata) it falls back to the monotonic release counter.

+/// The changelog line is always appended. Pure — unit-tested.

+fn render_applied_message(version: u64, product_version: &str) -> String {

+    let head = if product_version.is_empty() {

+        format!("Updated spt-core (release {version}).")

+        format!("Updated spt-core to v{product_version}.")

+    format!("{head}\nCheck out the changelog at {RELEASES_URL}")


Now rewrite `cmd_update_fetch` to stage the update set. First view current helper block (offsets shifted):