Documentation
¶
Overview ¶
Package upgrade is the update transaction (lifecycle plan R2): resolve the latest release, decide whether this install may act, download, verify, and STAGE -- never activate. Activation (rename + re-exec + converge) is R3's; everything here is safe to run on a machine doing live work.
Index ¶
- Constants
- Variables
- func ClearPendingConverge(stateDir string) error
- func HeldVersion(stateDir string) string
- func LatestVersion(ctx context.Context, baseURL string) (string, error)
- func PendingConverge(stateDir string) string
- func PrevDir(stateDir string) string
- func StageDir(stateDir string) string
- func StatePath(stateDir string) string
- func TrustedKey(pubHex string) bool
- func VerifyChecksums(checksums, sig []byte) error
- type ActivateOptions
- type CompatManifest
- type Decision
- type Options
- type Owner
- type State
Constants ¶
const DefaultBaseURL = "https://github.com/Nathandela/swarm"
DefaultBaseURL is the release host. A test substitutes its own httptest server; production never overrides it.
Variables ¶
var ErrNothingStaged = errors.New("upgrade: nothing staged; run `swarm upgrade --stage` first")
ErrNothingStaged is Activate's answer when no verified build awaits.
var ErrNothingToRollBack = errors.New("upgrade: no rollback slots; nothing was ever activated from here")
ErrNothingToRollBack is Rollback's answer when no slots exist.
var ErrOffline = errors.New("upgrade: release host unreachable")
ErrOffline wraps every network failure of the resolve step, so callers can fold "no network at 04:00" into a quiet skip.
Functions ¶
func ClearPendingConverge ¶
ClearPendingConverge marks the install converged. The caller clears ONLY on a converge that exited converged (or found no daemon to converge, which the next client start resolves onto the installed binary).
func HeldVersion ¶
HeldVersion reads the hold; "" when none.
func LatestVersion ¶
LatestVersion resolves the latest published tag from the /releases/latest HTTP redirect -- one HEAD, no API, and therefore no per-IP unauthenticated rate limit for a NAT'd fleet to exhaust (committee: Gemini #8, Fable LOW). The Location it follows ends .../releases/tag/<tag>.
func PendingConverge ¶
PendingConverge reports the version whose install awaits a confirmed converge; "" when none.
func StatePath ¶
StatePath is where the transaction records each run. cmd/swarm's doctor reads the same function, so reader and writer cannot drift on the location.
func TrustedKey ¶
TrustedKey reports whether pubHex is one of the trust slots -- the release pipeline's pre-publish selfcheck, so a valid-but-wrong secret fails the RELEASE instead of shipping assets every fleet machine rejects (R2/R3 audit: codex finding 7, Fable M5).
func VerifyChecksums ¶
VerifyChecksums reports whether sig (the checksums.txt.sig release asset, base64 of a raw ed25519 signature) signs checksums over any trusted key.
Types ¶
type ActivateOptions ¶
type ActivateOptions struct {
StateDir string
BinPath string // the installed binary to replace (os.Executable, unresolved)
// Installed is the running binary's version (internal/version.Version): a
// staged tag equal to it is an interrupted activation's leftover, answered
// "current" instead of re-running the transaction against the already-
// installed build -- which would rebuild the rollback slots from it and
// destroy the true originals (R2/R3 audit, Fable M3b).
Installed string
// DaemonAlive reports whether a daemon currently holds the singleton lock.
// The wire guard needs it: ZERO running sessions does not mean no daemon,
// and a live old daemon's next launch under a wire-bumped install is the
// pinned ProcessLost cell (R2/R3 audit, codex finding 2). nil is treated as
// "assume alive" -- fail closed, never open.
DaemonAlive func() bool
}
ActivateOptions configures activation.
type CompatManifest ¶
type CompatManifest struct {
Version string `json:"version"` // the release tag
Shimwire int `json:"shimwire"` // internal/shimwire.Version
Protocol int `json:"protocol"` // internal/protocol.Version
Schema int `json:"schema"` // internal/persist.SchemaVersion
}
CompatManifest is the release's compatibility card: the wire and schema constants the archived build was compiled with, emitted at build time from the SAME source constants (scripts/releasemanifest) and read at activation time from the STAGED archive. It exists so activation is a pure disk comparison -- the committee killed both alternatives: executing a staged binary to ask it (R4's probe, withdrawn), and installing first so converge could ask afterwards, which put a new binary under an old daemon and broke every launch on a wire bump (the compat matrix's own pinned ProcessLost cell). The manifest rides INSIDE the tarball as compat.json, so the signed checksum that covers the archive covers it too -- signed compatibility metadata, not a hidden verb (committee: codex finding 1).
func CurrentManifest ¶
func CurrentManifest(tag string) CompatManifest
CurrentManifest is THIS build's card -- what the emitter writes at release, and what activation compares a staged card against for the axes that gate.
type Decision ¶
type Decision struct {
Action string // "stage" | "current" | "refuse"
Reason string // human sentence; for refuse, names the owning delegate
Latest string // the resolved tag, when resolution succeeded
Owner Owner
// RefuseOutcome is the machine-readable refusal class ("refused-dev",
// "refused-owner", "refused-downgrade"), set exactly where the refusal is
// decided -- outcomes must never be re-derived from prose (Fable L4).
RefuseOutcome string
}
Decision is Check's answer: what a run of Stage would do, and why.
type Options ¶
type Options struct {
StateDir string // the swarm state dir; staging, lock and upgrade.json live under it
BinPath string // the binary being upgraded (os.Executable, unresolved)
Installed string // internal/version.Version of the running binary
BaseURL string // "" means DefaultBaseURL; tests point it at a fixture server
// AllowDowngrade permits latest < installed, for the yanked-release day.
// Never set by any unattended path (committee M-4: a re-pointed `latest`
// must not silently downgrade a fleet).
AllowDowngrade bool
}
Options configures one transaction run.
type Owner ¶
type Owner string
Owner classifies who manages the binary at a path. The update transaction self-replaces ONLY OwnerSelf: every package manager keeps its own books, and a binary rewritten behind those books is the recorded 2026-08-27 brew incident and the dpkg/rpm corruption the committee named (C2/C5) -- the exact argument that produced brew deference produces deference for every owner.
const ( OwnerSelf Owner = "self" // tarball / install-script: ours to replace OwnerBrew Owner = "brew" // Homebrew cask: `brew upgrade --cask swarm` owns it OwnerDpkg Owner = "dpkg" // apt/dpkg package: apt owns it OwnerRpm Owner = "rpm" // rpm/dnf package: dnf owns it OwnerGo Owner = "go" // `go install` under GOPATH/bin: the user's toolchain owns it OwnerNone Owner = "unknown" // classification failed; treated as NOT self (refuse) )
func ClassifyOwner ¶
ClassifyOwner answers for the RESOLVED path of the binary actually being upgraded -- never for what a package database merely remembers. The recorded brew incident is precisely both-present-and-diverged: a hand-copied /usr/local/bin/swarm beside a brew record, where `brew list --cask` succeeds and says nothing about the binary the daemon runs (committee H4, Sonnet #2).
type State ¶
type State struct {
CheckedAt time.Time `json:"checked_at"`
Installed string `json:"installed"`
Latest string `json:"latest,omitempty"`
Owner string `json:"owner,omitempty"`
// Outcome: "current", "staged", "offline", "busy", or "refused-dev",
// "refused-owner", "refused-downgrade", "failed-<step>". The fetch half's
// outcome ONLY -- activation (R3) records its own, separately, because
// merging them is how a checksum failure once hid behind a green converge.
Outcome string `json:"outcome"`
Detail string `json:"detail,omitempty"`
// StagedVersion is set while a verified build sits in the staging dir
// awaiting activation; "" otherwise.
StagedVersion string `json:"staged_version,omitempty"`
}
State is one run's durable outcome, written on EVERY run -- including the refusals and the offline skips -- because the unit exit code is deliberately lossy (a deferral is green) and this file is what `swarm doctor` and the TUI's quiet line read instead (committee C3: a month of failed downloads must not be invisible).
func Activate ¶
func Activate(opts ActivateOptions) (State, error)
Activate installs the staged build and execs its converge. ON SUCCESS IT DOES NOT RETURN -- the process becomes the new binary's `daemon restart --unattended`. Every other outcome returns with the state recorded: "deferred-wirebump" (staged kept, nothing installed), "failed-*" or ErrNothingStaged.
func Rollback ¶
func Rollback(opts ActivateOptions) (State, error)
Rollback restores the slot binaries over BinPath and execs the RESTORED binary's converge -- the same handoff rule as activation, for the same reason. On success it does not return. Hardened per the R2/R3 audit: the slots' card is REQUIRED (a rollback whose compatibility cannot be read is refused, never risked -- codex finding 5), the wire guard applies in the rollback direction too (finding 3: wire skew strands sessions either way), ownership is re-checked (finding 6), and a successful rollback CONSUMES its slots -- a second invocation finds nothing to restore instead of overwriting the hold with the restored version and re-arming the bad release (finding 5's treadmill).
func Stage ¶
Stage runs Check and, on "stage", downloads, verifies (sha256 against checksums.txt, checksums.txt against its ed25519 signature) and unpacks the build into StageDir. It NEVER touches BinPath -- activation is R3 -- so a machine mid-work loses nothing when this runs at 04:00. Every outcome, including each refusal, lands in upgrade.json. The whole run holds an exclusive flock; a second concurrent run reports "busy" and touches nothing (committee M-3).