probe: discover environments from manifests, not a hardcoded list

The environment list was written out four times — probe.py's shim tuple and its
results dict, tests/test_probe.py's ENVIRONMENTS, and ci.yml's wheel loop — so
adding an environment meant four edits and five workstreams collided on three
files. Discovery is now a tomllib scan of environments/*/pyproject.toml plus a
@probes(name) registry, with an optional [tool.arena] tasksets key for one
package that ships several (tera_spatial declares four).

An environment discovered without a registered probe WARNS rather than exits 1,
because interactive environments land their engine before their probe. That is
temporary; it becomes a hard failure with the last registration.

The warning is not a licence to stop being gated. test_probe.py pins the
ungated set to an explicit PENDING_PROBES allowlist that only ever shrinks —
without it, removing a @probes decorator moved the name from the table into a
warning line that already carries five, and every remaining assertion stayed
vacuously true while the environment silently stopped being gated.

Also untracks three environments/*/uv.lock files, which had been failing the
lock-policy step on main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:54:56 -07:00
co-authored by Claude Opus 5
parent 16f4ee21ae
commit 8fe348ca82
6 changed files with 179 additions and 10502 deletions
+93 -15
View File
@@ -62,7 +62,10 @@ the first time and this is what caught it:
the first block was the shortcut's luckiest.
Exit code is the API: 1 if any environment's floor is above zero or its ceiling is
unreachable.
unreachable. Nothing here is listed by hand — the environments are `environments/*/pyproject.toml`
and each probe registers itself with `@probes(taskset_id)`, so adding one is a function appended
at the end of this file. A manifest with no probe is a warning today and an error the day the
last one is written.
uv run --with regex python probe.py
"""
@@ -76,25 +79,80 @@ import random
import re
import statistics
import sys
import tomllib
import types
from collections.abc import Callable
from pathlib import Path
ENVS = Path(__file__).parent / "environments"
def discover() -> dict[str, list[Path]]:
"""Taskset id -> the package directories that ship it, read off the manifests.
The seven names this file used to carry as a literal tuple are `environments/*/pyproject.toml`
now: an environment is gated by existing, not by being remembered here. One directory may
declare more than one taskset — the Tera bridge ships four spatial tasksets out of a single
package — through an optional `[tool.arena] tasksets = [...]`. Absent that key the taskset id
is the project name, which is what all seven of the originals do.
Other sessions create directories under `environments/` while this runs, so a manifest that
disappears between the glob and the read is skipped rather than fatal. A manifest that is
present and unparseable is still fatal — that is a broken commit, not a race.
"""
found: dict[str, list[Path]] = {}
for manifest in sorted(ENVS.glob("*/pyproject.toml")):
try:
data = tomllib.loads(manifest.read_text(encoding="utf-8"))
except FileNotFoundError:
continue
wheel = (data.get("tool", {}).get("hatch", {}).get("build", {})
.get("targets", {}).get("wheel", {}))
packages = wheel.get("packages") or [manifest.parent.name]
names = data.get("tool", {}).get("arena", {}).get("tasksets")
if not names:
name = data.get("project", {}).get("name")
if not name:
print(f"warning: {manifest} declares no project.name — skipped", file=sys.stderr)
continue
names = [name]
for name in names:
found[name] = [manifest.parent / package for package in packages]
return found
# Each package's __init__ imports its taskset, which imports verifiers — a heavy dependency
# this file does not need and should not require. A self-check that only runs once the
# training stack is installed is a self-check nobody runs. So the package name is registered
# as a namespace pointing at the source directory, and the leaf modules are imported under
# it directly: intra-package imports still resolve, __init__ never executes.
for _pkg in ("redaction_pressure", "canary_trap", "fault_localisation", "schema_migration",
"bot_detection", "grand_exchange", "drop_table_inference"):
_shim = types.ModuleType(_pkg)
_shim.__path__ = [str(ENVS / _pkg / _pkg)]
sys.modules[_pkg] = _shim
for _paths in discover().values():
for _path in _paths:
_shim = types.ModuleType(_path.name)
_shim.__path__ = [str(_path)]
sys.modules[_path.name] = _shim
# Every floor-and-ceiling function registers itself here under the taskset id `discover()`
# reads out of the manifest, so adding an environment is one appended function at the end of
# this file and no edit anywhere else in the repository.
_PROBES: dict[str, Callable[[], dict[str, float]]] = {}
def probes(name: str):
def register(fn: Callable[[], dict[str, float]]) -> Callable[[], dict[str, float]]:
if name in _PROBES:
raise RuntimeError(f"two probes are registered for {name}")
_PROBES[name] = fn
return fn
return register
TASKS = 24
@probes("redaction-pressure")
def redaction() -> dict[str, float]:
from redaction_pressure.corpus import build_slices
from redaction_pressure.scan import measure, parse_rules
@@ -126,6 +184,7 @@ def redaction() -> dict[str, float]:
}
@probes("canary-trap")
def canary() -> dict[str, float]:
from canary_trap.corpus import SUBJECTS, build
from canary_trap.scan import measure, parse_probes
@@ -149,6 +208,7 @@ def canary() -> dict[str, float]:
}
@probes("fault-localisation")
def fault() -> dict[str, float]:
from fault_localisation.incident import build, loudest
@@ -174,6 +234,7 @@ def fault() -> dict[str, float]:
}
@probes("schema-migration")
def migration() -> dict[str, float]:
from schema_migration.run import measure
@@ -206,6 +267,7 @@ def migration() -> dict[str, float]:
}
@probes("bot-detection")
def bots() -> dict[str, float]:
from bot_detection.accounts import build_slices
from bot_detection.scan import (
@@ -327,6 +389,7 @@ def bots() -> dict[str, float]:
}
@probes("grand-exchange")
def exchange() -> dict[str, float]:
from grand_exchange.book import (
BUY_BAND, FILL_SHARE, MAX_ITEM_SHARE, MAX_ORDERS, MIN_CROSSINGS, SELL_BAND, TAX,
@@ -605,6 +668,7 @@ def exchange() -> dict[str, float]:
}
@probes("drop-table-inference")
def drops() -> dict[str, float]:
from drop_table_inference.estimate import (
_tail_loss, measure, parse_estimate, reference_estimate,
@@ -749,15 +813,29 @@ def drops() -> dict[str, float]:
def main() -> int:
results = {
"redaction-pressure": redaction(),
"canary-trap": canary(),
"fault-localisation": fault(),
"schema-migration": migration(),
"bot-detection": bots(),
"grand-exchange": exchange(),
"drop-table-inference": drops(),
}
discovered = discover()
if not discovered:
print("no environments under environments/*/pyproject.toml — the scan is empty, which is"
" a broken checkout and not a clean run", file=sys.stderr)
return 1
orphans = sorted(set(_PROBES) - set(discovered))
if orphans:
print(f"probe registered for {', '.join(orphans)}, which no manifest declares — a"
" renamed or deleted environment", file=sys.stderr)
return 1
# A WARNING and not exit 1, deliberately. A new environment's `pyproject.toml` lands before
# its probe does, several sessions share this repository, and a hard gate here turns CI red
# for all of them the moment someone scaffolds a directory. This becomes `return 1` in the
# same commit that lands `exchange_live()` — the last environment that has a manifest and no
# probe. Until then an ungated environment is loud but not fatal.
missing = sorted(set(discovered) - set(_PROBES))
if missing:
print(f"warning: no probe is registered for {', '.join(missing)} — NOT GATED",
file=sys.stderr)
results = {name: _PROBES[name]() for name in sorted(discovered) if name in _PROBES}
print(f"{'environment':22}{'inaction':>10}{'crude':>9}{'plausible':>11}{'oracle':>9} verdict")
failed = False
for name, r in results.items():