refactor(tooling): T-1286 — generate, pr and dev become reach domains

Twelve scripts retired, three domains registered. `reach` now covers nine.

generate: `generate-brands` and `generate-corporations` were the second and
third copies of the same 24-line build-if-missing-then-exec bash `tooling/atlas`
carried, so they collapsed into `core.process.cargo_binary` rather than being
ported. `import_economics` shelled out to the first of those, so it now calls
that helper — `generated_brands.toml` comes back byte-identical, and the stamp
registry swaps the retired wrapper for `core/process.py`.

pr: `watchlist-diff` derives its watched set from `generator_sources.py` instead
of restating it, so it cannot drift from the stamp check.

dev: the environment scripts split decision from performing, per D-263's
guarded-exec rule. `godot_plan()` and `worktree_plan()` decide what would
happen; `install_godot()`, `install_rust()` and `setup_worktree()` do it.
`tooling/test_environment.py` pins the version pin, both override precedences,
the already-current skip, the platform refusal and both worktree refusals —
none of them performed. `make setup` now installs reach first, since the
targets that install rust and godot are reach verbs.

Two live bugs found while porting:

- The clerk read its decision index from `decisions/README.md`, a path that
  stopped existing when the DQR tree moved to `governance/`. Every clerk agent
  has been grepping blind; its prompt pointed at the same dead directory.
- The conformance exec-check matched any `x.system()` regardless of receiver,
  so `platform.system()` read as `os.system()`. Narrowed and re-proved against
  a real mutant.

`process.run` gains `input=`, `timeout=` and a `ProcessTimeout` subclass so a
killed run stays distinguishable from a verdict. The pre-push hook no longer
merges the clerk's stderr into its stdout — under streaming the last merged
line is a JSONL event, which would read as an unrecognised verdict and block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 17:00:46 +02:00
co-authored by Claude Opus 5
parent a45415b23a
commit 338644b409
41 changed files with 1183 additions and 615 deletions
+25 -20
View File
@@ -1,13 +1,18 @@
"""Brand layer (D-189, #827): generate_brands shell-out, TOML import, validation."""
import sqlite3
import subprocess
import sys
import tomllib
from pathlib import Path
from .errors import ImportAborted
from .paths import BRANDS_TOML, GENERATE_BRANDS_WRAPPER, GENERATED_BRANDS_TOML, REPO_ROOT
from .paths import BRANDS_TOML, GENERATED_BRANDS_TOML, REPO_ROOT
# economy-db/ is hyphenated, so it is not importable as a package and cannot
# reach `tooling.core` by normal import. The bootstrap goes away with T-1272
# (the hyphen sweep); until then it is explicit rather than implied.
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
VALID_BRAND_CATEGORIES: set[str] = {
"terroir", "heritage_craft", "tech_premium", "cultural",
@@ -28,28 +33,28 @@ def regenerate_brands() -> None:
This replaces the former split (tooling/generate-brands run separately by
make regen-db) with a single, coherent brand pipeline owned by one stamp.
The wrapper script builds the binary on demand and runs it with the default
canonical seed=1; callers that need non-canonical seeds must still invoke
the wrapper directly (experimentation only — committed output must be seed=1).
The binary is built on demand and run with the default canonical seed=1;
callers that need non-canonical seeds invoke it directly (experimentation
only — committed output must be seed=1).
This used to shell out to a `tooling/generate-brands` bash wrapper. The
wrapper was one of three identical copies of build-if-missing-then-exec, so
it was retired into `core.process.cargo_binary` (T-1286) and this calls that
helper instead. Same binary, same seed, same output.
"""
if not GENERATE_BRANDS_WRAPPER.exists():
raise FileNotFoundError(
f"generate_brands wrapper not found at {GENERATE_BRANDS_WRAPPER}"
)
from tooling.core.errors import ReachError
from tooling.core.process import cargo_binary
print(" [pre/10] Running generate_brands (Rust) to refresh generated_brands.toml...")
result = subprocess.run(
[str(GENERATE_BRANDS_WRAPPER)],
cwd=str(REPO_ROOT),
capture_output=True,
text=True,
)
if result.returncode != 0:
print(result.stdout, file=sys.stderr)
print(result.stderr, file=sys.stderr)
raise ImportAborted()
try:
stdout = cargo_binary("generate_brands")
except ReachError as exc:
print(str(exc), file=sys.stderr)
raise ImportAborted() from exc
# Print the Rust binary's own summary lines (brands generated, coverage).
# Indent so they fold under the pre-step heading.
for line in result.stdout.splitlines():
for line in stdout.splitlines():
if line.strip():
print(f" {line}")