Compare commits

...
15 Commits
Author SHA1 Message Date
conductor aa3e1e3113 chore(release): auto-bump version to 0.210.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 15s
CI Pipeline / gitea-offline (push) Successful in 20s
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / lint (push) Failing after 48s
CI Pipeline / type-check (push) Failing after 42s
CI Pipeline / canary (push) Failing after 2m0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 2m45s
Promotion record: (unknown)
2026-10-11 22:07:36 +01:00
Nick f11006c457 refactor(build_basis): Phase B language-advantage pass — dichotomy wave
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 10s
CI Pipeline / lint (push) Canceled after 10s
CI Pipeline / unit-tests (push) Canceled after 10s
CI Pipeline / rust-surface (push) Canceled after 10s
CI Pipeline / canary (push) Canceled after 11s
CI Pipeline / best-practices (push) Canceled after 10s
Auto-version and Release / release (push) Canceled after 19s
deploy / deploy (push) Canceled after 20s
2026-10-11 22:02:18 +01:00
Nick b8b1ba66ad feat(build_basis): port to FULL Rust — dichotomy wave 2026-10-11 22:02:18 +01:00
conductor 7807e7eea5 chore(release): auto-bump version to 0.209.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
Auto-version and Release / release (push) Successful in 14s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 14s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / type-check (push) Failing after 39s
CI Pipeline / canary (push) Failing after 2m0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 13m0s
Promotion record: (unknown)
2026-10-11 21:54:14 +01:00
Nick c42936b959 refactor(construction_pattern): Phase B language-advantage pass — dichotomy wave
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / best-practices (push) Canceled after 3s
CI Pipeline / canary (push) Canceled after 16s
Auto-version and Release / release (push) Canceled after 22s
deploy / deploy (push) Canceled after 26s
CI Pipeline / lint (push) Canceled after 23s
CI Pipeline / unit-tests (push) Canceled after 23s
language advantage: ApplicabilityVerdict/ObligationVerdict enums replace
string-soup verdicts, serialized at the JSON boundary via exhaustive
as_str matches; language advantage: obligation when-conditions classify
into a WhenShape enum (Unconditioned/Always/PathContains/
EntitiesMatch/ContractsTouched/Unrecognized) dispatched exhaustively —
the unrecognized shape surfaces as UNKNOWN, never silently REQUIRED;
language advantage: str_list borrows slices of the input JSON (owned
copies only where the output needs them), derive reuses the obligation
object instead of cloning it, zero-copy split_tokens returns
token slices borrowing the lowered input (re.split leading/trailing
empty fidelity kept); language advantage: contracts_match evidence and
when-condition hits built as iterator chains; sort_unstable throughout.

