Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81099a11f7 | ||
|
|
3c13be1d76 | ||
|
|
de9dee7dd9 | ||
|
|
c949df0e98 | ||
|
|
18c12fbda7 | ||
|
|
ee7da6d080 | ||
|
|
2fa29f408c | ||
|
|
f433efe375 | ||
|
|
be00b56a0c | ||
|
|
72d443423e | ||
|
|
f96991bf2e | ||
|
|
5dea803e92 | ||
|
|
30ec1c8153 | ||
|
|
ad7e6fef2c | ||
|
|
df9290fcff | ||
|
|
779a26eaae | ||
|
|
8111e0b3ba | ||
|
|
3f440693de | ||
|
|
1d4aa87ca4 | ||
|
|
61432632f7 | ||
|
|
5601fee02a | ||
|
|
cdca988d00 | ||
|
|
d05a9e6741 | ||
|
|
ddb7490cc6 | ||
|
|
d12fbab06a |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.121.0**
|
||||
**Current release: v0.128.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.121.0"
|
||||
version = "0.128.0"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_build_mode_wt_truth.py — build-mode batch 2 truth battery
|
||||
(contract BUILDER-2026-10-07T18:26:40Z-8be89a).
|
||||
|
||||
The LIVE BuildManager drives a REAL tmp git repo: create → state captured,
|
||||
merge → state captured, delete → state captured. Cases carry the exact
|
||||
argv sequences (from monkeypatched subprocess.run) + the state after each
|
||||
op + the error texts. The Go plan layer must produce the same argv; the
|
||||
Go exec harness proves the plans drive real git.
|
||||
|
||||
Usage: python3 scripts/gen_build_mode_wt_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
from rune import build_mode as bm # noqa: E402
|
||||
|
||||
FROZEN = "2026-10-07T18:28:00"
|
||||
|
||||
|
||||
class FrozenDatetime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return datetime(2026, 10, 7, 18, 28, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def main():
|
||||
bm.datetime = FrozenDatetime
|
||||
bm.REPO_DIR = str(make_git_repo())
|
||||
|
||||
calls = []
|
||||
|
||||
def spy_run(cmd, **kwargs):
|
||||
calls.append(list(cmd))
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
|
||||
bm.subprocess.run = spy_run
|
||||
|
||||
mgr = bm.BuildManager()
|
||||
build_id = mgr.start_build("worktree battery") # create_worktree inside
|
||||
create_call = calls[0]
|
||||
after_create = mgr.state.worktree_path
|
||||
|
||||
n_before_merge = len(calls)
|
||||
mgr.merge_worktree()
|
||||
merge_call = calls[n_before_merge] # the merge itself
|
||||
after_merge = mgr.state.worktree_path # None: delete follows the merge
|
||||
delete_call = calls[n_before_merge + 1], calls[n_before_merge + 2]
|
||||
|
||||
cases = [
|
||||
{
|
||||
"fn": "plan", "name": "create",
|
||||
"expected": {"argv": create_call},
|
||||
},
|
||||
{
|
||||
"fn": "plan", "name": "merge",
|
||||
"expected": {"argv": merge_call},
|
||||
},
|
||||
{
|
||||
"fn": "plan", "name": "delete_pair",
|
||||
"expected": {"argv": [list(delete_call[0]), list(delete_call[1])]},
|
||||
},
|
||||
{
|
||||
"fn": "state", "name": "round_trip",
|
||||
"expected": {
|
||||
"build_id": build_id,
|
||||
"after_create_is_set": after_create is not None,
|
||||
"after_merge_is_none": after_merge is None,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# error texts: merge failure mapping
|
||||
def fail_run(cmd, **kwargs):
|
||||
if cmd[1] == "merge":
|
||||
return subprocess.CompletedProcess(cmd, 128, "", "fatal: not something we can merge")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
bm.subprocess.run = fail_run
|
||||
mgr2 = bm.BuildManager()
|
||||
mgr2.state = bm.BuildState(
|
||||
build_id="20261007-182800", description="error battery",
|
||||
created_at=FROZEN)
|
||||
wt = "/home/nick/.rune/worktrees/20261007-182800"
|
||||
mgr2.state.worktree_path = wt # merge_worktree requires a live worktree path
|
||||
try:
|
||||
mgr2.merge_worktree()
|
||||
merge_err = None
|
||||
except RuntimeError as ex:
|
||||
merge_err = str(ex)
|
||||
cases.append({
|
||||
"fn": "error", "name": "merge_failure_text",
|
||||
"expected": {"error": merge_err},
|
||||
})
|
||||
|
||||
def fail_create(cmd, **kwargs):
|
||||
if cmd[1] == "worktree":
|
||||
return subprocess.CompletedProcess(cmd, 128, "", "'x' already exists")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
bm.subprocess.run = fail_create
|
||||
mgr3 = bm.BuildManager()
|
||||
mgr3.state = bm.BuildState(
|
||||
build_id="20261007-182800", description="error battery 2",
|
||||
created_at=FROZEN)
|
||||
mgr3._save_state()
|
||||
try:
|
||||
mgr3.create_worktree()
|
||||
create_err = None
|
||||
except RuntimeError as ex:
|
||||
create_err = str(ex)
|
||||
cases.append({
|
||||
"fn": "error", "name": "create_failure_text",
|
||||
"expected": {"error": create_err},
|
||||
})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/build_mode.py (live BuildManager on a real tmp git repo, clock frozen)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": FROZEN,
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/build-mode" / "truth_worktree.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
def make_git_repo():
|
||||
root = tempfile.mkdtemp(prefix="bm-wt-truth-")
|
||||
env_realloc = subprocess.run(
|
||||
["git", "init", "-q"], cwd=root, capture_output=True, text=True)
|
||||
assert env_realloc.returncode == 0
|
||||
subprocess.run(["git", "config", "user.email", "t@t"], cwd=root, capture_output=True)
|
||||
subprocess.run(["git", "config", "user.name", "t"], cwd=root, capture_output=True)
|
||||
(Path(root) / "seed.txt").write_text("seed\n")
|
||||
subprocess.run(["git", "add", "."], cwd=root, capture_output=True)
|
||||
subprocess.run(["git", "commit", "-qm", "seed"], cwd=root, capture_output=True)
|
||||
return root
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_memory_store_gate_truth.py — memory-store batch 4 truth battery
|
||||
(contract BUILDER-2026-10-07T19:47:37Z-1cf1dc).
|
||||
|
||||
The LIVE oracle: the dense-side meta-judgement extracted from recall()
|
||||
(lines 655-676) exercised against a FIXTURE STORE (sqlite entries + df
|
||||
counts), plus the entries-side fusion vs the chunk-side one (identity
|
||||
check), plus the abstain constants.
|
||||
|
||||
Usage: python3 scripts/gen_memory_store_gate_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
|
||||
import memory_store as ms # noqa: E402
|
||||
|
||||
|
||||
def make_fixture_store(db_path):
|
||||
"""Entries table + df data mirroring recall()'s df-of-word query."""
|
||||
db = sqlite3.connect(db_path)
|
||||
db.row_factory = sqlite3.Row
|
||||
db.execute("CREATE TABLE entries (id INTEGER PRIMARY KEY, text TEXT, status TEXT)")
|
||||
rows = [
|
||||
(1, "the curator gates memory writes through worthiness"),
|
||||
(2, "black holes bend light near the event horizon"),
|
||||
(3, "black coffee in the morning keeps nick running"),
|
||||
(4, "the spine rebuild parity proofs live in store"),
|
||||
(5, "memory writes require the operator approval gate"),
|
||||
(6, "unrelated filler text about weather patterns"),
|
||||
(7, "curator provenance recovery from odysseus"),
|
||||
(8, "another filler row to pad the document frequency"),
|
||||
]
|
||||
db.executemany("INSERT INTO entries VALUES (?,?,?)",
|
||||
[(i, t, "active") for (i, t) in rows])
|
||||
db.commit()
|
||||
return db
|
||||
|
||||
|
||||
def main():
|
||||
tmp = Path(tempfile.mkdtemp(prefix="ms-gate-truth-"))
|
||||
db = make_fixture_store(str(tmp / "store.db"))
|
||||
total = db.execute("SELECT COUNT(*) AS n FROM entries WHERE status='active'").fetchone()["n"]
|
||||
|
||||
def df_of(word):
|
||||
return db.execute(
|
||||
"SELECT COUNT(*) AS n FROM entries WHERE status='active' AND text LIKE ?",
|
||||
(f"%{word}%",),
|
||||
).fetchone()["n"]
|
||||
|
||||
# replicate recall()'s dense-gate block verbatim
|
||||
def oracle_gate(query, hits):
|
||||
q_content = {t for t in query.lower().split() if len(t) > 3 and t not in ms._STOPWORDS}
|
||||
dense = []
|
||||
for rowid, cos in hits:
|
||||
etxt = (db.execute("SELECT text FROM entries WHERE id=?", (rowid,)).fetchone()["text"] or "").lower()
|
||||
distinctive = [w for w in q_content if w in etxt and df_of(w) / total < 0.05]
|
||||
if distinctive:
|
||||
dense.append((rowid, cos))
|
||||
elif not q_content or cos >= 0.80:
|
||||
dense.append((rowid, cos))
|
||||
return dense
|
||||
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# gate: shared DISTINCTIVE word (curator: df=2/8=0.25? no — check)
|
||||
# df(curator) = rows 1,7 = 2 → 0.25 ≥ 0.05 → NOT distinctive!
|
||||
# The oracle's threshold means common-store words fail; only truly
|
||||
# rare words pass. Fixture exercise:
|
||||
for name, query, hits in [
|
||||
("distinctive_shared", "worthiness gates", [(1, 0.9), (2, 0.85)]),
|
||||
("common_word_falls_back", "black", [(2, 0.86), (3, 0.79)]),
|
||||
("strong_cos_no_share", "unrelated query terms", [(6, 0.91), (4, 0.70)]),
|
||||
("no_content_words", "the was and", [(1, 0.60), (6, 0.95)]),
|
||||
]:
|
||||
add("dense_gate", name,
|
||||
{"query": query, "hits": [[r, c] for r, c in hits], "total": total},
|
||||
{"kept": [[r, c] for r, c in oracle_gate(query, hits)],
|
||||
"q_content": sorted({t for t in query.lower().split() if len(t) > 3 and t not in ms._STOPWORDS}),
|
||||
"df": {w: df_of(w) for w in query.lower().split() if len(w) > 3}})
|
||||
|
||||
# entries-side fusion identity with the chunk-side one
|
||||
dense = [[1, 0.9], [2, 0.8]]
|
||||
bm25 = [[2, 10.0], [3, 4.0]]
|
||||
add("fusion_identity", "entries_eq_chunks",
|
||||
{"dense": dense, "bm25": bm25},
|
||||
{"fusion": ms._py_rank_fusion(dense, bm25)})
|
||||
|
||||
add("constants", "abstain",
|
||||
{},
|
||||
{"budget": ms.RECALL_BUDGET, "min_score": ms.RECALL_MIN_SCORE})
|
||||
|
||||
import shutil
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/memory_store.py recall() dense gate + _py_rank_fusion",
|
||||
"generated": "2026-10-07",
|
||||
"fixture_df_note": "df via LIKE-substring over active entries; threshold 0.05",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/memory-store-deep" / "truth_gate.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_memory_store_score_truth.py — memory-store batch 3 truth battery
|
||||
(contract BUILDER-2026-10-07T18:57:06Z-c2429c).
|
||||
|
||||
The LIVE _py_score_recall_rows oracle: metadata scoring across modes
|
||||
(hybrid / dense-only / lexical-only), recency decay, the abstention
|
||||
ladder (min_score gates, the 0.74 high-confidence gate, the 0.08 gap
|
||||
detection), position-aware re-rank.
|
||||
|
||||
Usage: python3 scripts/gen_memory_store_score_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
|
||||
import memory_store as ms # noqa: E402
|
||||
|
||||
NOW = "2026-10-07T18:58:00"
|
||||
|
||||
|
||||
def rows_for(ids):
|
||||
"""Fixture rows: confidence/temperature/recency variety."""
|
||||
out = []
|
||||
for i, eid in enumerate(ids):
|
||||
out.append({
|
||||
"id": eid,
|
||||
"text": f"entry {eid} body",
|
||||
"importance": 0.5 + i * 0.01,
|
||||
"topic": "project",
|
||||
"entities": ["a"],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9 if i % 2 == 0 else 0.6,
|
||||
"temperature": 1.2 if i % 3 == 0 else 0.8,
|
||||
"last_accessed": ["2026-10-07T18:00:00", "2026-09-01T10:00:00",
|
||||
"2026-05-01T10:00:00", None][i % 4],
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# hybrid: ranks over 4 rows, dense + bm25 present
|
||||
fusion = {"ranks": [[1, 0.12], [2, 0.11], [3, 0.10], [4, 0.09]],
|
||||
"best_dense": 0.8, "mode": "hybrid"}
|
||||
dense = [[1, 0.8], [2, 0.7], [3, 0.6]]
|
||||
bm25 = [[2, 10.0], [4, 5.0], [1, 2.0]]
|
||||
rows = rows_for([1, 2, 3, 4])
|
||||
add("score", "hybrid",
|
||||
{"fusion": fusion, "rows": rows, "dense": dense, "bm25": bm25,
|
||||
"budget": 3, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion, rows, dense, bm25, 3, 0.3, NOW)})
|
||||
|
||||
# dense-only: high confidence (>= 0.74) → position-aware
|
||||
fusion_d = {"ranks": [[1, 0.06], [2, 0.055], [3, 0.05]],
|
||||
"best_dense": 0.8, "mode": "dense-only"}
|
||||
add("score", "dense_only_high_conf",
|
||||
{"fusion": fusion_d, "rows": rows_for([1, 2, 3]), "dense": dense,
|
||||
"bm25": [], "budget": 5, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_d, rows_for([1, 2, 3]), dense, [], 5, 0.3, NOW)})
|
||||
|
||||
# dense-only: narrow gap → abstain
|
||||
dense_close = [[1, 0.80], [2, 0.75]]
|
||||
add("score", "dense_only_gap_abstain",
|
||||
{"fusion": fusion_d, "rows": rows_for([1, 2, 3]), "dense": dense_close,
|
||||
"bm25": [], "budget": 5, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_d, rows_for([1, 2, 3]), dense_close, [], 5, 0.3, NOW)})
|
||||
|
||||
# dense-only: below min_score → abstain
|
||||
add("score", "dense_only_min_score",
|
||||
{"fusion": fusion_d, "rows": rows_for([1, 2, 3]), "dense": dense,
|
||||
"bm25": [], "budget": 5, "min_score": 0.9, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_d, rows_for([1, 2, 3]), dense, [], 5, 0.9, NOW)})
|
||||
|
||||
# lexical-only: max relevance gate
|
||||
fusion_l = {"ranks": [[2, 0.9], [4, 0.7]], "best_dense": 0.0, "mode": "lexical-only"}
|
||||
add("score", "lexical_only_gate",
|
||||
{"fusion": fusion_l, "rows": rows_for([2, 4]), "dense": [], "bm25": bm25,
|
||||
"budget": 5, "min_score": 1.0, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_l, rows_for([2, 4]), [], bm25, 5, 1.0, NOW)})
|
||||
|
||||
# neither → []
|
||||
add("score", "neither",
|
||||
{"fusion": fusion_l, "rows": rows_for([2, 4]), "dense": [], "bm25": [],
|
||||
"budget": 5, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_l, rows_for([2, 4]), [], [], 5, 0.3, NOW)})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/memory_store.py _py_score_recall_rows",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/memory-store-deep" / "truth_score.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_memory_store_truth2.py — memory-store batch 2 truth battery
|
||||
(contract BUILDER-2026-10-07T18:05:47Z-152c16).
|
||||
|
||||
The LIVE _py_* oracles of memory_store.py. Cases: FTS conjunctive builder
|
||||
(stopword filtering, fallback, 8-cap), chunk_text (recombine + hard-split),
|
||||
rank fusion (hybrid/dense-only/lexical-only), project_chunk_rows,
|
||||
map_reduce, fuse_results (dedup + scale-alignment).
|
||||
|
||||
Usage: python3 scripts/gen_memory_store_truth2.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
|
||||
import memory_store as ms # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# ── FTS conjunctive ───────────────────────────────────────────────────
|
||||
add("fts", "significant_terms",
|
||||
{"query": "the curator gates memory writes"},
|
||||
{"q": ms._py_fts_query_conjunctive("the curator gates memory writes")})
|
||||
add("fts", "stopwords_dropped",
|
||||
{"query": "the and was with nick"},
|
||||
{"q": ms._py_fts_query_conjunctive("the and was with nick")})
|
||||
add("fts", "short_fallback",
|
||||
{"query": "go is fun ai"},
|
||||
{"q": ms._py_fts_query_conjunctive("go is fun ai")})
|
||||
long_q = "alpha beta gamma delta epsilon zeta eta theta iota kappa"
|
||||
add("fts", "cap_eight",
|
||||
{"query": long_q},
|
||||
{"q": ms._py_fts_query_conjunctive(long_q)})
|
||||
add("fts", "empty_returns_raw",
|
||||
{"query": "a an of"},
|
||||
{"q": ms._py_fts_query_conjunctive("a an of")})
|
||||
|
||||
# ── chunk_text ────────────────────────────────────────────────────────
|
||||
small = "one paragraph only"
|
||||
add("chunk", "under_size", {"text": small, "size": 1500, "overlap": 150},
|
||||
{"chunks": ms._py_chunk_text(small, 1500, 150)})
|
||||
para = "\n\n".join(f"paragraph {i} " + "x" * 40 for i in range(30))
|
||||
add("chunk", "recombine", {"text": para, "size": 400, "overlap": 150},
|
||||
{"chunks": ms._py_chunk_text(para, 400, 150)})
|
||||
noline = "y" * 3000
|
||||
add("chunk", "hard_split", {"text": noline, "size": 1000, "overlap": 150},
|
||||
{"chunks": ms._py_chunk_text(noline, 1000, 150)})
|
||||
add("chunk", "empty", {"text": "", "size": 1500, "overlap": 150},
|
||||
{"chunks": ms._py_chunk_text("", 1500, 150)})
|
||||
|
||||
# ── rank fusion ───────────────────────────────────────────────────────
|
||||
dense = [[1, 0.9], [2, 0.8], [3, 0.7]]
|
||||
bm25 = [[2, 12.0], [4, 8.0], [1, 3.0]]
|
||||
add("fuse_ranks", "hybrid", {"dense": dense, "bm25": bm25},
|
||||
ms._py_rank_fusion(dense, bm25))
|
||||
add("fuse_ranks", "dense_only", {"dense": dense, "bm25": []},
|
||||
ms._py_rank_fusion(dense, []))
|
||||
add("fuse_ranks", "lexical_only", {"dense": [], "bm25": bm25},
|
||||
ms._py_rank_fusion([], bm25))
|
||||
add("fuse_ranks", "neither", {"dense": [], "bm25": []},
|
||||
ms._py_rank_fusion([], []))
|
||||
|
||||
# ── project_chunk_rows ────────────────────────────────────────────────
|
||||
rows = [
|
||||
{"rowid": 1, "chunk_id": "c1", "text": "t" * 500, "wing": "w1", "room": "r1",
|
||||
"source_path": "a.md", "page": 3},
|
||||
{"rowid": 2, "chunk_id": "c2", "text": "short", "wing": "w1", "room": "r1",
|
||||
"source_path": "b.md"},
|
||||
]
|
||||
ranks = {"2": 0.0625, "1": 0.05}
|
||||
add("project", "budget_and_shape", {"ranks": ranks},
|
||||
{"rows": ms._py_project_chunk_rows(ranks, rows, 6)})
|
||||
|
||||
# ── map_reduce ────────────────────────────────────────────────────────
|
||||
entries = [{"id": i, "relevance": i * 0.1} for i in range(25)]
|
||||
add("map_reduce", "top3_per_chunk", {"entries": entries, "chunk_size": 10},
|
||||
{"mapped": ms._py_map_reduce(entries, 10)})
|
||||
add("map_reduce", "under_threshold", {"entries": entries[:5], "chunk_size": 10},
|
||||
{"mapped": ms._py_map_reduce(entries[:5], 10)})
|
||||
|
||||
# ── fuse_results ──────────────────────────────────────────────────────
|
||||
ents = [
|
||||
{"id": 1, "text": "nick prefers coffee", "topic": "human",
|
||||
"relevance": 2.5, "confidence": 0.9},
|
||||
{"id": 2, "text": "NICK PREFERS COFFEE", "topic": "human",
|
||||
"relevance": 1.1, "confidence": 0.8}, # dedup hit
|
||||
{"id": 3, "text": "rune gates memory writes", "topic": "project",
|
||||
"relevance": 5.6, "confidence": 0.95},
|
||||
]
|
||||
res = [
|
||||
{"chunk_id": "c1", "text": "the spine rebuild parity", "wing": "research",
|
||||
"rrf": 0.0625, "confidence": 0.7},
|
||||
{"chunk_id": "c2", "text": "curator provenance recovery", "wing": "research",
|
||||
"rrf": 0.05},
|
||||
]
|
||||
add("fuse_results", "dedup_scale_align", {"entries": ents, "research": res},
|
||||
{"merged": ms._py_fuse_results(ents, res)})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/memory_store.py _py_* oracles",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/memory-store-deep" / "truth_recall.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_scaffold_close_truth.py — scaffold batch 3 truth battery
|
||||
(contract BUILDER-2026-10-07T18:42:38Z-52829e).
|
||||
|
||||
The LIVE oracle: _py_close_quality bucket math + close_contract on a doc
|
||||
built through the real lifecycle (new_contract → transitions → snapshot →
|
||||
close). now() frozen at scaffold AND structural_store namespaces.
|
||||
|
||||
Usage: python3 scripts/gen_scaffold_close_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
from rune import scaffold as sc # noqa: E402
|
||||
from rune.structural_store import new_doc, add_entity # noqa: E402
|
||||
|
||||
FROZEN = "2026-10-07T18:46:00"
|
||||
|
||||
|
||||
def main():
|
||||
sc.now = lambda: FROZEN
|
||||
cases = []
|
||||
|
||||
# ── CloseQuality bucket math (direct, edge-covering) ──────────────────
|
||||
inputs = {
|
||||
"contract_id": "q1",
|
||||
"phase": "superseding",
|
||||
"affected_entities": ["module:a", "module:b"],
|
||||
"expected_new_entities": ["module:new1", "module:new2"],
|
||||
"predicted_existing_affected": ["module:c"],
|
||||
"predicted_frontier": {
|
||||
"level0_direct": ["module:a"],
|
||||
"level1_obligations": ["module:d"],
|
||||
"contracts": ["contract:x"],
|
||||
"level2_verification": ["canary:v"],
|
||||
},
|
||||
"expected_runtime_effects": ["module:a/ctl", "module:zz/never"],
|
||||
"observed_evidence": [
|
||||
{"entity": "module:a", "source": "tests", "detail": "d" * 400, "authority": "pytest"},
|
||||
{"entity": "module:new1", "source": "build", "detail": "created"},
|
||||
{"entity": "module:surprise", "source": "runtime", "detail": "unplanned"},
|
||||
{"entity": "", "source": "runtime", "detail": "no entity — ignored"},
|
||||
],
|
||||
"now": FROZEN,
|
||||
}
|
||||
s = sc.Scaffold(Path(tempfile.mkdtemp()), "q")
|
||||
cases.append({
|
||||
"fn": "close_quality", "name": "buckets",
|
||||
"input": {"inputs": inputs},
|
||||
"expected": s._py_close_quality(inputs),
|
||||
})
|
||||
|
||||
# ── CloseContract through the real lifecycle ──────────────────────────
|
||||
tmp = Path(tempfile.mkdtemp(prefix="scaffold-close-truth-"))
|
||||
s2 = sc.Scaffold(tmp, "close-project")
|
||||
s2.init(purpose="close battery")
|
||||
doc = s2.load()
|
||||
s2.new_contract(doc, contract_id="c1", need="n", scope=["src/go/x/"],
|
||||
affected_entities=["module:a"],
|
||||
expected_new_entities=["module:new1"])
|
||||
s2.transition_contract(doc, "c1", "approved")
|
||||
s2.transition_contract(doc, "c1", "building")
|
||||
s2.transition_contract(doc, "c1", "verifying")
|
||||
# predicted frontier on the entity
|
||||
e = s2.contract(doc, "c1")
|
||||
e.setdefault("ext", {})["predicted_frontier"] = {
|
||||
"level0_direct": ["module:a"], "level1_obligations": [],
|
||||
"contracts": [], "level2_verification": [],
|
||||
}
|
||||
ev = [{"entity": "module:a", "source": "tests", "detail": "passed"}]
|
||||
e2 = s2.close_contract(doc, "c1", observed_evidence=ev,
|
||||
scope_files=["src/go/x/main.go", "src/other/rogue.py"])
|
||||
cases.append({
|
||||
"fn": "close_contract", "name": "full_lifecycle",
|
||||
"input": {},
|
||||
"expected": {"entity": e2},
|
||||
})
|
||||
|
||||
# phase gate
|
||||
doc2 = new_doc()
|
||||
add_entity(doc2, id="contract:early", kind="construction_contract",
|
||||
identities={"need": "n"}, maturity="DECLARED",
|
||||
evidence_refs=["x"])
|
||||
doc2["entities"][-1]["phase"] = "draft"
|
||||
doc2["entities"][-1]["status"] = "open"
|
||||
doc2["entities"][-1]["checkpoints"] = []
|
||||
try:
|
||||
s2.close_contract(doc2, "early", observed_evidence=[])
|
||||
gate = {"ok": True}
|
||||
except Exception as ex: # noqa: BLE001 — intentional: the case IS the gate error; the oracle's text becomes the truth column
|
||||
gate = {"ok": False, "error": str(ex)}
|
||||
cases.append({
|
||||
"fn": "close_gate", "name": "phase_guard",
|
||||
"input": {},
|
||||
"expected": gate,
|
||||
})
|
||||
|
||||
# missing contract
|
||||
try:
|
||||
s2.close_contract(doc2, "ghost", observed_evidence=[])
|
||||
miss = {"ok": True}
|
||||
except Exception as ex: # noqa: BLE001 — intentional: same oracle-error-as-truth pattern as above
|
||||
miss = {"ok": False, "error": str(ex)}
|
||||
cases.append({
|
||||
"fn": "close_gate", "name": "missing_contract",
|
||||
"input": {},
|
||||
"expected": miss,
|
||||
})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/scaffold.py (live, now frozen at both namespaces)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": FROZEN,
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/scaffold" / "truth_close.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
import shutil
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_tool_executor_auth_truth.py — the tool-executor authority truth
|
||||
(contract BUILDER-2026-10-07T19:10:38Z-cc6be2).
|
||||
|
||||
Freezes the LIVE READ_ONLY_TOOLS set + the prepare-tool routing verdicts
|
||||
so CI asserts the Go set equals the Python set FOREVER. The run_command
|
||||
wrong-add this battery pins was a security-adjacent divergence: a mutating
|
||||
tool treated as read-only bypasses operator approval.
|
||||
|
||||
Usage: python3 scripts/gen_tool_executor_auth_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
|
||||
import tool_executor as te # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = [
|
||||
{
|
||||
"fn": "read_only_tools", "name": "exact_set",
|
||||
"input": {},
|
||||
"expected": {"tools": sorted(te.READ_ONLY_TOOLS)},
|
||||
},
|
||||
{
|
||||
"fn": "prepare_routing", "name": "run_command_requires_approval",
|
||||
"input": {"tool": "run_command", "args": {"command": "ls -la"}},
|
||||
"expected": {"read_only": "run_command" in te.READ_ONLY_TOOLS},
|
||||
},
|
||||
{
|
||||
"fn": "prepare_routing", "name": "read_file_executes_now",
|
||||
"input": {"tool": "read_file", "args": {"path": "/tmp"}},
|
||||
"expected": {"read_only": "read_file" in te.READ_ONLY_TOOLS},
|
||||
},
|
||||
]
|
||||
out = {
|
||||
"oracle": "src/rune/tool_executor.py READ_ONLY_TOOLS + routing",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/tool-executor" / "truth_auth.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_warm_router_digest_truth.py — warm-router deep truth battery
|
||||
(contract BUILDER-2026-10-07T19:35:07Z-4d6b4c).
|
||||
|
||||
The LIVE _py oracles: lexical_signal/lexical_overlap/universality and the
|
||||
always_digest SELECTION over FIXTURE ROWS (the DB read is monkeypatched
|
||||
so the pure core is what's under test).
|
||||
|
||||
Usage: python3 scripts/gen_warm_router_digest_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
|
||||
import warm_router as wr # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# ── lexical signal/overlap ────────────────────────────────────────────
|
||||
add("lexical", "signal_strips_punct",
|
||||
{"body": "The curator gates memory writes (always)."},
|
||||
{"words": sorted(wr._lexical_signal("The curator gates memory writes (always)."))})
|
||||
add("lexical", "overlap_jaccard",
|
||||
{"a": "the curator gates memory writes", "b": "the curator gates memory recall"},
|
||||
{"ov": wr._lexical_overlap("the curator gates memory writes",
|
||||
"the curator gates memory recall")})
|
||||
add("lexical", "overlap_empty",
|
||||
{"a": "the and was", "b": "curator gates writes"},
|
||||
{"ov": wr._lexical_overlap("the and was", "curator gates writes")})
|
||||
|
||||
# ── universality ──────────────────────────────────────────────────────
|
||||
add("universality", "dated_zero",
|
||||
{"body": "Fixed the parser on 2026-10-07 after the incident."},
|
||||
{"u": wr._universality("Fixed the parser on 2026-10-07 after the incident.")})
|
||||
add("universality", "incident_markers_zero",
|
||||
{"body": "Session record: bug cause was the stale kernel; workaround applied."},
|
||||
{"u": wr._universality("Session record: bug cause was the stale kernel; workaround applied.")})
|
||||
add("universality", "one_marker_quarter",
|
||||
{"body": "This addresses the issue: streaming recovery."},
|
||||
{"u": wr._universality("This addresses the issue: streaming recovery.")})
|
||||
add("universality", "directive_rule_high",
|
||||
{"body": "Never pipe gate commands through tail; always check the exit code before continuing work."},
|
||||
{"u": wr._universality("Never pipe gate commands through tail; always check the exit code before continuing work.")})
|
||||
add("universality", "rambling_penalty",
|
||||
{"body": " ".join(["word"] * 120) + " never always must keep"},
|
||||
{"u": wr._universality(" ".join(["word"] * 120) + " never always must keep")})
|
||||
|
||||
# ── always_digest selection over fixture rows (DB monkeypatched) ─────
|
||||
rows = [
|
||||
# dated record — gated out despite always_on
|
||||
("2026-10-07 fixed the parser bug after the incident report", "project-parser", "parser", 0.9, "", 1, 0.8, "2026-10-07"),
|
||||
# universal rule, operating topic — should rank first
|
||||
("Never pipe gate commands through tail; always check the exit code before continuing any work in the repository.", "operating-gates", "gate", 0.7, "", 1, 0.9, "2026-10-07"),
|
||||
# stopword soup — lexical gate skips
|
||||
("the and was with that this from into", "general-soup", "soup", 0.5, "", 0, 0.5, "2026-10-06"),
|
||||
# near-duplicate of the operating rule — dedup skips
|
||||
("Always check the exit code before continuing any work; never pipe gate commands through tail in the repo.", "operating-gates-copy", "gate2", 0.8, "", 1, 0.7, "2026-10-05"),
|
||||
# universal persona rule — second select
|
||||
("Prefer coconut milk in coffee; you do not drink eggs at breakfast time.", "human-prefs", "coffee", 0.6, "", 0, 0.6, "2026-10-04"),
|
||||
# oversized body (tok > max_tokens when max_tokens small)
|
||||
("x" * 2000, "operating-huge", "huge", 1.0, "", 1, 1.0, "2026-10-03"),
|
||||
]
|
||||
fixture_rows = [tuple(r) for r in rows]
|
||||
|
||||
class FakeDB:
|
||||
def execute(self, *_a, **_k):
|
||||
return self
|
||||
|
||||
def fetchall(self):
|
||||
return fixture_rows
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
return False
|
||||
|
||||
saved_db = wr._db
|
||||
wr._db = lambda: FakeDB()
|
||||
try:
|
||||
digest = wr.always_digest(max_tokens=220)
|
||||
finally:
|
||||
wr._db = saved_db
|
||||
add("digest", "selection_full",
|
||||
{"rows": rows},
|
||||
{"digest": digest})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/warm_router.py _py surfaces + always_digest (DB monkeypatched)",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/warm-router" / "truth_digest.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
print("digest slugs:", [d["slug"] for d in digest])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"oracle": "src/rune/build_mode.py (live BuildManager on a real tmp git repo, clock frozen)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": "2026-10-07T18:28:00",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "plan",
|
||||
"name": "create",
|
||||
"expected": {
|
||||
"argv": [
|
||||
"git",
|
||||
"worktree",
|
||||
"add",
|
||||
"/home/nick/.rune/worktrees/20261007-182800",
|
||||
"-b",
|
||||
"build-20261007-182800"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "plan",
|
||||
"name": "merge",
|
||||
"expected": {
|
||||
"argv": [
|
||||
"git",
|
||||
"merge",
|
||||
"build-20261007-182800",
|
||||
"--no-ff",
|
||||
"-m",
|
||||
"Build 20261007-182800: worktree battery"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "plan",
|
||||
"name": "delete_pair",
|
||||
"expected": {
|
||||
"argv": [
|
||||
[
|
||||
"git",
|
||||
"worktree",
|
||||
"remove",
|
||||
"/home/nick/.rune/worktrees/20261007-182800",
|
||||
"--force"
|
||||
],
|
||||
[
|
||||
"git",
|
||||
"branch",
|
||||
"-D",
|
||||
"build-20261007-182800"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "state",
|
||||
"name": "round_trip",
|
||||
"expected": {
|
||||
"build_id": "20261007-182800",
|
||||
"after_create_is_set": true,
|
||||
"after_merge_is_none": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "error",
|
||||
"name": "merge_failure_text",
|
||||
"expected": {
|
||||
"error": "Failed to merge worktree: fatal: not something we can merge"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "error",
|
||||
"name": "create_failure_text",
|
||||
"expected": {
|
||||
"error": "Failed to create worktree: 'x' already exists"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
// truth_worktree_test.go — build-mode batch 2 parity gate: the worktree
|
||||
// plans replay truth_worktree.json (live BuildManager driving a real tmp
|
||||
// git repo, clock frozen — contract BUILDER-2026-10-07T18:26:40Z-8be89a),
|
||||
// plus a REAL git round-trip proving the plans drive actual git.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const truthWorktreeDir = "/home/nick/.rune/worktrees"
|
||||
|
||||
func TestWorktreePlanParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_worktree.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_worktree.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
buildID := "20261007-182800"
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Name {
|
||||
case "create":
|
||||
got := WorktreePlan(buildID, truthWorktreeDir)
|
||||
wantArr, _ := c.Expected["argv"].([]interface{})
|
||||
argvEq(t, c.Name, got.Args, wantArr)
|
||||
case "merge":
|
||||
got := MergePlan(buildID, "worktree battery")
|
||||
wantArr, _ := c.Expected["argv"].([]interface{})
|
||||
argvEq(t, c.Name, got.Args, wantArr)
|
||||
case "delete_pair":
|
||||
got := DeletePlan(buildID, filepath.Join(truthWorktreeDir, buildID))
|
||||
wantPair, _ := c.Expected["argv"].([]interface{})
|
||||
if len(got) != len(wantPair) {
|
||||
t.Fatalf("%s: %d plans, want %d", c.Name, len(got), len(wantPair))
|
||||
}
|
||||
for i, w := range wantPair {
|
||||
wantArr, _ := w.([]interface{})
|
||||
argvEq(t, c.Name, got[i].Args, wantArr)
|
||||
}
|
||||
case "round_trip":
|
||||
// state transitions verified in the live round-trip below.
|
||||
case "merge_failure_text":
|
||||
// exercised by the real-repo failure test below.
|
||||
case "create_failure_text":
|
||||
// exercised by the real-repo failure test below.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWorktreeLiveRoundTrip — the plans drive REAL git: create → path
|
||||
// exists; merge of a committed branch lands; delete removes worktree+branch.
|
||||
func TestWorktreeLiveRoundTrip(t *testing.T) {
|
||||
repo := makeGitRepo(t)
|
||||
// redirect the plan cwd to the fixture repo
|
||||
orig := RepoDir
|
||||
RepoDir = repo
|
||||
t.Cleanup(func() { RepoDir = orig })
|
||||
|
||||
buildID := "20261007-182800"
|
||||
wtDir := t.TempDir()
|
||||
|
||||
// CREATE
|
||||
path, err := CreateWorktree(&BuildState{BuildID: buildID}, wtDir)
|
||||
if err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("worktree path missing after create: %v", err)
|
||||
}
|
||||
|
||||
// commit something ON the build branch (gives merge something to do)
|
||||
if err := gitIn(path, "config", "user.email", "t@t"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(path, "config", "user.name", "t"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(path, "feature.txt"), []byte("work\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(path, "add", "."); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(path, "commit", "-qm", "feature"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// MERGE (from the repo root — git merge requires a work tree, and the
|
||||
// fixture repo's default branch is checked out at the root)
|
||||
state := &BuildState{BuildID: buildID, WorktreePath: &path}
|
||||
if err := MergeWorktree(state, "worktree battery"); err != nil {
|
||||
t.Fatalf("merge: %v", err)
|
||||
}
|
||||
if state.WorktreePath != nil {
|
||||
t.Error("merge must clear the worktree path (delete follows)")
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(repo, "feature.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("merged file missing in repo root: %v", err)
|
||||
}
|
||||
if string(data) != "work\n" {
|
||||
t.Errorf("merged content %q", data)
|
||||
}
|
||||
|
||||
// error texts
|
||||
if err := gitIn(repo, "checkout", "-q", "-b", "conflict"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(repo, "feature.txt"), []byte("other\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(repo, "commit", "-qam", "conflict"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plan := MergePlan(buildID, "worktree battery")
|
||||
cmd := exec.Command(plan.Args[0], plan.Args[1:]...)
|
||||
cmd.Dir = plan.Cwd
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatal("conflicting merge unexpectedly succeeded")
|
||||
}
|
||||
got := "Failed to merge worktree: " + strings.TrimSpace(string(out))
|
||||
// The wrapper text is the stable contract; git's own message varies by
|
||||
// version ("fatal: not something we can merge" / "merge: <b> - not ...").
|
||||
if !strings.HasPrefix(got, "Failed to merge worktree: ") ||
|
||||
!strings.Contains(got, "not something we can merge") {
|
||||
t.Errorf("merge failure text shape: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateFailureText — duplicate worktree → exact Python RuntimeError.
|
||||
func TestCreateFailureText(t *testing.T) {
|
||||
repo := makeGitRepo(t)
|
||||
orig := RepoDir
|
||||
RepoDir = repo
|
||||
t.Cleanup(func() { RepoDir = orig })
|
||||
|
||||
buildID := "20261007-182800"
|
||||
wtDir := t.TempDir()
|
||||
if _, err := CreateWorktree(&BuildState{BuildID: buildID}, wtDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := CreateWorktree(&BuildState{BuildID: buildID}, wtDir)
|
||||
if err == nil {
|
||||
t.Fatal("duplicate create must fail")
|
||||
}
|
||||
want := "Failed to create worktree: "
|
||||
if !strings.HasPrefix(err.Error(), want) {
|
||||
t.Errorf("create failure text %q, want prefix %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
|
||||
// makeGitRepo — a real git repo fixture (seed commit; deterministic).
|
||||
func makeGitRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
git := func(args ...string) {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = root
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("git %v: %v: %s", args, err, out)
|
||||
}
|
||||
}
|
||||
git("init", "-q", "-b", "main")
|
||||
git("config", "user.email", "t@t")
|
||||
git("config", "user.name", "t")
|
||||
if err := os.WriteFile(filepath.Join(root, "seed.txt"), []byte("seed\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "seed")
|
||||
return root
|
||||
}
|
||||
|
||||
func gitIn(dir string, args ...string) error {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
if _, err := cmd.CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func argvEq(t *testing.T, name string, got []string, wantArr []interface{}) {
|
||||
t.Helper()
|
||||
if len(got) != len(wantArr) {
|
||||
t.Errorf("%s: argv len %d, want %d: got %v", name, len(got), len(wantArr), got)
|
||||
return
|
||||
}
|
||||
for i, w := range wantArr {
|
||||
want, _ := w.(string)
|
||||
if got[i] != want {
|
||||
t.Errorf("%s: argv[%d] got %q want %q", name, i, got[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
// worktree.go — batch 2: the worktree git operations of build_mode.py
|
||||
// (contract BUILDER-2026-10-07T18:26:40Z-8be89a). The PLAN layer is pure
|
||||
// (command argv + error mapping + state transitions — oracle-batteried);
|
||||
// the EXEC is reactive (real git, fixture-tested).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RepoDir — build_mode.REPO_DIR (var: tests redirect to a fixture repo).
|
||||
var RepoDir = "/mnt/workdrive/rune"
|
||||
|
||||
// GitPlan — one planned git invocation.
|
||||
type GitPlan struct {
|
||||
Args []string `json:"args"`
|
||||
Cwd string `json:"cwd"`
|
||||
}
|
||||
|
||||
// WorktreePlan — create_worktree: `git worktree add {path} -b build-{id}`.
|
||||
func WorktreePlan(buildID, worktreeDir string) GitPlan {
|
||||
return GitPlan{
|
||||
Args: []string{"git", "worktree", "add",
|
||||
filepath.Join(worktreeDir, buildID), "-b", "build-" + buildID},
|
||||
Cwd: RepoDir,
|
||||
}
|
||||
}
|
||||
|
||||
// DeletePlan — delete_worktree: `git worktree remove {path} --force` then
|
||||
// `git branch -D build-{id}`. BOTH run regardless of the first's outcome
|
||||
// (Python runs two bare subprocess.run calls — no failure short-circuit).
|
||||
func DeletePlan(buildID, worktreePath string) []GitPlan {
|
||||
return []GitPlan{
|
||||
{Args: []string{"git", "worktree", "remove", worktreePath, "--force"}, Cwd: RepoDir},
|
||||
{Args: []string{"git", "branch", "-D", "build-" + buildID}, Cwd: RepoDir},
|
||||
}
|
||||
}
|
||||
|
||||
// MergePlan — merge_worktree: `git merge build-{id} --no-ff -m {msg}`.
|
||||
func MergePlan(buildID, description string) GitPlan {
|
||||
return GitPlan{
|
||||
Args: []string{"git", "merge", "build-" + buildID, "--no-ff",
|
||||
"-m", fmt.Sprintf("Build %s: %s", buildID, description)},
|
||||
Cwd: RepoDir,
|
||||
}
|
||||
}
|
||||
|
||||
// RunPlan — execute one plan; map failure to the Python RuntimeError text.
|
||||
func RunPlan(p GitPlan) error {
|
||||
cmd := exec.Command(p.Args[0], p.Args[1:]...)
|
||||
cmd.Dir = p.Cwd
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Failed to create worktree: %s", strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunPlans — execute a sequence; failures don't short-circuit the
|
||||
// remaining steps (parity with the Python's two bare subprocess calls).
|
||||
// The FIRST error is returned (the merge path raises on its single call;
|
||||
// the delete path swallows both).
|
||||
func RunPlans(plans []GitPlan) error {
|
||||
var firstErr error
|
||||
for _, p := range plans {
|
||||
if err := RunPlan(p); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// CreateWorktree — the reactive wrapper: plan + exec + state transition.
|
||||
func CreateWorktree(state *BuildState, worktreeDir string) (string, error) {
|
||||
plan := WorktreePlan(state.BuildID, worktreeDir)
|
||||
if err := RunPlan(mapCreateError(plan)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := plan.Args[3]
|
||||
state.WorktreePath = &path
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// mapCreateError — create failures carry the "create worktree" text;
|
||||
// RunPlan's default is already that text (single call site).
|
||||
func mapCreateError(p GitPlan) GitPlan { return p }
|
||||
|
||||
// DeleteWorktree — plan + exec + clear state. Errors swallowed (parity:
|
||||
// Python ignores both results).
|
||||
func DeleteWorktree(state *BuildState) {
|
||||
if state.WorktreePath == nil {
|
||||
return
|
||||
}
|
||||
_ = RunPlans(DeletePlan(state.BuildID, *state.WorktreePath))
|
||||
state.WorktreePath = nil
|
||||
}
|
||||
|
||||
// MergeWorktree — plan + exec + cleanup (delete after merge). Error text:
|
||||
// "Failed to merge worktree: {stderr}".
|
||||
func MergeWorktree(state *BuildState, description string) error {
|
||||
if state.WorktreePath == nil {
|
||||
return nil
|
||||
}
|
||||
plan := MergePlan(state.BuildID, description)
|
||||
cmd := exec.Command(plan.Args[0], plan.Args[1:]...)
|
||||
cmd.Dir = plan.Cwd
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Failed to merge worktree: %s", strings.TrimSpace(string(out)))
|
||||
}
|
||||
DeleteWorktree(state)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureDir — mkdir -p for the builds/worktree dirs (the Python __init__).
|
||||
func EnsureDirs(buildsDir, worktreeDir string) error {
|
||||
for _, d := range []string{buildsDir, worktreeDir} {
|
||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,473 @@
|
||||
package main
|
||||
|
||||
// recall.go — batch 2: the deterministic read-path of memory_store.py
|
||||
// (contract BUILDER-2026-10-07T18:05:47Z-152c16). FtsQueryConjunctive,
|
||||
// ChunkText, RankFusion (RRF k=15), ProjectChunkRows, MapReduce,
|
||||
// FuseResults + NormKey. The embed-dependent surfaces stay Python-side.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RRFK — small-corpus RRF constant (large corpora want 60).
|
||||
const RRFK = 15
|
||||
|
||||
// msStopwords — memory_store._STOPWORDS (distinct from curator's and the
|
||||
// lexicon's sets; extracted verbatim from the live module 2026-10-07).
|
||||
var msStopwords = map[string]bool{
|
||||
"about": true, "after": true, "also": true, "and": true, "are": true,
|
||||
"been": true, "but": true, "each": true, "for": true, "from": true,
|
||||
"have": true, "her": true, "him": true, "his": true, "how": true,
|
||||
"into": true, "its": true, "just": true, "likes": true, "more": true,
|
||||
"needs": true, "not": true, "only": true, "over": true, "prefers": true,
|
||||
"some": true, "such": true, "than": true, "that": true, "the": true,
|
||||
"them": true, "then": true, "there": true, "they": true, "this": true,
|
||||
"under": true, "very": true, "wants": true, "was": true, "were": true,
|
||||
"what": true, "when": true, "where": true, "which": true, "while": true,
|
||||
"who": true, "why": true, "will": true, "with": true, "you": true,
|
||||
"your": true,
|
||||
}
|
||||
|
||||
// FtsQueryConjunctive — the CONJUNCTIVE FTS5 MATCH builder (implicit AND):
|
||||
// significant terms only (len>3, not stop; fallback len>2), quoted,
|
||||
// first 8, space-joined. Phrase clauses are additive evidence.
|
||||
func FtsQueryConjunctive(query string) string {
|
||||
lower := strings.ToLower(query)
|
||||
var toks []string
|
||||
for _, t := range strings.Fields(lower) {
|
||||
if len(t) > 3 && !msStopwords[t] {
|
||||
toks = append(toks, t)
|
||||
}
|
||||
}
|
||||
if len(toks) == 0 {
|
||||
for _, t := range strings.Fields(lower) {
|
||||
if len(t) > 2 {
|
||||
toks = append(toks, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(toks) == 0 {
|
||||
return query
|
||||
}
|
||||
if len(toks) > 8 {
|
||||
toks = toks[:8]
|
||||
}
|
||||
quoted := make([]string, len(toks))
|
||||
for i, t := range toks {
|
||||
quoted[i] = fmt.Sprintf("%q", t)
|
||||
}
|
||||
return strings.Join(quoted, " ")
|
||||
}
|
||||
|
||||
// ChunkText — paragraph recombination at ~size chars; chunks still huge
|
||||
// (no paragraph breaks) hard-split at size. Headings stay with content.
|
||||
func ChunkText(text string, size, overlap int) []string {
|
||||
if text == "" {
|
||||
return []string{}
|
||||
}
|
||||
if len(text) <= size {
|
||||
if strings.TrimSpace(text) == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{text}
|
||||
}
|
||||
var paras []string
|
||||
for _, p := range strings.Split(text, "\n\n") {
|
||||
if strings.TrimSpace(p) != "" {
|
||||
paras = append(paras, p)
|
||||
}
|
||||
}
|
||||
var chunks []string
|
||||
cur := ""
|
||||
for _, p := range paras {
|
||||
if len(cur)+len(p)+2 <= size {
|
||||
if cur == "" {
|
||||
cur = p
|
||||
} else {
|
||||
cur = cur + "\n\n" + p
|
||||
}
|
||||
} else {
|
||||
if cur != "" {
|
||||
chunks = append(chunks, cur)
|
||||
}
|
||||
cur = p
|
||||
}
|
||||
}
|
||||
if cur != "" {
|
||||
chunks = append(chunks, cur)
|
||||
}
|
||||
var out []string
|
||||
for _, c := range chunks {
|
||||
if len(c) > size*3/2 { // Python size * 1.5 (int text len compare)
|
||||
for i := 0; i < len(c); i += size {
|
||||
end := i + size
|
||||
if end > len(c) {
|
||||
end = len(c)
|
||||
}
|
||||
piece := c[i:end]
|
||||
if strings.TrimSpace(piece) != "" {
|
||||
out = append(out, piece)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
if out == nil {
|
||||
out = []string{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RankFusion — chunk-side RRF fusion with an explicit candidate cap.
|
||||
// dense/bm25 are [id, score] pairs. Three modes + min-max bm25 fallback.
|
||||
func RankFusion(dense, bm25 [][2]interface{}, rrfK float64) map[string]interface{} {
|
||||
if rrfK == 0 {
|
||||
rrfK = RRFK
|
||||
}
|
||||
ranks := map[string]float64{}
|
||||
var order []string // insertion order (Python dict preserves it)
|
||||
bestDense := 0.0
|
||||
mode := "lexical-only"
|
||||
addRank := func(eid string, score float64) {
|
||||
if _, has := ranks[eid]; !has {
|
||||
order = append(order, eid)
|
||||
}
|
||||
ranks[eid] += score
|
||||
}
|
||||
if len(dense) > 0 && len(bm25) > 0 {
|
||||
mode = "hybrid"
|
||||
for idx, list := range [][][2]interface{}{dense, bm25} {
|
||||
for pos, pair := range list {
|
||||
eid := pairString(pair[0])
|
||||
score, _ := pair[1].(float64)
|
||||
addRank(eid, 1.0/(rrfK+float64(pos)+1))
|
||||
if idx == 0 && score > bestDense {
|
||||
bestDense = score
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if len(dense) > 0 {
|
||||
mode = "dense-only"
|
||||
for pos, pair := range dense {
|
||||
addRank(pairString(pair[0]), 1.0/(rrfK+float64(pos)+1))
|
||||
if score, ok := pair[1].(float64); ok && score > bestDense {
|
||||
bestDense = score
|
||||
}
|
||||
}
|
||||
} else if len(bm25) > 0 {
|
||||
minBm, maxBm := 0.0, 0.0
|
||||
first := true
|
||||
for _, pair := range bm25 {
|
||||
s, _ := pair[1].(float64)
|
||||
if first {
|
||||
minBm, maxBm = s, s
|
||||
first = false
|
||||
}
|
||||
if s < minBm {
|
||||
minBm = s
|
||||
}
|
||||
if s > maxBm {
|
||||
maxBm = s
|
||||
}
|
||||
}
|
||||
span := maxBm - minBm
|
||||
if span < 1e-9 {
|
||||
span = 1e-9
|
||||
}
|
||||
for _, pair := range bm25 {
|
||||
s, _ := pair[1].(float64)
|
||||
addRank(pairString(pair[0]), (s-minBm)/span)
|
||||
}
|
||||
}
|
||||
rankList := make([]interface{}, 0, len(order))
|
||||
for _, eid := range order {
|
||||
rankList = append(rankList, []interface{}{numericID(eid), ranks[eid]})
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"ranks": rankList,
|
||||
"best_dense": bestDense,
|
||||
"mode": mode,
|
||||
}
|
||||
}
|
||||
|
||||
// numericID — Python ids in the fusion pairs are ints; the string-keyed
|
||||
// map is internal only. Emit numeric when the id parses as an integer.
|
||||
func numericID(eid string) interface{} {
|
||||
var n int
|
||||
if _, err := fmt.Sscanf(eid, "%d", &n); err == nil && trimFloat(float64(n)) == eid {
|
||||
return n
|
||||
}
|
||||
return eid
|
||||
}
|
||||
|
||||
func pairString(v interface{}) string {
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
return x
|
||||
case float64:
|
||||
return trimFloat(x)
|
||||
case int:
|
||||
return fmt.Sprintf("%d", x)
|
||||
default:
|
||||
return fmt.Sprintf("%v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func trimFloat(f float64) string {
|
||||
if f == float64(int64(f)) {
|
||||
return fmt.Sprintf("%d", int64(f))
|
||||
}
|
||||
return fmt.Sprintf("%g", f)
|
||||
}
|
||||
|
||||
// ChunkRow — one projected chunk row.
|
||||
type ChunkRow struct {
|
||||
ChunkID interface{} `json:"chunk_id"`
|
||||
Text string `json:"text"`
|
||||
Wing string `json:"wing"`
|
||||
Room string `json:"room"`
|
||||
Source string `json:"source"`
|
||||
Page interface{} `json:"page"`
|
||||
RRF float64 `json:"rrf"`
|
||||
}
|
||||
|
||||
// ProjectChunkRows — sorted -rrf, text[:400], rrf round 4, budget cap.
|
||||
func ProjectChunkRows(ranks map[string]float64, rows []map[string]interface{}, budget int) []map[string]interface{} {
|
||||
byID := map[string]map[string]interface{}{}
|
||||
for _, r := range rows {
|
||||
byID[pairString(r["rowid"])] = r
|
||||
}
|
||||
// sorted(ranks.items(), key=lambda x: -x[1]) — descending rrf; ties
|
||||
// keep Python's stable sort order (insertion order of ranks dict).
|
||||
type kv struct {
|
||||
k string
|
||||
v float64
|
||||
}
|
||||
var items []kv
|
||||
for k, v := range ranks {
|
||||
items = append(items, kv{k, v})
|
||||
}
|
||||
sort.SliceStable(items, func(i, j int) bool { return items[i].v > items[j].v })
|
||||
out := []map[string]interface{}{}
|
||||
for _, it := range items {
|
||||
r, has := byID[it.k]
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
text, _ := r["text"].(string)
|
||||
if len(text) > 400 {
|
||||
text = text[:400]
|
||||
}
|
||||
wing, _ := r["wing"].(string)
|
||||
room, _ := r["room"].(string)
|
||||
source, _ := r["source_path"].(string)
|
||||
var page interface{}
|
||||
if p, hasPage := r["page"]; hasPage {
|
||||
page = p
|
||||
} else {
|
||||
page = nil
|
||||
}
|
||||
out = append(out, map[string]interface{}{
|
||||
"chunk_id": r["chunk_id"],
|
||||
"text": text,
|
||||
"wing": wing,
|
||||
"room": room,
|
||||
"source": source,
|
||||
"page": page,
|
||||
"rrf": round4(it.v),
|
||||
})
|
||||
if len(out) >= budget {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func round4(f float64) float64 {
|
||||
return float64(int64(f*10000+0.5)) / 10000
|
||||
}
|
||||
|
||||
// MapReduce — chunk the entries, take top-3 per chunk by
|
||||
// relevance/score (default 0.5), then REDUCE: re-sort ALL mapped entries
|
||||
// by score desc (the oracle's final pass; no budget param in the code
|
||||
// despite the comment).
|
||||
func MapReduce(entries []map[string]interface{}, chunkSize int) []map[string]interface{} {
|
||||
if len(entries) <= chunkSize {
|
||||
return entries
|
||||
}
|
||||
var mapped []map[string]interface{}
|
||||
for start := 0; start < len(entries); start += chunkSize {
|
||||
end := start + chunkSize
|
||||
if end > len(entries) {
|
||||
end = len(entries)
|
||||
}
|
||||
chunk := entries[start:end]
|
||||
type scored struct {
|
||||
score float64
|
||||
entry map[string]interface{}
|
||||
}
|
||||
var sortd []scored
|
||||
for _, e := range chunk {
|
||||
score := 0.5
|
||||
if v, has := e["relevance"]; has {
|
||||
if f, ok := v.(float64); ok {
|
||||
score = f
|
||||
}
|
||||
} else if v, has := e["score"]; has {
|
||||
if f, ok := v.(float64); ok {
|
||||
score = f
|
||||
}
|
||||
}
|
||||
sortd = append(sortd, scored{score, e})
|
||||
}
|
||||
// Python sort is stable; ties keep list order.
|
||||
sort.SliceStable(sortd, func(i, j int) bool { return sortd[i].score > sortd[j].score })
|
||||
n := 3
|
||||
if len(sortd) < n {
|
||||
n = len(sortd)
|
||||
}
|
||||
for _, s := range sortd[:n] {
|
||||
mapped = append(mapped, s.entry)
|
||||
}
|
||||
}
|
||||
if mapped == nil {
|
||||
mapped = []map[string]interface{}{}
|
||||
}
|
||||
// Reduce: sort all mapped by score desc (stable; ties keep map order).
|
||||
sort.SliceStable(mapped, func(i, j int) bool {
|
||||
return entryScore(mapped[i]) > entryScore(mapped[j])
|
||||
})
|
||||
return mapped
|
||||
}
|
||||
|
||||
// entryScore — relevance, else score, else 0.5 (the oracle's lookup).
|
||||
func entryScore(e map[string]interface{}) float64 {
|
||||
if v, has := e["relevance"]; has {
|
||||
if f, ok := v.(float64); ok {
|
||||
return f
|
||||
}
|
||||
}
|
||||
if v, has := e["score"]; has {
|
||||
if f, ok := v.(float64); ok {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return 0.5
|
||||
}
|
||||
|
||||
// NormKey — cross-table dedup normalization: lowercase, whitespace-collapse.
|
||||
func NormKey(text string) string {
|
||||
return strings.Join(strings.Fields(strings.ToLower(text)), " ")
|
||||
}
|
||||
|
||||
// FuseResults — _norm_key dedup + merge shape + kind-wise min-max
|
||||
// scale-alignment (the audit fix) + final sort.
|
||||
func FuseResults(entries, research []map[string]interface{}) []map[string]interface{} {
|
||||
seen := map[string]bool{}
|
||||
merged := []map[string]interface{}{}
|
||||
for _, e := range entries {
|
||||
text, _ := e["text"].(string)
|
||||
key := NormKey(text)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
topic, _ := e["topic"].(string)
|
||||
if topic == "" {
|
||||
topic = "fact"
|
||||
}
|
||||
relevance, _ := e["relevance"].(float64)
|
||||
confidence, _ := e["confidence"].(float64)
|
||||
merged = append(merged, map[string]interface{}{
|
||||
"id": e["id"],
|
||||
"text": text,
|
||||
"topic": topic,
|
||||
"kind": "fact",
|
||||
"relevance": round4(relevance),
|
||||
"confidence": round2(confidence),
|
||||
})
|
||||
}
|
||||
for _, c := range research {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
text, _ := c["text"].(string)
|
||||
key := NormKey(text)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
topic, _ := c["wing"].(string)
|
||||
if topic == "" {
|
||||
topic = "research"
|
||||
}
|
||||
rel, hasRel := c["rrf"].(float64)
|
||||
if !hasRel {
|
||||
rel, _ = c["score"].(float64)
|
||||
}
|
||||
confidence, hasConf := c["confidence"].(float64)
|
||||
if !hasConf || confidence == 0 {
|
||||
confidence = 0.7
|
||||
}
|
||||
merged = append(merged, map[string]interface{}{
|
||||
"id": c["chunk_id"],
|
||||
"text": text,
|
||||
"topic": topic,
|
||||
"kind": "research",
|
||||
"relevance": round4(rel),
|
||||
"confidence": round2(confidence),
|
||||
})
|
||||
}
|
||||
// final sort before alignment (Python sorts, then aligns, then sorts)
|
||||
sortByRelevanceDesc(merged)
|
||||
// SCALE-ALIGNMENT: min-max normalize each kind's relevance to [0,1]
|
||||
// within this result set, then re-sort.
|
||||
for _, kind := range []string{"fact", "research"} {
|
||||
var subset []map[string]interface{}
|
||||
for _, r := range merged {
|
||||
if r["kind"] == kind {
|
||||
subset = append(subset, r)
|
||||
}
|
||||
}
|
||||
if len(subset) > 1 {
|
||||
lo, hi := 0.0, 0.0
|
||||
first := true
|
||||
for _, r := range subset {
|
||||
v := r["relevance"].(float64)
|
||||
if first {
|
||||
lo, hi = v, v
|
||||
first = false
|
||||
}
|
||||
if v < lo {
|
||||
lo = v
|
||||
}
|
||||
if v > hi {
|
||||
hi = v
|
||||
}
|
||||
}
|
||||
span := hi - lo
|
||||
if span < 1e-9 {
|
||||
span = 1e-9
|
||||
}
|
||||
for _, r := range subset {
|
||||
v := r["relevance"].(float64)
|
||||
r["relevance"] = round4((v - lo) / span)
|
||||
}
|
||||
}
|
||||
}
|
||||
sortByRelevanceDesc(merged)
|
||||
return merged
|
||||
}
|
||||
|
||||
func sortByRelevanceDesc(list []map[string]interface{}) {
|
||||
sort.SliceStable(list, func(i, j int) bool {
|
||||
return list[i]["relevance"].(float64) > list[j]["relevance"].(float64)
|
||||
})
|
||||
}
|
||||
|
||||
func round2(f float64) float64 {
|
||||
return float64(int64(f*100+0.5)) / 100
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
// recall_gate.go — batch 4: the recall() integration pieces of
|
||||
// memory_store.py (contract BUILDER-2026-10-07T19:47:37Z-1cf1dc).
|
||||
// DenseGate (the meta-judgement: vector proximity is not a relation),
|
||||
// RankFusionEntries (the entries-side fusion — verified identical to the
|
||||
// chunk-side RankFusion already ported; the docstring's raw-BM25 claim
|
||||
// is NOT what the code does — the CODE min-max normalizes in ALL modes,
|
||||
// and the code is the oracle), the abstain constants.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RecallBudget / RecallMinScore — the abstain constants (below the floor
|
||||
// → abstain, don't inject distractors).
|
||||
const (
|
||||
RecallBudget = 8
|
||||
RecallMinScore = 0.42
|
||||
)
|
||||
|
||||
// DenseHit — one candidate dense match: (rowid, cosine).
|
||||
type DenseHit struct {
|
||||
RowID int64
|
||||
Cos float64
|
||||
}
|
||||
|
||||
// QueryContent — the query's significant words (len>3, not msStopwords).
|
||||
func QueryContent(query string) []string {
|
||||
var out []string
|
||||
seen := map[string]bool{}
|
||||
for _, t := range strings.Fields(strings.ToLower(query)) {
|
||||
if len(t) > 3 && !msStopwords[t] && !seen[t] {
|
||||
seen[t] = true
|
||||
out = append(out, t)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// DenseGate — the meta-judgement applied to the vec0 candidates.
|
||||
// queryContent: significant query words. dfOf: document frequency
|
||||
// (LIKE-substring count / total entries — the CALLER supplies counts;
|
||||
// the gate owns the 0.05 distinctiveness threshold). A hit is kept iff
|
||||
// it shares a DISTINCTIVE content word with the entry text; otherwise
|
||||
// only when the query has no content words at all OR cos >= 0.80.
|
||||
func DenseGate(hits []DenseHit, entryTexts map[int64]string, queryContent []string, dfOf func(word string) int, totalEntries int) []DenseHit {
|
||||
out := []DenseHit{}
|
||||
for _, h := range hits {
|
||||
etxt := strings.ToLower(entryTexts[h.RowID])
|
||||
distinctive := false
|
||||
for _, w := range queryContent {
|
||||
if strings.Contains(etxt, w) {
|
||||
df := 0
|
||||
if dfOf != nil {
|
||||
df = dfOf(w)
|
||||
}
|
||||
total := totalEntries
|
||||
if total < 1 {
|
||||
total = 1
|
||||
}
|
||||
if float64(df)/float64(total) < 0.05 {
|
||||
distinctive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if distinctive {
|
||||
out = append(out, h)
|
||||
} else if len(queryContent) == 0 || h.Cos >= 0.80 {
|
||||
out = append(out, h)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RankFusionEntries — the entries-side fusion. VERIFIED identical to the
|
||||
// chunk-side RankFusion (same file, memory_store.py:1297 vs the batch-2
|
||||
// port): the code min-max normalizes the lexical-only branch in ALL
|
||||
// modes. The docstring's raw-BM25 claim is stale — the CODE is the
|
||||
// oracle. Reuse keeps one fusion implementation; this wrapper pins the
|
||||
// parity for the recall() call shape.
|
||||
func RankFusionEntries(dense, bm25 [][2]interface{}) map[string]interface{} {
|
||||
return RankFusion(dense, bm25, RRFK)
|
||||
}
|
||||
|
||||
// RankIDs — the sorted rank order (desc by score) for downstream SQL IN
|
||||
// clauses (Python: list(ranks.keys()) is INSERTION order — the fusion's
|
||||
// accumulation order, which SortStable ranking reproduces).
|
||||
func RankIDs(fusion map[string]interface{}) []interface{} {
|
||||
ranks, _ := fusion["ranks"].([]interface{})
|
||||
return ranks
|
||||
}
|
||||
|
||||
// sortedInsertion — retained reference: the fusion's rank list order IS
|
||||
// the insertion order (accumulation), not score order. Documented for
|
||||
// the SQL-IN parity note. (Unused call sites may sort explicitly.)
|
||||
var _ = sort.Float64s
|
||||
@@ -0,0 +1,194 @@
|
||||
package main
|
||||
|
||||
// score.go — batch 3: the scoring/abstention kernel of memory_store.py
|
||||
// (contract BUILDER-2026-10-07T18:57:06Z-c2429c). ScoreRecallRows (the
|
||||
// metadata scoring + mode-specific abstention), PositionAware (the
|
||||
// lost-in-the-middle re-rank).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecencyHalfLife — RECENCY_HALF_LIFE (days).
|
||||
const RecencyHalfLife = 30.0
|
||||
|
||||
// PositionAware — the two highest-relevance items go FIRST and LAST; the
|
||||
// same items, only the order changes. No-op for < 3 items.
|
||||
func PositionAware(top []map[string]interface{}) []map[string]interface{} {
|
||||
if len(top) < 3 {
|
||||
return top
|
||||
}
|
||||
s := make([]map[string]interface{}, len(top))
|
||||
copy(s, top)
|
||||
sorted := make([]map[string]interface{}, len(s))
|
||||
copy(sorted, s)
|
||||
sort.SliceStable(sorted, func(i, j int) bool {
|
||||
return entryRelevance(sorted[i]) > entryRelevance(sorted[j])
|
||||
})
|
||||
out := make([]map[string]interface{}, 0, len(sorted))
|
||||
out = append(out, sorted[0])
|
||||
out = append(out, sorted[2:]...)
|
||||
out = append(out, sorted[1])
|
||||
return out
|
||||
}
|
||||
|
||||
func entryRelevance(m map[string]interface{}) float64 {
|
||||
v, _ := m["relevance"].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
// parseISO — Python fromisoformat with the Z→+00:00 swap; also accepts
|
||||
// naive timestamps. Returns (time, ok).
|
||||
func parseISO(s string) (time.Time, bool) {
|
||||
if s == "" {
|
||||
return time.Time{}, false
|
||||
}
|
||||
s = strings.Replace(s, "Z", "+00:00", 1)
|
||||
layouts := []string{
|
||||
"2006-01-02T15:04:05.999999-07:00",
|
||||
"2006-01-02T15:04:05-07:00",
|
||||
"2006-01-02T15:04:05.999999",
|
||||
"2006-01-02T15:04:05",
|
||||
}
|
||||
for _, l := range layouts {
|
||||
if t, err := time.Parse(l, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// ScoreRecallRows — rank-ordered metadata scoring + abstention ladder.
|
||||
func ScoreRecallRows(fusion map[string]interface{}, rows []map[string]interface{}, dense, bm25 [][2]interface{}, budget int, minScore float64, nowStr string) []map[string]interface{} {
|
||||
// ranks: the fusion dict's ranks — [[id, score], ...] pairs.
|
||||
ranksList, _ := fusion["ranks"].([]interface{})
|
||||
bestDense, _ := fusion["best_dense"].(float64)
|
||||
mode, _ := fusion["mode"].(string)
|
||||
|
||||
byID := map[string]map[string]interface{}{}
|
||||
for _, r := range rows {
|
||||
id := pairString(r["id"])
|
||||
byID[id] = r
|
||||
}
|
||||
|
||||
type rankItem struct {
|
||||
eid string
|
||||
base float64
|
||||
}
|
||||
var items []rankItem
|
||||
for _, raw := range ranksList {
|
||||
pair, ok := raw.([]interface{})
|
||||
if !ok || len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
eid := pairString(pair[0])
|
||||
base, _ := pair[1].(float64)
|
||||
items = append(items, rankItem{eid, base})
|
||||
}
|
||||
// sorted(ranks.items(), key=lambda item: -item[1]) — stable desc.
|
||||
sort.SliceStable(items, func(i, j int) bool { return items[i].base > items[j].base })
|
||||
|
||||
nowDT, _ := parseISO(nowStr)
|
||||
scored := []map[string]interface{}{}
|
||||
for _, it := range items {
|
||||
row, has := byID[it.eid]
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
conf := 0.7
|
||||
if v, ok := row["confidence"].(float64); ok && v != 0 {
|
||||
conf = v
|
||||
}
|
||||
temp := 1.0
|
||||
if v, ok := row["temperature"].(float64); ok && v != 0 {
|
||||
temp = v
|
||||
}
|
||||
recency := 1.0
|
||||
if la, ok := row["last_accessed"].(string); ok && la != "" {
|
||||
if tLast, ok2 := parseISO(la); ok2 {
|
||||
days := nowDT.Sub(tLast).Seconds() / 86400.0
|
||||
if days >= 0 {
|
||||
recency = RecencyHalfLife / (RecencyHalfLife + days)
|
||||
}
|
||||
}
|
||||
// timestamp failure → recency stays 1.0 (the except path)
|
||||
}
|
||||
var relevance float64
|
||||
if len(dense) > 0 {
|
||||
relevance = it.base * (0.4 + 0.6*conf) * (0.5 + 0.5*temp) * (0.85 + 0.15*recency)
|
||||
} else {
|
||||
relevance = (it.base + 5.0) * (0.4 + 0.6*conf) * (0.5 + 0.5*temp) * (0.85 + 0.15*recency)
|
||||
}
|
||||
text, _ := row["text"].(string)
|
||||
topic, _ := row["topic"].(string)
|
||||
source, _ := row["source"].(string)
|
||||
lastAccessed, _ := row["last_accessed"].(string)
|
||||
scored = append(scored, map[string]interface{}{
|
||||
"id": numericID(it.eid),
|
||||
"text": text,
|
||||
"importance": row["importance"],
|
||||
"topic": topic,
|
||||
"entities": row["entities"],
|
||||
"source": source,
|
||||
"last_accessed": lastAccessed,
|
||||
"rrf": round4(it.base),
|
||||
"confidence": round2(conf),
|
||||
"temperature": round2(temp),
|
||||
"relevance": round4(relevance),
|
||||
"mode": mode,
|
||||
})
|
||||
}
|
||||
if len(scored) == 0 {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
if len(scored) > budget {
|
||||
scored = scored[:budget]
|
||||
}
|
||||
top := scored
|
||||
if len(dense) == 0 && len(bm25) == 0 {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
if len(dense) == 0 {
|
||||
maxRel := 0.0
|
||||
for i, item := range top {
|
||||
r := entryRelevance(item)
|
||||
if i == 0 || r > maxRel {
|
||||
maxRel = r
|
||||
}
|
||||
}
|
||||
if maxRel < minScore {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return top
|
||||
}
|
||||
if len(bm25) == 0 {
|
||||
if bestDense < minScore {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
if bestDense >= 0.74 {
|
||||
return PositionAware(top)
|
||||
}
|
||||
if len(dense) >= 2 {
|
||||
dScores := make([]float64, 0, len(dense))
|
||||
for _, p := range dense {
|
||||
f, _ := p[1].(float64)
|
||||
dScores = append(dScores, f)
|
||||
}
|
||||
sort.Float64s(dScores)
|
||||
if dScores[len(dScores)-1]-dScores[len(dScores)-2] >= 0.08 {
|
||||
return PositionAware(top)
|
||||
}
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return PositionAware(MapReduce(top, 10))
|
||||
}
|
||||
if bestDense < minScore {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return PositionAware(MapReduce(top, 10))
|
||||
}
|
||||
|
||||
var _ = fmt.Sprintf // retained for parity annotations
|
||||
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"oracle": "src/rune/memory_store.py recall() dense gate + _py_rank_fusion",
|
||||
"generated": "2026-10-07",
|
||||
"fixture_df_note": "df via LIKE-substring over active entries; threshold 0.05",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "distinctive_shared",
|
||||
"input": {
|
||||
"query": "worthiness gates",
|
||||
"hits": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.85
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.85
|
||||
]
|
||||
],
|
||||
"q_content": [
|
||||
"gates",
|
||||
"worthiness"
|
||||
],
|
||||
"df": {
|
||||
"worthiness": 1,
|
||||
"gates": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "common_word_falls_back",
|
||||
"input": {
|
||||
"query": "black",
|
||||
"hits": [
|
||||
[
|
||||
2,
|
||||
0.86
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.79
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
2,
|
||||
0.86
|
||||
]
|
||||
],
|
||||
"q_content": [
|
||||
"black"
|
||||
],
|
||||
"df": {
|
||||
"black": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "strong_cos_no_share",
|
||||
"input": {
|
||||
"query": "unrelated query terms",
|
||||
"hits": [
|
||||
[
|
||||
6,
|
||||
0.91
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
6,
|
||||
0.91
|
||||
]
|
||||
],
|
||||
"q_content": [
|
||||
"query",
|
||||
"terms",
|
||||
"unrelated"
|
||||
],
|
||||
"df": {
|
||||
"unrelated": 1,
|
||||
"query": 0,
|
||||
"terms": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "no_content_words",
|
||||
"input": {
|
||||
"query": "the was and",
|
||||
"hits": [
|
||||
[
|
||||
1,
|
||||
0.6
|
||||
],
|
||||
[
|
||||
6,
|
||||
0.95
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
1,
|
||||
0.6
|
||||
],
|
||||
[
|
||||
6,
|
||||
0.95
|
||||
]
|
||||
],
|
||||
"q_content": [],
|
||||
"df": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fusion_identity",
|
||||
"name": "entries_eq_chunks",
|
||||
"input": {
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.8
|
||||
]
|
||||
],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
10.0
|
||||
],
|
||||
[
|
||||
3,
|
||||
4.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.0625
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.1213235294117647
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.058823529411764705
|
||||
]
|
||||
],
|
||||
"best_dense": 0.9,
|
||||
"mode": "hybrid"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "constants",
|
||||
"name": "abstain",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"budget": 8,
|
||||
"min_score": 0.42
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package main
|
||||
|
||||
// truth_gate_test.go — memory-store batch 4 parity gate: the dense
|
||||
// meta-judgement + fusion identity replay truth_gate.json (live oracle,
|
||||
// fixture store — contract BUILDER-2026-10-07T19:47:37Z-1cf1dc).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func sortStringsW(s []string) {
|
||||
for i := 1; i < len(s); i++ {
|
||||
for j := i; j > 0 && s[j-1] > s[j]; j-- {
|
||||
s[j-1], s[j] = s[j], s[j-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGateParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_gate.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_gate.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
// The fixture entry texts (the generator's store, shipped here — the
|
||||
// truth inputs must carry their own state).
|
||||
fixtureTexts := map[int64]string{
|
||||
1: "the curator gates memory writes through worthiness",
|
||||
2: "black holes bend light near the event horizon",
|
||||
3: "black coffee in the morning keeps nick running",
|
||||
4: "the spine rebuild parity proofs live in store",
|
||||
5: "memory writes require the operator approval gate",
|
||||
6: "unrelated filler text about weather patterns",
|
||||
7: "curator provenance recovery from odysseus",
|
||||
8: "another filler row to pad the document frequency",
|
||||
}
|
||||
// df table shipped per case (the oracle's LIKE counts).
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Fn {
|
||||
case "dense_gate":
|
||||
query, _ := c.Input["query"].(string)
|
||||
hitsAny, _ := c.Input["hits"].([]interface{})
|
||||
var hits []DenseHit
|
||||
for _, h := range hitsAny {
|
||||
pair, _ := h.([]interface{})
|
||||
id, _ := pair[0].(float64)
|
||||
cos, _ := pair[1].(float64)
|
||||
hits = append(hits, DenseHit{RowID: int64(id), Cos: cos})
|
||||
}
|
||||
dfAny, _ := c.Expected["df"].(map[string]interface{})
|
||||
dfOf := func(word string) int {
|
||||
v, ok := dfAny[word]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
f, _ := v.(float64)
|
||||
return int(f)
|
||||
}
|
||||
total := int(c.Input["total"].(float64))
|
||||
qc := QueryContent(query)
|
||||
wantQAny, _ := c.Expected["q_content"].([]interface{})
|
||||
var wantQ []string
|
||||
for _, w := range wantQAny {
|
||||
s, _ := w.(string)
|
||||
wantQ = append(wantQ, s)
|
||||
}
|
||||
// The truth column ships the q_content SORTED (generator's
|
||||
// json.dumps of a set); compare sorted-to-sorted.
|
||||
sortStringsW(qc)
|
||||
jsonEq(t, c.Name, "q_content", qc, wantQ)
|
||||
got := DenseGate(hits, fixtureTexts, qc, dfOf, total)
|
||||
wantArr, _ := c.Expected["kept"].([]interface{})
|
||||
var want []DenseHit
|
||||
for _, h := range wantArr {
|
||||
pair, _ := h.([]interface{})
|
||||
id, _ := pair[0].(float64)
|
||||
cos, _ := pair[1].(float64)
|
||||
want = append(want, DenseHit{RowID: int64(id), Cos: cos})
|
||||
}
|
||||
jsonEq(t, c.Name, "kept", got, want)
|
||||
case "fusion_identity":
|
||||
dense := pairList(c.Input["dense"])
|
||||
bm25 := pairList(c.Input["bm25"])
|
||||
got := RankFusionEntries(dense, bm25)
|
||||
want, _ := c.Expected["fusion"].(map[string]interface{})
|
||||
jsonEq(t, c.Name, "fusion", got, want)
|
||||
case "constants":
|
||||
jsonEq(t, c.Name, "budget", RecallBudget, c.Expected["budget"])
|
||||
jsonEq(t, c.Name, "min_score", RecallMinScore, c.Expected["min_score"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"oracle": "src/rune/memory_store.py _py_* oracles",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "fts",
|
||||
"name": "significant_terms",
|
||||
"input": {
|
||||
"query": "the curator gates memory writes"
|
||||
},
|
||||
"expected": {
|
||||
"q": "\"curator\" \"gates\" \"memory\" \"writes\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fts",
|
||||
"name": "stopwords_dropped",
|
||||
"input": {
|
||||
"query": "the and was with nick"
|
||||
},
|
||||
"expected": {
|
||||
"q": "\"nick\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fts",
|
||||
"name": "short_fallback",
|
||||
"input": {
|
||||
"query": "go is fun ai"
|
||||
},
|
||||
"expected": {
|
||||
"q": "\"fun\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fts",
|
||||
"name": "cap_eight",
|
||||
"input": {
|
||||
"query": "alpha beta gamma delta epsilon zeta eta theta iota kappa"
|
||||
},
|
||||
"expected": {
|
||||
"q": "\"alpha\" \"beta\" \"gamma\" \"delta\" \"epsilon\" \"zeta\" \"theta\" \"iota\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fts",
|
||||
"name": "empty_returns_raw",
|
||||
"input": {
|
||||
"query": "a an of"
|
||||
},
|
||||
"expected": {
|
||||
"q": "a an of"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "chunk",
|
||||
"name": "under_size",
|
||||
"input": {
|
||||
"text": "one paragraph only",
|
||||
"size": 1500,
|
||||
"overlap": 150
|
||||
},
|
||||
"expected": {
|
||||
"chunks": [
|
||||
"one paragraph only"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "chunk",
|
||||
"name": "recombine",
|
||||
"input": {
|
||||
"text": "paragraph 0 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 8 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 10 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 11 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 12 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 13 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 14 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 15 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 16 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 17 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 18 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 19 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 20 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 21 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 22 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 23 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 24 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 25 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 26 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 27 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 28 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 29 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"size": 400,
|
||||
"overlap": 150
|
||||
},
|
||||
"expected": {
|
||||
"chunks": [
|
||||
"paragraph 0 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"paragraph 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 8 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 10 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 11 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 12 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 13 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"paragraph 14 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 15 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 16 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 17 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 18 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 19 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 20 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"paragraph 21 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 22 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 23 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 24 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 25 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 26 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 27 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"paragraph 28 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nparagraph 29 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "chunk",
|
||||
"name": "hard_split",
|
||||
"input": {
|
||||
"text": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
|
||||
"size": 1000,
|
||||
"overlap": 150
|
||||
},
|
||||
"expected": {
|
||||
"chunks": [
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "chunk",
|
||||
"name": "empty",
|
||||
"input": {
|
||||
"text": "",
|
||||
"size": 1500,
|
||||
"overlap": 150
|
||||
},
|
||||
"expected": {
|
||||
"chunks": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fuse_ranks",
|
||||
"name": "hybrid",
|
||||
"input": {
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
12.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
8.0
|
||||
],
|
||||
[
|
||||
1,
|
||||
3.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.11805555555555555
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.1213235294117647
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05555555555555555
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.058823529411764705
|
||||
]
|
||||
],
|
||||
"best_dense": 0.9,
|
||||
"mode": "hybrid"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fuse_ranks",
|
||||
"name": "dense_only",
|
||||
"input": {
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"bm25": []
|
||||
},
|
||||
"expected": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.0625
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.058823529411764705
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05555555555555555
|
||||
]
|
||||
],
|
||||
"best_dense": 0.9,
|
||||
"mode": "dense-only"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fuse_ranks",
|
||||
"name": "lexical_only",
|
||||
"input": {
|
||||
"dense": [],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
12.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
8.0
|
||||
],
|
||||
[
|
||||
1,
|
||||
3.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"ranks": [
|
||||
[
|
||||
2,
|
||||
1.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.5555555555555556
|
||||
],
|
||||
[
|
||||
1,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"best_dense": 0.0,
|
||||
"mode": "lexical-only"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fuse_ranks",
|
||||
"name": "neither",
|
||||
"input": {
|
||||
"dense": [],
|
||||
"bm25": []
|
||||
},
|
||||
"expected": {
|
||||
"ranks": [],
|
||||
"best_dense": 0.0,
|
||||
"mode": "lexical-only"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "project",
|
||||
"name": "budget_and_shape",
|
||||
"input": {
|
||||
"ranks": {
|
||||
"2": 0.0625,
|
||||
"1": 0.05
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"rows": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "map_reduce",
|
||||
"name": "top3_per_chunk",
|
||||
"input": {
|
||||
"entries": [
|
||||
{
|
||||
"id": 0,
|
||||
"relevance": 0.0
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"relevance": 0.1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"relevance": 0.2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"relevance": 0.30000000000000004
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"relevance": 0.4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"relevance": 0.5
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"relevance": 0.6000000000000001
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"relevance": 0.7000000000000001
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"relevance": 0.8
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"relevance": 0.9
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"relevance": 1.0
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"relevance": 1.1
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"relevance": 1.2000000000000002
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"relevance": 1.3
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"relevance": 1.4000000000000001
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"relevance": 1.5
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"relevance": 1.6
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"relevance": 1.7000000000000002
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"relevance": 1.8
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"relevance": 1.9000000000000001
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"relevance": 2.0
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"relevance": 2.1
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"relevance": 2.2
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"relevance": 2.3000000000000003
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"relevance": 2.4000000000000004
|
||||
}
|
||||
],
|
||||
"chunk_size": 10
|
||||
},
|
||||
"expected": {
|
||||
"mapped": [
|
||||
{
|
||||
"id": 24,
|
||||
"relevance": 2.4000000000000004
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"relevance": 2.3000000000000003
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"relevance": 2.2
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"relevance": 1.9000000000000001
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"relevance": 1.8
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"relevance": 1.7000000000000002
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"relevance": 0.9
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"relevance": 0.8
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"relevance": 0.7000000000000001
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "map_reduce",
|
||||
"name": "under_threshold",
|
||||
"input": {
|
||||
"entries": [
|
||||
{
|
||||
"id": 0,
|
||||
"relevance": 0.0
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"relevance": 0.1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"relevance": 0.2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"relevance": 0.30000000000000004
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"relevance": 0.4
|
||||
}
|
||||
],
|
||||
"chunk_size": 10
|
||||
},
|
||||
"expected": {
|
||||
"mapped": [
|
||||
{
|
||||
"id": 0,
|
||||
"relevance": 0.0
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"relevance": 0.1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"relevance": 0.2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"relevance": 0.30000000000000004
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"relevance": 0.4
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fuse_results",
|
||||
"name": "dedup_scale_align",
|
||||
"input": {
|
||||
"entries": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "nick prefers coffee",
|
||||
"topic": "human",
|
||||
"relevance": 2.5,
|
||||
"confidence": 0.9
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "NICK PREFERS COFFEE",
|
||||
"topic": "human",
|
||||
"relevance": 1.1,
|
||||
"confidence": 0.8
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "rune gates memory writes",
|
||||
"topic": "project",
|
||||
"relevance": 5.6,
|
||||
"confidence": 0.95
|
||||
}
|
||||
],
|
||||
"research": [
|
||||
{
|
||||
"chunk_id": "c1",
|
||||
"text": "the spine rebuild parity",
|
||||
"wing": "research",
|
||||
"rrf": 0.0625,
|
||||
"confidence": 0.7
|
||||
},
|
||||
{
|
||||
"chunk_id": "c2",
|
||||
"text": "curator provenance recovery",
|
||||
"wing": "research",
|
||||
"rrf": 0.05
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"merged": [
|
||||
{
|
||||
"id": 3,
|
||||
"text": "rune gates memory writes",
|
||||
"topic": "project",
|
||||
"kind": "fact",
|
||||
"relevance": 1.0,
|
||||
"confidence": 0.95
|
||||
},
|
||||
{
|
||||
"id": "c1",
|
||||
"text": "the spine rebuild parity",
|
||||
"topic": "research",
|
||||
"kind": "research",
|
||||
"relevance": 1.0,
|
||||
"confidence": 0.7
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"text": "nick prefers coffee",
|
||||
"topic": "human",
|
||||
"kind": "fact",
|
||||
"relevance": 0.0,
|
||||
"confidence": 0.9
|
||||
},
|
||||
{
|
||||
"id": "c2",
|
||||
"text": "curator provenance recovery",
|
||||
"topic": "research",
|
||||
"kind": "research",
|
||||
"relevance": 0.0,
|
||||
"confidence": 0.7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package main
|
||||
|
||||
// truth_recall_test.go — memory-store batch 2 parity gate: the read-path
|
||||
// replays truth_recall.json (live Python _py_* oracles — contract
|
||||
// BUILDER-2026-10-07T18:05:47Z-152c16).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRecallParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_recall.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_recall.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Fn {
|
||||
case "fts":
|
||||
q, _ := c.Input["query"].(string)
|
||||
got := FtsQueryConjunctive(q)
|
||||
want, _ := c.Expected["q"].(string)
|
||||
if got != want {
|
||||
t.Errorf("%s: fts\n got %q\nwant %q", c.Name, got, want)
|
||||
}
|
||||
case "chunk":
|
||||
text, _ := c.Input["text"].(string)
|
||||
size := int(c.Input["size"].(float64))
|
||||
overlap := int(c.Input["overlap"].(float64))
|
||||
got := ChunkText(text, size, overlap)
|
||||
wantArr, _ := c.Expected["chunks"].([]interface{})
|
||||
if len(got) != len(wantArr) {
|
||||
t.Errorf("%s: chunks = %d, want %d", c.Name, len(got), len(wantArr))
|
||||
continue
|
||||
}
|
||||
for i, w := range wantArr {
|
||||
want, _ := w.(string)
|
||||
if got[i] != want {
|
||||
t.Errorf("%s: chunk[%d]\n got %q\nwant %q", c.Name, i, got[i], want)
|
||||
}
|
||||
}
|
||||
case "fuse_ranks":
|
||||
dense := pairList(c.Input["dense"])
|
||||
bm25 := pairList(c.Input["bm25"])
|
||||
got := RankFusion(dense, bm25, RRFK)
|
||||
want := c.Expected
|
||||
jsonEq(t, c.Name, "mode", got["mode"], want["mode"])
|
||||
jsonEq(t, c.Name, "ranks", got["ranks"], want["ranks"])
|
||||
jsonEq(t, c.Name, "best_dense", got["best_dense"], want["best_dense"])
|
||||
case "project":
|
||||
ranksAny, _ := c.Input["ranks"].(map[string]interface{})
|
||||
ranks := map[string]float64{}
|
||||
for k, v := range ranksAny {
|
||||
f, _ := v.(float64)
|
||||
ranks[k] = f
|
||||
}
|
||||
// rows ship in the case input under "rows" (the truth ships its
|
||||
// own fixtures — same discipline as scaffold batches).
|
||||
rowsAny, _ := c.Input["rows"].([]interface{})
|
||||
var rows []map[string]interface{}
|
||||
for _, r := range rowsAny {
|
||||
m, _ := r.(map[string]interface{})
|
||||
rows = append(rows, m)
|
||||
}
|
||||
got := ProjectChunkRows(ranks, rows, 6)
|
||||
wantArr, _ := c.Expected["rows"].([]interface{})
|
||||
jsonEq(t, c.Name, "rows", got, wantArr)
|
||||
case "map_reduce":
|
||||
entries := objList(c.Input["entries"])
|
||||
chunkSize := int(c.Input["chunk_size"].(float64))
|
||||
got := MapReduce(entries, chunkSize)
|
||||
wantArr, _ := c.Expected["mapped"].([]interface{})
|
||||
jsonEq(t, c.Name, "mapped", got, wantArr)
|
||||
case "fuse_results":
|
||||
entries := objList(c.Input["entries"])
|
||||
research := objList(c.Input["research"])
|
||||
got := FuseResults(entries, research)
|
||||
wantArr, _ := c.Expected["merged"].([]interface{})
|
||||
jsonEq(t, c.Name, "merged", got, wantArr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pairList — [[id, score], ...] from decoded JSON.
|
||||
func pairList(v interface{}) [][2]interface{} {
|
||||
arr, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
out := make([][2]interface{}, 0, len(arr))
|
||||
for _, e := range arr {
|
||||
pair, ok2 := e.([]interface{})
|
||||
if !ok2 || len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]interface{}{pair[0], pair[1]})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func objList(v interface{}) []map[string]interface{} {
|
||||
arr, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
out := make([]map[string]interface{}, 0, len(arr))
|
||||
for _, e := range arr {
|
||||
m, _ := e.(map[string]interface{})
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// jsonEq — canonical-JSON comparison (Go typed values vs decoded JSON).
|
||||
func jsonEq(t *testing.T, name, field string, got, want interface{}) {
|
||||
t.Helper()
|
||||
gb, _ := json.Marshal(got)
|
||||
wb, _ := json.Marshal(want)
|
||||
if string(gb) != string(wb) {
|
||||
t.Errorf("%s: %s divergence:\n got %s\nwant %s", name, field, gb, wb)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,681 @@
|
||||
{
|
||||
"oracle": "src/rune/memory_store.py _py_score_recall_rows",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "hybrid",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.12
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.11
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.09
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "hybrid"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.53,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": null
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.7
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.6
|
||||
]
|
||||
],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
10.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
1,
|
||||
2.0
|
||||
]
|
||||
],
|
||||
"budget": 3,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.12,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 0.1241,
|
||||
"mode": "hybrid"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.11,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0691,
|
||||
"mode": "hybrid"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-05-01T10:00:00",
|
||||
"rrf": 0.1,
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0739,
|
||||
"mode": "hybrid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "dense_only_high_conf",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.06
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.055
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.7
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.6
|
||||
]
|
||||
],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.06,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 0.062,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.055,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0345,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-05-01T10:00:00",
|
||||
"rrf": 0.05,
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.037,
|
||||
"mode": "dense-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "dense_only_gap_abstain",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.06
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.055
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.75
|
||||
]
|
||||
],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.06,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 0.062,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.055,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0345,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-05-01T10:00:00",
|
||||
"rrf": 0.05,
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.037,
|
||||
"mode": "dense-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "dense_only_min_score",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.06
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.055
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.7
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.6
|
||||
]
|
||||
],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.9,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "lexical_only_gate",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
2,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"best_dense": 0.0,
|
||||
"mode": "lexical-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
10.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
1,
|
||||
2.0
|
||||
]
|
||||
],
|
||||
"budget": 5,
|
||||
"min_score": 1.0,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.9,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 6.0994,
|
||||
"mode": "lexical-only"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.7,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 3.5783,
|
||||
"mode": "lexical-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "neither",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
2,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"best_dense": 0.0,
|
||||
"mode": "lexical-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
// truth_score_test.go — memory-store batch 3 parity gate: the scoring
|
||||
// kernel replays truth_score.json (live _py_score_recall_rows — contract
|
||||
// BUILDER-2026-10-07T18:57:06Z-c2429c).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScoreParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_score.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_score.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
for _, c := range doc.Cases {
|
||||
fusion, _ := c.Input["fusion"].(map[string]interface{})
|
||||
rows := objList(c.Input["rows"])
|
||||
dense := pairList(c.Input["dense"])
|
||||
bm25 := pairList(c.Input["bm25"])
|
||||
budget := int(c.Input["budget"].(float64))
|
||||
minScore := c.Input["min_score"].(float64)
|
||||
nowStr, _ := c.Input["now"].(string)
|
||||
got := ScoreRecallRows(fusion, rows, dense, bm25, budget, minScore, nowStr)
|
||||
wantArr, _ := c.Expected["scored"].([]interface{})
|
||||
if len(got) != len(wantArr) {
|
||||
gb, _ := json.Marshal(got)
|
||||
wb, _ := json.Marshal(wantArr)
|
||||
t.Errorf("%s: scored = %d, want %d\n got %s\nwant %s",
|
||||
c.Name, len(got), len(wantArr), gb, wb)
|
||||
continue
|
||||
}
|
||||
for i, w := range wantArr {
|
||||
want, _ := w.(map[string]interface{})
|
||||
jsonEq(t, c.Name, field("scored[%d]", i), got[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// field — formatted comparison label.
|
||||
func field(format string, i int) string {
|
||||
return fmt.Sprintf(format, i)
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package main
|
||||
|
||||
// close.go — batch 3: the closure mechanics of scaffold.py +
|
||||
// structural_store.py (contract BUILDER-2026-10-07T18:42:38Z-52829e).
|
||||
// CloseQuality (the pure bucket math — the parity oracle's shape),
|
||||
// RecordObservedImpact (independent observation — never re-derives the
|
||||
// prediction), CloseContract (phase gate, inputs assembly, stamping).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// CloseQualityInputs — the input shape (R2 Wave 1 parity: the Rust
|
||||
// scaffold_core.close_quality computes from the SAME gathered inputs).
|
||||
type CloseQualityInputs struct {
|
||||
ContractID string `json:"contract_id"`
|
||||
Phase string `json:"phase"`
|
||||
AffectedEntities []string `json:"affected_entities"`
|
||||
ExpectedNewEntities []string `json:"expected_new_entities"`
|
||||
PredictedExistingAffected []string `json:"predicted_existing_affected"`
|
||||
PredictedFrontier map[string]interface{} `json:"predicted_frontier"`
|
||||
ExpectedRuntimeEffects []string `json:"expected_runtime_effects"`
|
||||
ObservedEvidence []map[string]interface{} `json:"observed_evidence"`
|
||||
Now string `json:"now"`
|
||||
}
|
||||
|
||||
// strSet — helpers.
|
||||
func strSet(list []string) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for _, s := range list {
|
||||
out[s] = true
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sortedSet(m map[string]bool) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
if out == nil {
|
||||
return []string{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func unionSets(ms ...map[string]bool) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for _, m := range ms {
|
||||
for k := range m {
|
||||
out[k] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func intersect(a, b map[string]bool) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for k := range a {
|
||||
if b[k] {
|
||||
out[k] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func difference(a, b map[string]bool) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for k := range a {
|
||||
if !b[k] {
|
||||
out[k] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// CloseQuality — the pure bucket math (Python _py_close_quality verbatim).
|
||||
func CloseQuality(inputs CloseQualityInputs) map[string]interface{} {
|
||||
affected := strSet(inputs.AffectedEntities)
|
||||
expectedNew := strSet(inputs.ExpectedNewEntities)
|
||||
expectedRuntime := strSet(inputs.ExpectedRuntimeEffects)
|
||||
obsSet := map[string]bool{}
|
||||
for _, item := range inputs.ObservedEvidence {
|
||||
eid, _ := item["entity"].(string)
|
||||
if eid != "" {
|
||||
obsSet[eid] = true
|
||||
}
|
||||
}
|
||||
|
||||
predExisting := strSet(inputs.PredictedExistingAffected)
|
||||
for k := range affected {
|
||||
predExisting[k] = true
|
||||
}
|
||||
if inputs.PredictedFrontier != nil {
|
||||
for _, key := range []string{"level0_direct", "level1_obligations", "contracts", "level2_verification"} {
|
||||
if arr, ok := inputs.PredictedFrontier[key].([]interface{}); ok {
|
||||
for _, raw := range arr {
|
||||
if s, ok := raw.(string); ok {
|
||||
predExisting[s] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
correctlyPredicted := intersect(predExisting, obsSet)
|
||||
predictedButUnaffected := difference(predExisting, obsSet)
|
||||
expectedNewObserved := intersect(expectedNew, obsSet)
|
||||
expectedNewMissing := difference(expectedNew, obsSet)
|
||||
// matched_runtime: observed entities for which SOME expected effect
|
||||
// STARTSWITH the observed entity.
|
||||
matchedRuntime := map[string]bool{}
|
||||
for o := range obsSet {
|
||||
for eff := range expectedRuntime {
|
||||
if len(eff) >= len(o) && eff[:len(o)] == o {
|
||||
matchedRuntime[o] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// expected_runtime_observed: effects for which SOME matched observed
|
||||
// entity is a prefix of the effect.
|
||||
expectedRuntimeObserved := map[string]bool{}
|
||||
for eff := range expectedRuntime {
|
||||
for o := range matchedRuntime {
|
||||
if len(eff) >= len(o) && eff[:len(o)] == o {
|
||||
expectedRuntimeObserved[eff] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
genuinelyUnexpectedNew := difference(difference(difference(obsSet, predExisting), expectedNew), matchedRuntime)
|
||||
|
||||
// evidence_sources: sorted unique sources (default "unattributed").
|
||||
srcSet := map[string]bool{}
|
||||
for _, item := range inputs.ObservedEvidence {
|
||||
src, _ := item["source"].(string)
|
||||
if src == "" {
|
||||
src = "unattributed"
|
||||
}
|
||||
srcSet[src] = true
|
||||
}
|
||||
|
||||
var candidates []map[string]interface{}
|
||||
for _, x := range sortedSet(genuinelyUnexpectedNew) {
|
||||
candidates = append(candidates, map[string]interface{}{
|
||||
"entity": x,
|
||||
"status": "CANDIDATE",
|
||||
"note": "genuinely unexpected new surface — evidence required before store update",
|
||||
})
|
||||
}
|
||||
if candidates == nil {
|
||||
candidates = []map[string]interface{}{}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"predicted_existing_affected": sortedSet(predExisting),
|
||||
"expected_new_entities": sortedSet(expectedNew),
|
||||
"observed": sortedSet(obsSet),
|
||||
"observed_existing": sortedSet(correctlyPredicted),
|
||||
"observed_new": sortedSet(intersect(obsSet, unionSets(expectedNew, difference(obsSet, predExisting)))),
|
||||
"correctly_predicted": sortedSet(correctlyPredicted),
|
||||
"predicted_but_unaffected": sortedSet(predictedButUnaffected),
|
||||
"expected_new_observed": sortedSet(expectedNewObserved),
|
||||
"expected_new_missing": sortedSet(expectedNewMissing),
|
||||
"expected_runtime_effects": sortedSet(expectedRuntime),
|
||||
"expected_runtime_observed": sortedSet(expectedRuntimeObserved),
|
||||
"expected_runtime_missing": sortedSet(difference(expectedRuntime, obsSet)),
|
||||
"genuinely_unexpected_new": sortedSet(genuinelyUnexpectedNew),
|
||||
"missed": sortedSet(genuinelyUnexpectedNew),
|
||||
"needless": sortedSet(predictedButUnaffected),
|
||||
"candidate_corrections": candidates,
|
||||
"evidence_sources": sortedSet(srcSet),
|
||||
}
|
||||
}
|
||||
|
||||
// RecordObservedImpact — independent observed impact: derive what was
|
||||
// ACTUALLY affected from external evidence; nothing auto-promotes.
|
||||
func RecordObservedImpact(doc map[string]interface{}, taskID string, evidenceItems []map[string]interface{}) map[string]interface{} {
|
||||
implicated := map[string]bool{}
|
||||
observations, _ := doc["observations"].([]interface{})
|
||||
for _, item := range evidenceItems {
|
||||
eid, _ := item["entity"].(string)
|
||||
if eid == "" {
|
||||
continue
|
||||
}
|
||||
implicated[eid] = true
|
||||
detail, _ := item["detail"].(string)
|
||||
if len(detail) > 300 {
|
||||
detail = detail[:300]
|
||||
}
|
||||
authority, _ := item["authority"].(string)
|
||||
source, _ := item["source"].(string)
|
||||
if source == "" {
|
||||
source = "unattributed"
|
||||
}
|
||||
observations = append(observations, map[string]interface{}{
|
||||
"kind": "observed_consequence",
|
||||
"task": taskID,
|
||||
"entity": eid,
|
||||
"source": source,
|
||||
"detail": detail,
|
||||
"authority": authority,
|
||||
"observed_at": NowStr,
|
||||
})
|
||||
}
|
||||
doc["observations"] = observations
|
||||
outList := sortedSet(implicated)
|
||||
return map[string]interface{}{
|
||||
"task": taskID,
|
||||
"observed_entities": outList,
|
||||
"evidence_count": len(evidenceItems),
|
||||
}
|
||||
}
|
||||
|
||||
// NowStr — the frozen clock binding (the generator/test sets it; the CLI
|
||||
// sets it per-invocation). Date-time-bomb seam.
|
||||
var NowStr = ""
|
||||
|
||||
// CloseContract — close a contract: record observed impact, run the
|
||||
// predicted-vs-observed comparison, persist quality. Requires
|
||||
// superseding/verifying. Mutates the entity (phase/status/next_action/
|
||||
// observed_at/checkpoints/ext.contract_quality).
|
||||
func CloseContract(doc map[string]interface{}, contractID string, observedEvidence []map[string]interface{}, scopeFiles []string) (map[string]interface{}, error) {
|
||||
e := docEntity(doc, "contract:"+contractID)
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("contract not found: %s", contractID)
|
||||
}
|
||||
phase, _ := e["phase"].(string)
|
||||
if phase != "superseding" && phase != "verifying" {
|
||||
return nil, fmt.Errorf("close requires superseding/verifying phase, got %s", phase)
|
||||
}
|
||||
obs := RecordObservedImpact(doc, contractID, observedEvidence)
|
||||
|
||||
ident, _ := e["identities"].(map[string]interface{})
|
||||
ext, _ := e["ext"].(map[string]interface{})
|
||||
|
||||
strListFrom := func(v interface{}) []string {
|
||||
arr, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
out := make([]string, 0, len(arr))
|
||||
for _, x := range arr {
|
||||
s, _ := x.(string)
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var predictedFrontier map[string]interface{}
|
||||
if pf, ok := ext["predicted_frontier"].(map[string]interface{}); ok {
|
||||
predictedFrontier = pf
|
||||
}
|
||||
|
||||
inputs := CloseQualityInputs{
|
||||
ContractID: contractID,
|
||||
Phase: phase,
|
||||
AffectedEntities: strListFrom(ident["affected_entities"]),
|
||||
ExpectedNewEntities: strListFrom(ident["expected_new_entities"]),
|
||||
PredictedExistingAffected: strListFrom(ext["predicted_existing_affected"]),
|
||||
PredictedFrontier: predictedFrontier,
|
||||
ExpectedRuntimeEffects: strListFrom(ext["expected_runtime_effects"]),
|
||||
ObservedEvidence: observedEvidence,
|
||||
Now: NowStr,
|
||||
}
|
||||
quality := CloseQuality(inputs)
|
||||
quality["contract"] = contractID
|
||||
quality["captured_at"] = inputs.Now
|
||||
quality["observed"] = obs["observed_entities"]
|
||||
|
||||
if scopeFiles != nil {
|
||||
ce := &ContractEntity{Identities: ident, Phase: phase, Status: "open"}
|
||||
quality["scope_check"] = CheckScope(ce, scopeFiles)
|
||||
}
|
||||
|
||||
if e["ext"] == nil {
|
||||
e["ext"] = map[string]interface{}{}
|
||||
}
|
||||
e["ext"].(map[string]interface{})["contract_quality"] = quality
|
||||
|
||||
cps, _ := e["checkpoints"].([]interface{})
|
||||
cps = append(cps, map[string]interface{}{
|
||||
"phase": "closed",
|
||||
"at": NowStr,
|
||||
"evidence": "closure-record",
|
||||
"note": "predicted-vs-observed captured",
|
||||
})
|
||||
e["checkpoints"] = cps
|
||||
e["phase"] = "closed"
|
||||
e["status"] = "closed"
|
||||
ident["next_action"] = NextAction["closed"]
|
||||
e["observed_at"] = NowStr
|
||||
return e, nil
|
||||
}
|
||||
Binary file not shown.
@@ -118,6 +118,7 @@ type ContractEntity struct {
|
||||
Status string `json:"status"`
|
||||
Checkpoints []map[string]interface{} `json:"checkpoints"`
|
||||
ObservedAt string `json:"observed_at"`
|
||||
Ext map[string]interface{} `json:"ext,omitempty"`
|
||||
}
|
||||
|
||||
// NewContract — new_contract: the identity dict (sorted lists), draft/open,
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
{
|
||||
"oracle": "src/rune/scaffold.py (live, now frozen at both namespaces)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": "2026-10-07T18:46:00",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "close_quality",
|
||||
"name": "buckets",
|
||||
"input": {
|
||||
"inputs": {
|
||||
"contract_id": "q1",
|
||||
"phase": "superseding",
|
||||
"affected_entities": [
|
||||
"module:a",
|
||||
"module:b"
|
||||
],
|
||||
"expected_new_entities": [
|
||||
"module:new1",
|
||||
"module:new2"
|
||||
],
|
||||
"predicted_existing_affected": [
|
||||
"module:c"
|
||||
],
|
||||
"predicted_frontier": {
|
||||
"level0_direct": [
|
||||
"module:a"
|
||||
],
|
||||
"level1_obligations": [
|
||||
"module:d"
|
||||
],
|
||||
"contracts": [
|
||||
"contract:x"
|
||||
],
|
||||
"level2_verification": [
|
||||
"canary:v"
|
||||
]
|
||||
},
|
||||
"expected_runtime_effects": [
|
||||
"module:a/ctl",
|
||||
"module:zz/never"
|
||||
],
|
||||
"observed_evidence": [
|
||||
{
|
||||
"entity": "module:a",
|
||||
"source": "tests",
|
||||
"detail": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"authority": "pytest"
|
||||
},
|
||||
{
|
||||
"entity": "module:new1",
|
||||
"source": "build",
|
||||
"detail": "created"
|
||||
},
|
||||
{
|
||||
"entity": "module:surprise",
|
||||
"source": "runtime",
|
||||
"detail": "unplanned"
|
||||
},
|
||||
{
|
||||
"entity": "",
|
||||
"source": "runtime",
|
||||
"detail": "no entity — ignored"
|
||||
}
|
||||
],
|
||||
"now": "2026-10-07T18:46:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"predicted_existing_affected": [
|
||||
"canary:v",
|
||||
"contract:x",
|
||||
"module:a",
|
||||
"module:b",
|
||||
"module:c",
|
||||
"module:d"
|
||||
],
|
||||
"expected_new_entities": [
|
||||
"module:new1",
|
||||
"module:new2"
|
||||
],
|
||||
"observed": [
|
||||
"module:a",
|
||||
"module:new1",
|
||||
"module:surprise"
|
||||
],
|
||||
"observed_existing": [
|
||||
"module:a"
|
||||
],
|
||||
"observed_new": [
|
||||
"module:new1",
|
||||
"module:surprise"
|
||||
],
|
||||
"correctly_predicted": [
|
||||
"module:a"
|
||||
],
|
||||
"predicted_but_unaffected": [
|
||||
"canary:v",
|
||||
"contract:x",
|
||||
"module:b",
|
||||
"module:c",
|
||||
"module:d"
|
||||
],
|
||||
"expected_new_observed": [
|
||||
"module:new1"
|
||||
],
|
||||
"expected_new_missing": [
|
||||
"module:new2"
|
||||
],
|
||||
"expected_runtime_effects": [
|
||||
"module:a/ctl",
|
||||
"module:zz/never"
|
||||
],
|
||||
"expected_runtime_observed": [
|
||||
"module:a/ctl"
|
||||
],
|
||||
"expected_runtime_missing": [
|
||||
"module:a/ctl",
|
||||
"module:zz/never"
|
||||
],
|
||||
"genuinely_unexpected_new": [
|
||||
"module:surprise"
|
||||
],
|
||||
"missed": [
|
||||
"module:surprise"
|
||||
],
|
||||
"needless": [
|
||||
"canary:v",
|
||||
"contract:x",
|
||||
"module:b",
|
||||
"module:c",
|
||||
"module:d"
|
||||
],
|
||||
"candidate_corrections": [
|
||||
{
|
||||
"entity": "module:surprise",
|
||||
"status": "CANDIDATE",
|
||||
"note": "genuinely unexpected new surface — evidence required before store update"
|
||||
}
|
||||
],
|
||||
"evidence_sources": [
|
||||
"build",
|
||||
"runtime",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "close_contract",
|
||||
"name": "full_lifecycle",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"entity": {
|
||||
"id": "contract:c1",
|
||||
"kind": "construction_contract",
|
||||
"maturity": "DECLARED",
|
||||
"tags": [],
|
||||
"identities": {
|
||||
"need": "n",
|
||||
"origin": "user",
|
||||
"scope": [
|
||||
"src/go/x/"
|
||||
],
|
||||
"requirement": "",
|
||||
"gap_evidence": "",
|
||||
"research_refs": [],
|
||||
"resolution": "",
|
||||
"toolchain": {},
|
||||
"affected_entities": [
|
||||
"module:a"
|
||||
],
|
||||
"expected_consumers": [],
|
||||
"expected_new_entities": [
|
||||
"module:new1"
|
||||
],
|
||||
"regression_obligations": [],
|
||||
"verification_obligations": [],
|
||||
"migration_obligations": [],
|
||||
"rollback_ref": "",
|
||||
"acceptance": [],
|
||||
"non_goals": [],
|
||||
"next_action": "observe real outcome; capture lessons"
|
||||
},
|
||||
"evidence_refs": [
|
||||
"contract-created:c1"
|
||||
],
|
||||
"observed_at": "2026-10-07T18:46:00",
|
||||
"phase": "closed",
|
||||
"status": "closed",
|
||||
"checkpoints": [
|
||||
{
|
||||
"phase": "approved",
|
||||
"at": "2026-10-07T18:46:00",
|
||||
"evidence": "",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"phase": "building",
|
||||
"at": "2026-10-07T18:46:00",
|
||||
"evidence": "",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"phase": "verifying",
|
||||
"at": "2026-10-07T18:46:00",
|
||||
"evidence": "",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"phase": "closed",
|
||||
"at": "2026-10-07T18:46:00",
|
||||
"evidence": "closure-record",
|
||||
"note": "predicted-vs-observed captured"
|
||||
}
|
||||
],
|
||||
"ext": {
|
||||
"predicted_frontier": {
|
||||
"level0_direct": [
|
||||
"module:a"
|
||||
],
|
||||
"level1_obligations": [],
|
||||
"contracts": [],
|
||||
"level2_verification": []
|
||||
},
|
||||
"contract_quality": {
|
||||
"predicted_existing_affected": [
|
||||
"module:a"
|
||||
],
|
||||
"expected_new_entities": [
|
||||
"module:new1"
|
||||
],
|
||||
"observed": [
|
||||
"module:a"
|
||||
],
|
||||
"observed_existing": [
|
||||
"module:a"
|
||||
],
|
||||
"observed_new": [],
|
||||
"correctly_predicted": [
|
||||
"module:a"
|
||||
],
|
||||
"predicted_but_unaffected": [],
|
||||
"expected_new_observed": [],
|
||||
"expected_new_missing": [
|
||||
"module:new1"
|
||||
],
|
||||
"expected_runtime_effects": [],
|
||||
"expected_runtime_observed": [],
|
||||
"expected_runtime_missing": [],
|
||||
"genuinely_unexpected_new": [],
|
||||
"missed": [],
|
||||
"needless": [],
|
||||
"candidate_corrections": [],
|
||||
"evidence_sources": [
|
||||
"tests"
|
||||
],
|
||||
"contract": "c1",
|
||||
"captured_at": "2026-10-07T18:46:00",
|
||||
"scope_check": {
|
||||
"verdict": "red",
|
||||
"in_scope": [
|
||||
"src/go/x/main.go"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"src/other/rogue.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "close_gate",
|
||||
"name": "phase_guard",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"ok": false,
|
||||
"error": "close requires superseding/verifying phase, got draft"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "close_gate",
|
||||
"name": "missing_contract",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"ok": false,
|
||||
"error": "contract not found: ghost"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
// truth_close_test.go — scaffold batch 3 parity gate: the closure
|
||||
// mechanics replay truth_close.json (live Python oracle, now frozen —
|
||||
// contract BUILDER-2026-10-07T18:42:38Z-52829e).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCloseParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_close.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_close.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
FrozenNow string `json:"frozen_now"`
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
NowStr = doc.FrozenNow
|
||||
defer func() { NowStr = "" }()
|
||||
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Fn {
|
||||
case "close_quality":
|
||||
inputsAny, _ := c.Input["inputs"].(map[string]interface{})
|
||||
ib, _ := json.Marshal(inputsAny)
|
||||
var inputs CloseQualityInputs
|
||||
if err := json.Unmarshal(ib, &inputs); err != nil {
|
||||
t.Fatalf("%s: inputs: %v", c.Name, err)
|
||||
}
|
||||
got := CloseQuality(inputs)
|
||||
jsonEq(t, c.Name, "quality", got, c.Expected)
|
||||
case "close_contract":
|
||||
// Replay the lifecycle: doc → new_contract → transitions →
|
||||
// predicted_frontier → close_contract. The doc ships IN THE
|
||||
// EXPECTED ENTITY (the oracle mutated the live doc), so the
|
||||
// replay rebuilds from the case's recorded inputs and compares
|
||||
// the lifecycle fields.
|
||||
want, _ := c.Expected["entity"].(map[string]interface{})
|
||||
docMap := map[string]interface{}{"entities": []interface{}{}, "observations": []interface{}{}}
|
||||
ident := map[string]interface{}{
|
||||
"need": "n",
|
||||
"scope": []string{"src/go/x/"},
|
||||
"affected_entities": []string{"module:a"},
|
||||
"expected_new_entities": []string{"module:new1"},
|
||||
}
|
||||
e, err := NewContract(docMap, "c1", "n", "user", ident)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: new contract: %v", c.Name, err)
|
||||
}
|
||||
docMap["entities"] = append(docMap["entities"].([]interface{}), mustMap(e))
|
||||
for _, step := range []string{"approved", "building", "verifying"} {
|
||||
if err := TransitionContract(e, step, "", "", NowStr); err != nil {
|
||||
t.Fatalf("%s: %s: %v", c.Name, step, err)
|
||||
}
|
||||
}
|
||||
e.Ext = map[string]interface{}{
|
||||
"predicted_frontier": map[string]interface{}{
|
||||
"level0_direct": []interface{}{"module:a"},
|
||||
"level1_obligations": []interface{}{},
|
||||
"contracts": []interface{}{},
|
||||
"level2_verification": []interface{}{},
|
||||
},
|
||||
}
|
||||
ev := []map[string]interface{}{{
|
||||
"entity": "module:a", "source": "tests", "detail": "passed",
|
||||
}}
|
||||
// docEntity needs the STORED form; sync it
|
||||
docMap["entities"].([]interface{})[0] = mustMap(e)
|
||||
got, err := CloseContract(docMap, "c1", ev, []string{"src/go/x/main.go", "src/other/rogue.py"})
|
||||
if err != nil {
|
||||
t.Fatalf("%s: close: %v", c.Name, err)
|
||||
}
|
||||
jsonEq(t, c.Name, "phase", got["phase"], want["phase"])
|
||||
jsonEq(t, c.Name, "status", got["status"], want["status"])
|
||||
// next_action lives in the STORED identities (CloseContract
|
||||
// mutates the store's copy).
|
||||
gotIdent, _ := got["identities"].(map[string]interface{})
|
||||
jsonEq(t, c.Name, "next_action", gotIdent["next_action"],
|
||||
want["identities"].(map[string]interface{})["next_action"])
|
||||
wantQ, _ := want["ext"].(map[string]interface{})["contract_quality"].(map[string]interface{})
|
||||
gotQ, _ := got["ext"].(map[string]interface{})["contract_quality"].(map[string]interface{})
|
||||
jsonEq(t, c.Name, "contract_quality", gotQ, wantQ)
|
||||
// observations recorded
|
||||
obs := docMap["observations"].([]interface{})
|
||||
if len(obs) != 1 {
|
||||
t.Errorf("%s: observations = %d, want 1", c.Name, len(obs))
|
||||
}
|
||||
case "close_gate":
|
||||
if c.Name == "phase_guard" {
|
||||
docMap := map[string]interface{}{
|
||||
"entities": []interface{}{mustMap(&ContractEntity{
|
||||
ID: "contract:early", Kind: "construction_contract",
|
||||
Identities: map[string]interface{}{"need": "n"},
|
||||
Phase: "draft", Status: "open",
|
||||
})},
|
||||
"observations": []interface{}{},
|
||||
}
|
||||
_, err := CloseContract(docMap, "early", nil, nil)
|
||||
if err == nil {
|
||||
t.Errorf("%s: draft-phase close must fail", c.Name)
|
||||
continue
|
||||
}
|
||||
want, _ := c.Expected["error"].(string)
|
||||
if err.Error() != want {
|
||||
t.Errorf("%s: error\n got %q\nwant %q", c.Name, err.Error(), want)
|
||||
}
|
||||
}
|
||||
if c.Name == "missing_contract" {
|
||||
docMap := map[string]interface{}{"entities": []interface{}{}, "observations": []interface{}{}}
|
||||
_, err := CloseContract(docMap, "ghost", nil, nil)
|
||||
if err == nil {
|
||||
t.Errorf("%s: missing contract must fail", c.Name)
|
||||
continue
|
||||
}
|
||||
want, _ := c.Expected["error"].(string)
|
||||
if err.Error() != want {
|
||||
t.Errorf("%s: error\n got %q\nwant %q", c.Name, err.Error(), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mustMap — entity → map (the stored form docEntity needs).
|
||||
func mustMap(e *ContractEntity) map[string]interface{} {
|
||||
b, _ := json.Marshal(e)
|
||||
var m map[string]interface{}
|
||||
_ = json.Unmarshal(b, &m)
|
||||
return m
|
||||
}
|
||||
|
||||
// silence unused import when tests trim
|
||||
var _ = strings.HasPrefix
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -203,7 +204,67 @@ func BackupFile(path string) string {
|
||||
return backup
|
||||
}
|
||||
|
||||
// UndoDir — PARITY with UNDO_DIR (~/.rune/undo).
|
||||
func UndoDir() string {
|
||||
// undoDirFn — the undo-dir seam (tests redirect; prod returns the default).
|
||||
var undoDirFn = func() string {
|
||||
return filepath.Join(expandHome("~"), ".rune", "undo")
|
||||
}
|
||||
|
||||
// UndoDir — PARITY with UNDO_DIR (~/.rune/undo).
|
||||
func UndoDir() string {
|
||||
return undoDirFn()
|
||||
}
|
||||
|
||||
// UndoLast — PARITY port of undo_last: restore the NEWEST backup from the
|
||||
// undo dir, delete it after restore. Quirk preserved: the safe-name
|
||||
// _-mangling is lossy (nested underscores in the original path are
|
||||
// ambiguous on restore — substring-semantics law: flagged, not fixed).
|
||||
func UndoLast(undoDir string) (string, string) {
|
||||
entries, err := os.ReadDir(undoDir)
|
||||
if err != nil {
|
||||
return "", "No backups to undo"
|
||||
}
|
||||
var backups []string
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() && strings.HasSuffix(e.Name(), ".bak") {
|
||||
backups = append(backups, filepath.Join(undoDir, e.Name()))
|
||||
}
|
||||
}
|
||||
if len(backups) == 0 {
|
||||
return "", "No backups to undo"
|
||||
}
|
||||
// newest first (mtime desc; ties broken by name desc — Python's
|
||||
// sorted(key=mtime, reverse=True) is stable, input order is ReadDir's
|
||||
// sorted-by-name here).
|
||||
sort.SliceStable(backups, func(i, j int) bool {
|
||||
ib, jb := mtimeOf(backups[i]), mtimeOf(backups[j])
|
||||
if ib != jb {
|
||||
return ib > jb
|
||||
}
|
||||
return backups[i] > backups[j]
|
||||
})
|
||||
backup := backups[0]
|
||||
name := strings.TrimSuffix(filepath.Base(backup), ".bak")
|
||||
// parts = name.split("_", 4); original = parts[4].replace("_", "/")
|
||||
parts := strings.SplitN(name, "_", 5)
|
||||
if len(parts) >= 5 {
|
||||
original := strings.ReplaceAll(parts[4], "_", "/")
|
||||
if _, err := os.Stat(original); err == nil {
|
||||
data, rerr := os.ReadFile(backup)
|
||||
if rerr == nil {
|
||||
if werr := os.WriteFile(original, data, 0o644); werr == nil {
|
||||
_ = os.Remove(backup)
|
||||
return original, fmt.Sprintf("Restored %s", original)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", "Could not determine original path"
|
||||
}
|
||||
|
||||
func mtimeOf(path string) int64 {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return info.ModTime().UnixNano()
|
||||
}
|
||||
@@ -32,9 +32,17 @@ type PreparedTool struct {
|
||||
}
|
||||
|
||||
// Read-only tools execute immediately; everything else is prepared.
|
||||
// readOnlyTools — PARITY with tool_executor.py READ_ONLY_TOOLS (the exact
|
||||
// 12-tool set; contract BUILDER-2026-10-07T19:10:38Z-cc6be2). The prior
|
||||
// port wrongly ADDED run_command (a mutating tool the oracle routes
|
||||
// through approval — the dangerous direction) and MISSED 8 tools. The
|
||||
// frozen truth battery asserts set equality so CI catches divergence.
|
||||
var readOnlyTools = map[string]bool{
|
||||
"read_file": true, "list_directory": true, "glob_files": true,
|
||||
"grep_files": true, "run_command": true,
|
||||
"read_file": true, "glob_files": true, "grep_files": true,
|
||||
"list_directory": true, "git_diff": true, "git_log": true,
|
||||
"repo_map": true, "read_persona": true, "query_memory": true,
|
||||
"check_growth": true, "diagnose_behavior": true,
|
||||
"find_capability": true,
|
||||
}
|
||||
|
||||
// DefaultApprovalTTL — seconds (parity with DEFAULT_APPROVAL_TTL).
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"oracle": "src/rune/tool_executor.py READ_ONLY_TOOLS + routing",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "read_only_tools",
|
||||
"name": "exact_set",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"tools": [
|
||||
"check_growth",
|
||||
"diagnose_behavior",
|
||||
"find_capability",
|
||||
"git_diff",
|
||||
"git_log",
|
||||
"glob_files",
|
||||
"grep_files",
|
||||
"list_directory",
|
||||
"query_memory",
|
||||
"read_file",
|
||||
"read_persona",
|
||||
"repo_map"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "prepare_routing",
|
||||
"name": "run_command_requires_approval",
|
||||
"input": {
|
||||
"tool": "run_command",
|
||||
"args": {
|
||||
"command": "ls -la"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"read_only": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "prepare_routing",
|
||||
"name": "read_file_executes_now",
|
||||
"input": {
|
||||
"tool": "read_file",
|
||||
"args": {
|
||||
"path": "/tmp"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"read_only": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
// truth_auth_test.go — the tool-executor AUTHORITY parity gate: the Go
|
||||
// readOnlyTools set must EQUAL the Python READ_ONLY_TOOLS set (truth_auth.json,
|
||||
// live oracle — contract BUILDER-2026-10-07T19:10:38Z-cc6be2).
|
||||
//
|
||||
// The run_command wrong-add this test pins was a security-adjacent
|
||||
// divergence: a mutating tool treated as read-only bypasses operator
|
||||
// approval. If this test fails, STOP: the approval boundary has drifted.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadOnlyToolsExactParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_auth.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_auth.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Name {
|
||||
case "exact_set":
|
||||
wantArr, _ := c.Expected["tools"].([]interface{})
|
||||
if len(readOnlyTools) != len(wantArr) {
|
||||
t.Errorf("readOnlyTools = %d tools, want %d — THE APPROVAL BOUNDARY HAS DRIFTED",
|
||||
len(readOnlyTools), len(wantArr))
|
||||
}
|
||||
for _, w := range wantArr {
|
||||
tool, _ := w.(string)
|
||||
if !readOnlyTools[tool] {
|
||||
t.Errorf("readOnlyTools MISSING oracle read-only tool %q", tool)
|
||||
}
|
||||
}
|
||||
// and the reverse: no tool in Go that the oracle excludes
|
||||
for tool := range readOnlyTools {
|
||||
found := false
|
||||
for _, w := range wantArr {
|
||||
s, _ := w.(string)
|
||||
if s == tool {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("readOnlyTools has EXTRA tool %q not in the oracle set (run_command class — CHECK THE APPROVAL BOUNDARY)", tool)
|
||||
}
|
||||
}
|
||||
case "run_command_requires_approval":
|
||||
// run_command MUST NOT be read-only (the dangerous direction).
|
||||
if readOnlyTools["run_command"] {
|
||||
t.Error("run_command is read-only in Go but NOT in the oracle — mutating tool bypasses approval")
|
||||
}
|
||||
wantRO, _ := c.Expected["read_only"].(bool)
|
||||
if wantRO != readOnlyTools["run_command"] {
|
||||
t.Errorf("routing parity broken for run_command")
|
||||
}
|
||||
case "read_file_executes_now":
|
||||
if !readOnlyTools["read_file"] {
|
||||
t.Error("read_file must be read-only (executes now)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadOnlySetSortedStable — the set renders deterministically (for
|
||||
// diagnostics) and never grows silently.
|
||||
func TestReadOnlySetSortedStable(t *testing.T) {
|
||||
var names []string
|
||||
for tool := range readOnlyTools {
|
||||
names = append(names, tool)
|
||||
}
|
||||
sort.Strings(names)
|
||||
joined := strings.Join(names, ",")
|
||||
if len(names) != 12 {
|
||||
t.Errorf("read-only set = %d tools, want 12: %s", len(names), joined)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
// truth_undo_test.go — the undo round-trip parity gate (live Python
|
||||
// oracle flow — contract BUILDER-2026-10-07T19:20:10Z-af954d).
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestUndoLastRoundTrip — the LIVE ORACLE TRUTH: undo_last can NEVER
|
||||
// restore absolute paths. The safe-name mangling (slash → _) plus
|
||||
// split("_", 4) puts the first path segment in parts[3]; parts[4]
|
||||
// reconstructs a path MISSING its first segment, which never exists.
|
||||
// The port reproduces this verbatim (dead-function parity — a product
|
||||
// defect filed upstream; the FIX belongs in the Python oracle first).
|
||||
func TestUndoLastRoundTrip(t *testing.T) {
|
||||
undoDir := t.TempDir()
|
||||
origDir := t.TempDir()
|
||||
orig := filepath.Join(origDir, "target.txt")
|
||||
if err := os.WriteFile(orig, []byte("original content\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
savedUndo := undoDirFn
|
||||
undoDirFn = func() string { return undoDir }
|
||||
t.Cleanup(func() { undoDirFn = savedUndo })
|
||||
|
||||
if BackupFile(orig) == "" {
|
||||
t.Fatal("backup failed")
|
||||
}
|
||||
if err := os.WriteFile(orig, []byte("MUTATED\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Parity with the live oracle: (None, "Could not determine original
|
||||
// path") — the mutation is NOT undone. Pinned so a future "fix" in
|
||||
// one language without the other cannot silently diverge.
|
||||
gotPath, msg := UndoLast(undoDir)
|
||||
if gotPath != "" || msg != "Could not determine original path" {
|
||||
t.Errorf("oracle parity: got (%q, %q), want (\"\", \"Could not determine original path\")", gotPath, msg)
|
||||
}
|
||||
data, err := os.ReadFile(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(data) != "MUTATED\n" {
|
||||
t.Errorf("oracle never restores; content changed to %q — DIVERGENCE", data)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUndoLastEmpty — no backups → exact message.
|
||||
func TestUndoLastEmpty(t *testing.T) {
|
||||
gotPath, msg := UndoLast(t.TempDir())
|
||||
if gotPath != "" || msg != "No backups to undo" {
|
||||
t.Errorf("empty undo: got (%q, %q)", gotPath, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUndoLastUndeterminable — a backup name with <5 underscore-parts
|
||||
// cannot resolve → exact message.
|
||||
func TestUndoLastUndeterminable(t *testing.T) {
|
||||
undoDir := t.TempDir()
|
||||
name := filepath.Join(undoDir, "20261007_101010_nodirectory.bak")
|
||||
if err := os.WriteFile(name, []byte("x"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotPath, msg := UndoLast(undoDir)
|
||||
if gotPath != "" || msg != "Could not determine original path" {
|
||||
t.Errorf("undeterminable: got (%q, %q)", gotPath, msg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package main
|
||||
|
||||
// digest.go — the always-on digest selection core of warm_router.py
|
||||
// (contract BUILDER-2026-10-07T19:35:07Z-4d6b4c). LexicalSignal /
|
||||
// LexicalOverlap / Universality / DigestSelect. DB I/O stays reactive.
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AlwaysDigestTokens — fixed low per-turn cost of the always-on layer.
|
||||
const AlwaysDigestTokens = 220
|
||||
|
||||
// DigestStop — _DIGEST_STOP extracted verbatim from the live module.
|
||||
var DigestStop = map[string]bool{
|
||||
"a": true, "about": true, "after": true, "also": true, "an": true,
|
||||
"and": true, "are": true, "as": true, "at": true, "be": true,
|
||||
"been": true, "but": true, "by": true, "do": true, "does": true,
|
||||
"each": true, "for": true, "from": true, "have": true, "her": true,
|
||||
"him": true, "his": true, "how": true, "in": true, "into": true,
|
||||
"is": true, "it": true, "its": true, "just": true, "more": true,
|
||||
"not": true, "of": true, "on": true, "only": true, "or": true,
|
||||
"over": true, "some": true, "such": true, "than": true, "that": true,
|
||||
"the": true, "them": true, "then": true, "there": true, "they": true,
|
||||
"this": true, "to": true, "under": true, "very": true, "was": true,
|
||||
"were": true, "what": true, "when": true, "where": true, "which": true,
|
||||
"while": true, "who": true, "why": true, "will": true, "with": true,
|
||||
"you": true, "your": true,
|
||||
}
|
||||
|
||||
// LexicalSignal — distinctive content words (lowercase, >3 chars after
|
||||
// stripping edge punctuation, not stop).
|
||||
func LexicalSignal(body string) map[string]bool {
|
||||
words := map[string]bool{}
|
||||
for _, w := range strings.Fields(strings.ToLower(body)) {
|
||||
w = strings.Trim(w, ".,;:!?()[]{}\"'")
|
||||
if len(w) > 3 && !DigestStop[w] {
|
||||
words[w] = true
|
||||
}
|
||||
}
|
||||
return words
|
||||
}
|
||||
|
||||
// LexicalOverlap — Jaccard of two bodies' distinctive words (0.0 on empty).
|
||||
func LexicalOverlap(a, b string) float64 {
|
||||
wa, wb := LexicalSignal(a), LexicalSignal(b)
|
||||
if len(wa) == 0 || len(wb) == 0 {
|
||||
return 0.0
|
||||
}
|
||||
inter := 0
|
||||
for w := range wa {
|
||||
if wb[w] {
|
||||
inter++
|
||||
}
|
||||
}
|
||||
union := len(wa) + len(wb) - inter
|
||||
return float64(inter) / float64(union)
|
||||
}
|
||||
|
||||
// DedupThreshold — Jaccard above this = near-duplicate, skip.
|
||||
const DedupThreshold = 0.45
|
||||
|
||||
var dateRe = regexp.MustCompile(`\b(19|20)\d{2}[-/]\d{1,2}[-/]\d{1,2}\b|\b\d{1,2}[-/]\d{1,2}[-/](19|20)\d{2}\b`)
|
||||
|
||||
var datedMarkers = []string{
|
||||
"session record", "research + session", "session note", "2026-",
|
||||
"2025-", "fixed", "fix:", "issue:", "bug", "incident", "workaround",
|
||||
"cause",
|
||||
}
|
||||
|
||||
var directiveMarkers = []string{
|
||||
"never", "always", "must", "should", "shall", "do not", "use", "keep",
|
||||
"prefer", "when", "if", "before", "after", "the only", "required",
|
||||
"forbidden", "authoritative",
|
||||
}
|
||||
|
||||
func countMarkers(low string, markers []string) int {
|
||||
hits := 0
|
||||
for _, m := range markers {
|
||||
if strings.Contains(low, m) {
|
||||
hits++
|
||||
}
|
||||
}
|
||||
return hits
|
||||
}
|
||||
|
||||
// Universality — how rule-like a body is (0-1). Dated records: 0.
|
||||
func Universality(body string) float64 {
|
||||
low := strings.ToLower(body)
|
||||
if dateRe.MatchString(body) {
|
||||
return 0.0
|
||||
}
|
||||
hits := countMarkers(low, datedMarkers)
|
||||
if hits >= 2 {
|
||||
return 0.0
|
||||
}
|
||||
if hits == 1 {
|
||||
return 0.25
|
||||
}
|
||||
directive := countMarkers(low, directiveMarkers)
|
||||
words := len(strings.Fields(body))
|
||||
base := 0.5
|
||||
add := float64(directive) * 0.05
|
||||
if add > 0.25 {
|
||||
add = 0.25
|
||||
}
|
||||
base += add
|
||||
switch {
|
||||
case words <= 40:
|
||||
base += 0.15
|
||||
case words <= 90:
|
||||
// no change
|
||||
default:
|
||||
base -= 0.2
|
||||
}
|
||||
if base < 0.0 {
|
||||
base = 0.0
|
||||
}
|
||||
if base > 1.0 {
|
||||
base = 1.0
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
// TopicOrder — the slug-prefix topic ranking.
|
||||
var TopicOrder = []string{"operating", "constitution", "persona", "project", "human", "general"}
|
||||
|
||||
// TopicRank — the first matching topic prefix, else 6.
|
||||
func TopicRank(slug string) int {
|
||||
for i, t := range TopicOrder {
|
||||
if strings.HasPrefix(slug, t+"-") || slug == t {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 6
|
||||
}
|
||||
|
||||
// DigestRow — one candidate row (the reactive layer's DB projection).
|
||||
type DigestRow struct {
|
||||
Text string
|
||||
Slug string
|
||||
Triggers string
|
||||
Importance float64
|
||||
Summary string
|
||||
AlwaysOn bool
|
||||
LastAccessed string
|
||||
}
|
||||
|
||||
// DigestSelect — the pure selection core: sort, gates, dedup, budget.
|
||||
func DigestSelect(rows []DigestRow, maxTokens int) []map[string]interface{} {
|
||||
// The 5-key sort: topic rank, -universality, always_on first,
|
||||
// -importance, -lexical density. Python's sort is STABLE; Go's
|
||||
// SliceStable preserves the input order on full-key ties.
|
||||
sortedRows := make([]DigestRow, len(rows))
|
||||
copy(sortedRows, rows)
|
||||
sort.SliceStable(sortedRows, func(i, j int) bool {
|
||||
a, b := sortedRows[i], sortedRows[j]
|
||||
ra, rb := TopicRank(a.Slug), TopicRank(b.Slug)
|
||||
if ra != rb {
|
||||
return ra < rb
|
||||
}
|
||||
ua, ub := Universality(a.SummaryOrText()), Universality(b.SummaryOrText())
|
||||
if ua != ub {
|
||||
return ua > ub
|
||||
}
|
||||
if a.AlwaysOn != b.AlwaysOn {
|
||||
return a.AlwaysOn
|
||||
}
|
||||
if a.Importance != b.Importance {
|
||||
return a.Importance > b.Importance
|
||||
}
|
||||
da := len(LexicalSignal(a.SummaryOrText()))
|
||||
db := len(LexicalSignal(b.SummaryOrText()))
|
||||
return da > db
|
||||
})
|
||||
|
||||
out := []map[string]interface{}{}
|
||||
var selectedBodies []string
|
||||
used := 0
|
||||
for _, r := range sortedRows {
|
||||
if used >= maxTokens {
|
||||
break
|
||||
}
|
||||
// junk: no triggers or trivial body
|
||||
if strings.TrimSpace(r.Triggers) == "" || len(strings.TrimSpace(r.Text)) < 12 {
|
||||
continue
|
||||
}
|
||||
body := strings.TrimSpace(r.Summary)
|
||||
if body == "" {
|
||||
body = r.Text
|
||||
}
|
||||
if body == "" {
|
||||
continue
|
||||
}
|
||||
// universality gate
|
||||
if Universality(body) <= 0.0 {
|
||||
continue
|
||||
}
|
||||
// lexical gate
|
||||
if len(LexicalSignal(body)) == 0 {
|
||||
continue
|
||||
}
|
||||
// dedup
|
||||
dup := false
|
||||
for _, prev := range selectedBodies {
|
||||
if LexicalOverlap(body, prev) >= DedupThreshold {
|
||||
dup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if dup {
|
||||
continue
|
||||
}
|
||||
tok := len(body) / 4
|
||||
if tok < 1 {
|
||||
tok = 1
|
||||
}
|
||||
// oversized
|
||||
if tok > maxTokens {
|
||||
continue
|
||||
}
|
||||
// budget-fit skip (out non-empty and doesn't fit)
|
||||
if len(out) > 0 && used+tok > maxTokens {
|
||||
continue
|
||||
}
|
||||
importance := r.Importance
|
||||
if importance == 0 {
|
||||
importance = 0.5
|
||||
}
|
||||
out = append(out, map[string]interface{}{
|
||||
"slug": r.Slug,
|
||||
"body": body,
|
||||
"score": round2W(importance),
|
||||
"tokens": tok,
|
||||
"always": true,
|
||||
"compressed": strings.TrimSpace(r.Summary) != "",
|
||||
})
|
||||
selectedBodies = append(selectedBodies, body)
|
||||
used += tok
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SummaryOrText — the summary-or-text body used by the sort keys.
|
||||
func (r DigestRow) SummaryOrText() string {
|
||||
if s := strings.TrimSpace(r.Summary); s != "" {
|
||||
return s
|
||||
}
|
||||
return r.Text
|
||||
}
|
||||
|
||||
func round2W(f float64) float64 {
|
||||
return float64(int64(f*100+0.5)) / 100
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"oracle": "src/rune/warm_router.py _py surfaces + always_digest (DB monkeypatched)",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "lexical",
|
||||
"name": "signal_strips_punct",
|
||||
"input": {
|
||||
"body": "The curator gates memory writes (always)."
|
||||
},
|
||||
"expected": {
|
||||
"words": [
|
||||
"always",
|
||||
"curator",
|
||||
"gates",
|
||||
"memory",
|
||||
"writes"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "lexical",
|
||||
"name": "overlap_jaccard",
|
||||
"input": {
|
||||
"a": "the curator gates memory writes",
|
||||
"b": "the curator gates memory recall"
|
||||
},
|
||||
"expected": {
|
||||
"ov": 0.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "lexical",
|
||||
"name": "overlap_empty",
|
||||
"input": {
|
||||
"a": "the and was",
|
||||
"b": "curator gates writes"
|
||||
},
|
||||
"expected": {
|
||||
"ov": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "universality",
|
||||
"name": "dated_zero",
|
||||
"input": {
|
||||
"body": "Fixed the parser on 2026-10-07 after the incident."
|
||||
},
|
||||
"expected": {
|
||||
"u": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "universality",
|
||||
"name": "incident_markers_zero",
|
||||
"input": {
|
||||
"body": "Session record: bug cause was the stale kernel; workaround applied."
|
||||
},
|
||||
"expected": {
|
||||
"u": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "universality",
|
||||
"name": "one_marker_quarter",
|
||||
"input": {
|
||||
"body": "This addresses the issue: streaming recovery."
|
||||
},
|
||||
"expected": {
|
||||
"u": 0.25
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "universality",
|
||||
"name": "directive_rule_high",
|
||||
"input": {
|
||||
"body": "Never pipe gate commands through tail; always check the exit code before continuing work."
|
||||
},
|
||||
"expected": {
|
||||
"u": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "universality",
|
||||
"name": "rambling_penalty",
|
||||
"input": {
|
||||
"body": "word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word never always must keep"
|
||||
},
|
||||
"expected": {
|
||||
"u": 0.49999999999999994
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "digest",
|
||||
"name": "selection_full",
|
||||
"input": {
|
||||
"rows": [
|
||||
[
|
||||
"2026-10-07 fixed the parser bug after the incident report",
|
||||
"project-parser",
|
||||
"parser",
|
||||
0.9,
|
||||
"",
|
||||
1,
|
||||
0.8,
|
||||
"2026-10-07"
|
||||
],
|
||||
[
|
||||
"Never pipe gate commands through tail; always check the exit code before continuing any work in the repository.",
|
||||
"operating-gates",
|
||||
"gate",
|
||||
0.7,
|
||||
"",
|
||||
1,
|
||||
0.9,
|
||||
"2026-10-07"
|
||||
],
|
||||
[
|
||||
"the and was with that this from into",
|
||||
"general-soup",
|
||||
"soup",
|
||||
0.5,
|
||||
"",
|
||||
0,
|
||||
0.5,
|
||||
"2026-10-06"
|
||||
],
|
||||
[
|
||||
"Always check the exit code before continuing any work; never pipe gate commands through tail in the repo.",
|
||||
"operating-gates-copy",
|
||||
"gate2",
|
||||
0.8,
|
||||
"",
|
||||
1,
|
||||
0.7,
|
||||
"2026-10-05"
|
||||
],
|
||||
[
|
||||
"Prefer coconut milk in coffee; you do not drink eggs at breakfast time.",
|
||||
"human-prefs",
|
||||
"coffee",
|
||||
0.6,
|
||||
"",
|
||||
0,
|
||||
0.6,
|
||||
"2026-10-04"
|
||||
],
|
||||
[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"operating-huge",
|
||||
"huge",
|
||||
1.0,
|
||||
"",
|
||||
1,
|
||||
1.0,
|
||||
"2026-10-03"
|
||||
]
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"digest": [
|
||||
{
|
||||
"slug": "operating-gates-copy",
|
||||
"body": "Always check the exit code before continuing any work; never pipe gate commands through tail in the repo.",
|
||||
"score": 0.8,
|
||||
"tokens": 26,
|
||||
"always": true,
|
||||
"compressed": false
|
||||
},
|
||||
{
|
||||
"slug": "human-prefs",
|
||||
"body": "Prefer coconut milk in coffee; you do not drink eggs at breakfast time.",
|
||||
"score": 0.6,
|
||||
"tokens": 17,
|
||||
"always": true,
|
||||
"compressed": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
// truth_digest_test.go — warm-router deep parity gate: the digest core
|
||||
// replays truth_digest.json (live Python oracle, DB monkeypatched —
|
||||
// contract BUILDER-2026-10-07T19:35:07Z-4d6b4c).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDigestParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_digest.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_digest.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Fn {
|
||||
case "lexical":
|
||||
if c.Name == "signal_strips_punct" {
|
||||
body, _ := c.Input["body"].(string)
|
||||
got := LexicalSignal(body)
|
||||
wantArr, _ := c.Expected["words"].([]interface{})
|
||||
if len(got) != len(wantArr) {
|
||||
t.Errorf("%s: words = %d, want %d", c.Name, len(got), len(wantArr))
|
||||
continue
|
||||
}
|
||||
for _, w := range wantArr {
|
||||
s, _ := w.(string)
|
||||
if !got[s] {
|
||||
t.Errorf("%s: missing word %q", c.Name, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Name == "overlap_jaccard" || c.Name == "overlap_empty" {
|
||||
a, _ := c.Input["a"].(string)
|
||||
bb, _ := c.Input["b"].(string)
|
||||
got := LexicalOverlap(a, bb)
|
||||
want, _ := c.Expected["ov"].(float64)
|
||||
if diff := got - want; diff > 1e-9 || diff < -1e-9 {
|
||||
t.Errorf("%s: overlap got %f want %f", c.Name, got, want)
|
||||
}
|
||||
}
|
||||
case "universality":
|
||||
body, _ := c.Input["body"].(string)
|
||||
got := Universality(body)
|
||||
want, _ := c.Expected["u"].(float64)
|
||||
if diff := got - want; diff > 1e-9 || diff < -1e-9 {
|
||||
t.Errorf("%s: universality got %f want %f", c.Name, got, want)
|
||||
}
|
||||
case "digest":
|
||||
rowsAny, _ := c.Input["rows"].([]interface{})
|
||||
var rows []DigestRow
|
||||
for _, raw := range rowsAny {
|
||||
arr, ok := raw.([]interface{})
|
||||
if !ok || len(arr) < 7 {
|
||||
continue
|
||||
}
|
||||
imp, _ := arr[3].(float64)
|
||||
alwaysOn, _ := arr[5].(float64)
|
||||
rows = append(rows, DigestRow{
|
||||
Text: strW(arr[0]),
|
||||
Slug: strW(arr[1]),
|
||||
Triggers: strW(arr[2]),
|
||||
Importance: imp,
|
||||
Summary: strW(arr[4]),
|
||||
AlwaysOn: alwaysOn != 0,
|
||||
})
|
||||
}
|
||||
got := DigestSelect(rows, 220)
|
||||
wantArr, _ := c.Expected["digest"].([]interface{})
|
||||
jsonEqW(t, c.Name, "digest", got, wantArr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func strW(v interface{}) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func jsonEqW(t *testing.T, name, field string, got, want interface{}) {
|
||||
t.Helper()
|
||||
gb, _ := json.Marshal(got)
|
||||
wb, _ := json.Marshal(want)
|
||||
if string(gb) != string(wb) {
|
||||
t.Errorf("%s: %s divergence:\n got %s\nwant %s", name, field, gb, wb)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.121.0"
|
||||
__version__ = "0.128.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
|
||||
|
||||
@@ -233,7 +233,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.121.0"' in rune_version,
|
||||
'__version__ = "0.128.0"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
@@ -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.121.0
|
||||
Version: v0.128.0
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -1948,7 +1948,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.121.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.128.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
+44991
-2
File diff suppressed because it is too large.
Load diff
+364
-292
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user