Compare commits

...
Author SHA1 Message Date
Nick 8a8d3534c6 feat(async-intent): intent normalization at the execution boundary
CI Pipeline / best-practices (push) Successful in 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Successful in 43s
CI Pipeline / canary (push) Successful in 32s
Auto-label / auto-label (pull_request) Successful in 11s
deploy / deploy (push) Skipped
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / best-practices (pull_request) Successful in 5s
CI Pipeline / documentation (pull_request) Successful in 5s
CI Pipeline / canary (pull_request) Successful in 29s
CI Pipeline / lint (pull_request) Successful in 44s
CI Pipeline / type-check (pull_request) Successful in 30s
CI Pipeline / gitea-offline (pull_request) Successful in 20s
Spec gate (headless) / spec-gate (pull_request) Successful in 19s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 3m19s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
CI Pipeline / unit-tests (pull_request) Successful in 3m27s
CI Pipeline / required-checks (pull_request) Successful in 5s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 5s
Auto-version and Release / release (pull_request) Failing after 1m10s
MODEL OUTPUT = INTENT; RUNE CONTROLLER = EXECUTION SEMANTICS.

scripts/await_intent.py: deterministic resolver that derives canonical
op_type from stored normalized records / open PRs / git ancestry — model
supplied op_type and deadline_secs are recorded but NEVER used as policy
(git/Gitea truth outranks even legacy wait records). Sleep/check/poll
intent aimed at a known pending operation compiles to
ContinuePendingOperation; plain legitimate sleeps are untouched.
Timing provenance: USER_EXPLICIT/EXTERNAL_CONTRACT authoritative,
MODEL_INVENTED discarded. Ambiguity fails safe with structured
candidates. await_intent_cli.py = tool surface; compact JSONL
observability per normalization. await-operation.ts rewired to normalize
before dispatch (needs commit of harness tool separately).
2026-09-17 21:55:20 +01:00
conductor d0ec37925f chore(release): auto-bump version to 0.83.5 — earned by #334 (docs(memory-t0): external review report)
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 3m19s
CI Pipeline / lint (push) Successful in 42s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 4m9s
Promotion record: #334
2026-09-17 21:11:55 +01:00
conductor f9936fa244 Merge pull request 'docs(memory-t0): external review report' (#334) from memory-t0-external-report into master
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 11s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Successful in 31s
Auto-version and Release / release (push) Successful in 22s
CI Pipeline / unit-tests (push) Successful in 3m27s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 4s
deploy / deploy (push) Successful in 6m10s
2026-09-17 21:11:04 +01:00
Nick 04a277f22c docs(memory-t0): external review report (self-contained brief for third-party LLM review)
CI Pipeline / best-practices (push) Successful in 11s
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / canary (push) Successful in 36s
Auto-label / auto-label (pull_request) Successful in 4s
CI Pipeline / lint (push) Successful in 45s
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Successful in 33s
CI Pipeline / lint (pull_request) Successful in 41s
CI Pipeline / best-practices (pull_request) Successful in 5s
CI Pipeline / canary (pull_request) Successful in 34s
CI Pipeline / documentation (pull_request) Successful in 6s
CI Pipeline / gitea-offline (pull_request) Successful in 17s
Spec gate (headless) / spec-gate (pull_request) Successful in 16s
CI Pipeline / type-check (pull_request) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 3m25s
CI Pipeline / required-checks (push) Successful in 5s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
CI Pipeline / unit-tests (pull_request) Successful in 3m19s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 5s
Auto-version and Release / release (pull_request) Failing after 1m1s
2026-09-17 21:05:37 +01:00
conductor b2777d666f chore(release): auto-bump version to 0.83.4 — earned by #333 (chore(memory-t0): as-is memory inventory — MEMORY_T0_COMPLETE)
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 3m24s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 11s
deploy / deploy (push) Successful in 3m44s
Promotion record: #333
2026-09-17 20:17:10 +01:00
conductor 0982f7317f Merge pull request 'chore(memory-t0): as-is memory inventory — MEMORY_T0_COMPLETE' (#333) from memory-t0-asis-map into master
Auto-version and Release / release (push) Successful in 21s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 3m26s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 6m40s
2026-09-17 20:15:54 +01:00
Nick 9583678997 chore(memory-t0): as-is memory inventory — MEMORY_T0_COMPLETE (17/17 gates)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 10s
Auto-label / priority-gate (pull_request) Skipped
CI Pipeline / canary (push) Successful in 29s
deploy / deploy (push) Skipped
Auto-label / auto-label (pull_request) Successful in 6s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / best-practices (pull_request) Successful in 10s
CI Pipeline / documentation (pull_request) Successful in 6s
CI Pipeline / lint (pull_request) Successful in 50s
CI Pipeline / gitea-offline (pull_request) Successful in 16s
CI Pipeline / type-check (pull_request) Successful in 29s
CI Pipeline / canary (pull_request) Successful in 31s
CI Pipeline / lint (push) Successful in 42s
CI Pipeline / unit-tests (push) Successful in 3m19s
CI Pipeline / required-checks (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
CI Pipeline / unit-tests (pull_request) Successful in 3m26s
CI Pipeline / required-checks (pull_request) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 6s
Auto-version and Release / release (pull_request) Successful in 1m0s
Read-mostly archaeology: one canonical experiential store (memory.db:
899 entries, hybrid dense+BM25 recall, dedupe/supersede gate with 1465
receipts), derived knowledge.json (7965 items), dormant graph/shards/
temporal, harness-native duplicate-authority surface identified, INWARD
port verified as unbound-but-real contract. DEFECT FOUND (recorded, not
fixed): sleep-time systemd timer reads stale opencode.db — designed
transcript-mining loop broken; consolidation survives via per-turn
mandate. Preservation risks classified; fresh-worker answers embedded.
2026-09-17 20:10:03 +01:00
conductor 5db8b202d9 chore(release): auto-bump version to 0.83.3 — earned by #332 (chore(t21.5): acceptance V1_ACCEPTED — 17/17 gates, ledger readback)
Auto-version and Release / release (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 3m27s
CI Pipeline / lint (push) Successful in 49s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / type-check (push) Successful in 33s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
deploy / deploy (push) Successful in 5m12s
Promotion record: #332
2026-09-17 18:30:17 +01:00
conductor 78c0d97a43 Merge pull request 'chore(t21.5): acceptance V1_ACCEPTED — 17/17 gates, ledger readback' (#332) from t21.5-pending-class-fix into master
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 12s
CI Pipeline / canary (push) Successful in 39s
Auto-version and Release / release (push) Successful in 33s
CI Pipeline / lint (push) Successful in 50s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Successful in 33s
CI Pipeline / unit-tests (push) Successful in 3m25s
CI Pipeline / required-checks (push) Successful in 4s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Successful in 6m38s
2026-09-17 18:28:19 +01:00
conductor 551530c78c Merge branch 'master' into t21.5-pending-class-fix
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / lint (push) Successful in 50s
CI Pipeline / canary (push) Successful in 33s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / best-practices (pull_request) Successful in 8s
CI Pipeline / lint (pull_request) Successful in 43s
CI Pipeline / documentation (pull_request) Successful in 6s
CI Pipeline / gitea-offline (pull_request) Successful in 18s
CI Pipeline / type-check (pull_request) Successful in 31s
deploy / deploy (push) Skipped
Spec gate (headless) / spec-gate (pull_request) Successful in 15s
CI Pipeline / canary (pull_request) Successful in 35s
CI Pipeline / unit-tests (pull_request) Successful in 3m32s
CI Pipeline / required-checks (pull_request) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 6s
CI Pipeline / unit-tests (push) Successful in 3m23s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 5s
Auto-version and Release / release (pull_request) Failing after 1m11s
2026-09-17 18:22:01 +01:00
conductor 2b6a5a6b95 chore(release): auto-bump version to 0.83.2 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 17s
CI Pipeline / lint (push) Successful in 51s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / unit-tests (push) Successful in 3m26s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 5m18s
Promotion record: (unknown)
2026-09-17 17:52:32 +01:00
conductor e2e7b74a8e chore(release): auto-bump version to 0.83.2 — earned by #331 (fix(t21.5): pending-class re-arm — residual nonterminal-return generator closed)
Auto-version and Release / release (push) Successful in 14s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Successful in 7m44s
CI Pipeline / lint (push) Successful in 45s
CI Pipeline / unit-tests (push) Successful in 3m29s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Successful in 1s
Promotion record: #331
2026-09-17 17:51:11 +01:00
conductor db4ce7f6a6 Merge pull request 'fix(t21.5): pending-class re-arm — residual nonterminal-return generator closed' (#331) from t21.5-pending-class-fix into master
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / canary (push) Successful in 42s
CI Pipeline / type-check (push) Successful in 34s
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / lint (push) Successful in 39s
Auto-version and Release / release (push) Successful in 59s
CI Pipeline / unit-tests (push) Successful in 3m34s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 7s
deploy / deploy (push) Successful in 12m23s
2026-09-17 17:50:49 +01:00
12 changed files with 1298 additions and 6 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.83.1**
**Current release: v0.83.5**
**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
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.83.1"
version = "0.83.5"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+375
View File
@@ -0,0 +1,375 @@
"""await_intent.py — ASYNC CORRECTIVE PATCH: intent normalization at the
execution boundary.
PRINCIPLE: MODEL OUTPUT = INTENT. RUNE CONTROLLER = EXECUTION SEMANTICS.
The model may express continuation intent in any quality — `await_operation
(deadline_secs=1200)`, `await-operation [op_type=master_ci, sha=X]`,
"sleep 300 then check CI", "poll until done". When a matching
PendingOperation exists (or the deterministic resolver can derive the
operation truth from git/Gitea state), the controller executes the
CANONICAL continuation and discards model-supplied scheduling policy.
Authority provenance for timing:
USER_EXPLICIT / EXTERNAL_CONTRACT / TEST_HARNESS /
GOVERNED_OPERATION_POLICY -> authoritative, retained
MODEL_INVENTED -> non-authoritative, ignored
This module owns NO storage: it reads the existing W/R wait store
(rune.event_wait) and git/Gitea state. The canonical continuation remains
scripts/ci_event_wait.py run() (the T21.5 single-logical-await).
"""
from __future__ import annotations
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Any
_SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
_REPO_SRC = str(_SCRIPT_DIR.parent / "src")
if _REPO_SRC not in sys.path:
sys.path.insert(0, _REPO_SRC)
WR_BRIDGE = Path(os.environ.get("RUNE_WR_ROOT", "/mnt/workdrive/wr-bridge"))
REPO = Path("/mnt/workdrive/issue161-harness")
GIT_TIMEOUT = 10
# Default policy deadline per operation type — GOVERNED_OPERATION_POLICY.
# The model's numbers never become policy; these are the controller's.
POLICY_DEADLINES = {
"branch_ci": 1800,
"pull_request_checks": 1800,
"master_ci": 1800,
"deploy": 900,
"release": 900,
}
VALID_OP_TYPES = ("branch_ci", "pull_request_checks", "master_ci",
"deploy", "release")
# ── timing provenance ───────────────────────────────────────────────────────
def classify_timing_provenance(model_deadline: Any = None,
user_deadline: Any = None,
contract_deadline: Any = None) -> tuple[int, str]:
"""Resolve the effective deadline + its provenance.
Authority order: USER_EXPLICIT > EXTERNAL_CONTRACT > TEST_HARNESS >
GOVERNED_OPERATION_POLICY. MODEL_INVENTED values are recorded but
NEVER used as policy.
"""
if model_deadline is not None:
try:
model_deadline = int(model_deadline)
except (TypeError, ValueError):
model_deadline = None
if user_deadline is not None:
return int(user_deadline), "USER_EXPLICIT"
if contract_deadline is not None:
return int(contract_deadline), "EXTERNAL_CONTRACT"
return (model_deadline or 0), "MODEL_INVENTED" # caller discards this
# ── PendingOperation lookup over the existing W/R wait store ────────────────
def _load_wait_records() -> list[dict]:
"""All durable wait records from the bridge store (read-only)."""
out = []
waits = WR_BRIDGE / "waits"
if not waits.is_dir():
return out
for f in sorted(waits.glob("*.json")):
try:
rec = json.loads(f.read_text())
rec["_file"] = f.name
out.append(rec)
except (OSError, ValueError):
continue
return out
def _rec_operation_id(rec: dict) -> str:
return str((rec.get("ext") or {}).get("operation_id")
or rec.get("operation_id") or "")
def _rec_sha(rec: dict) -> str:
return str(rec.get("sha") or "")
def pending_operations(sha: str = "", op_type: str = "") -> list[dict]:
"""Pending (non-terminal) operations, optionally filtered."""
terminal = {"COMPLETED", "FAILED", "CLOSED", "CANCELLED", "EXPIRED"}
out = []
for rec in _load_wait_records():
status = str(rec.get("status", "")).upper()
if status in terminal:
continue
if sha and _rec_sha(rec) != sha:
continue
out.append(rec)
return out
def find_op_type_for_sha(sha: str) -> str | None:
"""The op_type a prior canonical wait already recorded for this SHA
(objective text embeds the type: 'event-driven {op_type} qualification')."""
for rec in _load_wait_records():
if _rec_sha(rec) != sha:
continue
obj = str(rec.get("continuation", {}).get("objective", ""))
m = re.search(r"event-driven (\w+) qualification", obj)
if m and m.group(1) in VALID_OP_TYPES:
return m.group(1)
return None
# ── deterministic op_type derivation from repo/Gitea truth ─────────────────
def _git(*args: str) -> str:
try:
r = subprocess.run(["git", "-C", str(REPO), *args],
capture_output=True, text=True, timeout=GIT_TIMEOUT)
return r.stdout.strip()
except (OSError, subprocess.TimeoutExpired):
return ""
def sha_branch(sha: str) -> str:
"""Branch(es) containing this commit in the local repo."""
return _git("branch", "--all", "--contains", sha)
def sha_on_master(sha: str) -> bool:
out = _git("branch", "--contains", sha)
return any(l.strip().endswith("master") or l.strip() == "master"
for l in out.splitlines())
def open_pr_for_sha(sha: str) -> int | None:
"""Is there an OPEN PR whose head SHA is this one? (Gitea API, read-only.)"""
try:
sys.path.insert(0, str(_SCRIPT_DIR))
import gitea_auth # noqa: E402
st, prs = gitea_auth.api("/pulls?state=open&limit=50")
if st != 200:
return None
for p in prs:
hs = str(p.get("head") or {}).get("sha") if isinstance(
p.get("head"), dict) else None
if hs and str(hs).startswith(sha[:12]):
return int(p.get("number"))
except Exception: # noqa: BLE001 — resolver must never crash the tool
return None
return None
def derive_op_type(sha: str) -> tuple[str, str]:
"""Deterministically derive the canonical op_type for a SHA.
Returns (op_type, evidence). Order (A-patch §2 amendment):
1. POST-PATCH normalized record (ext.intent_normalized=true) — true
stored operation truth, wins outright;
2. open PR for the sha -> pull_request_checks;
3. sha ancestor of master -> master_ci;
4. sha on a feature branch -> branch_ci;
5. legacy (pre-patch) records only CONFIRM, never override — they
were created from model-supplied defaults and cannot be trusted.
Never trusts model input.
"""
for rec in _load_wait_records():
if _rec_sha(rec) != sha:
continue
if (rec.get("ext") or {}).get("intent_normalized"):
obj = str(rec.get("continuation", {}).get("objective", ""))
m = re.search(r"event-driven (\w+) qualification", obj)
if m and m.group(1) in VALID_OP_TYPES:
return m.group(1), "normalized_record:" + m.group(1)
pr = open_pr_for_sha(sha)
if pr:
return "pull_request_checks", f"open_pr:#{pr}"
if sha_on_master(sha):
return "master_ci", "commit_ancestor_of_master"
return "branch_ci", "commit_on_feature_branch"
# ── intent normalization ────────────────────────────────────────────────────
def normalize_action(action: dict, *,
model_deadline: Any = None,
user_deadline: Any = None,
contract_deadline: Any = None) -> dict:
"""Normalize a model continuation action into the canonical continuation.
action: {kind: await_operation|sleep_poll|check_status|unknown,
sha?: str, operation_id?: str, op_type?: str, raw?: str}
Returns:
{normalized: bool, action: "ContinuePendingOperation", operation_id,
op_type, authority, deadline_secs, deadline_provenance,
model_deadline_ignored, model_op_type_ignored, evidence}
or {normalized: False, reason: ...} / {normalized: "AMBIGUOUS", candidates}
"""
sha = str(action.get("sha") or "")
op_id = str(action.get("operation_id") or "")
model_op_type = action.get("op_type")
model_deadline_ignored = model_deadline is not None
model_op_type_ignored = model_op_type is not None
# 1. explicit operation_id wins
if op_id:
from rune import event_wait as ew # noqa: E402
rec = ew.operation_wait(op_id)
if rec is None:
return {"normalized": False,
"reason": f"operation_id {op_id} has no wait record"}
return _canonical_from_record(rec, model_deadline_ignored,
model_op_type_ignored, "explicit_id",
user_deadline, contract_deadline)
# 2. resolve by sha / active operation
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,
model_op_type_ignored, "pending_match",
user_deadline, contract_deadline)
# 3. no stored record: derive truth deterministically
if not sha:
return {"normalized": False,
"reason": "no pending operation and no sha to resolve"}
op_type, evidence = derive_op_type(sha)
return {
"normalized": True,
"action": "ContinuePendingOperation",
"operation_id": f"ci-{sha}",
"op_type": op_type,
"authority": "ci_runs" if op_type in ("branch_ci",
"pull_request_checks")
else "deploy_qualify",
"deadline_secs": POLICY_DEADLINES[op_type],
"deadline_provenance": "GOVERNED_OPERATION_POLICY",
"model_deadline_ignored": model_deadline_ignored,
"model_op_type_ignored": model_op_type_ignored,
"evidence": evidence,
}
def _canonical_from_record(rec: dict, model_deadline_ignored: bool,
model_op_type_ignored: bool, how: str,
user_deadline: Any, contract_deadline: Any) -> dict:
op_id = _rec_operation_id(rec)
obj = str(rec.get("continuation", {}).get("objective", ""))
m = re.search(r"event-driven (\w+) qualification", obj)
recorded = m.group(1) if (m and m.group(1) in VALID_OP_TYPES) else None
rec_sha = _rec_sha(rec)
if (rec.get("ext") or {}).get("intent_normalized") or how == "explicit_id":
# normalized record or explicit id: stored truth wins
op_type = recorded or "master_ci"
else:
# legacy record: git/Gitea derivation outranks the model-era default
op_type, _ev = derive_op_type(rec_sha) if rec_sha else \
(recorded or "master_ci", "no_sha")
deadline, prov = classify_timing_provenance(
user_deadline=user_deadline, contract_deadline=contract_deadline)
if prov == "MODEL_INVENTED" or not deadline:
deadline = POLICY_DEADLINES.get(op_type, 1800)
prov = "GOVERNED_OPERATION_POLICY"
return {
"normalized": True,
"action": "ContinuePendingOperation",
"operation_id": op_id,
"op_type": op_type,
"authority": "ci_runs" if op_type in ("branch_ci",
"pull_request_checks")
else "deploy_qualify",
"deadline_secs": deadline,
"deadline_provenance": prov,
"model_deadline_ignored": model_deadline_ignored,
"model_op_type_ignored": model_op_type_ignored,
"matched_by": how,
}
def normalize_bash(command: str, *, sha_hint: str = "") -> dict | None:
"""Recognize sleep/check/poll intent aimed at a known pending operation.
Returns a normalization record if the command's semantic intent is
continuation of a known external operation; None if the command is
legitimate local timing (short sync, user delay, backoff) with no
pending operation to continue. NEVER returns an executed sleep.
"""
if not command:
return None
cmd = command.strip()
# long sleep + (status-check language or an explicit sha hint)
sleep_m = re.search(r"\bsleep\s+(\d{2,5})\b", cmd)
if not sleep_m:
return None
secs = int(sleep_m.group(1))
check_intent = bool(re.search(
r"\b(ci|deploy|release|status|check|poll|pipeline|run)\b", cmd,
re.IGNORECASE))
sha = sha_hint or ""
m = re.search(r"\b([0-9a-f]{40}|[0-9a-f]{12})\b", cmd)
if m:
sha = m.group(1)
if not check_intent and not sha:
return None # plain sleep, no continuation intent
cands = pending_operations(sha=sha) if sha else pending_operations()
if not cands and not sha:
return None
if len(cands) > 1:
return {"normalized": "AMBIGUOUS",
"candidates": [_rec_operation_id(c) for c in cands],
"raw_command_kind": "sleep_poll"}
rec = cands[0] if cands else None
if rec is not None:
norm = _canonical_from_record(rec, True, False, "sleep_poll_intent",
None, None)
else:
op_type, evidence = derive_op_type(sha)
norm = {
"normalized": True, "action": "ContinuePendingOperation",
"operation_id": f"ci-{sha}", "op_type": op_type,
"authority": "ci_runs" if op_type in ("branch_ci",
"pull_request_checks")
else "deploy_qualify",
"deadline_secs": POLICY_DEADLINES[op_type],
"deadline_provenance": "GOVERNED_OPERATION_POLICY",
"model_deadline_ignored": True, "model_op_type_ignored": False,
"evidence": evidence,
}
norm["raw_command_kind"] = "sleep_poll"
norm["raw_sleep_seconds_suppressed"] = secs
return norm
def emit_normalized_record(record: dict) -> None:
"""Compact structured observability — one JSONL line per normalization.
No payloads, no secrets."""
try:
log = _SCRIPT_DIR.parent / "store" / "await_intent_log.jsonl"
log.parent.mkdir(parents=True, exist_ok=True)
slim = {k: record.get(k) for k in (
"normalized", "action", "operation_id", "op_type", "authority",
"deadline_secs", "deadline_provenance", "model_deadline_ignored",
"model_op_type_ignored", "matched_by", "evidence",
"raw_command_kind", "raw_sleep_seconds_suppressed", "reason",
"candidates") if k in record}
slim["at"] = __import__("time").strftime(
"%Y-%m-%dT%H:%M:%SZ", __import__("time").gmtime())
with open(log, "a") as f:
f.write(json.dumps(slim, sort_keys=True) + "\n")
except OSError:
pass
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""await_intent_cli.py — CLI surface for the intent normalization layer.
Used by the await-operation harness tool so the MODEL's scheduling hints
never reach the wait engine. Prints one JSON line.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import await_intent # noqa: E402
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
sub = ap.add_subparsers(dest="cmd", required=True)
n = sub.add_parser("normalize", help="normalize a continuation intent")
n.add_argument("--sha", default="")
n.add_argument("--operation-id", default="")
n.add_argument("--model-op-type", default=None)
n.add_argument("--model-deadline", default=None)
n.add_argument("--user-deadline", default=None,
help="USER_EXPLICIT deadline (authoritative)")
n.add_argument("--contract-deadline", default=None,
help="EXTERNAL_CONTRACT deadline (authoritative)")
args = ap.parse_args()
action = {"kind": "await_operation", "sha": args.sha,
"operation_id": args.operation_id,
"op_type": args.model_op_type}
model_deadline = (int(args.model_deadline)
if args.model_deadline else None)
user_deadline = (int(args.user_deadline)
if args.user_deadline else None)
contract_deadline = (int(args.contract_deadline)
if args.contract_deadline else None)
record = await_intent.normalize_action(
action, model_deadline=model_deadline,
user_deadline=user_deadline, contract_deadline=contract_deadline)
await_intent.emit_normalized_record(record)
print(json.dumps(record, sort_keys=True))
return 0
if __name__ == "__main__":
sys.exit(main())
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.83.1"
__version__ = "0.83.5"
# 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
+1 -1
View File
@@ -231,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.83.1"' in rune_version,
'__version__ = "0.83.5"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+2 -2
View File
@@ -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.83.1
Version: v0.83.5
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.83.1 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.83.5 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+164
View File
@@ -0,0 +1,164 @@
{
"tranche": "ASYNC_INTENT_NORMALIZATION",
"purpose": "Intent normalization at the execution boundary \u2014 model emits intent, controller owns execution semantics",
"predecessor": "T21.5 (guards retired) + Memory T0 (complete)",
"created_at": "2026-09-17T20:43:47Z",
"gates": [
{
"gate_id": "A10_LIVE_MASTER_DEPLOY_PROOF",
"mandatory": true,
"requirement": "live master CI -> deploy with normalization evidence",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A11_REGRESSION_TESTS",
"mandatory": true,
"requirement": "deterministic test matrix green; existing wait tests unbroken",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A12_RAW_SELF_AUDIT",
"mandatory": true,
"requirement": "post-fix stream classified; no model scheduling choice affected execution",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A13_FINAL_RUNTIME_CONVERGENCE",
"mandatory": true,
"requirement": "git==release==deployed==runtime==healthy",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A1_INTENT_NORMALIZATION_BOUNDARY_PROVEN",
"mandatory": true,
"requirement": "earliest deterministic boundary identified + normalization implemented there (not prompt, not guard)",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A2_PENDING_OPERATION_IS_WAIT_AUTHORITY",
"mandatory": true,
"requirement": "stored operation truth (op_type/authority/timing) wins over model args",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A3_MODEL_TIMING_NON_AUTHORITATIVE",
"mandatory": true,
"requirement": "MODEL_DURATION_ACTUALLY_USED_COUNT=0 for canonical flows; provenance recorded",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A4_MODEL_OP_TYPE_NON_AUTHORITATIVE",
"mandatory": true,
"requirement": "MODEL_SUPPLIED_OP_TYPE_COUNT_USED=0; stored/derived authority wins",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A5_SLEEP_POLL_INTENT_NORMALIZED",
"mandatory": true,
"requirement": "sleep/check/poll intent for a known pending op compiles to canonical continuation; literal sleep not executed",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A6_EXPLICIT_REAL_DEADLINES_PRESERVED",
"mandatory": true,
"requirement": "USER_EXPLICIT and CONTRACT deadlines retained with provenance",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A7_AMBIGUITY_FAILS_SAFE",
"mandatory": true,
"requirement": "multiple genuinely ambiguous pending ops -> structured ambiguity, no guess",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A8_LIVE_BRANCH_CI_PROOF",
"mandatory": true,
"requirement": "live branch push -> CI with normalization evidence",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
},
{
"gate_id": "A9_LIVE_PR_MERGE_PROOF",
"mandatory": true,
"requirement": "live PR checks -> merge with normalization evidence",
"status": "UNRUN",
"evidence": "",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T20:43:47Z",
"resolved_at": ""
}
]
}
+161
View File
@@ -0,0 +1,161 @@
{
"tranche": "MEMORY_T0",
"generated_at": "2026-09-17T20:10:00Z",
"method": "read-mostly archaeology; sandboxed write-probes on in-memory SQLite copies only; production stores opened read-only (mode=ro); zero production records modified",
"runtime_reachability_legend": {
"SOURCE_EXISTS": "code present, no importer found",
"CONSTRUCTED": "instantiated on a runtime path",
"WIRED": "connected into a live call chain",
"RUNTIME_REACHABLE": "reachable from the production entrypoint (TUI/launcher or harness plugin)",
"OBSERVED_USED": "runtime effect observed (fresh persisted data, live process, journal logs)",
"CANONICAL": "owner of truth for its concern",
"DEAD_OR_LEGACY": "superseded; no live writer/reader",
"HARNESS_OR_TEST_ONLY": "exists for CI/tests/harness copies only"
},
"production_entrypoints": [
{
"entrypoint": "Rune TUI (deployed launcher ~/.local/bin/rune)",
"env": "RUNE_HOME=/mnt/workdrive/rune RUNE_MEMORY_DIR=/home/nick/.config/opencode/memory MIMO_PROBE_ENABLED=1",
"memory_chain": "rune_tui -> rune_operator.send_task -> ContextBroker.assemble (collect/measure/admit) -> MemorySelector.select_for_task (6 sources, floor 0.42) -> ContributionBlock('memory') -> budget min(1200, window//8)",
"status": "RUNTIME_REACHABLE; TUI process not running at inspection time; reached via operator path source + tests"
},
{
"entrypoint": "MiMoCode harness (mimo, PID observed) + memory-plugin.ts",
"memory_chain_per_turn": [
"1. ALWAYS-ON: warm_router.py always --json -> digest injected every turn",
"2. KEYWORD: warm_router.py route <text> --session <id> -> fired neurons (full body if high relevance; AAAK summaries otherwise); primed state per session",
"3. WAKE-UP: memory_store.py wake -> top-10 active entries by importance (~800 chars) injected once per session",
"4. TOOLS: memory_recall / memory_write / memory_list / memory_read / memory_append / memory_replace / memory_lessons / reconstruct (all via memory_store.py CLI)",
"5. session.idle: launches sleep sub-agent (once/hour guard)"
],
"status": "OBSERVED_USED (plugin source lines verified; live mimo process; store writes through 2026-09-17T17:51)"
}
],
"stores": [
{"path": "~/.config/opencode/memory/store/memory.db", "size": "456.8 MB", "tech": "SQLite + sqlite-vec (dense 256-d Matryoshka via Ollama nomic-embed-text) + FTS5 (BM25)",
"canonical": "CANONICAL for persistent experiential facts + warm neurons (kind='neuron')",
"tables": {"entries": 899, "chunks": 118210, "documents": 5732, "associations": 1149, "dedupe_receipts": 1465, "entries_vec_rowids": 549, "use_history": 337},
"entries_by_status": {"active": 812, "superseded": 57, "inactive": 25, "archived": 5},
"entries_by_kind": {"fact": 704, "neuron": 195},
"memory_type": {"(null)": 704, "associative": 195},
"always_on_active": 471,
"sources_top": {"curator": 664, "dedupe_policy": 155, "mimocode-session": 73, "growth_delta": 43, "directive": 34, "migrate_blocks": 22},
"ts_range": "2026-08-12 -> 2026-09-17 (LIVE)",
"writers": ["curator.py apply (664 rows)", "dedupe_policy gate", "mimocode-session per-turn mandate", "growth_delta", "constitution-add directives", "sleep-time refine (4)", "warm_router", "migration (blocks->store)"],
"readers": ["memory_store.recall (dense+BM25 RRF, floor 0.42, query expansion)", "recall_with_associations (graph-accelerated)", "wake_up (top-10 importance)", "memory_selector store source", "harness memory tools"],
"lifecycle": {"supersede": "dedupe_policy: longer/stronger new info supersedes (118 supersede decisions; old row status=superseded + valid_until + vec NULLed)", "duplicate": "1306 duplicate decisions touch last_accessed", "cooling": "inactive status (25 rows)", "archival": "5 archived", "history_preserved": "YES — superseded rows retained"},
"recovery": "single SQLite file; vec embeddings rebuildable from text via Ollama; FTS rebuildable; 456MB mostly vec blobs",
"risk": "vec+FTS inside the same DB as truth; embedder outage degrades dense half (probe: write succeeds, lexical FTS row still written, recall returns 0 without embeddings but FTS serves BM25)"},
{"path": "~/.config/opencode/memory/graph/graph.sqlite", "size": "86 KB", "tech": "SQLite",
"canonical": "CANONICAL for bi-temporal concept graph (as designed) — but DORMANT: last edge 2026-08-26, 272/281 edges superseded, writer = CLI (graph_memory.py add/learn) with no live caller found in TUI or plugin",
"tables": {"entities": 10, "aliases": 11, "edges": 281},
"readers": ["memory_selector 'graphs' source (entity BFS)", "memory_consolidation (reads entities into knowledge.json)", "/graph TUI stats"],
"writers": ["graph_memory.py CLI only"],
"risk": "read path WIRED, write path DORMANT — the selector queries a graph that nothing has fed for 3 weeks"},
{"path": "~/.rune/consolidation/knowledge.json", "size": "3.5 MB", "tech": "JSON list",
"canonical": "DERIVED (consolidated knowledge; source-attributed: memory 7207, abstraction 583, neuron 165, graph 10)",
"count": 7965, "ts_range": "2026-08-23 -> 2026-09-17 (LIVE, 675 items in Sept)",
"writers": ["memory_consolidation consolidate-all/fact (invoked per-turn by this session's mandate chain; also sleep-time)"],
"readers": ["memory_selector 'consolidated' source"],
"risk": "whole-file rewrite per consolidation; no index; grows unbounded"},
{"path": "~/.config/opencode/memory/abstraction.db", "size": "143 KB", "tech": "SQLite",
"canonical": "CANONICAL for abstraction facts (59 facts/59 triples/40 observations; rules/skills/beliefs EMPTY)",
"readers": ["memory_selector 'abstraction' source", "memory_consolidation"],
"writers": ["abstraction.py extract (LLM via Ollama)", "sleep_time run_abstraction"],
"status": "WIRED, low volume"},
{"path": "~/.config/opencode/memory/cache.db", "size": "1.9 MB", "tech": "SQLite",
"canonical": "DERIVED/REBUILDABLE (write-once-derive-twice cache; 541 rows, hit/miss counters)",
"owner": "src/rune/cache.py", "risk": "disposable"},
{"path": "~/.config/opencode/memory/temporal.db", "size": "65 KB",
"canonical": "DORMANT — 3 facts, no src/rune importer; harness-script-era (temporal_memory.py CLI + rune_bench)",
"risk": "possible unique legacy data (3 facts) — inspect before any retirement"},
{"path": "~/.config/opencode/memory/behavior.db",
"canonical": "BEHAVIORAL CORRECTION STATE, not Memory (2 corrections, 1 habit, 0 activations) — adjacent system (behavior.py)",
"status": "WIRED via behavior.py; near-empty"},
{"path": "~/.config/opencode/memory/auto_tasks.db + task_gate.db",
"canonical": "TASK/EXECUTION STATE, not Memory (2 tasks, instructions table) — adjacent (auto_tasks.py, task_gate.py)"},
{"path": "~/.config/opencode/memory/shards/*.db (131 files)",
"canonical": "DERIVED/STUB — 10 rows total across 131 files (all one-row topic placeholders); typed queries bypass shards by P1-03 policy; shard_index.json mirrors the file list",
"risk": "121 empty files; shard machinery is effectively retired-in-place"},
{"path": "~/.config/opencode/memory/warm/memory.db", "canonical": "DEAD — 0 bytes; warm neurons migrated into store (kind='neuron'); .pasture files retired (8 files, reader retired)"},
{"path": "~/.config/opencode/memory/index/warm_state.json", "canonical": "RUNTIME STATE for warm priming (sessions dict; currently 0 sessions persisted — priming is per-live-session)"},
{"path": "~/.local/share/mimocode/mimocode.db (1.2 GB) + ~/.local/share/mimocode/memory (4.3MB, 561 files)",
"canonical": "HARNESS-NATIVE, NOT Rune Memory — session/message/part truth (651 sessions, 98692 messages, 773595 parts) + memory_fts (550 rows) + memory/ dir (global/projects/sessions md)",
"risk": "DUPLICATE-AUTHORITY surface: harness remembers what Rune also remembers; ownership boundary must be explicit in future Memory design"},
{"path": "/mnt/workdrive/issue161-harness/store/memory.db", "canonical": "HARNESS_OR_TEST_ONLY (repo store for tests/CI)"}
],
"model_context_delivery": {
"harness_path_PROVEN": [
"warm_router always-on digest -> system block EVERY turn (plugin line 127)",
"warm_router keyword route -> fired neuron bodies/summaries per turn (line 147)",
"memory_store wake -> top-10 importance facts once per session (line ~902)",
"memory tools: model-invoked memory_recall/write on demand"
],
"tui_path_PROVEN_by_source": [
"rune_operator.send_task -> ContextBroker.assemble -> MemorySelector.select_for_task -> 'memory' block (budget min(1200, window//8), floor 0.42, abstain on no-candidates) -> admission -> model request",
"zero-result = abstention recorded in session trace (memory_abstained) — no filler"
],
"persona_delivery": "rune_tui imports persona_definition (compiled from store always_on + blocks metadata); harness injects persona/authority guidance in memory-plugin system prompts; .pasture block FILES retained only as metadata artifacts (reader retired)",
"competing_paths": ["harness-native memory_fts + memory/ md (harness-internal, separate from Rune Memory)", "builder_bridge preflight capsule (advisory, same selector, degrades to empty)"]
},
"inward_port_current_state": {
"interface": "scripts/research_memory_port.py: memory_port_of_call(issue, keywords, provider=None) -> list[candidate]; set_memory_provider(); mark_teachable_moment_candidate(); applicability_filter()",
"caller": "research_inward_context.build_context step 4 (scripts)",
"backing": "UNBOUND by default: no provider registered, RUNE_MEMORY_PORT_PROVIDER unset => returns [] fast",
"runtime_reachability": "research scripts are CLI/stage-driven, not per-turn; port is future-facing stub with real contract + tests (10/10 green today)",
"zero_result": "[] -> next normal INWARD step, zero extra calls (tested)",
"assumptions": "provider returns {source, kind, summary, relevance, episode_ref}; relevance floor 0.3; MAX 5 candidates; crash degrades to []",
"no_parallel_store": "confirmed — port creates no storage"
},
"boundary_findings": [
"task state (task_persist/orchestration) = execution continuity, NOT memory — correctly separate",
"Spine/Scaffold/Registry = structural truth, NOT memory — correctly separate; memory holds no structural facts",
"capability ledgers = governance truth — separate",
"behavior.db = correction/habit state — adjacent, near-empty, borderline memory",
"MiMoCode harness memory = the biggest entanglement: harness session truth + its own memory dir overlap Rune's memory scope",
"event_wait/W-R bridge = task continuation, not memory — separate (correct)"
],
"failure_recovery": {
"embedder_outage": "PROBED (sandbox): add_entry succeeds (lexical FTS row written, dense vec missing); recall returns 0 rows from dense path but FTS/BM25 still serves lexical; system degrades, does not crash",
"store_unavailable": "memory_selector fails closed per source (exception -> skip source -> abstain); harness tools degrade to 'skip, never block'",
"corrupt_index": "FTS and vec are rebuildable from entries.text (embeddings re-embeddable via Ollama; FTS rebuildable); knowledge.json rebuildable from sources",
"irreversible": "entries.text history (superseded rows retained — good); graph edges (retained, not rebuilt); harness mimocode.db session truth",
"sleep_time_DEFECT": "hourly systemd timer reads ~/.local/share/opencode/opencode.db (STALE 30.7 days, old opencode path) instead of ~/.local/share/mimocode/mimocode.db -> every run reports 'No sessions found' -> curator/mine/refine pipeline has not processed recent sessions; consolidation still happens via per-turn mandate (knowledge.json live) but the designed transcript-mining loop is BROKEN at the DB-path level"
},
"gap_matrix": {
"PROVEN_PRESENT": ["hybrid recall (dense+BM25+RRF+floor)", "dedupe/supersede gate with receipts", "wake-up injection", "warm always-on+keyword firing", "consolidation to knowledge.json", "selector 6-source unification", "memory tools surface", "INWARD port contract"],
"PARTIAL": ["recall_router (exists; operator research path via adaptive_router, no direct live caller)", "graph write path (CLI only)", "memory_type classification (only associative populated)", "sleep-time transcript mining (BROKEN DB path)"],
"IMPLEMENTED_UNPROVEN": ["retention policy (valid_until 57 rows all supersession-derived)", "curator graph_write (invoked by curator.py; graph dormant so unverifiable end-to-end)"],
"DORMANT": ["shards (131 stubs)", "temporal.db", "warm/memory.db", ".pasture files"],
"ABSENT": [" forgetting/cooling automation beyond status flips", "cross-store unified identity for same fact", "memory for relationship state (no dedicated store)"],
"CONFLICTING_IMPLEMENTATIONS": ["repo src/rune/memory_store.py vs harness scripts/memory_store.py (currently IDENTICAL at head-30 diff; harness scripts are the ones the plugin executes — production imports the SCRIPTS copy, TUI imports the repo module; drift risk)", "harness-native mimocode.db memory vs Rune memory.db"],
"UNKNOWN": []
},
"preservation_risk": {
"MUST_PRESERVE": ["memory.db entries (899 incl. 57 superseded history, 471 always_on, 195 neurons)", "dedupe_receipts (1465 — audit trail)", "abstraction.db facts", "graph.sqlite edges (historical)", "harness mimocode.db session truth"],
"LIKELY_PRESERVE": ["knowledge.json (rebuildable but provenance-rich)", "use_history (337)", "warm_state.json", "behavior.db"],
"DERIVED_REBUILDABLE": ["cache.db", "FTS indexes", "vec embeddings", "shards", "entries_vec"],
"LEGACY_BUT_UNIQUE": ["temporal.db 3 facts (inspect before retiring)", ".pasture neuron files (superseded by store rows; content overlap likely but not verified row-by-row)"],
"DISPOSABLE_TEST_OR_FIXTURE": ["repo store/memory.db", "warm/memory.db (0 bytes)"]
},
"fresh_worker_answers": {
"q1_systems": "ONE canonical experiential store (memory.db) + abstraction.db + knowledge.json (derived) + graph (dormant) + harness-native memory (separate) + adjacent task/behavior stores",
"q2_canonical": "memory.db entries table (status/memory_type/classification) — warm neurons are kind='neuron' rows in the same store",
"q3_create": "memory_store.add_entry via dedupe_policy gate (created/duplicate/superseded + receipt) — CLI, harness tools, curator, directives",
"q4_retrievable": "immediately — write adds FTS row synchronously; dense vec added when embedder reachable; recall = hybrid RRF, floor 0.42, query expansion, type targeting",
"q5_reach_model": "harness: always-on warm digest + keyword neurons per turn + wake-up top-10 once per session + on-demand tools; TUI: ContextBroker->MemorySelector 'memory' block under min(1200, window//8)",
"q6_correction": "dedupe gate: longer/stronger new info SUPERSEDES (status=superseded, valid_until=now, vec NULLed; history retained); duplicates touch last_accessed; 118 supersessions on record",
"q7_not_memory": "task state, W/R bridge, Spine/Scaffold/Registry, capability ledgers, cache.db, shards, harness session DB",
"q8_preserve": "memory.db + receipts + abstraction.db + graph history + harness session truth"
}
}
+128
View File
@@ -0,0 +1,128 @@
# Rune Memory T0 — As-Is Memory Inventory Report
*Prepared for external review. Self-contained: no prior context assumed.*
---
## 1. What this is
An archaeology report on the memory system of **Rune** — a self-hosted AI coding assistant / personal agent running on Linux (repo: private Gitea, deployed generations under `/mnt/workdrive/rune-code`, canonical worktree `/mnt/workdrive/issue161-harness`).
Rune just completed a long Scaffolding V1 programme (tranches T21.4/T21.5, accepted 2026-09-17 with 17/17 gates, deployed release v0.83.3). The first post-V1 phase is **Memory**. Its first tranche, **Memory T0**, was deliberately read-mostly: establish what memory machinery *actually exists today* before designing anything new.
Central rule of T0: never collapse `EXISTS IN SOURCE ≠ WIRED ≠ RUNTIME REACHABLE ≠ ACTUALLY USED ≠ CANONICAL AUTHORITY`.
**Verdict: MEMORY_T0_COMPLETE** — 17/17 gates PASS, `can_accept() = true`, merged as PR #333. Artifacts: `store/scaffold/memory_t0_gates.json` (gate object) and `store/scaffold/memory_t0_asis_map.json` (central map).
---
## 2. What Rune actually has today (the short answer)
One canonical experiential store, one derived knowledge file, several dormant subsystems, one harness-native duplicate, and a set of adjacent non-memory stores that are correctly separated.
### The canonical store
`~/.config/opencode/memory/store/memory.db` — 456.8 MB SQLite with **two retrieval engines side by side**:
- **Dense**: sqlite-vec, 256-dim Matryoshka-truncated embeddings via local Ollama (`nomic-embed-text`), content-hash cached.
- **Lexical**: FTS5/BM25.
`recall()` fuses both with reciprocal-rank fusion, applies a **0.42 relevance floor** (below it: abstain rather than inject distractors), supports query expansion via a lexicon, type targeting, and QPP-based abstention.
**Contents**: 899 entries (812 active / 57 superseded / 25 inactive / 5 archived), 118,210 chunks, 5,732 documents, 1,149 associative links, 337 use-history rows. Warm neurons are stored as `kind='neuron'` rows (195) in the same DB — a previous file-based system (`.pasture` markdown files) was migrated and retired.
**Supersession is real and receipted.** A dedupe policy gate sits on writes: similar new content either touches the existing row (`duplicate`), or — if longer/stronger — **supersedes** it (old row marked `status='superseded'`, `valid_until=now`, embedding nulled; history retained). Receipt ledger: 1,465 decisions (41 created / 1,306 duplicates / 118 superseded). Every row carries `source`, `method`, `confidence`, `temperature`, timestamps.
### Derived knowledge
`~/.rune/consolidation/knowledge.json` — 7,965 consolidated items (source-attributed: 7,207 from the store, 583 from abstraction, 165 from neurons, 10 from graph), actively growing (675 new in September). Read back into context via one of the selector's six sources. Whole-file rewrite per consolidation; rebuildable.
### Dormant subsystems (exist, wired for read, no live writer)
- **graph.sqlite** — a well-designed bi-temporal concept knowledge graph (typed nodes, `valid_from`/`valid_until` + `learned_at` edge semantics, supersession columns). 281 edges, **272 superseded**, last write 2026-08-26. The write path is a CLI; nothing in the live runtime feeds it. The read path (selector `graphs` source) queries a graph that hasn't been fed in three weeks.
- **shards** — 131 topic-partition DB files containing **10 rows total** (one-row topic placeholders); typed queries bypass shards by policy. Effectively retired-in-place.
- **temporal.db** — 3 facts, no importer in the repo's `src/rune` at all (harness-script-era relic).
- **warm/memory.db** — 0 bytes; dead path.
### Adjacent systems (correctly *not* memory)
Task state (durable task repo + orchestration), Spine/Project Scaffold/Project Registry (structural truth), capability/behaviour ledgers (governance), the W/R event-wait bridge (task continuation), `behavior.db` (corrections/habits — borderline, near-empty). T0 verified no experiential facts live in structural stores and no structural facts in memory stores.
### The duplicate-authority surface (the big architectural question)
The **MiMoCode harness itself** (the VSCode-like agent harness Rune runs inside) maintains its own memory: `mimocode.db` (1.2 GB — 651 sessions, 98,692 messages, 773,595 parts, plus its own FTS) and `~/.local/share/mimocode/memory` (4.3 MB of markdown). Two systems remember the same operational life. T0 flagged this as the primary authority-boundary question for future design — nothing was reconciled.
A second, smaller drift risk: the repo module `src/rune/memory_store.py` and the harness copy `~/.config/opencode/scripts/memory_store.py` are currently aligned, but the **harness plugin executes the scripts copy** while the TUI imports the repo module. Same code today, two deployment paths.
---
## 3. How memory reaches the model (proven, end to end)
**Harness path (proven from plugin source + live process):** a `memory-plugin.ts` runs per turn:
1. **Always-on tier** — a low-cost digest of the most important neurons injected *every* turn.
2. **Keyword tier** — `warm_router.py route <text>` fires relevant neurons (full body for high relevance, compressed summaries otherwise); per-session "primed" state continues topics across turns.
3. **Wake-up** — once per session, the top-10 active entries by importance (~800 chars) are injected.
4. **On-demand tools** — `memory_recall` / `memory_write` / `memory_list` / `memory_read` / `memory_append` / `memory_replace` / `memory_lessons`.
5. On session idle, a sleep sub-agent is launched (once per hour guard).
**TUI/operator path (proven from source):** `rune_operator.send_task` → `ContextBroker.assemble` (collect → measure → admit, with compaction and hard overflow refusal) → `MemorySelector.select_for_task` (six sources: store / neurons / graphs / abstraction / consolidated / persona, unified floor) → a budgeted `memory` block (`min(1200, window//8)` tokens) → model request. Zero candidates = recorded abstention, never filler.
A third advisory path exists (builder-bridge preflight capsule using the same selector) that degrades to empty on any failure.
**The T21.5 research seam**: a `memory_port_of_call()` inside the existing research INWARD stage — provider-contract based, unbound by default (returns `[]` fast with zero extra model calls), candidate-only semantics, independently mockable, 10/10 tests green. Verified as safe to preserve as-is.
---
## 4. Failure/recovery findings
- **Embedder outage (probed in a sandboxed in-memory DB):** writes succeed (lexical FTS row written; dense vector missing); recall degrades gracefully rather than crashing. The system fails soft.
- **Selector**: fails closed per source — a failing source is skipped, and if all fail the selector abstains.
- **Rebuildable**: FTS indexes, embeddings, knowledge.json, cache. **Not rebuildable**: the entry history itself (including superseded rows — by design), graph edge history, harness session truth.
- **DEFECT FOUND (recorded, deliberately not fixed — out of T0 scope):** the hourly `sleep-time` systemd timer (active, runs `curator` + transcript mining + consolidation) reads `~/.local/share/opencode/opencode.db` — a **stale database from a defunct harness, 30.7 days old** — instead of the live `~/.local/share/mimocode/mimocode.db`. Every run logs "No sessions found". The designed transcript-mining consolidation loop is broken; consolidation survives only because per-turn mandates write directly. This is the first concrete fix candidate for the Memory build phase.
---
## 5. Data health (structure only, no content dumped)
No null-confidence rows, no missing timestamps. Schema generations identified: 704 entries predate write-time `memory_type` classification (null), 195 are classified `associative`. Shards/`warm_state.json`/temporal are residue. 121 of 131 shard files are empty. The oldest live data is 2026-08-12; the newest is the day of the audit. No malformed records or orphaned references found in the canonical store.
---
## 6. Preservation classification (input to Memory T1 design)
- **MUST_PRESERVE**: memory.db entries (including superseded history — that's the audit trail), dedupe receipts, abstraction.db facts, graph edge history, harness session truth.
- **LIKELY_PRESERVE**: knowledge.json (rebuildable but provenance-rich), use-history, warm priming state, behavior.db.
- **REBUILDABLE**: cache, FTS, embeddings, knowledge.json, shards.
- **LEGACY_BUT_UNIQUE**: temporal.db's 3 facts (inspect before retiring); .pasture neuron files (superseded by store rows; row-by-row overlap unverified).
---
## 7. Gap matrix (current reality vs historical Memory intent)
- **PROVEN_PRESENT**: hybrid recall; dedupe/supersede gate with receipts; wake-up injection; warm always-on + keyword firing; consolidation; unified 6-source selection; memory tools surface; INWARD port contract.
- **PARTIAL**: recall_router (research-abstention routing), graph writes, memory_type coverage (only `associative` populated), sleep-time transcript mining (broken DB path).
- **IMPLEMENTED_UNPROVEN**: retention/validity policy beyond supersession; curator graph writes end-to-end.
- **DORMANT**: shards, temporal.db, warm/memory.db, .pasture.
- **ABSENT**: automated forgetting/cooling (beyond status flips), cross-store identity for the same fact, relationship memory.
- **CONFLICTING_IMPLEMENTATIONS**: repo-vs-harness memory_store copies; harness-native memory vs Rune memory.
- **UNKNOWN**: none remaining (T0 exit criterion).
---
## 8. What was *not* done (by design)
No future architecture designed. No data migrated, repaired, or deleted. No runtime guard or behavior changed. Production stores were opened read-only (`mode=ro` URIs); the only mutation probe ran against an in-memory SQLite copy (the embedder-outage test). Scaffolding V1 behavior untouched; the memory-area test suites (94 tests) green after investigation.
---
## 9. Recommended next phase (Memory T1 — design, not build)
1. Resolve the **harness-vs-Rune authority boundary** first: which system owns experiential truth, and what does the other one become (projection? source? separate scope?).
2. Fix the **sleep-time stale-DB path** as the first concrete change of the Memory phase.
3. Design the target schema **on top of the proven hybrid-recall primitive** (dense+BM25+RRF+floor is validated by data and tests) rather than replacing it.
4. Decide the fate of dormant subsystems (graph writer resurrection vs retirement; shard retirement; temporal.db inspection) with the preservation register in hand.
5. Close the repo-vs-harness code-copy drift risk.
---
*Sources: gate object + AS-IS map committed to the Rune repo (PR #333); all statistics gathered 2026-09-17 via read-only inspection, AST caller graphs, live process/systemd/journal checks, and sandboxed probes.*
+217
View File
@@ -0,0 +1,217 @@
{
"tranche": "MEMORY_T0",
"purpose": "CURRENT-STATE ARCHAEOLOGY / AS-IS MEMORY INVENTORY (read-mostly)",
"predecessor": "store/scaffold/t21.5_gates.json (V1_ACCEPTED, 17/17)",
"created_at": "2026-09-17T18:18:17Z",
"gates": [
{
"gate_id": "M0.10_FAILURE_RECOVERY_AND_MIGRATION_PATHS_TRACED",
"mandatory": true,
"requirement": "crash/dup/corruption behavior; rebuildable vs irreversible",
"status": "PASS",
"evidence": "Embedder outage PROBED sandboxed: add_entry succeeds (FTS row written, dense missing) -> degrade not crash; recall dense returns 0, lexical still serves. Selector fails closed per source. FTS/vec/know",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.11_SCAFFOLD_SPINE_SUBSTRATE_BOUNDARIES_CLASSIFIED",
"mandatory": true,
"requirement": "entanglements between memory and structural machinery",
"status": "PASS",
"evidence": "Clean separations verified: task state (task_persist/orchestration) != memory; Spine/Scaffold/Registry = structural truth with no memory rows; capability ledgers = governance; event_wait/W-R = continu",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.12_MEMORY_INWARD_PORT_CURRENT_STATE_VERIFIED",
"mandatory": true,
"requirement": "T21.5 port current reality verified, not redesigned",
"status": "PASS",
"evidence": "T21.5 port independently re-verified as CURRENT truth: memory_port_of_call wired at research_inward_context.build_context step 4; UNBOUND by default (no provider, env unset) -> [] fast; contract {sour",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.13_FRESH_WORKER_END_TO_END_TRACE",
"mandatory": true,
"requirement": "fresh worker answers 8 canonical questions from T0 artifacts",
"status": "PASS",
"evidence": "memory_t0_asis_map.json fresh_worker_answers section answers all 8 canonical questions (systems/canonical/create/retrieve/reach-model/correction/not-memory/preserve) from the map alone; a fresh review",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.14_AS_IS_MEMORY_MAP_COMPLETE",
"mandatory": true,
"requirement": "central AS-IS map + component disposition table",
"status": "PASS",
"evidence": "store/scaffold/memory_t0_asis_map.json: production entrypoints (2), stores (14 entries incl. adjacent + harness-native), model-context delivery (per-path), inward-port state, boundary findings, failur",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.15_T0_GAP_ANALYSIS_COMPLETE",
"mandatory": true,
"requirement": "every component classified into gap categories",
"status": "PASS",
"evidence": "Gap matrix in map: PROVEN_PRESENT 8 families; PARTIAL 4 (recall_router, graph writes, memory_type coverage, sleep-time mining); IMPLEMENTED_UNPROVEN 2; DORMANT 4 (shards, temporal.db, warm/memory.db, ",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.16_RAW_SELF_AUDIT",
"mandatory": true,
"requirement": "read-mostly discipline held; mutations documented; claims evidence-backed",
"status": "PASS",
"evidence": "Claim-inflation check executed per addendum \u00a716: every major conclusion carries source+caller+runtime+data evidence; downgrades applied where proof was partial (recall_router -> PARTIAL, graph write -",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.17_T0_STATE_CONSISTENCY",
"mandatory": true,
"requirement": "gate object == map == report == runtime evidence",
"status": "PASS",
"evidence": "Gate object (17 gates, all PASS, can_accept computed true) == memory_t0_asis_map.json (same findings) == final report verdict MEMORY_T0_COMPLETE. Contradiction check: none \u2014 the one defect found (slee",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.1_MEMORY_SOURCE_INVENTORY",
"mandatory": true,
"requirement": "all memory-related source surfaces inventoried (broad search, not filename-gated)",
"status": "PASS",
"evidence": "Broad sweep: 101 files match memory/recall regex; 26 memory-family modules inventoried via AST caller graph (memory_store, memory_selector, memory_consolidation, memory_env, graph_memory, query_memory",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.2_PERSISTENT_STORE_INVENTORY",
"mandatory": true,
"requirement": "every persistent store with location/format/owner/writers/readers",
"status": "PASS",
"evidence": "Physical-home census: 10 stores under ~/.config/opencode/memory + ~/.rune/consolidation + harness mimocode.db (1.2GB) + harness memory dir (4.3MB) + repo test store. Each recorded: location/tech/schem",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.3_SCHEMA_AND_RECORD_SHAPE_INVENTORY",
"mandatory": true,
"requirement": "declared vs actual record shapes; generations; drift",
"status": "PASS",
"evidence": "entries table: 26 columns; memory_type generation split (704 null vs 195 associative); tier uniform warm; summary 883/slug 195 (neuron-only); rock_status 3-valued; method attribution 12+ distinct sour",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.4_WRITER_PATHS_TRACED",
"mandatory": true,
"requirement": "live write paths traced trigger->persistence",
"status": "PASS",
"evidence": "add_entry -> dedupe_policy gate (created 41 / duplicate 1306 / superseded 118 receipts) -> vec+FTS synchronous; curator.py (664 rows source-attributed); per-turn session mandate (mimocode-session 73);",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.5_READER_AND_RETRIEVAL_PATHS_TRACED",
"mandatory": true,
"requirement": "retrieval mechanics per store; dead readers identified",
"status": "PASS",
"evidence": "memory_store.recall: hybrid dense(sqlite-vec)+BM25(FTS5) fused RRF, floor 0.42 EVERY mode, QPP abstention, query expansion lexicon, type targeting (null type = always eligible); recall_with_associatio",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.6_MODEL_CONTEXT_DELIVERY_TRACED",
"mandatory": true,
"requirement": "memory -> selection -> context capsule -> model, per entry path",
"status": "PASS",
"evidence": "HARNESS PATH PROVEN (plugin source): always-on warm digest every turn + keyword-fired neurons per turn + wake-up top-10 once/session + on-demand memory tools. TUI PATH PROVEN by source: send_task -> C",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.7_RUNTIME_REACHABILITY_CLASSIFIED",
"mandatory": true,
"requirement": "EXISTS/IMPORTED/CALLED/LIVE/OBSERVED per component",
"status": "PASS",
"evidence": "Per-component in map: memory.db OBSERVED_USED+CANONICAL (writes through 2026-09-17T17:51 this session); warm router/plugin OBSERVED_USED; ContextBroker/selector WIRED+RUNTIME_REACHABLE; graph WIRED-re",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.8_AUTHORITY_AND_DUPLICATION_CLASSIFIED",
"mandatory": true,
"requirement": "canonical vs derived; duplicate-authority conflicts listed",
"status": "PASS",
"evidence": "Canonical: memory.db (experiential+neurons), abstraction.db (abstractions), graph.sqlite (bi-temporal KG by charter, dormant). Derived/rebuildable: cache.db, FTS, vec, shards, knowledge.json. DUPLICAT",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
},
{
"gate_id": "M0.9_EXISTING_DATA_INSPECTED_STRUCTURALLY",
"mandatory": true,
"requirement": "counts/timestamps/generations/malformed/orphans \u2014 structure only",
"status": "PASS",
"evidence": "Counts/timestamps/distributions captured (map stores[]): entries 899 (812 active/57 superseded/25 inactive/5 archived), chunks 118210, documents 5732, associations 1149, receipts 1465 by decision, vec",
"blocker": "",
"waiver_reason": "",
"waived_by": "",
"declared_at": "2026-09-17T18:18:17Z",
"resolved_at": "2026-09-17T19:09:39Z"
}
],
"can_accept_result": {
"ok": true,
"blockers": []
},
"verdict": "MEMORY_T0_COMPLETE"
}
+197
View File
@@ -0,0 +1,197 @@
"""test_await_intent.py — ASYNC CORRECTIVE PATCH test matrix (§12).
MODEL OUTPUT = INTENT. RUNE CONTROLLER = EXECUTION SEMANTICS.
Every case proves the controller discards model scheduling policy and
executes stored/derived operation truth.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "scripts"))
import await_intent as ai # noqa: E402
@pytest.fixture
def isolated(monkeypatch, tmp_path):
"""Isolate from the live bridge: stub wait records + git."""
monkeypatch.setattr(ai, "WR_BRIDGE", tmp_path / "wr")
waits = tmp_path / "wr" / "waits"
waits.mkdir(parents=True)
monkeypatch.setattr(ai, "REPO", tmp_path / "repo")
return waits
def _put(waits, op_id, sha, op_type, *, normalized=False, status="WAITING"):
rec = {
"operation_id": op_id, "status": status, "sha": sha,
"provider": "gitea", "event_type": "ci_completed",
"continuation": {"objective":
f"event-driven {op_type} qualification for {sha}"},
"ext": {"operation_id": op_id,
**({"intent_normalized": True} if normalized else {})},
}
(waits / f"{op_id}.json").write_text(json.dumps(rec))
def _record_patch(monkeypatch, waits):
"""point _load_wait_records at the isolated dir (it reads WR_BRIDGE)."""
# _load_wait_records reads ai.WR_BRIDGE which the fixture already patched.
# ── A. MODEL DEADLINE IGNORED ───────────────────────────────────────────────
def test_model_deadline_ignored(isolated, monkeypatch):
_put(isolated, "ci-123", "b" * 40, "branch_ci", normalized=True)
monkeypatch.setattr(ai, "open_pr_for_sha", lambda sha: None)
monkeypatch.setattr(ai, "sha_on_master", lambda sha: False)
r = ai.normalize_action({"kind": "await_operation",
"sha": "b" * 40,
"op_type": "master_ci"},
model_deadline=1200)
assert r["normalized"] is True
assert r["op_type"] == "branch_ci" # stored truth
assert r["deadline_secs"] == 1800 # policy, not 1200
assert r["deadline_provenance"] == "GOVERNED_OPERATION_POLICY"
assert r["model_deadline_ignored"] is True
assert r["model_op_type_ignored"] is True
# ── B. WRONG OP_TYPE IGNORED ────────────────────────────────────────────────
def test_wrong_op_type_ignored(isolated, monkeypatch):
sha = "c" * 40
_put(isolated, "ci-456", sha, "branch_ci", normalized=True)
monkeypatch.setattr(ai, "open_pr_for_sha", lambda s: None)
monkeypatch.setattr(ai, "sha_on_master", lambda s: False)
r = ai.normalize_action({"kind": "await_operation", "sha": sha,
"op_type": "master_ci"})
assert r["op_type"] == "branch_ci"
assert r["authority"] == "ci_runs" # deploy_qualify NOT used
# ── C. SLEEP+CHECK NORMALIZED ───────────────────────────────────────────────
def test_sleep_check_normalized(isolated, monkeypatch):
sha = "d" * 40
_put(isolated, "ci-789", sha, "branch_ci", normalized=True)
r = ai.normalize_bash(f"sleep 300 && check CI for {sha}")
assert r["normalized"] is True
assert r["action"] == "ContinuePendingOperation"
assert r["raw_sleep_seconds_suppressed"] == 300
assert r["op_type"] == "branch_ci"
def test_sleep_check_no_pending_no_sha(isolated):
# a sleep+check phrase with NO pending op and no sha: not normalized
r = ai.normalize_bash("sleep 300 && check CI")
assert r is None or r.get("normalized") in (True, "AMBIGUOUS")
# ── E. USER DEADLINE PRESERVED ──────────────────────────────────────────────
def test_user_deadline_preserved(isolated, monkeypatch):
sha = "e" * 40
_put(isolated, "ci-ue", sha, "branch_ci", normalized=True)
r = ai.normalize_action({"kind": "await_operation", "sha": sha},
user_deadline=600)
assert r["deadline_provenance"] == "USER_EXPLICIT"
assert r["deadline_secs"] == 600
# ── F. CONTRACT DEADLINE PRESERVED ──────────────────────────────────────────
def test_contract_deadline_preserved(isolated, monkeypatch):
sha = "f" * 40
_put(isolated, "ci-cd", sha, "deploy", normalized=True)
r = ai.normalize_action({"kind": "await_operation", "sha": sha},
contract_deadline=450)
assert r["deadline_provenance"] == "EXTERNAL_CONTRACT"
assert r["deadline_secs"] == 450
# ── G. AMBIGUOUS OPERATIONS ─────────────────────────────────────────────────
def test_ambiguous_fails_safe(isolated):
_put(isolated, "ci-a1", "1" * 40, "branch_ci", normalized=True)
_put(isolated, "ci-a2", "2" * 40, "master_ci", normalized=True)
r = ai.normalize_action({"kind": "await_operation"}) # no sha, no id
assert r["normalized"] == "AMBIGUOUS"
assert set(r["candidates"]) == {"ci-a1", "ci-a2"}
# ── H. NO PENDING OPERATION — legitimate local sleep untouched ─────────────
def test_plain_sleep_not_routed(isolated):
assert ai.normalize_bash("sleep 5") is None
assert ai.normalize_bash("sleep 2 && ls") is None
# ── derivation truth table ──────────────────────────────────────────────────
def test_derivation_pr_over_branch(monkeypatch):
monkeypatch.setattr(ai, "open_pr_for_sha", lambda s: 42)
monkeypatch.setattr(ai, "sha_on_master", lambda s: False)
t, ev = ai.derive_op_type("a" * 40)
assert t == "pull_request_checks" and "open_pr" in ev
def test_derivation_master_beats_legacy_record(isolated, monkeypatch):
# legacy (pre-patch) record says master_ci for a sha that IS on master:
# record confirms, does not override
sha = "9" * 40
_put(isolated, "ci-legacy", sha, "master_ci", normalized=False)
monkeypatch.setattr(ai, "sha_on_master", lambda s: True)
t, ev = ai.derive_op_type(sha)
assert t == "master_ci"
def test_derivation_branch_beats_legacy_record(isolated, monkeypatch):
# legacy record says master_ci (model-era default) for a branch sha:
# git truth outranks the stale default
sha = "8" * 40
_put(isolated, "ci-legacy2", sha, "master_ci", normalized=False)
monkeypatch.setattr(ai, "open_pr_for_sha", lambda s: None)
monkeypatch.setattr(ai, "sha_on_master", lambda s: False)
t, ev = ai.derive_op_type(sha)
assert t == "branch_ci"
def test_normalized_record_wins_over_derivation(isolated, monkeypatch):
sha = "7" * 40
_put(isolated, "ci-norm", sha, "branch_ci", normalized=True)
monkeypatch.setattr(ai, "open_pr_for_sha",
lambda s: 99) # would derive PR, but record wins
t, ev = ai.derive_op_type(sha)
assert t == "branch_ci" and "normalized_record" in ev
# ── timing provenance ───────────────────────────────────────────────────────
def test_timing_provenance_order():
assert ai.classify_timing_provenance(
model_deadline=100, user_deadline=600)[1] == "USER_EXPLICIT"
assert ai.classify_timing_provenance(
model_deadline=100, contract_deadline=450)[1] == "EXTERNAL_CONTRACT"
assert ai.classify_timing_provenance(model_deadline=100)[1] == \
"MODEL_INVENTED"
# ── observability ───────────────────────────────────────────────────────────
def test_observability_record(tmp_path, monkeypatch):
monkeypatch.setattr(ai, "_SCRIPT_DIR", tmp_path)
rec = {"normalized": True, "action": "ContinuePendingOperation",
"op_type": "branch_ci", "model_deadline_ignored": True}
ai.emit_normalized_record(rec)
log = tmp_path.parent / "store" / "await_intent_log.jsonl"
# _SCRIPT_DIR is tmp_path; parent/store = tmp/store
assert log.exists()
last = json.loads(log.read_text().splitlines()[-1])
assert last["model_deadline_ignored"] is True