- ci_event_wait: OPERATION_SPECS (op-specific completion authority: branch_ci/pull_request_checks -> own runs; master_ci/deploy/release -> deploy qualification); SINGLE LOGICAL AWAIT (all nonterminal conditions re-arm internally; model sees only terminal verdicts or real deadline) - await-operation tool: op_type parameter - runtime wait guard + cwd guard RETIRED (canonical hook = documented no-op; prevention lives in tests, not interception) - wr_root_resolver.py: ONE wait/resume root authority; split-brain fails loudly; start_wr_daemon.py resolves root once; governed reconciliation - construction_activation._auto_structural_awareness: PROJECT_BUILD bootstrap auto-registers Registry + spine manages edge (no manual bookkeeping); fixed latent dispatch_from_decision mode-object bug - research_memory_port.py: future-facing Memory port inside existing INWARD (provider contract, zero-result fast path, teachable-moment candidate seam; APPLICABILITY governs transfer) - v1_parity_manifest.py: 56 rows, 0 blocking mandatory-V1 items - G1-G12 gate evidence in store/scaffold/t21.5_gates.json
101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
"""wr_root_resolver.py — T21.5 §7: THE ONE wait/resume root authority.
|
|
|
|
T21.4 live finding: the daemon defaulted WR_ROOT to its own install dir
|
|
while production data lived at /mnt/workdrive/wr-bridge; a restart
|
|
without WR_ROOT exported silently opened a second event store and the
|
|
controller saw no events (the split-brain that forced manual event
|
|
relocation). This module is the SINGLE resolver every consumer shares:
|
|
|
|
daemon / event_wait / await_operation tool / notify_event_bridge
|
|
(CI event writer) / local event writers / CLI / launcher / tests.
|
|
|
|
Resolution order:
|
|
1. RUNE_WR_ROOT env (explicit override; tests use it)
|
|
2. canonical production root (exists?)
|
|
3. legacy home install root (exists?)
|
|
4. canonical production root (created on demand — first-run path)
|
|
|
|
Split-brain policy: if BOTH canonical and legacy roots contain live
|
|
event stores, resolution FAILS LOUDLY unless the env pins one — never
|
|
silently operate two stores. Reconciliation is governed, not silent:
|
|
run scripts/wr_root_reconcile.py.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
CANONICAL_WR_ROOT = Path("/mnt/workdrive/wr-bridge")
|
|
LEGACY_WR_ROOT = Path.home() / ".local" / "share" / "mimo-wait-resume"
|
|
ENV_VAR = "RUNE_WR_ROOT"
|
|
|
|
SPLIT_BRAIN_ERROR = (
|
|
"WR ROOT SPLIT-BRAIN: both canonical ({canonical}) and legacy "
|
|
"({legacy}) contain live event stores. Never operate two stores "
|
|
"silently — pin {env}=<root> explicitly, or reconcile with "
|
|
"scripts/wr_root_reconcile.py."
|
|
)
|
|
|
|
|
|
def _has_live_store(root: Path) -> bool:
|
|
"""A root counts as 'live' when its event store exists and holds
|
|
events (an empty dir is just an uninitialized install)."""
|
|
events = root / "events"
|
|
if not events.is_dir():
|
|
return False
|
|
return any(events.glob("*.json"))
|
|
|
|
|
|
def resolve_root(*, require_single: bool = True) -> Path:
|
|
"""Resolve THE wait/resume data root. Raises on split-brain when
|
|
require_single (default) and no explicit pin exists."""
|
|
env = os.environ.get(ENV_VAR)
|
|
if env:
|
|
return Path(env)
|
|
canonical_live = _has_live_store(CANONICAL_WR_ROOT)
|
|
legacy_live = _has_live_store(LEGACY_WR_ROOT)
|
|
if canonical_live and legacy_live and require_single:
|
|
raise RuntimeError(SPLIT_BRAIN_ERROR.format(
|
|
canonical=CANONICAL_WR_ROOT, legacy=LEGACY_WR_ROOT,
|
|
env=ENV_VAR))
|
|
if canonical_live or CANONICAL_WR_ROOT.is_dir():
|
|
return CANONICAL_WR_ROOT
|
|
if legacy_live or LEGACY_WR_ROOT.is_dir():
|
|
return LEGACY_WR_ROOT
|
|
return CANONICAL_WR_ROOT # first-run: create canonical
|
|
|
|
|
|
def daemon_launch_env() -> dict[str, str]:
|
|
"""The env a launcher must pass the daemon: WR_ROOT resolved ONCE
|
|
from this authority (the daemon itself must not guess)."""
|
|
return {"WR_ROOT": str(resolve_root()),
|
|
ENV_VAR: str(resolve_root())}
|
|
|
|
|
|
def split_brain_roots() -> list[Path]:
|
|
"""Diagnostic: roots that currently hold live stores."""
|
|
return [r for r in (CANONICAL_WR_ROOT, LEGACY_WR_ROOT)
|
|
if _has_live_store(r)]
|
|
|
|
|
|
def main() -> int:
|
|
try:
|
|
root = resolve_root()
|
|
except RuntimeError as exc:
|
|
print(str(exc), file=sys.stderr)
|
|
return 2
|
|
print(json.dumps({
|
|
"resolved_root": str(root),
|
|
"canonical": str(CANONICAL_WR_ROOT),
|
|
"legacy": str(LEGACY_WR_ROOT),
|
|
"split_brain_roots": [str(p) for p in split_brain_roots()],
|
|
"env_pinned": bool(os.environ.get(ENV_VAR)),
|
|
}))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|