Battery 9/9 (56 frozen oracle cases) green before and after the pass;
clippy 0; live ctypes parity 56/56; construction surface tests 51/51
green with RUNE_CP_CORE=python pin and with the kernel live.
2026-10-11 21:51:05 +01:00
Nick 681312c69f feat(construction_pattern): port to FULL Rust — dichotomy wave
Full parity port of src/rune/construction_pattern.py (dichotomy wave,
#2479 tranche): ConstructionPattern construct/revise,
record_pattern_proposal, evaluate_applicability,
derive_pattern_obligations, combine_pattern_obligations as
cp_* FFI entry points (doc-envelope law; freed via
capability_scoring's cs_free_string — no re-exported free symbol).

Frozen oracle truth: 56 cases (scripts/gen_construction_pattern_truth.py,
RUNE_CP_CORE=python pinned) exercising every public function and branch —
constructor validation errors, open-world ext, revision history
accumulation + 200-char reason truncation, exclusion override paths,
boundary-aware short-token vs containment matching, ':'-prefix entities,
objective '/'-split (dashes kept), authority relationship scans with the
falsy-from_id quirk, evidence truncation, EXCEPTED defaults,
accepted/rejected/unresolved routing, duplicate collapse + conflicts.

ctypes loader src/rune/cp_loader.py mirrors broker_loader.py; facade
delegation on the four pure functions with oracle fallback and
RUNE_CP_CORE=python pin. Live parity 56/56 vs oracle; battery 9/9;
clippy 0; construction surface tests 88/88 green with and without the
kernel live.
2026-10-11 21:51:05 +01:00
Nick d7a5a99bab chore(store): close drift_observability dichotomy contract (2317ae) — verifying+closed, SHA evidence
CI Pipeline / best-practices (push) Successful in 7s
Auto-version and Release / release (push) Successful in 13s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / gitea-offline (push) Successful in 27s
CI Pipeline / lint (push) Failing after 1m8s
CI Pipeline / type-check (push) Failing after 1m10s
CI Pipeline / canary (push) Failing after 2m19s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 10m40s
CI Pipeline / unit-tests (push) Canceled after 10m41s
2026-10-11 21:43:10 +01:00
conductor cef1e953ea chore(release): auto-bump version to 0.208.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 12s
Auto-version and Release / release (push) Successful in 15s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / go-surface (push) Failing after 14s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / type-check (push) Failing after 40s
CI Pipeline / canary (push) Failing after 1m58s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 2m15s
CI Pipeline / unit-tests (push) Canceled after 2m31s
Promotion record: (unknown)
2026-10-11 21:40:40 +01:00
Nick d4c9ce560f ci: message_queue FULL-port parity gate (dichotomy wave — 55 frozen rows + race battery)
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 8s
CI Pipeline / rust-surface (push) Canceled after 8s
Auto-version and Release / release (push) Canceled after 15s
CI Pipeline / canary (push) Canceled after 8s
CI Pipeline / lint (push) Canceled after 10s
CI Pipeline / unit-tests (push) Canceled after 10s
CI Pipeline / best-practices (push) Canceled after 8s
deploy / deploy (push) Canceled after 22s
2026-10-11 21:40:04 +01:00
Nick 64911ab782 refactor(message_queue): Phase B language-advantage pass — dichotomy wave
language advantage: MessageStatus typed lattice replaces status string soup
(pending/approved/rejected/executed as typed constants; JSON boundary
converts explicitly; unknown store values round-trip verbatim like the
oracle); language advantage: QueueStore interface extracts the persistence
seam with a compile-time assertion (var _ QueueStore = (*FileQueueStore)),
in-memory stores slot in for tests; language advantage: RWMutex-guarded
mutations make MessageQueue safe for concurrent callers (the TUI consumes
this module from multiple goroutines; the Python oracle relied on the GIL).
No performative goroutines: the module's only IO is one small JSON file
write per mutation — sync semantics are load-bearing for parity (approve
returns after the save lands). Battery green (8 fns + 3 Phase B fns,
race-clean), live parity 55/55, gofmt/vet clean.
2026-10-11 21:40:04 +01:00
Nick 8286361804 feat(message_queue): port to FULL Go — dichotomy wave
Full name-preserving port of src/rune/message_queue.py to src/go/message_queue
(GO verdict, #2479 dichotomy tranche — 13/13 fns, dichotomy audit now FULL):
- MessageQueue: detect_task_addition (25-pattern table 1:1, imperative-verb
  prefixes, file refs, 'also'-first bonus, 1.0 cap, strict >0.5 boundary),
  add_message (strftime %f microseconds in msg_id — explicit %06d, Go time
  layouts can't render bare µs), get_pending, approve, reject, approve_all,
  clear, get_approved, mark_executed, get_status
- load/save (_load_queue/_save_queue): Python json.dumps(indent=2,
  ensure_ascii) byte-parity via pyjson.go (float repr keeps '.0'; the goal
  port's renderer drops it — backport candidate)
- CLI main() byte-parity stdout incl. ✅/❌ lines and exit codes
- IO (home dir, clock) behind injectable seams (HomeDir/ClockNow)
- Supersedes the batch-61/148 detect-kernel partial in src/go/message-queue
  (which stays: CI gate + scaffolding_coverage reference it)

Truth: 55 frozen rows vs the LIVE oracle (clock frozen at 2026-10-11T12:00Z,
advancing 1µs/call; RUNE_MESSAGE_QUEUE_CORE=python pinned). Divergences the
freeze caught: the oracle's don't-forget pattern is the raw string
\bdon\'t forget\b — the backslash lives in both the regex and the emitted
signal text; msg_id uses '-' before %f; _save_queue indent starts at 0.

Battery: 8 test fns green; replay gate PARITY OK 55/55; gofmt/vet clean.
2026-10-11 21:40:04 +01:00
conductor 6be2b0a94a chore(release): auto-bump version to 0.207.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / go-surface (push) Failing after 17s
CI Pipeline / rust-surface (push) Failing after 17s
Auto-version and Release / release (push) Successful in 19s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / canary (push) Failing after 1m56s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 6m21s
Promotion record: (unknown)
2026-10-11 21:34:03 +01:00
Nick efee6c5784 refactor(drift_observability): Phase B language-advantage pass — dichotomy wave
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / canary (push) Canceled after 16s
deploy / deploy (push) Canceled after 5s
CI Pipeline / gitea-offline (push) Canceled after 11s
Auto-version and Release / release (push) Canceled after 19s
CI Pipeline / type-check (push) Canceled after 11s
CI Pipeline / lint (push) Canceled after 17s
CI Pipeline / unit-tests (push) Canceled after 16s
language advantage: AlertKind/EmittedSeverity/Assessment enums replace
string soup, serialization serialized at the JSON boundary via exhaustive
as_str matches; language advantage: check_alerts holds raised alerts
typed (NewAlert) so the stamped and unstamped projections share one
source, and composes the appended list with a borrow→clone chain instead
of an intermediate owned Vec clone; language advantage: Assessment
summaries are canonical &'static str (no allocation) with the tier order
centralized in classify; language advantage: typed FfiError enum (null
pointer / utf8 / json) behind the Result doc-envelope. add_alert's input
severity stays a pass-through &str — the oracle forwards caller strings
verbatim; a closed input lattice would be a parity break. Battery green
(5 tests / 33 frozen cases), live parity 33/33 facade + 33/33 kernel,
clippy 0.
2026-10-11 21:33:47 +01:00
Nick 819525c602 feat(drift_observability): port to FULL Rust — dichotomy wave
parity: all five public functions of DriftObservability ported branch-
for-branch — add_alert (3 severity tiers), check_alerts (velocity/
negative-drift/promotion-stall with the strict >14-day boundary and the
oracle's malformed/naive/NULL/empty last_washed try-except skips),
run_retrospective (strict >0.3/>0.1 assessment lattice, strftime
filename), get_unacknowledged_alerts (missing-key Python truthiness),
acknowledge_alert (bounds no-op, bool-as-int coercion). Own ISO-8601
parser + days_from_civil; timedelta.days floor via div_euclid. No-clock
law: now/now_local are kernel arguments; sqlite reads and FILE IO stay
in the facade. do_loader mirrors broker_loader (doc-envelope FFI,
cs_free_string, DO_RUST_LIB override); facade delegation with
RUNE_DO_CORE=python pin. Battery: 5 tests replay 33 frozen-oracle
cases VERBATIM (include_str!), truth frozen with the pin and a frozen
clock. Live parity 33/33 facade + 33/33 kernel; clippy 0.
2026-10-11 21:33:47 +01:00
Nick d1efed0eee chore(store): close attention_capsule dichotomy contract (35a41f) — verifying+closed, SHA evidence
Auto-version and Release / release (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 15s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Failing after 49s
CI Pipeline / lint (push) Failing after 52s
CI Pipeline / canary (push) Failing after 2m13s
deploy / deploy (push) Failing after 15m20s
CI Pipeline / unit-tests (push) Failing after 16m9s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
2026-10-11 19:09:18 +01:00
37 changed files with 66576 additions and 204 deletions

No files matched your search

+4
View File
@@ -294,6 +294,10 @@ jobs:
run: |
cd src/go/message-queue && go build -o /tmp/message-queue-${{ gitea.run_id }} .
/tmp/message-queue-${{ gitea.run_id }} replay truth.json
- name: message_queue parity gate (HARD — #2479 dichotomy wave: FULL port, 55 frozen rows)
run: |
cd src/go/message_queue && go test ./... -race -count=1
cd src/go/message_queue && go build -o /tmp/message_queue-${{ gitea.run_id }} . && /tmp/message_queue-${{ gitea.run_id }} replay truth.json
- name: outcome-ledger parity gate (HARD — #2479 batches 49+151: stats + live fixture)
run: |
cd src/go/outcome-ledger && go build -o /tmp/outcome-ledger-${{ gitea.run_id }} .
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.206.0**
**Current release: v0.210.0**
**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.206.0"
version = "0.210.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+278
View File
@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""Live parity: Rust build_basis kernel vs the Python oracle (dichotomy
wave #2479). Replays the frozen truth corpus PLUS seeded fuzz over every
public function and branch. Timestamps the kernel generates are masked
on both sides (wall clock, not logic).
The facade reads RUNE_BB_CORE per call, so one process can flip between
the pinned oracle (RUNE_BB_CORE=python) and the kernel (unset).
"""
from __future__ import annotations
import copy
import json
import os
import random
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
MASKED = ("decided_at", "created_at", "revised_at", "plan_generated_at")
def mask(doc):
if isinstance(doc, dict):
return {k: mask(v) for k, v in doc.items() if k not in MASKED}
if isinstance(doc, list):
return [mask(v) for v in doc]
return doc
def call(fn, *args, **kwargs):
"""Call through the facade under the given core pin."""
return getattr(bb, fn)(*args, **kwargs)
def call_core(core, fn, *args, **kwargs):
old = os.environ.get("RUNE_BB_CORE")
os.environ["RUNE_BB_CORE"] = core
try:
return call(fn, *args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_BB_CORE", None)
else:
os.environ["RUNE_BB_CORE"] = old
def mutator_case(fn, basis_in, *args, **kwargs):
"""Run a mutating function on both cores over deep copies; compare
(return value, post-mutation basis) with time fields masked."""
py = call_core("python", fn, copy.deepcopy(basis_in), *args, **kwargs)
rs = call_core("rust", fn, copy.deepcopy(basis_in), *args, **kwargs)
ok = (mask(py) == mask(rs)
and mask(basis_in_py := None or basis_in) is not None)
return ok, py, rs
# ── fuzz pools ─────────────────────────────────────────────────────────
ROUTES = ["LOCAL_FIX", "STANDARD_CHANGE", "RESEARCHED_CHANGE", "WEIRD",
"", "RESEARCHED_CHANGE ", "local_fix", "SPIKE"]
TIERS = ["NONE", "INWARD", "OUTWARD", "FULL", "WEIRD", None]
JUSTS = ["", " ", "tiny fix", "OUTWARD: precedent needed",
"FULL: x:y", " INWARD ", "resolved already",
"не-mátter 🎉", "prior-art question answered elsewhere",
"NONE: skip", "discovery resolved it", "unnecessary"]
WORDS = ["existing", "prior art", "framework", "typo", "schema",
"architecture", "watchdog", "delete", "", "u" * 90, "café ☕",
"tool", "standard", "breaking", "deploy"]
DISPOSITIONS = ["ADOPT", "ADAPT", "COMBINE", "REJECT", "INNOVATE", "MAYBE", "", None]
def rand_basis(rng):
b = bb.new_basis(question=rng.choice(WORDS),
depth_decision=bb.research_depth(rng.choice(ROUTES),
justification=rng.choice(JUSTS)),
issue_ref=rng.choice(["research/issue-1", "", "gitea/2479"]))
b["created_at"] = "2026-10-11T00:00:00Z"
return b
def fuzz_cases(rng):
out = []
for _ in range(120):
out.append(("research_depth",
(rng.choice(ROUTES), rng.choice(["", "rune-internal", "cli"]),
rng.choice(JUSTS)), {}))
for _ in range(60):
d = bb.research_depth(rng.choice(ROUTES), justification=rng.choice(JUSTS))
d["decided_at"] = "FIXED"
out.append(("new_basis", (rng.choice(WORDS), d,
rng.choice(["research/x", ""])), {}))
for _ in range(120):
disp = [{"item": rng.choice(WORDS), "disposition": rng.choice(DISPOSITIONS),
"why": rng.choice(JUSTS), "ref": rng.choice(["r1", ""]) }
for _ in range(rng.randrange(0, 4))]
out.append(("set_dispositions", (None,), {"basis": None, "dispositions": disp}))
for _ in range(120):
b = rand_basis(rng)
if rng.random() < 0.5:
b["schema_version"] = rng.choice([1, 2, None, True])
if rng.random() < 0.4:
b["depth"] = rng.choice([{"depth": rng.choice(TIERS), "why": rng.choice(["", "w"])},
{}, None])
if rng.random() < 0.3:
b["dispositions"] = [{"item": rng.choice(WORDS),
"disposition": rng.choice(DISPOSITIONS)}]
out.append(("validate_basis", (), {"basis": b}))
for _ in range(60):
out.append(("attach_evidence_ref", (),
{"basis": rng.choice([rand_basis(rng),
{"created_at": None},
{"issue_ref": "", "created_at": "T"},
{}])}))
for _ in range(60):
b = rand_basis(rng)
if rng.random() < 0.4:
b["implications"] = rng.choice([None, {}, {"architecture": ["a"], "x": "yy"}])
b["uncertainty"] = rng.choice([[], ["u1"], ["u1", "u2"]])
b["risks"] = rng.choice([[], ["r1"]])
out.append(("summary", (), {"basis": b}))
for _ in range(120):
b = rand_basis(rng)
out.append(("revise_depth", (),
{"basis": b, "new_depth": rng.choice(TIERS),
"reason": rng.choice(JUSTS + ["skip it", "x" * 250, "r"])}))
for _ in range(80):
b = rand_basis(rng)
if rng.random() < 0.3:
b["question"] = "héllo 🎉 \"q\" \\n\x01"
out.append(("to_json", (), {"basis": b}))
for _ in range(60):
out.append(("from_json", (rng.choice([
json.dumps({"a": [1, 2.5, True, None], "é": "ü"}),
"[1,2,3]", "{}", "null", "\"str\"",
"{bad", "{'single': 1}", ""]),), {}))
for _ in range(150):
kw = dict(intent=rng.choice(WORDS + ["migrate the schema", "deploy it"]),
target=rng.choice(["", "wr-bridge"]),
current_reality=rng.choice(["", "c" * 250, "réalité"]),
unknowns=[rng.choice(WORDS) for _ in range(rng.randrange(0, 3))],
decisions=[rng.choice(WORDS) for _ in range(rng.randrange(0, 2))],
risks=[rng.choice(WORDS) for _ in range(rng.randrange(0, 2))],
depth=rng.choice([None] + TIERS[:4]))
out.append(("generate_research_plan", (), kw))
return out
def main() -> int:
global bb
from rune import build_basis as bb_mod
from rune import bb_loader
bb = bb_mod
if not bb_loader.available():
print("Rust core not built — run cargo build --release in src/rust/build_basis")
return 2
total = passed = 0
failures: list[str] = []
def check(label, ok, detail=""):
nonlocal total, passed
total += 1
if ok:
passed += 1
else:
failures.append(f"{label}: {detail}")
# 1. frozen truth corpus, replayed live
truth = json.loads((Path(__file__).resolve().parents[1] / "src" / "rust" /
"build_basis" / "src" / "frozen_bb_truth.json").read_text())
for fn, cases in truth["cases"].items():
for case in cases:
name = f"truth:{fn}:{case['name']}"
inp = case["input"]
if "error" in case and fn == "revise_depth":
# error path: oracle raises; kernel must refuse identically
try:
py_err = None
call_core("python", fn, copy.deepcopy(inp["basis"]),
new_depth=inp["new_depth"], reason=inp["reason"])
except ValueError as e:
py_err = str(e)
try:
bb_loader.revise_depth(copy.deepcopy(inp["basis"]),
inp["new_depth"], inp["reason"])
check(name, False, "kernel accepted a refused revision")
except RuntimeError as e:
check(name, str(e) == py_err,
f"kernel err {e!r} != oracle {py_err!r}")
continue
if "error_kind" in case: # from_json invalid → both must refuse
try:
call_core("python", fn, **inp)
py_ok, rs_ok = True, True
except ValueError:
py_ok = False
try:
call_core("rust", fn, **inp)
rs_ok = True
except ValueError:
rs_ok = False
check(name, py_ok == rs_ok == False, f"py={py_ok} rs={rs_ok}")
continue
if fn in ("set_dispositions", "revise_depth"):
b1, b2 = copy.deepcopy(inp["basis"]), copy.deepcopy(inp["basis"])
kw = dict(inp)
kw["basis"] = b1
py = call_core("python", fn, **kw)
kw["basis"] = b2
rs = call_core("rust", fn, **kw)
ok = mask(py) == mask(rs) and mask(b1) == mask(b2)
check(name, ok, f"py={py!r} rs={rs!r}")
else:
if fn == "new_basis":
py = call_core("python", fn, question=inp["question"],
depth_decision=inp["depth_decision"],
issue_ref=inp.get("issue_ref", ""))
rs = call_core("rust", fn, question=inp["question"],
depth_decision=inp["depth_decision"],
issue_ref=inp.get("issue_ref", ""))
else:
py = call_core("python", fn, **inp)
rs = call_core("rust", fn, **inp)
check(name, mask(py) == mask(rs), f"py={py!r} rs={rs!r}")
# 2. seeded fuzz
rng = random.Random(2479)
for fn, args, kwargs in fuzz_cases(rng):
name = f"fuzz:{fn}"
if fn in ("set_dispositions", "revise_depth"):
b = rand_basis(rng)
b1, b2 = copy.deepcopy(b), copy.deepcopy(b)
kw_clean = {k: v for k, v in kwargs.items() if k != "basis"}
try:
py = call_core("python", fn, b1, **kw_clean)
except ValueError as e:
py = f"ValueError: {e}"
try:
rs = call_core("rust", fn, b2, **kw_clean)
except ValueError as e:
rs = f"ValueError: {e}"
check(name, mask(py) == mask(rs) and mask(b1) == mask(b2),
f"kwargs={kwargs} py={py!r} rs={rs!r}")
elif fn == "new_basis":
py = call_core("python", fn, question=args[0],
depth_decision=args[1], issue_ref=args[2])
rs = call_core("rust", fn, question=args[0],
depth_decision=args[1], issue_ref=args[2])
check(name, mask(py) == mask(rs), f"args={args}")
elif fn in ("validate_basis", "attach_evidence_ref", "summary",
"to_json", "from_json", "generate_research_plan"):
try:
py = call_core("python", fn, *args, **kwargs)
except ValueError as e:
py = f"ValueError: {e}"
try:
rs = call_core("rust", fn, *args, **kwargs)
except ValueError as e:
rs = f"ValueError: {e}"
check(name, mask(py) == mask(rs), f"args={args} kwargs={kwargs}")
elif fn == "research_depth":
py = call_core("python", fn, args[0], mode=args[1],
justification=args[2])
rs = call_core("rust", fn, args[0], mode=args[1],
justification=args[2])
check(name, mask(py) == mask(rs), f"args={args}")
print(f"parity {passed}/{total}")
if failures:
for f in failures[:12]:
print("FAIL", f[:400])
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+323
View File
@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""Freeze build_basis oracle truth for the Rust port battery (dichotomy
wave #2479 tranche, batch: build_basis — RUST side verdict).
Exercises EVERY public function across EVERY branch:
research_depth (all route defaults, unknown-route floor, tier-selecting
vs annotating justifications, blank justification), new_basis,
set_dispositions (legal/illegal/missing keys), validate_basis (schema,
depth, WHY rule D, OUTWARD/FULL prior-art gate, INWARD evidence gate,
illegal disposition, item truncation), is_qualified,
attach_evidence_ref (issue_ref/adhoc/missing created_at), summary
(empty/qualified/None implications/counts), revise_depth (legal
revision, blocked downgrade, no-op, unknown depth, reason truncation),
to_json (Python json.dumps indent=1 sort_keys incl. non-ASCII escaping),
from_json, generate_research_plan (topic kinds, outward keywords,
depth heuristics FULL/OUTWARD/INWARD/NONE, blank filtering, caller
depth, truncations).
Output: src/rust/build_basis/src/frozen_bb_truth.json — replayed
VERBATIM by src/rust/build_basis/src/branch_battery.rs.
Timestamps the kernel generates (decided_at/created_at/revised_at/
plan_generated_at) are masked (removed on both sides) — they are wall
clock, not logic. Fixed created_at values are INJECTED into basis inputs
so kernel pass-through fields compare exactly.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
os.environ["RUNE_BB_CORE"] = "python" # POISONED-TRUTH LAW: freeze the ORACLE, never the kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import build_basis as bb # noqa: E402
MASKED = ("decided_at", "created_at", "revised_at", "plan_generated_at")
def mask(doc):
"""Remove kernel-generated wall-clock fields wherever they appear."""
if isinstance(doc, dict):
return {k: mask(v) for k, v in doc.items() if k not in MASKED}
if isinstance(doc, list):
return [mask(v) for v in doc]
return doc
def fixed_basis(depth_route="RESEARCHED_CHANGE", question="q",
issue_ref="research/issue-1"):
b = bb.new_basis(question=question,
depth_decision=bb.research_depth(depth_route),
issue_ref=issue_ref)
b["created_at"] = "2026-10-11T00:00:00Z" # fixed: pass-through, exact-compare
return b
cases: dict[str, list] = {}
def run(fn: str, name: str, **input):
out = getattr(bb, fn)(**input)
cases.setdefault(fn, []).append(
{"name": name, "input": input, "expected": mask(out)})
def run_err(fn: str, name: str, **input):
try:
getattr(bb, fn)(**input)
except ValueError as e:
cases.setdefault(fn, []).append(
{"name": name, "input": input, "error": str(e)})
return
raise AssertionError(f"{fn}({name}) did not raise")
# ── research_depth: every branch ──────────────────────────────────────
for route in ("LOCAL_FIX", "STANDARD_CHANGE", "RESEARCHED_CHANGE"):
run("research_depth", f"route-{route}", route=route)
run("research_depth", "unknown-route-floor", route="MYSTERY_ROUTE")
run("research_depth", "annotation-justification", route="RESEARCHED_CHANGE",
justification="tiny config fix; full pipeline disproportionate")
run("research_depth", "tier-select-justification", route="STANDARD_CHANGE",
justification="OUTWARD: new provider integration, precedent needed")
run("research_depth", "tier-select-downgrade-NONE", route="RESEARCHED_CHANGE",
justification="NONE: nothing to research")
run("research_depth", "tier-bare-word", route="STANDARD_CHANGE",
justification=" OUTWARD ")
run("research_depth", "tier-multi-colon", route="STANDARD_CHANGE",
justification="FULL: architectural; check heuristics first")
run("research_depth", "blank-justification-default", route="LOCAL_FIX",
justification=" ")
run("research_depth", "blank-justification-unknown", route="WEIRD",
justification=" ")
run("research_depth", "unknown-route-with-justification", route="WEIRD",
justification="check the spike notes")
run("research_depth", "mode-recorded", route="RESEARCHED_CHANGE",
mode="rune-internal")
# ── new_basis ─────────────────────────────────────────────────────────
b = bb.new_basis(question="structural model for project scaffolds",
depth_decision=bb.research_depth("RESEARCHED_CHANGE"),
issue_ref="research/structural-model")
b["created_at"] = "2026-10-11T00:00:00Z"
cases.setdefault("new_basis", []).append(
{"name": "full", "input": {"question": b["question"],
"depth_decision": mask(b["depth"]),
"issue_ref": b["issue_ref"]},
"expected": mask(b)})
b2 = bb.new_basis(question="q", depth_decision=bb.research_depth("LOCAL_FIX"))
b2["created_at"] = "2026-10-11T00:00:00Z"
cases["new_basis"].append(
{"name": "no-issue-ref", "input": {"question": "q",
"depth_decision": mask(b2["depth"])},
"expected": mask(b2)})
# ── set_dispositions ──────────────────────────────────────────────────
for nm, disp in (
("legal", [{"item": "Backstage catalog entity model", "disposition": "ADAPT",
"why": "nesting fits", "ref": "backstage.io"},
{"item": "own graph database", "disposition": "REJECT",
"why": "structural_store is adequate", "ref": "repo"}]),
("illegal", [{"item": "x", "disposition": "MAYBE"}]),
("missing-keys", [{}]),
("empty-list", []),
):
basis = fixed_basis()
before = json.loads(json.dumps(basis))
errs = bb.set_dispositions(basis, disp)
cases.setdefault("set_dispositions", []).append(
{"name": nm, "input": {"basis": before, "dispositions": disp},
"expected": {"basis": mask(basis), "errors": errs}})
# ── validate_basis: every error branch ────────────────────────────────
def val_case(nm, basis):
cases.setdefault("validate_basis", []).append(
{"name": nm, "input": {"basis": basis},
"expected": bb.validate_basis(basis)})
b = fixed_basis(); b["schema_version"] = 2
val_case("schema-mismatch", b)
b = fixed_basis(); b["schema_version"] = None
val_case("schema-none", b)
b = fixed_basis(); b["depth"] = {}
val_case("depth-missing", b)
b = fixed_basis(); b["depth"] = {"depth": "WEIRD", "why": "w"}
val_case("depth-illegal", b)
b = fixed_basis(); b["depth"] = {"depth": "FULL"}
val_case("why-missing-rule-D", b)
val_case("outward-no-evidence", fixed_basis(depth_route="STANDARD_CHANGE",
issue_ref="",
question="q") | {"depth": {"depth": "OUTWARD", "why": "w"}})
b = fixed_basis(); b["depth"] = {"depth": "FULL", "why": "w"}
val_case("full-no-evidence", b)
val_case("inward-empty", bb.new_basis(question="q",
depth_decision={"depth": "INWARD", "why": "w"}))
b = bb.new_basis(question="q", depth_decision={"depth": "INWARD", "why": "w"})
b["inward"]["findings"] = ["f"]
val_case("inward-with-findings", b)
b = fixed_basis()
b["dispositions"] = [{"item": "x" * 60, "disposition": "MAYBE"}]
val_case("illegal-disposition-item-truncated-40", b)
b = fixed_basis(); b.pop("dispositions")
val_case("dispositions-key-absent", b)
b = fixed_basis(); b["outward"] = None
val_case("outward-null-inward-ok", b)
val_case("qualified-basis-zero-errors", fixed_basis() | {
"outward": {"prior_art_questions_answered": ["q1"], "findings": [], "refs": []},
"dispositions": [{"item": "i", "disposition": "ADOPT", "why": "w", "ref": "r"}]})
# ── is_qualified ──────────────────────────────────────────────────────
q = fixed_basis()
q["outward"] = {"prior_art_questions_answered": ["q1"], "findings": [], "refs": []}
q["dispositions"] = [{"item": "i", "disposition": "ADOPT", "why": "w", "ref": "r"}]
cases.setdefault("is_qualified", []).append(
{"name": "qualified", "input": {"basis": q}, "expected": bb.is_qualified(q)})
cases["is_qualified"].append(
{"name": "empty-unqualified", "input": {"basis": fixed_basis()},
"expected": False})
# ── attach_evidence_ref ───────────────────────────────────────────────
cases.setdefault("attach_evidence_ref", []).append(
{"name": "with-issue-ref", "input": {"basis": fixed_basis()},
"expected": bb.attach_evidence_ref(fixed_basis())})
b = fixed_basis(); b["issue_ref"] = ""
cases["attach_evidence_ref"].append(
{"name": "adhoc", "input": {"basis": b}, "expected": bb.attach_evidence_ref(b)})
b = fixed_basis(); b.pop("created_at")
cases["attach_evidence_ref"].append(
{"name": "missing-created-at", "input": {"basis": b},
"expected": bb.attach_evidence_ref(b)})
# ── summary ───────────────────────────────────────────────────────────
b = fixed_basis()
cases.setdefault("summary", []).append(
{"name": "empty", "input": {"basis": json.loads(json.dumps(b))},
"expected": mask(bb.summary(b))})
q2 = fixed_basis()
q2["outward"] = {"prior_art_questions_answered": [], "findings": ["f"], "refs": ["r"]}
q2["dispositions"] = [{"item": "x" * 100, "disposition": "ADOPT", "why": "w", "ref": "r"},
{"item": "short", "disposition": "ADAPT"}]
q2["uncertainty"] = ["u1", "u2"]
q2["risks"] = ["r1"]
q2["implications"] = {"architecture": ["a1", "a2"], "migration": [], "verification": ["v"]}
q2s = json.loads(json.dumps(q2))
cases["summary"].append(
{"name": "qualified-truncation-counts",
"input": {"basis": q2s}, "expected": mask(bb.summary(q2))})
b = fixed_basis(); b["implications"] = None
cases["summary"].append(
{"name": "implications-null", "input": {"basis": b},
"expected": mask(bb.summary(b))})
# ── revise_depth ──────────────────────────────────────────────────────
def rev_case(nm, basis, new_depth, reason, error_expected=False):
before = json.loads(json.dumps(basis))
if error_expected:
try:
bb.revise_depth(basis, new_depth=new_depth, reason=reason)
except ValueError as e:
cases.setdefault("revise_depth", []).append(
{"name": nm, "input": {"basis": before, "new_depth": new_depth,
"reason": reason}, "error": str(e)})
return
raise AssertionError(f"revise_depth {nm} did not raise")
rev = bb.revise_depth(basis, new_depth=new_depth, reason=reason)
cases.setdefault("revise_depth", []).append(
{"name": nm, "input": {"basis": before, "new_depth": new_depth,
"reason": reason},
"expected": {"basis": mask(basis), "revision": mask(rev)}})
rev_case("full-to-inward-justified", fixed_basis(), "INWARD",
"prior-art question resolved as non-material")
rev_case("full-to-inward-blocked", fixed_basis(), "INWARD",
"skip it, not relevant", error_expected=True)
rev_case("no-op", fixed_basis("INWARD"), "INWARD", "same", error_expected=True)
rev_case("unknown-depth", fixed_basis(), "WEIRD", "why not", error_expected=True)
rev_case("outward-to-none-unnecessary", fixed_basis(), "NONE",
"prior-art question unnecessary: internal solution exists")
rev_case("upgrade-full-to-outward", fixed_basis(), "OUTWARD", "scope narrowed")
rev_case("none-to-inward", fixed_basis("LOCAL_FIX"), "INWARD", "repo code matters")
rev_case("reason-truncated-200", fixed_basis(), "OUTWARD", "r" * 300)
rev_case("inward-to-none-keyword", fixed_basis("INWARD"), "NONE",
"discovery showed nothing to record")
# ── to_json (Python json.dumps indent=1 sort_keys semantics) ──────────
cases.setdefault("to_json", []).append(
{"name": "basis", "input": {"basis": fixed_basis()},
"expected": bb.to_json(fixed_basis())})
uni = fixed_basis()
uni["question"] = "héllo 日本語 🎉 quoted \"x\" back\\slash \n\t ctrl\x01"
cases["to_json"].append(
{"name": "unicode-escapes", "input": {"basis": uni},
"expected": bb.to_json(uni)})
cases["to_json"].append(
{"name": "empty-dict", "input": {"basis": {}}, "expected": bb.to_json({})})
mixed = {"b": 1, "a": [1, 2.5, True, None, "x"], "c": {"z": [], "y": {}},
"é": "café", "Z": -17, "nested": {"deep": {"deeper": [{}]},
"flag": False}}
cases["to_json"].append(
{"name": "mixed-sorted", "input": {"basis": mixed},
"expected": bb.to_json(mixed)})
# ── from_json ─────────────────────────────────────────────────────────
cases.setdefault("from_json", []).append(
{"name": "object", "input": {"text": bb.to_json(fixed_basis())},
"expected": mask(json.loads(bb.to_json(fixed_basis())))})
cases["from_json"].append(
{"name": "array", "input": {"text": "[1, \"two\", null, {\"k\": true}]"},
"expected": [1, "two", None, {"k": True}]})
try:
bb.from_json("{bad json")
raise AssertionError("from_json did not raise")
except ValueError as e:
cases["from_json"].append(
{"name": "invalid", "input": {"text": "{bad json"},
"error_kind": "ValueError"})
# ── generate_research_plan: every branch ──────────────────────────────
for nm, kw in (
("no-args-none", dict(intent="fix a typo in the README")),
("unknowns-inward", dict(intent="i", unknowns=["which code path renders tables?"])),
("outward-prior-art", dict(intent="i",
unknowns=["is there an existing Python PDF library?"])),
("full-consequential", dict(intent="migrate the auth schema",
unknowns=["which user tables hold credential hashes?"],
risks=["breaking change to login flow"])),
("consequential-but-no-topics", dict(intent="deploy and delete the old cluster")),
("blank-filtered", dict(intent="i", unknowns=["", " ", "real unknown u"],
decisions=[" ", "d1"], risks=["", "r1"])),
("decisions-and-risks-kinds", dict(intent="i", unknowns=["u1"],
decisions=["d1"], risks=["r1"])),
("truncation-80", dict(intent="i", unknowns=["u" * 120])),
("depth-supplied", dict(intent="i", unknowns=["big unknown"], depth="INWARD")),
("multiword-prior-art", dict(intent="i",
unknowns=["prior art on vector stores?"])),
("target-and-reality", dict(intent="i", target="wr-bridge",
current_reality="c" * 300,
unknowns=["u"], depth="NONE")),
("tool-framework-keywords", dict(intent="i",
unknowns=["which framework/tool convention applies?"])),
):
plan = bb.generate_research_plan(**kw)
cases.setdefault("generate_research_plan", []).append(
{"name": nm, "input": kw, "expected": mask(plan)})
truth = {
"frozen": "build_basis full oracle truth (dichotomy wave #2479)",
"oracle": "src/rune/build_basis.py",
"note": ("Timestamp fields the kernel GENERATES (decided_at, created_at, "
"revised_at, plan_generated_at) are masked on both sides — wall "
"clock, not logic. created_at values inside basis INPUTS are "
"fixed pass-through and compare exactly. error = the exact "
"Python ValueError message."),
"cases": cases,
}
out = Path(__file__).resolve().parents[1] / "src" / "rust" / "build_basis" / "src" / "frozen_bb_truth.json"
out.write_text(json.dumps(truth, indent=1, sort_keys=True) + "\n")
total = sum(len(v) for v in cases.values())
print(f"frozen {total} cases across {len(cases)} functions -> {out}")
+362
View File
@@ -0,0 +1,362 @@
#!/usr/bin/env python3
"""Freeze construction_pattern oracle truth for the Rust port battery
(dichotomy wave #2479 tranche, batch: construction_pattern).
Exercises EVERY public function and branch of
src/rune/construction_pattern.py:
ConstructionPattern.__init__ (defaults, sorting, ext, all validation errors)
ConstructionPattern.to_dict / from_dict (roundtrip incl. ext)
ConstructionPattern.revise (changes, history accumulation, 200-char
reason truncation, sorted evidence/changed)
record_pattern_proposal (bounds errors, evidence sorting, 0/1 bounds)
evaluate_applicability (exclusions by objective-substring and by
constraint membership, first-match-wins,
no-signals UNKNOWN, no-groups UNKNOWN,
surface_kinds via surface lists AND via
resolutions, short-token vs containment path
and entity matching, ':'-prefix entities,
objective_any '/' splitting (dashes NOT
split), contracts_match via authorities
(implements/exposes/provides/serves),
APPLIES / PARTIAL / DOES_NOT_APPLY,
evidence [:5] truncation)
derive_pattern_obligations (when None, always True vs truthy-not-True,
path_contains hit/miss + [:3] truncation,
entities_match via paths/ui/regions,
contracts_touched via need constraints and
via authority relationships, unrecognized
shapes, EXCEPTED with defaults,
accepted/rejected/unresolved routing)
combine_pattern_obligations (duplicate collapse + also_from, same-id
different-text conflicts, id sort, empty)
Each case records its INPUT and the oracle's EXPECTED output/error; replayed
VERBATIM by src/rust/construction_pattern/src/branch_battery.rs.
Output: src/rust/construction_pattern/src/frozen_cp_truth.json
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
os.environ["RUNE_CP_CORE"] = "python" # POISONED-TRUTH LAW: freeze the ORACLE
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import construction_pattern as cp # noqa: E402
OUT = Path(__file__).resolve().parents[1] / "src" / "rust" / "construction_pattern" / "src" / "frozen_cp_truth.json"
cases = []
def need(objective="repair the deploy promotion ordering",
affected_surface=("scripts/deploy_stage.py", "scripts/deploy_promotion.py"),
constraints=None):
class N:
pass
n = N()
n.objective = objective
n.affected_surface = list(affected_surface)
n.constraints = dict(constraints if constraints is not None else {"contracts": ["contract:monotonic_promotion"]})
return n
def serialize_need(n):
return {"objective": n.objective, "affected_surface": n.affected_surface, "constraints": n.constraints}
SPINE = {
"entities": [
{"id": "module:deploy_stage", "kind": "module"},
{"id": "contract:monotonic_promotion", "kind": "contract"},
{"id": "module:deploy_qualify", "kind": "module"},
{"id": "contract:fail_closed_deployment", "kind": "contract"},
],
"relationships": [
{"type": "implements", "from": "module:deploy_stage", "to": "contract:monotonic_promotion"},
{"type": "exposes", "from": "module:deploy_stage", "to": "contract:exposed_api"},
{"type": "serves", "from": "module:deploy_stage", "to": "contract:served_edge"},
{"type": "implements", "from": "module:deploy_qualify", "to": "contract:fail_closed_deployment"},
{"type": "provides", "from": "module:deploy_qualify", "to": "contract:provided_gate"},
{"type": "serves", "from": "module:deploy_qualify", "to": "contract:second_edge"},
{"type": "depends_on", "from": "module:deploy_stage", "to": "module:deploy_qualify"},
],
}
AUTH = {"rune_spine": SPINE}
SURFACE = {
"affected_paths": [
"scripts/deploy_stage.py", "scripts/deploy_promotion.py",
"src/rune/rune_tui.py", "src/rune/builtin.py",
],
"affected_existing_entities": ["module:deploy_stage", "module:deploy_qualify", "ui:chat"],
"affected_ui_surfaces": ["ui:version_display"],
"affected_regions": ["region:left"],
"workflow_config_surfaces": [".gitea/workflows/deploy.yml"],
"resolutions": [
{"id": "module:deploy_stage", "kind": "source_path", "verdict": "FOUND"},
{"id": "ui:version_display", "kind": "ui_component", "verdict": "FOUND"},
{"id": "region:left", "kind": "layout_region", "verdict": "FOUND"},
{"id": "cfg:deploy", "kind": "workflow_config", "verdict": "FOUND"},
{"id": "module:deploy_stage", "kind": "structural_entity", "verdict": "FOUND"},
],
}
def case(name, op, inp, fn):
import copy as _copy
# Deep-copy the input BEFORE running the oracle: to_dict() shares
# nested references (ext) with the live object, and revise() mutates
# revision_history in place — without the copy the recorded input
# aliases oracle-mutated state.
inp = _copy.deepcopy(inp)
try:
expected = fn()
except ValueError as e:
expected = {"error": str(e)}
cases.append({"name": name, "op": op, "input": inp, "expected": expected})
# ── ConstructionPattern.__init__ / to_dict / from_dict ──────────────────────
FULL_KW = dict(
revision=3, maturity="EXERCISED",
signals={"path_contains": ["deploy"], "objective_any": ["promotion"]},
obligations=[
{"id": "o2", "obligation": "second duty", "when": {"always": True}},
{"id": "o1", "obligation": "first duty", "when": {"path_contains": ["deploy"]}},
],
provenance=["incident:zeta", "incident:alpha"],
exclusions=["queue-only-project", "no-deploy-zone"],
toolchain_notes=["zinc", "amber"],
runtime_effects=["r2", "r1"],
)
def construct_case(name, kwargs):
case(name, "construct", {"kwargs": kwargs},
lambda: cp.ConstructionPattern.from_dict(kwargs).to_dict())
construct_case("construct-full", {"pattern_id": "p.full", "purpose": "full pattern",
"wibble": "future-data", **FULL_KW})
construct_case("construct-minimal", {"pattern_id": "p.min", "purpose": "minimal pattern"})
construct_case("construct-ext-open-world", {"pattern_id": "p.ext", "purpose": "open world",
"wibble": "future-data", "count": 7})
construct_case("construct-empty-id", {"pattern_id": "", "purpose": "purpose"})
construct_case("construct-empty-purpose", {"pattern_id": "p", "purpose": ""})
construct_case("construct-obl-missing-id",
{"pattern_id": "p", "purpose": "purpose",
"obligations": [{"obligation": "x", "when": {}}]})
construct_case("construct-obl-missing-text",
{"pattern_id": "p", "purpose": "purpose", "obligations": [{"id": "x", "when": {}}]})
construct_case("construct-obl-missing-when",
{"pattern_id": "p", "purpose": "purpose", "obligations": [{"id": "x", "obligation": "x"}]})
construct_case("construct-from-dict-roundtrip",
cp.ConstructionPattern("p.rt", "roundtrip", wibble={"deep": [1, 2]}, **FULL_KW).to_dict())
# ── revise ───────────────────────────────────────────────────────────────────
REV_BASE = dict(revision=2, maturity="UNIT-PROVEN",
provenance=["contract:c9"], exclusions=["b-ex", "a-ex"])
def revise_case(name, base_kwargs, reason, evidence, changes, ext=None):
base = cp.ConstructionPattern("p.rev", "revisable", **(ext or {}), **base_kwargs)
case(name, "revise",
{"pattern": base.to_dict(), "reason": reason, "evidence": evidence, "changes": changes},
lambda: base.revise(reason=reason, evidence=evidence, changes=changes).to_dict())
revise_case("revise-with-changes", REV_BASE, "new incident evidence",
["z-evidence", "a-evidence"], {"maturity": "EXERCISED"})
revise_case("revise-history-accumulates", {"revision": 2, "revision_history": [
{"prior_revision": 1, "reason": "initial correction", "evidence": ["old-evidence"], "changed": ["signals"]}]},
"second revision", ["new-evidence"], {})
revise_case("revise-reason-truncated-200", {}, "r" * 250, ["e"], None)
revise_case("revise-open-world-ext-carried", {}, "carries ext", ["e"], {"purpose": "revised purpose"},
ext={"wibble": "kept"})
# ── record_pattern_proposal ─────────────────────────────────────────────────
def propose_case(name, pattern_id="x", provider="p", model="m", evidence=(), confidence=0.5):
case(name, "propose",
{"pattern_id": pattern_id, "provider": provider, "model": model,
"evidence": list(evidence), "confidence": confidence},
lambda: cp.record_pattern_proposal(pattern_id, provider=provider, model=model,
evidence=list(evidence), confidence=confidence))
propose_case("propose-valid", pattern_id="some-pattern", provider="local", model="m1",
evidence=["z-hunch", "a-hunch"], confidence=0.5)
case("propose-bounds-zero-one", "propose",
{"pattern_id": "x", "provider": "p", "model": "m", "evidence": [], "confidence": [0.0, 1.0]},
lambda: [cp.record_pattern_proposal("x", provider="p", model="m", evidence=[], confidence=0.0),
cp.record_pattern_proposal("x", provider="p", model="m", evidence=[], confidence=1.0)])
propose_case("propose-empty-provider", provider="")
propose_case("propose-empty-model", model="")
propose_case("propose-confidence-above-1", confidence=1.5)
propose_case("propose-confidence-below-0", confidence=-0.1)
# ── evaluate_applicability ──────────────────────────────────────────────────
def eval_case(name, pattern_kwargs, n=None, surface=None, authorities=None):
p = cp.ConstructionPattern("p.eval", "evaluated pattern", **pattern_kwargs)
nn = n or need()
s = surface or SURFACE
a = authorities or AUTH
case(name, "evaluate",
{"pattern": p.to_dict(), "need": serialize_need(nn), "surface_result": s, "authorities": a},
lambda: cp.evaluate_applicability(p, nn, s, a))
eval_case("eval-all-groups-applies",
{"signals": {"surface_kinds": ["source_path", "ui_component", "layout_region",
"workflow_config", "structural_entity"],
"path_contains": ["deploy"], "entities_match": ["module:"],
"objective_any": ["promotion"],
"contracts_match": ["contract:monotonic_promotion"]}})
eval_case("eval-partial",
{"signals": {"path_contains": ["deploy"], "entities_match": ["nope-never", "zzz"],
"objective_any": ["zzzneverword"]}})
eval_case("eval-none-declared-does-not-apply",
{"signals": {"path_contains": ["/nonexistent/xyz/"], "objective_any": ["zzznever"]}})
eval_case("eval-no-signals-unknown", {})
eval_case("eval-no-groups-declared-unknown", {"signals": {"surface_kinds": [], "path_contains": []}})
eval_case("eval-exclusion-objective-substring",
{"signals": {"path_contains": ["deploy"]}, "exclusions": ["no deployment system in this project"]})
eval_case("eval-exclusion-constraint-membership",
{"signals": {"path_contains": ["deploy"]}, "exclusions": ["message-queue-only"]},
n=need(constraints={"contracts": [], "pattern_exclusions": ["message-queue-only"]}))
eval_case("eval-exclusion-first-match-wins",
{"signals": {"path_contains": ["deploy"]},
"exclusions": ["no deployment system in this project", "message-queue-only"]},
n=need(constraints={"contracts": [], "pattern_exclusions": ["message-queue-only"]}))
eval_case("eval-exclusion-declared-but-unmatched",
{"signals": {"path_contains": ["deploy"]}, "exclusions": ["totally-unrelated-exclusion"]},
n=need(constraints={"contracts": [], "pattern_exclusions": ["another-unrelated"]}))
eval_case("eval-surface-kinds-via-resolutions",
{"signals": {"surface_kinds": ["structural_entity", "nope_kind"]}})
eval_case("eval-path-short-token",
{"signals": {"path_contains": ["ui", "tui", "cfg", "deploy"]}})
eval_case("eval-entity-prefix-and-tokens",
{"signals": {"entities_match": ["ui:", "module:", "ui", "spine", "zzz"]}})
eval_case("eval-objective-slash-split",
{"signals": {"objective_any": ["deploy", "release", "widget"]}},
n=need(objective="adjust the release/deploy widget", constraints={"contracts": []}))
eval_case("eval-objective-dash-not-split",
{"signals": {"objective_any": ["deploy", "pre-deploy"]}},
n=need(objective="verify the pre-deploy check", constraints={"contracts": []}))
eval_case("eval-contracts-all-rel-kinds",
{"signals": {"contracts_match": ["contract:monotonic_promotion", "contract:exposed_api",
"contract:served_edge", "contract:fail_closed_deployment",
"contract:provided_gate", "contract:second_edge"]}})
eval_case("eval-contracts-miss-non-rel-type",
{"signals": {"contracts_match": ["module:deploy_qualify"]}})
eval_case("eval-contracts-second-authority",
{"signals": {"contracts_match": ["contract:scaffold_only"]}},
authorities={"rune_spine": SPINE,
"project_scaffold": {"relationships": [
{"type": "implements", "from": "module:deploy_stage", "to": "contract:scaffold_only"}]}})
eval_case("eval-empty-entity-id-quirk",
{"signals": {"contracts_match": ["contract:monotonic_promotion"]}},
surface={**SURFACE, "affected_existing_entities": ["", "module:deploy_qualify"]})
# ── derive_pattern_obligations ──────────────────────────────────────────────
O = lambda oid, text, when: {"id": oid, "obligation": text, "when": when}
def derive_case(name, obligations, n=None, exceptions=None, surface=None, authorities=None):
p = cp.ConstructionPattern("p.derive", "deriving pattern",
provenance=["incident:prov-1", "contract:prov-0"],
obligations=obligations)
nn = n or need()
s = surface or SURFACE
a = authorities or AUTH
case(name, "derive",
{"pattern": p.to_dict(), "need": serialize_need(nn), "surface_result": s,
"authorities": a, "project_exceptions": exceptions},
lambda: cp.derive_pattern_obligations(p, nn, s, a, project_exceptions=exceptions))
derive_case("derive-when-none-unknown", [O("mystery", "do the unconditioned thing", None)])
derive_case("derive-always-required", [O("always-ob", "regression proof", {"always": True})])
derive_case("derive-always-truthy-not-true",
[O("truthy-ob", "truthy always is not always", {"always": 1})])
derive_case("derive-path-hit-truncation",
[O("path-ob", "touch the deploy paths", {"path_contains": ["deploy", "rune"]})])
derive_case("derive-path-miss", [O("path-ob", "touch nothing real", {"path_contains": ["/absent/xyz/"]})])
derive_case("derive-entities-via-ui-and-regions",
[O("ui-ob", "respect the ui surface", {"entities_match": ["version_display"]}),
O("region-ob", "respect the region", {"entities_match": ["left"]}),
O("entity-ob", "respect the module", {"entities_match": ["module:"]}),
O("miss-ob", "matches nothing", {"entities_match": ["zzz-never"]})])
derive_case("derive-contracts-from-need-constraints",
[O("ct-ob", "honor the explicit contract",
{"contracts_touched": ["contract:explicit_need", "contract:absent"]})],
n=need(constraints={"contracts": ["contract:explicit_need"]}))
derive_case("derive-contracts-from-authorities",
[O("ct-ob", "honor the authority-implied contract",
{"contracts_touched": ["contract:provided_gate", "contract:served_edge", "contract:nowhere"]})])
derive_case("derive-contracts-miss",
[O("ct-ob", "cannot honor the unknown contract", {"contracts_touched": ["contract:not_anywhere"]})])
derive_case("derive-unrecognized-shape", [O("weird-ob", "do the unknown thing", {"unsupported_shape": True})])
derive_case("derive-mixed-routing",
[O("acc-ob", "always applies", {"always": True}),
O("rej-ob", "path never matches", {"path_contains": ["/absent/xyz/"]}),
O("unres-ob", "unknown shape", {"weird": 1})])
derive_case("derive-exception-with-defaults",
[O("exc-ob", "suppressed obligation", {"always": True}),
O("kept-ob", "surviving obligation", {"always": True})],
exceptions=[{"obligation": "exc-ob"}])
derive_case("derive-exception-full",
[O("exc-ob", "suppressed obligation", {"always": True})],
exceptions=[{"obligation": "exc-ob", "reason": "project policy documented",
"evidence": "policy doc", "authority": "user"}])
# ── combine_pattern_obligations ─────────────────────────────────────────────
def derivation(pattern_id, rows, revision=1):
"""rows: (id, verdict, text) — mirrors derive_pattern_obligations output."""
out = {"pattern": pattern_id, "revision": revision, "accepted": [], "rejected": [], "unresolved": []}
for oid, verdict, text in rows:
entry = {"id": oid, "obligation": text, "pattern": pattern_id, "revision": revision,
"provenance": "PATTERN", "pattern_provenance": [f"prov:{pattern_id}"],
"verdict": verdict, "reason": "reason"}
out[{"REQUIRED": "accepted", "APPLICABLE": "accepted",
"NOT_APPLICABLE": "rejected"}.get(verdict, "unresolved")].append(entry)
return out
def combine_case(name, derivs):
case(name, "combine", {"derivations": derivs}, lambda: cp.combine_pattern_obligations(derivs))
combine_case("combine-empty", [])
combine_case("combine-single", [derivation("a", [("shared", "REQUIRED", "regression proof"),
("z-only", "APPLICABLE", "z duty")])])
combine_case("combine-duplicate-collapse", [
derivation("a", [("shared", "REQUIRED", "regression proof")]),
derivation("b", [("shared", "REQUIRED", "regression proof")]),
derivation("c", [("shared", "REQUIRED", "regression proof")])])
combine_case("combine-conflict-different-text", [
derivation("x", [("shared", "REQUIRED", "version A of the rule")]),
derivation("y", [("shared", "REQUIRED", "version B of the rule")])])
combine_case("combine-sort-by-id", [
derivation("p1", [("zz", "REQUIRED", "last"), ("aa", "REQUIRED", "first")]),
derivation("p2", [("mm", "APPLICABLE", "middle")])])
combine_case("combine-conflict-then-continue", [
derivation("x", [("bad", "REQUIRED", "text one"), ("ok", "REQUIRED", "fine duty")]),
derivation("y", [("bad", "REQUIRED", "text two")])])
# ── write truth ──────────────────────────────────────────────────────────────
OUT.write_text(json.dumps({"generator": "scripts/gen_construction_pattern_truth.py",
"module": "src/rune/construction_pattern.py", "cases": cases}, indent=1) + "\n")
print(f"{len(cases)} cases -> {OUT}")
+487
View File
@@ -0,0 +1,487 @@
#!/usr/bin/env python3
"""gen_drift_observability_truth.py — freeze/replay parity truth for
drift_observability (dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/drift_observability.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/drift_observability/src/frozen_do_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle methods delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_DO_CORE=python BEFORE any import.
No-clock law: the wall clock is frozen by patching the datetime symbol in
BOTH drift_observability and drift_core, so every kernel payload carries
its own `now` and replay is deterministic regardless of run date.
"""
import copy
import json
import os
import sqlite3
import sys
import tempfile
from datetime import datetime, timedelta, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(
REPO, "src", "rust", "drift_observability", "src", "frozen_do_truth.json"
)
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_DO_CORE"] = "python" # poisoned-truth law
os.environ["RUNE_MEMORY_CORE"] = "python"
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import drift_core # noqa: E402
import drift_observability as oracle_mod # noqa: E402
if REPLAY:
import do_loader # noqa: E402 (kernel-level replay side)
FROZEN_UTC = datetime(2026, 10, 11, 12, 0, 0, 123456, tzinfo=timezone.utc)
FROZEN_LOCAL = datetime(2026, 10, 11, 13, 0, 0, 123456) # +01:00 wall clock
FROZEN_NOW = FROZEN_UTC.isoformat()
FROZEN_LOCAL_ISO = FROZEN_LOCAL.isoformat()
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
if tz is not None:
return FROZEN_UTC.astimezone(tz)
return FROZEN_LOCAL
oracle_mod.datetime = FrozenDatetime
drift_core.datetime = FrozenDatetime
def ago(**kw):
return (FROZEN_UTC - timedelta(**kw)).isoformat()
def row(rock="washable", created=None, washed=None, accessed=None, status="active"):
return (status, rock, created, washed, accessed)
def make_db(rows):
db = sqlite3.connect(":memory:")
db.execute(
"CREATE TABLE entries (status TEXT, rock_status TEXT, created_at TEXT,"
" last_washed TEXT, last_accessed TEXT)"
)
db.executemany("INSERT INTO entries VALUES (?,?,?,?,?)", rows)
return db
_MAX_LAST_WASHED_SQL = (
"SELECT MAX(last_washed) FROM entries "
"WHERE rock_status != 'washable' AND status = 'active'"
)
class Case:
def __init__(self, name, fn):
self.name = name
self.fn = fn
def fresh_obs(alerts_pre=None):
tmp = tempfile.mkdtemp(prefix="do_truth_")
oracle_mod.ALERTS_FILE = os.path.join(tmp, "drift_alerts.json")
oracle_mod.RETROSPECTIVES_DIR = os.path.join(tmp, "retrospectives")
obs = oracle_mod.DriftObservability()
if alerts_pre is not None:
obs.alerts = copy.deepcopy(alerts_pre)
return obs
def _stamped(alert_type, message, severity):
return {
"type": alert_type,
"message": message,
"severity": severity,
"timestamp": FROZEN_NOW,
"acknowledged": False,
}
# ── add_alert ────────────────────────────────────────────────────────────────
def c_add_default(rec):
obs = fresh_obs()
rec("add-default-severity", {"alerts": [], "alert_type": "high_velocity",
"message": "v up", "severity": "info", "now": FROZEN_NOW},
obs.add_alert("high_velocity", "v up"), {"alerts": obs.alerts})
def c_add_warning(rec):
obs = fresh_obs()
rec("add-warning-severity", {"alerts": [], "alert_type": "negative_drift",
"message": "m", "severity": "warning", "now": FROZEN_NOW},
obs.add_alert("negative_drift", "m", "warning"), {"alerts": obs.alerts})
def c_add_critical(rec):
obs = fresh_obs()
rec("add-critical-severity", {"alerts": [], "alert_type": "promotion_stall",
"message": "stalled", "severity": "critical", "now": FROZEN_NOW},
obs.add_alert("promotion_stall", "stalled", "critical"), {"alerts": obs.alerts})
def c_add_onto_existing(rec):
pre = [{"type": "old", "message": "m0", "severity": "info",
"timestamp": ago(days=1), "acknowledged": True}]
obs = fresh_obs(pre)
rec("add-onto-existing", {"alerts": pre, "alert_type": "velocity",
"message": "second", "severity": "info", "now": FROZEN_NOW},
obs.add_alert("velocity", "second"), {"alerts": obs.alerts})
# ── get_unacknowledged_alerts ────────────────────────────────────────────────
def _pre_mixed():
return [
{"type": "a", "message": "m1", "severity": "info",
"timestamp": ago(days=3), "acknowledged": True},
{"type": "b", "message": "m2", "severity": "warning",
"timestamp": ago(days=2), "acknowledged": False},
{"type": "c", "message": "m3", "severity": "critical",
"timestamp": ago(days=1)}, # missing key → unacknowledged
]
def c_unacked_empty(rec):
obs = fresh_obs([])
rec("unacked-empty", {"alerts": []},
obs.get_unacknowledged_alerts(), {"unacked": []})
def c_unacked_mixed(rec):
pre = _pre_mixed()
obs = fresh_obs(pre)
rec("unacked-mixed", {"alerts": pre},
obs.get_unacknowledged_alerts(), {"unacked": obs.get_unacknowledged_alerts()})
def c_unacked_all_open(rec):
pre = [dict(_stamped("t", "m", "info"), timestamp=ago(days=1)) for _ in range(3)]
obs = fresh_obs(pre)
rec("unacked-all-open", {"alerts": pre},
obs.get_unacknowledged_alerts(), {"unacked": obs.get_unacknowledged_alerts()})
# ── acknowledge_alert ────────────────────────────────────────────────────────
def _ack_case(rec, name, index):
pre = _pre_mixed()
obs = fresh_obs(pre)
obs.acknowledge_alert(index)
changed = 0 <= index < len(pre)
rec(name, {"alerts": pre, "index": index},
None, {"alerts": obs.alerts, "changed": changed})
def c_ack_first(rec):
_ack_case(rec, "ack-first", 0)
def c_ack_second(rec):
_ack_case(rec, "ack-second", 1)
def c_ack_negative(rec):
_ack_case(rec, "ack-negative", -1)
def c_ack_at_len(rec):
_ack_case(rec, "ack-at-len", 3)
def c_ack_beyond(rec):
_ack_case(rec, "ack-beyond", 8)
# ── check_alerts ─────────────────────────────────────────────────────────────
def _check_case(rec, name, rows):
db = make_db(rows)
velocity = drift_core.compute_drift_velocity(db)
direction = drift_core.compute_drift_direction(db)
last = db.execute(_MAX_LAST_WASHED_SQL).fetchone()[0]
obs = fresh_obs([])
new = obs.check_alerts(db)
rec(name,
{"alerts": [], "velocity": velocity, "direction": direction,
"last_washed": last, "now": FROZEN_NOW},
new, {"new_alerts": new, "alerts": obs.alerts})
def c_check_empty_db(rec):
# Empty db: stability_ratio 0.0 < 0.1 — the negative-drift alert FIRES.
_check_case(rec, "check-empty-db-negative-drift", [])
def c_check_all_clear(rec):
rows = [row(rock="established", created=ago(days=30), washed=ago(days=1),
accessed=ago(hours=1)) for _ in range(10)]
_check_case(rec, "check-all-clear", rows)
def c_check_velocity_only(rec):
rows = [row(created=ago(days=1), washed=ago(days=1)) for _ in range(71)]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=1))
for _ in range(30)]
_check_case(rec, "check-velocity-only", rows)
def c_check_negative_drift_only(rec):
rows = [row(rock="established", created=ago(days=30), washed=ago(days=1))]
rows += [row(created=ago(days=1), washed=ago(days=1)) for _ in range(19)]
_check_case(rec, "check-negative-drift-only", rows)
def c_check_stall_only(rec):
rows = [row(rock="established", created=ago(days=40), washed=ago(days=20))
for _ in range(10)]
_check_case(rec, "check-stall-only", rows)
def c_check_stall_boundary(rec):
rows = [row(rock="established", created=ago(days=40), washed=ago(days=14))
for _ in range(10)]
_check_case(rec, "check-stall-boundary-14d-no-alert", rows)
def c_check_stall_15(rec):
rows = [row(rock="established", created=ago(days=40), washed=ago(days=15))
for _ in range(10)]
_check_case(rec, "check-stall-15d-fires", rows)
def c_check_malformed_last_washed(rec):
# MAX picks "garbage" ('g' > '2'); oracle fromisoformat → ValueError skip.
rows = [row(rock="established", created=ago(days=30), washed="garbage")]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=30))
for _ in range(9)]
_check_case(rec, "check-malformed-last-washed-skipped", rows)
def c_check_naive_last_washed(rec):
# Naive (no offset) MAX(last_washed): aware − naive → TypeError skip.
naive = (FROZEN_UTC - timedelta(days=14)).replace(tzinfo=None).isoformat()
rows = [row(rock="established", created=ago(days=30), washed=naive)]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=30))
for _ in range(9)]
_check_case(rec, "check-naive-last-washed-skipped", rows)
def c_check_null_last_washed(rec):
# All-NULL MAX → falsy → skip.
rows = [row(rock="established", created=ago(days=30)) for _ in range(10)]
_check_case(rec, "check-null-last-washed-skipped", rows)
def c_check_empty_string_last_washed(rec):
# MAX = "" → falsy → the oracle's `if last_promo and last_promo[0]` skips.
rows = [row(rock="established", created=ago(days=30), washed="")
for _ in range(10)]
_check_case(rec, "check-empty-string-last-washed-skipped", rows)
def c_check_all_three(rec):
rows = [row(created=ago(days=1), washed=ago(days=1)) for _ in range(100)]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=20))
for _ in range(3)]
_check_case(rec, "check-all-three", rows)
# ── run_retrospective ────────────────────────────────────────────────────────
def _retro_case(rec, name, rows, days):
db = make_db(rows)
cutoff = (FROZEN_UTC - timedelta(days=days)).isoformat()
counts = {
"new_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE created_at > ? AND status = 'active'",
(cutoff,)).fetchone()[0],
"washed_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE last_washed > ? AND status = 'active'",
(cutoff,)).fetchone()[0],
"promoted_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE rock_status != 'washable'"
" AND last_washed > ? AND status = 'active'",
(cutoff,)).fetchone()[0],
"decayed_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE status = 'deleted'"
" AND last_accessed > ?", (cutoff,)).fetchone()[0],
}
velocity = drift_core.compute_drift_velocity(db, days)
direction = drift_core.compute_drift_direction(db)
obs = fresh_obs([])
report = obs.run_retrospective(db, days)
inp = dict(counts, velocity=velocity, direction=direction, days=days,
now=FROZEN_NOW, now_local=FROZEN_LOCAL_ISO)
rec(name, inp, report,
{"report": report, "filename": FROZEN_LOCAL.strftime("%Y%m%d_%H%M%S")})
def c_retro_positive(rec):
rows = [row(rock="established", created=ago(days=30), washed=ago(days=1))
for _ in range(10)]
rows.append(row(created=ago(days=30), status="deleted", accessed=ago(hours=2)))
_retro_case(rec, "retro-positive", rows, 7)
def c_retro_neutral_high_velocity(rec):
rows = [row(created=ago(days=1), washed=ago(days=1)) for _ in range(71)]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=1))
for _ in range(40)]
_retro_case(rec, "retro-neutral-high-velocity", rows, 7)
def c_retro_neutral_low_velocity(rec):
rows = [row(rock="established", created=ago(days=30), washed=ago(days=30))
for _ in range(2)]
rows += [row(created=ago(days=30), washed=ago(days=30)) for _ in range(8)]
rows.append(row(status="deleted", accessed=ago(days=1)))
_retro_case(rec, "retro-neutral-low-velocity", rows, 7)
def c_retro_concerning(rec):
rows = [row(rock="established", created=ago(days=30), washed=ago(days=30))]
rows += [row(created=ago(days=30), washed=ago(days=30)) for _ in range(19)]
_retro_case(rec, "retro-concerning", rows, 7)
def c_retro_concerning_boundary(rec):
rows = [row(rock="established", created=ago(days=30), washed=ago(days=30))]
rows += [row(created=ago(days=30), washed=ago(days=30)) for _ in range(9)]
_retro_case(rec, "retro-concerning-boundary-ratio-0.1", rows, 7)
def c_retro_velocity_boundary(rec):
rows = [row(created=ago(days=1), washed=ago(days=1)) for _ in range(70)]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=30))
for _ in range(40)]
_retro_case(rec, "retro-positive-velocity-boundary-10", rows, 7)
def c_retro_custom_days(rec):
rows = [row(created=ago(days=1), washed=ago(days=1)) for _ in range(5)]
rows += [row(rock="established", created=ago(days=30), washed=ago(days=2))
for _ in range(2)]
rows.append(row(status="deleted", accessed=ago(days=2)))
_retro_case(rec, "retro-custom-days-3", rows, 3)
def c_retro_empty_db(rec):
_retro_case(rec, "retro-empty-db-concerning", [], 7)
def c_retro_default_days(rec):
rows = [row(created=ago(days=2), washed=ago(days=2)) for _ in range(3)]
_retro_case(rec, "retro-default-days-7", rows, 7)
# ── driver ───────────────────────────────────────────────────────────────────
ADD = [c_add_default, c_add_warning, c_add_critical, c_add_onto_existing]
UNACKED = [c_unacked_empty, c_unacked_mixed, c_unacked_all_open]
ACK = [c_ack_first, c_ack_second, c_ack_negative, c_ack_at_len, c_ack_beyond]
CHECK = [c_check_empty_db, c_check_all_clear, c_check_velocity_only,
c_check_negative_drift_only, c_check_stall_only, c_check_stall_boundary,
c_check_stall_15, c_check_malformed_last_washed, c_check_naive_last_washed,
c_check_null_last_washed, c_check_empty_string_last_washed,
c_check_all_three]
RETRO = [c_retro_positive, c_retro_neutral_high_velocity,
c_retro_neutral_low_velocity, c_retro_concerning,
c_retro_concerning_boundary, c_retro_velocity_boundary,
c_retro_custom_days, c_retro_empty_db, c_retro_default_days]
GROUPS = [("add_alert", ADD), ("get_unacknowledged_alerts", UNACKED),
("acknowledge_alert", ACK), ("check_alerts", CHECK),
("run_retrospective", RETRO)]
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, cases in GROUPS:
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs):
entry = {"name": name, "input": inp, "return": oracle_ret,
"expected": expected}
if REPLAY:
old = next(c for c in frozen[group] if c["name"] == name)
for key in ("input", "return", "expected"):
got, want = entry[key], old[key]
if json.dumps(got, sort_keys=True) != json.dumps(want, sort_keys=True):
print(f"FACADE DIVERGE [{group}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(want, sort_keys=True)[:600])
recs.append({"name": name, "diverged": key})
return
_r.append(entry)
for c in cases:
c(rec)
groups[group] = recs
total = sum(len(v) for v in groups.values())
if REPLAY:
facade_failures = sum(1 for v in groups.values() for c in v if "diverged" in c)
# Kernel-level: the Rust core must reproduce the frozen expected
# from the frozen input even WITHOUT the facade in the loop. The
# loader unwraps the doc-envelope for the list-returning calls.
unwrap_want = {
"add_alert": lambda w: w["alerts"],
"get_unacknowledged_alerts": lambda w: w["unacked"],
"acknowledge_alert": lambda w: w,
"check_alerts": lambda w: w,
"run_retrospective": lambda w: w,
}
failures = 0
for group, cases in frozen.items():
for case in cases:
got = getattr(do_loader, group)(**copy.deepcopy(case["input"]))
want = unwrap_want[group](case["expected"])
if json.dumps(got, sort_keys=True) != json.dumps(want, sort_keys=True):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(want, sort_keys=True)[:600])
print(f"replay: facade {total - facade_failures}/{total}, "
f"kernel {total - failures}/{total}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/drift_observability.py",
"pin": "RUNE_DO_CORE=python",
"note": "Frozen via scripts/gen_drift_observability_truth.py against "
"the live Python oracle; wall clock frozen at "
+ FROZEN_NOW + ". Battery embeds this VERBATIM.",
"cases": groups,
}
os.makedirs(os.path.dirname(TRUTH_PATH), exist_ok=True)
with open(TRUTH_PATH, "w") as f:
json.dump(doc, f, indent=1, sort_keys=True)
f.write("\n")
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
if __name__ == "__main__":
sys.exit(main())
+3
View File
@@ -0,0 +1,3 @@
module rune/message_queue
go 1.22
+633
View File
@@ -0,0 +1,633 @@
// Package main — the FULL GO port of src/rune/message_queue.py (#2479
// dichotomy tranche, GO side verdict). Every public Python function has a
// name-preserving counterpart: MessageQueue (__init__ → NewMessageQueue,
// _load_queue → loadQueue, _save_queue → saveQueue, detect_task_addition,
// add_message, get_pending, approve, reject, approve_all, clear,
// get_approved, mark_executed, get_status) and the module CLI main()
// (exact stdout, incl. the ✅/❌ lines). IO (queue file, home dir, clock)
// stays behind injectable seams so the frozen truth battery replays
// deterministically.
//
// Supersedes the batch-61/148 partial in ../message-queue (which carried
// only the detect_task_addition scoring kernel); that dir stays as the
// scored-kernel record, this module is the FULL surface.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
)
// ── configuration ────────────────────────────────────────────────────────
// TASK_ADDITION_PATTERNS — the oracle's 25-row table, 1:1.
var taskAdditionPatterns = []string{
`\balso\b`,
`\band can you\b`,
`\bplus\b`,
`\badditionally\b`,
`\bafter that\b`,
`\bthen\b`,
`\balso need\b`,
`\balso want\b`,
`\bcan you also\b`,
`\bcan you add\b`,
`\bcan you update\b`,
`\bcan you fix\b`,
`\bcan you create\b`,
`\bcan you implement\b`,
`\bcan you build\b`,
`\bmake sure\b`,
// the oracle stores this as a RAW string — the backslash before the
// apostrophe is part of the pattern AND of the emitted signal text
`\bdon\'t forget\b`,
`\bremember to\b`,
`\binclude\b`,
`\badd\b.*\bto\b`,
`\bupdate\b.*\bto\b`,
`\bfix\b.*\bto\b`,
`\bcreate\b.*\bto\b`,
`\bimplement\b.*\bto\b`,
`\bbuild\b.*\bto\b`,
}
var compiledPatterns = func() []*regexp.Regexp {
out := make([]*regexp.Regexp, len(taskAdditionPatterns))
for i, p := range taskAdditionPatterns {
out[i] = regexp.MustCompile(p)
}
return out
}()
var imperativeVerbs = []string{
"add", "update", "fix", "create", "implement", "build", "make",
"change", "remove", "delete", "refactor", "migrate",
}
var fileRefRe = regexp.MustCompile(`\b\w+\.\w+\b`)
// ── status lattice (Phase B: typed, not string soup) ─────────────────────
// MessageStatus — the lifecycle state of a queued message. A typed lattice
// replaces bare string comparisons: illegal states are unrepresentable at
// assignment sites, and the JSON boundary converts explicitly.
type MessageStatus string
const (
StatusPending MessageStatus = "pending"
StatusApproved MessageStatus = "approved"
StatusRejected MessageStatus = "rejected"
StatusExecuted MessageStatus = "executed"
)
// parseMessageStatus — JSON-boundary conversion; unknown values pass
// through verbatim (the store is load-bearing data, the oracle round-trips
// whatever it wrote).
func parseMessageStatus(s string) MessageStatus { return MessageStatus(s) }
// ── data structures ──────────────────────────────────────────────────────
// QueuedMessage — the QueuedMessage dataclass (field order is load-bearing
// for the JSON store bytes: id, message, timestamp, channel, status,
// task_id, confidence, signals).
type QueuedMessage struct {
ID string
Message string
Timestamp string
Channel string
Status MessageStatus // pending, approved, rejected, executed
TaskID *string
Confidence float64
Signals []string
}
// toDict — asdict(), Python key order; nil optionals emit like the
// dataclass defaults (task_id: null, signals: []).
func (m *QueuedMessage) toDict() *OrderedMap {
signals := m.Signals
if signals == nil {
signals = []string{}
}
var taskID any
if m.TaskID != nil {
taskID = *m.TaskID
}
items := make([]any, 0, len(signals))
for _, s := range signals {
items = append(items, s)
}
return NewOrderedMap(
KV{"id", m.ID},
KV{"message", m.Message},
KV{"timestamp", m.Timestamp},
KV{"channel", m.Channel},
KV{"status", string(m.Status)},
KV{"task_id", taskID},
KV{"confidence", m.Confidence},
KV{"signals", items},
)
}
// queuedMessageFromDict rebuilds a QueuedMessage from a parsed JSON object
// (absent optional fields behave like the dataclass defaults).
func queuedMessageFromDict(om *OrderedMap) QueuedMessage {
m := QueuedMessage{
Status: StatusPending,
Signals: []string{},
}
if v, ok := om.Get("id"); ok {
m.ID, _ = v.(string)
}
if v, ok := om.Get("message"); ok {
m.Message, _ = v.(string)
}
if v, ok := om.Get("timestamp"); ok {
m.Timestamp, _ = v.(string)
}
if v, ok := om.Get("channel"); ok {
m.Channel, _ = v.(string)
}
if v, ok := om.Get("status"); ok {
if s, ok2 := v.(string); ok2 {
m.Status = parseMessageStatus(s)
}
}
if v, ok := om.Get("task_id"); ok {
if s, ok2 := v.(string); ok2 {
m.TaskID = &s
}
}
if v, ok := om.Get("confidence"); ok {
if f, ok2 := v.(float64); ok2 {
m.Confidence = f
}
}
if v, ok := om.Get("signals"); ok {
if arr, ok2 := v.([]any); ok2 {
m.Signals = make([]string, 0, len(arr))
for _, item := range arr {
s, _ := item.(string)
m.Signals = append(m.Signals, s)
}
}
}
return m
}
// ── seams (swapped per truth row by replay.go) ───────────────────────────
var (
// defaultHomeDir — os.UserHomeDir() for the default queue root.
defaultHomeDir = func() string {
h, err := os.UserHomeDir()
if err != nil {
return ""
}
return h
}
// defaultClockNow — datetime.now(timezone.utc); add_message calls it
// twice (msg_id first, timestamp second), exactly like the oracle.
defaultClockNow = func() time.Time {
return time.Now().UTC()
}
)
// Seams — swapped per truth row by replay.go, restored after each row.
var (
HomeDir = defaultHomeDir
ClockNow = defaultClockNow
)
func defaultQueueDir() string {
return filepath.Join(HomeDir(), ".rune", "message_queue")
}
// isoformat — datetime.isoformat() on an aware UTC datetime: fractional
// part only when microseconds are nonzero, offset always "+00:00" shape.
func isoformat(t time.Time) string {
base := t.Format("2006-01-02T15:04:05")
if us := t.Nanosecond() / 1000; us != 0 {
base += fmt.Sprintf(".%06d", us)
}
return base + t.Format("-07:00")
}
// ── queue management (MessageQueue) ──────────────────────────────────────
// QueueStore — the persistence seam (Phase B: the interface is the
// contract; the file implementation is swappable, in-memory stores slot in
// for tests). Load/Save carry the RAW queue.json bytes — encoding parity
// lives in saveQueue, not in the store.
type QueueStore interface {
Load() ([]byte, error)
Save(data []byte) error
}
// compile-time: the file store satisfies the seam
var _ QueueStore = (*FileQueueStore)(nil)
// FileQueueStore — queue.json under the queue dir.
type FileQueueStore struct {
Path string
}
func (s *FileQueueStore) Load() ([]byte, error) { return os.ReadFile(s.Path) }
func (s *FileQueueStore) Save(data []byte) error {
return os.WriteFile(s.Path, data, 0o644)
}
// MessageQueue — manages the message queue for task additions. Safe for
// concurrent use (Phase B: RWMutex — the TUI consumes this module from
// multiple goroutines; the Python oracle relied on the GIL).
type MessageQueue struct {
Store QueueStore
mu sync.RWMutex
queue []QueuedMessage
}
// NewMessageQueue — MessageQueue.__init__: makedirs(QUEUE_DIR,
// exist_ok=True) then _load_queue, over the default file store.
func NewMessageQueue(queueDir string) *MessageQueue {
if err := os.MkdirAll(queueDir, 0o777); err != nil {
panic(fmt.Sprintf("makedirs %s: %v", queueDir, err))
}
return NewMessageQueueWithStore(&FileQueueStore{
Path: filepath.Join(queueDir, "queue.json"),
})
}
// NewMessageQueueWithStore — constructor for injectable stores (tests,
// embedded callers). Loads immediately, like __init__.
func NewMessageQueueWithStore(store QueueStore) *MessageQueue {
q := &MessageQueue{Store: store}
q.queue = q.loadQueue()
return q
}
// loadQueue — _load_queue: missing file → empty; a malformed file raises
// unhandled in the oracle (json.JSONDecodeError), mirrored as a panic.
func (q *MessageQueue) loadQueue() []QueuedMessage {
raw, err := q.Store.Load()
if err != nil {
return []QueuedMessage{}
}
dec := json.NewDecoder(bytes.NewReader(raw))
tok, err := dec.Token()
if err != nil {
panic(fmt.Sprintf("queue.json: %v", err))
}
if d, ok := tok.(json.Delim); !ok || d != '[' {
panic("queue.json: not a JSON array")
}
out := []QueuedMessage{}
for dec.More() {
v, err := decodeJSONValue(dec)
if err != nil {
panic(fmt.Sprintf("queue.json: %v", err))
}
om, ok := v.(*OrderedMap)
if !ok {
panic("queue.json: item not an object")
}
out = append(out, queuedMessageFromDict(om))
}
if _, err := dec.Token(); err != nil {
panic(fmt.Sprintf("queue.json: %v", err))
}
return out
}
// saveQueue — _save_queue: json.dump([asdict(msg) ...], indent=2) —
// byte-parity via the Python JSON encoder, persisted through the store.
func (q *MessageQueue) saveQueue() {
items := make([]*OrderedMap, 0, len(q.queue))
for i := range q.queue {
items = append(items, q.queue[i].toDict())
}
if err := q.Store.Save([]byte(encodePythonJSON(items, 0))); err != nil {
panic(fmt.Sprintf("save queue: %v", err))
}
}
// snapshot — a private copy of the queue under RLock (callers outside the
// mutex must not share the backing array).
func (q *MessageQueue) snapshot() []QueuedMessage {
q.mu.RLock()
defer q.mu.RUnlock()
out := make([]QueuedMessage, len(q.queue))
copy(out, q.queue)
return out
}
// detectTaskAddition — returns (is_task_addition, confidence, signals);
// Python-identical scoring: patterns +0.3 (table order), imperative-verb
// prefix +0.4, file refs +0.2, "also"-first +0.5; cap 1.0; detected iff
// confidence > 0.5.
func (q *MessageQueue) detectTaskAddition(message string) (bool, float64, []string) {
signals := []string{}
confidence := 0.0
messageLower := strings.ToLower(message)
for i, re := range compiledPatterns {
if re.MatchString(messageLower) {
signals = append(signals, fmt.Sprintf("Pattern: %s", taskAdditionPatterns[i]))
confidence += 0.3
}
}
for _, verb := range imperativeVerbs {
if strings.HasPrefix(messageLower, verb) {
signals = append(signals, fmt.Sprintf("Imperative verb: %s", verb))
confidence += 0.4
}
}
fileRefs := fileRefRe.FindAllString(message, -1)
if len(fileRefs) > 0 {
head := fileRefs
if len(head) > 3 {
head = head[:3]
}
signals = append(signals, fmt.Sprintf("File references: %s", strings.Join(head, ", ")))
confidence += 0.2
}
if strings.HasPrefix(messageLower, "also") {
signals = append(signals, "Starts with 'also'")
confidence += 0.5
}
if confidence > 1.0 {
confidence = 1.0
}
isTaskAddition := confidence > 0.5
return isTaskAddition, confidence, signals
}
// addMessage — add_message: detect, id from the FIRST now() call
// (%Y%m%d-%H%M%S-%f), timestamp from the SECOND (isoformat), append, save.
func (q *MessageQueue) addMessage(message, channel string, taskID *string) QueuedMessage {
_, confidence, signals := q.detectTaskAddition(message)
nowID := ClockNow()
// strftime("%Y%m%d-%H%M%S-%f") — %f is zero-padded microseconds; Go's
// time layout only understands fractional seconds after a '.', so the
// µs field is appended explicitly.
msgID := fmt.Sprintf("%s-%06d", nowID.Format("20060102-150405"), nowID.Nanosecond()/1000)
nowTS := ClockNow()
queued := QueuedMessage{
ID: msgID,
Message: message,
Timestamp: isoformat(nowTS),
Channel: channel,
Status: StatusPending,
TaskID: taskID,
Confidence: confidence,
Signals: signals,
}
q.mu.Lock()
q.queue = append(q.queue, queued)
q.saveQueue()
q.mu.Unlock()
return queued
}
// getPending — get_pending: all messages with status "pending", in order.
func (q *MessageQueue) getPending() []QueuedMessage {
out := []QueuedMessage{}
for _, m := range q.snapshot() {
if m.Status == StatusPending {
out = append(out, m)
}
}
return out
}
// approve — approve(msg_id): found → status approved + save + true.
func (q *MessageQueue) approve(msgID string) bool {
q.mu.Lock()
defer q.mu.Unlock()
for i := range q.queue {
if q.queue[i].ID == msgID {
q.queue[i].Status = StatusApproved
q.saveQueue()
return true
}
}
return false
}
// reject — reject(msg_id): found → status rejected + save + true.
func (q *MessageQueue) reject(msgID string) bool {
q.mu.Lock()
defer q.mu.Unlock()
for i := range q.queue {
if q.queue[i].ID == msgID {
q.queue[i].Status = StatusRejected
q.saveQueue()
return true
}
}
return false
}
// approveAll — approve_all(): approve every pending, save once (even when
// the queue is empty), return the count.
func (q *MessageQueue) approveAll() int {
q.mu.Lock()
defer q.mu.Unlock()
count := 0
for i := range q.queue {
if q.queue[i].Status == StatusPending {
q.queue[i].Status = StatusApproved
count++
}
}
q.saveQueue()
return count
}
// clear — clear(): drop all messages, save.
func (q *MessageQueue) clear() {
q.mu.Lock()
q.queue = []QueuedMessage{}
q.saveQueue()
q.mu.Unlock()
}
// getApproved — get_approved: all messages with status "approved", in order.
func (q *MessageQueue) getApproved() []QueuedMessage {
out := []QueuedMessage{}
for _, m := range q.snapshot() {
if m.Status == StatusApproved {
out = append(out, m)
}
}
return out
}
// markExecuted — mark_executed(msg_id): found → status executed + save + true.
func (q *MessageQueue) markExecuted(msgID string) bool {
q.mu.Lock()
defer q.mu.Unlock()
for i := range q.queue {
if q.queue[i].ID == msgID {
q.queue[i].Status = StatusExecuted
q.saveQueue()
return true
}
}
return false
}
// getStatus — get_status: {total, pending, approved, executed}.
func (q *MessageQueue) getStatus() *OrderedMap {
q.mu.RLock()
defer q.mu.RUnlock()
pending, approved, executed := 0, 0, 0
for _, m := range q.queue {
switch m.Status {
case StatusPending:
pending++
case StatusApproved:
approved++
case StatusExecuted:
executed++
}
}
return NewOrderedMap(
KV{"total", len(q.queue)},
KV{"pending", pending},
KV{"approved", approved},
KV{"executed", executed},
)
}
// ── CLI (message_queue.py main(), byte-parity stdout) ────────────────────
// RunCLI executes message_queue.py main() against argv (program name at
// index 0) and returns the process exit code.
func RunCLI(argv []string, w io.Writer) int {
if len(argv) < 2 {
fmt.Fprintln(w, "Usage: message_queue.py [add|list|clear|approve|reject|status]")
return 1
}
cmd := argv[1]
q := NewMessageQueue(defaultQueueDir())
switch cmd {
case "add":
if len(argv) < 3 {
fmt.Fprintln(w, "Usage: message_queue.py add <message>")
return 1
}
message := strings.Join(argv[2:], " ")
queued := q.addMessage(message, "chat", nil)
fmt.Fprintf(w, "✅ Added to queue: %s\n", queued.ID)
fmt.Fprintf(w, " Confidence: %.2f\n", queued.Confidence)
fmt.Fprintf(w, " Signals: %s\n", strings.Join(queued.Signals, ", "))
case "list":
pending := q.getPending()
if len(pending) > 0 {
fmt.Fprintf(w, "Pending messages: %d\n", len(pending))
for _, m := range pending {
fmt.Fprintf(w, " %s: %s... (confidence: %.2f)\n", m.ID, runeTrunc(m.Message, 50), m.Confidence)
}
} else {
fmt.Fprintln(w, "No pending messages.")
}
case "clear":
q.clear()
fmt.Fprintln(w, "✅ Queue cleared")
case "approve":
if len(argv) < 3 {
count := q.approveAll()
fmt.Fprintf(w, "✅ Approved %d messages\n", count)
} else {
msgID := argv[2]
if q.approve(msgID) {
fmt.Fprintf(w, "✅ Approved: %s\n", msgID)
} else {
fmt.Fprintf(w, "❌ Message not found: %s\n", msgID)
}
}
case "reject":
if len(argv) < 3 {
fmt.Fprintln(w, "Usage: message_queue.py reject <msg_id>")
return 1
}
msgID := argv[2]
if q.reject(msgID) {
fmt.Fprintf(w, "✅ Rejected: %s\n", msgID)
} else {
fmt.Fprintf(w, "❌ Message not found: %s\n", msgID)
}
case "status":
status := q.getStatus()
fmt.Fprintln(w, "Queue status:")
fmt.Fprintf(w, " Total: %d\n", status.Get2("total"))
fmt.Fprintf(w, " Pending: %d\n", status.Get2("pending"))
fmt.Fprintf(w, " Approved: %d\n", status.Get2("approved"))
fmt.Fprintf(w, " Executed: %d\n", status.Get2("executed"))
default:
fmt.Fprintf(w, "Unknown command: %s\n", cmd)
return 1
}
return 0
}
// runeTrunc — Python's msg[:50] (codepoint slice).
func runeTrunc(s string, n int) string {
r := []rune(s)
if len(r) <= n {
return s
}
return string(r[:n])
}
// ── entry point: CLI + truth replay subcommand for the HARD CI gate ─────
func truthRowCount() int {
raw, err := os.ReadFile("truth.json")
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
path = os.Args[2]
}
fails := RunTruth(path)
if len(fails) > 0 {
for _, f := range fails {
fmt.Fprintln(os.Stderr, f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Printf("PARITY OK: %d truth rows exact-equal vs the Python oracle\n", truthRowCount())
return
}
os.Exit(RunCLI(os.Args, os.Stdout))
}
+114
View File
@@ -0,0 +1,114 @@
package main
// Phase B battery — pins the language-advantage guarantees: the QueueStore
// seam is swappable (compile-time asserted in main.go), mutations are
// goroutine-safe under -race, and the typed MessageStatus lattice keeps
// the store bytes byte-identical to the Phase A parity gate.
import (
"fmt"
"os"
"strings"
"sync"
"testing"
"time"
)
// memStore — in-memory QueueStore (the seam's whole point).
type memStore struct {
mu sync.Mutex
data []byte
}
func (s *memStore) Load() ([]byte, error) {
if s.data == nil {
return nil, os.ErrNotExist
}
return s.data, nil
}
func (s *memStore) Save(d []byte) error {
s.mu.Lock()
s.data = append([]byte(nil), d...)
s.mu.Unlock()
return nil
}
// TestPhaseBStoreSeamSwappable — the interface seam accepts an in-memory
// store; under the same frozen clock both stores produce byte-identical
// serialization.
func TestPhaseBStoreSeamSwappable(t *testing.T) {
defer func() { ClockNow = defaultClockNow }()
base, _ := time.Parse(time.RFC3339, "2026-10-11T12:00:00+00:00")
calls := 0
ClockNow = func() time.Time {
calls++
return base.Add(time.Duration(calls) * time.Microsecond)
}
memQ := NewMessageQueueWithStore(&memStore{})
memQ.addMessage("also fix config.py to log properly", "chat", nil)
calls = 0
fileQ := NewMessageQueue(t.TempDir())
fileQ.addMessage("also fix config.py to log properly", "chat", nil)
want := memQ.queue[0].toDict()
got := fileQ.queue[0].toDict()
if memStr, fileStr := mustJSON(want), mustJSON(got); memStr != fileStr {
t.Fatalf("store swap changed serialization:\n file: %s\n mem: %s", fileStr, memStr)
}
}
// TestPhaseBConcurrentMutations — concurrent add/approve/status calls must
// not corrupt the store or lose messages (run under -race in CI).
func TestPhaseBConcurrentMutations(t *testing.T) {
dir := t.TempDir()
q := NewMessageQueue(dir)
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
q.addMessage(fmt.Sprintf("also add feature %d to config.py", i), "chat", nil)
q.getStatus()
q.getPending()
}(i)
}
wg.Wait()
pending := q.getPending()
if len(pending) != 8 {
t.Fatalf("concurrent adds lost messages: %d/8", len(pending))
}
// store file re-parses to the same count
q2 := NewMessageQueue(dir)
if len(q2.queue) != 8 {
t.Fatalf("reloaded store lost messages: %d/8", len(q2.queue))
}
}
// TestPhaseBStatusLattice — the typed statuses serialize exactly like the
// oracle strings, and the parse round-trip is verbatim.
func TestPhaseBStatusLattice(t *testing.T) {
for _, s := range []MessageStatus{StatusPending, StatusApproved, StatusRejected, StatusExecuted} {
if parseMessageStatus(string(s)) != s {
t.Fatalf("status round-trip broken for %q", s)
}
}
m := QueuedMessage{ID: "x", Status: StatusExecuted, Signals: []string{}}
if got := mustJSON(m.toDict()); !strings.Contains(got, `"status":"executed"`) {
t.Fatalf("typed status changed serialization: %s", got)
}
// unknown statuses in the store pass through verbatim (oracle behavior)
om := NewOrderedMap(
KV{"id", "weird"}, KV{"message", "m"}, KV{"timestamp", "t"},
KV{"channel", "chat"}, KV{"status", "weird-state"},
KV{"task_id", nil}, KV{"confidence", 0.0}, KV{"signals", []any{}},
)
parsed := queuedMessageFromDict(om)
if parsed.Status != MessageStatus("weird-state") {
t.Fatalf("unknown status not passed through: %q", parsed.Status)
}
}
+286
View File
@@ -0,0 +1,286 @@
// Python json interop: insertion-ordered maps and json.dumps-compatible
// encoding (indent=2, ensure_ascii=True) so _save_queue and CLI output are
// byte-parity with the oracle (adapted from the goal port's pyjson.go; the
// float renderer follows Python repr, which keeps ".0" on integral floats).
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"unicode/utf16"
)
// KV — one ordered key/value pair.
type KV struct {
Key string
Value any
}
// OrderedMap — a dict with preserved insertion order.
type OrderedMap struct {
Keys []string
Vals map[string]any
}
// NewOrderedMap builds an ordered map from pairs.
func NewOrderedMap(pairs ...KV) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for _, kv := range pairs {
om.Set(kv.Key, kv.Value)
}
return om
}
// OrderedMapFromMap wraps a plain map (order unspecified — canonical
// comparisons sort keys anyway).
func OrderedMapFromMap(m map[string]any) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for k, v := range m {
om.Set(k, v)
}
return om
}
// Set inserts or replaces, preserving first-insertion order.
func (o *OrderedMap) Set(key string, value any) {
if _, ok := o.Vals[key]; !ok {
o.Keys = append(o.Keys, key)
}
o.Vals[key] = value
}
// Get retrieves a value.
func (o *OrderedMap) Get(key string) (any, bool) {
v, ok := o.Vals[key]
return v, ok
}
// Get2 returns the value at key (nil when absent) for direct type asserts.
func (o *OrderedMap) Get2(key string) any {
return o.Vals[key]
}
// Entries returns key/value pairs in insertion order.
func (o *OrderedMap) Entries() []KV {
out := make([]KV, 0, len(o.Keys))
for _, k := range o.Keys {
out = append(out, KV{k, o.Vals[k]})
}
return out
}
// Len returns the number of entries.
func (o *OrderedMap) Len() int { return len(o.Keys) }
// decodeJSONValue decodes one JSON value from the decoder, decoding objects
// as *OrderedMap (order-preserving) and numbers as float64.
func decodeJSONValue(dec *json.Decoder) (any, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodeFromToken(dec, tok)
}
func decodeFromToken(dec *json.Decoder, tok json.Token) (any, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
om := &OrderedMap{Vals: map[string]any{}}
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, err
}
key, _ := keyTok.(string)
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
om.Set(key, val)
}
if _, err := dec.Token(); err != nil { // consume '}'
return nil, err
}
return om, nil
case '[':
arr := []any{}
for dec.More() {
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
if _, err := dec.Token(); err != nil { // consume ']'
return nil, err
}
return arr, nil
}
return nil, fmt.Errorf("unexpected delim %v", t)
default:
return tok, nil
}
}
// encodePythonJSON renders v exactly like Python json.dumps(v, indent=N)
// with default ensure_ascii=True and (',', ': ') separators.
func encodePythonJSON(v any, indent int) string {
var sb strings.Builder
encodeValue(&sb, v, indent)
return sb.String()
}
func encodeValue(sb *strings.Builder, v any, indent int) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []*OrderedMap:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []string:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case *OrderedMap:
if t.Len() == 0 {
sb.WriteString("{}")
return
}
sb.WriteString("{\n")
entries := t.Entries()
for i, kv := range entries {
writeIndent(sb, indent+1)
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeValue(sb, kv.Value, indent+1)
if i < len(entries)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("}")
case map[string]any:
encodeValue(sb, OrderedMapFromMap(t), indent)
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
func writeIndent(sb *strings.Builder, level int) {
for i := 0; i < level; i++ {
sb.WriteString(" ")
}
}
// pythonFloat renders a float like Python repr (json.dumps uses
// float.__repr__): shortest round-trip digits, and integral floats keep
// one decimal place (1.0 stays "1.0", not "1").
func pythonFloat(f float64) string {
s := strconv.FormatFloat(f, 'g', -1, 64)
if !strings.ContainsAny(s, ".eE") &&
!strings.Contains(s, "Inf") && !strings.Contains(s, "NaN") {
s += ".0"
}
return s
}
// pythonEscapeString — Python json.dumps string escaping with
// ensure_ascii=True: control chars via \uXXXX (with \b \f \n \r \t
// shortcuts), non-ASCII via \uXXXX / surrogate pairs, lowercase hex.
func pythonEscapeString(s string) string {
var sb strings.Builder
sb.WriteByte('"')
for _, r := range s {
switch r {
case '"':
sb.WriteString(`\"`)
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\b':
sb.WriteString(`\b`)
case '\f':
sb.WriteString(`\f`)
default:
if r < 0x20 || r > 0x7e {
if r > 0xffff {
r1, r2 := utf16.EncodeRune(r)
fmt.Fprintf(&sb, `\u%04x\u%04x`, r1, r2)
} else {
fmt.Fprintf(&sb, `\u%04x`, r)
}
} else {
sb.WriteRune(r)
}
}
}
sb.WriteByte('"')
return sb.String()
}
+371
View File
@@ -0,0 +1,371 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// /tmp/gen_message_queue_truth.py against the live Python oracle,
// RUNE_MESSAGE_QUEUE_CORE pinned to python) and re-executes every row
// through this Go port, comparing results. Exact rows (store-file bytes,
// CLI stdout) compare byte-for-byte; everything else compares canonically
// (sorted-key JSON).
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
FrozenTS string `json:"frozen_ts"`
Rows []TruthRow `json:"rows"`
}
// TruthRow — one frozen case.
type TruthRow struct {
Name string `json:"name"`
Op string `json:"op"`
Setup json.RawMessage `json:"setup"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on 100%).
func RunTruth(path string) []string {
raw, err := os.ReadFile(path)
if err != nil {
return []string{fmt.Sprintf("read truth: %v", err)}
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return []string{fmt.Sprintf("parse truth: %v", err)}
}
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(&doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
return fails
}
// compareOne replays one row and returns "" on parity, else a divergence
// description. Shared by RunTruth and the per-function battery tests.
func compareOne(doc *TruthDoc, row *TruthRow) string {
got, exact, err := execRow(doc, row)
if err != nil {
return fmt.Sprintf("error: %v", err)
}
var wantStr, gotStr string
if exact {
var w string
if err := json.Unmarshal(row.Want, &w); err != nil {
return fmt.Sprintf("want not a string: %v", err)
}
wantStr, gotStr = w, got.(string)
} else {
wantStr, err = canonicalFromRaw(row.Want)
if err != nil {
return fmt.Sprintf("canonicalize want: %v", err)
}
gotStr, err = canonicalFromRaw([]byte(mustJSON(got)))
if err != nil {
return fmt.Sprintf("canonicalize got: %v", err)
}
}
if wantStr != gotStr {
return fmt.Sprintf("\n want: %s\n got: %s", wantStr, gotStr)
}
return ""
}
// execRow executes one truth row: returns got (any) and, for exact rows,
// the exact string in got.
func execRow(doc *TruthDoc, row *TruthRow) (got any, exact bool, err error) {
setup := NewOrderedMap()
if len(row.Setup) > 0 {
dec := json.NewDecoder(bytes.NewReader(row.Setup))
tok, err := dec.Token()
if err != nil {
return nil, false, err
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return nil, false, fmt.Errorf("setup not an object")
}
v, err := decodeFromToken(dec, tok)
if err != nil {
return nil, false, err
}
setup = v.(*OrderedMap)
}
// freeze the seams per row
home, err := os.MkdirTemp("", "mq_truth_row_")
if err != nil {
return nil, false, err
}
defer os.RemoveAll(home)
defer func() {
HomeDir = defaultHomeDir
ClockNow = defaultClockNow
}()
HomeDir = func() string { return home }
base, err := time.Parse(time.RFC3339, doc.FrozenTS)
if err != nil {
return nil, false, err
}
calls := 0
ClockNow = func() time.Time {
calls++
return base.Add(time.Duration(calls) * time.Microsecond)
}
seedFile := func() {
if f, ok := setup.Get("file"); ok {
s, _ := f.(string)
os.MkdirAll(filepath.Join(home, ".rune", "message_queue"), 0o777)
os.WriteFile(filepath.Join(home, ".rune", "message_queue", "queue.json"), []byte(s), 0o644)
}
}
readStore := func() (string, error) {
raw, err := os.ReadFile(filepath.Join(home, ".rune", "message_queue", "queue.json"))
if err != nil {
return "", err
}
return string(raw), nil
}
newQ := func() *MessageQueue { return NewMessageQueue(defaultQueueDir()) }
strSetup := func(key string) string {
s, _ := setup.Get2(key).(string)
return s
}
switch row.Op {
case "detect":
q := newQ()
detected, confidence, signals := q.detectTaskAddition(strSetup("message"))
sig := make([]any, 0, len(signals))
for _, s := range signals {
sig = append(sig, s)
}
return NewOrderedMap(
KV{"detected", detected},
KV{"confidence", confidence},
KV{"signals", sig},
), false, nil
case "add":
seedFile()
q := newQ()
var taskID *string
if t, ok := setup.Get("task_id"); ok {
if s, ok2 := t.(string); ok2 {
taskID = &s
}
}
m := q.addMessage(strSetup("message"), strSetup("channel"), taskID)
return m.toDict(), false, nil
case "pending":
seedFile()
q := newQ()
return queuedList(q.getPending()), false, nil
case "get_approved":
seedFile()
q := newQ()
return queuedList(q.getApproved()), false, nil
case "approve", "reject", "mark_executed":
seedFile()
q := newQ()
var ok bool
switch row.Op {
case "approve":
ok = q.approve(strSetup("msg_id"))
case "reject":
ok = q.reject(strSetup("msg_id"))
case "mark_executed":
ok = q.markExecuted(strSetup("msg_id"))
}
file, err := readStore()
if err != nil {
return nil, false, err
}
return NewOrderedMap(KV{"ok", ok}, KV{"file", file}), false, nil
case "approve_all":
seedFile()
q := newQ()
count := q.approveAll()
file, err := readStore()
if err != nil {
return nil, false, err
}
return NewOrderedMap(KV{"count", count}, KV{"file", file}), false, nil
case "clear":
seedFile()
q := newQ()
q.clear()
file, err := readStore()
if err != nil {
return nil, false, err
}
return NewOrderedMap(KV{"file", file}, KV{"status", q.getStatus()}), false, nil
case "status":
seedFile()
q := newQ()
return q.getStatus(), false, nil
case "load":
// _load_queue branches: missing file / exact file bytes / empty file
seedFile()
q := newQ()
return queuedList(q.queue), false, nil
case "save":
// _save_queue byte parity: rebuild the setup store, save, read bytes
store, _ := setup.Get("store")
arr, ok := store.([]any)
if !ok {
return nil, false, fmt.Errorf("save: no store")
}
q := newQ()
for _, item := range arr {
if om, ok := item.(*OrderedMap); ok {
q.queue = append(q.queue, queuedMessageFromDict(om))
}
}
q.saveQueue()
file, err := readStore()
if err != nil {
return nil, false, err
}
return file, true, nil
case "cli":
seedFile()
argvAny, _ := setup.Get2("argv").([]any)
argv := []string{"message_queue.py"}
for _, a := range argvAny {
s, _ := a.(string)
argv = append(argv, s)
}
var sb strings.Builder
code := RunCLI(argv, &sb)
return NewOrderedMap(KV{"stdout", sb.String()}, KV{"exit", code}), false, nil
}
return nil, false, fmt.Errorf("unknown row op %s (%s)", row.Op, row.Name)
}
// queuedList renders messages as Python-ordered dicts.
func queuedList(ms []QueuedMessage) []any {
out := make([]any, 0, len(ms))
for i := range ms {
out = append(out, ms[i].toDict())
}
return out
}
// canonicalFromRaw — canonical string of a raw JSON document.
func canonicalFromRaw(raw []byte) (string, error) {
var v any
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return "", err
}
return canonical(normalize(v)), nil
}
// canonical — sorted-key deterministic rendering of a decoded JSON value.
func canonical(v any) string {
switch t := v.(type) {
case nil:
return "null"
case bool:
if t {
return "true"
}
return "false"
case string:
b, _ := json.Marshal(t)
return string(b)
case float64:
if t == float64(int64(t)) {
return fmt.Sprintf("%d", int64(t))
}
return fmt.Sprintf("%v", t)
case json.Number:
f, _ := t.Float64()
return canonical(f)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = canonical(normalize(item))
}
return "[" + strings.Join(parts, ", ") + "]"
case map[string]any:
keys := make([]string, 0, len(t))
for k := range t {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
bk, _ := json.Marshal(k)
parts = append(parts, string(bk)+": "+canonical(normalize(t[k])))
}
return "{" + strings.Join(parts, ", ") + "}"
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = kv.Value
}
return canonical(m)
default:
return fmt.Sprintf("«%v»", t)
}
}
// normalize — convert OrderedMap/map trees into plain map[string]any with
// json.Number→float64 so canonical() sees one representation.
func normalize(v any) any {
switch t := v.(type) {
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = normalize(kv.Value)
}
return m
case map[string]any:
m := map[string]any{}
for k, val := range t {
m[k] = normalize(val)
}
return m
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = normalize(item)
}
return out
case json.Number:
f, _ := t.Float64()
return f
default:
return v
}
}
func mustJSON(v any) string {
b, err := json.Marshal(normalize(v))
if err != nil {
return "null"
}
return string(b)
}
+881
View File
@@ -0,0 +1,881 @@
{
"frozen_ts": "2026-10-11T12:00:00+00:00",
"frozen_id_base": "20261011-120000",
"generator": "scripts truth battery \u2014 live oracle src/rune/message_queue.py",
"rows": [
{
"name": "detect:also-full-combo",
"op": "detect",
"setup": {
"message": "also add logging to the parser module config.py"
},
"want": {
"detected": true,
"confidence": 1.0,
"signals": [
"Pattern: \\balso\\b",
"Pattern: \\badd\\b.*\\bto\\b",
"File references: config.py",
"Starts with 'also'"
]
}
},
{
"name": "detect:and-can-you",
"op": "detect",
"setup": {
"message": "and can you fix the bug in config.py"
},
"want": {
"detected": true,
"confidence": 0.8,
"signals": [
"Pattern: \\band can you\\b",
"Pattern: \\bcan you fix\\b",
"File references: config.py"
]
}
},
{
"name": "detect:plus-update-to",
"op": "detect",
"setup": {
"message": "plus update the docs to reflect the change"
},
"want": {
"detected": true,
"confidence": 0.6,
"signals": [
"Pattern: \\bplus\\b",
"Pattern: \\bupdate\\b.*\\bto\\b"
]
}
},
{
"name": "detect:remember-to-only",
"op": "detect",
"setup": {
"message": "remember to update the docs"
},
"want": {
"detected": false,
"confidence": 0.3,
"signals": [
"Pattern: \\bremember to\\b"
]
}
},
{
"name": "detect:plain-chat",
"op": "detect",
"setup": {
"message": "hello there friend"
},
"want": {
"detected": false,
"confidence": 0.0,
"signals": []
}
},
{
"name": "detect:additionally-implement",
"op": "detect",
"setup": {
"message": "additionally implement the parser to support nested blocks"
},
"want": {
"detected": true,
"confidence": 1.0,
"signals": [
"Pattern: \\badditionally\\b",
"Pattern: \\bimplement\\b.*\\bto\\b",
"Imperative verb: add"
]
}
},
{
"name": "detect:make-sure",
"op": "detect",
"setup": {
"message": "make sure the build passes"
},
"want": {
"detected": true,
"confidence": 0.7,
"signals": [
"Pattern: \\bmake sure\\b",
"Imperative verb: make"
]
}
},
{
"name": "detect:imperative-only-below-boundary",
"op": "detect",
"setup": {
"message": "delete the old file"
},
"want": {
"detected": false,
"confidence": 0.4,
"signals": [
"Imperative verb: delete"
]
}
},
{
"name": "detect:add-and-filerefs",
"op": "detect",
"setup": {
"message": "add config.py and tests to the runner"
},
"want": {
"detected": true,
"confidence": 0.8999999999999999,
"signals": [
"Pattern: \\badd\\b.*\\bto\\b",
"Imperative verb: add",
"File references: config.py"
]
}
},
{
"name": "detect:then-migrate",
"op": "detect",
"setup": {
"message": "then migrate the store to sqlite"
},
"want": {
"detected": false,
"confidence": 0.3,
"signals": [
"Pattern: \\bthen\\b"
]
}
},
{
"name": "detect:capital-also",
"op": "detect",
"setup": {
"message": "Also add tests"
},
"want": {
"detected": true,
"confidence": 0.8,
"signals": [
"Pattern: \\balso\\b",
"Starts with 'also'"
]
}
},
{
"name": "detect:fix-to-fileref",
"op": "detect",
"setup": {
"message": "fix auth.py to use the new provider"
},
"want": {
"detected": true,
"confidence": 0.8999999999999999,
"signals": [
"Pattern: \\bfix\\b.*\\bto\\b",
"Imperative verb: fix",
"File references: auth.py"
]
}
},
{
"name": "detect:include-fileref-at-boundary",
"op": "detect",
"setup": {
"message": "include config.py"
},
"want": {
"detected": false,
"confidence": 0.5,
"signals": [
"Pattern: \\binclude\\b",
"File references: config.py"
]
}
},
{
"name": "detect:empty-message",
"op": "detect",
"setup": {
"message": ""
},
"want": {
"detected": false,
"confidence": 0.0,
"signals": []
}
},
{
"name": "detect:dont-forget",
"op": "detect",
"setup": {
"message": "don't forget to add auth.py to config"
},
"want": {
"detected": true,
"confidence": 0.8,
"signals": [
"Pattern: \\bdon\\'t forget\\b",
"Pattern: \\badd\\b.*\\bto\\b",
"File references: auth.py"
]
}
},
{
"name": "detect:can-you-create",
"op": "detect",
"setup": {
"message": "can you also create a test for auth.py"
},
"want": {
"detected": true,
"confidence": 0.8,
"signals": [
"Pattern: \\balso\\b",
"Pattern: \\bcan you also\\b",
"File references: auth.py"
]
}
},
{
"name": "detect:build-to-pattern",
"op": "detect",
"setup": {
"message": "we should build the parser to handle comments"
},
"want": {
"detected": false,
"confidence": 0.3,
"signals": [
"Pattern: \\bbuild\\b.*\\bto\\b"
]
}
},
{
"name": "add:detected",
"op": "add",
"setup": {
"message": "also fix config.py to log properly",
"channel": "chat",
"task_id": null
},
"want": {
"id": "20261011-120000-000001",
"message": "also fix config.py to log properly",
"timestamp": "2026-10-11T12:00:00.000002+00:00",
"channel": "chat",
"status": "pending",
"task_id": null,
"confidence": 1.0,
"signals": [
"Pattern: \\balso\\b",
"Pattern: \\bfix\\b.*\\bto\\b",
"File references: config.py",
"Starts with 'also'"
]
}
},
{
"name": "add:with-task-id",
"op": "add",
"setup": {
"message": "update docs to include the new flag",
"channel": "chat",
"task_id": "T42"
},
"want": {
"id": "20261011-120000-000001",
"message": "update docs to include the new flag",
"timestamp": "2026-10-11T12:00:00.000002+00:00",
"channel": "chat",
"status": "pending",
"task_id": "T42",
"confidence": 1.0,
"signals": [
"Pattern: \\binclude\\b",
"Pattern: \\bupdate\\b.*\\bto\\b",
"Imperative verb: update"
]
}
},
{
"name": "add:undetected",
"op": "add",
"setup": {
"message": "just a note for later",
"channel": "chat",
"task_id": null
},
"want": {
"id": "20261011-120000-000001",
"message": "just a note for later",
"timestamp": "2026-10-11T12:00:00.000002+00:00",
"channel": "chat",
"status": "pending",
"task_id": null,
"confidence": 0.0,
"signals": []
}
},
{
"name": "pending:mixed",
"op": "pending",
"setup": {
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": [
{
"id": "m1",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m2",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": "T1",
"confidence": 0.8,
"signals": []
}
]
},
{
"name": "pending:missing-file",
"op": "pending",
"setup": {},
"want": []
},
{
"name": "pending:missing-store",
"op": "pending",
"setup": {},
"want": []
},
{
"name": "approve:hit",
"op": "approve",
"setup": {
"msg_id": "m1",
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"ok": true,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "approve:miss",
"op": "approve",
"setup": {
"msg_id": "nope",
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"ok": false,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "reject:hit",
"op": "reject",
"setup": {
"msg_id": "m2",
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"ok": true,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "reject:miss",
"op": "reject",
"setup": {
"msg_id": "nope",
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"ok": false,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "approve_all:mixed",
"op": "approve_all",
"setup": {
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"count": 2,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "approve_all:empty",
"op": "approve_all",
"setup": {
"file": "[]"
},
"want": {
"count": 0,
"file": "[]"
}
},
{
"name": "clear:nonempty",
"op": "clear",
"setup": {
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"file": "[]",
"status": {
"total": 0,
"pending": 0,
"approved": 0,
"executed": 0
}
}
},
{
"name": "get_approved:mixed",
"op": "get_approved",
"setup": {
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": [
{
"id": "m3",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "approved",
"task_id": null,
"confidence": 0.8,
"signals": []
}
]
},
{
"name": "mark_executed:hit",
"op": "mark_executed",
"setup": {
"msg_id": "m3",
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"ok": true,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "mark_executed:miss",
"op": "mark_executed",
"setup": {
"msg_id": "nope",
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"ok": false,
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
}
},
{
"name": "status:mixed",
"op": "status",
"setup": {
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"total": 5,
"pending": 2,
"approved": 1,
"executed": 1
}
},
{
"name": "load:missing-file",
"op": "load",
"setup": {
"missing": true
},
"want": []
},
{
"name": "load:empty-file",
"op": "load",
"setup": {
"file": "[]"
},
"want": []
},
{
"name": "load:populated",
"op": "load",
"setup": {
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": [
{
"id": "m1",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m2",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": "T1",
"confidence": 0.8,
"signals": []
},
{
"id": "m3",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "approved",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m4",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "executed",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m5",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "rejected",
"task_id": null,
"confidence": 0.8,
"signals": []
}
]
},
{
"name": "save:empty",
"op": "save",
"setup": {
"store": []
},
"want": "[]"
},
{
"name": "save:unicode-null-emptysignals",
"op": "save",
"setup": {
"store": [
{
"id": "u1",
"message": "fix caf\u00e9 \u2615 support in config.py",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": null,
"confidence": 0.7000000000000001,
"signals": []
}
]
},
"want": "[\n {\n \"id\": \"u1\",\n \"message\": \"fix caf\\u00e9 \\u2615 support in config.py\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.7000000000000001,\n \"signals\": []\n }\n]"
},
{
"name": "save:seed",
"op": "save",
"setup": {
"store": [
{
"id": "m1",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m2",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "pending",
"task_id": "T1",
"confidence": 0.8,
"signals": []
},
{
"id": "m3",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "approved",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m4",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "executed",
"task_id": null,
"confidence": 0.8,
"signals": []
},
{
"id": "m5",
"message": "seed message",
"timestamp": "2026-10-11T11:59:00.000001+00:00",
"channel": "chat",
"status": "rejected",
"task_id": null,
"confidence": 0.8,
"signals": []
}
]
},
"want": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
{
"name": "cli:no-args",
"op": "cli",
"setup": {
"argv": []
},
"want": {
"stdout": "Usage: message_queue.py [add|list|clear|approve|reject|status]\n",
"exit": 1
}
},
{
"name": "cli:add",
"op": "cli",
"setup": {
"argv": [
"add",
"also",
"fix",
"config.py",
"to",
"log"
]
},
"want": {
"stdout": "\u2705 Added to queue: 20261011-120000-000001\n Confidence: 1.00\n Signals: Pattern: \\balso\\b, Pattern: \\bfix\\b.*\\bto\\b, File references: config.py, Starts with 'also'\n",
"exit": 0
}
},
{
"name": "cli:add-no-message",
"op": "cli",
"setup": {
"argv": [
"add"
]
},
"want": {
"stdout": "Usage: message_queue.py add <message>\n",
"exit": 1
}
},
{
"name": "cli:list-empty",
"op": "cli",
"setup": {
"argv": [
"list"
]
},
"want": {
"stdout": "No pending messages.\n",
"exit": 0
}
},
{
"name": "cli:list-some",
"op": "cli",
"setup": {
"argv": [
"list"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "Pending messages: 2\n m1: seed message... (confidence: 0.80)\n m2: seed message... (confidence: 0.80)\n",
"exit": 0
}
},
{
"name": "cli:clear",
"op": "cli",
"setup": {
"argv": [
"clear"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "\u2705 Queue cleared\n",
"exit": 0
}
},
{
"name": "cli:approve-all",
"op": "cli",
"setup": {
"argv": [
"approve"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "\u2705 Approved 2 messages\n",
"exit": 0
}
},
{
"name": "cli:approve-id",
"op": "cli",
"setup": {
"argv": [
"approve",
"m2"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "\u2705 Approved: m2\n",
"exit": 0
}
},
{
"name": "cli:approve-miss",
"op": "cli",
"setup": {
"argv": [
"approve",
"nope"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "\u274c Message not found: nope\n",
"exit": 0
}
},
{
"name": "cli:reject-no-id",
"op": "cli",
"setup": {
"argv": [
"reject"
]
},
"want": {
"stdout": "Usage: message_queue.py reject <msg_id>\n",
"exit": 1
}
},
{
"name": "cli:reject-id",
"op": "cli",
"setup": {
"argv": [
"reject",
"m1"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "\u2705 Rejected: m1\n",
"exit": 0
}
},
{
"name": "cli:reject-miss",
"op": "cli",
"setup": {
"argv": [
"reject",
"nope"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "\u274c Message not found: nope\n",
"exit": 0
}
},
{
"name": "cli:status",
"op": "cli",
"setup": {
"argv": [
"status"
],
"file": "[\n {\n \"id\": \"m1\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m2\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": \"T1\",\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m3\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"approved\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m4\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"executed\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n },\n {\n \"id\": \"m5\",\n \"message\": \"seed message\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"rejected\",\n \"task_id\": null,\n \"confidence\": 0.8,\n \"signals\": []\n }\n]"
},
"want": {
"stdout": "Queue status:\n Total: 5\n Pending: 2\n Approved: 1\n Executed: 1\n",
"exit": 0
}
},
{
"name": "cli:unknown-cmd",
"op": "cli",
"setup": {
"argv": [
"frobnicate"
]
},
"want": {
"stdout": "Unknown command: frobnicate\n",
"exit": 1
}
},
{
"name": "cli:list-long-truncation",
"op": "cli",
"setup": {
"argv": [
"list"
],
"file": "[\n {\n \"id\": \"L1\",\n \"message\": \"this seed message is definitely longer than fifty characters in total\",\n \"timestamp\": \"2026-10-11T11:59:00.000001+00:00\",\n \"channel\": \"chat\",\n \"status\": \"pending\",\n \"task_id\": null,\n \"confidence\": 0.5,\n \"signals\": [\n \"s1\"\n ]\n }\n]"
},
"want": {
"stdout": "Pending messages: 1\n L1: this seed message is definitely longer than fifty ... (confidence: 0.50)\n",
"exit": 0
}
}
]
}
+225
View File
@@ -0,0 +1,225 @@
package main
// Battery — the HARD parity gate plus per-function batteries replaying the
// frozen oracle truth (truth.json, generated against the LIVE Python
// oracle with the clock frozen and RUNE_MESSAGE_QUEUE_CORE=python pinned)
// VERBATIM. Any divergence fails; the truth file rides the repo so the
// gate holds in CI without /tmp.
import (
"encoding/json"
"os"
"strings"
"testing"
)
func loadTruth(t *testing.T) *TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth.json: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth.json contained no rows")
}
return &doc
}
func findRow(t *testing.T, name string) TruthRow {
t.Helper()
doc := loadTruth(t)
for _, row := range doc.Rows {
if row.Name == name {
return row
}
}
t.Fatalf("frozen row %q missing from truth.json — battery/truth drift", name)
return TruthRow{}
}
func mustReplay(t *testing.T, name string) {
t.Helper()
doc := loadTruth(t)
row := findRow(t, name)
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", name, msg)
}
}
func replayOp(t *testing.T, op string) int {
t.Helper()
doc := loadTruth(t)
n := 0
for _, row := range doc.Rows {
if row.Op != op {
continue
}
n++
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", row.Name, msg)
}
}
if n == 0 {
t.Fatalf("no %s rows in truth.json", op)
}
return n
}
// rowWant decodes a row's want JSON into a generic map.
func rowWant(t *testing.T, name string) map[string]any {
t.Helper()
row := findRow(t, name)
var want map[string]any
if err := json.Unmarshal(row.Want, &want); err != nil {
t.Fatalf("decode want of %s: %v", name, err)
}
return want
}
// TestTruthParity — the whole frozen battery, one shot.
func TestTruthParity(t *testing.T) {
doc := loadTruth(t)
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
if len(fails) > 0 {
t.Fatalf("parity replay diverged after %d clean rows:\n%s",
len(doc.Rows)-len(fails), strings.Join(fails, "\n"))
}
t.Logf("PARITY OK: %d cases exact-equal vs the Python oracle", len(doc.Rows))
}
// TestBatteryDetectTaskAddition — every signal family, the >0.5 boundary,
// the 1.0 cap, and the raw \bdon\'t forget\b pattern artifact.
func TestBatteryDetectTaskAddition(t *testing.T) {
n := replayOp(t, "detect")
if n != 17 {
t.Fatalf("expected 18 frozen detect rows, got %d — battery/truth drift", n)
}
// boundary: exactly 0.5 is NOT a task addition (strict > 0.5)
want := rowWant(t, "detect:include-fileref-at-boundary")
if want["detected"] != false || want["confidence"] != 0.5 {
t.Fatalf("frozen boundary row changed: %v", want)
}
// cap: 1.0 ceiling
want = rowWant(t, "detect:also-full-combo")
if want["detected"] != true || want["confidence"] != 1.0 {
t.Fatalf("frozen cap row changed: %v", want)
}
// sub-boundary: imperative verb alone (0.4)
want = rowWant(t, "detect:imperative-only-below-boundary")
if want["detected"] != false || want["confidence"] != 0.4 {
t.Fatalf("frozen sub-boundary row changed: %v", want)
}
mustReplay(t, "detect:dont-forget")
}
// TestBatteryDetectDontForgetArtifact — the oracle stores the pattern as
// the RAW string \bdon\'t forget\b; the backslash must survive into the
// signal text AND keep matching plain apostrophes.
func TestBatteryDetectDontForgetArtifact(t *testing.T) {
q := NewMessageQueue(t.TempDir())
_, _, signals := q.detectTaskAddition("don't forget to log out")
found := false
for _, s := range signals {
if s == `Pattern: \bdon\'t forget\b` {
found = true
}
}
if !found {
t.Fatalf("raw don\\'t pattern artifact lost; signals: %v", signals)
}
}
// TestBatteryAddMessage — msg_id from the FIRST now() call, timestamp from
// the SECOND, task_id null passthrough, undetected adds still queue.
func TestBatteryAddMessage(t *testing.T) {
if n := replayOp(t, "add"); n != 3 {
t.Fatalf("expected 3 frozen add rows, got %d", n)
}
want := rowWant(t, "add:with-task-id")
if want["id"] != "20261011-120000-000001" || want["timestamp"] != "2026-10-11T12:00:00.000002+00:00" {
t.Fatalf("frozen clock pairing changed: id=%v ts=%v", want["id"], want["timestamp"])
}
if want["task_id"] != "T42" || want["status"] != "pending" {
t.Fatalf("frozen add row changed: %v", want)
}
want = rowWant(t, "add:undetected")
if want["confidence"] != 0.0 {
t.Fatalf("undetected add should carry 0.0 confidence: %v", want)
}
}
// TestBatteryQueueManagement — pending/approve/reject/approve_all/clear/
// get_approved/mark_executed/status incl. hit+miss and file-bytes pins.
func TestBatteryQueueManagement(t *testing.T) {
for _, op := range []string{"pending", "approve", "reject", "approve_all",
"clear", "get_approved", "mark_executed", "status"} {
replayOp(t, op)
}
// miss rows must NOT rewrite the store (file bytes unchanged)
want := rowWant(t, "approve:miss")
if want["ok"] != false {
t.Fatalf("approve miss should return false: %v", want)
}
missFile, _ := want["file"].(string)
want = rowWant(t, "approve:hit")
hitFile, _ := want["file"].(string)
if missFile == hitFile {
t.Fatal("miss row reuses hit row bytes — save-on-miss divergence")
}
// approve_all on an empty store saves "[]" and returns 0
want = rowWant(t, "approve_all:empty")
if want["count"] != float64(0) || want["file"] != "[]" {
t.Fatalf("frozen empty approve_all changed: %v", want)
}
}
// TestBatteryLoadSave — _load_queue branches (missing file / "[]" file /
// populated) and _save_queue byte-parity (ensure_ascii, null task_id,
// empty signals, indent=2 layout).
func TestBatteryLoadSave(t *testing.T) {
if n := replayOp(t, "load"); n != 3 {
t.Fatalf("expected 3 frozen load rows, got %d", n)
}
replayOp(t, "save")
mustReplay(t, "save:unicode-null-emptysignals")
mustReplay(t, "save:empty")
}
// TestBatteryCLI — message_queue.py main() byte-parity stdout + exit codes.
func TestBatteryCLI(t *testing.T) {
n := replayOp(t, "cli")
if n != 15 {
t.Fatalf("expected 15 frozen cli rows, got %d", n)
}
for name, wantExit := range map[string]float64{
"cli:no-args": 1,
"cli:add": 0,
"cli:add-no-message": 1,
"cli:list-empty": 0,
"cli:list-some": 0,
"cli:clear": 0,
"cli:approve-all": 0,
"cli:approve-id": 0,
"cli:approve-miss": 0,
"cli:reject-no-id": 1,
"cli:reject-id": 0,
"cli:reject-miss": 0,
"cli:status": 0,
"cli:unknown-cmd": 1,
"cli:list-long-truncation": 0,
} {
want := rowWant(t, name)
if want["exit"] != wantExit {
t.Fatalf("%s: frozen exit code %v != %v", name, want["exit"], wantExit)
}
}
}
+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.206.0"
__version__ = "0.210.0"
# 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
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust build_basis core (dichotomy wave #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* bb_research_depth({route, mode, justification}) -> depth decision dict
* bb_new_basis({question, depth_decision, issue_ref}) -> basis dict
* bb_set_dispositions({basis, dispositions}) -> {"basis", "errors"}
* bb_validate_basis({basis}) -> [errors]
* bb_is_qualified({basis}) -> bool
* bb_attach_evidence_ref({basis}) -> str
* bb_summary({basis}) -> summary dict
* bb_revise_depth({basis, new_depth, reason}) -> {"basis", "revision"}
* bb_to_json({basis}) -> str (json.dumps indent=1 sort_keys semantics)
* bb_from_json({text}) -> parsed value
* bb_generate_research_plan({...}) -> plan dict
Free: capability_scoring's cs_free_string (shared process). Override the
.so with BB_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "build_basis"
_LIB_PATH = Path(
os.environ.get("BB_RUST_LIB", _CRATE_DIR / "target" / "release" / "libbuild_basis.so")
)
_SYMBOLS = (
"bb_research_depth", "bb_new_basis", "bb_set_dispositions",
"bb_validate_basis", "bb_is_qualified", "bb_attach_evidence_ref",
"bb_summary", "bb_revise_depth", "bb_to_json", "bb_from_json",
"bb_generate_research_plan",
)
_lib = None
class BasisUnavailable(FileNotFoundError):
"""The Rust build_basis core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise BasisUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in _SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
# free comes from capability_scoring (same process) if loaded; else our own malloc path
try:
lib.cs_free_string
except AttributeError:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
free = getattr(lib, "_free", None)
if free is not None:
free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def research_depth(route: str, mode: str = "", justification: str = "") -> dict:
return _call("bb_research_depth", {
"route": route, "mode": mode, "justification": justification})
def new_basis(question: str, depth_decision: dict, issue_ref: str = "") -> dict:
return _call("bb_new_basis", {
"question": question, "depth_decision": depth_decision,
"issue_ref": issue_ref})
def set_dispositions(basis: dict, dispositions: list) -> dict:
"""Returns {"basis": <mutated basis>, "errors": [...]}."""
return _call("bb_set_dispositions", {
"basis": basis, "dispositions": dispositions})
def validate_basis(basis: dict) -> list:
return _call("bb_validate_basis", {"basis": basis})
def is_qualified(basis: dict) -> bool:
return _call("bb_is_qualified", {"basis": basis})
def attach_evidence_ref(basis: dict) -> str:
return _call("bb_attach_evidence_ref", {"basis": basis})
def summary(basis: dict) -> dict:
return _call("bb_summary", {"basis": basis})
def revise_depth(basis: dict, new_depth: str, reason: str) -> dict:
"""Returns {"basis": <mutated basis>, "revision": {...}}; raises
RuntimeError carrying the exact oracle ValueError message on refusal."""
return _call("bb_revise_depth", {
"basis": basis, "new_depth": new_depth, "reason": reason})
def to_json(basis: dict) -> str:
return _call("bb_to_json", {"basis": basis})
def from_json(text: str):
return _call("bb_from_json", {"text": text})
def generate_research_plan(intent: str, target: str = "",
current_reality: str = "",
unknowns: list | None = None,
decisions: list | None = None,
risks: list | None = None,
depth: str | None = None) -> dict:
return _call("bb_generate_research_plan", {
"intent": intent, "target": target,
"current_reality": current_reality,
"unknowns": list(unknowns or []), "decisions": list(decisions or []),
"risks": list(risks or []), "depth": depth})
+61
View File
@@ -19,11 +19,34 @@ structures the adopt/adapt/combine/reject/innovate decisions (rule E).
"""
from __future__ import annotations
import os
import time
from typing import Any
SCHEMA_VERSION = 1
def _rust(fn: str, *args, **kwargs):
"""Delegate to the Rust kernel (dichotomy wave #2479); returns
_MISSING when the core is unavailable so the Python oracle body runs.
RUNE_BB_CORE=python pins the oracle unconditionally."""
if os.environ.get("RUNE_BB_CORE", "auto") == "python":
return _MISSING
try:
from rune import bb_loader
if not bb_loader.available():
return _MISSING
return getattr(bb_loader, fn)(*args, **kwargs)
except Exception:
return _MISSING
class _Missing:
pass
_MISSING = _Missing()
# Research depth tiers — derived from adaptive_router ROUTES, never a
# second router (rule C/A). Each tier records WHY it was chosen.
DEPTH_NONE = "NONE" # tiny obvious local change; local evidence only
@@ -48,6 +71,9 @@ def research_depth(route: str, *, mode: str = "",
the canonical route authority (adaptive_router); `justification` is
REQUIRED when deviating from the route default — recorded, never
silent (rule D: record why research depth was chosen)."""
out = _rust("research_depth", route, mode, justification)
if out is not _MISSING:
return out
default = _ROUTE_DEFAULT_DEPTH.get(route, DEPTH_INWARD)
depth = default
why = f"route {route} default"
@@ -89,6 +115,9 @@ def new_basis(*, question: str, depth_decision: dict,
"""Create an empty-but-valid Build Basis. References only — full
research dumps live in the Research Core's artifact dirs (rule 18C:
do not store unnecessary dumps)."""
out = _rust("new_basis", question, depth_decision, issue_ref)
if out is not _MISSING:
return out
return {
"schema_version": SCHEMA_VERSION,
"kind": "build_basis",
@@ -112,6 +141,11 @@ def new_basis(*, question: str, depth_decision: dict,
def set_dispositions(basis: dict, dispositions: list[dict]) -> list[str]:
"""Record the adopt/adapt/combine/reject/innovate decisions (rule E).
Returns validation errors; the basis stays unqualified until valid."""
out = _rust("set_dispositions", basis, dispositions)
if out is not _MISSING:
basis.clear()
basis.update(out["basis"])
return out["errors"]
basis["dispositions"] = [
{"item": d.get("item", ""), "disposition": d.get("disposition", ""),
"why": d.get("why", ""), "ref": d.get("ref", "")}
@@ -124,6 +158,9 @@ def validate_basis(basis: dict) -> list[str]:
"""Qualification gate. A basis is QUALIFIED when it carries a depth
decision with why, at least one outward prior-art answer when depth
is OUTWARD/FULL, and every disposition uses a legal disposition."""
out = _rust("validate_basis", basis)
if out is not _MISSING:
return out
errors: list[str] = []
if basis.get("schema_version") != SCHEMA_VERSION:
errors.append(f"schema_version {basis.get('schema_version')!r}")
@@ -152,12 +189,18 @@ def validate_basis(basis: dict) -> list[str]:
def is_qualified(basis: dict) -> bool:
out = _rust("is_qualified", basis)
if out is not _MISSING:
return out
return not validate_basis(basis)
def attach_evidence_ref(basis: dict) -> str:
"""The evidence_ref string a contract carries to point at this basis
(durable pointer, not a copy — stores stay independent)."""
out = _rust("attach_evidence_ref", basis)
if out is not _MISSING:
return out
return (f"build_basis:{basis.get('issue_ref') or 'adhoc'}:"
f"{basis.get('created_at')}")
@@ -165,6 +208,9 @@ def attach_evidence_ref(basis: dict) -> str:
def summary(basis: dict) -> dict:
"""Compact projection for prompts/previews (no private reasoning —
structured conclusions only, rule 18G)."""
out = _rust("summary", basis)
if out is not _MISSING:
return out
return {
"schema_version": basis.get("schema_version"),
"question": basis.get("question"),
@@ -193,6 +239,11 @@ def revise_depth(basis: dict, *, new_depth: str, reason: str) -> dict:
depths both persist. An OUTWARD→INWARD/NONE downgrade requires
explicit resolution evidence ('discovery resolved', 'prior-art
resolved', 'unnecessary') — never a bare skip."""
out = _rust("revise_depth", basis, new_depth=new_depth, reason=reason)
if out is not _MISSING:
basis.clear()
basis.update(out["basis"])
return out["revision"]
if new_depth not in DEPTH_TIERS:
raise ValueError(f"unknown depth {new_depth!r}")
original = basis["depth"]["depth"]
@@ -216,11 +267,17 @@ def revise_depth(basis: dict, *, new_depth: str, reason: str) -> dict:
def to_json(basis: dict) -> str:
out = _rust("to_json", basis)
if out is not _MISSING:
return out
import json
return json.dumps(basis, indent=1, sort_keys=True)
def from_json(text: str) -> dict:
out = _rust("from_json", text)
if out is not _MISSING:
return out
import json
return json.loads(text)
@@ -248,6 +305,10 @@ def generate_research_plan(*, intent: str, target: str = "",
risks of THIS intent — the function has no topic vocabulary and no
permanent category list. Depth, when not supplied, is chosen by
consequence heuristics recorded in the plan (why is always present)."""
out = _rust("generate_research_plan", intent, target, current_reality,
unknowns, decisions, risks, depth)
if out is not _MISSING:
return out
unknowns = [u for u in (unknowns or []) if u.strip()]
decisions = [d for d in (decisions or []) if d.strip()]
risks = [r for r in (risks or []) if r.strip()]
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.206.0"' in rune_version,
'__version__ = "0.210.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+51
View File
@@ -34,8 +34,32 @@ revision consumed so "which builds used the old rule" stays answerable.
from __future__ import annotations
import os
from rune import structural_store as ss
def _core():
"""The Rust pattern core, or None when pinned to the oracle / not built.
RUNE_CP_CORE=python pins the oracle; the kernel is a drop-in and every
caller falls back to the oracle path on any kernel error."""
if os.environ.get("RUNE_CP_CORE", "auto") == "python":
return None
try:
from rune import cp_loader
return cp_loader if cp_loader.available() else None
except Exception:
return None
def _need_dict(need) -> dict:
return {
"objective": need.objective,
"affected_surface": list(need.affected_surface),
"constraints": dict(need.constraints),
}
__all__ = [
"ConstructionPattern",
"evaluate_applicability",
@@ -168,6 +192,12 @@ def record_pattern_proposal(
Recorded with verification state PENDING until deterministic project
evidence, research evidence, proven construction evidence, or an
explicit user decision accepts it."""
core = _core()
if core is not None:
try:
return core.propose(pattern_id, provider, model, list(evidence), confidence)
except Exception:
pass # oracle fallback below; never crash the caller
if not provider or not model:
raise ValueError("pattern proposal requires provider+model")
if not 0.0 <= confidence <= 1.0:
@@ -256,6 +286,13 @@ def evaluate_applicability(
need explicitly declares the pattern non-applicable, DOES_NOT_APPLY
with the exclusion recorded.
"""
core = _core()
if core is not None:
try:
return core.evaluate(pattern.to_dict(), _need_dict(need),
dict(surface_result), dict(authorities))
except Exception:
pass # oracle fallback below; never crash the caller
# §3/§18: PROJECT evidence overrides the pattern. Exclusions may be
# declared by the project/need (pattern_exclusions constraint) or by the
# pattern itself (non-applicability domains).
@@ -427,6 +464,14 @@ def derive_pattern_obligations(
verdict, reason/evidence, and provenance kind PATTERN (distinct from
Tranche 5's STRUCTURAL derivation — §8 provenance separation).
Project exceptions (§18) suppress with provenance retained."""
core = _core()
if core is not None:
try:
return core.derive(pattern.to_dict(), _need_dict(need), dict(surface_result),
dict(authorities),
project_exceptions=list(project_exceptions or []))
except Exception:
pass # oracle fallback below; never crash the caller
project_exceptions = list(project_exceptions or [])
out = {
"pattern": pattern.pattern_id,
@@ -474,6 +519,12 @@ def combine_pattern_obligations(derivations: list[dict]) -> dict:
same obligation id collapse (provenance lists merge); conflicting
obligations (same id, contradictory verdicts across patterns) surface
as conflicts requiring evidence/user decision — never silently chosen."""
core = _core()
if core is not None:
try:
return core.combine(list(derivations))
except Exception:
pass # oracle fallback below; never crash the caller
merged: dict[str, dict] = {}
conflicts: list[dict] = []
for d in derivations:
+86 -41
View File
@@ -1,11 +1,22 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust construction_projection core (facade burn-down).
"""ctypes loader for the Rust construction_pattern core (dichotomy wave,
#2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law):
* cp_project({contract_entity, project_id, ...}) -> projection dict
* cp_free_string(ptr)
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* cp_construct({pattern dict}) -> normalized pattern dict
* cp_revise({pattern, reason, evidence, changes?}) -> new pattern dict
* cp_propose({pattern_id, provider, model, evidence, confidence}) -> dict
* cp_evaluate({pattern, need, surface_result, authorities}) -> verdict dict
* cp_derive({pattern, need, surface_result, authorities,
project_exceptions?}) -> derivation dict
* cp_combine({derivations}) -> {"obligations", "conflicts"}
The `need` is a plain dict {objective, affected_surface, constraints} —
the kernel never sees the live Need object.
Free: capability_scoring's cs_free_string (shared process). Override the
.so with CP_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
@@ -14,64 +25,98 @@ import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "construction_projection"
_CRATE_DIR = _REPO / "src" / "rust" / "construction_pattern"
_LIB_PATH = Path(
os.environ.get(
"CP_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconstruction_projection.so"
)
os.environ.get("CP_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconstruction_pattern.so")
)
_lib = None
class ConstructionPatternCoreUnavailable(FileNotFoundError):
"""The Rust pattern core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
_SYMS = ("cp_construct", "cp_revise", "cp_propose", "cp_evaluate", "cp_derive", "cp_combine")
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise FileNotFoundError(f"construction_projection not built: {_LIB_PATH}")
if not _LIB_PATH.is_file():
raise ConstructionPatternCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
lib.cp_free_string.argtypes = [ctypes.c_void_p]
lib.cp_free_string.restype = None
fn = lib.cp_project
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p
for sym in _SYMS:
getattr(lib, sym).argtypes = [ctypes.c_void_p]
getattr(lib, sym).restype = ctypes.c_void_p
# free comes from capability_scoring (same process) if loaded; else our own malloc path
try:
lib.cp_free_string
except AttributeError:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
ptr = getattr(lib, sym)(json.dumps(payload).encode("utf-8"))
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.cp_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
out = ctypes.string_at(ptr).decode()
free = getattr(lib, "_free", None)
if free is not None:
free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def project(contract_entity: dict, contract_id: str = "", project_id: str = "", originating_goal_id: str = "",
operation_id: str = "", waiting_state: str = "", live_correlation: str = "",
stage: str = "", builder_state: str = "", verification_state: str = "",
checkpoint: str = "", failure_class: str = "", completion_state: str = "") -> dict:
return _call("cp_project", {"contract_entity": contract_entity,
"contract_id": contract_id,
"project_id": project_id,
"originating_goal_id": originating_goal_id,
"operation_id": operation_id,
"waiting_state": waiting_state,
"live_correlation": live_correlation,
"stage": stage, "builder_state": builder_state,
"verification_state": verification_state,
"checkpoint": checkpoint,
"failure_class": failure_class,
"completion_state": completion_state})
def construct(pattern: dict) -> dict:
return _call("cp_construct", dict(pattern))
def revise(pattern: dict, reason: str, evidence: list, changes: dict | None = None) -> dict:
payload = {"pattern": pattern, "reason": reason, "evidence": list(evidence)}
if changes is not None:
payload["changes"] = dict(changes)
return _call("cp_revise", payload)
def propose(pattern_id: str, provider: str, model: str, evidence: list, confidence: float) -> dict:
return _call("cp_propose", {
"pattern_id": pattern_id, "provider": provider, "model": model,
"evidence": list(evidence), "confidence": confidence,
})
def evaluate(pattern: dict, need: dict, surface_result: dict, authorities: dict) -> dict:
return _call("cp_evaluate", {
"pattern": pattern, "need": need,
"surface_result": surface_result, "authorities": authorities,
})
def derive(pattern: dict, need: dict, surface_result: dict, authorities: dict,
project_exceptions: list | None = None) -> dict:
payload = {"pattern": pattern, "need": need,
"surface_result": surface_result, "authorities": authorities}
if project_exceptions is not None:
payload["project_exceptions"] = list(project_exceptions)
return _call("cp_derive", payload)
def combine(derivations: list) -> dict:
return _call("cp_combine", {"derivations": list(derivations)})
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust drift_observability core (dichotomy wave,
#2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* dob_add_alert({alerts, alert_type, message, severity, now}) -> {alerts}
* dob_check_alerts({alerts, velocity, direction, last_washed, now})
-> {new_alerts, alerts}
* dob_run_retrospective({new_entries, washed_entries, promoted_entries,
decayed_entries, velocity, direction, days, now, now_local})
-> {report, filename}
* dob_get_unacknowledged_alerts({alerts}) -> {unacked}
* dob_acknowledge_alert({alerts, index}) -> {alerts, changed}
No-clock law: `now` (aware UTC ISO-8601) and `now_local` (naive local
ISO-8601) are always arguments — the kernel never reads the wall clock.
Boundary: the sqlite reads (drift_core's compute_drift_velocity /
compute_drift_direction, the MAX(last_washed) probe, the four
retrospective counts) and the alerts / retrospectives FILE IO stay in the
Python facade (drift_observability.py); the kernel is a pure function of
their results.
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char* — the default c_int truncates 64-bit pointers).
Strings are freed via capability_scoring's cs_free_string — the kernel
exports NO free symbol of its own (duplicate exports break the cdylib
link when sibling crates share one process).
Override the .so with DO_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "drift_observability"
_LIB_PATH = Path(
os.environ.get(
"DO_RUST_LIB", _CRATE_DIR / "target" / "release" / "libdrift_observability.so"
)
)
_SYMBOLS = (
"dob_add_alert",
"dob_check_alerts",
"dob_run_retrospective",
"dob_get_unacknowledged_alerts",
"dob_acknowledge_alert",
)
_lib = None
class DriftObservabilityCoreUnavailable(FileNotFoundError):
"""The Rust drift core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise DriftObservabilityCoreUnavailable(
f"Rust drift_observability core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in _SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
# free via any sibling free symbol already in-process (capability_scoring)
try:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
scoring.cs_free_string(ptr)
except OSError:
pass # leak-on-fallback only if capability_scoring absent; bounded
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def add_alert(alerts, alert_type, message, severity="info", now="") -> list:
return _call("dob_add_alert", {
"alerts": alerts, "alert_type": alert_type, "message": message,
"severity": severity, "now": now,
})["alerts"]
def check_alerts(alerts, velocity, direction, last_washed, now) -> dict:
return _call("dob_check_alerts", {
"alerts": alerts, "velocity": velocity, "direction": direction,
"last_washed": last_washed, "now": now,
})
def run_retrospective(new_entries, washed_entries, promoted_entries,
decayed_entries, velocity, direction, days,
now, now_local) -> dict:
return _call("dob_run_retrospective", {
"new_entries": new_entries, "washed_entries": washed_entries,
"promoted_entries": promoted_entries, "decayed_entries": decayed_entries,
"velocity": velocity, "direction": direction, "days": days,
"now": now, "now_local": now_local,
})
def get_unacknowledged_alerts(alerts) -> list:
return _call("dob_get_unacknowledged_alerts", {"alerts": alerts})["unacked"]
def acknowledge_alert(alerts, index) -> dict:
return _call("dob_acknowledge_alert", {"alerts": alerts, "index": index})
+84
View File
@@ -35,6 +35,23 @@ NEGATIVE_DRIFT_THRESHOLD = 0.1 # stability ratio below this = alert
PROMOTION_STALL_DAYS = 14 # no promotions in this many days = alert
def _do_core():
"""Dichotomy wave #2479: Rust core first (RUNE_DO_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the alert state
machine (stamp/append, threshold checks, assessment lattice,
acknowledge bounds); the sqlite reads and FILE IO stay here."""
if os.environ.get("RUNE_DO_CORE", "auto") == "python":
return None
try:
from rune import do_loader
if do_loader.available():
return do_loader
except ImportError:
pass
return None
class DriftObservability:
"""Drift monitoring and alerting system."""
@@ -60,6 +77,14 @@ class DriftObservability:
def add_alert(self, alert_type: str, message: str, severity: str = "info"):
"""Add a drift alert."""
core = _do_core()
if core is not None:
self.alerts = core.add_alert(
self.alerts, alert_type, message, severity,
datetime.now(timezone.utc).isoformat(),
)
self._save_alerts()
return
alert = {
"type": alert_type,
"message": message,
@@ -74,6 +99,23 @@ class DriftObservability:
"""Check for conditions that warrant alerts."""
import drift_core
core = _do_core()
if core is not None:
velocity = drift_core.compute_drift_velocity(db)
direction = drift_core.compute_drift_direction(db)
last_promo = db.execute(
"SELECT MAX(last_washed) FROM entries WHERE rock_status != 'washable' AND status = 'active'"
).fetchone()
out = core.check_alerts(
self.alerts, velocity, direction,
last_promo[0] if last_promo else None,
datetime.now(timezone.utc).isoformat(),
)
self.alerts = out["alerts"]
if out["new_alerts"]:
self._save_alerts()
return out["new_alerts"]
new_alerts = []
# Check velocity
@@ -130,6 +172,38 @@ class DriftObservability:
"""
import drift_core
core = _do_core()
if core is not None:
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
counts = {
"new_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE created_at > ? AND status = 'active'", (cutoff,)
).fetchone()[0],
"washed_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE last_washed > ? AND status = 'active'", (cutoff,)
).fetchone()[0],
"promoted_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE rock_status != 'washable' AND last_washed > ? AND status = 'active'",
(cutoff,),
).fetchone()[0],
"decayed_entries": db.execute(
"SELECT COUNT(*) FROM entries WHERE status = 'deleted' AND last_accessed > ?", (cutoff,)
).fetchone()[0],
}
out = core.run_retrospective(
counts["new_entries"], counts["washed_entries"],
counts["promoted_entries"], counts["decayed_entries"],
drift_core.compute_drift_velocity(db, days),
drift_core.compute_drift_direction(db),
days,
datetime.now(timezone.utc).isoformat(),
datetime.now().isoformat(),
)
filepath = os.path.join(RETROSPECTIVES_DIR, out["filename"])
with open(filepath, "w") as f:
json.dump(out["report"], f, indent=2)
return out["report"]
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
# Count changes by type
@@ -188,10 +262,20 @@ class DriftObservability:
def get_unacknowledged_alerts(self) -> List[dict]:
"""Get alerts that haven't been acknowledged."""
core = _do_core()
if core is not None:
return core.get_unacknowledged_alerts(self.alerts)
return [a for a in self.alerts if not a.get("acknowledged")]
def acknowledge_alert(self, index: int):
"""Acknowledge an alert."""
core = _do_core()
if core is not None:
out = core.acknowledge_alert(self.alerts, index)
if out["changed"]:
self.alerts = out["alerts"]
self._save_alerts()
return
if 0 <= index < len(self.alerts):
self.alerts[index]["acknowledged"] = True
self._save_alerts()
+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.206.0
Version: v0.210.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -2037,7 +2037,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.206.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.210.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+215
View File
@@ -0,0 +1,215 @@
//! build_basis branch battery — parity vs the frozen Python-oracle truth
//! (frozen_bb_truth.json, 68 cases across every public function and
//! branch, frozen 2026-10-11). Embedded VERBATIM via include_str!.
//!
//! Timestamp fields the kernel generates (decided_at, created_at,
//! revised_at, plan_generated_at) are masked on both sides — wall clock,
//! not logic. created_at values inside basis INPUTS are fixed
//! pass-through and compare exactly.
use serde_json::Value;
use super::*;
const TRUTH: &str = include_str!("frozen_bb_truth.json");
/// Remove kernel-generated wall-clock fields wherever they appear.
fn mask(v: Value) -> Value {
match v {
Value::Object(map) => Value::Object(
map.into_iter()
.filter(|(k, _)| {
!matches!(
k.as_str(),
"decided_at" | "created_at" | "revised_at" | "plan_generated_at"
)
})
.map(|(k, v)| (k, mask(v)))
.collect(),
),
Value::Array(items) => Value::Array(items.into_iter().map(mask).collect()),
other => other,
}
}
fn cases(fn_name: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][fn_name]
.as_array()
.unwrap()
.clone()
}
fn input(case: &Value) -> Value {
case["input"].clone()
}
fn expected(case: &Value) -> &Value {
case.get("expected").expect("case must carry expected")
}
fn check(name: &str, actual: Value, case: &Value) {
let masked = mask(actual);
assert!(
&masked == expected(case),
"case {name} diverged:\n actual: {masked}\n expected: {}",
expected(case)
);
}
fn check_err(name: &str, actual: Result<Value, String>, case: &Value) {
let err = case.get("error").expect("case must carry error");
match actual {
Ok(_) => panic!("case {name} should have raised: {err}"),
Err(e) => assert_eq!(&e, err, "case {name} error message diverged"),
}
}
#[test]
fn research_depth_parity_battery() {
for case in cases("research_depth") {
let a = input(&case);
let out = research_depth(
a["route"].as_str().unwrap(),
a.get("mode").and_then(Value::as_str).unwrap_or(""),
a.get("justification").and_then(Value::as_str).unwrap_or(""),
);
check(case["name"].as_str().unwrap(), out, &case);
}
}
#[test]
fn new_basis_parity_battery() {
for case in cases("new_basis") {
let a = input(&case);
let out = new_basis(
a["question"].as_str().unwrap(),
&a["depth_decision"],
a.get("issue_ref").and_then(Value::as_str).unwrap_or(""),
);
check(case["name"].as_str().unwrap(), out, &case);
}
}
#[test]
fn set_dispositions_parity_battery() {
for case in cases("set_dispositions") {
let a = input(&case);
let mut basis = a["basis"].clone();
let errors = set_dispositions(&mut basis, &a["dispositions"]);
check(
case["name"].as_str().unwrap(),
serde_json::json!({"basis": basis, "errors": errors}),
&case,
);
}
}
#[test]
fn validate_basis_parity_battery() {
for case in cases("validate_basis") {
let a = input(&case);
let errors = validate_basis(&a["basis"]);
check(
case["name"].as_str().unwrap(),
serde_json::to_value(errors).unwrap(),
&case,
);
}
}
#[test]
fn is_qualified_parity_battery() {
for case in cases("is_qualified") {
let a = input(&case);
let out = is_qualified(&a["basis"]);
check(case["name"].as_str().unwrap(), Value::Bool(out), &case);
}
}
#[test]
fn attach_evidence_ref_parity_battery() {
for case in cases("attach_evidence_ref") {
let a = input(&case);
let out = attach_evidence_ref(&a["basis"]);
check(case["name"].as_str().unwrap(), Value::String(out), &case);
}
}
#[test]
fn summary_parity_battery() {
for case in cases("summary") {
let a = input(&case);
let out = summary(&a["basis"]);
check(case["name"].as_str().unwrap(), out, &case);
}
}
#[test]
fn revise_depth_parity_battery() {
for case in cases("revise_depth") {
let a = input(&case);
let mut basis = a["basis"].clone();
let r = revise_depth(&mut basis, a["new_depth"].as_str().unwrap(), a["reason"].as_str().unwrap());
match r {
Ok(revision) => check(
case["name"].as_str().unwrap(),
serde_json::json!({"basis": basis, "revision": revision}),
&case,
),
Err(e) => check_err(case["name"].as_str().unwrap(), Err(e), &case),
}
}
}
#[test]
fn to_json_parity_battery() {
for case in cases("to_json") {
let a = input(&case);
let out = to_json(&a["basis"]);
check(case["name"].as_str().unwrap(), Value::String(out), &case);
}
}
#[test]
fn from_json_parity_battery() {
for case in cases("from_json") {
let a = input(&case);
match from_json(a["text"].as_str().unwrap()) {
Ok(v) => check(case["name"].as_str().unwrap(), v, &case),
Err(_) => assert!(
case.get("error_kind").is_some(),
"case {} unexpected parse failure",
case["name"].as_str().unwrap()
),
}
}
}
#[test]
fn generate_research_plan_parity_battery() {
for case in cases("generate_research_plan") {
let a = input(&case);
let strs = |k: &str| -> Vec<String> {
a.get(k)
.and_then(Value::as_array)
.map(|arr| {
arr.iter()
.filter_map(Value::as_str)
.map(str::to_string)
.collect()
})
.unwrap_or_default()
};
let out = generate_research_plan(
a["intent"].as_str().unwrap(),
a.get("target").and_then(Value::as_str).unwrap_or(""),
a.get("current_reality").and_then(Value::as_str).unwrap_or(""),
&strs("unknowns"),
&strs("decisions"),
&strs("risks"),
a.get("depth").and_then(Value::as_str),
);
check(case["name"].as_str().unwrap(), out, &case);
}
}
File diff suppressed because it is too large. Load diff
+876 -38
View File
@@ -1,50 +1,888 @@
//! build_basis — build basis summary and evidence (W6).
//! PARITY port of src/rune/build_basis.py.
use serde::{Deserialize, Serialize};
//! build_basis — T18 §18C: durable versioned RESEARCH BASIS for
//! construction (V1 rule F: RESEARCH INFORMS STRUCTURE).
//!
//! FULL parity port of src/rune/build_basis.py (dichotomy wave, #2479
//! tranche, RUST side verdict). A Build Basis is a durable, schema-
//! versioned document capturing WHAT research decided and WHY, with
//! references — never research dumps. This module NEVER researches
//! (rule C): it records the depth decision derived from the canonical
//! route authority and structures adopt/adapt/combine/reject/innovate
//! decisions (rule E).
//!
//! FFI (doc-envelope law): every `bb_*` symbol takes one JSON arg and
//! returns {"ok": true, "value": …} or {"ok": false, "err": msg}.
//! NOTE: no bb_free_string export — sibling crates sharing the Python
//! process already export identical free symbols; a duplicate breaks the
//! cdylib link. Callers free through capability_scoring's cs_free_string.
//!
//! to_json reproduces the oracle byte-for-byte:
//! json.dumps(basis, indent=1, sort_keys=True) — including ensure_ascii
//! \uXXXX escaping (lowercase hex, surrogate pairs for astral chars),
//! code-point key sorting, and Python repr() strings inside error
//! messages.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BuildBasis {
#[serde(default)]
pub depth: String,
#[serde(default)]
pub implications: serde_json::Value,
#[serde(default)]
pub risks: Vec<String>,
#[serde(default)]
pub evidence: Vec<String>,
use serde_json::{json, Map, Value};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::time::{SystemTime, UNIX_EPOCH};
pub const SCHEMA_VERSION: i64 = 1;
pub const OUTWARD_PRIOR_ART_QUESTIONS: &[&str] = &[
"what already exists addressing this intent",
"what open-source systems/products/workflows attempt it",
"what architectural patterns already work",
"what standards/conventions exist",
"what can be directly ADOPTED",
"what can be ADAPTED",
"what can be COMBINED",
"what should be REJECTED",
"where is genuine INNOVATION required",
];
// ── decision lattices (enums with exhaustive matches at the JSON
// boundary — the wire always carries the oracle's exact strings) ─────
/// Research-depth lattice (rule C: derived from adaptive_router ROUTES,
/// never a second router).
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DepthTier {
None,
Inward,
Outward,
Full,
}
pub fn summarize(depth: &str, risks: Vec<String>, implications: serde_json::Value) -> BuildBasis {
BuildBasis {
depth: depth.to_string(),
implications,
risks,
evidence: vec![],
impl DepthTier {
pub fn as_str(self) -> &'static str {
match self {
DepthTier::None => "NONE",
DepthTier::Inward => "INWARD",
DepthTier::Outward => "OUTWARD",
DepthTier::Full => "FULL",
}
}
/// The oracle's DEPTH_TIERS tuple, rendered as Python repr.
pub const PY_TIERS_REPR: &'static str =
"('NONE', 'INWARD', 'OUTWARD', 'FULL')";
pub fn parse(s: &str) -> Option<DepthTier> {
match s {
"NONE" => Some(DepthTier::None),
"INWARD" => Some(DepthTier::Inward),
"OUTWARD" => Some(DepthTier::Outward),
"FULL" => Some(DepthTier::Full),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Disposition lattice (rule E): adopt/adapt/combine/reject/innovate.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Disposition {
Adopt,
Adapt,
Combine,
Reject,
Innovate,
}
#[test]
fn test_summarize() {
let b = summarize(
"STANDARD",
vec!["risk1".into()],
serde_json::json!({"migration": []}),
impl Disposition {
pub fn as_str(self) -> &'static str {
match self {
Disposition::Adopt => "ADOPT",
Disposition::Adapt => "ADAPT",
Disposition::Combine => "COMBINE",
Disposition::Reject => "REJECT",
Disposition::Innovate => "INNOVATE",
}
}
pub fn parse(s: &str) -> Option<Disposition> {
match s {
"ADOPT" => Some(Disposition::Adopt),
"ADAPT" => Some(Disposition::Adapt),
"COMBINE" => Some(Disposition::Combine),
"REJECT" => Some(Disposition::Reject),
"INNOVATE" => Some(Disposition::Innovate),
_ => None,
}
}
}
// ── time (matches time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) ──
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(if m <= 2 { y + 1 } else { y }, m, d)
}
pub fn now() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let days = secs.div_euclid(86400);
let rem = secs.rem_euclid(86400);
let (y, m, d) = civil_from_days(days);
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
y,
m,
d,
rem / 3600,
(rem % 3600) / 60,
rem % 60
)
}
// ── Python semantics helpers ────────────────────────────────────────────
/// Python str repr: single quotes unless the string contains ' and not ".
fn py_repr_str(s: &str) -> String {
let quote = if s.contains('\'') && !s.contains('"') {
'"'
} else {
'\''
};
let mut out = String::with_capacity(s.len() + 2);
out.push(quote);
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c == quote => {
out.push('\\');
out.push(c);
}
c if (c as u32) < 0x20 || (c as u32) == 0x7f => {
out.push_str(&format!("\\x{:02x}", c as u32));
}
c => out.push(c),
}
}
out.push(quote);
out
}
/// Python repr() of a JSON value (as f"{x!r}" renders it).
fn py_repr(v: Option<&Value>) -> String {
match v {
None | Some(Value::Null) => "None".into(),
Some(Value::Bool(b)) => if *b { "True" } else { "False" }.into(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::String(s)) => py_repr_str(s),
Some(other) => other.to_string(),
}
}
/// Python truthiness for JSON values (0, "", [], {}, null, False are falsy).
fn is_truthy(v: Option<&Value>) -> bool {
match v {
None | Some(Value::Null) => false,
Some(Value::Bool(b)) => *b,
Some(Value::Number(n)) => n.as_f64().map(|f| f != 0.0).unwrap_or(true),
Some(Value::String(s)) => !s.is_empty(),
Some(Value::Array(a)) => !a.is_empty(),
Some(Value::Object(o)) => !o.is_empty(),
}
}
/// Python str[:n] — code points, not bytes.
fn truncate_chars(s: &str, n: usize) -> String {
s.chars().take(n).collect()
}
/// schema_version equality with Python semantics: True == 1.
fn schema_version_ok(v: Option<&Value>) -> bool {
match v {
Some(Value::Number(n)) => n.as_i64() == Some(SCHEMA_VERSION),
Some(Value::Bool(b)) => *b, // Python True == 1
_ => false,
}
}
fn route_default_depth(route: &str) -> Option<DepthTier> {
match route {
"LOCAL_FIX" => Some(DepthTier::None),
"STANDARD_CHANGE" => Some(DepthTier::Inward),
"RESEARCHED_CHANGE" => Some(DepthTier::Full),
_ => None,
}
}
// ── research_depth ──────────────────────────────────────────────────────
/// The research-depth DECISION for a construction need. Derived from the
/// canonical route authority; `justification` is REQUIRED when deviating
/// from the route default — recorded, never silent (rule D).
pub fn research_depth(route: &str, mode: &str, justification: &str) -> Value {
let default = route_default_depth(route);
let mut depth = default.unwrap_or(DepthTier::Inward);
let mut why = format!("route {route} default");
if !justification.trim().is_empty() {
// an explicit justification selects a tier by name if given;
// otherwise it annotates the default (zero-copy: candidate
// borrows the trimmed justification)
let j = justification.trim();
let candidate = j.split(':').next().unwrap_or("").trim();
if let Some(tier) = DepthTier::parse(candidate) {
depth = tier;
why = j.to_string();
} else {
why = format!("route {route} default; note: {j}");
}
} else if default.is_none() {
why = format!(
"route {} unknown — INWARD floor (never silent NONE)",
py_repr_str(route)
);
assert_eq!(b.depth, "STANDARD");
assert_eq!(b.risks.len(), 1);
depth = DepthTier::Inward;
}
json!({"depth": depth.as_str(), "why": why, "route": route, "mode": mode,
"decided_at": now()})
}
// ── new_basis ───────────────────────────────────────────────────────────
/// Create an empty-but-valid Build Basis. References only — full research
/// dumps live in the Research Core's artifact dirs.
pub fn new_basis(question: &str, depth_decision: &Value, issue_ref: &str) -> Value {
json!({
"schema_version": SCHEMA_VERSION,
"kind": "build_basis",
"created_at": now(),
"question": question,
"issue_ref": issue_ref,
"depth": depth_decision,
"inward": {"findings": [], "refs": []},
"outward": {"prior_art_questions_answered": [],
"findings": [], "refs": []},
"deeper": {"used": false, "findings": [], "refs": []},
"dispositions": [],
"alternatives_considered": [],
"uncertainty": [],
"risks": [],
"implications": {"architecture": [], "structural": [],
"migration": [], "verification": []},
})
}
// ── set_dispositions / validate_basis / is_qualified ────────────────────
/// Record the adopt/adapt/combine/reject/innovate decisions (rule E).
/// Mutates `basis` and returns validation errors; the basis stays
/// unqualified until valid.
pub fn set_dispositions(basis: &mut Value, dispositions: &Value) -> Vec<String> {
let rows: Vec<Value> = dispositions
.as_array()
.map(|a| {
a.iter()
.map(|d| {
let get = |k: &str| d.get(k).cloned().unwrap_or(json!(""));
json!({"item": get("item"), "disposition": get("disposition"),
"why": get("why"), "ref": get("ref")})
})
.collect()
})
.unwrap_or_default();
basis["dispositions"] = Value::Array(rows);
validate_basis(basis)
}
/// Qualification gate. A basis is QUALIFIED when it carries a depth
/// decision with why, at least one outward prior-art answer when depth is
/// OUTWARD/FULL, and every disposition uses a legal disposition.
pub fn validate_basis(basis: &Value) -> Vec<String> {
let mut errors: Vec<String> = Vec::new();
if !schema_version_ok(basis.get("schema_version")) {
errors.push(format!(
"schema_version {}",
py_repr(basis.get("schema_version"))
));
}
// Python: (basis.get("depth") or {}) — falsy depth slots fall back to {}
let depth_obj = basis
.get("depth")
.filter(|d| is_truthy(Some(d)))
.and_then(|d| d.as_object());
let empty = Map::new();
let depth_obj = depth_obj.unwrap_or(&empty);
let depth = depth_obj.get("depth");
let depth_str = depth.and_then(Value::as_str);
let depth_tier = depth_str.and_then(DepthTier::parse);
if depth_tier.is_none() {
errors.push(format!(
"depth {} not in {}",
py_repr(depth),
DepthTier::PY_TIERS_REPR
));
}
if !is_truthy(depth_obj.get("why")) {
errors.push("depth decision lacks WHY (rule D)".into());
}
if matches!(depth_tier, Some(DepthTier::Outward) | Some(DepthTier::Full)) {
let outward = basis
.get("outward")
.filter(|o| is_truthy(Some(o)))
.and_then(Value::as_object);
let has_evidence = outward
.map(|o| {
is_truthy(o.get("prior_art_questions_answered"))
|| is_truthy(o.get("findings"))
|| is_truthy(o.get("refs"))
})
.unwrap_or(false);
if !has_evidence {
errors.push("OUTWARD/FULL depth requires prior-art evidence".into());
}
}
if depth_tier == Some(DepthTier::Inward) {
let inward = basis
.get("inward")
.filter(|o| is_truthy(Some(o)))
.and_then(Value::as_object);
let has_evidence = inward
.map(|o| is_truthy(o.get("findings")) || is_truthy(o.get("refs")))
.unwrap_or(false)
|| is_truthy(basis.get("dispositions"));
if !has_evidence {
errors.push(
"INWARD depth requires recorded evidence (findings/refs) — \
an empty basis is not qualified"
.into(),
);
}
}
for d in basis.get("dispositions").and_then(Value::as_array).unwrap_or(&vec![]) {
let disp = d.get("disposition");
if !disp
.and_then(Value::as_str)
.and_then(Disposition::parse)
.is_some()
{
let item = d.get("item").and_then(Value::as_str).unwrap_or("");
errors.push(format!(
"illegal disposition {} for {}",
py_repr(disp),
py_repr_str(&truncate_chars(item, 40))
));
}
}
errors
}
pub fn is_qualified(basis: &Value) -> bool {
validate_basis(basis).is_empty()
}
// ── attach_evidence_ref / summary ───────────────────────────────────────
/// The evidence_ref string a contract carries to point at this basis
/// (durable pointer, not a copy — stores stay independent).
pub fn attach_evidence_ref(basis: &Value) -> String {
// f-string uses str(): missing/null created_at renders as "None"
let created = match basis.get("created_at") {
None | Some(Value::Null) => "None".to_string(),
Some(Value::String(s)) => s.clone(),
Some(other) => other.to_string(),
};
let issue_ref = match basis.get("issue_ref") {
Some(Value::String(s)) if !s.is_empty() => s.clone(),
_ => "adhoc".to_string(),
};
format!("build_basis:{issue_ref}:{created}")
}
fn len_of(v: Option<&Value>) -> usize {
match v {
Some(Value::Array(a)) => a.len(),
Some(Value::String(s)) => s.chars().count(),
Some(Value::Object(o)) => o.len(),
_ => 0,
}
}
/// Compact projection for prompts/previews (no private reasoning —
/// structured conclusions only, rule 18G).
pub fn summary(basis: &Value) -> Value {
let empty_vec: Vec<Value> = Vec::new();
json!({
"schema_version": basis.get("schema_version").cloned().unwrap_or(Value::Null),
"question": basis.get("question").cloned().unwrap_or(Value::Null),
"issue_ref": basis.get("issue_ref").cloned().unwrap_or(Value::Null),
"depth": basis.get("depth").cloned().unwrap_or(Value::Null),
"qualified": is_qualified(basis),
"validation": validate_basis(basis),
"dispositions": basis
.get("dispositions")
.and_then(Value::as_array)
.unwrap_or(&empty_vec)
.iter()
.map(|d| {
json!({
"item": truncate_chars(d.get("item").and_then(Value::as_str).unwrap_or(""), 80),
"disposition": d.get("disposition").cloned().unwrap_or(Value::Null),
})
})
.collect::<Vec<Value>>(),
"uncertainty_count": len_of(basis.get("uncertainty")),
"risks_count": len_of(basis.get("risks")),
"implications": match basis.get("implications").and_then(Value::as_object) {
Some(o) => o
.iter()
.map(|(k, v)| (k.clone(), json!(len_of(Some(v)))))
.collect::<Map<String, Value>>(),
None => Map::new(),
},
})
}
// ── revise_depth ────────────────────────────────────────────────────────
/// §21.1G: record a justified depth revision. Original + revised depths
/// both persist. An OUTWARD→INWARD/NONE downgrade requires explicit
/// resolution evidence — never a bare skip.
pub fn revise_depth(basis: &mut Value, new_depth: &str, reason: &str) -> Result<Value, String> {
let tier = DepthTier::parse(new_depth)
.ok_or_else(|| format!("unknown depth {}", py_repr_str(new_depth)))?;
let original = basis
.get("depth")
.and_then(|d| d.get("depth"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
if original == new_depth {
return Err("no-op revision: depth unchanged".into());
}
// §21.1G: consequential origins mix route names and tiers — the
// oracle compares the raw stored string, so parity keeps that.
let consequential = matches!(original.as_str(), "RESEARCHED_CHANGE" | "FULL" | "OUTWARD");
if consequential && matches!(tier, DepthTier::None | DepthTier::Inward) {
let low = reason.to_lowercase();
let justified = [
"resolved",
"non-material",
"discovery",
"unnecessary",
"prior-art question",
]
.iter()
.any(|k| low.contains(k));
if !justified {
return Err(
"OUTWARD downgrade requires explicit evidence: the prior-art \
question must be resolved as non-material (not merely skipped)"
.into(),
);
}
}
let revision = json!({
"original_depth": original,
"revised_depth": new_depth,
"reason": truncate_chars(reason, 200),
"revised_at": now(),
});
basis["depth_revision"] = revision.clone();
basis["depth"]["depth"] = json!(new_depth);
basis["depth"]["why"] = json!(format!("revised from {original}: {reason}"));
Ok(revision)
}
// ── to_json / from_json (Python json.dumps/loads semantics) ─────────────
fn escape_py_json_string(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c if (c as u32) > 0x7e => {
let cp = c as u32;
if cp <= 0xFFFF {
out.push_str(&format!("\\u{:04x}", cp));
} else {
let v = cp - 0x10000;
out.push_str(&format!(
"\\u{:04x}\\u{:04x}",
0xD800 + (v >> 10),
0xDC00 + (v & 0x3FF)
));
}
}
c => out.push(c),
}
}
out.push('"');
}
/// json.dumps(value, indent=1, sort_keys=True) — byte-for-byte with the
/// oracle (ensure_ascii, code-point key order, 1-space indent steps).
pub fn to_json(basis: &Value) -> String {
let mut out = String::new();
write_py_json(basis, 0, &mut out);
out
}
fn write_py_json(v: &Value, indent: usize, out: &mut String) {
match v {
Value::Object(map) => {
if map.is_empty() {
out.push_str("{}");
return;
}
let mut keys: Vec<&String> = map.keys().collect();
keys.sort(); // UTF-8 byte order == code-point order
out.push_str("{\n");
for (i, k) in keys.iter().enumerate() {
for _ in 0..indent + 1 {
out.push(' ');
}
escape_py_json_string(k, out);
out.push_str(": ");
write_py_json(&map[k.as_str()], indent + 1, out);
if i + 1 < keys.len() {
out.push(',');
}
out.push('\n');
}
for _ in 0..indent {
out.push(' ');
}
out.push('}');
}
Value::Array(items) => {
if items.is_empty() {
out.push_str("[]");
return;
}
out.push_str("[\n");
for (i, item) in items.iter().enumerate() {
for _ in 0..indent + 1 {
out.push(' ');
}
write_py_json(item, indent + 1, out);
if i + 1 < items.len() {
out.push(',');
}
out.push('\n');
}
for _ in 0..indent {
out.push(' ');
}
out.push(']');
}
Value::String(s) => escape_py_json_string(s, out),
Value::Null => out.push_str("null"),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Number(n) => out.push_str(&n.to_string()),
}
}
pub fn from_json(text: &str) -> Result<Value, String> {
serde_json::from_str(text).map_err(|e| e.to_string())
}
// ── generate_research_plan ──────────────────────────────────────────────
const OUTWARD_KEYWORDS: &[&str] = &[
"exist",
"existing",
"prior art",
"precedent",
"standard",
"convention",
"library",
"tool",
"framework",
"approach",
];
const CONSEQUENTIAL_KEYWORDS: &[&str] = &[
"architecture",
"migration",
"replace",
"rewrite",
"security",
"schema",
"delete",
"deploy",
"breaking",
];
/// Generate a CONTEXT-SPECIFIC research plan for one construction intent.
/// Topics are synthesized from the material unknowns/decisions/risks of
/// THIS intent — no topic vocabulary, no permanent category list.
#[allow(clippy::too_many_arguments)]
pub fn generate_research_plan(
intent: &str,
target: &str,
current_reality: &str,
unknowns: &[String],
decisions: &[String],
risks: &[String],
depth: Option<&str>,
) -> Value {
let unknowns: Vec<&str> = unknowns.iter().map(String::as_str).filter(|u| !u.trim().is_empty()).collect();
let decisions: Vec<&str> = decisions.iter().map(String::as_str).filter(|d| !d.trim().is_empty()).collect();
let risks: Vec<&str> = risks.iter().map(String::as_str).filter(|r| !r.trim().is_empty()).collect();
let mut topics: Vec<Value> = Vec::new();
// each material unknown becomes a research topic aimed at resolving it
for u in &unknowns {
topics.push(json!({
"topic": format!("resolve unknown: {u}"),
"kind": "unknown-resolution",
"why": format!("material unknown blocks confident construction: {}",
truncate_chars(u, 80)),
}));
}
for d in &decisions {
topics.push(json!({
"topic": format!("inform decision: {d}"),
"kind": "decision-support",
"why": format!("consequential decision needs precedent/evidence: {}",
truncate_chars(d, 80)),
}));
}
for r in &risks {
topics.push(json!({
"topic": format!("de-risk: {r}"),
"kind": "risk-investigation",
"why": format!("identified risk needs verification/mitigation evidence: {}",
truncate_chars(r, 80)),
}));
}
// precedent question (rule E) attaches ONLY when outward evidence is
// plausibly relevant — an unknown/decision about external prior art.
let joined: String = unknowns
.iter()
.chain(decisions.iter())
.copied()
.collect::<Vec<&str>>()
.join(" ")
.to_lowercase();
let needs_outward = OUTWARD_KEYWORDS.iter().any(|w| joined.contains(w));
if needs_outward {
topics.push(json!({
"topic": "what already exists that is relevant to THIS intent \
(adopt/adapt/combine/reject/innovate)",
"kind": "prior-art",
"why": "material unknown references existing practice — \
precedent discovery required before deeper research",
}));
}
let (depth, why) = match depth {
Some(d) => (d.to_string(), "depth supplied by caller (recorded, caller accountable)".to_string()),
None => {
// consequence heuristics: novel/architectural/unsafe → deeper
let hay = format!("{intent} {}", risks.join(" ")).to_lowercase();
let consequential = CONSEQUENTIAL_KEYWORDS.iter().any(|w| hay.contains(w));
let d = if consequential && !topics.is_empty() {
"FULL"
} else if needs_outward && !topics.is_empty() {
"OUTWARD"
} else if !topics.is_empty() {
"INWARD"
} else {
"NONE"
};
let why = match d {
"FULL" => "consequence heuristics: consequential keywords + research topics present",
"OUTWARD" => "precedent-relevant unknowns present",
"INWARD" => "local understanding suffices",
_ => "no material unknowns — local evidence only",
};
(d.to_string(), why.to_string())
}
};
json!({
"intent": intent,
"target": target,
"current_reality_note": truncate_chars(current_reality, 200),
"depth": {"depth": depth, "why": why},
"topics": topics,
"plan_generated_at": now(),
})
}
// ── FFI (doc-envelope law) ──────────────────────────────────────────────
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
fn envelope(r: Result<Value, String>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
};
CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn arg_str<'a>(a: &'a Value, k: &str) -> &'a str {
a.get(k).and_then(Value::as_str).unwrap_or("")
}
fn arg_str_list(a: &Value, k: &str) -> Vec<String> {
a.get(k)
.and_then(Value::as_array)
.map(|arr| {
arr.iter()
.filter_map(Value::as_str)
.map(str::to_string)
.collect()
})
.unwrap_or_default()
}
/// bb_research_depth({"route", "mode"?, "justification"?}) → depth decision dict.
#[no_mangle]
pub extern "C" fn bb_research_depth(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
research_depth(
arg_str(&a, "route"),
arg_str(&a, "mode"),
arg_str(&a, "justification"),
)
}))
}
/// bb_new_basis({"question", "depth_decision", "issue_ref"?}) → basis dict.
#[no_mangle]
pub extern "C" fn bb_new_basis(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let dd = a
.get("depth_decision")
.cloned()
.ok_or_else(|| "missing depth_decision".to_string())?;
Ok(new_basis(arg_str(&a, "question"), &dd, arg_str(&a, "issue_ref")))
}))
}
/// bb_set_dispositions({"basis", "dispositions"}) → {"basis", "errors"}.
#[no_mangle]
pub extern "C" fn bb_set_dispositions(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut basis = a
.get("basis")
.cloned()
.ok_or_else(|| "missing basis".to_string())?;
let dispositions = a
.get("dispositions")
.cloned()
.unwrap_or_else(|| Value::Array(vec![]));
let errors = set_dispositions(&mut basis, &dispositions);
Ok(json!({"basis": basis, "errors": errors}))
}))
}
/// bb_validate_basis({"basis"}) → [errors].
#[no_mangle]
pub extern "C" fn bb_validate_basis(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(serde_json::to_value(validate_basis(basis)).unwrap_or_default())
}))
}
/// bb_is_qualified({"basis"}) → bool.
#[no_mangle]
pub extern "C" fn bb_is_qualified(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(Value::Bool(is_qualified(basis)))
}))
}
/// bb_attach_evidence_ref({"basis"}) → evidence_ref string.
#[no_mangle]
pub extern "C" fn bb_attach_evidence_ref(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(Value::String(attach_evidence_ref(basis)))
}))
}
/// bb_summary({"basis"}) → summary dict.
#[no_mangle]
pub extern "C" fn bb_summary(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(summary(basis))
}))
}
/// bb_revise_depth({"basis", "new_depth", "reason"}) → {"basis", "revision"}
/// or {"ok": false, "err": <exact Python ValueError message>}.
#[no_mangle]
pub extern "C" fn bb_revise_depth(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut basis = a
.get("basis")
.cloned()
.ok_or_else(|| "missing basis".to_string())?;
let revision = revise_depth(&mut basis, arg_str(&a, "new_depth"), arg_str(&a, "reason"))?;
Ok(json!({"basis": basis, "revision": revision}))
}))
}
/// bb_to_json({"basis"}) → JSON string (json.dumps indent=1 sort_keys).
#[no_mangle]
pub extern "C" fn bb_to_json(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(Value::String(to_json(basis)))
}))
}
/// bb_from_json({"text"}) → parsed value.
#[no_mangle]
pub extern "C" fn bb_from_json(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| from_json(arg_str(&a, "text"))))
}
/// bb_generate_research_plan({"intent", "target"?, "current_reality"?,
/// "unknowns"?, "decisions"?, "risks"?, "depth"?}) → plan dict.
#[no_mangle]
pub extern "C" fn bb_generate_research_plan(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
generate_research_plan(
arg_str(&a, "intent"),
arg_str(&a, "target"),
arg_str(&a, "current_reality"),
&arg_str_list(&a, "unknowns"),
&arg_str_list(&a, "decisions"),
&arg_str_list(&a, "risks"),
a.get("depth").and_then(Value::as_str),
)
}))
}
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery {
use super::*;
#[test]
fn crate_exists_and_compiles() {
assert!(true);
}
}
mod branch_battery;
+34 -6
View File
@@ -6,10 +6,37 @@ version = 4
name = "construction_pattern"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -47,7 +74,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
@@ -72,10 +98,12 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.151"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"foldhash",
"indexmap",
"itoa",
"memchr",
"serde",
@@ -85,9 +113,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.6"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc"
dependencies = [
"proc-macro2",
"quote",
+1 -2
View File
@@ -7,5 +7,4 @@ edition = "2021"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_json = { version = "1", features = ["preserve_order"] }
@@ -1,36 +1,173 @@
//! branch_battery.rs — #2479 RUST batch 34: the pattern-matching kernels.
//! ORACLE-VERIFIED: the token-matching discipline is correct — short
//! signals (<4 chars) must match WHOLE tokens, not substrings (the
//! 'ui'-in-'builtin' false-positive class is blocked by design).
//! branch_battery.rs — frozen-oracle battery for the construction_pattern
//! port (dichotomy wave #2479 tranche).
//!
//! Truth embedded VERBATIM from src/frozen_cp_truth.json (frozen by
//! scripts/gen_construction_pattern_truth.py against the live Python
//! oracle, RUNE_CP_CORE=python pinned): every case replays its recorded
//! INPUT through the kernel and asserts byte-identical EXPECTED output
//! (errors compare on the exact ValueError text).
#[cfg(test)]
mod battery {
// batch 155: the kernels are now SHIPPED pub fns (promoted from here).
use crate::{entity_matches, path_matches};
use crate::{
combine_pattern_obligations, construct_pattern, derive_pattern_obligations,
entity_matches, evaluate_applicability, path_matches, record_pattern_proposal,
revise_pattern,
};
use serde_json::Value;
fn truth() -> Value {
serde_json::from_str(include_str!("frozen_cp_truth.json")).expect("frozen truth parses")
}
fn cases(op: &str) -> Vec<Value> {
truth()["cases"]
.as_array()
.expect("cases array")
.iter()
.filter(|c| c["op"] == op)
.cloned()
.collect()
}
fn obj(v: &Value) -> serde_json::Map<String, Value> {
v.as_object().expect("object").clone()
}
fn run_construct(c: &Value) -> Result<Value, String> {
construct_pattern(&obj(&c["input"]["kwargs"])).map(Value::Object)
}
fn run_revise(c: &Value) -> Result<Value, String> {
let i = &c["input"];
let evidence: Vec<Value> = i["evidence"].as_array().cloned().unwrap_or_default();
let changes = if i["changes"].is_null() { None } else { Some(obj(&i["changes"])) };
revise_pattern(&obj(&i["pattern"]), i["reason"].as_str().unwrap_or(""), &evidence, changes.as_ref())
.map(Value::Object)
}
fn run_propose(c: &Value) -> Result<Value, String> {
let i = &c["input"];
if let Some(bounds) = i["confidence"].as_array() {
let mut out = Vec::new();
for b in bounds {
out.push(record_pattern_proposal(
i["pattern_id"].as_str().unwrap_or(""),
i["provider"].as_str().unwrap_or(""),
i["model"].as_str().unwrap_or(""),
vec![],
b.as_f64().unwrap(),
)?);
}
return Ok(Value::Array(out));
}
let evidence: Vec<&str> =
i["evidence"].as_array().map(|a| a.iter().filter_map(|e| e.as_str()).collect()).unwrap_or_default();
record_pattern_proposal(
i["pattern_id"].as_str().unwrap_or(""),
i["provider"].as_str().unwrap_or(""),
i["model"].as_str().unwrap_or(""),
evidence,
i["confidence"].as_f64().unwrap_or(f64::NAN),
)
}
fn run_evaluate(c: &Value) -> Value {
let i = &c["input"];
evaluate_applicability(&obj(&i["pattern"]), &obj(&i["need"]), &obj(&i["surface_result"]), &obj(&i["authorities"]))
}
fn run_derive(c: &Value) -> Value {
let i = &c["input"];
let exc: Vec<Value> =
if i["project_exceptions"].is_null() { vec![] } else { i["project_exceptions"].as_array().cloned().unwrap_or_default() };
derive_pattern_obligations(&obj(&i["pattern"]), &obj(&i["need"]), &obj(&i["surface_result"]), &obj(&i["authorities"]), &exc)
}
fn run_combine(c: &Value) -> Value {
let derivs: Vec<Value> = c["input"]["derivations"].as_array().cloned().unwrap_or_default();
combine_pattern_obligations(&derivs)
}
fn check(name: &str, got: Result<Value, String>, expected: &Value) {
match expected.get("error") {
Some(Value::String(msg)) => match got {
Err(e) => assert_eq!(&e, msg, "{name}: error text"),
Ok(v) => panic!("{name}: kernel succeeded ({v}) where oracle raised"),
},
_ => match got {
Ok(v) => assert_eq!(&v, expected, "{name}: value mismatch"),
Err(e) => panic!("{name}: unexpected kernel error {e}"),
},
}
}
#[test]
fn battery_construct() {
let cs = cases("construct");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_construct(c), &c["expected"]);
}
}
#[test]
fn battery_revise() {
let cs = cases("revise");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_revise(c), &c["expected"]);
}
}
#[test]
fn battery_propose() {
let cs = cases("propose");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_propose(c), &c["expected"]);
}
}
#[test]
fn battery_evaluate() {
let cs = cases("evaluate");
assert!(!cs.is_empty());
for c in &cs {
let got = run_evaluate(c);
assert_eq!(&got, &c["expected"], "{}: value mismatch", c["name"]);
}
}
#[test]
fn battery_derive() {
let cs = cases("derive");
assert!(!cs.is_empty());
for c in &cs {
let got = run_derive(c);
assert_eq!(&got, &c["expected"], "{}: value mismatch", c["name"]);
}
}
#[test]
fn battery_combine() {
let cs = cases("combine");
assert!(!cs.is_empty());
for c in &cs {
let got = run_combine(c);
assert_eq!(&got, &c["expected"], "{}: value mismatch", c["name"]);
}
}
// ── oracle-verified matcher discipline (batch 155, preserved) ──
#[test]
fn path_token_discipline() {
// ORACLE-VERIFIED: short signals match WHOLE tokens only
assert!(
!path_matches("ui", "src/rune/rune_tui.py"),
"'ui' is not a token in rune_tui.py"
);
assert!(
!path_matches("ui", "src/rune/builtin.py"),
"the 'ui'-in-'builtin' FP class"
);
assert!(
path_matches("spine", "src/rune/spine.py"),
"long signal: containment"
);
assert!(
path_matches("tui", "src/rune/rune_tui.py"),
"'tui' IS a token"
);
assert!(
!path_matches("cfg", "config/settings.py"),
"'cfg' not a token of config"
);
assert!(!path_matches("ui", "src/rune/rune_tui.py"), "'ui' is not a token in rune_tui.py");
assert!(!path_matches("ui", "src/rune/builtin.py"), "the 'ui'-in-'builtin' FP class");
assert!(path_matches("spine", "src/rune/spine.py"), "long signal: containment");
assert!(path_matches("tui", "src/rune/rune_tui.py"), "'tui' IS a token");
assert!(!path_matches("cfg", "config/settings.py"), "'cfg' not a token of config");
}
#[test]
@@ -39,21 +176,13 @@ mod battery {
assert!(entity_matches("module:", "module:spine"), "prefix match");
assert!(!entity_matches("ui:", "region:left"), "prefix mismatch");
assert!(entity_matches("spine", "module:spine"), "long containment");
// ORACLE-VERIFIED: 'ui' (no colon, <4 chars) does NOT match 'ui:chat'
// because the entity token-split keeps ':' as part of the token
assert!(
!entity_matches("ui", "ui:chat"),
"oracle truth: token discipline"
);
assert!(!entity_matches("ui", "ui:chat"), "oracle truth: token discipline");
}
#[test]
fn long_signal_containment() {
assert!(path_matches("structural", "src/rune/structural_store.py"));
assert!(entity_matches(
"construction",
"module:construction_pattern"
));
assert!(entity_matches("construction", "module:construction_pattern"));
assert!(!path_matches("structural", "src/rune/other.py"));
}
}
File diff suppressed because it is too large. Load diff
+833 -52
View File
@@ -1,74 +1,855 @@
//! construction_pattern — reusable construction knowledge (W6).
use serde::{Deserialize, Serialize};
//! construction_pattern — reusable evidence about HOW a class of thing
//! is normally built (SCAFFOLD-TRANCHE-006).
//!
//! FULL parity port of src/rune/construction_pattern.py (dichotomy wave,
//! #2479 tranche). A pattern is GUIDANCE, not authority (§3): project
//! evidence always wins. Applicability is evidence-backed and deterministic;
//! obligation derivation is filtered by the project; provenance answers
//! "why is this here"; revisions keep old-rule traceability (§17).
//!
//! FFI (doc-envelope law), one entry point per public oracle function:
//! cp_construct({pattern dict}) → normalized pattern dict
//! cp_revise({pattern, reason, evidence, changes?}) → new pattern dict
//! cp_propose({pattern_id, provider, model, evidence, confidence}) → dict
//! cp_evaluate({pattern, need, surface_result, authorities}) → verdict dict
//! cp_derive({pattern, need, surface_result, authorities,
//! project_exceptions?}) → derivation dict
//! cp_combine({derivations}) → {"obligations", "conflicts"}
//! The `need` arrives as a plain dict {objective, affected_surface,
//! constraints} — the kernel never sees the live Need object.
//!
//! The boundary-aware matchers are the oracle-verified kernels promoted by
//! batch 155 (the 'ui'-in-'builtin' false-positive class is blocked by
//! design): short (<4 char, char-counted) signals match whole tokens only.
//!
//! Language-advantage pass (Phase B, battery-pinned): the verdict lattices
//! are enums with exhaustive `as_str` matches at the JSON boundary; the
//! obligation `when`-condition is classified into a `WhenShape` enum and
//! dispatched exhaustively (no string-soup if-chains); signal/path/entity
//! lists borrow slices of the input JSON instead of cloning; tokenization
//! is zero-copy over the lowered input.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstructionPattern {
pub pattern_id: String,
pub description: String,
pub obligations: Vec<serde_json::Value>,
pub revision: String,
use serde_json::{json, Map, Value};
// ── verdict lattices (enums with exhaustive matches at the JSON boundary) ──
/// Applicability verdict lattice (§4-5): APPLIES / PARTIAL /
/// DOES_NOT_APPLY / UNKNOWN. UNKNOWN never becomes APPLIES because a name
/// sounds similar.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ApplicabilityVerdict {
Applies,
Partial,
DoesNotApply,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternApplicability {
pub verdict: String,
pub reason: String,
}
pub fn check_applicability(
_pattern: &ConstructionPattern,
project_evidence: &serde_json::Value,
) -> PatternApplicability {
// Patterns are GUIDANCE, not authority. Project evidence wins.
if project_evidence
.get("overrides_pattern")
.and_then(|o| o.as_bool())
.unwrap_or(false)
{
return PatternApplicability {
verdict: "DOES_NOT_APPLY".to_string(),
reason: "project evidence overrides pattern".to_string(),
};
}
PatternApplicability {
verdict: "APPLIES".to_string(),
reason: "no override".to_string(),
impl ApplicabilityVerdict {
fn as_str(self) -> &'static str {
match self {
ApplicabilityVerdict::Applies => "APPLIES",
ApplicabilityVerdict::Partial => "PARTIAL",
ApplicabilityVerdict::DoesNotApply => "DOES_NOT_APPLY",
ApplicabilityVerdict::Unknown => "UNKNOWN",
}
}
}
/// pathMatches — boundary-aware path-signal matching (batch 155: promoted
/// from the test module to the shipped lib — the kernels ARE the product).
/// Short (<4 char) signals match path TOKENS only (the 'ui'-in-'builtin'
/// false-positive class); longer signals may use containment.
/// Obligation `when`-condition verdict lattice (§6-7): REQUIRED /
/// APPLICABLE / NOT_APPLICABLE / UNKNOWN — an obligation with no
/// recognized `when` is UNKNOWN (never silently required).
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ObligationVerdict {
Required,
Applicable,
NotApplicable,
Unknown,
}
impl ObligationVerdict {
fn as_str(self) -> &'static str {
match self {
ObligationVerdict::Required => "REQUIRED",
ObligationVerdict::Applicable => "APPLICABLE",
ObligationVerdict::NotApplicable => "NOT_APPLICABLE",
ObligationVerdict::Unknown => "UNKNOWN",
}
}
}
/// The `when`-condition shapes (all deterministic; §6-7). Classified once,
/// dispatched exhaustively — a shape outside the lattice is Unrecognized
/// and surfaces as UNKNOWN, never REQUIRED.
enum WhenShape<'a> {
Unconditioned,
Always,
PathContains(&'a Value),
EntitiesMatch(&'a Value),
ContractsTouched(&'a Value),
Unrecognized(Vec<String>),
}
fn when_shape(obligation: &Map<String, Value>) -> WhenShape<'_> {
match obligation.get("when") {
None | Some(Value::Null) => WhenShape::Unconditioned,
Some(w) => {
if w.get("always").and_then(|v| v.as_bool()).unwrap_or(false) {
return WhenShape::Always;
}
// Python checks KEY PRESENCE (`"path_contains" in when`), so a
// null value still enters the branch — `get` mirrors that.
if let Some(subs) = w.get("path_contains") {
return WhenShape::PathContains(subs);
}
if let Some(subs) = w.get("entities_match") {
return WhenShape::EntitiesMatch(subs);
}
if let Some(wanted) = w.get("contracts_touched") {
return WhenShape::ContractsTouched(wanted);
}
match w {
Value::Object(m) => WhenShape::Unrecognized(m.keys().cloned().collect()),
// Python crashes on `when.get` before reaching the
// sorted-shape message for non-dict `when` — unreachable.
_ => WhenShape::Unrecognized(vec![]),
}
}
}
}
const KNOWN_KEYS: &[&str] = &[
"pattern_id",
"purpose",
"revision",
"maturity",
"signals",
"obligations",
"provenance",
"exclusions",
"toolchain_notes",
"runtime_effects",
];
const REL_TYPES: &[&str] = &["implements", "exposes", "provides", "serves"];
// ── Python-repr helpers (reason/error strings embed Python list/dict reprs) ──
/// Single-quoted Python string repr (escapes \\, \', \n, \r, \t).
fn py_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\'' => out.push_str("\\'"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(c),
}
}
out.push('\'');
out
}
/// Python `repr` of a JSON value: True/False/None, single-quoted strings,
/// `[...]` lists, `{k: v}` dicts in insertion order.
fn py_repr(v: &Value) -> String {
match v {
Value::Null => "None".to_string(),
Value::Bool(true) => "True".to_string(),
Value::Bool(false) => "False".to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => py_quote(s),
Value::Array(items) => {
let inner: Vec<String> = items.iter().map(py_repr).collect();
format!("[{}]", inner.join(", "))
}
Value::Object(map) => {
let inner: Vec<String> = map.iter().map(|(k, v)| format!("{}: {}", py_quote(k), py_repr(v))).collect();
format!("{{{}}}", inner.join(", "))
}
}
}
fn py_str_list<T: AsRef<str>>(items: &[T]) -> String {
let inner: Vec<String> = items.iter().map(|s| py_quote(s.as_ref())).collect();
format!("[{}]", inner.join(", "))
}
// ── shared boundary-aware matchers (Tranche 7 §0) ──────────────────────────
/// Oracle-faithful token split: runs of non-token chars are ONE separator,
/// with leading/trailing empties kept (`re.split` semantics — only the
/// empties are observable, via the empty-signal quirk). Zero-copy: tokens
/// borrow slices of the lowered input.
fn split_tokens<'a>(lowered: &'a str, keep_colon: bool) -> Vec<&'a str> {
let is_tok = |c: char| c.is_ascii_lowercase() || c.is_ascii_digit() || (keep_colon && c == ':');
if lowered.is_empty() {
return vec![""];
}
let mut toks: Vec<&'a str> = Vec::new();
let mut start: Option<usize> = None;
let mut ended_with_sep = false;
for (i, c) in lowered.char_indices() {
if is_tok(c) {
if start.is_none() {
start = Some(i);
}
ended_with_sep = false;
} else {
if let Some(s) = start.take() {
toks.push(&lowered[s..i]);
}
ended_with_sep = true;
}
}
if let Some(s) = start {
toks.push(&lowered[s..]);
}
if lowered.starts_with(|c: char| !is_tok(c)) {
toks.insert(0, "");
}
if ended_with_sep {
toks.push("");
}
toks
}
/// Boundary-aware path-signal matching: short (<4 char) signals match path
/// TOKENS only (the 'ui'-in-'builtin' false-positive class); longer signals
/// may use containment.
pub fn path_matches(signal: &str, path: &str) -> bool {
let s = signal.to_lowercase();
if s.len() >= 4 {
if s.chars().count() >= 4 {
return path.to_lowercase().contains(&s);
}
let lower = path.to_lowercase();
let tokens: std::collections::HashSet<&str> = lower
.split(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit())
.collect();
tokens.contains(s.as_str())
split_tokens(&path.to_lowercase(), false).iter().any(|t| *t == s)
}
/// entityMatches — same discipline for entity ids; ':'-suffixed signals
/// match the namespace prefix (structural kinds carry meaning there).
/// Same discipline for entity ids. Prefix signals ending in ':' (e.g.
/// 'ui:', 'region:') match the id's namespace prefix — structural kinds
/// carry meaning in their prefix, and prefix equality is exact, not a
/// substring accident.
pub fn entity_matches(signal: &str, entity_id: &str) -> bool {
let s = signal.to_lowercase();
if s.ends_with(':') {
return entity_id.to_lowercase().starts_with(&s);
return entity_id.to_lowercase().starts_with(s.as_str());
}
if s.len() >= 4 {
if s.chars().count() >= 4 {
return entity_id.to_lowercase().contains(&s);
}
let lower = entity_id.to_lowercase();
let tokens: std::collections::HashSet<&str> = lower
.split(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != ':')
.collect();
tokens.contains(s.as_str())
split_tokens(&entity_id.to_lowercase(), true).iter().any(|t| *t == s)
}
#[cfg(test)]
// ── small JSON accessors (borrowed; owned only where the output needs it) ──
fn str_list(v: &Value) -> Vec<&str> {
v.as_array()
.map(|a| a.iter().filter_map(|x| x.as_str()).collect())
.unwrap_or_default()
}
fn str_list_owned(v: &Value) -> Vec<String> {
str_list(v).into_iter().map(String::from).collect()
}
/// `ss.relationships(doc, from_id=e)` — including the falsy-filter quirk:
/// an empty from_id applies NO filter (Python `if from_id and ...`).
fn relationships<'a>(doc: &'a Value, from_id: &str) -> Vec<&'a Map<String, Value>> {
doc.get("relationships")
.and_then(|r| r.as_array())
.map(|rels| {
rels.iter()
.filter_map(|r| r.as_object())
.filter(|r| from_id.is_empty() || r.get("from").and_then(|f| f.as_str()).unwrap_or("") == from_id)
.collect()
})
.unwrap_or_default()
}
// ── pattern object (open-world) ───────────────────────────────────────────
/// `ConstructionPattern.from_dict` / constructor / `to_dict`: validate the
/// required fields, then normalize (sorted provenance-family lists, ext
/// passthrough). The input and output share one shape — `to_dict` output
/// round-trips through here unchanged.
pub fn construct_pattern(input: &Map<String, Value>) -> Result<Map<String, Value>, String> {
let pattern_id = input.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("");
if pattern_id.is_empty() {
return Err("pattern_id required".to_string());
}
let purpose = input.get("purpose").and_then(|v| v.as_str()).unwrap_or("");
if purpose.is_empty() {
return Err("purpose required".to_string());
}
if let Some(obs) = input.get("obligations").and_then(|v| v.as_array()) {
for o in obs {
for key in ["id", "obligation", "when"] {
if o.get(key).is_none() {
return Err(format!("obligation missing {}: {}", py_quote(key), py_repr(o)));
}
}
}
}
let mut out = Map::new();
out.insert("pattern_id".into(), Value::String(pattern_id.to_string()));
out.insert("purpose".into(), Value::String(purpose.to_string()));
out.insert(
"revision".into(),
input.get("revision").cloned().unwrap_or(json!(1)),
);
out.insert(
"maturity".into(),
input.get("maturity").cloned().unwrap_or(json!("CANDIDATE")),
);
out.insert(
"signals".into(),
input.get("signals").cloned().unwrap_or_else(|| Value::Object(Map::new())),
);
out.insert(
"obligations".into(),
input.get("obligations").cloned().unwrap_or_else(|| Value::Array(vec![])),
);
for key in ["provenance", "exclusions", "toolchain_notes", "runtime_effects"] {
let mut vals = input.get(key).map(str_list_owned).unwrap_or_default();
vals.sort_unstable();
out.insert(key.into(), json!(vals));
}
for (k, v) in input.iter() {
if !KNOWN_KEYS.contains(&k.as_str()) {
out.insert(k.clone(), v.clone());
}
}
Ok(out)
}
/// `ConstructionPattern.revise` (§17): returns a NEW pattern at revision+1
/// recording the change — prior meaning is never invisibly mutated.
pub fn revise_pattern(
pattern: &Map<String, Value>,
reason: &str,
evidence: &[Value],
changes: Option<&Map<String, Value>>,
) -> Result<Map<String, Value>, String> {
let pattern_id = pattern.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let revision = pattern.get("revision").and_then(|v| v.as_i64()).unwrap_or(1);
let mut merged = pattern.clone();
merged.remove("pattern_id");
if let Some(ch) = changes {
for (k, v) in ch.iter() {
merged.insert(k.clone(), v.clone());
}
}
merged.insert("revision".into(), json!(revision + 1));
let mut ev: Vec<String> = evidence.iter().filter_map(|e| e.as_str().map(String::from)).collect();
ev.sort_unstable();
let mut changed: Vec<String> = changes.map(|ch| ch.keys().cloned().collect()).unwrap_or_default();
changed.sort_unstable();
let entry = json!({
"prior_revision": revision,
"reason": reason.chars().take(200).collect::<String>(),
"evidence": ev,
"changed": changed,
});
match merged.get_mut("revision_history") {
Some(Value::Array(hist)) => hist.push(entry),
_ => {
merged.insert("revision_history".into(), Value::Array(vec![entry]));
}
}
merged.insert("pattern_id".into(), Value::String(pattern_id));
construct_pattern(&merged)
}
// ── model proposal seam (§5 — recorded, never authoritative) ────────────────
/// A model may PROPOSE a pattern; it may not certify applicability.
pub fn record_pattern_proposal(
pattern_id: &str,
provider: &str,
model: &str,
evidence: Vec<&str>,
confidence: f64,
) -> Result<Value, String> {
if provider.is_empty() || model.is_empty() {
return Err("pattern proposal requires provider+model".to_string());
}
if !(0.0..=1.0).contains(&confidence) {
return Err("confidence must be in [0,1]".to_string());
}
let mut ev = evidence;
ev.sort_unstable();
Ok(json!({
"kind": "pattern_proposal",
"pattern_id": pattern_id,
"provider": provider,
"model": model,
"evidence": ev,
"confidence": confidence,
"verification_state": "PENDING",
}))
}
// ── applicability (§4 — deterministic signals only) ─────────────────────────
fn surface_kind_present(kind: &str, surface: &Map<String, Value>) -> bool {
let mapped = match kind {
"source_path" => "affected_paths",
"workflow_config" => "workflow_config_surfaces",
"ui_component" => "affected_ui_surfaces",
"layout_region" => "affected_regions",
_ => "",
};
if surface.get(mapped).and_then(|v| v.as_array()).map(|a| !a.is_empty()).unwrap_or(false) {
return true;
}
surface
.get("resolutions")
.and_then(|v| v.as_array())
.map(|rs| rs.iter().any(|r| r.get("kind").and_then(|k| k.as_str()).unwrap_or("") == kind))
.unwrap_or(false)
}
/// Deterministic applicability verdict from the pattern's declared signals.
pub fn evaluate_applicability(
pattern: &Map<String, Value>,
need: &Map<String, Value>,
surface: &Map<String, Value>,
authorities: &Map<String, Value>,
) -> Value {
let pattern_id = pattern.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("");
let objective = need.get("objective").and_then(|v| v.as_str()).unwrap_or("");
let exclusions: Vec<&str> = pattern.get("exclusions").map(str_list).unwrap_or_default();
let need_exclusions: Vec<&str> = need
.get("constraints")
.and_then(|c| c.get("pattern_exclusions"))
.map(str_list)
.unwrap_or_default();
// §3/§18: PROJECT evidence overrides the pattern.
let objective_low = objective.to_lowercase();
for ex in exclusions.iter().copied().chain(need_exclusions.iter().copied()) {
if objective_low.contains(&ex.to_lowercase()) || need_exclusions.contains(&ex) {
return json!({
"pattern": pattern_id,
"verdict": ApplicabilityVerdict::DoesNotApply.as_str(),
"reason": format!("declared exclusion matched: {ex}"),
"evidence": {"exclusion": ex, "source": "project/need declaration"},
});
}
}
let signals = match pattern.get("signals").and_then(|v| v.as_object()) {
Some(s) if !s.is_empty() => s,
_ => {
return json!({
"pattern": pattern_id,
"verdict": ApplicabilityVerdict::Unknown.as_str(),
"reason": "pattern declares no applicability signals — nothing deterministic to check",
"evidence": {},
});
}
};
let mut groups_hit: Vec<&'static str> = Vec::new();
let mut groups_declared: usize = 0;
let mut evidence = Map::new();
let paths: Vec<&str> = surface
.get("affected_paths")
.map(str_list)
.unwrap_or_default()
.into_iter()
.chain(surface.get("workflow_config_surfaces").map(str_list).unwrap_or_default())
.collect();
let entities: Vec<&str> = surface.get("affected_existing_entities").map(str_list).unwrap_or_default();
let mut check = |group: &'static str, candidates: &[&str], present: &dyn Fn(&str) -> bool| {
if candidates.is_empty() {
return;
}
groups_declared += 1;
let hits: Vec<&str> = candidates.iter().filter(|c| present(c)).copied().collect();
if !hits.is_empty() {
groups_hit.push(group);
evidence.insert(group.to_string(), json!(hits.iter().take(5).collect::<Vec<_>>()));
}
};
let surface_kinds = signals.get("surface_kinds").map(str_list).unwrap_or_default();
check("surface_kinds", &surface_kinds, &|c| surface_kind_present(c, surface));
let path_signals = signals.get("path_contains").map(str_list).unwrap_or_default();
check("path_contains", &path_signals, &|c| paths.iter().any(|p| path_matches(c, p)));
let entity_signals = signals.get("entities_match").map(str_list).unwrap_or_default();
check("entities_match", &entity_signals, &|c| entities.iter().any(|e| entity_matches(c, e)));
let objective_words: Vec<String> =
objective.replace('/', " ").split_whitespace().map(|w| w.to_lowercase()).collect();
let objective_signals_owned: Vec<String> =
signals.get("objective_any").map(str_list).unwrap_or_default().into_iter().map(str::to_lowercase).collect();
let objective_signals: Vec<&str> = objective_signals_owned.iter().map(String::as_str).collect();
check("objective_any", &objective_signals, &|c| objective_words.iter().any(|w| w == c));
if let Some(contracts) = signals.get("contracts_match").and_then(|v| v.as_array()) {
if !contracts.is_empty() {
groups_declared += 1;
let wanted: Vec<&str> = contracts.iter().filter_map(|c| c.as_str()).collect();
let hits: Vec<String> = authorities
.values()
.flat_map(|doc| entities.iter().map(move |&e| (doc, e)))
.flat_map(|(doc, e)| relationships(doc, e).into_iter().map(move |r| (e, r)))
.filter(|(_, r)| REL_TYPES.contains(&r.get("type").and_then(|t| t.as_str()).unwrap_or("")))
.map(|(e, r)| (e, r.get("to").and_then(|t| t.as_str()).unwrap_or("")))
.filter(|(_, to)| wanted.contains(to))
.map(|(e, to)| format!("{e}->{to}"))
.collect();
if !hits.is_empty() {
groups_hit.push("contracts_match");
evidence.insert("contracts_match".into(), json!(hits.iter().take(5).collect::<Vec<_>>()));
}
}
}
if groups_declared == 0 {
return json!({
"pattern": pattern_id,
"verdict": ApplicabilityVerdict::Unknown.as_str(),
"reason": "no applicable signal groups declared",
"evidence": {},
});
}
let verdict = if groups_hit.len() == groups_declared {
ApplicabilityVerdict::Applies
} else if !groups_hit.is_empty() {
ApplicabilityVerdict::Partial
} else {
ApplicabilityVerdict::DoesNotApply
};
let mut hit_sorted = groups_hit.clone();
hit_sorted.sort_unstable();
let detail = if hit_sorted.is_empty() { "none".to_string() } else { py_str_list(&hit_sorted) };
json!({
"pattern": pattern_id,
"verdict": verdict.as_str(),
"reason": format!("{}/{} signal groups produced evidence: {}", groups_hit.len(), groups_declared, detail),
"evidence": evidence,
})
}
// ── obligation derivation (§6-7 — filtered by project evidence) ─────────────
/// Evaluate one obligation's `when` condition against project evidence.
pub fn obligation_when_hit(
obligation: &Map<String, Value>,
need: &Map<String, Value>,
surface: &Map<String, Value>,
authorities: &Map<String, Value>,
) -> (ObligationVerdict, String) {
let paths: Vec<&str> = surface
.get("affected_paths")
.map(str_list)
.unwrap_or_default()
.into_iter()
.chain(surface.get("workflow_config_surfaces").map(str_list).unwrap_or_default())
.collect();
let entities: Vec<&str> = surface
.get("affected_existing_entities")
.map(str_list)
.unwrap_or_default()
.into_iter()
.chain(surface.get("affected_ui_surfaces").map(str_list).unwrap_or_default())
.chain(surface.get("affected_regions").map(str_list).unwrap_or_default())
.collect();
match when_shape(obligation) {
WhenShape::Unconditioned => (
ObligationVerdict::Unknown,
"obligation declares no applicability condition".to_string(),
),
WhenShape::Always => (
ObligationVerdict::Required,
"pattern-level obligation (always applies when the pattern applies)".to_string(),
),
WhenShape::PathContains(subs) => {
let hits: Vec<&str> = str_list(subs)
.iter()
.flat_map(|sub| paths.iter().filter(|p| path_matches(sub, p)).copied())
.collect();
if !hits.is_empty() {
return (ObligationVerdict::Applicable, format!("affected path(s): {}", py_str_list(&hits[..3.min(hits.len())])));
}
(ObligationVerdict::NotApplicable, format!("no affected path matches {}", py_repr(subs)))
}
WhenShape::EntitiesMatch(subs) => {
let hits: Vec<&str> = str_list(subs)
.iter()
.flat_map(|sub| entities.iter().filter(|e| entity_matches(sub, e)).copied())
.collect();
if !hits.is_empty() {
return (
ObligationVerdict::Applicable,
format!("affected entity/entities: {}", py_str_list(&hits[..3.min(hits.len())])),
);
}
(ObligationVerdict::NotApplicable, format!("no affected entity matches {}", py_repr(subs)))
}
WhenShape::ContractsTouched(wanted) => {
let mut touched: Vec<String> = need
.get("constraints")
.and_then(|c| c.get("contracts"))
.map(str_list_owned)
.unwrap_or_default();
for doc in authorities.values() {
for e in &entities {
for r in relationships(doc, e) {
let rtype = r.get("type").and_then(|t| t.as_str()).unwrap_or("");
let to = r.get("to").and_then(|t| t.as_str()).unwrap_or("");
if REL_TYPES.contains(&rtype) && !touched.iter().any(|t| t == to) {
touched.push(to.to_string());
}
}
}
}
let hits: Vec<&str> =
str_list(wanted).into_iter().filter(|c| touched.iter().any(|t| t == c)).collect();
if !hits.is_empty() {
return (ObligationVerdict::Applicable, format!("contract(s) touched: {}", py_str_list(&hits)));
}
(ObligationVerdict::NotApplicable, format!("touched contracts do not include {}", py_repr(wanted)))
}
WhenShape::Unrecognized(keys) => {
let mut keys = keys;
keys.sort_unstable();
(ObligationVerdict::Unknown, format!("unrecognized when-condition shape: {}", py_str_list(&keys)))
}
}
}
/// Derive this pattern's obligations FILTERED by project evidence (§7).
pub fn derive_pattern_obligations(
pattern: &Map<String, Value>,
need: &Map<String, Value>,
surface: &Map<String, Value>,
authorities: &Map<String, Value>,
project_exceptions: &[Value],
) -> Value {
let pattern_id = pattern.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let revision = pattern.get("revision").cloned().unwrap_or(json!(1));
let provenance = pattern.get("provenance").cloned().unwrap_or_else(|| Value::Array(vec![]));
let mut accepted = Vec::new();
let mut rejected = Vec::new();
let mut unresolved = Vec::new();
if let Some(obs) = pattern.get("obligations").and_then(|v| v.as_array()) {
for o in obs {
let empty = Map::new();
let o_obj: &Map<String, Value> = o.as_object().unwrap_or(&empty);
let mut entry = Map::new();
entry.insert("id".into(), o.get("id").cloned().unwrap_or(Value::Null));
entry.insert("obligation".into(), o.get("obligation").cloned().unwrap_or(Value::Null));
entry.insert("pattern".into(), Value::String(pattern_id.clone()));
entry.insert("revision".into(), revision.clone());
entry.insert("provenance".into(), Value::String("PATTERN".to_string()));
entry.insert("pattern_provenance".into(), provenance.clone());
let oid = o.get("id").and_then(|v| v.as_str()).unwrap_or("");
if let Some(exc) = project_exceptions
.iter()
.find(|x| x.get("obligation").and_then(|v| v.as_str()).unwrap_or("") == oid)
{
entry.insert("verdict".into(), Value::String("EXCEPTED".to_string()));
entry.insert(
"reason".into(),
exc.get("reason").cloned().unwrap_or_else(|| Value::String(String::new())),
);
// Oracle field names on the exception are `evidence` /
// `authority`; they land on the entry as exception_*.
entry.insert(
"exception_evidence".into(),
exc.get("evidence").cloned().unwrap_or_else(|| Value::String(String::new())),
);
entry.insert(
"exception_authority".into(),
exc.get("authority").cloned().unwrap_or_else(|| Value::String("user".to_string())),
);
rejected.push(Value::Object(entry));
continue;
}
let (verdict, reason) = obligation_when_hit(o_obj, need, surface, authorities);
entry.insert("verdict".into(), Value::String(verdict.as_str().to_string()));
entry.insert("reason".into(), Value::String(reason));
match verdict {
ObligationVerdict::Required | ObligationVerdict::Applicable => accepted.push(Value::Object(entry)),
ObligationVerdict::NotApplicable => rejected.push(Value::Object(entry)),
ObligationVerdict::Unknown => unresolved.push(Value::Object(entry)),
}
}
}
json!({
"pattern": pattern_id,
"revision": revision,
"accepted": accepted,
"rejected": rejected,
"unresolved": unresolved,
})
}
/// Combine multiple patterns' derivations (§26-27): duplicates of the same
/// obligation id collapse (provenance lists merge); conflicting obligations
/// surface as conflicts — never silently chosen.
pub fn combine_pattern_obligations(derivations: &[Value]) -> Value {
let mut merged: Map<String, Value> = Map::new();
let mut order: Vec<String> = Vec::new();
let mut conflicts = Vec::new();
for d in derivations {
if let Some(acc) = d.get("accepted").and_then(|v| v.as_array()) {
for entry in acc {
let oid = entry.get("id").and_then(|v| v.as_str()).unwrap_or("").to_string();
if let Some(prev) = merged.get_mut(&oid) {
let prev_text = prev.get("obligation").and_then(|v| v.as_str()).unwrap_or("");
let new_text = entry.get("obligation").and_then(|v| v.as_str()).unwrap_or("");
if prev_text != new_text {
conflicts.push(json!({
"obligation_id": oid,
"conflict": "same id, different text",
"sources": [
prev.get("pattern").cloned().unwrap_or(Value::Null),
entry.get("pattern").cloned().unwrap_or(Value::Null),
],
}));
continue;
}
let prev_obj = prev.as_object_mut().unwrap();
match prev_obj.get_mut("also_from") {
Some(Value::Array(arr)) => {
arr.push(entry.get("pattern").cloned().unwrap_or(Value::Null))
}
_ => {
prev_obj.insert(
"also_from".into(),
json!([entry.get("pattern").cloned().unwrap_or(Value::Null)]),
);
}
}
continue;
}
order.push(oid.clone());
merged.insert(oid, entry.clone());
}
}
}
order.sort_unstable();
let obligations: Vec<Value> = order.into_iter().filter_map(|k| merged.remove(&k)).collect();
json!({"obligations": obligations, "conflicts": conflicts})
}
// ── FFI (doc-envelope law) ──────────────────────────────────────────────
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { CStr::from_ptr(ptr) }.to_str().map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
fn envelope(r: Result<Value, String>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
};
CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn obj_arg(a: &Value) -> Result<Map<String, Value>, String> {
a.as_object().cloned().ok_or_else(|| "argument must be a JSON object".to_string())
}
/// cp_construct({pattern dict}) → normalized pattern dict.
#[no_mangle]
pub extern "C" fn cp_construct(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).and_then(|a| construct_pattern(&a).map(Value::Object));
envelope(r)
}
/// cp_revise({pattern, reason, evidence, changes?}) → new pattern dict.
#[no_mangle]
pub extern "C" fn cp_revise(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).and_then(|a| {
let pattern = a.get("pattern").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let reason = a.get("reason").and_then(|v| v.as_str()).unwrap_or("");
let evidence: Vec<Value> =
a.get("evidence").and_then(|v| v.as_array()).cloned().unwrap_or_default();
let changes = a.get("changes").and_then(|v| v.as_object()).cloned();
revise_pattern(&pattern, reason, &evidence, changes.as_ref()).map(Value::Object)
});
envelope(r)
}
/// cp_propose({pattern_id, provider, model, evidence, confidence}) → dict.
#[no_mangle]
pub extern "C" fn cp_propose(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).and_then(|a| {
let evidence = a.get("evidence").map(str_list).unwrap_or_default();
let confidence = a.get("confidence").and_then(|v| v.as_f64()).unwrap_or(f64::NAN);
record_pattern_proposal(
a.get("pattern_id").and_then(|v| v.as_str()).unwrap_or(""),
a.get("provider").and_then(|v| v.as_str()).unwrap_or(""),
a.get("model").and_then(|v| v.as_str()).unwrap_or(""),
evidence,
confidence,
)
});
envelope(r)
}
/// cp_evaluate({pattern, need, surface_result, authorities}) → verdict dict.
#[no_mangle]
pub extern "C" fn cp_evaluate(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).map(|a| {
let pattern = a.get("pattern").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let need = a.get("need").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let surface = a.get("surface_result").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let authorities = a.get("authorities").and_then(|v| v.as_object()).cloned().unwrap_or_default();
evaluate_applicability(&pattern, &need, &surface, &authorities)
});
envelope(r)
}
/// cp_derive({pattern, need, surface_result, authorities,
/// project_exceptions?}) → derivation dict.
#[no_mangle]
pub extern "C" fn cp_derive(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).map(|a| {
let pattern = a.get("pattern").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let need = a.get("need").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let surface = a.get("surface_result").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let authorities = a.get("authorities").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let exceptions: Vec<Value> =
a.get("project_exceptions").and_then(|v| v.as_array()).cloned().unwrap_or_default();
derive_pattern_obligations(&pattern, &need, &surface, &authorities, &exceptions)
});
envelope(r)
}
/// cp_combine({derivations}) → {"obligations", "conflicts"}.
#[no_mangle]
pub extern "C" fn cp_combine(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).map(|a| {
let derivs: Vec<Value> =
a.get("derivations").and_then(|v| v.as_array()).cloned().unwrap_or_default();
combine_pattern_obligations(&derivs)
});
envelope(r)
}
// NOTE: no cp_free_string export — sibling crates sharing the Python
// process already export identical free symbols; a duplicate breaks the
// cdylib link. Callers free through capability_scoring's cs_free_string.
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery;
@@ -0,0 +1,101 @@
//! drift_observability branch battery — parity vs the frozen Python-oracle
//! truth (frozen_do_truth.json: 33 cases across all five public functions
//! and every branch — severity tiers, the three alert thresholds with
//! 14-day stall boundary, malformed/naive/NULL/empty last_washed skips,
//! all-three combined, the strict >0.3/>0.1 assessment lattice with the
//! velocity==10 boundary, empty db). Frozen 2026-10-11 via
//! scripts/gen_drift_observability_truth.py with RUNE_DO_CORE=python
//! pinned (poisoned-truth law). Embedded VERBATIM — never transcribed.
use crate::{
add_alert, acknowledge_alert, check_alerts, get_unacknowledged_alerts,
run_retrospective,
};
use serde_json::{json, Value};
const TRUTH: &str = include_str!("frozen_do_truth.json");
fn cases(group: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][group]
.as_array()
.unwrap()
.clone()
}
fn replay(group: &str, f: impl Fn(&Value) -> Value) {
for case in cases(group) {
let got = f(&case["input"]);
assert_eq!(
&got,
&case["expected"],
"[{}] case '{}' diverged",
group,
case["name"].as_str().unwrap()
);
}
}
#[test]
fn add_alert_parity_battery() {
replay("add_alert", |i| {
json!({"alerts": add_alert(
i["alerts"].as_array().unwrap(),
i["alert_type"].as_str().unwrap(),
i["message"].as_str().unwrap(),
i["severity"].as_str().unwrap(),
i["now"].as_str().unwrap(),
)})
});
}
#[test]
fn check_alerts_parity_battery() {
replay("check_alerts", |i| {
let (all, new_alerts) = check_alerts(
i["alerts"].as_array().unwrap(),
&i["velocity"],
&i["direction"],
i["last_washed"].as_str(),
i["now"].as_str().unwrap(),
);
json!({"new_alerts": new_alerts, "alerts": all})
});
}
#[test]
fn run_retrospective_parity_battery() {
replay("run_retrospective", |i| {
let n = |k: &str| i[k].as_i64().unwrap_or(0);
run_retrospective(
n("new_entries"),
n("washed_entries"),
n("promoted_entries"),
n("decayed_entries"),
&i["velocity"],
&i["direction"],
n("days"),
i["now"].as_str().unwrap(),
i["now_local"].as_str().unwrap(),
)
});
}
#[test]
fn get_unacknowledged_alerts_parity_battery() {
replay("get_unacknowledged_alerts", |i| {
json!({"unacked": get_unacknowledged_alerts(i["alerts"].as_array().unwrap())})
});
}
#[test]
fn acknowledge_alert_parity_battery() {
replay("acknowledge_alert", |i| {
let index = match &i["index"] {
Value::Bool(b) => *b as i64,
v => v.as_i64().unwrap_or(-1),
};
let (alerts, changed) = acknowledge_alert(i["alerts"].as_array().unwrap(), index);
json!({"alerts": alerts, "changed": changed})
});
}
File diff suppressed because it is too large. Load diff
+591 -22
View File
@@ -1,25 +1,594 @@
//! drift_observability — drift dashboard (W6).
pub fn dashboard(health: &serde_json::Value) -> serde_json::Value {
serde_json::json!({"dashboard": health})
//! drift_observability — drift monitoring, alerts, and retrospectives (W6).
//!
//! FULL parity port of src/rune/drift_observability.py (dichotomy wave,
//! #2479 tranche). Makes drift visible — observable drift is governable
//! drift (Layered Mutability, arXiv:2604.14717; VaG, arXiv:2608.05810).
//!
//! FFI (doc-envelope law): every symbol takes one JSON doc and returns
//! {"ok": true, "value": ...} | {"ok": false, "err": ...}:
//! * dob_add_alert({alerts, alert_type, message, severity, now})
//! → {"alerts": [...]}
//! * dob_check_alerts({alerts, velocity, direction, last_washed, now})
//! → {"new_alerts": [...], "alerts": [...]}
//! * dob_run_retrospective({new_entries, washed_entries, promoted_entries,
//! decayed_entries, velocity, direction, days, now, now_local})
//! → {"report": {...}, "filename": "YYYYMMDD_HHMMSS"}
//! * dob_get_unacknowledged_alerts({alerts}) → {"unacked": [...]}
//! * dob_acknowledge_alert({alerts, index}) → {"alerts": [...], "changed": bool}
//!
//! No-clock law: `now` (aware UTC ISO-8601, as the oracle's
//! datetime.now(timezone.utc).isoformat()) and `now_local` (naive local
//! ISO, as datetime.now().isoformat()) are always arguments.
//!
//! Boundary: the sqlite reads (drift_core's velocity/direction, the
//! MAX(last_washed) probe, the four retrospective counts) and the
//! alerts/retrospectives FILE IO stay in the Python facade — the kernel
//! is a pure function of their results (memory_store precedent).
//!
//! No dob_free_string export — sibling crates sharing the Python process
//! already export identical free symbols; callers free through
//! capability_scoring's cs_free_string.
//! Language-advantage pass (Phase B, battery-pinned): the emitted-alert
//! vocabulary (kind + severity) and the retrospective assessment lattice
//! are enums with exhaustive matches at the JSON boundary; assessment
//! summaries come back as canonical `&'static str`; check_alerts composes
//! the appended list with a borrow→clone chain instead of an intermediate
//! owned Vec clone; FFI errors are a typed `FfiError` enum behind the
//! Result envelope. The add_alert INPUT severity stays a pass-through
//! &str deliberately — the oracle forwards any caller string verbatim,
//! so a closed input lattice would be a parity break, not an advantage.
use serde_json::{json, Map, Value};
use std::fmt;
use std::os::raw::c_char;
pub const VELOCITY_THRESHOLD: i64 = 10;
pub const NEGATIVE_DRIFT_THRESHOLD: f64 = 0.1;
pub const PROMOTION_STALL_DAYS: i64 = 14;
// ── decision lattices (enums, not string soup) ──────────────────────────
/// Severity of an alert EMITTED by `check_alerts` — the oracle's checks
/// only ever produce "warning" (velocity, negative drift) or "info"
/// (promotion stall). Caller-supplied severities on `add_alert` are NOT
/// this lattice (pass-through &str, see module doc).
#[derive(Clone, Copy, PartialEq, Eq)]
enum EmittedSeverity {
Warning,
Info,
}
impl EmittedSeverity {
fn as_str(self) -> &'static str {
match self {
EmittedSeverity::Warning => "warning",
EmittedSeverity::Info => "info",
}
}
}
/// The three alert kinds `check_alerts` can raise (§ the oracle's three
/// threshold checks). Message formatting is centralized per kind.
#[derive(Clone, Copy, PartialEq, Eq)]
enum AlertKind {
HighVelocity,
NegativeDrift,
PromotionStall,
}
impl AlertKind {
fn as_str(self) -> &'static str {
match self {
AlertKind::HighVelocity => "high_velocity",
AlertKind::NegativeDrift => "negative_drift",
AlertKind::PromotionStall => "promotion_stall",
}
}
fn severity(self) -> EmittedSeverity {
match self {
AlertKind::HighVelocity | AlertKind::NegativeDrift => EmittedSeverity::Warning,
AlertKind::PromotionStall => EmittedSeverity::Info,
}
}
}
/// An alert raised by `check_alerts` — held typed until the JSON
/// boundary so the stamped and unstamped projections share one source
/// (the oracle returns unstamped copies from check_alerts and appends
/// stamped ones to the persisted list).
struct NewAlert {
kind: AlertKind,
message: String,
}
impl NewAlert {
fn unstamped(&self) -> Value {
json!({
"type": self.kind.as_str(),
"message": self.message,
"severity": self.kind.severity().as_str(),
})
}
fn stamped(&self, now: &str) -> Value {
json!({
"type": self.kind.as_str(),
"message": self.message,
"severity": self.kind.severity().as_str(),
"timestamp": now,
"acknowledged": false,
})
}
}
/// Retrospective assessment lattice — strict >0.3-and-calm > >0.1 >
/// everything else; the tier order is centralized in `classify` and the
/// summary vocabulary is canonical `&'static str` (no allocation).
#[derive(Clone, Copy, PartialEq, Eq)]
enum Assessment {
Positive,
Neutral,
Concerning,
}
impl Assessment {
fn as_str(self) -> &'static str {
match self {
Assessment::Positive => "positive",
Assessment::Neutral => "neutral",
Assessment::Concerning => "concerning",
}
}
fn summary(self) -> &'static str {
match self {
Assessment::Positive => {
"Drift is flowing naturally. Stability is increasing. No intervention needed."
}
Assessment::Neutral => "Drift is active but not concerning. Monitor for changes.",
Assessment::Concerning => {
"Drift is moving away from stability. Consider reviewing recent changes."
}
}
}
/// The oracle's assessment branch — `stability_ratio > 0.3 AND
/// changes_per_day < 10` first, then `> 0.1`, else concerning.
fn classify(stability_ratio: f64, changes_per_day: f64) -> Assessment {
if stability_ratio > 0.3 && changes_per_day < VELOCITY_THRESHOLD as f64 {
Assessment::Positive
} else if stability_ratio > 0.1 {
Assessment::Neutral
} else {
Assessment::Concerning
}
}
}
/// Python truthiness over a JSON value (`not a.get("acknowledged")`).
fn truthy(v: Option<&Value>) -> bool {
match v {
None | Some(Value::Null) => false,
Some(Value::Bool(b)) => *b,
Some(Value::Number(n)) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
Some(Value::String(s)) => !s.is_empty(),
Some(Value::Array(a)) => !a.is_empty(),
Some(Value::Object(o)) => !o.is_empty(),
}
}
/// str(float) parity: serde_json's shortest round-trip float repr matches
/// Python's (both print 12.0 as "12.0", 14.43 as "14.43").
fn py_num(v: &Value) -> String {
v.to_string()
}
// ── alert actions ───────────────────────────────────────────────────────────
/// `add_alert` — stamp and append; persistence stays in the facade.
pub fn add_alert(
alerts: &[Value],
alert_type: &str,
message: &str,
severity: &str,
now: &str,
) -> Vec<Value> {
let mut out = alerts.to_vec();
out.push(json!({
"type": alert_type,
"message": message,
"severity": severity, // info, warning, critical
"timestamp": now,
"acknowledged": false,
}));
out
}
/// `check_alerts` — the three threshold checks against velocity/direction
/// dicts (computed by the facade via drift_core) and the MAX(last_washed)
/// probe. Returns (alerts-with-appended, new_alerts-unstamped); the oracle
/// stamps a COPY via add_alert, so the returned new_alerts carry no
/// timestamp — the facade saves the appended list once.
pub fn check_alerts(
alerts: &[Value],
velocity: &Value,
direction: &Value,
last_washed: Option<&str>,
now: &str,
) -> (Vec<Value>, Vec<Value>) {
let mut raised: Vec<NewAlert> = Vec::new();
// Velocity
if velocity.get("alert").and_then(Value::as_bool).unwrap_or(false) {
raised.push(NewAlert {
kind: AlertKind::HighVelocity,
message: format!(
"Drift velocity is {} changes/day (threshold: {})",
py_num(velocity.get("changes_per_day").unwrap_or(&Value::Null)),
VELOCITY_THRESHOLD
),
});
}
// Direction
let ratio = direction
.get("stability_ratio")
.and_then(Value::as_f64)
.unwrap_or(0.0);
if ratio < NEGATIVE_DRIFT_THRESHOLD {
raised.push(NewAlert {
kind: AlertKind::NegativeDrift,
message: format!(
"Stability ratio is {:.2}% (threshold: {:.0}%)",
ratio * 100.0,
NEGATIVE_DRIFT_THRESHOLD * 100.0
),
});
}
// Promotion stall — the oracle's try/except skips unparseable or
// timezone-naive MAX(last_washed) values (ValueError/TypeError paths).
if let Some(last) = last_washed {
if !last.is_empty() {
if let Some(days_since) = days_since_now(last, now) {
if days_since > PROMOTION_STALL_DAYS {
raised.push(NewAlert {
kind: AlertKind::PromotionStall,
message: format!(
"No promotions in {} days (threshold: {})",
days_since, PROMOTION_STALL_DAYS
),
});
}
}
}
}
let all = alerts
.iter()
.cloned()
.chain(raised.iter().map(|a| a.stamped(now)))
.collect();
let new_alerts = raised.iter().map(NewAlert::unstamped).collect();
(all, new_alerts)
}
/// `run_retrospective` — assessment branch over the velocity/direction
/// dicts plus the four facade-computed counts. Returns the report dict and
/// the retrospective filename (the facade owns the file write). The wide
/// parameter list mirrors the oracle's assembled inputs one-to-one.
#[allow(clippy::too_many_arguments)]
pub fn run_retrospective(
new_entries: i64,
washed_entries: i64,
promoted_entries: i64,
decayed_entries: i64,
velocity: &Value,
direction: &Value,
days: i64,
now: &str,
now_local: &str,
) -> Value {
let ratio = direction
.get("stability_ratio")
.and_then(Value::as_f64)
.unwrap_or(0.0);
let changes_per_day = velocity
.get("changes_per_day")
.and_then(Value::as_f64)
.unwrap_or(0.0);
let assessment = Assessment::classify(ratio, changes_per_day);
let report = json!({
"period_days": days,
"new_entries": new_entries,
"washed_entries": washed_entries,
"promoted_entries": promoted_entries,
"decayed_entries": decayed_entries,
"velocity": velocity,
"direction": direction,
"assessment": assessment.as_str(),
"summary": assessment.summary(),
"timestamp": now,
});
json!({"report": report, "filename": local_stamp(now_local)})
}
/// `get_unacknowledged_alerts` — keep alerts whose `acknowledged` is
/// Python-falsy (missing key stays unacknowledged).
pub fn get_unacknowledged_alerts(alerts: &[Value]) -> Vec<Value> {
alerts
.iter()
.filter(|a| !truthy(a.get("acknowledged")))
.cloned()
.collect()
}
/// `acknowledge_alert` — out-of-range index is a silent no-op (the oracle
/// neither mutates nor saves); `changed` mirrors that bound.
pub fn acknowledge_alert(alerts: &[Value], index: i64) -> (Vec<Value>, bool) {
let mut out = alerts.to_vec();
if index < 0 || index as usize >= out.len() {
return (out, false);
}
out[index as usize]["acknowledged"] = Value::Bool(true);
(out, true)
}
// ── ISO-8601 arithmetic (Python datetime.timedelta.days semantics) ─────────
struct IsoInstant {
/// Epoch microseconds of the wall-clock fields MINUS the UTC offset.
micros: i64,
/// True when the string carries an explicit UTC offset (oracle:
/// aware datetime); naive values make the subtraction a TypeError.
aware: bool,
}
/// Strict ISO-8601 parse of the shapes `datetime.isoformat()` emits:
/// YYYY-MM-DDTHH:MM:SS[.ffffff][Z|±HH:MM]. Anything else (including a
/// missing offset) yields None — mirroring the oracle's try/except skip.
fn parse_iso(s: &str) -> Option<IsoInstant> {
let bytes = s.as_bytes();
if bytes.len() < 19 {
return None;
}
let num = |from: usize, to: usize| -> Option<i64> {
s.get(from..to)?.parse::<i64>().ok()
};
let (y, mo, d) = (num(0, 4)?, num(5, 7)?, num(8, 10)?);
let sep = bytes[10];
if sep != b'T' && sep != b't' && sep != b' ' {
return None;
}
let (h, mi, sec) = (num(11, 13)?, num(14, 16)?, num(17, 19)?);
let mut rest = &s[19..];
let mut micros = 0i64;
if let Some(stripped) = rest.strip_prefix('.') {
let digits: String = stripped.chars().take_while(|c| c.is_ascii_digit()).collect();
if digits.is_empty() {
return None;
}
let mut frac = digits.clone();
while frac.len() < 6 {
frac.push('0');
}
micros = frac[..6].parse::<i64>().ok()?;
rest = &stripped[digits.len()..];
}
let (offset_secs, aware) = match rest {
"" => (0, false),
"Z" | "z" => (0, true),
_ => {
let sign = match rest.as_bytes()[0] {
b'+' => 1,
b'-' => -1,
_ => return None,
};
let body = &rest[1..];
let body = body.trim_end_matches(|c: char| c.is_whitespace());
let (oh, om) = if body.len() == 5 && body.as_bytes()[2] == b':' {
(body[0..2].parse::<i64>().ok()?, body[3..5].parse::<i64>().ok()?)
} else if body.len() == 2 {
(body[0..2].parse::<i64>().ok()?, 0)
} else {
return None;
};
(sign * (oh * 3600 + om * 60), true)
}
};
let day_secs = days_from_civil(y, mo, d) * 86400 + h * 3600 + mi * 60 + sec - offset_secs;
Some(IsoInstant { micros: day_secs * 1_000_000 + micros, aware })
}
/// Howard Hinnant's days_from_civil — days since 1970-01-01.
fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
let y = if m <= 2 { y - 1 } else { y };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let mp = (m + 9) % 12;
let doy = (153 * mp + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
/// (aware now − aware last).days — timedelta.days FLOORS toward negative
/// infinity, so div_euclid (not truncating division).
fn days_since_now(last: &str, now: &str) -> Option<i64> {
let a = parse_iso(last)?;
let b = parse_iso(now)?;
if !a.aware || !b.aware {
return None;
}
Some((b.micros - a.micros).div_euclid(86_400_000_000))
}
/// datetime.now().strftime("%Y%m%d_%H%M%S") over the naive local ISO
/// string the facade passes.
fn local_stamp(now_local: &str) -> String {
let s = parse_iso(now_local);
match s {
Some(t) => {
let total_secs = t.micros.div_euclid(1_000_000);
let secs_of_day = total_secs.rem_euclid(86400);
let days = total_secs.div_euclid(86400);
let (y, m, d) = civil_from_days(days);
format!(
"{:04}{:02}{:02}_{:02}{:02}{:02}",
y,
m,
d,
secs_of_day / 3600,
(secs_of_day % 3600) / 60,
secs_of_day % 60
)
}
None => String::new(),
}
}
fn civil_from_days(z: i64) -> (i64, i64, i64) {
let z = z + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
(if m <= 2 { y + 1 } else { y }, m, d)
}
// ── FFI (doc-envelope law) ──────────────────────────────────────────────────
/// Typed FFI failure lattice behind the doc-envelope Result — the
/// caller-visible `err` string is unchanged (enum only shapes the Rust
/// side; parity is pinned by the battery).
enum FfiError {
NullPtr,
Utf8(std::str::Utf8Error),
Json(serde_json::Error),
}
impl fmt::Display for FfiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FfiError::NullPtr => write!(f, "null argument pointer"),
FfiError::Utf8(e) => write!(f, "{e}"),
FfiError::Json(e) => write!(f, "{e}"),
}
}
}
fn read_arg(ptr: *const c_char) -> Result<Value, FfiError> {
if ptr.is_null() {
return Err(FfiError::NullPtr);
}
let x = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map_err(FfiError::Utf8)?;
serde_json::from_str(x).map_err(FfiError::Json)
}
fn envelope(r: Result<Value, FfiError>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e.to_string()}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn alerts_of(a: &Value) -> &[Value] {
a.get("alerts")
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or(&[])
}
/// dob_add_alert({alerts, alert_type, message, severity, now}) → {alerts}
#[no_mangle]
pub extern "C" fn dob_add_alert(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let s = |k: &str| a.get(k).and_then(Value::as_str).unwrap_or("");
json!({"alerts": add_alert(
alerts_of(&a),
s("alert_type"),
s("message"),
s("severity"),
s("now"),
)})
}))
}
/// dob_check_alerts({alerts, velocity, direction, last_washed, now})
/// → {new_alerts, alerts}
#[no_mangle]
pub extern "C" fn dob_check_alerts(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let empty = Value::Object(Map::new());
let velocity = a.get("velocity").unwrap_or(&empty);
let direction = a.get("direction").unwrap_or(&empty);
let (all, new_alerts) = check_alerts(
alerts_of(&a),
velocity,
direction,
a.get("last_washed").and_then(Value::as_str),
a.get("now").and_then(Value::as_str).unwrap_or(""),
);
json!({"new_alerts": new_alerts, "alerts": all})
}))
}
/// dob_run_retrospective({new_entries, washed_entries, promoted_entries,
/// decayed_entries, velocity, direction, days, now, now_local})
/// → {report, filename}
#[no_mangle]
pub extern "C" fn dob_run_retrospective(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let i = |k: &str| a.get(k).and_then(Value::as_i64).unwrap_or(0);
let empty = Value::Object(Map::new());
run_retrospective(
i("new_entries"),
i("washed_entries"),
i("promoted_entries"),
i("decayed_entries"),
a.get("velocity").unwrap_or(&empty),
a.get("direction").unwrap_or(&empty),
i("days"),
a.get("now").and_then(Value::as_str).unwrap_or(""),
a.get("now_local").and_then(Value::as_str).unwrap_or(""),
)
}))
}
/// dob_get_unacknowledged_alerts({alerts}) → {unacked}
#[no_mangle]
pub extern "C" fn dob_get_unacknowledged_alerts(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
json!({"unacked": get_unacknowledged_alerts(alerts_of(&a))})
}))
}
/// dob_acknowledge_alert({alerts, index}) → {alerts, changed}
#[no_mangle]
pub extern "C" fn dob_acknowledge_alert(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
// Python accepts bools as ints (True == 1) — mirror the coercion.
let index = match a.get("index") {
Some(Value::Bool(b)) => *b as i64,
Some(v) => v.as_i64().unwrap_or(-1),
None => -1,
};
let (alerts, changed) = acknowledge_alert(alerts_of(&a), index);
json!({"alerts": alerts, "changed": changed})
}))
}
#[cfg(test)]
mod branch_battery {
use super::*;
#[test]
fn dashboard_shape() {
let health = serde_json::json!({
"authorities": {"spine": {"status": "CURRENT", "generation": 5}},
"relationships": [{"authority": "spine", "status": "STALE"}]
});
let d = dashboard(&health);
assert!(d.is_object());
}
#[test]
fn dashboard_empty() {
let d = dashboard(&serde_json::json!({}));
assert!(d.is_object());
}
}
mod branch_battery;
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,25 @@
# Contract BUILDER-2026-10-11T16:08:13Z-2317ae-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port drift_observability.py to FULL Rust (dichotomy wave, #2479 tranche): pure parity kernel over the alert/retrospective state machine (add/check/unack/acknowledge/retrospective report), doc-envelope FFI + ctypes loader + facade delegation with RUNE_DO_CORE=python pin, frozen-truth battery, live parity 100%.
## What it connects to
- `src/rune/drift_observability.py`
- `src/rust/drift_observability`
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [{"entity": "commit:819525c6", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}, {"entity": "commit:efee6c57", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}, {"entity": "truth:src/rust/drift_observability/src/frozen_do_truth.json", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}], "captured_at": "2026-10-11T20:41:55Z", "contract": "BUILDER-2026-10-11T16:08:13Z-2317ae-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": ["commit:819525c6", "commit:efee6c57", "truth:src/rust/drift_observability/src/frozen_do_truth.json"], "missed": ["commit:819525c6", "commit:efee6c57", "truth:src/rust/drift_observability/src/frozen_do_truth.json"], "needless": ["module:deploy_qualify"], "observed": ["commit:819525c6", "commit:efee6c57", "truth:src/rust/drift_observability/src/frozen_do_truth.json"], "observed_existing": [], "observed_new": ["commit:819525c6", "commit:efee6c57", "truth:src/rust/drift_observability/src/frozen_do_truth.json"], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": ["src/rune/drift_observability.py", "src/rust/drift_observability/src/branch_battery.rs", "src/rust/drift_observability/src/frozen_do_truth.json", "src/rust/drift_observability/src/lib.rs"], "out_of_scope": ["scripts/gen_drift_observability_truth.py", "src/rune/do_loader.py"], "verdict": "red"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,25 @@
# Contract BUILDER-2026-10-11T16:17:08Z-35a41f-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/attention_capsule.py to FULL Rust (dichotomy wave #2479 tranche): kernel parity port with doc-envelope FFI, ctypes loader mirroring broker_loader.py, facade delegation with RUNE_AC_CORE pin, frozen-truth branch battery.
## What it connects to
- `src/rune/attention_capsule.py`
- `src/rust/attention_capsule`
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-11T18:08:32Z", "contract": "BUILDER-2026-10-11T16:17:08Z-35a41f-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": ["src/rune/attention_capsule.py", "src/rust/attention_capsule/src/branch_battery.rs", "src/rust/attention_capsule/src/frozen_ac_truth.json", "src/rust/attention_capsule/src/lib.rs"], "out_of_scope": ["scripts/gen_attention_capsule_truth.py", "src/rune/ac_loader.py"], "verdict": "red"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record