design 001 rejects Bazel as packaging, never evaluates it as a dependency graph nf-mm60 ← Beads

closed priority 2 task unassigned

Twinned from https://github.com/cfsh/ninefold/issues/458 by tools/beads/import_github.py's reconcile pass.

Design doc 001 rejects Bazel, but only ever evaluates it as a packaging tool. Build-system-as-dependency-graph — no packaging, py_test over source, the graph replacing presubmit.py's hand-written routing table — was never considered. This issue records the gap and the evaluation, so a later round starts from it rather than re-deriving it.

Raised by Aedan in chat, reading [#436](https://github.com/cfsh/ninefold/pull/436):

> "I don't understand why no build system tho? I guess some of our build system is the godot stuff. Using a build system would still allow presubmit to test the tools themselves, since the presubmit.py would invoke the build system"

The gap

§3.3 is titled *"Packaging and invocation"*; §4 option F is *"Python compiled to executables via Bazel (or PyInstaller/pex/shiv)"*. Both evaluate build-system-as-packaging, and F's central argument is staleness — *"every packaging option yields an artifact that goes stale until explicitly rebuilt, and #380 is the in-repo precedent"*.

That argument does not apply to the graph proposal at all. Nothing is packaged; py_test runs source; ./tools/pr/poll.py keeps working exactly as §5.2 specifies. So 001's strongest objection is answering a different question, and the doc should not be read as having settled this one.

Why it matters

presubmit.py hand-rolls the affected-target query a build graph gives for free — ROUTES, route(), and a bespoke glob→regex compiler (_compile(), ~35 lines, written because fnmatch's * matches /). §5.1 concedes the shape in its own warning:

> ⚠️ A path→suite map is a second place correctness lives, and it fails open.

The bigger prize is declared inputs being *checked* — a test that reads an undeclared file fails, so the map cannot silently rot the way #352's CHARTER.md-only diff did.

Evaluation (2026-08-03)

Three findings, and the first is load-bearing:

1. Bazel has no sandboxing on Windows. Per Bazel's own docs, Windows has no sandboxing support, so actions run under the local strategy and the sandboxed strategy silently falls back to non-sandboxed local execution; the only alternative is an experimental --experimental_use_windows_sandbox requiring an external sandboxfs binary.

Without enforcement, data = [...] is a hand-written list of inputs in a BUILD file — the same "second place correctness lives" problem, relocated, plus a build system to maintain. Checked inputs are the entire reason a graph beats a glob table, and that is exactly the part Windows does not get.

2. The routing cases that actually bite are cross-language, which is where the graph degrades to hand-declaration anyway. - PackPresetTests.cs is C#, but its inputs are a .py, a .json, a .cfg and ~390 .wav files. Graphing it means modelling Godot.NET.Sdk plus the machine-specific NuGet.Config that points at the Godot install's bundled SDK package cache. Realistically it becomes a genrule wrapping dotnet test — zero graph, precisely where the graph was wanted. - tools/reviewer/CHARTER.md is prose consumed by two hand-written regexes (dispatch.sh:151, poll.sh:284). - The probe sweep (godot --headless --import, ~24 launches, ~3m28s) is not modelable at all.

Net: the graph would cover the Python subset — one glob, tools/**/*.py → unittest — which is the easy half of the routing. The hard half stays hand-declared.

3. Pants is the better technical fit and is unavailable. It infers Python dependencies from import statements rather than requiring BUILD-file authorship, and --changed-since=origin/main is exactly this feature. But it requires Linux, macOS, or Windows 10 via WSL 2 — no native Windows.

The WSL 2 angle — the reason to keep this open

Aedan, same conversation:

> "potentially we could move to WSL2 if more issues like this compound"

This is the trigger worth watching, because WSL 2 moves two of the three findings at once: Bazel gets real sandboxing (so declared inputs become checked, restoring the actual benefit), and Pants becomes available (so dependency inference is on the table without BUILD-file authorship). Finding 2 — the C#/Godot side resisting the graph — survives the move unchanged.

WSL 2 is a much larger decision than this issue and should not be taken *for* it. But the tooling tier is accumulating Windows-specific findings that a move would erase wholesale, and they should be counted in one place rather than rediscovered per PR. Recorded so far:

If that list keeps growing, the WSL 2 question should be raised on its own terms — and this issue becomes one input to it rather than the decision.

Suggested disposition

Cheapest de-risk, available today and costing nothing: delete ROUTES and always run --all. Measured at ~6s (dotnet 1.6s · python 2.9s · shellcheck). §5.1 already pre-authorizes it — *"if the map ever costs more than it saves, delete it and always run --all"*. presubmit.py then becomes a suite runner plus a --doctor, with no graph left to be wrong, and the whole question defers cleanly.

Revisit a real build system when any of these fire: - --all gets slow enough that target selection genuinely matters. - The tooling tier takes third-party Python dependencies (001 §3.3 says reach for uv + PEP 723 first). - The repo moves to WSL 2 for unrelated reasons.

Not a proposal to adopt anything. Filed so 001's rejection is not later cited as having covered this.

Refs #436, #433 · design_docs/001-no-logic-in-shell.md §3.3, §4F, §5.1

Sources: [Bazel — Sandboxing](https://bazel.build/docs/sandboxing) · [Bazel — Windows sandboxing status](https://github.com/bazelbuild/bazel/discussions/18401) · [Pants — Prerequisites](https://www.pantsbuild.org/dev/docs/getting-started/prerequisites)

Dependencies

None.

Comments

No comments.

Add a comment