proton_updater

    Update manifest schema shared by the Proton runtime and CLI.

    proton
    updater
    manifest
    Download zip
    Version
    0.2.5
    License
    Apache-2.0
    Last updated
    10 days ago
    Downloads
    7K

    #moonbit-community/proton_updater

    The update manifest schema, shared by the Proton runtime and proton_cli.

    The runtime reads manifests; the CLI writes them. Putting the schema in its own module keeps one definition of the format rather than two that drift, and lets the CLI depend on it without depending on the whole runtime.

    #Scope

    Decoding and ordering. This module has no network access, no filesystem access, and no cryptography — it does not verify signatures, only carries them. That separation is deliberate: decoding a manifest proves it is well formed and nothing more, and a type that could be mistaken for a trusted manifest is worse than one that obviously is not.

    The caller is responsible, in order, for: verifying the manifest signature, rejecting a revision that is not strictly newer than the installed one, and rejecting a manifest older than its freshness window.

    #Decoding

    let manifest = @updater.Manifest::parse(text)
    match manifest.platform("darwin-arm64") {
    Some(update) => download(update.url(), update.size())
    None => () // Nothing on offer for this platform.
    }

    schema_version must be exactly 2, kind must be "full", and unknown fields are errors. A release manifest is one strict protocol document: invalid or unsupported content fails loudly instead of being treated as no update.

    #Ordering

    revision is a positive unsigned 64-bit release sequence. It is the security ordering: every published update increments it, and it never resets when the display version changes. Keeping it separate lets applications use ordinary version labels without making rollback prevention depend on semantic-version policy.

    Version is exactly three non-negative integers, with no leading zeros and no pre-release or build suffix. It is display metadata, not the install ordering; the strict shape keeps manifests predictable while revision decides whether an artifact may replace the installed application.

    Timestamp accepts only YYYY-MM-DDTHH:MM:SSZ. Every field is fixed width in that form, so lexicographic order is chronological order and is_before needs no calendar arithmetic. Numeric offsets are refused because comparing them would require converting them, and a freshness check that silently mis-converts is a check that has quietly stopped working.

    #Encodings

    sha256 and signature are hexadecimal, lowercase, and strictly validated: one encoding across the manifest and the key format means one strict decoder rather than two that can disagree.

    Artifact URLs must be https. The signature is checked regardless, so this is not what makes an update safe; it removes an opportunity rather than a defence.

    ManifestError

    pub(all) suberror ManifestError {
    NotJson
    NotAnObject(path~ : String)
    UnknownSchemaVersion(found~ : Int)
    UnknownField(path~ : String)
    MissingField(path~ : String)
    InvalidField(path~ : String, expectation~ : String)
    } derive(Eq,
    Debug
    )

    An update manifest could not be decoded.

    Every variant names the field at fault, because a manifest is written by a release process and read by a client that cannot ask for a correction.

    ManifestError::equal

    ManifestError::message

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

    ManifestError::not_equal

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

    ManifestError::output

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

    ManifestError::to_string

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

    Manifest

    pub struct Manifest {
    version : Version
    revision : UInt64
    published_at : Timestamp
    notes_url : String?
    platforms : Map[String, PlatformUpdate]
    }

    A decoded update manifest.

    Decoding proves the document is well formed. It proves nothing about authenticity: the caller verifies the manifest signature before trusting any of this, and enforces revision monotonicity and freshness afterwards.

    Manifest::from_json

    fn Manifest::from_json(json : Json) -> Manifest raise ManifestError

    Decodes a manifest from an already parsed JSON value.

    Manifest::notes_url

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

    Manifest::parse

    fn Manifest::parse(text : String) -> Manifest raise ManifestError

    Decodes a manifest document.

    Manifest::platform

    fn Manifest::platform(self : Manifest, platform : String) -> PlatformUpdate?

    Returns the update offered for one platform identifier, if any.

    Manifest::platforms

    fn Manifest::platforms(self : Manifest) -> Array[String]

    Returns every platform identifier this manifest offers an update for.

    Manifest::published_at

    fn Manifest::published_at(self : Manifest) -> Timestamp

    Manifest::revision

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

    The signed, monotonically increasing release order.

    Unlike the display version, this value is compared at install time and never resets. It is what prevents an older signed release from replacing a newer installed application.

    Manifest::version

    fn Manifest::version(self : Manifest) -> Version

    PlatformUpdate

    pub struct PlatformUpdate {
    url : String
    size : Int64
    sha256_hex : String
    signature_hex : String
    } derive(Eq,
    Debug
    )

    One platform's full-artifact update.

    PlatformUpdate::equal

    PlatformUpdate::not_equal

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

    PlatformUpdate::sha256_hex

    fn PlatformUpdate::sha256_hex(self : PlatformUpdate) -> String

    PlatformUpdate::signature_hex

    fn PlatformUpdate::signature_hex(self : PlatformUpdate) -> String

    PlatformUpdate::size

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

    PlatformUpdate::url

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

    Timestamp

    pub struct Timestamp {
    text : String
    } derive(Eq,
    Debug
    )

    A publication instant, stored as the exact text it was written with.

    Only YYYY-MM-DDTHH:MM:SSZ is accepted: fixed width, UTC, no fractional seconds and no numeric offset. Every field is fixed width in that form, so lexicographic order is chronological order and comparison needs no calendar arithmetic at all.

    Accepting offsets would mean converting before comparing, and a freshness check that silently mis-converts is a check that quietly stops working.
    impl Show for Timestamp

    Timestamp::equal

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

    Timestamp::is_before

    fn Timestamp::is_before(self : Timestamp, other : Timestamp) -> Bool

    Returns whether this instant precedes other.

    Timestamp::not_equal

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

    Timestamp::output

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

    Timestamp::parse

    fn Timestamp::parse(text : String) -> Timestamp?

    Parses YYYY-MM-DDTHH:MM:SSZ, validating field ranges.

    Timestamp::to_string

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

    Timestamp::to_text

    fn Timestamp::to_text(self : Timestamp) -> String

    Returns the text this timestamp was parsed from.

    Version

    pub struct Version {
    major : Int
    minor : Int
    patch : Int
    } derive(Eq,
    Debug
    )

    A release version, as three non-negative integers.

    The updater offers an update only when the manifest version is strictly greater than the running one, so ordering is a security control rather than a display concern: a version that compares wrongly is a rollback.

    Exactly three components are required, and pre-release or build suffixes are rejected. Ordering suffixed versions correctly is subtle — 1.0.0-rc.1 precedes 1.0.0, and -rc.10 follows -rc.2 — and a rule that is subtle in a comparison this important is better left out than approximated.
    impl Show for Version

    Version::equal

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

    Version::is_newer_than

    fn Version::is_newer_than(self : Version, other : Version) -> Bool

    Returns whether this version is strictly newer than other.

    Version::not_equal

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

    Version::output

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

    Version::parse

    fn Version::parse(text : String) -> Version?

    Parses major.minor.patch.

    Each component must be decimal digits with no sign, no leading zero beyond the single digit 0, and no suffix. Leading zeros are refused so that one release cannot be written two ways.

    Version::to_repr

    Version::to_string

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

    supported_schema_version

    let supported_schema_version : Int

    The only manifest schema this client understands.