Orchestrata Ultimate › Plans › briefs › p-rul040-stage1-codetools-verb
P-RUL040 Stage 1 — codetools side: the select-affected verb with a frozen JSON contract
Source: plans/briefs/p-rul040-stage1-codetools-verb.md at 8a2cb42ad · last changed 2026-10-20 · first committed 2026-10-20 · 88 lines.
Prose pending.
Plan text
P-RUL040 Stage 1 — codetools side: the select-affected verb with a frozen JSON contract
Status: DISPATCH-READY · Repo: /home/mohamed/workspace/orchestrata.codetools (branch master)
Authority: RUL-040 (Mohamed, verbatim, injected in plans/briefs/p-rul040-fail-closed-blast-radius.md §RULING) +
the Stage 0 adjudication in reports/p-rul040-stage0.md (seam = option (b): the codetools
select-affected verb consumed as a subprocess JSON contract from the DEPLOYED CLI). This packet is
the codetools half; the orchestrata consumption half is a SEPARATE packet on a separate fence.
Context (measured, verify each)
- The ladder already exists and is complete:
src/codetools/deploy/affected.py::select_affected(ADR-0024: rung 1 symbol closure, rung 2 module closure, rung 3 dynamic-module, rung 4 NAMED full verdicts, rung 5SelectionRefusednaming the missing fact) +selection_ladder.py,selection_refused.py,affected_lines.py. It is currently consumed ONLY insideverifybehind the experimental--affected-scopeflag (deploy/cli.py~117-160, ~195-213) — there is NO standalone verb and NO JSON contract a foreign repo can consume. - The consumer will be orchestrata's
run_tests.py --affectedvia its ownaffected_tests.py(module-granularity today; the escalate-boolean arm must die). Its interop surface isscripts/rts.py's frozen fact schema (sym_hash/2,file_hash/2,defines/2,calls/2,is_test/1,safety_escape/1) — see orchestratascripts/rts.pyif you need the exact arity shapes; do not edit orchestrata. - The push-gate incident this fixes: the gate's escalation selected 39,102 tests ("escalated to the full suite") and collapsed into thousands of killed batches. RUL-040: escalation must be MEASURED, never the whole suite; an unboundable change is a REFUSAL naming the set — never a silent full run.
The contract (coordinator-defined; implement exactly, freeze in the docstring + a contract test)
Command: codetools deploy select-affected --repo-root R --json reading one JSON document on stdin:
{
"changed_files": ["src/a.py", "tests/b/x.py"],
"facts": {
"sym_hash": [["sym.path", "hexhash"], ...],
"file_hash": [["relpath", "hexhash"], ...],
"defines": [["relpath", "sym.path"], ...],
"calls": [["caller.sym.path", "callee.sym.path"], ...],
"is_test": [["node.id"], ...],
"safety_escape": [["relpath"], ...]
}
}
stdout is exactly one JSON line; stderr is for progress/errors:
- bounded:
{"verdict": "bounded", "selection": ["tests/...::test_name", "py:<module>"], "named_full": [{"verdict": "dependency-or-environment-change:<path>", "path": "pyproject.toml"}]}— exit 0.selectionentries are pytest BASE node ids orpy:<module>keys;named_fullcarries the rung-4 verdicts with their paths (the CONSUMER decides what a named-full verdict means for its run; the verb never silently emits a bare "run everything"). - refused:
{"verdict": "refused", "missing": "no baseline test contexts for <facts>", "reason": "..."}— exit 2. The missing fact is NAMED (rung 5'sSelectionRefusedtext), never a generic "cannot bound". - tool error: any other failure (bad JSON, unknown fact shape, internal exception) — exit 1 with the message on stderr. Never exit 0 with a full selection as an error stand-in.
Facts mapping (the ladder's inputs from the facts — measure each and REPORT the exact mapping table):
symbol diff = sym_hash pairs vs the baseline hashes the facts carry (old hashes come from
file_hash+history if present; if the facts cannot express a baseline, that missing fact is a
REFUSAL, not a guess); defines/calls drive rung 1; is_test/rung-3 rules; rung 4 triggers on the
changed_files names (pyproject.toml, uv.lock, env/*.env, pytest config names); rung 5 whenever
a rung needs data the facts lack. Do NOT require a codetools coverage baseline — the contract's
baseline is the facts' hash pairs; if any rung is IMPOSSIBLE without a real coverage baseline, say
so explicitly in the report with the mechanism (it is either a fact-schema extension for the
orchestrata side or a documented contract limit — the coordinator rules it).
Deliverables
- D1 — the verb: wire
select-affectedunder the deploy activity (editsrc/codetools/deploy/cli.pyregister + a new module, e.g.src/codetools/deploy/affected_verb.py, reusingselect_affected/SelectionRefused— never reimplement rungs).prog/help must show the exact command above. - D2 — contract tests (real, no mocks): a bounded case over a small fixture facts document; a refused case (facts missing the baseline → exit 2 with a named missing fact); a tool-error case (malformed stdin JSON → exit 1); each asserted through the CLI subprocess (exit code + stdout JSON schema + stderr). One test pins the frozen contract shape (the exact keys above) so a future schema change fails loudly.
- D3 — the report's mapping table: every ladder rung × which fact fields satisfy it × what is refused when they cannot; verbatim CLI outputs for the three cases.
- D4 — lanes:
PYTEST_DEBUG_TEMPROOT=/var/tmp .venv/bin/python scripts/run_tests_parallel.py tests/deploy/ -j 8green; paste verbatim. Re-sync (uv sync --dev --no-editable) BEFORE running — the staleness guard refuses otherwise (that is the guard working).
Fence
ALLOWED: src/codetools/deploy/cli.py, new files under src/codetools/deploy/, new/existing tests
under tests/deploy/ that test the verb. FORBIDDEN (named): pyproject.toml, uv.lock,
deploy/**, src/codetools/review/** (REV-3 lane ACTIVE), anything else — including /tmp and any
absolute path (the fence wins every time; the previous packet was refused on a /tmp write).
FOREIGN DIRT — untouched: src/codetools/review/gates.py, tests/review/test_review_ruff_lookup.py,
plans/ast-*.json, kimi-export-*.md, plans/*.warmcache.
Conduct
RUL-037 (no python -c/heredoc writes; file tools only; no codemod scripts). Measure before
editing; verbatim outputs; no false attribution. Do NOT commit (the coordinator commits). Do not
run a test twice against the same code state. Final report ends with: git status of the allowlist
files, the rung×facts mapping table, the three contract-case outputs verbatim, the D4 lane output.