Files
rune/scripts/check_response_adapter_parity.py
T
Nick 1e3967a9f0 feat(response_adapter): close PARTIAL gaps — dichotomy wave
audit 5/7 PARTIAL -> 7/7 FULL (RUST side). Ported name-preserving:
to_dict (ordered dict form — serde struct field order = dataclass field
order, serde_json preserve_order keeps provenance/debug_refs insertion
order), parse_plain_response + parse_response as REAL fns (the audit's
substring match over-credited a test name and a doc mention — the
dispatcher's auto-detect and the plain parser did not exist), and
test_adapter assert-parity. REAL parity bugs fixed on the existing
parsers: parse_mimo_response now carries usage reasoning_tokens/
completion_tokens/total_tokens and parse_qwen_response
reasoning_words/completion_tokens/total_tokens with Python .get(k, 0)
pass-through semantics (ANY JSON value rides), parse_local_response
reasoning_tokens on the match branch; model/provider pass-through raw.

Truth: 36-case corpus (frozen_ra_gap_truth.json) via the shared
freeze_truth.py harness + scripts/ra_freeze_seams rig (pinned
RUNE_RESPONSE_ADAPTER_CORE=python; to_dict-carrying wrappers; CROSS-CALL
law — parse_response resolves its parser dict from module globals at
call time and test_adapter reads .user_text off the result, so the rig
restores originals around the self-test; no clock/HOME seams: the module
is pure). --verify byte-identical twice. Battery gap_battery 7/7 (36-case
VERBATIM order-sensitive replay + FFI roundtrip + key-order/auto-detect/
assert branch proofs). FFI: ra_parse_response + ra_replay cdylib entries
(cs_free_string serves the process); facade delegation in
src/rune/response_adapter.py + ra_loader.py with RUNE_RESPONSE_ADAPTER_
CORE=python pin (to_dict/test_adapter stay Python on the facade — pure
projection / stdout contract, W6 FFI-overhead law). Live parity 100%
(scripts/check_response_adapter_parity.py: FROZEN 36/36 ordered compare
through ra_replay, FUZZ 2000 ops seed 2479 incl. unicode/floats/nulls/
nested tags, PINS 10 facade calls identical under both pins); clippy -D
warnings + fmt + cargo build --release clean; contracts PASS + 7-fn
facade smoke + test_contracts_port.py green BOTH pins. Divergences
documented in verification/ra-summary.md (non-str content coercion,
non-dict usage, trim/strip control-char boundary).

Evidence: verification/ra-*.exit + ra-summary.md (logs local, *.log
gitignored).
2026-10-14 16:40:25 +01:00

213 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""Live parity: response_adapter Rust core vs Python oracle (dichotomy
wave, close-PARTIAL gap pass). Layers:
1. FROZEN — the gap truth corpus (src/rust/response_adapter/
frozen_ra_gap_truth.json, generated by
scripts/gen_response_adapter_truth.py against the LIVE Python oracle
through the scripts/ra_freeze_seams rig: pinned
RUNE_RESPONSE_ADAPTER_CORE=python floor, to_dict-carrying wrappers)
replayed through the ra_replay ctypes FFI entry of the built cdylib,
case by case, with ORDER-SENSITIVE comparison (json.dumps preserves
the insertion order on both sides — a key-order divergence fails).
2. FUZZ — seeded random responses (seed 2479): random content built
from token fragments including <think>/</think>, Thinking Process:/
Final:, unicode, whitespace runs; random usage dicts (ints, floats,
nulls), model/provider strings. The oracle's parse fns (pin ON, pure
Python) vs the Rust core through the FFI, exact ordered-dict compare
of the to_dict forms, across explicit and auto providers.
3. PINS — dual-mode facade battery: the same facade calls executed with
RUNE_RESPONSE_ADAPTER_CORE=python (oracle floor) and with the Rust
core live (delegation ON); results must be identical (the facade
both-pins law).
Exit 0 only at 100% parity.
"""
import json
import os
import random
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
CRATE = REPO / "src" / "rust" / "response_adapter"
FROZEN = CRATE / "frozen_ra_gap_truth.json"
os.environ["RUNE_RESPONSE_ADAPTER_CORE"] = "python" # oracle floor to start
sys.path.insert(0, str(REPO / "src"))
import rune.response_adapter as ra # noqa: E402
import rune.ra_loader as core # noqa: E402
if not core.available():
sys.exit(f"FATAL: Rust core not built — cargo build --release in {CRATE}")
TRUTH = json.loads(FROZEN.read_text())
fails: list[str] = []
counts = {"frozen": 0, "fuzz": 0, "pins": 0}
def fail(layer, name, detail):
fails.append(f"[{layer}] {name}: {detail}")
def ordered(obj) -> str:
return json.dumps(obj, ensure_ascii=False, separators=(", ", ": "))
def frozen_layer() -> None:
for i, case in enumerate(TRUTH):
try:
got = core.replay(case)
except Exception as e: # noqa: BLE001
fail("FROZEN", f"case{i}:{case['fn']}", f"FFI error {e}")
continue
want = case["expected"]
if ordered(got) != ordered(want):
fail("FROZEN", f"case{i}:{case['fn']}",
f"\n rust: {ordered(got)[:200]}\n want: {ordered(want)[:200]}")
else:
counts["frozen"] += 1
FRAGMENTS = [
"<think>", "</think>", "Thinking Process:", "Final:", "hello", "wörld",
"答案", "🜏", " ", "\n", "\t", "a b", "x", "", "Final", "<think >",
"process:", "think", "…", "\r\n", " three spaces ", "Final: ",
]
MODELS = ["mimo-v2.5-pro", "qwen3:14b", "", "モデル", None]
PROVIDERS = [None, "mimo", "qwen", "local", "plain", "weird"]
def rand_response(rng) -> dict:
content = "".join(rng.choice(FRAGMENTS)
for _ in range(rng.randint(0, 14)))
resp: dict = {"content": content}
m = rng.choice(MODELS)
if m is not None:
resp["model"] = m
if rng.random() < 0.5:
resp["reasoning_content"] = "".join(
rng.choice(FRAGMENTS) for _ in range(rng.randint(0, 6)))
if rng.random() < 0.5:
usage: dict = {}
for key in ("reasoning_tokens", "completion_tokens", "total_tokens"):
r = rng.random()
if r < 0.4:
usage[key] = rng.randint(0, 5000)
elif r < 0.5:
usage[key] = float(rng.randint(0, 90)) / 3
elif r < 0.55:
usage[key] = None
resp["usage"] = usage
if rng.random() < 0.3:
resp["provider"] = rng.choice(["openai", "groq", ""])
return resp
def fuzz_layer(n_seed: int = 400) -> None:
rng = random.Random(2479)
parse_fns = [
("parse_mimo_response", ra.parse_mimo_response,
core.parse_mimo_response),
("parse_qwen_response", ra.parse_qwen_response,
core.parse_qwen_response),
("parse_local_response", ra.parse_local_response,
core.parse_local_response),
("parse_plain_response", ra.parse_plain_response,
core.parse_plain_response),
]
for i in range(n_seed):
resp = rand_response(rng)
for name, py_fn, rs_fn in parse_fns:
want = py_fn(dict(resp)).to_dict()
try:
got = rs_fn(dict(resp))
except Exception as e: # noqa: BLE001
fail("FUZZ", f"{i}:{name}", f"FFI error {e}")
continue
if ordered(got) != ordered(want):
fail("FUZZ", f"{i}:{name}",
f"resp={json.dumps(resp, ensure_ascii=False)}\n"
f" rust: {ordered(got)[:220]}\n want: {ordered(want)[:220]}")
else:
counts["fuzz"] += 1
provider = rng.choice(PROVIDERS)
want = ra.parse_response(dict(resp), provider).to_dict()
try:
got = core.parse_response(dict(resp), provider)
except Exception as e: # noqa: BLE001
fail("FUZZ", f"{i}:parse_response", f"FFI error {e}")
continue
if ordered(got) != ordered(want):
fail("FUZZ", f"{i}:parse_response",
f"resp={json.dumps(resp, ensure_ascii=False)} provider={provider}\n"
f" rust: {ordered(got)[:220]}\n want: {ordered(want)[:220]}")
else:
counts["fuzz"] += 1
PIN_SNIPPET = r"""
import json, os, sys
sys.path.insert(0, "src")
import rune.response_adapter as ra
samples = [
{"reasoning_content": "think hard", "content": "mimo answer",
"model": "mimo-v2.5-pro",
"usage": {"reasoning_tokens": 4, "completion_tokens": 3, "total_tokens": 7}},
{"content": "<think>one two three</think>\n\n the answer ",
"model": "qwen3:14b", "usage": {"completion_tokens": 20}},
{"content": "Thinking Process: reason it out\nline two Final: final text",
"model": "qwen2.5:3b"},
{"content": "plain words", "provider": "openai", "model": "gpt-4"},
{"content": "auto <think>x</think> shape"},
{"content": "auto Thinking Process: y Final: z"},
]
out = [ra.parse_response(s).to_dict() for s in samples]
out += [ra.parse_mimo_response(samples[0]).to_dict(),
ra.parse_qwen_response(samples[1]).to_dict(),
ra.parse_local_response(samples[2]).to_dict(),
ra.parse_plain_response(samples[3]).to_dict()]
print(json.dumps(out, ensure_ascii=False))
"""
def pins_layer() -> None:
results = {}
for pin in ("python", "rust"):
env = dict(os.environ)
env.pop("RUNE_RESPONSE_ADAPTER_CORE", None)
if pin == "python":
env["RUNE_RESPONSE_ADAPTER_CORE"] = "python"
env["RA_RUST_LIB"] = str(CRATE / "target" / "release" /
"libresponse_adapter.so")
r = subprocess.run([sys.executable, "-c", PIN_SNIPPET],
cwd=str(REPO), env=env, capture_output=True,
text=True, timeout=120)
if r.returncode != 0:
fail("PINS", pin, f"snippet failed:\n{r.stderr[-400:]}")
return
results[pin] = r.stdout
if results["python"] != results["rust"]:
fail("PINS", "both-pins", "python-pinned vs live-core outputs differ")
else:
counts["pins"] = len(json.loads(results["python"]))
if __name__ == "__main__":
frozen_layer()
fuzz_layer()
pins_layer()
print(f"FROZEN: {counts['frozen']}/{len(TRUTH)} cases verbatim through ra_replay")
print(f"FUZZ: {counts['fuzz']} op comparisons (seed 2479)")
print(f"PINS: {counts['pins']} facade calls identical under both pins")
if fails:
print(f"\n{len(fails)} FAILURES (first 8):")
for f in fails[:8]:
print(" -", f)
sys.exit(1)
print("PARITY 100%")