Skip to content

distribution: extract the shared updater state machine (check → select → download → snapshot) — three apps now duplicate it #220

Closed

Description

@turinglambdaai

Follow-up to #212's progress reporting (PR#219). With movebit's updater shipped this week, the family now has three near-identical ~400-line update.rkt modules (taskly, fulcrum, movebit) layering the same orchestration on rivet/distribution primitives:

  • embedded-key constants + the raw→DER key wrap (the exact conversion fulcrum distribution: parse an Ed25519 public key from bytes, not only from a path #165 fixed — every new app re-derives it);
  • a state box (phase / percent / message / available-version) that background download threads publish and the UI polls through an RPC, because RVT1 events are thread-local;
  • perform-check! (reset → fetch → verify → select → map to a result hash) and start-download! (guard against a running worker, spawn the thread, publish progress/downloaded/error);
  • platform/arch symbols matching the release manifest vocabulary.

The duplication has already cost real bugs twice: the redirect-following fix (#153) had to be hand-applied to each app's downloader, and fulcrum's DER-key contract violation (#165) would have bitten any copier. Movebit additionally hit an Optional-encoding class error (#f vs (void)) that a shared encoder would have prevented.

Proposal

rivet/distribution/updater.rkt grows the orchestration, parameterized by the app's identity:

(make-updater #:application-id ... #:public-key ... #:key-id ... #:base-url ...
              #:data-dir ... #:maximum-bytes ...)
(updater-check! updater)        ; → (hash status available-version ...)
(updater-download! updater)     ; background thread, progress into the state
(updater-state updater)         ; phase / percent / message snapshot

Apps keep what is genuinely theirs: the embedded key, the base URL, the install step (which stays host-owned — movebit's staged-swap, fulcrum's open-the-installer, taskly's restart flow), and the auto-check policy.

Out of scope here

  • Install mechanics: movebit's Windows staged-swap helper (tar + detached cmd + robocopy) works but needs real-time-AV robustness work before it is a blessed pattern; the install-plan framework already in updater.rkt is the right landing zone once it matures.
  • Manifest contents (e.g. adding MSI artifacts for Windows silent upgrades) — release-pipeline side.

Reference implementations

  • fulcrum app/update.rkt (398 lines, shipped 0.4.x)
  • movebit racket/movebit/update.rkt (422 lines, shipped fd22218)
  • PR#219 already moves the download-progress half upstream.

Happy to do the extraction PR once the contract shape looks right.

Activity

  1. turinglambdaai commented on Oct 12, 2026

    @turinglambdaai
    OwnerAuthor

    Audit finding: this extraction must also absorb bounded-worker behavior instead of copying the weakest of the three implementations. Taskly already carries a 120-second no-progress watchdog and 30-minute total bound because get-pure-port / read-bytes-avail! can wait forever when a server stalls or never closes after the advertised bytes. Fulcrum and MoveBit remain vulnerable to a permanent downloading phase.

    I am treating these as part of the upstream contract:

    • One synchronized check/download operation per updater.
    • Deeply immutable state snapshots.
    • Cumulative progress from the first-party download-update.
    • Explicit cancellation.
    • Configurable check, no-progress, and total deadlines using monotonic time.
    • Terminal cancelled/error states and guaranteed .partial cleanup.
    • Deterministic injected-transport tests with no live network.
    • Fail-closed platform and architecture validation and a stable per-installation rollout bucket.

    Installation remains host/platform-owned as proposed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions