Bazel External Fetch Authority

Bazel External Fetch Authority

GloriousFlywheel currently proves shared Bazel remote-cache acceleration for action outputs. That is not the same thing as external repository/archive fetch authority.

Bazel can need external archives during repository resolution and analysis before a target can benefit from action-cache hits. If those downloads hit upstream release hosts directly, a transient upstream 502 can fail a cache-backed proof before the build reaches the shared remote cache.

Current Truth

The source repo has retry mitigation:

  • .bazelrc sets --experimental_repository_downloader_retries=5
  • .bazelrc sets --http_timeout_scaling=2.0
  • scripts/bazel-cache-backed.sh passes --remote_cache explicitly after the strict cache contract check

That means ordinary wrapper use is upstream-with-retries for external repository archives unless an operator also configures a repository cache or distdir. Source Bazel Proof now stages the Linux x64 Node 22.13.1 toolchain archive plus the critical Bzlmod archives that recently blocked GF proof/TTFCH analysis into an ephemeral verified distdir before Bazel starts, so those repository fetch paths are no longer raw upstream fetches during Bazel analysis. A checked coverage contract accounts for that required set and the remaining generated Node/hermetic launcher candidates that remain deliberately deferred. This is still not durable mirror authority.

Supported Wrapper Inputs

The repo-managed Bazel wrapper honors these optional inputs:

  • BAZEL_REPOSITORY_CACHE: passed as --repository_cache=<value>
  • BAZEL_DISTDIR: colon-separated paths passed as repeated --distdir=<value>

These settings are intentionally separate from BAZEL_REMOTE_CACHE. BAZEL_REMOTE_CACHE covers action outputs. BAZEL_REPOSITORY_CACHE and BAZEL_DISTDIR cover external repository/archive fetches.

The copied consumer wrapper in examples/bazel/gloriousflywheel-bazel.sh honors the same repository-cache and distdir inputs. It also accepts GF_BAZEL_INJECT_REPOSITORIES, a colon-separated list of repo=/absolute/path entries passed as repeated --inject_repository=<repo=/absolute/path> flags. That path is for generated local Bazel repositories whose contents are already materialized, verified, and present on the runner before Bazel starts.

For public pinned source inputs such as WAS-110 community archives, the intended flow is:

  1. mirror the public archives into an approved durable artifact store
  2. materialize the generated vendor repository offline from that mirror
  3. verify the generated repository and manifest
  4. inject it into the consuming workspace with GF_BAZEL_INJECT_REPOSITORIES=was110_vendor_blobs=/absolute/vendor/repo

This keeps mutable upstream URLs out of the build-time path without claiming that GloriousFlywheel owns the consumer repo’s pin files or release policy.

The current honey Attic stack declares was110-public-inputs as the dedicated RustFS bucket for that public WAS-110 mirror. Keep it separate from attic, bazel-cache, and tofu-state: those buckets are cache/state authority surfaces, not pinned-source mirror publication paths.

The live bucket contains these root objects:

object size sha256
WAS-110_8311_firmware_mod_2.4.0_bfw.7z 16637511 b0b03bc28c540239beffb3a9e64f653c4a403d2e7e97a291b2ed785a05079031
WAS-110_8311_firmware_mod_v2.8.3_basic.7z 10435192 5d72e7552e396127b59a548c6163113d088d545708aecb0fed38097b862288f8
public-source-lock.json 4058 source lock copied from 8311-was-110-firmware-builder/pins/public-source-lock.json
SHA256SUMS 213 sidecar for the two archive objects

The 2026-05-06 proof downloaded the two archive objects from was110-public-inputs through a bounded direct RustFS pod port-forward, validated SHA256SUMS, materialized a temporary WAS-110 vendor repository with pins/fetch-public-inputs.sh --offline --archive-dir <downloaded-mirror>, and then passed pins/verify-vendor-repo.sh against that generated repo.

Audit Command

Run:

just bazel-external-fetch-authority

Strict mode fails until a repository cache or distdir authority is configured:

just bazel-external-fetch-authority --strict

When a proof needs to show pre-Bazel staging rather than only an environment variable, require a materialization manifest:

