Handoff 2026-09-11 Part A-C:
A. tui_startup_canary two modes: candidate (--stage, sys.path SCRUBBED of
/mnt/workdrive/rune-code/current + imported-rune purge + contamination
check that rune resolves from the stage) and current (consuming path).
Scrub proven: candidate mode on the 76f71fc stage reports 76f71fc/0.66.2
while current is bb536c9/0.66.3. FIXED a live exit-code bug: main()
returned 1 but was called without sys.exit — a failed canary exited 0
(the deploy gate's own returncode check in deploy_stage.py was correct,
so promotion was never actually bypassed; the standalone script was).
B. Negative deployment canary (tests/test_deployment_canary.py): broken
staged candidate → canary exit 1 naming the failure → current identity
byte-identical before/after. Plus current-mode identity regression.
C. Release recursion: is_release_commit() extracted into
version_convergence.py (ONE source of truth); workflow guard uses
--is-release-subject CLI (no inline python in YAML); regression tests
cover recognition, never-bump-from-release-commits (incl. the 5x loop
case), substantive commits still bumping, and the CLI exit contract.
1241 tests green; ratchet 353/80; henxels clean.
119 lines
4.4 KiB
Python
119 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""tui_startup_canary.py — REAL TUI initialization canary, two proof modes.
|
|
|
|
Mode CANDIDATE (pre-promotion, default when --stage is given):
|
|
Tests the STAGED candidate exactly as supplied. Nothing from
|
|
/mnt/workdrive/rune-code/current is prepended or preferred — sys.path is
|
|
scrubbed of any current-deployment entry so a broken candidate cannot be
|
|
masked by the healthy previous deployment. If the staged candidate is
|
|
broken while current is healthy, this canary MUST fail.
|
|
|
|
Mode CURRENT (post-promotion consuming-path proof, --mode current):
|
|
Tests /mnt/workdrive/rune-code/current/src — proving the promoted
|
|
artifact is what the real consuming path resolves to.
|
|
|
|
Both modes run the ACTUAL RuneApp initialization through Textual's own
|
|
test driver — the same app construction + on_mount wiring the interactive
|
|
TUI executes — then exit 0. The interactive loop is never entered.
|
|
|
|
Prints the build identity (mode + identity) on success. Exit non-zero on
|
|
any initialization failure.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
CURRENT_SRC = "/mnt/workdrive/rune-code/current/src"
|
|
|
|
|
|
def _scrub_current_from_path() -> None:
|
|
"""Remove every current-deployment entry from sys.path (candidate mode).
|
|
|
|
The runner's checkout workspace or ambient PYTHONPATH may contain the
|
|
current deployment; a candidate canary must not silently resolve rune
|
|
from it.
|
|
"""
|
|
sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.abspath(CURRENT_SRC)]
|
|
# Also drop any already-imported rune modules from a prior import.
|
|
for mod in [m for m in sys.modules if m == "rune" or m.startswith("rune.")]:
|
|
del sys.modules[mod]
|
|
|
|
|
|
async def _canary() -> dict:
|
|
from textual.widgets import Static # noqa: F401 — imports must resolve
|
|
|
|
from rune.rune_tui import RuneApp
|
|
|
|
app = RuneApp()
|
|
async with app.run_test(size=(120, 40)) as pilot:
|
|
# on_mount has run; the channels/status bar are wired.
|
|
assert app.task_channel is not None, "task channel not initialized"
|
|
assert app.chat_channel is not None, "chat channel not initialized"
|
|
assert app.status_bar is not None, "status bar not initialized"
|
|
# One interaction cycle: let pending messages/refreshes settle.
|
|
await pilot.pause(0.1)
|
|
import rune
|
|
|
|
return rune.build_info()
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description="Rune TUI startup canary")
|
|
ap.add_argument(
|
|
"--mode",
|
|
choices=("candidate", "current"),
|
|
default="candidate",
|
|
help="candidate: test the staged tree (sys.path scrubbed of current); current: test the consuming path",
|
|
)
|
|
ap.add_argument("--stage", help="staged candidate root (candidate mode)")
|
|
args = ap.parse_args()
|
|
|
|
if args.mode == "candidate":
|
|
if not args.stage:
|
|
print("candidate mode requires --stage", file=sys.stderr)
|
|
return 2
|
|
stage_root = Path(args.stage).resolve()
|
|
stage_src = str(stage_root / "src")
|
|
if not (stage_root / "src" / "rune").is_dir():
|
|
print(f"staged candidate has no src/rune: {stage_src}", file=sys.stderr)
|
|
return 2
|
|
_scrub_current_from_path()
|
|
sys.path.insert(0, stage_src)
|
|
# Verify the scrub worked: rune must resolve from the STAGE, not current.
|
|
import rune # noqa: E402
|
|
|
|
resolved = getattr(rune, "__file__", "")
|
|
if "rune-code/current" in resolved:
|
|
print(
|
|
f"CANARY CONTAMINATED: rune imported from current, not the staged candidate ({resolved})",
|
|
file=sys.stderr,
|
|
)
|
|
return 2
|
|
else:
|
|
if CURRENT_SRC not in sys.path:
|
|
sys.path.insert(0, CURRENT_SRC)
|
|
|
|
async def _run() -> dict:
|
|
try:
|
|
return await _canary()
|
|
finally:
|
|
pass
|
|
|
|
try:
|
|
info = asyncio.run(_run())
|
|
except Exception as exc: # noqa: BLE001 — the canary's JOB is to catch any startup failure and report it as a failed gate; narrowing here would let an unexpected exception class crash the runner instead of failing the gate cleanly.
|
|
print(f"TUI STARTUP CANARY FAILED ({args.mode}): {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
return 1
|
|
print(json.dumps({"mode": args.mode, **info}))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|