Skip to content

fix(toolchain): sideloaded installs fail because the latest release carries no digest manifest #195

Description

@rmyndharis

Any build of main installed by a route other than Play cannot install a
toolchain. It fails before a single byte of the payload moves, and the log line
is the one written for a tampered download.

Why

ToolchainManager.downloadViaHttp() calls publishedDigestFor() before the
transfer (ToolchainManager.kt:699). That builds the manifest URL beside the
ZIP (manifestUrlFor, :1577, with MANIFEST_NAME = "toolchains.sha256" at
:1548) and, when the manifest does not name the ZIP, throws rather than
falling back (:922-925). Failing closed there is deliberate and correct.

releases/latest currently resolves to v1.0.0, and that release predates the
digest manifest: its assets are the APK, checksums.sha256 and the three
toolchain ZIPs, with no toolchains.sha256. Measured:

releases/latest/download/toolchain_go.zip      -> 200
releases/latest/download/toolchains.sha256     -> 404

The guarantee written at ToolchainManager.kt:907-909, that a release cannot
reach this state unnoticed, holds for releases the current release.yml
produces: release.yml requires toolchain-zips/toolchains.sha256 among its
artifacts and fails if a packaged ZIP is absent from it. What it cannot cover is
which release the latest pointer names, and that pointer can name a release
built before the requirement existed.

Who is affected

Only builds newer than the commit that introduced the check, which is not an
ancestor of v1.0.0. Installed v1.0.0 apps never request the manifest, so they
are unaffected and do install toolchains today.

That leaves developers running a debug APK or a sideloaded build of main,
which CONTRIBUTING.md treats as the normal way to work on this project.

Resolution

Publishing 1.1.0 fixes it: that release carries toolchains.sha256 and takes
the latest pointer. Deferred until then rather than editing a historical
release.

Worth considering separately, because the same pointer is read twice with a
gap: manifestUrlFor's own docstring (:1570-1575) notes that the manifest and
the payload are fetched through releases/latest/download/ at different
moments, so a release published in between yields a digest and a payload from
two different releases. It asks for the redirect chain to be measured against a
real release before anything is built on it. That measurement now exists:

releases/latest                     -> 302 -> releases/tag/<tag>
releases/latest/download/<asset>    -> 302 -> releases/download/<tag>/<asset>

both with cache-control: no-cache, and with per-asset-path edge caching that
can serve a stale mapping for a path recently requested. Resolving latest to a
concrete tag once and building both URLs from it is therefore implementable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions