tomato·bazeldocs v0 · latest
Docs/Reference/Modules/gate

gate

Registry admission gating for tomato-bazel: prove invariants about a module from its graph before it enters the registry.

Latest0.0.1
Versions1
CategoryModules & tooling
MaintainersMatt Marshall
Registryhttps://registry.tbzl.dev/modules/gate/
Sourcegithub.com/tomato-bazel/gate
MODULE.bazelstarlark
bazel_dep(name = "gate", version = "0.0.1")

View source & releases on GitHub ↗

Registry admission gating for tomato-bazel: prove invariants about a module from its graph before it is allowed into the registry.

Status: v0.0.1 — M1 done, M2 started

bzlmod (MODULE.bazel evaluation, module graph, MVS) is complete and verified against Bazel’s own resolution. Four gates run as Bazel targets: bazel test //gates/....

Why

bazel-registry publishes 74 modules / 314 versions and nothing validates an entry. rels release computes the SRI itself and it is never re-verified; there is no PR-triggered workflow in the registry at all. The cost shows up as reactive yankingrules_cc_host 0.1.0 (“cannot work as a bazel_dep”), rules_lang 0.4.2 (“breaks consumer package loading”). Both are failures a graph invariant catches at publish time.

Meanwhile the MODULE.bazel files in rules_k8s and rules_lean already carry hand-written proofs of exactly these invariants, in prose. This repo makes them machine-checked.

Design

Rust does extraction only. Every gate is a Bazel target.

  MODULE.bazel + *.bzl + source.json + tarballs

       [Rust]  tbzl-gate project     → facts.ttl

               rdf_dataset(...)

      ┌────────────┴────────────┐
  sparql_query_test(*.rq)   rdf_validate_test(shapes)
  one target per invariant   the convention tier
      └────────────┬────────────┘

       [Rust]  tbzl-gate certify     → certificate.binpb (signed)

rules_rdf already provides the primitive: sparql_query_test is a zero-row gate — run a .rq and fail the build if the result set is non-empty. So a gate is one query file plus one target, the violating rows are the counterexample, and adding a gate needs no Rust rebuild.

Transitive closure comes from SPARQL property paths (gate:depends_on+, gate:loads*) rather than an iterated fixpoint — the capability transfer function is pure union along load edges, so cap(f) = ⋃ { own(g) : f loads* g } is a single query.

Crates

CratePurpose
bzlmodMODULE.bazel evaluation, the module graph, MVS, registry client
gate-rdfProjection to Turtle. Facts only — no policy
gate-reportGate results → tomato.gate.v1.ConformanceReport. Projects; decides nothing
gate-snapshotUpstream registry metadata snapshots, for --check drift

The report is a proto, not a rendered document: it crosses into tomato-bazel/docs, which builds docs.tbzl.dev/conformance/ from it with no Bazel and no JVM. Publishing //gates:report_bundle ships the message alongside its own FileDescriptorSet, so the consumer needs neither a copy of the schema nor a generated client.

bzlmod is also roma’s unstarted B1 (roma/docs/bzlmod-strategy.md: own parse + MVS + registry fetch). Building it here means the registry gets gating now and roma gets bzlmod resolution from the same implementation — and it retires the three divergent regex parsers of MODULE.bazel currently in bazel-registry/tools/graph/graph.py, modgraph-operator, and tbzl.

Reconnaissance

cargo run -p bzlmod --example survey reports what the S/C/D/V gates would find across the registry today. It reports; it does not judge. Current output over the latest version of each of the 74 modules:

GateFinding
S45 local_path_override in published modules
C110 of 30 external atoms pinned at more than one version
D232 of 74 modules register toolchains that propagate to every consumer
D334 repo names chosen on a shared extension without a module namespace
V1dropped — compatibility_level is a no-op as of Bazel 8.6.0 / 9.1.0

The gates themselves run over the projection (bazel run //gates:report_<gate>), and currently find: S4 5, D2 58 registrations across 31 modules, D3 35, C1 26 version pairs over 19 contested atoms, S6 1 — 125 findings across 5 invariants, none of them clean, over 76 modules of which 39 have at least one finding.

Those numbers are published, not just printed. bazel build //gates:report projects them into a tomato.gate.v1.ConformanceReport, and docs.tbzl.dev/conformance/ renders it — the scoreboard, a page per invariant with its rationale taken from the .rq header, the third-party atom frontier, and each module’s findings on that module’s own reference page.

compatibility_level deserves a note, because it changes what C1 is for. Bazel made it and max_compatibility_level no-ops, so there is no longer any in-band way for a module to signal a breaking change: MVS takes max-of-minimums, and if A needs x 1.0.0 while B needs x 2.0.0, A silently gets 2.0.0. Bazel used to reject that and cannot now. One live version per atom is therefore the compatibility mechanism, not merely a tidiness goal — it is the only thing standing between a consumer and a silent breaking upgrade. The registry has 9 such silent upgrades today, including botnoc declaring rules_java 8.15.2 and receiving 9.1.0.

D3 is the one worth reading closely, because the naive form of it is wrong. Apparent repo names are module-scoped, so @crates in two unrelated modules is not automatically a conflict. The hazard is narrower: a name the module chose via a tag (oci.pull(name = ...)) on an extension defined in another module, since extension instances — and the repos they generate — are shared across the graph. That is the bug rules_k8s hit on its first consumer, and the fix it shipped (prefix with the module name) is what this checks. Singletons the extension emits for everyone, like rust_toolchains, are correctly ignored.

Install

.bazelrc:

common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/

MODULE.bazel:

bazel_dep(name = "gate", version = "0.0.1")

Conformance#

No gate findings. 8 contested atoms. See how gating works or the full report.

Contested atoms

Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.

AtomResolved hereElsewhere
apple_support 1.24.2 2.2.0 ×1
bazel_skylib 1.8.2 1.9.0 ×2
nlohmann_json 3.6.1 3.12.0.bcr.1 ×1
protobuf 33.4 34.0.bcr.1 ×2
rules_jvm_external 6.7 6.8 ×4
rules_python 1.7.0 2.0.1 ×1
rules_swift 3.1.2 3.6.1 ×1
upb 0.0.0-20220923-a547704 0.0.0-20230516-61a97ef ×1

Dependencies#

gate in the registry graph — what it depends on (left) and what depends on it (right).

Depends on

platforms1.0.0bazel_skylib1.8.2rules_rust0.70.0rules_rdf0.3.0rules_jena0.3.2rules_tomato0.1.2brand0.3.1devrules_shell0.6.1devstardoc0.7.2dev

Versions#

1 published version, newest first. Each resolves to an immutable, integrity-checked archive.

VersionIntegrity (sha256)Source archive
0.0.1 latest A05NaZjwvvo7dETw… tag archive ↗

Changelog#

All notable changes to gate. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.

Unreleased

Added

  • bzlmod crate: MODULE.bazel evaluation over starlark-rust in a restricted dialect (no def, no load), replacing regex scraping. Captures the directives the gates need: dev_dependency, register_toolchains, use_repo, every *_override, and compatibility_level.

  • bzlmod::Version: bzlmod version ordering per upstream Version.java, including the digit-vs-alphanumeric prerelease precedence that both of rels’ hand-rolled version_cmp functions get wrong.

  • Corpus test over all 314 registry entries.

  • --example survey: reconnaissance over the registry for the S/C/D/V gates.

  • bzlmod::registry: BCR-layout registry reader (FsRegistry), source.json / metadata.json models including yanked_versions.

  • bzlmod::graph: the module graph and minimal version selection. Discovery and selection iterate to a fixed point, because bumping a module to a higher version means reading that version’s MODULE.bazel, which may declare deps the lower version never had. dev_dependency edges are followed only from the root — the mechanism that makes a dev dep invisible to consumers, and the basis of D1.

  • ModuleGraph::downgraded_requirements: modules that asked for one version and silently got a higher one. Since compatibility_level became a no-op in Bazel 8.6/9.1 this is the only remaining signal for that class of break.

  • --example selections: prints MVS results as JSON for diffing against Bazel’s own answer. Verified against rules_k8s, rules_lean and rules_tla lockfiles: zero mismatches on every module the registry can resolve.

  • Bazel build: bazel test //... alongside cargo test, over @gate_crates.

  • gate-rdf: projects the registry to Turtle. Facts only, no policy.

  • //gates: the first four invariants as sparql_query_test targets — S4 (no override in a published module), D2 (undeclared toolchain leakage), D3 (unnamespaced repo on a shared extension), C1 (one live version per atom). Each is wired both against a clean fixture (must return zero rows) and a known-violation fixture diffed against a committed golden, so a malformed query cannot masquerade as a passing gate.

  • gate-report + //gates:report: the conformance report — a scoreboard, the findings per invariant, the findings per module, and the third-party atom frontier with BOM conformance. Regenerated from the projection on every build. Gate rationale is EXTRACTED from each .rq file’s header comment rather than written separately, so documentation cannot drift from the gate it documents. Findings are attributed to a module only when the query exposes a ?module variable; registry-level invariants like C1 stay on the report as a whole rather than being blamed on an arbitrary module.

  • //proto/tomato/gate/v1:conformance_proto — the report’s wire contract, and the new shape of //gates:report. It emits a single tomato.gate.v1.ConformanceReport (report.binpb) plus its own FileDescriptorSet (conformance-descriptor.binpb), so a consumer can decode it without a copy of the schema. //gates:report_bundle is the pair.

    This REPLACES the mdBook source tree the report used to be. Rendering moved to tomato-bazel/docs, which publishes docs.tbzl.dev/conformance/ — one site, one theme, one search index, and a module’s findings sitting on the same page as its bazel_dep line instead of in a parallel book. The report is now data; the editorial framing lives where editorial things belong.

    Provenance (generated_at, generated_from) arrives via string_flag, so a local bazel build //gates:report is byte-reproducible and an unstamped report is visibly unstamped rather than quietly claiming to be current.

    The prost toolchain (//proto:prost_toolchain) is tonic-free — the default one wires tonic_runtime into every rust_prost_library, and nothing here speaks gRPC — and is registered dev_dependency = True, because a non-dev register_toolchains is exactly the D2 leak this repo detects.

0.0.1 — scaffold

← All modules