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.