# Self-update

spt-core keeps itself current without ever interrupting your agents, and
without trusting anything unsigned.

## The invariant

**No endpoint process terminates or suspends during a self-update.** The
daemon's broker (holding PTYs, child processes, sockets) stays up; the brain
(all logic) swaps under it. A hosted session's process id and byte stream are
identical before and after.

## The trust chain

- Every release ships `SignedRelease` metadata: an Ed25519 signature over the
  release's artifact digests.
- Every binary embeds the **two-key trusted set** — an active primary and a
  never-used offline recovery key. Verification requires a valid signature
  from a trusted key *and* a matching artifact digest; an unverified binary
  never reaches the apply step.
- Losing the primary key is a non-event: the next release is signed with the
  recovery key (already trusted by every deployed binary) and rotates in a
  fresh primary.
- **Adapters sign their own content.** A `file_pull` adapter update is
  verified against the adapter author's key from its manifest; a `delegated`
  update is trusted only when the manifest attests the delegated updater
  verifies its own content (`self_verifies`). spt-core's release keys never
  vouch for adapter bytes.

## How updates move

Peer-propagated: one node fetches a release; paired nodes offer/fetch staged
releases from each other, each verifying independently before staging.
Updating is **consent-gated by default** — a notification surfaces at your
most-recently-active endpoint, and `spt update apply` is the explicit ack
(it re-verifies the staged release before touching the live daemon).
Full-auto is an explicit opt-in.

After self-updating, spt-core **ripple-updates registered adapters** through
each manifest's declared `[update]` avenue.

### Composite adapter updates — a delegated post-step (since v0.16.0)

An adapter can run a second, adapter-owned step **after** its primary update
avenue resolves, under the same `spt adapter update`. Declaring an optional
`[update.post]` sub-table (`command` required; an attestation-only
`self_verifies` flag) lets one lever both pull the adapter's `.spt` (e.g. from
`gh_release`) **and** run an in-harness sync (e.g. a plugin updater). The
post-step:

- **runs unconditionally** — even when the primary avenue was a no-op (its own
  idempotent check decides what changes);
- receives a **published JSON line on stdin** describing the just-resolved
  update (`adapter_applied`, `version`, `previous_version`, `adapter_dir`, …;
  additive keys only — ignore unknown);
- **decides the post-update notice via stdout** — custom text supersedes the
  static `[update].message`, the reserved sentinel `!!update-message!!` fires
  the static message, empty prints nothing;
- is **failure-isolated** — if it fails, spt-core warns loudly and falls back
  to the today behavior (an applied update fires `[update].message`); a
  committed pull is never rolled back.

The exact stdin keys, sentinel, and notice precedence are in the
[manifest `[update.post]` reference](../harness-contract/manifest.md#update--adapter-self-update).

## Commands

`spt update` · the consent notification flow (`spt notif`) —
[CLI reference](../cli/reference.md).
