Files
rune/scripts/admit_wave_tools_live_build.py
T
Nick b7345f000f feat(runtime): tools-live build — two-door mode bypass closed, T1-T4 landed
CRITICAL: chat-originated tool dispatch (Python local_tool_runtime.py)
had ZERO mode awareness — a chat write_file in CHAT mode executed
ungated (the Go serve surface gated but the chat path never touched
it). Closed: mode authorization at request intake in local_tool_
runtime, authorize seam in tool_protocol dispatch, approve() resume
door gated, hatch-exposure filtered. Typed ModeRefusal feeds the model
as tool-error data; failure breaker trips on repeat.

T1: ONE mode matrix artifact (schemas/mode/v1/mode_matrix.json) read
by BOTH Go (embed, byte-identical, 16-case battery) and Python
(mode_gate.py, per-dispatch read 0.011ms, RUNE_MODE env wins).
T2: model only SEES mode-allowed tools at capability selection.
T3: per-dispatch mode re-check (mid-loop switch pinned).
T4: tool events persist as channel-scoped role=tool conversation
records — results land in the chat lane through the bridge poll.

Main-session verify: mode dispatch gate + tool authority suites green
own runs. 31/31 + 3438/0 new-mode-gating failures.
2026-10-18 02:52:08 +01:00

