Files
rune/scripts/install_mimo_hook.py
T
conductor eb3a2c681c fix(scaffolding): T22 — connect the MiMo edge and apply the meaning gate on the native path
Reality-wins audit of the accepted Scaffolding V1 system against the live
repo/runtime found three genuine wiring defects (no architecture invented):

1. The MiMo edge was DEAD. `~/.mimocode/hooks/builder-bridge.ts` hardcoded
   the ephemeral interpreter `/tmp/ci-venv/bin/python`, which no longer
   exists, so every bridge call failed and the hook silently degraded —
   MiMo received NO Rune Core preflight at all (zero fire-log lines). The
   hook was also UNVERSIONED (no canonical copy), which is how it rotted.
   Repair: canonical source `config/mimo-hooks/builder-bridge.ts` with
   durable interpreter resolution (env override > repo venv > user venv >
   /usr/bin/python3), installed + hash-verified + drift-checked by
   `install_mimo_hook.py`, which now manages a hook SET rather than one file.

2. The native path skipped the canonical meaning layer. `construction_intent`
   (the §17J/§17L/§17M intent resolver: interrogative structure, negation,
   request modals, referent/proposal resolution) existed but was imported
   ONLY by tests. `rune_operator._construction_activation` went straight to
   `classify_mode`, so a pure question carrying a target reference
   ("explain how this module works") classified EXISTING_PROJECT_CHANGE and
   recorded a construction activation for a discussion turn. Repair: the
   intent layer now resolves MEANING before the ONE classifier decides mode
   (the boundary the intent module documents). DISCUSSION maps to
   NOT_CONSTRUCTION — the canonical vocabulary is unchanged, an explicit
   user override (/scaffold-mode) outranks the gate, and an unavailable
   intent layer preserves the previous behaviour exactly rather than
   silently changing governance.

3. The shared Construction Context reported a TERMINAL contract as active.
   `construction_context()` selected on `status == "open"` alone, so 8
   contracts with phase `abandoned` (terminal) were reported as the active
   construction. Repair: terminal phases are now DERIVED once from the
   canonical transition table (`scaffold.TERMINAL_PHASES`), and a contract
   is active only when its phase is non-terminal.

Also: the bridge capsule now surfaces the active Scaffolding construction
(contract/phase/scope/obligations/next-action) to the builder as a
read-only projection of the shared Construction Context — the MiMo edge of
state Scaffolding already owns. Inject-nothing is preserved when no
contract is active; the projection never mutates and degrades to [].

Spine projection refreshed (import graph legitimately gained importers).

Honest boundary: no byte-level enforcement point exists. `tool_executor.py`
never consults construction state, so the 19A invariant gates only the
/build-start route. Establishing MiMo-surface enforcement is NEW
architecture (a tool.execute.before governance hook) and was NOT built
here — it is recorded as the exact remaining blocker.

Verification: 511 passed across the construction/operator/bridge/hook/
structural/parity suites; native-default matrix 12/12; real operator-path
proof with fresh-process readback (EXISTING_PROJECT_CHANGE activated,
question -> NOT_CONSTRUCTION, PROJECT_BUILD bootstraps scaffold + registry);
installed hook invoked directly -> capsule injected + fire log written.
LOCAL_MODEL_COMPUTE_EXECUTED = NO.
2026-09-23 18:09:26 +01:00

73 lines
2.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""install_mimo_hook.py — T21.2 §21.2D (T22: multi-hook): canonical MiMo
hook installer.
Canonical sources: config/mimo-hooks/*.ts
Targets: ~/.mimocode/hooks/<same-name>.ts
Every hook is hash-verified after install; drift is detected in both
directions. T22 added builder-bridge.ts to the managed set — it was
previously installed by hand, unversioned, which is how it silently rotted
(hardcoded ephemeral interpreter). A hook that reaches the builder must be
version-controlled and drift-checked like any other authority.
Usage:
python3 scripts/install_mimo_hook.py # sync all managed hooks
python3 scripts/install_mimo_hook.py --check # verify only, install nothing
"""
import hashlib
import shutil
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
CANONICAL_DIR = REPO / "config" / "mimo-hooks"
TARGET_DIR = Path.home() / ".mimocode" / "hooks"
# The managed set. Adding a file here is what makes a hook version-controlled.
MANAGED = ["event-wait-guard.ts", "builder-bridge.ts"]
def sha256(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
def sync_one(name: str, check_only: bool) -> bool:
canonical = CANONICAL_DIR / name
target = TARGET_DIR / name
if not canonical.is_file():
print(f"canonical source missing: {canonical}")
return False
canon_hash = sha256(canonical)
if target.is_file() and sha256(target) == canon_hash:
print(f"in-sync: {name} {canon_hash[:12]}")
return True
if check_only:
state = "DRIFT" if target.is_file() else "MISSING"
inst = sha256(target)[:12] if target.is_file() else "-"
print(f"{state}: {name} canonical={canon_hash[:12]} installed={inst}")
return False
if target.is_file():
print(f"DRIFT: {name} canonical={canon_hash[:12]} installed={sha256(target)[:12]}")
else:
print(f"hook missing at {target}")
TARGET_DIR.mkdir(parents=True, exist_ok=True)
shutil.copy2(canonical, target)
if sha256(target) != canon_hash:
print(f"INSTALL FAILED: {name} hash mismatch after copy")
return False
print(f"installed: {name} {canon_hash[:12]}")
return True
def main() -> int:
check_only = "--check" in sys.argv
ok = True
for name in MANAGED:
ok = sync_one(name, check_only) and ok
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())