kitsubito's staged Linux release **cleared** — it'll no longer serve it to SPT_DEV, so ENLYZEAM is safe.

Here's the full picture.

## Recovery: done
hfenduleam restored to working `spt 0.3.0` (from the `spt.exe.old-5` PE backup the apply left behind). The bricked ELF is overwritten.

## Q1: "APPLIED:5" — real UX bug
`5` is the internal **monotonic release counter**, not the product version. It should say `APPLIED v0.3.1`. The apply outcome carries the counter, never the semver. Minor, but yes — should be fixed.

## Q2: the corruption — a serious bug in `spt update fetch` (my v0.3.1 feature)
**Root cause, confirmed by the file headers:** the new `spt.exe` was a **Linux ELF** (`7F 45 4C 46`), not a Windows PE. Sequence:
1. You ran `spt update fetch` on **kitsubito (Linux)** → it staged the **Linux** v0.3.1 binary + Linux metadata into its cache.
2. hfenduleam (Windows) restarted → its pump immediately pulled kitsubito's staged release over P2P → got the **Linux** artifact.
3. `spt update apply` verified it (signature ✓, SHA256 ✓ — it *is* a validly-signed release) and swapped it in. But the single-release path has **no platform check**, so a Linux binary landed as `spt.exe` → "not a valid application" / "16-bit application."

**Two distinct defects:**
1. **`spt update fetch` stages a single-platform release** for the *fetching* node's OS. When a peer of a *different* OS pulls it, they get the wrong binary. (The system's proper multi-platform path — `SignedUpdateSet`, which selects the recipient's target triple, REQ-UPD-6 — got bypassed because my fetch staged a bare single-platform `SignedRelease`.)
2. **No platform guard on the single-release apply/serve.** A guard refusing "this artifact's OS ≠ my OS" would have *prevented the brick* outright.

## Severity + immediate guidance
`spt update fetch` is **actively unsafe in a mixed-OS subnet** as shipped in v0.3.1 — it can brick cross-platform peers. **Don't use `spt update fetch` again until this is patched.** (I've cleared kitsubito's cache; the danger is dormant now.)

## Fix plan → v0.3.2
- **(a) Platform guard** on `apply_staged` + the P2P serve — refuse cross-OS artifacts (the safety net; prevents the corruption regardless).
- **(b) `spt update fetch` stages the multi-platform set** (all OS artifacts), so cross-platform pull actually works instead of needing the guard to block it.
- **(c)** `APPLIED:5` → `APPLIED v0.3.1`.

Want me to implement v0.3.2 now (a+b+c), full gates + CI + release? This is a real data-corruption bug in a shipped feature, so I'd treat it as priority.


change (c): "Updated spt-core to v0.3.1." --- write the plan for the full fix set, then /commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


On it — change (c) to "Updated spt-core to v0.3.1.", write the v0.3.2 plan, commune, resume next session. First I need the precise field/API names for an implementation-ready plan. Gather the update-set/platform machinery + the publish path:


Bash

cd "C:/Users/decid/Documents/projects/spt-core" && echo "=== ReleaseSpec / SignedRelease fields (platform? version string?) ==="; sed -n '40,130p' crates/spt-daemon/src/release.rs | grep -nE "pub struct|pub .*:|target|triple|platform|version|semver|product" | head -30

release struct fields


