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
SignedReleasemetadata: 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_pulladapter update is verified against the adapter author’s key from its manifest; adelegatedupdate 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.
Commands
spt update · the consent notification flow (spt notif) —
CLI reference.