258 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Admission dispatch for wave wave-tools-live-build: B-TOOLS-LIVE —
the tool loop wired through chat, gated twice, made visible (T1–T4 + the
two-door bypass fix).
classify + activate + admit (the headless scaffolding entry law,
tests/test_construction_admission.py pattern), admitted through the
research gate on the chain research/factory-tools-live/07-qualify.json
(verdict PROCEED — the chain's terminal QUALIFY artifact).
The critical defect first (adversarial A1, red-first): the chat tool
dispatch (Python local_tool_runtime) had ZERO mode awareness while the Go
serve surface gates — a chat-originated write_file in CHAT executed
ungated. Artifacts this wave admits:
- T1 ONE matrix artifact: schemas/mode/v1/mode_matrix.json (mode -> tool
classes -> concrete executor tools, honest 501s for pending classes),
read by BOTH doors — the Go authority embeds the byte-identical copy
(scripts/gen_mode_matrix.py + --check drift gate) and derives its
frozen profiles from it; Python reads the canonical file.
- T3 dispatch gate: tool_protocol.dispatch_validated_tool gained the
authorize seam; LocalToolRuntime gates at request intake (before the
approval branch), on the approval/resume door (approve()), and the
hatch exposure filter (A4 half). Typed ModeRefusal rides the tool lane
as data; mode read per-dispatch (A3 mid-loop switch). src/rune/
mode_gate.py mirrors the Go readModeState law (absent/corrupt/unknown
-> CHAT; RUNE_MODE env wins).
- T2 SEE gate: operator._capability_selection intersects the broker
selection (and the full-toolset degrade) with the active mode's
allowed set — the model never sees unreachable tools.
- T4 tool transcript: operator persists each tool_event as a
channel-scoped role="tool" conversation record (bounded render,
deterministic idempotent msg_ids; the bridge poll renders it with zero
new machinery) and the runtime preserves pre-tool narration into the
user_text assembly.
Red-first battery: tests/test_mode_dispatch_gate.py (28 RED against the
unfixed code — the bypass proven — then green).
"""
import json
import os
import subprocess
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
SLUG = "wave-tools-live-build"
RESEARCH_SLUG = "factory-tools-live"
OBJECTIVE = (
"B-TOOLS-LIVE T1-T4 + the two-door bypass fix. CRITICAL first: chat "
"tool dispatch (Python local_tool_runtime) had ZERO mode awareness "
"while the Go serve surface gates — a chat-originated write_file in "
"CHAT executed ungated (adversarial A1). T1: the frozen CHAT/TASK/"
"PLAN matrix moves to ONE JSON artifact schemas/mode/v1/"
"mode_matrix.json (mode classes -> concrete executor tools; "
"read->{the 13 read-family executor tools}, shell->{run_command}, "
"build/edit->{write_file,edit_file,format_code,run_tests,"
"git_commit}, pending classes honestly 501), read by BOTH doors — "
"toolexec embeds "
"the byte-identical copy (scripts/gen_mode_matrix.py, --check drift "
"gate) and generates its profiles from it; Python reads the "
"canonical file through the new src/rune/mode_gate.py. T3: "
"tool_protocol.dispatch_validated_tool authorizes against the matrix "
"BEFORE execute (authorize seam; every dispatch incl. "
"find_capability, the approval prepare path, and the approve/resume "
"door); LocalToolRuntime gates at request intake before the approval "
"branch; typed ModeRefusal result feeds the model as tool-error "
"data and the loop records a tool_event; mode read per-dispatch (A3 "
"mid-loop switch); active-mode law mirrors readModeState "
"(absent/corrupt/unknown -> CHAT default, RUNE_MODE env wins); the "
"find_capability hatch exposure is mode-filtered (A4 half). T2: "
"operator._capability_selection intersects the broker selection "
"AND the full-toolset degrade with the active mode's allowed set — "
"the model never SEES unreachable tools. T4: the operator persists "
"each tool_event as a channel-scoped role='tool' conversation record "
"(bounded one-line render, deterministic idempotent msg_ids so "
"resume replays no-op) — the landed bridge poll renders results in "
"the chat lane with zero new machinery — and the runtime preserves "
"pre-tool narration into the final user_text assembly (gap 6). "
"Red-first frozen battery tests/test_mode_dispatch_gate.py: 28 RED "
"against unfixed code (the bypass proven: write_file executed / "
"approval offered in CHAT), then green after the gates."
)
AFFECTED = [
"schemas/mode/v1/mode_matrix.json",
"scripts/gen_mode_matrix.py",
"scripts/admit_wave_tools_live_build.py",
"src/rune/mode_gate.py",
"src/rune/tool_protocol.py",
"src/rune/local_tool_runtime.py",
"src/rune/rune_operator.py",
"src/rune/canary_p1_01.py",
"src/go/tool-executor/internal/toolexec/modegate.go",
"src/go/tool-executor/internal/toolexec/mode_matrix.json",
"tests/test_mode_dispatch_gate.py",
"tests/test_local_tool_runtime.py",
"tests/test_p1_01_capability_authority.py",
"tests/test_tool_authority.py",
"tests/test_orchestration_conversation_state.py",
"tests/test_p0_05_acceptance.py",
"docs/factory/domains.json",
]
EXPECTED_NEW = [
"schemas/mode/v1/mode_matrix.json",
"scripts/gen_mode_matrix.py",
"scripts/admit_wave_tools_live_build.py",
"src/rune/mode_gate.py",
"src/go/tool-executor/internal/toolexec/mode_matrix.json",
"tests/test_mode_dispatch_gate.py",
]
def record_research_gate(marker: str) -> int:
"""Post-mint: pin the gate evidence on the contract's ext.research_gate
record so the contract resolves through the tools-live chain, never
through a registry glob. Idempotent."""
store_dir = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store_dir, "rune")
doc = scaffold.load()
c = scaffold.contract(doc, f"ADMIT-{SLUG}")
if c is None:
print("contract ADMIT-<slug> not found — nothing to record")
return 1
ext = c.setdefault("ext", {})
rg = ext.get("research_gate") or {}
rg.update({
"chain": f"research/{RESEARCH_SLUG}/07-qualify.json",
"verdict": "PROCEED",
"marker": marker,
})
ext["research_gate"] = rg
scaffold.commit(doc, what=f"record research_gate evidence for {SLUG}")
print("research_gate record committed on ADMIT-" + SLUG)
return 0
def main() -> int:
gate = subprocess.run(
[sys.executable, os.path.join("scripts", "check_research_gate.py"),
"admit", "--slug", SLUG, "--objective", OBJECTIVE,
"--research-slug", RESEARCH_SLUG],
capture_output=True, text=True, cwd=ROOT,
)
print(gate.stdout.strip())
if gate.returncode != 0:
print("research gate REFUSED — the law holds")
return 1
marker = ""
for line in gate.stdout.splitlines():
if line.startswith("research-gate:ALLOW"):
marker = line.strip()
break
if not marker:
print("no ALLOW marker in gate output — refusing to mint unrecorded")
return 1
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": f"need-{SLUG}",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": SLUG,
"affected_surface": AFFECTED,
"evidence_refs": [
marker,
"research/factory-tools-live/07-qualify.json",
"research/factory-tools-live/06-synthesis.json",
"research/factory-tools-live/05-adversarial.json",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder=SLUG)
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder=SLUG,
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref=SLUG,
expected_new_entities=EXPECTED_NEW,
expected_runtime_effects=[
"a chat-originated write_file in CHAT (default state) is "
"refused at the dispatch boundary with a typed mode_refusal "
"result; the executor is never invoked; the refusal rides "
"the tool lane and the loop continues (A1 closed)",
"an approval prepared under TASK does not execute after a "
"switch to CHAT — approve() refuses through the same matrix "
"(the resume door gated)",
"a mid-loop mode switch changes the next dispatch's verdict "
"(mode read per-dispatch, A3)",
"the model's tool set in CHAT contains only read-family "
"tools (broker selection and the full-toolset degrade both "
"intersected); find_capability cannot widen the seen set "
"past the mode (A4 half)",
"tool events land as role='tool' conversation records in "
"the channel (bounded one-line render) so the bridge poll "
"shows results in the chat lane; pre-tool narration "
"survives into the final user_text",
"the Go authority battery stays green byte-identical from "
"the ONE artifact (embedded copy drift-gated by "
"scripts/gen_mode_matrix.py --check)",
],
verification_obligations=[
"pytest tests/test_mode_dispatch_gate.py green (31 cases: "
"matrix parity, mode-state law, A1/A3/A4 dispatch gates, "
"SEE gate, tool transcript)",
"pytest tests/test_local_tool_runtime.py "
"tests/test_tool_protocol.py "
"tests/test_p1_01_capability_authority.py "
"tests/test_tool_authority.py "
"tests/test_orchestration_conversation_state.py green "
"(migrated batteries pin TASK explicitly)",
"python3 src/rune/canary_p0_05.py + "
"python3 src/rune/canary_p1_01.py print their PASSED lines",
"go test ./... green in src/go/tool-executor and "
"src/go/rune-entry (frozen 16-case mode battery + serve "
"surface byte-identical from the artifact)",
"python scripts/gen_mode_matrix.py --check exits 0 "
"(embedded copy byte-identical)",
],
approval_ref="user:wave-tools-live-build dispatch",
contract_id=f"ADMIT-{SLUG}",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
rc = 0 if env.get("success") else 1
if record_research_gate(marker) != 0:
return 1
return rc
if __name__ == "__main__":
sys.exit(main())