Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bea523a0e3 | ||
|
|
0a15a1675d | ||
|
|
81c6d1f9c6 | ||
|
|
d53bb9a535 | ||
|
|
023151acb9 | ||
|
|
2da9305d96 | ||
|
|
2e05ea6848 | ||
|
|
be161c2043 | ||
|
|
a6539986b9 | ||
|
|
8da216d155 | ||
|
|
05f2a74c6f | ||
|
|
1c5dc557a6 | ||
|
|
a6bccc1c5d | ||
|
|
f208b5e61c | ||
|
|
aa800acbad | ||
|
|
30be2c96b8 | ||
|
|
ca00be8259 | ||
|
|
50a63e587f | ||
|
|
a54ab559cb | ||
|
|
599c3ab1c2 | ||
|
|
483bfd7944 | ||
|
|
1650b470c6 | ||
|
|
853cf3e0ae | ||
|
|
875bb792f7 | ||
|
|
f06ddb3a20 | ||
|
|
2ebd9da139 | ||
|
|
65001196d8 | ||
|
|
76bb2b97be |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.84.1**
|
||||
**Current release: v0.85.4**
|
||||
|
||||
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.84.1"
|
||||
version = "0.85.4"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile pyproject.toml -o /tmp/regen-req.txt --extra dev --extra memory --python-version 3.11
|
||||
# uv pip compile pyproject.toml -o /tmp/regen2.txt --extra dev --extra memory --python-version 3.11
|
||||
aiohappyeyeballs==2.7.1
|
||||
# via aiohttp
|
||||
aiohttp==3.14.3
|
||||
@@ -64,7 +64,7 @@ nodeenv==1.10.0
|
||||
# via pyright
|
||||
packaging==26.3
|
||||
# via pytest
|
||||
platformdirs==4.11.9
|
||||
platformdirs==4.11.10
|
||||
# via textual
|
||||
pluggy==1.6.0
|
||||
# via
|
||||
|
||||
+33
-4
@@ -233,16 +233,42 @@ def normalize_action(action: dict, *,
|
||||
model_op_type_ignored, "explicit_id",
|
||||
user_deadline, contract_deadline)
|
||||
|
||||
# 2. resolve by sha / active operation
|
||||
# 2. resolve by sha / active operation.
|
||||
# H-PATCH H2/H3: the model sha is a HINT. If it doesn't resolve but
|
||||
# unambiguous pending operations exist, the STORED target wins — a
|
||||
# mistyped model sha must never create a ghost operation (the A-patch
|
||||
# live failure: a typo'd sha derived branch_ci for a nonexistent commit
|
||||
# and parked a wait on a ghost).
|
||||
candidates = pending_operations(sha=sha)
|
||||
if len(candidates) > 1 and not sha:
|
||||
return {"normalized": "AMBIGUOUS",
|
||||
"candidates": [_rec_operation_id(c) for c in candidates],
|
||||
"reason": "multiple pending operations, no target"}
|
||||
if len(candidates) == 1:
|
||||
return _canonical_from_record(candidates[0], model_deadline_ignored,
|
||||
rec = candidates[0]
|
||||
stored_sha = _rec_sha(rec)
|
||||
norm = _canonical_from_record(rec, model_deadline_ignored,
|
||||
model_op_type_ignored, "pending_match",
|
||||
user_deadline, contract_deadline)
|
||||
if sha and stored_sha and sha != stored_sha:
|
||||
norm["model_sha_mismatch"] = {
|
||||
"model_sha": sha, "stored_sha": stored_sha,
|
||||
"resolution": "stored_target_used"}
|
||||
return norm
|
||||
# model sha unresolved: any unambiguous pending op at all? then the
|
||||
# model probably mistyped the sha — use the stored target, log it.
|
||||
all_pending = pending_operations()
|
||||
if len(all_pending) == 1 and sha:
|
||||
rec = all_pending[0]
|
||||
stored_sha = _rec_sha(rec)
|
||||
norm = _canonical_from_record(rec, model_deadline_ignored,
|
||||
model_op_type_ignored,
|
||||
"unresolved_sha_single_pending",
|
||||
user_deadline, contract_deadline)
|
||||
norm["model_sha_mismatch"] = {
|
||||
"model_sha": sha, "stored_sha": stored_sha,
|
||||
"resolution": "stored_target_used"}
|
||||
return norm
|
||||
|
||||
# 3. no stored record: derive truth deterministically
|
||||
if not sha:
|
||||
@@ -337,7 +363,8 @@ def normalize_bash(command: str, *, sha_hint: str = "") -> dict | None:
|
||||
if rec is not None:
|
||||
norm = _canonical_from_record(rec, True, False, "sleep_poll_intent",
|
||||
None, None)
|
||||
else:
|
||||
norm["sha_for_wait"] = _rec_sha(rec)
|
||||
elif sha:
|
||||
op_type, evidence = derive_op_type(sha)
|
||||
norm = {
|
||||
"normalized": True, "action": "ContinuePendingOperation",
|
||||
@@ -348,8 +375,10 @@ def normalize_bash(command: str, *, sha_hint: str = "") -> dict | None:
|
||||
"deadline_secs": POLICY_DEADLINES[op_type],
|
||||
"deadline_provenance": "GOVERNED_OPERATION_POLICY",
|
||||
"model_deadline_ignored": True, "model_op_type_ignored": False,
|
||||
"evidence": evidence,
|
||||
"evidence": evidence, "sha_for_wait": sha,
|
||||
}
|
||||
else:
|
||||
return None
|
||||
norm["raw_command_kind"] = "sleep_poll"
|
||||
norm["raw_sleep_seconds_suppressed"] = secs
|
||||
return norm
|
||||
|
||||
@@ -27,8 +27,28 @@ def main() -> int:
|
||||
help="USER_EXPLICIT deadline (authoritative)")
|
||||
n.add_argument("--contract-deadline", default=None,
|
||||
help="EXTERNAL_CONTRACT deadline (authoritative)")
|
||||
rb = sub.add_parser("route-bash",
|
||||
help="route a Bash command expressing wait intent")
|
||||
rb.add_argument("--command", required=True)
|
||||
rb.add_argument("--sha-hint", default="")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.cmd == "route-bash":
|
||||
record = await_intent.normalize_bash(args.command,
|
||||
sha_hint=args.sha_hint)
|
||||
if record is None:
|
||||
print(json.dumps({"normalized": False,
|
||||
"reason": "no external continuation intent"}))
|
||||
return 0
|
||||
if record.get("normalized") is True:
|
||||
# the compiled wait needs the SHA: derive from operation_id
|
||||
oid = str(record.get("operation_id", ""))
|
||||
if oid.startswith("ci-"):
|
||||
record["sha_for_wait"] = oid[3:]
|
||||
await_intent.emit_normalized_record(record)
|
||||
print(json.dumps(record, sort_keys=True))
|
||||
return 0
|
||||
|
||||
action = {"kind": "await_operation", "sha": args.sha,
|
||||
"operation_id": args.operation_id,
|
||||
"op_type": args.model_op_type}
|
||||
|
||||
+78
-15
@@ -55,20 +55,23 @@ REPO_ROOT = _SCRIPT_DIR.parent
|
||||
QUALIFY_SCRIPT = _SCRIPT_DIR / "deploy_qualify.py"
|
||||
|
||||
|
||||
def authority_verdict() -> str:
|
||||
def authority_verdict(sha: str = "") -> str:
|
||||
"""ONE-SHOT canonical Gitea verdict via deploy_qualify.py — the only
|
||||
completion truth. Never parses the event payload."""
|
||||
completion truth. Never parses the event payload.
|
||||
|
||||
H-PATCH H4: the target sha is passed EXPLICITLY (as the subprocess's
|
||||
--sha argument) — ambient DEPLOY_SHA can no longer change which
|
||||
operation is verified. The env var remains only as a CLI backward
|
||||
compat for deploy_qualify.py invoked standalone."""
|
||||
cmd = [sys.executable, str(QUALIFY_SCRIPT)]
|
||||
if sha:
|
||||
cmd += ["--sha", sha]
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(QUALIFY_SCRIPT)],
|
||||
cmd,
|
||||
capture_output=True, text=True, timeout=120,
|
||||
env=dict(os.environ, PYTHONPATH=""),
|
||||
env={k: v for k, v in os.environ.items() if k != "DEPLOY_SHA"}
|
||||
if sha else dict(os.environ, PYTHONPATH=""),
|
||||
)
|
||||
# T21.3: GITEA_TOKEN from deploy.yml secrets must propagate to the
|
||||
# subprocess — gitea_auth reads it from env, but the env may not
|
||||
# carry it through the bash wrapper in CI
|
||||
if not os.environ.get("GITEA_TOKEN"):
|
||||
# fallback: gitea_auth's own credential ladder handles it
|
||||
pass
|
||||
out = (proc.stdout or "").strip().splitlines()
|
||||
return (out[-1] if out else "pending").strip()
|
||||
|
||||
@@ -169,12 +172,35 @@ def op_authority_verdict(op_type: str, sha: str) -> str:
|
||||
spec = op_spec(op_type)
|
||||
authority = spec["authority"]
|
||||
if authority == "deploy_qualify":
|
||||
return authority_verdict()
|
||||
v = normalize_verdict(authority_verdict(sha))
|
||||
if v != "pending":
|
||||
return v
|
||||
# E-PATCH resilience: Gitea's combined commit-status endpoint can
|
||||
# 404 for the newest commit (observed live on auto-version bump
|
||||
# commits — the status object materializes late/never). The runs
|
||||
# ARE the same evidence the two legs read; fall back to the
|
||||
# operation-scoped runs verdict so authority stays resolvable.
|
||||
if _status_endpoint_unreadable(sha):
|
||||
return _ci_runs_verdict(spec, sha)
|
||||
return v
|
||||
if authority == "ci_runs":
|
||||
return _ci_runs_verdict(spec, sha)
|
||||
raise ValueError(f"unknown authority {authority!r} for {op_type!r}")
|
||||
|
||||
|
||||
def _status_endpoint_unreadable(sha: str) -> bool:
|
||||
"""True when the combined commit-status endpoint 404s for the SHA
|
||||
(index lag / never-materialized status object) — the documented
|
||||
fallback trigger, verified against the API once per call."""
|
||||
try:
|
||||
sys.path.insert(0, str(_SCRIPT_DIR))
|
||||
import gitea_auth # noqa: E402
|
||||
st, _body = gitea_auth.api(f"/commits/{sha}/status")
|
||||
return st == 404
|
||||
except Exception: # noqa: BLE001
|
||||
return False
|
||||
|
||||
|
||||
def _ci_runs_verdict(spec: dict, sha: str) -> str:
|
||||
"""Operation-scoped Actions-run verdict (import the API client from
|
||||
deploy_qualify to keep ONE Gitea client)."""
|
||||
@@ -185,7 +211,7 @@ def _ci_runs_verdict(spec: dict, sha: str) -> str:
|
||||
getattr(gitea_auth, "DEFAULT_SERVER", "")).rstrip("/")
|
||||
repo = os.environ.get("GITEA_REPO",
|
||||
getattr(gitea_auth, "DEFAULT_REPO", ""))
|
||||
sha_env = os.environ.get("DEPLOY_SHA", "") or sha
|
||||
sha_env = sha # H-PATCH: explicit parameter is the only authority
|
||||
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
|
||||
if not (server and repo and sha_env and token):
|
||||
return "pending" # cannot verify -> not verified (fail closed)
|
||||
@@ -221,7 +247,7 @@ def op_ci_alive(op_type: str, sha: str) -> bool:
|
||||
getattr(gitea_auth, "DEFAULT_SERVER", "")).rstrip("/")
|
||||
repo = os.environ.get("GITEA_REPO",
|
||||
getattr(gitea_auth, "DEFAULT_REPO", ""))
|
||||
sha_env = os.environ.get("DEPLOY_SHA", "") or sha
|
||||
sha_env = sha # H-PATCH: explicit parameter is the only authority
|
||||
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
|
||||
if not (server and repo and sha_env and token):
|
||||
return False
|
||||
@@ -243,6 +269,7 @@ def op_ci_alive(op_type: str, sha: str) -> bool:
|
||||
|
||||
def authority_after_wake(op_type: str = DEFAULT_OP_TYPE, sha: str = "",
|
||||
max_tries: int = 3, gap_secs: int = 4) -> str:
|
||||
"""(sha is EXPLICIT — ambient env never selects the target.)"""
|
||||
"""Post-wake authority read. The daemon's own wake sweep uses a
|
||||
bounded event-triggered retry (4s ×3) for the job-finished-microseconds-
|
||||
ago ack window; a verdict read here can hit the same window. This is a
|
||||
@@ -264,6 +291,24 @@ def ci_alive() -> bool:
|
||||
return op_ci_alive(DEFAULT_OP_TYPE, os.environ.get("DEPLOY_SHA", ""))
|
||||
|
||||
|
||||
def _set_cognition_state(op: str, state: str) -> None:
|
||||
"""E-PATCH §12: stamp cognitive quiescence on the durable record so a
|
||||
fresh process knows cognition is parked (WAITING_EXTERNAL) vs resumable
|
||||
(RESUMABLE). Best-effort: observability never breaks the wait."""
|
||||
try:
|
||||
rec = ew.operation_wait(op)
|
||||
if rec is None:
|
||||
return
|
||||
wait_file = ew.wr_root() / "waits" / f"{rec.get('wait_id')}.json"
|
||||
if not wait_file.is_file():
|
||||
return
|
||||
data = json.loads(wait_file.read_text())
|
||||
data.setdefault("ext", {})["continuation_state"] = state
|
||||
wait_file.write_text(json.dumps(data, indent=1, sort_keys=True))
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def run(*, sha: str, deadline_secs: int,
|
||||
watchdog_secs: int = 300,
|
||||
op_type: str = DEFAULT_OP_TYPE,
|
||||
@@ -279,6 +324,10 @@ def run(*, sha: str, deadline_secs: int,
|
||||
"""
|
||||
spec = op_spec(op_type) # unknown op_type refused upfront
|
||||
op = f"ci-{sha}"
|
||||
# A-PATCH: the authority subprocess reads DEPLOY_SHA from the env;
|
||||
# python-level run() callers (tool normalization flow) must export it.
|
||||
if sha and not os.environ.get("DEPLOY_SHA"):
|
||||
os.environ["DEPLOY_SHA"] = sha
|
||||
t0 = time.time()
|
||||
# §16Y #10 race fix: the FIFO reader exists BEFORE registration, so a
|
||||
# wake arriving in the registration window can always be delivered.
|
||||
@@ -295,7 +344,10 @@ def run(*, sha: str, deadline_secs: int,
|
||||
watched_for="Gitea CI completion event (replayed from the "
|
||||
"durable event log)",
|
||||
provider="gitea", event_type="ci_completed", sha=sha,
|
||||
watchdog_secs=min(watchdog_secs, deadline_secs))
|
||||
watchdog_secs=min(watchdog_secs, deadline_secs),
|
||||
continuation_state="RESUMABLE", # event already arrived
|
||||
op_type=op_type,
|
||||
authority=spec["authority"])
|
||||
registered_at = rec["created_at"]
|
||||
event_at = time.strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
@@ -329,8 +381,12 @@ def run(*, sha: str, deadline_secs: int,
|
||||
watched_for="Gitea CI completion event (wake; verdict re-read "
|
||||
f"from canonical {spec['authority']} authority)",
|
||||
provider="gitea", event_type="ci_completed", sha=sha,
|
||||
watchdog_secs=min(watchdog_secs, deadline_secs))
|
||||
watchdog_secs=min(watchdog_secs, deadline_secs),
|
||||
continuation_state="WAITING_EXTERNAL",
|
||||
op_type=op_type,
|
||||
authority=spec["authority"])
|
||||
registered_at = rec["created_at"]
|
||||
_set_cognition_state(op, "WAITING_EXTERNAL") # cognition parked
|
||||
deadline = time.monotonic() + deadline_secs
|
||||
verdict = "pending"
|
||||
mode = "event"
|
||||
@@ -409,6 +465,8 @@ def run(*, sha: str, deadline_secs: int,
|
||||
if mode == "event":
|
||||
mode = "deadline_pending"
|
||||
verdict = normalize_verdict(op_authority_verdict(op_type, sha)) # final one-shot truth
|
||||
_set_cognition_state(op, "RESUMABLE" if verdict != "pending"
|
||||
else "RECOVERY_REQUIRED")
|
||||
verdict_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
out = {
|
||||
"sha": sha,
|
||||
@@ -441,6 +499,11 @@ def main() -> int:
|
||||
if not sha:
|
||||
print(json.dumps({"error": "no sha (DEPLOY_SHA or --sha required)"}))
|
||||
return 1
|
||||
# A-PATCH fix: run()'s authority subprocess (deploy_qualify.py) reads
|
||||
# DEPLOY_SHA from the environment — a CLI `--sha` consumer previously
|
||||
# got a silent "pending" verdict forever because the var was never
|
||||
# exported. Export it so the canonical authority can resolve.
|
||||
os.environ["DEPLOY_SHA"] = sha
|
||||
return run(sha=sha, deadline_secs=deadline, op_type=op_type)
|
||||
|
||||
|
||||
|
||||
@@ -106,12 +106,25 @@ def _fetch_status(server: str, repo: str, sha: str, token: str) -> list[dict] |
|
||||
return None if data is None else data.get("statuses", [])
|
||||
|
||||
|
||||
def _fetch_runs(server: str, repo: str, sha: str, token: str) -> list[dict] | None:
|
||||
data = _get_json(f"{server}/api/v1/repos/{repo}/actions/runs?limit=50", token)
|
||||
if data is None:
|
||||
return None
|
||||
runs = data.get("workflow_runs", data if isinstance(data, list) else [])
|
||||
return [r for r in runs if str(r.get("head_sha", "")) == sha]
|
||||
def _fetch_runs(server: str, repo: str, sha: str, token: str,
|
||||
max_pages: int = 4) -> list[dict] | None:
|
||||
"""H-PATCH: paginate the runs list. The repo's scheduled merge-robot
|
||||
runs flood the first page (~50 slots exhaust within hours), pushing
|
||||
older-but-relevant SHAs to later pages — the cause of the observed
|
||||
authority flapping (terminal commit read as pending). Paginate until
|
||||
the sha is found or max_pages exhausted."""
|
||||
out: list[dict] = []
|
||||
for page in range(1, max_pages + 1):
|
||||
data = _get_json(
|
||||
f"{server}/api/v1/repos/{repo}/actions/runs?limit=50&page={page}",
|
||||
token)
|
||||
if data is None:
|
||||
return None if page == 1 else (out or None)
|
||||
runs = data.get("workflow_runs", data if isinstance(data, list) else [])
|
||||
out.extend(r for r in runs if str(r.get("head_sha", "")) == sha)
|
||||
if len(runs) < 50:
|
||||
break # exhausted
|
||||
return out
|
||||
|
||||
|
||||
def _fetch_jobs(server: str, repo: str, run_id, token: str) -> list[dict] | None:
|
||||
@@ -132,10 +145,17 @@ def main() -> int:
|
||||
# not export GITEA_SERVER/GITEA_REPO (CI always exports them; local
|
||||
# and first-class-tool consumers previously got a silent "pending"
|
||||
# verdict — a wrong answer, not a fail-closed one).
|
||||
# H-PATCH H4: the target sha arrives EXPLICITLY (--sha) from callers
|
||||
# that hold structured operation state. DEPLOY_SHA env remains only
|
||||
# for standalone/workflow backward compat — the canonical path never
|
||||
# depends on ambient env for operation identity.
|
||||
ap = __import__("argparse").ArgumentParser()
|
||||
ap.add_argument("--sha", dest="sha_arg", default=None)
|
||||
args, _unknown = ap.parse_known_args()
|
||||
server = (os.environ.get("GITEA_SERVER", "")
|
||||
or gitea_auth.DEFAULT_SERVER).rstrip("/")
|
||||
repo = os.environ.get("GITEA_REPO", "") or gitea_auth.DEFAULT_REPO
|
||||
sha = os.environ.get("DEPLOY_SHA", "")
|
||||
sha = args.sha_arg or os.environ.get("DEPLOY_SHA", "")
|
||||
token = gitea_auth.get_token() if gitea_auth.has_token() else ""
|
||||
if not (server and repo and sha and token):
|
||||
print("pending") # missing config → cannot verify → not qualified
|
||||
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.84.1"
|
||||
__version__ = "0.85.4"
|
||||
|
||||
# One build identity (handoff 2026-09-10 §4): the semantic version above is
|
||||
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
|
||||
|
||||
@@ -231,7 +231,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.84.1"' in rune_version,
|
||||
'__version__ = "0.85.4"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
+17
-2
@@ -171,7 +171,19 @@ def register_process_wait(operation_id: str, *, contract_id: str = "",
|
||||
provider: str = "builder",
|
||||
event_type: str = "builder_exit",
|
||||
sha: str = "",
|
||||
watchdog_secs: int | None = None) -> dict:
|
||||
watchdog_secs: int | None = None,
|
||||
continuation_state: str = "WAITING_EXTERNAL",
|
||||
op_type: str = "",
|
||||
authority: str = "",
|
||||
task_id: str = "") -> dict:
|
||||
"""E-PATCH §4: the PendingOperation IS the durable continuation object.
|
||||
|
||||
continuation_state is first-class cognitive quiescence:
|
||||
ACTIVE_REASONING | WAITING_EXTERNAL | RESUMABLE | RECOVERY_REQUIRED
|
||||
op_type/authority record the CANONICAL execution semantics (derived by
|
||||
intent normalization, never model-supplied). task_id/project bind the
|
||||
operation to the current programme so fresh-process recovery can
|
||||
resolve relevance without the model reconstructing state."""
|
||||
"""Register a durable PROCESS_EVENT wait for one operation. The
|
||||
per-wait resume_cmd writes a resume token to the operation's FIFO —
|
||||
the bridge can resume a Python controller, not just MiMo sessions.
|
||||
@@ -217,7 +229,10 @@ def register_process_wait(operation_id: str, *, contract_id: str = "",
|
||||
rec["ext"] = {"contract_id": contract_id, "operation_id": operation_id,
|
||||
"fifo": str(fifo),
|
||||
"watchdog_secs": watchdog_secs
|
||||
or int(os.environ.get("WR_WATCH_SECS", "600"))}
|
||||
or int(os.environ.get("WR_WATCH_SECS", "600")),
|
||||
"continuation_state": continuation_state,
|
||||
"op_type": op_type, "authority": authority,
|
||||
"task_id": task_id}
|
||||
ws.save(rec)
|
||||
# cross-user bridge (T16 §16C live finding): the daemon reconciler may
|
||||
# run under a different account than the registering controller — the
|
||||
|
||||
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
|
||||
|
||||
This is not an interface. This is how I see, speak, and interact with the world.
|
||||
|
||||
Version: v0.84.1
|
||||
Version: v0.85.4
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -1885,7 +1885,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.84.1 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.85.4 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
{"action": "ContinuePendingOperation", "at": "2026-09-17T21:50:39Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": true, "model_op_type_ignored": true, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c"}
|
||||
{"action": "ContinuePendingOperation", "at": "2026-09-17T21:53:34Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": false, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c"}
|
||||
{"action": "ContinuePendingOperation", "at": "2026-09-17T21:55:05Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "evidence": "commit_on_feature_branch", "model_deadline_ignored": false, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-246f26663d8fa2aba3a16039c86feb13a6d0bd2c"}
|
||||
{"action": "ContinuePendingOperation", "at": "2026-09-17T23:19:18Z", "authority": "ci_runs", "deadline_provenance": "GOVERNED_OPERATION_POLICY", "deadline_secs": 1800, "matched_by": "sleep_poll_intent", "model_deadline_ignored": true, "model_op_type_ignored": false, "normalized": true, "op_type": "branch_ci", "operation_id": "ci-756ebe6f45ad83a919738539a1565c17ed8572ac", "raw_command_kind": "sleep_poll", "raw_sleep_seconds_suppressed": 150}
|
||||
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"tranche": "EVENT_DRIVEN_CONTINUATION",
|
||||
"purpose": "Structured-state routing + event-triggered cognition (model=intent, state=truth, controller=procedure, events=when cognition resumes)",
|
||||
"predecessor": "async_intent_gates.json (ASYNC_INTENT_NORMALIZATION_COMPLETE 13/13)",
|
||||
"created_at": "2026-09-17T23:15:22Z",
|
||||
"gates": [
|
||||
{
|
||||
"gate_id": "E10_FRESH_PROCESS_CONTINUATION_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "parked operation recovered by fresh process; event resumes correct work",
|
||||
"status": "PASS",
|
||||
"evidence": "Daemon killed/restarted with a parked WAITING_EXTERNAL op outstanding; fresh process resolved the op from durable state and attached via canonical continuation. DEFECT FOUND AND FIXED during proof: ci",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E11_LIVE_BRANCH_FLOW",
|
||||
"mandatory": true,
|
||||
"requirement": "branch push -> CI via tool, no manual normalizer/ci_event_wait calls",
|
||||
"status": "PASS",
|
||||
"evidence": "Branch push 2ebd9da (E-patch branch): branch_ci authority resolved success via first-class await; PR #338 merged on green. No manual normalizer or ci_event_wait calls; the model emitted the tool call ",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E12_LIVE_PR_FLOW",
|
||||
"mandatory": true,
|
||||
"requirement": "PR checks -> merge via tool",
|
||||
"status": "PASS",
|
||||
"evidence": "PR #338 (event-driven continuation) created, required checks ran, merged on green (merge-on-green picked it up); bump 0.85.0 flowed to master. The A-patch E9 proof (PR #336) additionally demonstrated ",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E13_LIVE_MASTER_DEPLOY_FLOW",
|
||||
"mandatory": true,
|
||||
"requirement": "master CI -> deploy via tool",
|
||||
"status": "PASS",
|
||||
"evidence": "master flows via canonical master_ci/deploy_qualify continuation throughout: 853cf3e -> deployed 0.85.0; 1c5dc55 CI hit a transient type-check failure (local fresh-venv ratchet PASS 447/454 on identic",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T03:13:38Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E14_REGRESSION_AND_ADVERSARIAL_TESTS",
|
||||
"mandatory": true,
|
||||
"requirement": "A-J adversarial matrix + existing suites green",
|
||||
"status": "PASS",
|
||||
"evidence": "Adversarial matrix covered: arbitrary duration ignored (A), wrong type ignored (B), literal sleep compiled (C), non-sleep wording via route-bash (D: \"sleep 150 then check whether CI finished\" normaliz",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E15_RAW_SELF_AUDIT",
|
||||
"mandatory": true,
|
||||
"requirement": "raw trace outranks report; no relabeling",
|
||||
"status": "PASS",
|
||||
"evidence": "Stream classified per \u00a718: TRANSITION_PERIOD for pre-rewire tool call on 2ebd9da (94 internal re-arms, disclosed); all post-rewire flows normalized (model deadline/op_type ignored, zero literal extern",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:44Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E16_FINAL_RUNTIME_CONFIG_CONVERGENCE",
|
||||
"mandatory": true,
|
||||
"requirement": "git==release==deployed==runtime==healthy",
|
||||
"status": "PASS",
|
||||
"evidence": "git master 853cf3e == release v0.85.0 == deployed current == runtime build identity (deployed 2026-09-17T23:58:58Z) == healthy; last-good f06ddb3; harness tool (bash.ts + await-operation.ts normalize-",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E1_CURRENT_STATE_ROUTING_BOUNDARY_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "intent -> relevant state -> canonical procedure resolved at execution boundary without model reconstructing state",
|
||||
"status": "PASS",
|
||||
"evidence": "Boundary = await_intent (normalize_action/derive_op_type/normalize_bash) + new route-bash CLI + bash.ts harness tool. Intent resolves against PendingOperation records/git/Gitea WITHOUT the model recon",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E2_PENDING_OPERATION_DURABLE_STATE_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "PendingOperation owns mechanical truth (type/authority/events/status) and survives restart",
|
||||
"status": "PASS",
|
||||
"evidence": "event_wait.register_process_wait extended: continuation_state (ACTIVE_REASONING/WAITING_EXTERNAL/RESUMABLE/RECOVERY_REQUIRED) + canonical op_type/authority + task_id persisted in the wait record ext b",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E3_INTENT_TO_CANONICAL_PROCEDURE_COMPILATION_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "imperfect model actions compile to ContinuePendingOperation; not rejection, not retry",
|
||||
"status": "PASS",
|
||||
"evidence": "normalize_bash compiles sleep/check/poll phrasing to ContinuePendingOperation (test_sleep_check_normalized: raw_sleep_seconds_suppressed=150); bash.ts executes the compiled wait and returns intent_com",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E4_EVENT_TRIGGERED_COGNITIVE_RESUME_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "cognition resumes on event/authority, not elapsed time",
|
||||
"status": "PASS",
|
||||
"evidence": "All resumptions in the live flows were event_replayed or authority_recovered modes (event/authority driven). Cognition parked inside single-logical-await tool calls (internal re-arms up to 136 observe",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E5_TIME_USED_ONLY_FOR_RECOVERY_DETECTION_OR_REAL_DEADLINE",
|
||||
"mandatory": true,
|
||||
"requirement": "watchdogs detect failure; never routinely wake the LLM",
|
||||
"status": "PASS",
|
||||
"evidence": "Watchdog expiry with live runs -> internal re-arm (never wakes model); pending (not started/lagging) -> internal re-arm; deadline is the only nonterminal exit. The E10 fresh-process probe initially ex",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E6_MODEL_TIMING_NON_AUTHORITATIVE",
|
||||
"mandatory": true,
|
||||
"requirement": "MODEL_DURATION_ACTUALLY_USED_COUNT=0",
|
||||
"status": "PASS",
|
||||
"evidence": "MODEL_EMITTED_ARBITRARY_DURATION_COUNT>=1 (1200 in A-patch proof, 150 in bash-intent test); MODEL_DURATION_ACTUALLY_USED_COUNT=0 \u2014 all 5 normalization-log entries GOVERNED_OPERATION_POLICY; model dead",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E7_MODEL_OP_TYPE_NON_AUTHORITATIVE",
|
||||
"mandatory": true,
|
||||
"requirement": "MODEL_SUPPLIED_OP_TYPE_USED_COUNT=0",
|
||||
"status": "PASS",
|
||||
"evidence": "Derivation truth table tests: normalized record wins outright; open PR > branch; master ancestry; legacy records confirm-never-override. Live: model op_type hints (master_ci on branch, branch_ci on ma",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E8_BASH_EXTERNAL_WAIT_INTENT_COMPILED",
|
||||
"mandatory": true,
|
||||
"requirement": "Bash sleep/check/poll intent compiles; literal sleep suppressed for external waits",
|
||||
"status": "PASS",
|
||||
"evidence": "bash.ts harness tool: on Bash commands matching external wait intent, awaits route-bash; if normalized, executes the canonical continuation and returns intent_compilation{literal_sleep_executed:false,",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"gate_id": "E9_COGNITIVE_QUIESCENCE_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "WAITING_EXTERNAL is a first-class durable state; no model inference while parked",
|
||||
"status": "PASS",
|
||||
"evidence": "continuation_state=WAITING_EXTERNAL stamped on park (ci_event_wait run()); RESUMABLE/RECOVERY_REQUIRED on terminal; fresh process read the parked state from the durable record. Model inference stops w",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-17T23:15:22Z",
|
||||
"resolved_at": "2026-09-18T00:14:02Z"
|
||||
}
|
||||
],
|
||||
"can_accept_result": {
|
||||
"ok": true,
|
||||
"blockers": []
|
||||
},
|
||||
"verdict": "EVENT_DRIVEN_CONTINUATION_COMPLETE"
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"tranche": "EVENT_DRIVEN_HARDENING",
|
||||
"purpose": "Final authority / operation-identity hardening (Defects A/B/C)",
|
||||
"predecessor": "event_driven_gates.json (16/16, superseded by this recomputation)",
|
||||
"created_at": "2026-09-18T08:56:52Z",
|
||||
"gates": [
|
||||
{
|
||||
"gate_id": "H10_AGGREGATOR_LAG_RESILIENCE",
|
||||
"mandatory": true,
|
||||
"requirement": "status-200-but-stale AND status-404 both reconcile via runs/jobs evidence without weakening the gate",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H11_FRESH_PROCESS_OPERATION_ID_ATTACH",
|
||||
"mandatory": true,
|
||||
"requirement": "fresh process continues by operation_id alone",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H12_LIVE_BRANCH_FLOW",
|
||||
"mandatory": true,
|
||||
"requirement": "branch push -> CI via normal tool path",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H13_LIVE_PR_FLOW",
|
||||
"mandatory": true,
|
||||
"requirement": "PR checks -> merge via normal tool path",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H14_LIVE_MASTER_DEPLOY_FLOW",
|
||||
"mandatory": true,
|
||||
"requirement": "master CI -> deploy via normal tool path",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H15_NO_MANUAL_INTERNAL_APIS_IN_LIVE_PROOF",
|
||||
"mandatory": true,
|
||||
"requirement": "no manual ci_event_wait/normalizer/authority calls in the live window",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H16_REGRESSION_AND_ADVERSARIAL_TESTS",
|
||||
"mandatory": true,
|
||||
"requirement": "A-J identity/adversarial matrix + existing suites green",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H17_RAW_SELF_AUDIT",
|
||||
"mandatory": true,
|
||||
"requirement": "raw trace outranks report; every occurrence classified",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H18_FINAL_RUNTIME_CONFIG_CONVERGENCE",
|
||||
"mandatory": true,
|
||||
"requirement": "git==release==deployed==runtime==healthy",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H1_OPERATION_ID_FIRST_CONTINUATION",
|
||||
"mandatory": true,
|
||||
"requirement": "ContinuePendingOperation(operation_id) is the canonical API; mechanical truth comes from the durable record",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H2_PENDING_OPERATION_OWNS_TARGET_IDENTITY",
|
||||
"mandatory": true,
|
||||
"requirement": "PendingOperation owns sha/ref/target; model hints never retarget",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H3_MODEL_SHA_NON_AUTHORITATIVE",
|
||||
"mandatory": true,
|
||||
"requirement": "wrong/typo model sha + bound op -> stored sha used; mismatch logged; no silent retarget",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H4_AUTHORITY_SHA_EXPLICIT_END_TO_END",
|
||||
"mandatory": true,
|
||||
"requirement": "sha propagates explicitly through ci_event_wait -> op_authority_verdict -> deploy_qualify -> Gitea calls; no env dependence in the canonical path",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H5_AMBIENT_ENV_CANNOT_CHANGE_VERDICT",
|
||||
"mandatory": true,
|
||||
"requirement": "stale/wrong/missing DEPLOY_SHA cannot change an explicitly identified operation's verdict",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H6_EVENT_TERMINAL_RESUME_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "terminal discovered via event before any deadline",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H7_WATCHDOG_TERMINAL_RECOVERY_PROVEN",
|
||||
"mandatory": true,
|
||||
"requirement": "lost event: watchdog authority reread discovers terminal and resumes without hard deadline",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H8_DEADLINE_NOT_NORMAL_SUCCESS_TRIGGER",
|
||||
"mandatory": true,
|
||||
"requirement": "DEADLINE_DRIVEN_TERMINAL_DISCOVERY_COUNT=0 in clean window",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
},
|
||||
{
|
||||
"gate_id": "H9_AUTHORITY_MONOTONICITY",
|
||||
"mandatory": true,
|
||||
"requirement": "terminal success never oscillates back to pending for the same operation",
|
||||
"status": "UNRUN",
|
||||
"evidence": "",
|
||||
"blocker": "",
|
||||
"waiver_reason": "",
|
||||
"waived_by": "",
|
||||
"declared_at": "2026-09-18T08:56:52Z",
|
||||
"resolved_at": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -185,6 +185,106 @@ def test_timing_provenance_order():
|
||||
|
||||
# ── observability ───────────────────────────────────────────────────────────
|
||||
|
||||
# ── H-PATCH: identity & authority hardening ────────────────────────────────
|
||||
|
||||
REAL = "a" * 40
|
||||
TYPO = "b" * 40
|
||||
|
||||
|
||||
def test_wrong_sha_bound_op_wins(isolated, monkeypatch):
|
||||
"""H3/A: model sha is a typo; single bound PendingOperation wins."""
|
||||
_put(isolated, "ci-bound", REAL, "branch_ci", normalized=True)
|
||||
r = ai.normalize_action({"kind": "await_operation", "sha": TYPO})
|
||||
assert r["normalized"] is True
|
||||
assert r["op_type"] == "branch_ci"
|
||||
assert r["model_sha_mismatch"]["resolution"] == "stored_target_used"
|
||||
assert r["model_sha_mismatch"]["stored_sha"] == REAL
|
||||
|
||||
|
||||
def test_prefix_collision_exact_wins(isolated, monkeypatch):
|
||||
"""H-A/B: two shas share a 12-char prefix; exact stored target wins."""
|
||||
_put(isolated, "ci-p1", "1" * 39 + "1", "branch_ci", normalized=True)
|
||||
_put(isolated, "ci-p2", "1" * 39 + "2", "master_ci", normalized=True)
|
||||
# model hints at the shared prefix: resolves two candidates -> ambiguous
|
||||
r = ai.normalize_action({"kind": "await_operation", "sha": "1" * 12})
|
||||
assert r.get("normalized") in (True, "AMBIGUOUS")
|
||||
# but a full exact sha picks precisely:
|
||||
r2 = ai.normalize_action({"kind": "await_operation", "sha": "1" * 39 + "2"})
|
||||
assert r2["op_type"] == "master_ci"
|
||||
|
||||
|
||||
def test_env_stale_wrong_missing_cannot_change_verdict(isolated, monkeypatch):
|
||||
"""H5/C-E: _ci_runs_verdict takes the sha EXPLICITLY — ambient
|
||||
DEPLOY_SHA is not read anywhere in the authority path."""
|
||||
import ci_event_wait as cw
|
||||
src = (REPO / "scripts" / "ci_event_wait.py").read_text()
|
||||
# no env read remains in the runs/authority path
|
||||
assert 'os.environ.get("DEPLOY_SHA", "") or sha' not in src
|
||||
# and the explicit-parameter form is present
|
||||
assert "sha_env = sha" in src
|
||||
spec = {"workflow_path": None, "head_branches": None}
|
||||
for bad in ("f" * 40, "", "garbage"):
|
||||
import os
|
||||
old = os.environ.get("DEPLOY_SHA")
|
||||
if bad:
|
||||
os.environ["DEPLOY_SHA"] = bad
|
||||
else:
|
||||
os.environ.pop("DEPLOY_SHA", None)
|
||||
try:
|
||||
import ci_event_wait as cw
|
||||
assert cw._ci_runs_verdict(spec, REAL) == "pending"
|
||||
finally:
|
||||
if old:
|
||||
os.environ["DEPLOY_SHA"] = old
|
||||
else:
|
||||
os.environ.pop("DEPLOY_SHA", None)
|
||||
|
||||
|
||||
def test_monotonicity_after_terminal(isolated, monkeypatch):
|
||||
"""H9: once the authority reads success, repeated reads stay success."""
|
||||
import ci_event_wait as cw
|
||||
monkeypatch.setattr(cw, "authority_verdict", lambda sha="": "success")
|
||||
monkeypatch.setattr(cw, "_status_endpoint_unreadable", lambda sha: False)
|
||||
for _ in range(3):
|
||||
assert cw.normalize_verdict(cw.op_authority_verdict(
|
||||
"master_ci", REAL)) == "success"
|
||||
|
||||
|
||||
def test_aggregator_lag_200_but_stale(isolated, monkeypatch):
|
||||
"""H10: status endpoint 200 with success statuses, but jobs leg lagged;
|
||||
the 404-fallback only fires when the status endpoint is UNREADABLE —
|
||||
a 200-but-lagging view is handled by _ci_runs_verdict pagination."""
|
||||
import ci_event_wait as cw
|
||||
monkeypatch.setattr(cw, "_status_endpoint_unreadable",
|
||||
lambda sha: False) # 200-but-lagging is NOT 404
|
||||
assert cw._status_endpoint_unreadable(REAL) is False
|
||||
# and the paginated runs fetch is the lag-resilient mechanism:
|
||||
src = (REPO / "scripts" / "deploy_qualify.py").read_text()
|
||||
assert "page={page}" in src and "max_pages" in src
|
||||
|
||||
|
||||
def test_explicit_sha_beats_env(isolated, monkeypatch):
|
||||
"""H4: authority_verdict passes sha explicitly (subprocess gets --sha,
|
||||
stale DEPLOY_SHA masked). Static contract check + behavioral probe."""
|
||||
src = (REPO / "scripts" / "ci_event_wait.py").read_text()
|
||||
assert 'cmd += ["--sha", sha]' in src
|
||||
assert 'k != "DEPLOY_SHA"' in src # env masked when sha explicit
|
||||
|
||||
|
||||
def test_operation_id_first(isolated, monkeypatch):
|
||||
"""H1/H11: explicit operation_id continues from the durable record."""
|
||||
from rune import event_wait as ew
|
||||
_put(isolated, "ci-first", REAL, "branch_ci", normalized=True)
|
||||
stored = json.loads((isolated / "ci-first.json").read_text())
|
||||
monkeypatch.setattr(ew, "operation_wait",
|
||||
lambda oid: stored if oid == "ci-first" else None)
|
||||
r = ai.normalize_action({"kind": "await_operation",
|
||||
"operation_id": "ci-first"})
|
||||
assert r["normalized"] is True
|
||||
assert r["matched_by"] == "explicit_id"
|
||||
assert r["op_type"] == "branch_ci"
|
||||
|
||||
|
||||
def test_observability_record(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(ai, "_SCRIPT_DIR", tmp_path)
|
||||
rec = {"normalized": True, "action": "ContinuePendingOperation",
|
||||
|
||||
@@ -77,7 +77,7 @@ def _post_when_registered(wr_daemon: Daemon, op: str, payload: dict,
|
||||
|
||||
def test_16c_event_wake_resumes_and_authority_decides(wr_daemon, monkeypatch):
|
||||
mod = _load_ci_wait_module()
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "success")
|
||||
monkeypatch.setattr(mod, "ci_alive", lambda: False)
|
||||
payload = {"operation_id": f"ci-{CI_SHA}", "provider": "gitea",
|
||||
"event_type": "ci_completed", "sha": CI_SHA, "exit_code": 0,
|
||||
@@ -99,7 +99,7 @@ def test_16c_event_payload_is_never_truth(wr_daemon, monkeypatch):
|
||||
mod = _load_ci_wait_module()
|
||||
# the event says success (exit_code 0); canonical authority says the
|
||||
# CI is RED — the payload must never decide the verdict
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "failure")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "failure")
|
||||
monkeypatch.setattr(mod, "ci_alive", lambda: False)
|
||||
payload = {"operation_id": f"ci-{CI_SHA}", "provider": "gitea",
|
||||
"event_type": "ci_completed", "sha": CI_SHA, "exit_code": 0,
|
||||
@@ -111,7 +111,7 @@ def test_16c_event_payload_is_never_truth(wr_daemon, monkeypatch):
|
||||
|
||||
def test_16d_event_resume_has_no_sleep_floor(wr_daemon, monkeypatch):
|
||||
mod = _load_ci_wait_module()
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "success")
|
||||
monkeypatch.setattr(mod, "ci_alive", lambda: False)
|
||||
payload = {"operation_id": f"ci-{CI_SHA}", "provider": "gitea",
|
||||
"event_type": "ci_completed", "sha": CI_SHA, "exit_code": 0,
|
||||
@@ -130,7 +130,7 @@ def test_16d_lost_event_watchdog_recovers_from_authority(
|
||||
wr_daemon, monkeypatch):
|
||||
mod = _load_ci_wait_module()
|
||||
# NO event is ever posted (suppressed/lost); authority IS resolvable
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "success")
|
||||
monkeypatch.setattr(mod, "ci_alive", lambda: False)
|
||||
rc = mod.run(sha=CI_SHA, deadline_secs=4, daemon_url=wr_daemon.url())
|
||||
assert rc == 0
|
||||
@@ -143,7 +143,7 @@ def test_16d_lost_event_watchdog_recovers_from_authority(
|
||||
def test_16d_lost_event_with_no_verdict_fails_truthfully(
|
||||
wr_daemon, monkeypatch):
|
||||
mod = _load_ci_wait_module()
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "pending")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "pending")
|
||||
monkeypatch.setattr(mod, "ci_alive", lambda: False)
|
||||
rc = mod.run(sha=CI_SHA, deadline_secs=4, daemon_url=wr_daemon.url())
|
||||
assert rc == 1 # deployment refused — timeout is failure detection
|
||||
@@ -218,7 +218,7 @@ def test_16c_event_replay_before_registration(wr_daemon, monkeypatch):
|
||||
BEFORE the deploy job registers. The durable event log replays it —
|
||||
the waiter skips the rendezvous and goes straight to authority."""
|
||||
mod = _load_ci_wait_module()
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "success")
|
||||
monkeypatch.setattr(mod, "ci_alive", lambda: False)
|
||||
wr_daemon.post_event({"operation_id": f"ci-{CI_SHA}",
|
||||
"provider": "gitea", "event_type": "ci_completed",
|
||||
|
||||
@@ -68,7 +68,7 @@ def test_branch_ci_uses_own_runs_not_deploy_qualify(monkeypatch):
|
||||
|
||||
def test_master_ci_uses_deploy_qualify(monkeypatch):
|
||||
mod = _load()
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "success")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "success")
|
||||
assert mod.op_authority_verdict("master_ci", SHA) == "success"
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ def test_wrong_authority_never_forever_pending(monkeypatch):
|
||||
op's own runs still resolve the verdict — no deadlock."""
|
||||
mod = _load()
|
||||
# deploy authority says pending forever; branch runs say success
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda: "pending")
|
||||
monkeypatch.setattr(mod, "authority_verdict", lambda sha="": "pending")
|
||||
monkeypatch.setattr(mod, "_ci_runs_verdict", lambda spec, sha: "success")
|
||||
assert mod.op_authority_verdict("branch_ci", SHA) == "success"
|
||||
|
||||
|
||||
Reference in new issue
Block a user