just bazel-external-fetch-authority --strict --require-distdir-manifest

The Source Bazel Proof workflow runs the strict audit with --require-distdir-manifest after validating the materialization manifest against docs/contracts/bazel-distdir-source-proof-coverage.json.

The offline fixture proof is:

just bazel-external-fetch-authority-self-test

It verifies the status classifier for missing .bazelrc, upstream retry mitigation, .bazelrc repository-cache/distdir authority, wrapper environment inputs, strict-mode failure without fetch authority, and the exact scripts/bazel-cache-backed.sh CLI flags for BAZEL_REPOSITORY_CACHE plus a colon-separated BAZEL_DISTDIR.

The lockfile input inventory is:

just bazel-external-input-manifest

It parses MODULE.bazel.lock and reports Bzlmod registry files, generated repository archive inputs, and generated toolchain template URLs. The current repo manifest is intentionally not an offline-authority claim: the BCR registry files and http_archive inputs are hash-recorded in the lockfile, but the generated Node.js toolchain repositories expose version/template URLs without a lockfile hash in MODULE.bazel.lock. That means the source-repo proof still needs a repository-cache, distdir, or approved mirror policy before we call external input resolution durable.

The repo-owned candidate integrity contract is docs/contracts/bazel-external-input-mirror-candidates.json. It records the eight generated Node.js 22.13.1 toolchain archives selected by rules_nodejs, including concrete archive filenames and sha256 values cross-checked against Node’s upstream SHASUMS256.txt. The manifest guard reports those as candidate hashes, not lockfile hashes. This is a useful source-integrity step toward a distdir or approved mirror, but the contract is explicitly candidate-integrity-only: all entries have materialized: false, so it is not proof that a durable mirror, distdir, or repository cache is populated.

The repo-owned distdir materializer is:

just bazel-external-input-distdir --output /tmp/gf-bazel-distdir --name nodejs_linux_amd64:22.13.1:linux_amd64

It downloads the selected candidate archive, verifies the recorded sha256, writes SHA256SUMS, and writes bazel-distdir-manifest.json into the output directory. Source Bazel Proof uses this for the Linux x64 Node toolchain and critical Bzlmod archive paths before Bazel starts, validates the runtime materialization manifest with:

just bazel-distdir-coverage \
  --materialization-manifest /tmp/gf-bazel-distdir/bazel-distdir-manifest.json \
  --exact-materialization

It then exports BAZEL_DISTDIR and runs just bazel-external-fetch-authority --strict --require-distdir-manifest through the workflow command path. That proves pre-Bazel local distdir staging for the source proof’s selected Node archive. It still does not prove durable mirror retention or broad external-input authority for every generated platform archive.

The coverage contract is docs/contracts/bazel-distdir-source-proof-coverage.json. It must classify every Node candidate from docs/contracts/bazel-external-input-mirror-candidates.json as either required_inputs or deferred_inputs, and its authority class remains source-proof-ephemeral-only. The contract check is:

just bazel-distdir-coverage-contract-check

The provider-neutral mirror package and restore primitives are:

just bazel-distdir-mirror-package \
  --distdir-manifest /tmp/gf-bazel-distdir/bazel-distdir-manifest.json \
  --mirror-root /tmp/gf-distdir-mirror

just bazel-distdir-mirror-verify \
  --mirror-root /tmp/gf-distdir-mirror \
  --required-input nodejs_linux_amd64:22.13.1:linux_amd64

just bazel-distdir-mirror-restore \
  --mirror-root /tmp/gf-distdir-mirror \
  --output /tmp/gf-restored-distdir \
  --required-input nodejs_linux_amd64:22.13.1:linux_amd64

restore reconstructs a local Bazel --distdir directory from the verified mirror package and writes a normal bazel-distdir-manifest.json, so the same coverage validator can prove the restored bytes satisfy the current source proof input set. This is still a local restore mechanics proof, not live durable storage authority, not consumer exposure, and not broad/default RBE.

