Changes touching this path

  • publish-r2: mirror release artifacts to Cloudflare R2 for anonymous download (#807) The repo is private permanently (ADR 0045), so Release assets 404 for an unauthenticated fetch — which is what broke `curl | sh`. This adds the anonymous path that replaces them: every asset the Release publishes is mirrored to dl.millerbyte.com/<tag>/, served from R2 with no credentials. The bucket, its scoped credential and the dl.millerbyte.com custom domain are provisioned by scripts/setup-artifacts.js (scripts @ 736f6e8), and the anonymous fetch is already proven live against that bucket. A *publish* job, not post-announce, and the ordering is load-bearing: publish jobs run after `host` uploads the Release and `announce` waits for them, so R2 is populated before the post-announce smoke test. Listed post-announce instead, #808's `curl | sh` gate would race the very artifacts it fetches. The job body is hand-owned in its own file so `dist generate` cannot clobber it — the same shape as smoke-install.yml (#258). It carries a workflow_dispatch tag input so it can be exercised against an existing release rather than first running during a real one, verifies every mirrored asset anonymously against the Release's own sha256, and warns (not fails) if the edge is not caching. ⚠ dist-workspace.toml also documents that it must be edited with `dist generate`, never `dist init` — init rewrites the config from its own defaults and strips every comment, which today destroyed the #270 native-ARM64-runner rationale and the whole ADR 0045 attestations block before they were restored. 5b710e89 · dbf3dbe6…
  • installer: serve the R2 base first, and turn the curl | sh gate back on (#808) The hero one-liner has been returning 404 in production. The site's /install.sh proxy fetched the installer from GitHub anonymously, and the repo is private permanently (ADR 0045), so there was nothing to fetch. #807 put the artifacts on R2; this points the installer at them. The proxy now reads upstream from dl.millerbyte.com and rewrites the generated ARTIFACT_DOWNLOAD_URLS default to list the R2 base FIRST and GitHub second. dist's installers already try each base in order, so the fallback costs nothing and keeps working for anyone with credentials. ⭐ The rewrite derives the version from the line it is rewriting. The anchor already contains the release tag, so capturing it means the site needs no version knowledge, no 'latest' pointer to resolve and no credential — a test moves the tag and the R2 base follows. ⚠ The two installers genuinely differ and one regex would have silently matched neither: sh builds a space-separated string, ps1 builds an array. The ps1 anchor is pinned to the ASSIGNMENT rather than the URL, because the same URL also appears in that script's help text — a loose match would rewrite the comment, leave the real download list GitHub-only, and still pass every check that greps for the R2 base. The rewrite THROWS if the anchor is absent. A silent no-op is the current bug wearing a different hat: it would ship an installer pointing only at URLs that 404 for exactly the audience the one-liner exists for, while the route returns 200 and the bytes look like a real script. Nine tests cover both installers, both drift cases, and the help-text trap; the anchors were also validated against the real 54 KB v0.4.2 scripts. publish-r2 now also writes unversioned installer copies to /installers/. That is the one deliberately mutable object in the bucket — every v{VERSION}/ prefix stays immutable and year-cached, but 'latest' has to move or the one-liner pins itself to whichever release shipped first. It carries max-age=300, matching the proxy. smoke-install.yml loses its `if: false` and now defaults to the hero path rather than a GitHub-direct URL. Testing a URL no user is told to run proved nothing; this exercises the whole chain — proxy, rewrite, R2. The skip comment is kept as history, because why it was disabled is why the gate is worth having. 6bf1df11 · dbf3dbe6…diff

Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.