moonbit-community/proton/updater does not have a README file

    ArtifactFetch

    type ArtifactFetch = async (String, Int64, async (Bytes) -> Unit raise UpdateError) -> Unit raise UpdateError

    Streams an artifact into a consumer without retaining the whole body.

    The expected size is authenticated manifest data. A transport may use it to reject a mismatching response header early; UpdateChannel independently enforces it against the chunks actually delivered.

    Fetch

    type Fetch = async (String) -> Bytes raise UpdateError

    Fetches the whole body at a URL.

    The network is a parameter rather than a dependency so that the sequence below — verify, decode, compare, verify again — can be exercised end to end without one. That sequence is the part worth testing exhaustively; an HTTP client is not.

    UpdateError

    pub(all) suberror UpdateError {
    FetchFailed(url~ : String, detail~ : String)
    ManifestSignatureInvalid
    ManifestUndecodable(detail~ : String)
    ManifestStale(published_at~ : String, oldest_accepted~ : String)
    MalformedSignature(url~ : String)
    UntrustedKey(detail~ : String)
    ArtifactSizeMismatch(expected~ : Int64, actual~ : Int64)
    ArtifactDigestMismatch(expected~ : String, actual~ : String)
    ArtifactSignatureInvalid
    ClockUnusable(detail~ : String)
    InstallBusy
    RollbackRejected(target~ : UInt64, installed~ : UInt64)
    RevisionMismatch(detail~ : String)
    InstallInterrupted(detail~ : String)
    InstallFailed(step~ : String, detail~ : String)
    } derive(Eq,
    Debug
    )

    A reason an update was not accepted.

    Every variant is a refusal. There is no variant meaning "checked and could not tell": a check that cannot reach a conclusion is a check that failed, and the caller must treat it as such.

    UpdateError::equal

    fn UpdateError::equal(UpdateError, UpdateError) -> Bool

    UpdateError::message

    fn UpdateError::message(self : UpdateError) -> String

    UpdateError::not_equal

    fn UpdateError::not_equal(x : UpdateError, y : UpdateError) -> Bool

    UpdateError::output

    fn UpdateError::output(self : UpdateError, logger : &Logger) -> Unit

    UpdateError::to_string

    fn UpdateError::to_string(self : UpdateError) -> String

    AvailableUpdate

    pub struct AvailableUpdate {
    version :
    Version

    revision : UInt64
    notes_url : String?
    url : String
    size : Int64
    sha256_hex : String
    signature_hex : String
    } derive(Eq,
    Debug
    )

    A release that passed every check short of being downloaded.

    AvailableUpdate::equal

    AvailableUpdate::not_equal

    fn AvailableUpdate::not_equal(x : AvailableUpdate, y : AvailableUpdate) -> Bool

    AvailableUpdate::notes_url

    fn AvailableUpdate::notes_url(self : AvailableUpdate) -> String?

    AvailableUpdate::revision

    fn AvailableUpdate::revision(self : AvailableUpdate) -> UInt64

    AvailableUpdate::size

    fn AvailableUpdate::size(self : AvailableUpdate) -> Int64

    AvailableUpdate::url

    fn AvailableUpdate::url(self : AvailableUpdate) -> String

    CheckOutcome

    pub enum CheckOutcome {
    UpToDate
    Available(AvailableUpdate)
    } derive(Eq,
    Debug
    )

    What a check concluded.

    CheckOutcome::equal

    CheckOutcome::not_equal

    fn CheckOutcome::not_equal(x : CheckOutcome, y : CheckOutcome) -> Bool

    InstallOutcome

    pub enum InstallOutcome {
    Installed
    AlreadyInstalled
    } derive(Eq,
    Debug
    )

    InstallOutcome::equal

    InstallOutcome::not_equal

    fn InstallOutcome::not_equal(x : InstallOutcome, y : InstallOutcome) -> Bool

    UpdateChannel

    pub struct UpdateChannel {
    endpoint : String
    trusted_keys : Array[
    PublicKey
    ]
    platform : String
    current_revision : UInt64
    }

    An update channel, resolved from configuration.

    UpdateChannel::UpdateChannel

    fn UpdateChannel::UpdateChannel(endpoint : String, public_keys : Array[String], platform : String, current_revision : UInt64) -> UpdateChannel raise UpdateError

    Resolves a channel from its configured form.

    Trusted keys are parsed once, here, so that a key which cannot be used is reported when the channel is built rather than at the moment a signature needs checking.

    UpdateChannel::check

    async fn UpdateChannel::check(self : UpdateChannel, fetch : async (String) -> Bytes raise UpdateError, oldest_accepted :
    Timestamp
    ) -> CheckOutcome raise UpdateError

    Checks the channel for a newer release.

    oldest_accepted is the earliest publication instant this client will believe. It is supplied rather than derived from a clock inside this function: the calendar arithmetic that turns a freshness window into an instant belongs where a date library is available, and keeping it out means the whole check can be tested by naming the instant directly.

    The order of the steps is the point. The signature is checked before the document is parsed, so no attacker-supplied structure is interpreted until it is known to come from a trusted key. Freshness and revision are checked after, because both read fields that only the signature makes trustworthy.

    UpdateChannel::download_and_install

    async fn UpdateChannel::download_and_install(self : UpdateChannel, fetch : async (String, Int64, async (Bytes) -> Unit raise UpdateError) -> Unit raise UpdateError, update : AvailableUpdate, on_progress? : async (Int64) -> Unit noraise) -> InstallOutcome raise UpdateError

    Streams, authenticates and prepares or applies an available update.

    The native stage owns a private artifact from the first chunk until the platform consumes it. download_into authenticates the same chunks written there, and any transfer or authentication failure discards the stage before returning.

    macOS and Linux stage beside the running artifact so final replacement stays on one filesystem. Windows stages in the per-user temporary directory, keeps the verified installer open against modification, and elevates it only when the application requests relaunch. No staging path crosses the FFI boundary.

    The application is not restarted. When to do that is a question about the user's unsaved work, not about the update, so it belongs to the caller — see relaunch.

    cleanup_previous

    fn cleanup_previous() -> Unit raise UpdateError

    Removes the retained backup from a successfully relaunched application.

    current_revision

    fn current_revision() -> UInt64 raise UpdateError

    Returns the revision recorded by the running application artifact.

    http_fetch

    async fn http_fetch(url : String) -> Bytes raise UpdateError

    Fetches update metadata over HTTPS.

    Plain HTTP is refused. The signature is what makes a response trustworthy, so TLS is not what protects this exchange — but there is no reason to advertise which release a user is running to anyone on the path, and no reason to accept a redirect into cleartext.

    http_fetch_artifact

    async fn http_fetch_artifact(url : String, expected_size : Int64, consume : async (Bytes) -> Unit raise UpdateError) -> Unit raise UpdateError

    Streams an artifact over HTTPS without retaining its body.

    expected_size comes from the authenticated manifest. A Content-Length mismatch can therefore be refused before reading the body, but the consumer still counts actual chunks because the header may be absent after content decoding or on a chunked response.

    maximum_metadata_bytes

    let maximum_metadata_bytes : Int

    The largest body this client will accept from an update endpoint.

    A manifest and a signature are both small. A server that offers something enormous in their place is either broken or hostile, and either way the client should stop rather than fill memory on the way to a signature check it was going to fail.

    oldest_accepted

    fn oldest_accepted(freshness_days : Int) ->
    Timestamp
    raise UpdateError

    The earliest publication instant this client will believe, from the system clock.

    Kept out of UpdateChannel::check on purpose. That function is the one sequence where the order of the steps is the security property, and a clock read inside it would make the whole sequence depend on when the test ran. Here the clock is read once and the result is data.

    relaunch

    fn relaunch() -> Unit raise UpdateError

    Starts the installed replacement.

    The caller is expected to exit once this returns. Two copies of the same application running against the same state is a worse outcome than a moment with none, and the new process is already starting by the time this returns.