Documentation
¶
Overview ¶
Package update implements the core of `abcd update` (itd-130 / spc-32): a user-invoked fetch/verify/swap for the PATH-installed binary. It is adr-38 tier 2 — the verb's documented meaning IS the fetch; nothing here runs implicitly — and it completes a chosen update (tier 3): the tag is named before anything is replaced, the asset is verified against the same release's checksums.txt, and the swap is atomic in the target's directory. The transport is pinned: no proxy, no CA overrides from the environment, redirects only onto the release origin's own hosts, every hop re-checked under the urlguard policy. Transport-agnostic (adr-23): everything returns a structured Report; the CLI renders it.
Index ¶
Constants ¶
const RemedyPluginUpdate = "take a plugin update in the host (e.g. /plugin update abcd)"
RemedyPluginUpdate is the way out of every refusal whose cause is a binary the plugin install owns: the plugin-root refusal here, and the stale-binary note the CLI appends to an unknown command (iss-2608230943088357). One string, so the two surfaces cannot drift apart.
const UpdatedFormat = "abcd updated from %s to %s"
UpdatedFormat is the one wording of the line every binary swap prints when it completes: `abcd update`'s receipt and hooks/bootstrap.sh's success notice both lead with it, and the session check uses it for the one swap whose output nobody sees (the ruling CJ1b in .abcd/work/DECISIONS.md). The bootstrap is POSIX sh and cannot import it, so TestBootstrapUpdateLineIsTheSharedWording holds the script's printf literal to this constant.
Variables ¶
This section is empty.
Functions ¶
func NextStep ¶ added in v0.7.1
func NextStep(t ahoy.UpdateTarget) string
NextStep renders the one line naming how the classified install is brought forward — the answer `abcd update` itself would give. The swappable shape is pointed at the verb; every other shape is pointed at the mechanism that owns its binary, taken verbatim from Plan's refusal so the remedy text lives in exactly one place. This is the canonical "what do I run next" primitive: `update --check` and every schema-too-new refusal render through it (itd-130 / iss-2609012111168872).
func NextStepHere ¶ added in v0.7.1
func NextStepHere() string
NextStepHere classifies the install that answers to `abcd` on this machine and renders NextStep for it. Disk only: the walk reads PATH and the plugin root and touches no network (adr-38), so it is safe inside an error path.
func TooNew ¶ added in v0.7.1
TooNew is the canonical schema-too-new refusal: an artefact stamped with a schema this binary does not know yet. The frame is stable ("<what> schema vN; this abcd knows up to vM — update abcd: ...") and the tail names the command for this install shape, so the message ends in something the user can type rather than the verbless "upgrade" it used to end in.
func UpdatedLine ¶ added in v0.12.0
UpdatedLine renders UpdatedFormat. The caller sanitises both tags: they are read off an HTTP response or a record on disk.
Types ¶
type Ownership ¶ added in v0.8.0
type Ownership string
Ownership names the proof that the file about to be replaced is abcd's own. There are three, and only the first depends on anything a forge still serves — which is why the other two exist (iss-2609012000222546): release objects are deletable, and an ownership proof that rests on them stops proving the day they are deleted, stranding an install with no way forward.
const ( // OwnedByManifest: the file's digest appears in a published release's // checksums.txt. The strongest proof and the only one that also dates the // file, so it is tried first — but it dies with the release object. OwnedByManifest Ownership = "release-manifest" // OwnedByRunningExecutable: the file IS the executable this process is // running from. Nothing else can be: the code asking the question was // loaded out of those very bytes, so the file is abcd by construction and // no forge object is consulted to know it. OwnedByRunningExecutable Ownership = "running-executable" // OwnedByPathEntry: ~/.abcd.noindex/path-entry records this exact file as the copy // abcd installed, and the bytes still hash to what was recorded. Local, // written at install time by every install route, and equally independent // of what the forge still serves. OwnedByPathEntry Ownership = "path-entry-record" )
type Refusal ¶
type Refusal struct {
Shape string `json:"shape"`
Detail string `json:"detail"`
Remedy string `json:"remedy"`
}
Refusal is a named no: the shape that refused, why, and the remedy. Every refusal is loud and names its way out (the itd-130 dispatch contract).
func Plan ¶
func Plan(t ahoy.UpdateTarget) *Refusal
Plan maps the resolved PATH target onto the spc-32 dispatch table: only a regular file outside a package manager's tree proceeds; every other shape is a refusal naming the mechanism that owns it.
type ReleaseAssets ¶ added in v0.11.0
type ReleaseAssets struct {
// contains filtered or unexported fields
}
ReleaseAssets fetches named assets of one repository's releases under the updater's transport policy: the pinned origin, https only, the urlguard address policy, GitHub's own asset hosts as the only legal redirects, and the proxy and CA overrides ignored by its own client. It is the fetcher `abcd launch`'s parity diff reads a previous release's payload archive and checksums.txt through, and only when the operator asks for it (adr-38 tier 2). It verifies nothing itself: the caller checks the archive against the release's own checksums.
func NewReleaseAssets ¶ added in v0.11.0
func NewReleaseAssets(origin string) (*ReleaseAssets, error)
NewReleaseAssets builds the fetcher for the repository at origin, an https://github.com/<owner>/<repo> address.
func (*ReleaseAssets) EnvIgnored ¶ added in v0.11.0
func (a *ReleaseAssets) EnvIgnored() []string
EnvIgnored names the transport-override variables that were set when the fetcher was built and that its client does not honour.
func (*ReleaseAssets) FetchReleaseAsset ¶ added in v0.11.0
FetchReleaseAsset downloads one asset of tag. found is false, with no error, when the release or the asset does not exist; url is what was requested.
type Report ¶
type Report struct {
Origin string `json:"origin,omitempty"`
Tag string `json:"tag,omitempty"`
Asset string `json:"asset,omitempty"`
Digest string `json:"digest,omitempty"`
TargetPath string `json:"target_path,omitempty"`
OldVersion string `json:"old_version,omitempty"`
OldDigest string `json:"old_digest,omitempty"`
NewVersion string `json:"new_version,omitempty"`
Ownership Ownership `json:"ownership,omitempty"`
Action Action `json:"action"`
EnvIgnored []string `json:"env_ignored,omitempty"`
Refusal *Refusal `json:"refusal,omitempty"`
}
Report is the update receipt: origin, tag, digest, and what happened. It prints in both TTY and piped modes — silence is only ever about progress.
type Updater ¶
type Updater struct {
// contains filtered or unexported fields
}
Updater fetches and applies releases from one pinned origin.
func NewGitHubUpdater ¶
func NewGitHubUpdater() *Updater
NewGitHubUpdater builds the updater as it ships: the pinned origin, the full urlguard policy, and GitHub's own asset hosts as the only legal redirect targets.
func (*Updater) Apply ¶
Apply completes the chosen update: prove the target file is abcd's, download the tag's asset, verify it against the release's own manifest, and swap atomically. It never leaves a partial file: verification happens before anything at the target path is touched.
Ownership is proven by whichever of the three proofs answers first (see Ownership). The published-digest proof is tried first because it also DATES the file — the old version is derived from the release whose manifest matches the on-disk bytes, never read from the running binary, which may not be the file being replaced. It is not, however, the only proof, and must not be: release objects are deletable, and when the 2026-08-30 cleanup removed every manifest older than v0.6.9 this refused every install it had previously vouched for and sent the user to delete their only abcd (iss-2609012000222546). The two local proofs cannot be revoked from off the machine; a file they carry has no derivable version, so the receipt names it an unpublished build and reports its digest.