feat(witness): close PARTIAL gaps — dichotomy wave
This commit is contained in:
1 parent
28f5ed5ee1
commit
50039ed83b
5 files changed
+2424
-160
No files matched your search
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
"""check_witness_parity.py — live oracle-vs-Go parity sweep for the
|
||||
witness gap closure (#2479, contract factory-gap-wi).
|
||||
|
||||
The frozen envelope (truth.json, 58 cases) pins the JSON-native surface
|
||||
byte-identically. THIS script drives the LIVE Python oracle (kernel
|
||||
delegation on — the Rust cognition_core is the authority) and the Go
|
||||
binary (`op` mode) across the same branches plus the seams the harness
|
||||
cannot freeze: the full log round-trip with BYTE-COMPARED log files
|
||||
(frozen clock → identical jsonl bytes incl. ", "/": " separators,
|
||||
insertion order, ensure_ascii), read_log deque semantics against
|
||||
hand-seeded blank/malformed lines, log_stats shapes incl. list-shaped
|
||||
signals and skip_rate half-even rounding, the missing-file branches, and
|
||||
_now under the pinned clock.
|
||||
|
||||
Usage: PYTHONPATH=src python3 scripts/check_witness_parity.py
|
||||
[--bin src/go/witness/witness]
|
||||
Exit 0 on 100% parity, 1 with the first divergence.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
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 / "scripts"))
|
||||
|
||||
FROZEN_NOW = "2026-10-14T09:15:00.123456+00:00"
|
||||
SCRATCH_P = Path("/tmp/witness_parity_py/memory")
|
||||
SCRATCH_G = Path("/tmp/witness_parity_go/memory")
|
||||
|
||||
os.environ["RUNE_MEMORY_DIR"] = str(SCRATCH_P)
|
||||
os.environ["RUNE_FROZEN_NOW"] = FROZEN_NOW
|
||||
|
||||
from freeze_truth import _pin_clock # noqa: E402
|
||||
|
||||
import rune.witness as wt # noqa: E402
|
||||
|
||||
_pin_clock(wt) # module-global datetime pinned → _now() frozen both sides
|
||||
|
||||
IS_CORRECTION = [
|
||||
"no, that's wrong, fix it",
|
||||
"Actually, you misunderstood the requirement",
|
||||
"that answer is wrong",
|
||||
"incorrect output",
|
||||
"not right either",
|
||||
"please try again",
|
||||
"nope",
|
||||
"stop doing that",
|
||||
"we already did the migration",
|
||||
"that's not what I asked for", # hits via the lowercase that'?s? not (correct|right|what)
|
||||
"I meant the left column", # capital-I quirk → False
|
||||
"I already told you", # capital-I quirk → False
|
||||
"wrongly spelled", # \b boundary → False
|
||||
"looks good to me",
|
||||
"",
|
||||
"don't do that",
|
||||
"I told you the column",
|
||||
]
|
||||
|
||||
EXTRACT_TOPIC = [
|
||||
"the deployment runner needs repair now",
|
||||
"fix this now",
|
||||
"",
|
||||
"café résumé matters",
|
||||
"the fix_it_loop crashed again",
|
||||
"alpha beta gamma delta epsilon zeta eta theta",
|
||||
"WHY DOES THIS matter",
|
||||
"why does the memory keep drifting",
|
||||
]
|
||||
|
||||
DETECT_SIGNALS = [
|
||||
("", None, None, None),
|
||||
("hello", "boom", None, None),
|
||||
("hello", "é" * 250, None, None),
|
||||
("hello", None, "done", None),
|
||||
("hello", None, "completed", None),
|
||||
("hello", None, "abandoned", None),
|
||||
("hello", None, "failed", None),
|
||||
("hello", None, "running", None),
|
||||
("no, that's wrong, try again", None, None, None),
|
||||
("i need to check that", None, None, None),
|
||||
("we already did the migration", None, None, None),
|
||||
("I'll send the report", None, None, None),
|
||||
("anyway moving on to budgets", None, None, ["budget"]),
|
||||
("the deployment runner needs repair", None, None, ["deployment", "runner"]),
|
||||
("urgent! I prefer rust, but actually wait", None, None, None),
|
||||
("why does the memory keep drifting", None, None, None),
|
||||
]
|
||||
|
||||
LOG_ENTRIES = [
|
||||
({"correction": True, "corrected_topic": ["budgets"]}, "user corrected the budget", "sess-1"),
|
||||
({"urgency_detected": True}, "", "sess-2"),
|
||||
({"correction": True}, "r2", ""),
|
||||
({"learning_opportunity": True}, "noted — café", "s4"),
|
||||
({"completion": "done"}, "", "s5"),
|
||||
({"error": True}, "logged", "s6"),
|
||||
({"error": True}, "", "s7"),
|
||||
({"commitment_detected": True}, "planned", "s8"),
|
||||
]
|
||||
|
||||
|
||||
def run_go(binpath, env, req):
|
||||
p = subprocess.run([str(binpath), "op"], input=json.dumps(req),
|
||||
capture_output=True, text=True, env=env)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError(f"go op {req.get('op')} exited {p.returncode}: {p.stdout} {p.stderr}")
|
||||
out = json.loads(p.stdout)
|
||||
return out.get("result")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--bin", default=str(REPO / "src/go/witness/witness"))
|
||||
args = ap.parse_args()
|
||||
binpath = Path(args.bin)
|
||||
if not binpath.exists():
|
||||
print(f"Go binary missing: {binpath} (go build -o witness . in src/go/witness)")
|
||||
return 1
|
||||
|
||||
for scratch in (SCRATCH_P, SCRATCH_G):
|
||||
base = scratch.parent
|
||||
if base.exists():
|
||||
shutil.rmtree(base)
|
||||
scratch.mkdir(parents=True)
|
||||
|
||||
go_env = dict(os.environ)
|
||||
go_env["RUNE_MEMORY_DIR"] = str(SCRATCH_G)
|
||||
go_env["WITNESS_FROZEN_NOW"] = FROZEN_NOW
|
||||
|
||||
fails = total = 0
|
||||
|
||||
def check(name, oracle_val, go_val):
|
||||
nonlocal fails, total
|
||||
total += 1
|
||||
# log_stats may carry INT signal_types keys (list-shaped signals —
|
||||
# Python dicts accept them; json.dumps coerces to "1"/"2" on dump
|
||||
# but sort_keys chokes first). Normalize the way dumps coerces.
|
||||
if isinstance(oracle_val, dict) and "signal_types" in oracle_val:
|
||||
oracle_val = dict(oracle_val)
|
||||
oracle_val["signal_types"] = {
|
||||
str(k): v for k, v in oracle_val["signal_types"].items()}
|
||||
if json.dumps(oracle_val, sort_keys=True) != json.dumps(go_val, sort_keys=True):
|
||||
fails += 1
|
||||
print(f"DIVERGE {name}:\n oracle: {json.dumps(oracle_val)[:300]}\n"
|
||||
f" go: {json.dumps(go_val)[:300]}")
|
||||
|
||||
# _now under the pinned clock
|
||||
check("now", wt._now(), run_go(binpath, go_env, {"op": "now"}))
|
||||
|
||||
# is_correction — live public fn (capital-letter quirk included)
|
||||
for text in IS_CORRECTION:
|
||||
check(f"is_correction:{text[:30]!r}", wt.is_correction(text),
|
||||
run_go(binpath, go_env, {"op": "is_correction", "text": text}))
|
||||
|
||||
# extract_topic — live kernel delegation
|
||||
for text in EXTRACT_TOPIC:
|
||||
check(f"extract_topic:{text[:30]!r}", wt.extract_topic(text),
|
||||
run_go(binpath, go_env, {"op": "extract_topic", "text": text}))
|
||||
|
||||
# detect_signals — live kernel delegation, full dicts
|
||||
for msg, te, ts, rt in DETECT_SIGNALS:
|
||||
check(f"detect_signals:{msg[:30]!r}",
|
||||
wt.detect_signals(msg, tool_error=te, task_status=ts, recent_topics=rt),
|
||||
run_go(binpath, go_env, {"op": "detect_signals", "message": msg,
|
||||
"tool_error": te, "task_status": ts,
|
||||
"recent_topics": rt}))
|
||||
|
||||
# gather seams
|
||||
check("stopwords", wt._stopwords(), run_go(binpath, go_env, {"op": "stopwords"}))
|
||||
check("tables", wt._tables(), run_go(binpath, go_env, {"op": "tables"}))
|
||||
|
||||
# log family — read/stats on missing files
|
||||
check("read_log:missing", wt.read_log(20),
|
||||
run_go(binpath, go_env, {"op": "read_log", "limit": 20}))
|
||||
check("log_stats:missing", wt.log_stats(), run_go(binpath, go_env, {"op": "log_stats"}))
|
||||
|
||||
# log_entry sequence — then BYTE-COMPARE the two jsonl files
|
||||
for signals, reflection, session in LOG_ENTRIES:
|
||||
wt.log_entry(signals, reflection, session)
|
||||
run_go(binpath, go_env, {"op": "log_entry", "signals": signals,
|
||||
"reflection": reflection, "session_id": session})
|
||||
py_log = Path(wt.LOG_PATH).read_bytes()
|
||||
go_log = (SCRATCH_G / "index" / "witness_log.jsonl").read_bytes()
|
||||
total += 1
|
||||
if py_log != go_log:
|
||||
fails += 1
|
||||
print(f"DIVERGE log-file-bytes:\n oracle: {py_log[:200]!r}\n go: {go_log[:200]!r}")
|
||||
|
||||
# read_log limits (deque semantics) + log_stats (skip_rate half-even)
|
||||
for limit in (0, 1, 2, 3, 20, 99):
|
||||
check(f"read_log:{limit}", wt.read_log(limit),
|
||||
run_go(binpath, go_env, {"op": "read_log", "limit": limit}))
|
||||
check("log_stats:full", wt.log_stats(), run_go(binpath, go_env, {"op": "log_stats"}))
|
||||
|
||||
# hand-seeded blank + malformed lines land on BOTH files identically
|
||||
seed = b'{bad json\n\n{"signals": [1, 2, 2], "reflection": "x"}\n'
|
||||
with open(wt.LOG_PATH, "ab") as f:
|
||||
f.write(seed)
|
||||
with open(SCRATCH_G / "index" / "witness_log.jsonl", "ab") as f:
|
||||
f.write(seed)
|
||||
for limit in (3, 20):
|
||||
check(f"read_log:seeded:{limit}", wt.read_log(limit),
|
||||
run_go(binpath, go_env, {"op": "read_log", "limit": limit}))
|
||||
check("log_stats:seeded", wt.log_stats(), run_go(binpath, go_env, {"op": "log_stats"}))
|
||||
|
||||
# missing-file branches again after wipe
|
||||
os.remove(wt.LOG_PATH)
|
||||
os.remove(SCRATCH_G / "index" / "witness_log.jsonl")
|
||||
check("read_log:removed", wt.read_log(5),
|
||||
run_go(binpath, go_env, {"op": "read_log", "limit": 5}))
|
||||
check("log_stats:removed", wt.log_stats(), run_go(binpath, go_env, {"op": "log_stats"}))
|
||||
|
||||
print(f"parity: {total - fails}/{total} match")
|
||||
return 1 if fails else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_witness_truth.py — shared-harness truth battery for the witness
|
||||
gap closure (#2479, contract factory-gap-wi).
|
||||
|
||||
Uses scripts/freeze_truth.py (the ONE truth law) to freeze
|
||||
src/rune/witness.py behavior for the Go port at src/go/witness:
|
||||
|
||||
* _now — pinned via the envelope frozen clock (microsecond precision).
|
||||
* is_correction — public pure-Python fn: patterns match AS-IS against
|
||||
LOWERCASED text, so capital-letter patterns (r"\\bI (said|meant|asked)\\b",
|
||||
r"\\bthat'?s? not what I\\b", ...) can never fire — the quirk is
|
||||
frozen, not fixed ("that's not what I asked for" → False).
|
||||
* extract_topic / detect_signals — frozen under the LIVE kernel (the
|
||||
pure-Python oracle retired 2026-10-10): cc_witness_extract_topic's
|
||||
ASCII \\w runs and cc_witness_signals' deterministic order
|
||||
(error → correction → completion → drift → commitment →
|
||||
contradiction → preference → urgency → learning), case-INSENSITIVE
|
||||
family matching (kernel lowercases pattern and text — richer than
|
||||
public is_correction), rune-safe [:200] error_text truncation, and
|
||||
the frozen completion triple ("done"/"abandoned"/"failed" only).
|
||||
* _tables / _stopwords — the gather seams as sorted-list data.
|
||||
* log_entry / read_log / log_stats — the log family. LOG_PATH derives
|
||||
from memory_env at import time, so RUNE_MEMORY_DIR pins it to a
|
||||
scratch dir this script WIPES first; the cases themselves build the
|
||||
log state in order (log_entry creates the file), keeping --verify
|
||||
byte-identical. skip_rate pins Python .0f half-even rounding (37.5 →
|
||||
"38%", 33.33 → "33%").
|
||||
|
||||
Verify (byte-identical law):
|
||||
rm -rf /tmp/witness_gap_freeze && \\
|
||||
RUNE_MEMORY_DIR=/tmp/witness_gap_freeze/memory PYTHONPATH=src \\
|
||||
python3 scripts/freeze_truth.py --verify src/go/witness/truth.json \\
|
||||
--module rune.witness
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
FROZEN_NOW = "2026-10-14T09:15:00.123456+00:00"
|
||||
SCRATCH = Path("/tmp/witness_gap_freeze")
|
||||
|
||||
os.environ["RUNE_FROZEN_NOW"] = FROZEN_NOW
|
||||
os.environ["RUNE_MEMORY_DIR"] = str(SCRATCH / "memory")
|
||||
|
||||
from freeze_truth import freeze, write_envelope # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
if SCRATCH.exists():
|
||||
shutil.rmtree(SCRATCH)
|
||||
|
||||
cases = [
|
||||
# _now — frozen clock
|
||||
("_now", (), {}),
|
||||
# is_correction — hits across the pattern families
|
||||
("is_correction", ("no, that's wrong, fix it",), {}),
|
||||
("is_correction", ("Actually, you misunderstood the requirement",), {}),
|
||||
("is_correction", ("that answer is wrong",), {}),
|
||||
("is_correction", ("incorrect output",), {}),
|
||||
("is_correction", ("not right either",), {}),
|
||||
("is_correction", ("please try again",), {}),
|
||||
("is_correction", ("nope",), {}),
|
||||
("is_correction", ("stop doing that",), {}),
|
||||
("is_correction", ("we already did the migration",), {}),
|
||||
# is_correction — misses incl. the capital-letter quirk and boundaries
|
||||
("is_correction", ("that's not what I asked for",), {}),
|
||||
("is_correction", ("I meant the left column",), {}),
|
||||
("is_correction", ("I already told you",), {}),
|
||||
("is_correction", ("wrongly spelled",), {}),
|
||||
("is_correction", ("looks good to me",), {}),
|
||||
("is_correction", ("",), {}),
|
||||
# extract_topic — kernel ASCII \w runs, stopwords, cap 5
|
||||
("extract_topic", ("the deployment runner needs repair now",), {}),
|
||||
("extract_topic", ("fix this now",), {}),
|
||||
("extract_topic", ("",), {}),
|
||||
("extract_topic", ("café résumé matters",), {}),
|
||||
("extract_topic", ("the fix_it_loop crashed again",), {}),
|
||||
("extract_topic", ("alpha beta gamma delta epsilon zeta eta theta",), {}),
|
||||
("extract_topic", ("WHY DOES THIS matter",), {}),
|
||||
# detect_signals — error family + rune-safe [:200]
|
||||
("detect_signals", ("", None, None, None), {}),
|
||||
("detect_signals", ("hello", "boom", None, None), {}),
|
||||
("detect_signals", ("hello", "é" * 250, None, None), {}),
|
||||
# completion — the frozen triple only
|
||||
("detect_signals", ("hello", None, "done", None), {}),
|
||||
("detect_signals", ("hello", None, "completed", None), {}),
|
||||
("detect_signals", ("hello", None, "abandoned", None), {}),
|
||||
("detect_signals", ("hello", None, "failed", None), {}),
|
||||
("detect_signals", ("hello", None, "running", None), {}),
|
||||
# correction family (kernel: case-insensitive, richer than public)
|
||||
("detect_signals", ("no, that's wrong, try again", None, None, None), {}),
|
||||
("detect_signals", ("i need to check that", None, None, None), {}),
|
||||
("detect_signals", ("we already did the migration", None, None, None), {}),
|
||||
("detect_signals", ("I'll send the report", None, None, None), {}),
|
||||
# topic drift — new-topics order and the overlap ratio both ways
|
||||
("detect_signals", ("anyway moving on to budgets", None, None, ["budget"]), {}),
|
||||
("detect_signals", ("the deployment runner needs repair", None, None,
|
||||
["deployment", "runner"]), {}),
|
||||
# multi-family message — kernel family order pinned
|
||||
("detect_signals", ("urgent! I prefer rust, but actually wait", None, None, None), {}),
|
||||
("detect_signals", ("why does the memory keep drifting", None, None, None), {}),
|
||||
# gather seams
|
||||
("_tables", (), {}),
|
||||
("_stopwords", (), {}),
|
||||
# log family — cases build the scratch log state IN ORDER
|
||||
("read_log", (3,), {}), # missing file → []
|
||||
("log_stats", (), {}), # missing file → 3-key zero shape
|
||||
("log_entry", ({"correction": True, "corrected_topic": ["budgets"]},
|
||||
"user corrected the budget", "sess-1"), {}),
|
||||
("log_entry", ({"urgency_detected": True}, "", "sess-2"), {}),
|
||||
("read_log", (20,), {}),
|
||||
("read_log", (1,), {}),
|
||||
("log_stats", (), {}), # total 2, wr 1 → "50%"
|
||||
("log_entry", ({"correction": True}, "r2", ""), {}),
|
||||
("log_stats", (), {}), # total 3, wr 2 → "33%"
|
||||
("read_log", (0,), {}), # deque maxlen=0 → []
|
||||
("log_entry", ({"learning_opportunity": True}, "noted", "s4"), {}),
|
||||
("log_entry", ({"completion": "done"}, "", "s5"), {}),
|
||||
("log_entry", ({"error": True}, "logged", "s6"), {}),
|
||||
("log_entry", ({"error": True}, "", "s7"), {}),
|
||||
("log_entry", ({"commitment_detected": True}, "planned", "s8"), {}),
|
||||
("log_stats", (), {}), # total 8, wr 5 → 37.5 → "38%" (half-even)
|
||||
("read_log", (99,), {}),
|
||||
]
|
||||
truth = freeze("rune.witness", cases, oracle_module="rune.witness")
|
||||
dest = REPO / "src/go/witness/truth.json"
|
||||
write_envelope(FROZEN_NOW, truth, dest)
|
||||
print(f"wrote {len(truth)} cases -> {dest}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+833
-160
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,913 @@
|
||||
{
|
||||
"frozen_now": "2026-10-14T09:15:00.123456+00:00",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "_now",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": "2026-10-14T09:15:00.123456+00:00"
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"no, that's wrong, fix it"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"Actually, you misunderstood the requirement"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"that answer is wrong"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"incorrect output"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"not right either"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"please try again"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"nope"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"stop doing that"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"we already did the migration"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"that's not what I asked for"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": true
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"I meant the left column"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": false
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"I already told you"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": false
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"wrongly spelled"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": false
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
"looks good to me"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": false
|
||||
},
|
||||
{
|
||||
"fn": "is_correction",
|
||||
"args": [
|
||||
""
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": false
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
"the deployment runner needs repair now"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
"deployment",
|
||||
"runner",
|
||||
"needs",
|
||||
"repair"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
"fix this now"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": []
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
""
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": []
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
"café résumé matters"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
"matters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
"the fix_it_loop crashed again"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
"fix_it_loop",
|
||||
"crashed",
|
||||
"again"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
"alpha beta gamma delta epsilon zeta eta theta"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
"alpha",
|
||||
"beta",
|
||||
"gamma",
|
||||
"delta",
|
||||
"epsilon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "extract_topic",
|
||||
"args": [
|
||||
"WHY DOES THIS matter"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
"matter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
"boom",
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": true,
|
||||
"error_text": "boom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
"éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé",
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"error": true,
|
||||
"error_text": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
null,
|
||||
"done",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"completion": "done"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
null,
|
||||
"completed",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
null,
|
||||
"abandoned",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"completion": "abandoned"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
null,
|
||||
"failed",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"completion": "failed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"hello",
|
||||
null,
|
||||
"running",
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"no, that's wrong, try again",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"correction": true,
|
||||
"corrected_topic": [
|
||||
"wrong",
|
||||
"again"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"i need to check that",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"commitment_detected": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"we already did the migration",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"correction": true,
|
||||
"corrected_topic": [
|
||||
"already",
|
||||
"migration"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"I'll send the report",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"commitment_detected": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"anyway moving on to budgets",
|
||||
null,
|
||||
null,
|
||||
[
|
||||
"budget"
|
||||
]
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"topic_drift": true,
|
||||
"new_topics": [
|
||||
"anyway",
|
||||
"moving",
|
||||
"budgets"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"the deployment runner needs repair",
|
||||
null,
|
||||
null,
|
||||
[
|
||||
"deployment",
|
||||
"runner"
|
||||
]
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"urgent! I prefer rust, but actually wait",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"possible_contradiction": true,
|
||||
"preference_detected": true,
|
||||
"urgency_detected": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "detect_signals",
|
||||
"args": [
|
||||
"why does the memory keep drifting",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"learning_opportunity": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "_tables",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"stopwords": [
|
||||
"a",
|
||||
"after",
|
||||
"an",
|
||||
"and",
|
||||
"are",
|
||||
"as",
|
||||
"at",
|
||||
"be",
|
||||
"been",
|
||||
"before",
|
||||
"being",
|
||||
"but",
|
||||
"by",
|
||||
"can",
|
||||
"could",
|
||||
"did",
|
||||
"do",
|
||||
"does",
|
||||
"during",
|
||||
"for",
|
||||
"from",
|
||||
"had",
|
||||
"has",
|
||||
"have",
|
||||
"how",
|
||||
"in",
|
||||
"into",
|
||||
"is",
|
||||
"it",
|
||||
"its",
|
||||
"may",
|
||||
"might",
|
||||
"no",
|
||||
"not",
|
||||
"of",
|
||||
"on",
|
||||
"or",
|
||||
"shall",
|
||||
"should",
|
||||
"that",
|
||||
"the",
|
||||
"these",
|
||||
"this",
|
||||
"those",
|
||||
"through",
|
||||
"to",
|
||||
"was",
|
||||
"were",
|
||||
"what",
|
||||
"when",
|
||||
"where",
|
||||
"which",
|
||||
"who",
|
||||
"why",
|
||||
"will",
|
||||
"with",
|
||||
"would"
|
||||
],
|
||||
"correction": [
|
||||
"\\b(don'?t|do not) (do|say|use) (that|this|it)\\b",
|
||||
"\\bI (already|already told you)\\b",
|
||||
"\\bI (don'?t|do not) (want|need|mean)\\b",
|
||||
"\\bI (said|meant|asked)\\b",
|
||||
"\\bI (told|asked|mentioned) (you|this|that)\\b",
|
||||
"\\bactually[,.]?\\s*(it'?s?|that'?s?|you)\\b",
|
||||
"\\bfix (this|that|it)\\b",
|
||||
"\\bincorrect\\b",
|
||||
"\\bno[,.]?\\s*(that'?s?|that is|it'?s?|it is)\\s+(wrong|incorrect|not right)\\b",
|
||||
"\\bnope\\b",
|
||||
"\\bnot right\\b",
|
||||
"\\bnot what I\\b",
|
||||
"\\bstop (doing|saying|that)\\b",
|
||||
"\\bthat'?s? (not|n'?t) (what|how|why)\\b",
|
||||
"\\bthat'?s? not (correct|right|what)\\b",
|
||||
"\\bthat'?s? not what I\\b",
|
||||
"\\btry again\\b",
|
||||
"\\bwe (already|already did)\\b",
|
||||
"\\bwhy (did|are|is) (you|it|that)\\b",
|
||||
"\\bwrong\\b"
|
||||
],
|
||||
"commitment": [
|
||||
"\\bI need to\\b",
|
||||
"\\bI promise\\b",
|
||||
"\\bI will\\s+(send|do|fix|build|check|update|create|write)\\b",
|
||||
"\\bI'?ll\\s+(send|do|fix|build|check|update|create|write)\\b",
|
||||
"\\blet me\\s+(send|do|fix|build|check|update|create|write)\\b",
|
||||
"\\bremind me\\b"
|
||||
],
|
||||
"contradiction": [
|
||||
"\\b(but|however|actually|wait|that contradicts)\\b"
|
||||
],
|
||||
"preference": [
|
||||
"\\b(always|never) (use|do|say)\\b",
|
||||
"\\b(don'?t|do not) (like|want|need)\\b",
|
||||
"\\bI (prefer|like|love|hate|dislike)\\b",
|
||||
"\\bI'?m (not )?(a fan of|into|interested in)\\b"
|
||||
],
|
||||
"urgency": [
|
||||
"\\b(deadline|due|overdue|late)\\b",
|
||||
"\\b(important|critical|crucial|vital)\\b",
|
||||
"\\b(urgent|asap|immediately|now|quick|hurry)\\b"
|
||||
],
|
||||
"learning": [
|
||||
"\\b(I don'?t (understand|know|get))\\b",
|
||||
"\\b(how (does|do|did)|why (does|do|did)|what (is|are|was|were))\\b",
|
||||
"\\b(learn|understand|explain|teach|show me)\\b"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "_stopwords",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
"a",
|
||||
"after",
|
||||
"an",
|
||||
"and",
|
||||
"are",
|
||||
"as",
|
||||
"at",
|
||||
"be",
|
||||
"been",
|
||||
"before",
|
||||
"being",
|
||||
"but",
|
||||
"by",
|
||||
"can",
|
||||
"could",
|
||||
"did",
|
||||
"do",
|
||||
"does",
|
||||
"during",
|
||||
"for",
|
||||
"from",
|
||||
"had",
|
||||
"has",
|
||||
"have",
|
||||
"how",
|
||||
"in",
|
||||
"into",
|
||||
"is",
|
||||
"it",
|
||||
"its",
|
||||
"may",
|
||||
"might",
|
||||
"no",
|
||||
"not",
|
||||
"of",
|
||||
"on",
|
||||
"or",
|
||||
"shall",
|
||||
"should",
|
||||
"that",
|
||||
"the",
|
||||
"these",
|
||||
"this",
|
||||
"those",
|
||||
"through",
|
||||
"to",
|
||||
"was",
|
||||
"were",
|
||||
"what",
|
||||
"when",
|
||||
"where",
|
||||
"which",
|
||||
"who",
|
||||
"why",
|
||||
"will",
|
||||
"with",
|
||||
"would"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "read_log",
|
||||
"args": [
|
||||
3
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": []
|
||||
},
|
||||
{
|
||||
"fn": "log_stats",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"total": 0,
|
||||
"with_reflection": 0,
|
||||
"signal_types": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"correction": true,
|
||||
"corrected_topic": [
|
||||
"budgets"
|
||||
]
|
||||
},
|
||||
"user corrected the budget",
|
||||
"sess-1"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"urgency_detected": true
|
||||
},
|
||||
"",
|
||||
"sess-2"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "read_log",
|
||||
"args": [
|
||||
20
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "sess-1",
|
||||
"signals": {
|
||||
"correction": true,
|
||||
"corrected_topic": [
|
||||
"budgets"
|
||||
]
|
||||
},
|
||||
"reflection": "user corrected the budget",
|
||||
"signal_count": 2
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "sess-2",
|
||||
"signals": {
|
||||
"urgency_detected": true
|
||||
},
|
||||
"reflection": "",
|
||||
"signal_count": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "read_log",
|
||||
"args": [
|
||||
1
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "sess-2",
|
||||
"signals": {
|
||||
"urgency_detected": true
|
||||
},
|
||||
"reflection": "",
|
||||
"signal_count": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fn": "log_stats",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"total": 2,
|
||||
"with_reflection": 1,
|
||||
"skip_rate": "50%",
|
||||
"signal_types": {
|
||||
"correction": 1,
|
||||
"corrected_topic": 1,
|
||||
"urgency_detected": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"correction": true
|
||||
},
|
||||
"r2",
|
||||
""
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_stats",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"total": 3,
|
||||
"with_reflection": 2,
|
||||
"skip_rate": "33%",
|
||||
"signal_types": {
|
||||
"correction": 2,
|
||||
"corrected_topic": 1,
|
||||
"urgency_detected": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "read_log",
|
||||
"args": [
|
||||
0
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": []
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"learning_opportunity": true
|
||||
},
|
||||
"noted",
|
||||
"s4"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"completion": "done"
|
||||
},
|
||||
"",
|
||||
"s5"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"error": true
|
||||
},
|
||||
"logged",
|
||||
"s6"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"error": true
|
||||
},
|
||||
"",
|
||||
"s7"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_entry",
|
||||
"args": [
|
||||
{
|
||||
"commitment_detected": true
|
||||
},
|
||||
"planned",
|
||||
"s8"
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": null
|
||||
},
|
||||
{
|
||||
"fn": "log_stats",
|
||||
"args": [],
|
||||
"kwargs": {},
|
||||
"expected": {
|
||||
"total": 8,
|
||||
"with_reflection": 5,
|
||||
"skip_rate": "38%",
|
||||
"signal_types": {
|
||||
"correction": 2,
|
||||
"corrected_topic": 1,
|
||||
"urgency_detected": 1,
|
||||
"learning_opportunity": 1,
|
||||
"completion": 1,
|
||||
"error": 2,
|
||||
"commitment_detected": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "read_log",
|
||||
"args": [
|
||||
99
|
||||
],
|
||||
"kwargs": {},
|
||||
"expected": [
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "sess-1",
|
||||
"signals": {
|
||||
"correction": true,
|
||||
"corrected_topic": [
|
||||
"budgets"
|
||||
]
|
||||
},
|
||||
"reflection": "user corrected the budget",
|
||||
"signal_count": 2
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "sess-2",
|
||||
"signals": {
|
||||
"urgency_detected": true
|
||||
},
|
||||
"reflection": "",
|
||||
"signal_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "",
|
||||
"signals": {
|
||||
"correction": true
|
||||
},
|
||||
"reflection": "r2",
|
||||
"signal_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "s4",
|
||||
"signals": {
|
||||
"learning_opportunity": true
|
||||
},
|
||||
"reflection": "noted",
|
||||
"signal_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "s5",
|
||||
"signals": {
|
||||
"completion": "done"
|
||||
},
|
||||
"reflection": "",
|
||||
"signal_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "s6",
|
||||
"signals": {
|
||||
"error": true
|
||||
},
|
||||
"reflection": "logged",
|
||||
"signal_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "s7",
|
||||
"signals": {
|
||||
"error": true
|
||||
},
|
||||
"reflection": "",
|
||||
"signal_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-10-14T09:15:00.123456+00:00",
|
||||
"session_id": "s8",
|
||||
"signals": {
|
||||
"commitment_detected": true
|
||||
},
|
||||
"reflection": "planned",
|
||||
"signal_count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package main
|
||||
|
||||
// truth_test.go — the parity gate. Replays every case from truth.json
|
||||
// (the shared freeze_truth.py envelope: frozen clock + 58 Python-oracle
|
||||
// cases WITH inputs, generated by scripts/gen_witness_truth.py) VERBATIM
|
||||
// through the ported Go functions: _now, is_correction (capital-letter
|
||||
// quirk), extract_topic + detect_signals (kernel parity: ASCII \w runs,
|
||||
// deterministic signal order, case-insensitive families, rune-safe
|
||||
// truncation, frozen completion triple), _tables/_stopwords gather seams,
|
||||
// and the log family (log_entry / read_log / log_stats with skip_rate
|
||||
// half-even rounding). The log cases build the scratch log state IN
|
||||
// ORDER against an isolated log-path seam — replays are order-sensitive
|
||||
// by design, mirroring the oracle's append-only log. A divergence fails
|
||||
// the port, not the oracle.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//go:embed truth.json
|
||||
var frozenTruthJSON string
|
||||
|
||||
type frozenCase struct {
|
||||
Fn string `json:"fn"`
|
||||
Args []any `json:"args"`
|
||||
Kwargs map[string]any `json:"kwargs"`
|
||||
Expected json.RawMessage `json:"expected"`
|
||||
}
|
||||
|
||||
// canonical — render through the Python-parity serializer, then decode, so
|
||||
// both sides compare as decoded data (Python None and Go nil → null).
|
||||
func canonical(t *testing.T, v any) any {
|
||||
t.Helper()
|
||||
var out any
|
||||
if err := json.Unmarshal([]byte(pyDumps(v)), &out); err != nil {
|
||||
t.Fatalf("decode actual: %v", err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func argStr(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func argInt(t *testing.T, v any) int {
|
||||
t.Helper()
|
||||
f, ok := v.(float64)
|
||||
if !ok {
|
||||
t.Fatalf("expected number arg, got %v (%T)", v, v)
|
||||
}
|
||||
return int(f)
|
||||
}
|
||||
|
||||
func argStrSlice(t *testing.T, v any) []string {
|
||||
t.Helper()
|
||||
if v == nil {
|
||||
return []string{} // the facade converts None → recent_topics or []
|
||||
}
|
||||
l, ok := v.([]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected list arg, got %v (%T)", v, v)
|
||||
}
|
||||
out := []string{}
|
||||
for _, e := range l {
|
||||
out = append(out, argStr(e))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func argOrd(t *testing.T, v any) *ordObj {
|
||||
t.Helper()
|
||||
raw, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal signals arg: %v", err)
|
||||
}
|
||||
o, err := decodeOrdObj(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("decode signals arg: %v", err)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func TestFrozenTruthReplay(t *testing.T) {
|
||||
var env struct {
|
||||
FrozenNow string `json:"frozen_now"`
|
||||
Cases []frozenCase `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(frozenTruthJSON), &env); err != nil {
|
||||
t.Fatalf("decode truth envelope: %v", err)
|
||||
}
|
||||
if len(env.Cases) == 0 {
|
||||
t.Fatal("no truth cases — the parity gate is empty")
|
||||
}
|
||||
if env.FrozenNow != "2026-10-14T09:15:00.123456+00:00" {
|
||||
t.Fatalf("frozen clock moved: %q", env.FrozenNow)
|
||||
}
|
||||
savedClock := clock
|
||||
clock = func() string { return env.FrozenNow }
|
||||
t.Cleanup(func() { clock = savedClock })
|
||||
dir := t.TempDir()
|
||||
savedLog := logPathFunc
|
||||
logPathFunc = func() string { return filepath.Join(dir, "witness_log.jsonl") }
|
||||
t.Cleanup(func() { logPathFunc = savedLog })
|
||||
|
||||
for i, c := range env.Cases {
|
||||
var actual any
|
||||
switch c.Fn {
|
||||
case "_now":
|
||||
actual = now()
|
||||
case "is_correction":
|
||||
actual = isCorrection(argStr(c.Args[0]))
|
||||
case "extract_topic":
|
||||
actual = extractTopic(argStr(c.Args[0]))
|
||||
case "detect_signals":
|
||||
actual = detectSignals(argStr(c.Args[0]), argStr(c.Args[1]),
|
||||
argStr(c.Args[2]), argStrSlice(t, c.Args[3]))
|
||||
case "_tables":
|
||||
actual = tables()
|
||||
case "_stopwords":
|
||||
actual = stopwordsSorted()
|
||||
case "log_entry":
|
||||
if err := logEntry(argOrd(t, c.Args[0]), argStr(c.Args[1]), argStr(c.Args[2])); err != nil {
|
||||
t.Fatalf("case %d (log_entry): %v", i, err)
|
||||
}
|
||||
actual = nil
|
||||
case "read_log":
|
||||
actual = readLog(argInt(t, c.Args[0]))
|
||||
case "log_stats":
|
||||
actual = logStats()
|
||||
default:
|
||||
t.Fatalf("case %d: unknown fn %q", i, c.Fn)
|
||||
}
|
||||
var want any
|
||||
if err := json.Unmarshal(c.Expected, &want); err != nil {
|
||||
t.Fatalf("case %d (%s): decode expected: %v", i, c.Fn, err)
|
||||
}
|
||||
if got := canonical(t, actual); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("case %d (%s): got %v want %v", i, c.Fn, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogEntryLineBytes — the JSONL line is byte-identical to Python
|
||||
// json.dumps: ", "/": " separators, insertion order (timestamp,
|
||||
// session_id, signals, reflection, signal_count), ensure_ascii.
|
||||
func TestLogEntryLineBytes(t *testing.T) {
|
||||
clockSave := clock
|
||||
clock = func() string { return "2026-10-14T09:15:00.123456+00:00" }
|
||||
defer func() { clock = clockSave }()
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, "witness_log.jsonl")
|
||||
logSave := logPathFunc
|
||||
logPathFunc = func() string { return p }
|
||||
defer func() { logPathFunc = logSave }()
|
||||
|
||||
sig := newOrd()
|
||||
sig.Set("correction", true)
|
||||
sig.Set("corrected_topic", []string{"budgets"})
|
||||
if err := logEntry(sig, "user corrected the budget", "sess-1"); err != nil {
|
||||
t.Fatalf("logEntry: %v", err)
|
||||
}
|
||||
want := `{"timestamp": "2026-10-14T09:15:00.123456+00:00", "session_id": "sess-1", "signals": {"correction": true, "corrected_topic": ["budgets"]}, "reflection": "user corrected the budget", "signal_count": 2}` + "\n"
|
||||
raw, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatalf("read log: %v", err)
|
||||
}
|
||||
if string(raw) != want {
|
||||
t.Errorf("log line bytes:\n got %q\nwant %q", string(raw), want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadLogSeams — deque semantics (limit counts LINES), blank-line and
|
||||
// malformed-line skipping, trailing line without newline.
|
||||
func TestReadLogSeams(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, "witness_log.jsonl")
|
||||
save := logPathFunc
|
||||
logPathFunc = func() string { return p }
|
||||
defer func() { logPathFunc = save }()
|
||||
|
||||
content := `{"a": 1}` + "\n" +
|
||||
`{bad json` + "\n" +
|
||||
"\n" +
|
||||
`{"b": 2}` + "\n" +
|
||||
`{"c": 3}` // no trailing newline
|
||||
if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 5 lines total (a, bad, blank, b, c) — limit counts LINES:
|
||||
// limit 10 → all 3 valid; limit 2 → last two lines (b, c) → 2 valid;
|
||||
// limit 3 → blank, b, c → 2 valid.
|
||||
if got := readLog(10); len(got) != 3 {
|
||||
t.Errorf("limit 10: got %d entries, want 3", len(got))
|
||||
}
|
||||
if got := readLog(2); len(got) != 2 {
|
||||
t.Errorf("limit 2: got %d entries, want 2", len(got))
|
||||
}
|
||||
if got := readLog(3); len(got) != 2 {
|
||||
t.Errorf("limit 3 (blank line consumed): got %d entries, want 2", len(got))
|
||||
}
|
||||
if got := readLog(0); len(got) != 0 {
|
||||
t.Errorf("limit 0: got %d entries, want 0", len(got))
|
||||
}
|
||||
// missing file → []
|
||||
os.Remove(p)
|
||||
if got := readLog(5); len(got) != 0 {
|
||||
t.Errorf("missing file: got %d entries, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogStatsSeams — list-shaped signals counting, missing-file shape
|
||||
// (no skip_rate key), skip_rate half-even pins.
|
||||
func TestLogStatsSeams(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, "witness_log.jsonl")
|
||||
save := logPathFunc
|
||||
logPathFunc = func() string { return p }
|
||||
defer func() { logPathFunc = save }()
|
||||
|
||||
// signals as a LIST: elements become signal_types keys (oracle parity)
|
||||
line1 := `{"signals": [1, 2, 2], "reflection": ""}` + "\n"
|
||||
line2 := `{"signals": {"x": true}, "reflection": "kept"}` + "\n"
|
||||
if err := os.WriteFile(p, []byte(line1+line2), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := logStats()
|
||||
if got := s.GetInt("total"); got != 2 {
|
||||
t.Errorf("total: %d", got)
|
||||
}
|
||||
if got := s.GetInt("with_reflection"); got != 1 {
|
||||
t.Errorf("with_reflection: %d", got)
|
||||
}
|
||||
st, _ := s.Get("signal_types")
|
||||
sto := st.(*ordObj)
|
||||
for k, want := range map[string]int{"1": 1, "2": 2, "x": 1} {
|
||||
if got := sto.GetInt(k); got != want {
|
||||
t.Errorf("signal_types[%q]: got %d want %d", k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// skip_rate half-even — Python f"{x:.0f}%" on the exact binary double
|
||||
for _, tc := range []struct {
|
||||
total, wr int
|
||||
want string
|
||||
}{
|
||||
{1, 1, "0%"}, {2, 1, "50%"}, {3, 2, "33%"},
|
||||
{8, 5, "38%"}, // 37.5 → half-even → 38
|
||||
{8, 3, "62%"}, // 62.5 → half-even → 62
|
||||
{8, 7, "12%"}, // 12.5 → half-even → 12
|
||||
} {
|
||||
if got := skipRate(tc.total, tc.wr); got != tc.want {
|
||||
t.Errorf("skipRate(%d,%d): got %s want %s", tc.total, tc.wr, got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
// missing file: 3-key zero shape, NO skip_rate key
|
||||
os.Remove(p)
|
||||
s2 := logStats()
|
||||
if _, has := s2.Get("skip_rate"); has {
|
||||
t.Error("missing-file shape must not carry skip_rate")
|
||||
}
|
||||
if s2.GetInt("total") != 0 || s2.GetInt("with_reflection") != 0 {
|
||||
t.Errorf("missing-file zeros: %+v", s2)
|
||||
}
|
||||
if st, _ := s2.Get("signal_types"); st.(*ordObj).Len() != 0 {
|
||||
t.Errorf("missing-file signal_types: %+v", st)
|
||||
}
|
||||
}
|
||||
|
||||
// TestErrorTextRuneTruncation — Python str[:200] / kernel chars().take(200)
|
||||
// are char-based; multibyte error text must not split a rune.
|
||||
func TestErrorTextRuneTruncation(t *testing.T) {
|
||||
sig := detectSignals("hello", strings.Repeat("é", 250), "", nil)
|
||||
v, _ := sig.Get("error_text")
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
t.Fatalf("error_text: %v", v)
|
||||
}
|
||||
if len([]rune(s)) != 200 {
|
||||
t.Errorf("rune length: %d", len([]rune(s)))
|
||||
}
|
||||
}
|
||||
|
||||
// TestLiveNowShape — the live clock renders datetime.now(timezone.utc)
|
||||
// .isoformat() shape: microseconds always 6 digits when present, +00:00.
|
||||
func TestLiveNowShape(t *testing.T) {
|
||||
re := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?\+00:00$`)
|
||||
if !re.MatchString(liveNow()) {
|
||||
t.Errorf("liveNow shape: %q", liveNow())
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewTopicsOrder — drift's new_topics keep EXTRACTION order (the
|
||||
// kernel's cur-set iteration), never map order.
|
||||
func TestNewTopicsOrder(t *testing.T) {
|
||||
sig := detectSignals("anyway moving on to budgets", "", "", []string{"budget"})
|
||||
v, _ := sig.Get("new_topics")
|
||||
got, _ := v.([]string)
|
||||
want := []string{"anyway", "moving", "budgets"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("new_topics: %v", got)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("new_topics[%d]: got %q want %q", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user