=== ReleaseSpec / SignedRelease fields (platform? version string?) ===
5:pub struct ReleaseMetadata {
6:    /// Monotonic release version. An update is accepted only if this is strictly
7:    /// greater than the running build's version (rollback rejection).
8:    pub version: u64,
10:    pub channel: String,
12:    pub expires_at_ms: u64,
15:    pub key_id: String,
17:    pub artifact_sha256: String,
18:    /// The IPC protocol version the release's **brain** speaks (ADR-0004 §A axis,
20:    pub brain_ipc_version: u32,
22:    pub broker_resource_abi: u32,
29:            brain_ipc_version: self.brain_ipc_version,
38:pub struct SignedRelease {
40:    pub metadata_json: String,
42:    pub signature_hex: String,
45:/// The artifact entry inside a platform-targeted update set. The map key is
46:/// the Rust target triple (e.g. `x86_64-pc-windows-msvc`); this entry carries
47:/// the digest and update-class axes for that platform's binary.
50:pub struct UpdateArtifactMetadata {
51:    /// Lowercase-hex SHA-256 of this platform's binary.
52:    pub artifact_sha256: String,
53:    /// The IPC protocol version this platform's **brain** speaks.
54:    pub brain_ipc_version: u32,
55:    /// The held-resource ABI this platform's **broker** implements.
56:    pub broker_resource_abi: u32,
59:    pub asset_name: Option<String>,
63:    /// The update-class inputs for this platform artifact.
66:            brain_ipc_version: self.brain_ipc_version,
76:pub struct UpdateSetProvenance {
78:    pub git_commit: Option<String>,


cd "C:/Users/decid/Documents/projects/spt-core" && echo "=== plan_verified_update_set platform selection ==="; sed -n '170,215p' crates/spt-daemon/src/update.rs; echo "=== SignedUpdateSet shape ==="; git grep -nA8 "pub struct SignedUpdateSet" -- crates/spt-daemon/src/release.rs | head -16

update-set platform path


=== plan_verified_update_set platform selection ===
/// digest, then classify that platform artifact.
// [impl->REQ-UPD-6]
pub fn plan_verified_update_set(
    running: &BrokerAbi,
    signed: &SignedUpdateSet,
    platform: &str,
    artifact: &[u8],
    policy: &VerifyPolicy,
) -> Result<UpdatePlan, RejectReason> {
    let meta = verify_update_set_metadata(signed, policy)?;
    let selected = meta.artifact_for(platform)?;
    verify_update_set_artifact(&meta, platform, artifact)?;
    Ok(plan_update(running, selected.spec()))
}

/// Why a brain-only apply did not complete.
#[derive(Debug)]
pub enum ApplyError {
    /// The plan is not brain-only — routing it through the brain-only path would
    /// risk an endpoint, which this class must never do. Raised **before** the
    /// outgoing brain is touched. The caller routes a broker-compatible /
    /// broker-breaking plan through its own (consent-gated, scheduled) path;
    /// brain-only is the only swap C0 performs.
    NotBrainOnly(UpdateClass),
    /// The new brain failed to re-attach (the `relaunch` closure errored). The
    /// endpoint is **untouched** regardless — the broker + its hosted child
    /// persist across a failed re-attach exactly as across a brain crash — so the
    /// caller may retry against the still-live broker.
    Relaunch(io::Error),
}

impl std::fmt::Display for ApplyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApplyError::NotBrainOnly(c) => {
                write!(
                    f,
                    "apply_brain_only refused a {c:?} plan (would risk an endpoint)"
                )
            }
            ApplyError::Relaunch(e) => {
                write!(f, "brain-only relaunch failed (endpoint intact): {e}")
            }
        }
    }
}
=== SignedUpdateSet shape ===
crates/spt-daemon/src/release.rs:163:pub struct SignedUpdateSet {
crates/spt-daemon/src/release.rs-164-    /// The exact UTF-8 bytes that were signed (serialized [`UpdateSetMetadata`]).
crates/spt-daemon/src/release.rs-165-    pub metadata_json: String,
crates/spt-daemon/src/release.rs-166-    /// The Ed25519 signature over `metadata_json`'s bytes, lowercase hex.
crates/spt-daemon/src/release.rs-167-    pub signature_hex: String,
crates/spt-daemon/src/release.rs-168-}
crates/spt-daemon/src/release.rs-169-
crates/spt-daemon/src/release.rs-170-/// Canonical platform identifier for this build, matching Rust target triples
crates/spt-daemon/src/release.rs-171-/// used by Cargo and ADR-0016.


change (c): "Updated spt-core to v0.3.1." --- write the plan for the full fix set, then /commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the nex…


after the update message, also add: "Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases"


Critical insight: a single `SignedRelease`'s `ReleaseMetadata` has **no platform field** (only version/channel/sha/abi) — so a single-release apply is inherently platform-blind. The platform-safe path is the `SignedUpdateSet` (keyed by target triple). Let me grab the platform-id fn + whether the release currently publishes a set:


cd "C:/Users/decid/Documents/projects/spt-core" && echo "=== platform id fn (this build's triple) ==="; sed -n '170,195p' crates/spt-daemon/src/release.rs | grep -nE "pub fn|target|triple|fn .*platform|cfg!|return"; echo "=== does release publish a SignedUpdateSet? (xtask/release.yml) ==="; git grep -nE "SignedUpdateSet|update-set|update_set|stage_update_set|sign_update_set" -- crates/xtask/src .github/workflows/release.yml | head; echo "=== how propagate decides single vs set on serve ==="; git grep -nE "SignedUpdateSet|SignedRelease|staged_update_set|staged_release|stage_update_set" -- crates/spt-daemon/src/propagate.rs | head

platform-id fn + set publishing


=== platform id fn (this build's triple) ===
1:/// Canonical platform identifier for this build, matching Rust target triples
4:pub fn current_platform() -> &'static str {
5:    #[cfg(all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"))]
9:    #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
14:        all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"),
15:        all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")
=== does release publish a SignedUpdateSet? (xtask/release.yml) ===
crates/xtask/src/main.rs:799:    let signed = spt_daemon::SignedUpdateSet {
crates/xtask/src/main.rs:813:    let verified = spt_daemon::verify_update_set_metadata(&signed, &policy)
crates/xtask/src/main.rs:816:        spt_daemon::verify_update_set_artifact(&verified, target, bytes)
crates/xtask/src/main.rs:822:        .stage_update_set(&signed, &artifact_bytes)
=== how propagate decides single vs set on serve ===
crates/spt-daemon/src/propagate.rs:42:    SignedRelease, SignedUpdateSet, UpdateSetMetadata, VerifyPolicy,
crates/spt-daemon/src/propagate.rs:75:    Single(SignedRelease),
crates/spt-daemon/src/propagate.rs:76:    Set(SignedUpdateSet),
crates/spt-daemon/src/propagate.rs:390:                            let as_set = SignedUpdateSet {
crates/spt-daemon/src/propagate.rs:400:                            // first; legacy SignedRelease remains supported
crates/spt-daemon/src/propagate.rs:417:                                    let offer = SignedRelease {
crates/spt-daemon/src/propagate.rs:489:                                            cache.stage_update_set(


Prompt: after the update message, also add: "Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases"


Have everything: `release::current_platform()` is the node's triple; `plan_verified_update_set` selects by platform; xtask already builds a `SignedUpdateSet` at publish (main.rs:799); the pump serves a staged `Single` platform-blind. Root cause fully mapped. Writing the plan:


Write

# v0.3.2 — platform-safe `spt update fetch` + apply platform-guard + friendly apply message

JIT plan. **Origin (v0.3.1 field corruption, 2026-06-09):** `spt update fetch` on kitsubito (Linux) staged a single-platform `SignedRelease` (the Linux binary + a platform-LESS `ReleaseMetadata`). hfenduleam (Windows) restarted → its pump pulled kitsubito's staged release over P2P (the pump serves a staged `Single` as-is) → `spt update apply` verified it clean (signature ✓, SHA-256 ✓ — it IS a valid signed release) and swapped in a **Linux ELF as `spt.exe`** → "not a valid application" (bricked). **Recovered** from `spt.exe.old-5` (the PE backup the swap left); **kitsubito cache cleared** so it stops serving the cross-platform artifact to SPT_DEV.

## Root cause
- `ReleaseMetadata` (a single `SignedRelease`, release.rs:5) carries **no platform/triple identity** — only `{version:u64, channel, expires_at_ms, key_id, artifact_sha256, brain_ipc_version, broker_resource_abi}`. So a single-release apply is **platform-blind by construction**: nothing in verify (`plan_verified`) can reject a wrong-OS artifact.
- The platform-SAFE path already exists: `SignedUpdateSet` → `UpdateSetMetadata.artifact_for(triple)` → `plan_verified_update_set(running, set, platform, artifact, policy)` (update.rs:172) selects the recipient's platform via `release::current_platform()` (release.rs:174 — `x86_64-pc-windows-msvc` / `x86_64-unknown-linux-gnu`).
- The P2P pump serves a staged `Single` platform-blind (`propagate.rs` `StagedOffer::Single`, "legacy SignedRelease remains supported"). My REQ-UPD-7 `cmd_update_fetch` staged a `Single` (`cache.stage`), so it injected a platform-blind release into P2P serving.

## Fix set (REQ-UPD-8 + the message)

### 1. `spt update fetch` stages the multi-platform SET, not a single release
- **Maintainer/release side:** the release must host a signed `update-set.json` (a `SignedUpdateSet` covering every platform). `xtask release-publish` ALREADY constructs + signs a `SignedUpdateSet` (main.rs:799) and `stage_update_set`s it locally; extend the publish to **upload `update-set.json` as a release asset** (the v0.3.1 release had only per-platform `.release.json` → confirm it isn't uploaded today, then add it in release.yml / the publish step). It must also be reachable at `releases/{latest,tag/<tag>}/download/update-set.json`.
- **`cmd_update_fetch`:** download `update-set.json` (the signed set) + EVERY platform artifact it names (`UpdateArtifactMetadata.asset_name` per triple), verify via `plan_verified_update_set` for THIS node, then `cache.stage_update_set(&set, &artifacts)` (mirror xtask:813-822). Replaces the single-`SignedRelease` fetch.
- Result: the cache holds a platform-keyed set → local apply selects `current_platform()`, P2P re-serve offers the Set, each peer selects ITS OWN platform. Reuses REQ-UPD-6 → fully platform-safe end-to-end.

### 2. Defense-in-depth platform guard on apply (the safety net — must-have)
- Never let a platform-mismatched artifact apply, even via a legacy single path. In `apply_staged` (applyhost):
  - **Set path:** `plan_verified_update_set` already selects by `current_platform()` (an absent platform → reject) → inherently safe.
  - **Single path (legacy / any future):** stamp the target triple at stage time (a `platform` sidecar in the cache) and **refuse apply if it != `current_platform()`**; a `Single` with no platform stamp (pre-v0.3.2) → **refuse to apply** (fail-safe) with a "re-fetch on v0.3.2+" message. The P2P serve must also stop offering platform-blind `Single`s cross-platform (prefer Sets; or gate the Single offer on a matching requester platform).
- This guard ALONE would have prevented the v0.3.1 brick — so it ships regardless of #1.

### 3. (c) Friendly apply message
- `cmd_update_apply` prints `APPLIED:5` (the monotonic counter — release.rs has no semver in `ReleaseMetadata`). Change to:
  ```
  Updated spt-core to v0.3.2.
  Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases
  ```
- Needs the SEMVER. Add `product_version: String` (e.g. "0.3.2") to `ReleaseMetadata` + `UpdateSetMetadata` — **additive serde field** (default "" → old releases parse clean). Maintainer populates it at publish from `workspace.package.version`. `apply` reads it: `Updated spt-core to v{product_version}.` Fallback when empty: `Updated spt-core (release {counter}).` The changelog line is static (append always).

## Traceability
- Mint **REQ-UPD-8** (platform-safe origin fetch + apply platform-guard). impl on the set-staging fetch + the `apply_staged` platform guard + the publish set-upload; unit on (a) fetch set/asset derivation, (b) apply REFUSES a platform-mismatched / unstamped single (the regression that bricked hfenduleam), (c) the message render (semver present + fallback + URL). product_version + message tag under REQ-UPD-8.

## Sequencing
mint REQ-UPD-8 → add `product_version` metadata field (release.rs, additive) → xtask publish emits + UPLOADS `update-set.json` + stamps product_version → `cmd_update_fetch` stages the set → `apply_staged` platform guard + friendly message → gates (build/test/clippy/`--no-default-features`/traceable-reqs/xtask check) → push dev-freeform → **CI BOTH runners** → bump 0.3.2 + CHANGELOG → FF main → tag v0.3.2 → user signs (`release-publish --version 6`).

## Verification (the real cross-platform test — the bug's exact shape)
- Linux node (kitsubito): `spt update fetch` → stages the SET (both platform artifacts).
- Windows peer (hfenduleam) pulls → gets the **Windows** artifact (platform selection) → `spt update apply` → prints "Updated spt-core to v0.3.2." → `spt.exe` is a valid Windows **PE (MZ)**, runs. **No cross-platform brick.**
- Guard test: stage a mismatched/unstamped single → apply **refuses** (not silent corruption).

## Immediate next-session start
1. Recovery already done — hfenduleam on working **0.3.0**, kitsubito cache cleared. **DO NOT use `spt update fetch` until v0.3.2 ships** (the cross-platform brick is live in v0.3.1).
2. Start at: mint REQ-UPD-8 → add `product_version` to `ReleaseMetadata`/`UpdateSetMetadata`.
3. Confirm whether release.yml uploads `update-set.json` today (v0.3.1 assets were per-platform `.release.json` only → likely add it).
4. (Note: brain-only apply worked for 0.3.0→0.3.1, so no broker-ABI concern this hop.)


File created successfully at: C:\Users\decid\Documents\projects\spt-core\V032-PLAN.md (file state is current in your context — no need to Read it back)

# v0.3.2 — platform-safe `spt update fetch` + apply platform-guard + friendly apply message

JIT plan. **Origin (v0.3.1 field corruption, 2026-06-09):** `spt update fetch` on kitsubito (Linux) staged a single-platform `SignedRelease` (the Linux binary + a platform-LESS `ReleaseMetadata`). hfenduleam (Windows) restarted → its pump pulled kitsubito's staged release over P2P (the pump serves a staged `Single` as-is) → `spt update apply` verified it clean (signature ✓, SHA-256 ✓ — it IS a valid signed release) and swapped in a **Linux ELF as `spt.exe`** → "not a valid application" (bricked). **Recovered** from `spt.exe.old-5` (the PE backup the swap left); **kitsubito cache cleared** so it stops serving the cross-platform artifact to SPT_DEV.

## Root cause
- `ReleaseMetadata` (a single `SignedRelease`, release.rs:5) carries **no platform/triple identity** — only `{version:u64, channel, expires_at_ms, key_id, artifact_sha256, brain_ipc_version, broker_resource_abi}`. So a single-release apply is **platform-blind by construction**: nothing in verify (`plan_verified`) can reject a wrong-OS artifact.
- The platform-SAFE path already exists: `SignedUpdateSet` → `UpdateSetMetadata.artifact_for(triple)` → `plan_verified_update_set(running, set, platform, artifact, policy)` (update.rs:172) selects the recipient's platform via `release::current_platform()` (release.rs:174 — `x86_64-pc-windows-msvc` / `x86_64-unknown-linux-gnu`).
- The P2P pump serves a staged `Single` platform-blind (`propagate.rs` `StagedOffer::Single`, "legacy SignedRelease remains supported"). My REQ-UPD-7 `cmd_update_fetch` staged a `Single` (`cache.stage`), so it injected a platform-blind release into P2P serving.

## Fix set (REQ-UPD-8 + the message)

### 1. `spt update fetch` stages the multi-platform SET, not a single release
- **Maintainer/release side:** the release must host a signed `update-set.json` (a `SignedUpdateSet` covering every platform). `xtask release-publish` ALREADY constructs + signs a `SignedUpdateSet` (main.rs:799) and `stage_update_set`s it locally; extend the publish to **upload `update-set.json` as a release asset** (the v0.3.1 release had only per-platform `.release.json` → confirm it isn't uploaded today, then add it in release.yml / the publish step). It must also be reachable at `releases/{latest,tag/<tag>}/download/update-set.json`.
- **`cmd_update_fetch`:** download `update-set.json` (the signed set) + EVERY platform artifact it names (`UpdateArtifactMetadata.asset_name` per triple), verify via `plan_verified_update_set` for THIS node, then `cache.stage_update_set(&set, &artifacts)` (mirror xtask:813-822). Replaces the single-`SignedRelease` fetch.
- Result: the cache holds a platform-keyed set → local apply selects `current_platform()`, P2P re-serve offers the Set, each peer selects ITS OWN platform. Reuses REQ-UPD-6 → fully platform-safe end-to-end.

### 2. Defense-in-depth platform guard on apply (the safety net — must-have)
- Never let a platform-mismatched artifact apply, even via a legacy single path. In `apply_staged` (applyhost):
  - **Set path:** `plan_verified_update_set` already selects by `current_platform()` (an absent platform → reject) → inherently safe.
  - **Single path (legacy / any future):** stamp the target triple at stage time (a `platform` sidecar in the cache) and **refuse apply if it != `current_platform()`**; a `Single` with no platform stamp (pre-v0.3.2) → **refuse to apply** (fail-safe) with a "re-fetch on v0.3.2+" message. The P2P serve must also stop offering platform-blind `Single`s cross-platform (prefer Sets; or gate the Single offer on a matching requester platform).
- This guard ALONE would have prevented the v0.3.1 brick — so it ships regardless of #1.

### 3. (c) Friendly apply message
- `cmd_update_apply` prints `APPLIED:5` (the monotonic counter — release.rs has no semver in `ReleaseMetadata`). Change to:
  ```
  Updated spt-core to v0.3.2.
  Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases
  ```
- Needs the SEMVER. Add `product_version: String` (e.g. "0.3.2") to `ReleaseMetadata` + `UpdateSetMetadata` — **additive serde field** (default "" → old releases parse clean). Maintainer populates it at publish from `workspace.package.version`. `apply` reads it: `Updated spt-core to v{product_version}.` Fallback when empty: `Updated spt-core (release {counter}).` The changelog line is static (append always).

## Traceability
- Mint **REQ-UPD-8** (platform-safe origin fetch + apply platform-guard). impl on the set-staging fetch + the `apply_staged` platform guard + the publish set-upload; unit on (a) fetch set/asset derivation, (b) apply REFUSES a platform-mismatched / unstamped single (the regression that bricked hfenduleam), (c) the message render (semver present + fallback + URL). product_version + message tag under REQ-UPD-8.

## Sequencing
mint REQ-UPD-8 → add `product_version` metadata field (release.rs, additive) → xtask publish emits + UPLOADS `update-set.json` + stamps product_version → `cmd_update_fetch` stages the set → `apply_staged` platform guard + friendly message → gates (build/test/clippy/`--no-default-features`/traceable-reqs/xtask check) → push dev-freeform → **CI BOTH runners** → bump 0.3.2 + CHANGELOG → FF main → tag v0.3.2 → user signs (`release-publish --version 6`).

## Verification (the real cross-platform test — the bug's exact shape)
- Linux node (kitsubito): `spt update fetch` → stages the SET (both platform artifacts).
- Windows peer (hfenduleam) pulls → gets the **Windows** artifact (platform selection) → `spt update apply` → prints "Updated spt-core to v0.3.2." → `spt.exe` is a valid Windows **PE (MZ)**, runs. **No cross-platform brick.**
- Guard test: stage a mismatched/unstamped single → apply **refuses** (not silent corruption).

## Immediate next-session start
1. Recovery already done — hfenduleam on working **0.3.0**, kitsubito cache cleared. **DO NOT use `spt update fetch` until v0.3.2 ships** (the cross-platform brick is live in v0.3.1).
2. Start at: mint REQ-UPD-8 → add `product_version` to `ReleaseMetadata`/`UpdateSetMetadata`.
3. Confirm whether release.yml uploads `update-set.json` today (v0.3.1 assets were per-platform `.release.json` only → likely add it).
4. (Note: brain-only apply worked for 0.3.0→0.3.1, so no broker-ABI concern this hop.)


Plan written (`V032-PLAN.md`). Now commune — download fresh Psyche state, then write the delta with next steps + broad status + end goal:


Bash

$LIVE psyche-download doyle 2>&1 | grep -iE "<current |pending commune|v0.3" | head -4

fresh psyche state


<current machine="HFENDULEAM" project="spt-core" branch="main" head_sha="c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f" head_subject="fix(proto): CR-linesafe EVENT codec + send/ring stdin trim (REQ-HAZARD-E…" commits_since="0" commits_unpulled="0"/>
**INCIDENT ACTIVE: v0.3.1 cross-platform corruption. v0.3.2 track pending.**
**Current state:** v0.3.1 unsafe for mixed-OS subnets. HEAD main c5e3b27. v0.3.2 not started. Awaiting user signal to implement.
**v0.3.2 plan:**


Write

<live-context>
SESSION-END HANDOFF (resume next session). v0.3.2 plan is now WRITTEN (`V032-PLAN.md`, committed-pending) — implementation NOT started, by user direction ("write the plan, commune, resume next session").

**THE INCIDENT (root cause fully traced + recovered):** v0.3.1's `spt update fetch` (my REQ-UPD-7) caused a cross-platform binary corruption. kitsubito (Linux) `update fetch` staged a single-platform `SignedRelease` — and `ReleaseMetadata` has NO platform/triple field, so a single-release apply is platform-BLIND. hfenduleam (Windows) pulled it over P2P (the pump serves a staged `Single` as-is), `update apply` verified clean (sig+sha ok, no platform check) and swapped a **Linux ELF in as `spt.exe`** → "not a valid application." RECOVERED from `spt.exe.old-5` (the PE backup the swap leaves); kitsubito's staged cache CLEARED so it stops serving it to SPT_DEV (ENLYZEAM was at risk). **hfenduleam is back on working 0.3.0.**

**v0.3.2 fix set (REQ-UPD-8 + message) — full detail in V032-PLAN.md:**
1. `spt update fetch` must stage the multi-platform SET (`SignedUpdateSet` → `cache.stage_update_set`), not a single `SignedRelease` — reuses the existing platform-safe REQ-UPD-6 path (`plan_verified_update_set` selects `release::current_platform()`). Needs the release to UPLOAD `update-set.json` (xtask already BUILDS+signs the set at publish, main.rs:799 — just confirm/extend it to upload as a release asset; v0.3.1 published only per-platform `.release.json`).
2. Defense-in-depth: `apply_staged` platform guard — refuse a staged artifact whose platform != `current_platform()`; an unstamped legacy `Single` → refuse (fail-safe). This guard ALONE would've prevented the brick → ships regardless.
3. (c) apply message: `APPLIED:5` → multi-line:
   `Updated spt-core to v0.3.2.`
   `Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases`
   Needs semver → add `product_version: String` (additive serde) to `ReleaseMetadata`+`UpdateSetMetadata`, maintainer-stamped from workspace.package.version at publish; fallback "Updated spt-core (release N)." when absent.

**Process note:** keep the lesson — push→CI-on-both-runners BEFORE tagging (followed all session); an unsigned draft is freely re-taggable. But THIS bug slipped past CI entirely because it's a CROSS-NODE, MIXED-PLATFORM runtime interaction — no single-node unit/CI run exercises "Linux node serves Windows peer." The v0.3.2 verification MUST be the real cross-platform rig test (Linux fetch → Windows peer pulls → applies a valid PE), not just units.
</live-context>
<project-context>
HEAD = main `c5e3b27` (v0.3.1, PUBLIC + signed, counter 5). Working tree has uncommitted V031-PLAN.md (already committed earlier) + V032-PLAN.md (new, uncommitted) + .claude/doyle-commune.md.

**END GOAL (the project):** spt-core = the harness-independent CORE of an agent ecosystem (inter-agent messaging, live-agent lifecycle, terminal hosting, P2P subnet networking, self-update, runtime-manifest harness contract). A single `spt` binary + Rust crates; a clean-room rebuild of the sister project `claude_skill_owl`. Public since v0.1.0 (2026-06-05). Distributed via signed releases on SaberMage/spt-releases + P2P self-update across a node's subnet mesh.

**STATUS:** Milestones M0–M8 done; mesh full-mesh arc done (v0.3.0); daemon-lifecycle + boot-race self-heal done + rig-proven (v0.3.1). Fleet: kitsubito (Linux) on v0.3.1; hfenduleam (Windows, this box) on v0.3.0 (the apply bricked it, recovered to 0.3.0); ENLYZEAM in SPT_DEV (platform unknown). Subnet SPT_DEV has all 3.

**IMMEDIATE NEXT (next session, in order):**
1. Commit V032-PLAN.md (+ this is the resume anchor).
2. Implement v0.3.2 per V032-PLAN.md: mint REQ-UPD-8 → `product_version` metadata field → xtask uploads `update-set.json` → fetch stages the SET → apply platform-guard + friendly message → gates → CI both runners → release v0.3.2 (sign counter 6).
3. Real cross-platform rig verify (Linux fetch → Windows pull → valid PE applied).
4. Then re-roll the fleet to v0.3.2 (hfenduleam via installer first, since fetch is unsafe until 0.3.2).

**HARD RULE until v0.3.2 ships:** do NOT use `spt update fetch` — it bricks cross-platform peers (live in v0.3.1). Use the installer for cross-version updates.

Monotonic release counter: v0.1.0=1 · v0.1.1=2 · v0.2.0=3 · v0.3.0=4 · v0.3.1=5 · (v0.3.2→6). traceable-reqs binding in force; --help changes ⇒ xtask gen. Related memories: [[boot-race-self-heal]], [[daemon-service-detection-gotcha]], [[kitsubito-linux-rig]].
</project-context>


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\doyle-commune.md (file state is current in your context — no need to Read it back)

<live-context>
SESSION-END HANDOFF (resume next session). v0.3.2 plan is now WRITTEN (`V032-PLAN.md`, committed-pending) — implementation NOT started, by user direction ("write the plan, commune, resume next session").

**THE INCIDENT (root cause fully traced + recovered):** v0.3.1's `spt update fetch` (my REQ-UPD-7) caused a cross-platform binary corruption. kitsubito (Linux) `update fetch` staged a single-platform `SignedRelease` — and `ReleaseMetadata` has NO platform/triple field, so a single-release apply is platform-BLIND. hfenduleam (Windows) pulled it over P2P (the pump serves a staged `Single` as-is), `update apply` verified clean (sig+sha ok, no platform check) and swapped a **Linux ELF in as `spt.exe`** → "not a valid application." RECOVERED from `spt.exe.old-5` (the PE backup the swap leaves); kitsubito's staged cache CLEARED so it stops serving it to SPT_DEV (ENLYZEAM was at risk). **hfenduleam is back on working 0.3.0.**

**v0.3.2 fix set (REQ-UPD-8 + message) — full detail in V032-PLAN.md:**
1. `spt update fetch` must stage the multi-platform SET (`SignedUpdateSet` → `cache.stage_update_set`), not a single `SignedRelease` — reuses the existing platform-safe REQ-UPD-6 path (`plan_verified_update_set` selects `release::current_platform()`). Needs the release to UPLOAD `update-set.json` (xtask already BUILDS+signs the set at publish, main.rs:799 — just confirm/extend it to upload as a release asset; v0.3.1 published only per-platform `.release.json`).
2. Defense-in-depth: `apply_staged` platform guard — refuse a staged artifact whose platform != `current_platform()`; an unstamped legacy `Single` → refuse (fail-safe). This guard ALONE would've prevented the brick → ships regardless.
3. (c) apply message: `APPLIED:5` → multi-line:
   `Updated spt-core to v0.3.2.`
   `Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases`
   Needs semver → add `product_version: String` (additive serde) to `ReleaseMetadata`+`UpdateSetMetadata`, maintainer-stamped from workspace.package.version at publish; fallback "Updated spt-core (release N)." when absent.

**Process note:** keep the lesson — push→CI-on-both-runners BEFORE tagging (followed all session); an unsigned draft is freely re-taggable. But THIS bug slipped past CI entirely because it's a CROSS-NODE, MIXED-PLATFORM runtime interaction — no single-node unit/CI run exercises "Linux node serves Windows peer." The v0.3.2 verification MUST be the real cross-platform rig test (Linux fetch → Windows peer pulls → applies a valid PE), not just units.
</live-context>
<project-context>
HEAD = main `c5e3b27` (v0.3.1, PUBLIC + signed, counter 5). Working tree has uncommitted V031-PLAN.md (already committed earlier) + V032-PLAN.md (new, uncommitted) + .claude/doyle-commune.md.

**END GOAL (the project):** spt-core = the harness-independent CORE of an agent ecosystem (inter-agent messaging, live-agent lifecycle, terminal hosting, P2P subnet networking, self-update, runtime-manifest harness contract). A single `spt` binary + Rust crates; a clean-room rebuild of the sister project `claude_skill_owl`. Public since v0.1.0 (2026-06-05). Distributed via signed releases on SaberMage/spt-releases + P2P self-update across a node's subnet mesh.

**STATUS:** Milestones M0–M8 done; mesh full-mesh arc done (v0.3.0); daemon-lifecycle + boot-race self-heal done + rig-proven (v0.3.1). Fleet: kitsubito (Linux) on v0.3.1; hfenduleam (Windows, this box) on v0.3.0 (the apply bricked it, recovered to 0.3.0); ENLYZEAM in SPT_DEV (platform unknown). Subnet SPT_DEV has all 3.

**IMMEDIATE NEXT (next session, in order):**
1. Commit V032-PLAN.md (+ this is the resume anchor).
2. Implement v0.3.2 per V032-PLAN.md: mint REQ-UPD-8 → `product_version` metadata field → xtask uploads `update-set.json` → fetch stages the SET → apply platform-guard + friendly message → gates → CI both runners → release v0.3.2 (sign counter 6).
3. Real cross-platform rig verify (Linux fetch → Windows pull → valid PE applied).
4. Then re-roll the fleet to v0.3.2 (hfenduleam via installer first, since fetch is unsafe until 0.3.2).

**HARD RULE until v0.3.2 ships:** do NOT use `spt update fetch` — it bricks cross-platform peers (live in v0.3.1). Use the installer for cross-version updates.

Monotonic release counter: v0.1.0=1 · v0.1.1=2 · v0.2.0=3 · v0.3.0=4 · v0.3.1=5 · (v0.3.2→6). traceable-reqs binding in force; --help changes ⇒ xtask gen. Related memories: [[boot-race-self-heal]], [[daemon-service-detection-gotcha]], [[kitsubito-linux-rig]].
</project-context>


Commune queued. Commit the plan as the durable resume anchor (+ FF main):


Bash

cd "C:/Users/decid/Documents/projects/spt-core" && git checkout dev-freeform 2>&1 | tail -1 && git add V032-PLAN.md && git commit -q -F - <<'EOF'
docs(plan): V032-PLAN.md — platform-safe update fetch + apply guard (v0.3.1 corruption fix)

v0.3.1's `spt update fetch` staged a single-platform SignedRelease (platform-less
ReleaseMetadata) → a Windows peer pulled the Linux artifact over P2P and applied
an ELF as spt.exe (bricked, recovered). Plan: fetch stages the multi-platform
SignedUpdateSet (reuse REQ-UPD-6 platform selection) + a defense-in-depth apply
platform guard + a friendly apply message ("Updated spt-core to vX.Y.Z" + changelog
URL, needs a product_version semver field). Mint REQ-UPD-8. Implement next session.

DO NOT use `spt update fetch` until v0.3.2 ships — it bricks cross-platform peers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
git checkout main 2>&1 | tail -1 && git merge --ff-only dev-freeform 2>&1 | tail -1 && git push origin main dev-freeform 2>&1 | tail -2 && git log --oneline -1

commit plan + FF main + push


Command running in background with ID: b5jlrflo5. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\afad552a-7216-48d3-ba81-3c8294f2a399\tasks\b5jlrflo5.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b5jlrflo5</task-id>
<tool-use-id>toolu_01R8q79KwfMgMaxW5fmFHdj9</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\afad552a-7216-48d3-ba81-3c8294f2a399\tasks\b5jlrflo5.output</output-file>
<status>completed</status>
<summary>Background command "commit plan + FF main + push" completed (exit code 0)</summary>
</task-notification>


until grep -qE "V032|main|dev-freeform|error|rejected" "C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/afad552a-7216-48d3-ba81-3c8294f2a399/tasks/b5jlrflo5.output" 2>/dev/null; do sleep 3; done; cat "C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/afad552a-7216-48d3-ba81-3c8294f2a399/tasks/b5jlrflo5.output"

read plan commit+push


Switched to branch 'dev-freeform'
warning: in the working copy of 'V032-PLAN.md', LF will be replaced by CRLF the next time Git touches it
Switched to branch 'main'
 create mode 100644 V032-PLAN.md
   c5e3b27..4e774a3  dev-freeform -> dev-freeform
   c5e3b27..4e774a3  main -> main
4e774a3 docs(plan): V032-PLAN.md — platform-safe update fetch + apply guard (v0.3.1 corruption fix)


Prompt:
<task-notification>
<task-id>b5jlrflo5</task-id>
<tool-use-id>toolu_01R8q79KwfMgMaxW5fmFHdj9</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\afad552a-7216-48d3-ba81-3c8294f2a399\tasks\b5jlrflo5.output</output-file>
<status>completed</status>
<summary>Background command "commit plan + FF main + push" completed (exit code 0)</summary>
</task-notification>


Done — set up for next session.

**Recovery (this session):** hfenduleam restored to working `spt 0.3.0`; kitsubito's staged Linux release cleared (ENLYZEAM protected). No live brick risk as long as nobody runs `spt update fetch`.

**Plan written + committed** (`4e774a3`, `V032-PLAN.md`, on main + dev-freeform) — implementation-ready. v0.3.2 fix set:
1. `spt update fetch` stages the **multi-platform `SignedUpdateSet`** (reuses the existing platform-safe selection) — not a platform-blind single release.
2. **Apply platform-guard** (refuse wrong-OS / unstamped artifact) — the safety net that alone would've stopped the brick.
3. Apply message → `Updated spt-core to v0.3.2.` + the changelog URL (needs an additive `product_version` semver field).

**Communed to Psyche** — incident root cause + recovery, the v0.3.2 plan, immediate next steps, broad status, and the project end goal. Plus the key lesson: this bug slipped CI because it's a **cross-node mixed-platform runtime interaction** no single-node run exercises → v0.3.2 must be verified on the real Linux→Windows rig, not just units.

**Resume next session at:** mint REQ-UPD-8 → add `product_version` metadata → implement per `V032-PLAN.md`.

**Standing rule until v0.3.2 ships:** don't use `spt update fetch` (bricks cross-platform peers); use the installer for cross-version updates.