It writes MANIFEST.json, distdir/<sha256> object files, distdir-meta/<sha256>.json provenance sidecars, and bazel-distdir/<filename> aliases that can become a local BAZEL_DISTDIR after sync/restore. This validates the package shape and bytes. It still does not prove the package has been synced to durable storage, restored by CI, or exposed to consumers through a read-only credential.

The non-secret live authority package gate is:

just bazel-external-input-authority-package-gate --package <package.json>

That package must name a dedicated external-input mirror endpoint, bucket, mirror_prefix, network audience, scoped GF_EXTERNAL_INPUT_MIRROR_* credential injection, mirror layout, restore proof, read-only consumer exposure policy, retention, maintenance/failure-domain behavior, quota policy, observability, and authority separation. The gate rejects Civo, RustFS, current Attic/cache/state buckets, protected prefixes, inline secrets, and template placeholders. It also requires the durable authority contract to remain no-live-durable-authority until the live proof updates covered_inputs.

The endpoint field is carried by GF_EXTERNAL_INPUT_MIRROR_ENDPOINT; region and credentials use the matching GF_EXTERNAL_INPUT_MIRROR_REGION, GF_EXTERNAL_INPUT_MIRROR_ACCESS_KEY_ID, and GF_EXTERNAL_INPUT_MIRROR_SECRET_ACCESS_KEY names.

After a package exists and the scoped credentials are installed, the live proof harness is:

just bazel-distdir-mirror-live-readiness \
  --package <package.json>

just bazel-distdir-mirror-live-proof \
  --package <package.json> \
  --mirror-root <verified-mirror-root> \
  --all-candidates

The readiness guard fails closed before the expensive proof if the non-secret package is missing, has not reached posture=proof_ready, or the scoped GF_EXTERNAL_INPUT_MIRROR_* environment is absent or mismatched. It writes redacted bazel-distdir-mirror-live-readiness-evidence.json so operators can separate missing authority setup from runner or Bazel failures. The proof harness then uploads the verified package under mirror_prefix, downloads it back to a fresh mirror root, verifies the package again, restores a local BAZEL_DISTDIR, and writes bazel-distdir-mirror-live-proof-evidence.json. The manual Bazel Distdir Mirror Live Proof workflow first reruns the full package proof on tinyland-nix-heavy, runs readiness, then runs this harness. It does not run on pull requests and does not depend on GitHub artifact upload quota.

Generate the intentionally non-live template with:

just bazel-external-input-authority-package-template /tmp/external-input-authority-package.json

The package, restore, and authority-package fixture proofs are:

just bazel-distdir-mirror-package-contract-check
just bazel-distdir-mirror-restore-contract-check
just bazel-external-input-authority-package-contract-check
just bazel-distdir-mirror-live-proof-contract-check

The durable authority gate is docs/contracts/bazel-external-input-durable-authority.json. It is currently no-live-durable-authority: the source-proof Linux x64 archive plus critical Bzlmod archives are listed as ephemeral source-proof inputs, all 23 candidate inputs remain pending for durable coverage, and covered_inputs is intentionally empty. The validator requires any future durable claim to provide auth, retention, restore, provenance, and consumer exposure evidence instead of treating the source-proof distdir as a product mirror.

Run:

just bazel-external-input-authority-contract-check

This is the promotion gate for a repository cache, durable distdir, or approved mirror. It is not satisfied by BAZEL_REMOTE_CACHE, RustFS cache/state buckets, future RBE CAS/action-cache, or private local-only generated repositories.

The consumer wrapper proof is:

just consumer-bazel-wrapper-contract-check

It verifies the copied wrapper passes --remote_cache, read-only upload mode, repository-cache, distdir, and injected-repository flags, and rejects relative or missing injected repository paths.

Boundary

This is not Bazel remote execution. It also does not make an upstream archive mirror public or durable by itself. A future managed authority still needs an explicit policy for repository-cache location, distdir population, retention, and consumer exposure.

Public pinned blobs may be staged into an approved mirror or future approved CAS path when their provenance and retention policy are recorded. Private or license-restricted blobs remain private/local-only and must not be promoted into public cache, public mirror, or shared CAS surfaces by this wrapper.

GloriousFlywheel