# Platform-targeted update sets for debug rollout and release distribution

<!-- [doc->REQ-UPD-6] -->

## Status

accepted (2026-06-06)

spt-core's peer-propagated update substrate should understand a signed update as a platform-targeted artifact set, not only as one opaque binary blob. A node offering an update may stage metadata for multiple platform artifacts (for example Windows x86_64 and Linux x86_64); each recipient verifies the set metadata, fetches only the artifact matching its own platform, verifies that artifact's digest, and then follows the existing consent/apply path.

An update set has one signature over the whole set metadata, with per-platform artifact digests inside that signed metadata. This makes a debug version or stable version an atomic, auditable set while still letting each recipient fetch only its matching artifact.

If a recipient's platform is missing from an offered set, the result is a typed rejection such as `NoArtifactForPlatform`, not an up-to-date/no-op answer. A malformed or incomplete debug rollout should be visible in convergence reporting.

The receiver may support both the original single-artifact `SignedRelease` shape and the new platform-targeted set shape during migration. Debug rollout can be the first user of update sets; stable release publishing can move later without blocking the debugging workflow.

Platform keys in the signed metadata use Rust target triples, for example `x86_64-pc-windows-msvc` and `x86_64-unknown-linux-gnu`. Release asset names remain a packaging concern and may keep their public-friendly names; target triples are the protocol identifiers.

This keeps debug rollout fast without turning it into raw peer file-copy: a fast coordinator can assemble and sign every platform artifact, then let the existing subnet update machinery distribute the right bytes to each node. The generic receiver/protocol support belongs in the production daemon because public releases and debug rollouts have the same cross-platform distribution problem. The rollout driver does not: build recipes, cross-build/WSL/runner coordination, dev-key signing, and "stage this debug rollout" live in source-repo maintainer tooling (for example `xtask`), not in the public `spt` CLI/help/docs for end users.

Release monotonicity is scoped to the pinned channel. Stable releases and debug rollouts use separate counters, so a fast debug sequence cannot consume, block, or roll back the stable release counter; rollback protection compares only against the latest accepted version for the recipient's pinned channel.

Debug rollout targets are opt-in by channel pin, not by subnet membership alone. A debug offer may traverse trusted subnet peers, but a stable-pinned node rejects it at the metadata gate; only nodes pinned to the debug channel and provisioned with the dev signing key can stage and apply it.

Pinning a lab node to debug uses the existing release-key overlay: `$SPT_HOME/identity/release-keys.json` adds the dev public key and sets `"channel": "debug"` (for example `"keys": {"dev-debug-2026": "<ed25519-public-key-hex>"}`). Removing the key or pinning the channel back to `"stable"` takes the node out of debug rollout eligibility.

Channel pinning is exclusive. A debug-pinned node accepts only debug-channel offers and rejects stable offers until re-pinned to stable; a stable-pinned node accepts only stable-channel offers. This keeps rollback accounting and test provenance unambiguous.

Maintainer tooling may provide a helper to write that file correctly, but the canonical mechanism remains the generic release-key overlay. `spt` itself does not need a special debug-mode command; source-repo tooling can offer convenience such as `xtask debug-pin` for local lab setup.

Bad debug builds recover the same way as stable releases: publish a newer signed debug rollout, even if the artifact bytes are a previous known-good binary. Numeric rollback remains rejected; the debug channel never gets a bypass around the anti-rollback invariant.

Debug signing key custody is deliberately weaker than stable release signing. The stable release seed stays manual/password-manager driven per ADR-0015; the debug seed may live in a long-lived `SPT_DEBUG_RELEASE_SEED` environment variable on the fast coordinator so agents can drive rollouts. This is acceptable only because the key's blast radius is limited to nodes explicitly pinned to `debug` and provisioned with that dev public key; rotation is minting a new debug key, updating lab overlays, and deleting the old env var/key.

`xtask debug-rollout` must not auto-mint a signing key. If `SPT_DEBUG_RELEASE_SEED` is missing it fails with setup guidance; a separate `xtask debug-keygen` may print a seed/public-key pair for deliberate lab setup.

Initial debug rollout scope is the spt-core binary only. Adapter updates remain on the adapter ripple-update seam with adapter-owned signing or delegated verification; maintainer tooling may compose adapter updates later, but the fast core-debug loop should not blur those trust boundaries.

Debug rollout may carry broker-touching updates. Brain-only debug applies keep the normal no-endpoint-termination invariant. If the candidate class requires replacing the broker, apply requires the node to quiesce first: no broker-held endpoints or live resources may be running, and tooling must refuse with a typed "shut down/suspend hosted endpoints first" result rather than cycling them implicitly.

The first broker-touching debug flow refuses only; it does not auto-suspend or auto-shutdown endpoints. The refusal should list the blocking broker-held endpoints/resources and print the commands needed to quiesce them, preserving lifecycle control with the operator.

Initiation is local staging plus normal pull-based peer propagation. The debug driver writes the signed update set into the local daemon's staged-release cache and may poke the daemon to advertise/check immediately for responsiveness, but it must not introduce a bespoke "push this binary to node X" path.

The debug driver should verify convergence after staging. By default, "expected" means every reachable node in the selected lab subnet that is pinned to `debug`; maintainer tooling may accept an explicit node list for targeted tests. It watches until every expected debug-pinned reachable node reports the target debug version applied, or returns a timeout table with per-node state such as not pinned, offline, staged awaiting consent, blocked by live broker resources, applied, or rejected.

The debug channel sequence is maintainer-tooling state, not project truth or node identity. It may live in an ignored source-repo local file such as under `target/`; losing it is recoverable by choosing a higher debug version. Deployed nodes only require that the next accepted debug metadata version is greater than the current debug version they have seen.

Debug rollout metadata should carry signed diagnostic provenance: source git commit, dirty flag, build host, build timestamp, and platform target. These fields are for observability ("which bits are running where?"), not for trust; verification remains signature plus artifact digest plus channel/version policy.

The first implementation slice is intentionally narrow: update-set metadata and verification, daemon staging/read path with local platform selection, maintainer `xtask` helpers for debug pinning and debug rollout, and loopback/two-host proof of propagation plus convergence reporting. Remote build orchestration, rich dashboards, adapter inclusion, and stable-release publishing migration are later work.

## Considered Options

- **Single artifact per staged update** — simpler metadata, but forces dev tooling to stage different binaries through platform-specific side paths and makes debug rollout brittle.
- **Debug-only remote copy/install scripts** — fastest to hack, but bypasses the verification, rollback, channel, and consent gates that peer propagation exists to protect.
- **Platform-targeted update set** — chosen: one generic substrate shape, recipients fetch only their matching artifact, and both stable releases and debug rollouts reuse the same trust path.
