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).
This commit is contained in:
1 parent
653fbccf70
commit
1e3967a9f0
24 files changed
+2367
-109
No files matched your search
@@ -2,16 +2,16 @@
|
||||
|
||||
_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._
|
||||
|
||||
**FULL completion: 62%** (120/192 runtime modules at FULL)
|
||||
**FULL completion: 63%** (121/192 runtime modules at FULL)
|
||||
|
||||
Function-level detail: 74% (1690/2288 runtime functions matched) — secondary; includes in-flight work
|
||||
Function-level detail: 74% (1692/2288 runtime functions matched) — secondary; includes in-flight work
|
||||
|
||||
| Status | Modules | Lines | Meaning |
|
||||
|---|---|---|---|
|
||||
| FULL | 120 | 49341 | 80%+ of functions ported |
|
||||
| PARTIAL | 33 | 14691 | 20–80% — real logic exists, gaps named below |
|
||||
| FULL | 121 | 49623 | 80%+ of functions ported |
|
||||
| PARTIAL | 32 | 14469 | 20–80% — real logic exists, gaps named below |
|
||||
| NOT TOUCHED | 39 | 11471 | no name-preserving implementation |
|
||||
| Total runtime | 192 | 75503 | |
|
||||
| Total runtime | 192 | 75563 | |
|
||||
|
||||
## NOT TOUCHED — the port queue, in priority order (largest first)
|
||||
|
||||
@@ -81,7 +81,6 @@ Function-level detail: 74% (1690/2288 runtime functions matched) — secondary;
|
||||
| state_checkpoint | 182 | 6/9 | _sck_core, _load_ledger, _save_ledger |
|
||||
| warm_neuron_store | 122 | 3/6 | _db, neuron_slugs, handle_hook |
|
||||
| inference_trace | 204 | 4/6 | record_trace, read_traces |
|
||||
| response_adapter | 222 | 5/7 | test_adapter, to_dict |
|
||||
| structural_migration | 77 | 2/4 | apply_migration, consent_brief |
|
||||
| subconscious_pass | 236 | 3/5 | main, get_recent_findings |
|
||||
| subsystem_health | 213 | 4/6 | set_auto_filer, configure_logging |
|
||||
@@ -177,6 +176,7 @@ Function-level detail: 74% (1690/2288 runtime functions matched) — secondary;
|
||||
| toolchain | 295 | 5/6 | RUST |
|
||||
| tiered_executor | 291 | 8/8 | GO |
|
||||
| salience_gate | 283 | 6/6 | RUST |
|
||||
| response_adapter | 282 | 7/7 | RUST |
|
||||
| standardized_providers | 281 | 5/5 | GO |
|
||||
| shard_manager | 276 | 9/9 | RUST |
|
||||
| auto_context | 273 | 8/8 | GO |
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
#!/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%")
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_response_adapter_truth.py — freeze the response_adapter gap truth
|
||||
(to_dict / parse_mimo_response / parse_qwen_response /
|
||||
parse_local_response / parse_plain_response / parse_response /
|
||||
test_adapter) through the SHARED harness (scripts/freeze_truth.py — one
|
||||
law).
|
||||
|
||||
The oracle fns return RuneResponse dataclasses — plain JSON cannot carry
|
||||
them — so this generator runs under the freeze rig
|
||||
(scripts/ra_freeze_seams/sitecustomize.py): pinned
|
||||
RUNE_RESPONSE_ADAPTER_CORE=python floor + to_dict-carrying wrappers (the
|
||||
parse rows freeze the parser's canonical dict form; to_dict rows ride the
|
||||
harness's recv mechanism; test_adapter freezes its None return). No
|
||||
clock/HOME seams — the module is pure (no-clock law holds by
|
||||
construction).
|
||||
|
||||
Run (from the repo root):
|
||||
PYTHONPATH=scripts/ra_freeze_seams:src \
|
||||
python3 scripts/gen_response_adapter_truth.py
|
||||
|
||||
Verify (byte-identical, same rig):
|
||||
PYTHONPATH=scripts/ra_freeze_seams:src \
|
||||
python3 scripts/freeze_truth.py --verify \
|
||||
src/rust/response_adapter/frozen_ra_gap_truth.json \
|
||||
--module rune.response_adapter
|
||||
|
||||
Every ported function and branch is exercised:
|
||||
parse_mimo_response full usage / no usage / partial usage keys /
|
||||
empty reasoning (debug_refs []), missing model,
|
||||
missing content, empty response
|
||||
parse_qwen_response one think / two thinks (reasoning_words sum) /
|
||||
no think / empty content / think-only / multiline
|
||||
think (DOTALL) with padded visible / usage+model
|
||||
missing
|
||||
parse_local_response match / no match / two "Final:" (greedy group 2) /
|
||||
multiline thinking / match without model /
|
||||
whitespace-run sandwich
|
||||
parse_plain_response provider+model / neither / provider only
|
||||
parse_response auto-detect all four shapes / explicit provider
|
||||
each / unknown provider (plain fallback) /
|
||||
explicit plain over mimo shape
|
||||
to_dict full 7-field receiver / minimal receiver
|
||||
test_adapter None return
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "scripts"))
|
||||
|
||||
from freeze_truth import freeze, write # noqa: E402
|
||||
|
||||
import sitecustomize as rig # noqa: E402 (rig-bound: pinned core + to_dict seams)
|
||||
|
||||
OUT = Path(os.environ.get("RA_TRUTH_OUT")
|
||||
or REPO / "src" / "rust" / "response_adapter" / "frozen_ra_gap_truth.json")
|
||||
|
||||
FULL_RECV = {
|
||||
"cls": "RuneResponse",
|
||||
"user_text": "visible answer",
|
||||
"work_events": [{"kind": "work", "n": 1}],
|
||||
"tool_events": [{"tool": "shell"}],
|
||||
"status_updates": ["compiling"],
|
||||
"warnings": ["deprecated call"],
|
||||
"debug_refs": [{"type": "reasoning", "content": "because"}],
|
||||
"provenance": {"provider": "mimo", "model": "mimo-v2.5-pro",
|
||||
"reasoning_tokens": 10, "completion_tokens": 8,
|
||||
"total_tokens": 18},
|
||||
}
|
||||
MIN_RECV = {
|
||||
"cls": "RuneResponse",
|
||||
"user_text": "bare",
|
||||
"work_events": [],
|
||||
"tool_events": [],
|
||||
"status_updates": [],
|
||||
"warnings": [],
|
||||
"debug_refs": [],
|
||||
"provenance": {},
|
||||
}
|
||||
|
||||
cases = [
|
||||
# ── parse_mimo_response ──────────────────────────────────────────────────
|
||||
("parse_mimo_response",
|
||||
({"reasoning_content": "Let me think about this...",
|
||||
"content": "Hello! How can I help?",
|
||||
"model": "mimo-v2.5-pro",
|
||||
"usage": {"reasoning_tokens": 10, "completion_tokens": 8,
|
||||
"total_tokens": 18}},), {}),
|
||||
("parse_mimo_response",
|
||||
({"reasoning_content": "step one",
|
||||
"content": "answer", "model": "m"},), {}), # no usage → zeros
|
||||
("parse_mimo_response",
|
||||
({"content": "answer only", "model": "m"},), {}), # empty reasoning
|
||||
("parse_mimo_response", ({"reasoning_content": "r", "content": "c"},), {}), # model missing
|
||||
("parse_mimo_response",
|
||||
({"reasoning_content": "r", "model": "m"},), {}), # content missing
|
||||
("parse_mimo_response",
|
||||
({"reasoning_content": "r", "content": "c", "model": "m",
|
||||
"usage": {"reasoning_tokens": 7}},), {}), # partial usage
|
||||
("parse_mimo_response", ({},), {}), # empty response
|
||||
# ── parse_qwen_response ──────────────────────────────────────────────────
|
||||
("parse_qwen_response",
|
||||
({"content": "<think>plan the answer</think>The answer is 42.",
|
||||
"model": "qwen3:14b",
|
||||
"usage": {"completion_tokens": 20, "total_tokens": 31}},), {}),
|
||||
("parse_qwen_response",
|
||||
({"content": "<think>one two</think>mid<think>three</think>tail",
|
||||
"model": "q"},), {}), # two thinks
|
||||
("parse_qwen_response",
|
||||
({"content": "no think blocks here", "model": "q"},), {}),
|
||||
("parse_qwen_response", ({"content": "", "model": "q"},), {}),
|
||||
("parse_qwen_response",
|
||||
({"content": "<think>only thoughts</think>"},), {}), # think-only
|
||||
("parse_qwen_response",
|
||||
({"content": "<think>line1\nline2\ttabbed</think>\n\n padded visible \n",
|
||||
"model": "qwen3"},), {}), # DOTALL + strip
|
||||
("parse_qwen_response",
|
||||
({"content": "<think>words here</think>visible"},), {}), # usage+model missing
|
||||
# ── parse_local_response ────────────────────────────────────────────────
|
||||
("parse_local_response",
|
||||
({"content": "Thinking Process: I need to respond helpfully. Final: Hello!",
|
||||
"model": "qwen2.5:3b"},), {}),
|
||||
("parse_local_response",
|
||||
({"content": "just a plain local answer", "model": "l"},), {}), # no match
|
||||
("parse_local_response",
|
||||
({"content": "Thinking Process: a Final: first Final: second"},), {}), # greedy g2
|
||||
("parse_local_response",
|
||||
({"content": "Thinking Process: multi\nline\nreasoning Final: multi\nline answer",
|
||||
"model": "l"},), {}),
|
||||
("parse_local_response",
|
||||
({"content": "Thinking Process: spaced out Final: done"},), {}), # ws sandwich
|
||||
("parse_local_response",
|
||||
({"content": "prefix Thinking Process: mid text Final: tail end",
|
||||
"model": "local-model"},), {}), # pattern mid-string
|
||||
# ── parse_plain_response ────────────────────────────────────────────────
|
||||
("parse_plain_response",
|
||||
({"content": "Hello!", "provider": "openai", "model": "gpt-4"},), {}),
|
||||
("parse_plain_response", ({"content": "Hello!"},), {}),
|
||||
("parse_plain_response", ({"content": "hi", "provider": "groq"},), {}),
|
||||
# ── parse_response: auto-detect ──────────────────────────────────────────
|
||||
("parse_response",
|
||||
({"reasoning_content": "r", "content": "c", "model": "m"},), {}),
|
||||
("parse_response", ({"content": "x <think>y</think> z", "model": "q"},), {}),
|
||||
("parse_response", ({"content": "Thinking Process: a Final: b"},), {}),
|
||||
("parse_response", ({"content": "plain words", "model": "g"},), {}),
|
||||
# ── parse_response: explicit provider (positional + kwargs forms) ────────
|
||||
("parse_response",
|
||||
({"reasoning_content": "r", "content": "c", "model": "m",
|
||||
"usage": {"total_tokens": 5}}, "mimo"), {}),
|
||||
("parse_response", ({"content": "<think>q</think>v", "model": "q"}, "qwen"), {}),
|
||||
("parse_response",
|
||||
({"content": "Thinking Process: t Final: f", "model": "l"},),
|
||||
{"provider": "local"}),
|
||||
("parse_response",
|
||||
({"content": "hello", "provider": "mistral", "model": "m"},),
|
||||
{"provider": "plain"}),
|
||||
("parse_response", ({"content": "weird shape"}, "weird"), {}), # unknown → plain
|
||||
("parse_response",
|
||||
({"reasoning_content": "r", "content": "c"},), {"provider": "plain"}),
|
||||
# ── to_dict (recv rows — post-call receiver snapshot rides expected) ────
|
||||
{"fn": "RuneResponse.to_dict", "recv": FULL_RECV, "args": [], "kwargs": {}},
|
||||
{"fn": "RuneResponse.to_dict", "recv": MIN_RECV, "args": [], "kwargs": {}},
|
||||
# ── test_adapter (None return; the prints are stdout, not truth) ────────
|
||||
("test_adapter", (), {}),
|
||||
]
|
||||
|
||||
truth = freeze("rune.response_adapter", cases,
|
||||
oracle_module="rune.response_adapter")
|
||||
write(truth, OUT)
|
||||
print(f"froze {len(truth)} cases -> {OUT}")
|
||||
@@ -0,0 +1,82 @@
|
||||
# Freeze rig for the response_adapter dichotomy gap-closing.
|
||||
# Auto-imported on interpreter start (PYTHONPATH=scripts/ra_freeze_seams:src)
|
||||
# by BOTH the truth generator (scripts/gen_response_adapter_truth.py) and
|
||||
# the shared harness's --verify mode, so frozen truth is byte-reproducible
|
||||
# in any process. Mirrors scripts/lc_freeze_seams (the closest sibling:
|
||||
# a pure-function module whose fns return DATACLASSES — plain JSON cannot
|
||||
# carry a dataclass instance, so the rig shadows each parse fn with a
|
||||
# to_dict-carrying wrapper that records the parser's canonical dict form).
|
||||
#
|
||||
# * RUNE_RESPONSE_ADAPTER_CORE is pinned to "python" — the oracle runs its
|
||||
# pure-Python floors end to end (the shared harness's _pin_oracle_cores
|
||||
# pins the same gate; the explicit pin here covers re-imports that race
|
||||
# the harness, the poisoned-truth law).
|
||||
# * TO-DICT SEAMS: the 5 parse fns return RuneResponse instances; the rig
|
||||
# shadows each with a wrapper that returns the REAL oracle fn's output
|
||||
# in to_dict form. CROSS-CALL LAW: parse_response resolves its parsers
|
||||
# dict from module globals at CALL time (the shadows), so its inner
|
||||
# results are already dicts — the wrapper passes those through instead
|
||||
# of re-wrapping. test_adapter reads .user_text off parse_response's
|
||||
# result, so its wrapper temporarily restores ALL originals (a
|
||||
# single-threaded freeze process), runs the real self-test, then
|
||||
# re-installs the shadows.
|
||||
# * No clock, no HOME, no file IO in this module's surface — no clock or
|
||||
# home sandbox needed (the no-clock law holds: no fn reads time).
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ["RUNE_RESPONSE_ADAPTER_CORE"] = "python"
|
||||
# blanket pin (the loop law from lc_freeze_seams: only vars already in the
|
||||
# env — plus the explicit pin above for this cone's own gate)
|
||||
for _k in list(os.environ):
|
||||
if _k.startswith("RUNE_") and _k.endswith("_CORE"):
|
||||
os.environ[_k] = "python"
|
||||
|
||||
_SRC = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)))), "src")
|
||||
if _SRC not in sys.path:
|
||||
sys.path.insert(0, _SRC)
|
||||
|
||||
import rune.response_adapter as ra # noqa: E402
|
||||
|
||||
# Originals captured BEFORE the seam bindings below shadow the module
|
||||
# globals — the wrappers must call the captured originals, never the
|
||||
# shadowed name.
|
||||
_ORIG = {name: getattr(ra, name) for name in (
|
||||
"parse_mimo_response", "parse_qwen_response",
|
||||
"parse_local_response", "parse_plain_response", "parse_response")}
|
||||
|
||||
|
||||
def _to_dict_carrying(name):
|
||||
orig = _ORIG[name]
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
r = orig(*args, **kwargs)
|
||||
return r.to_dict() if hasattr(r, "to_dict") else r
|
||||
|
||||
wrapper.__name__ = name
|
||||
wrapper.__wrapped__ = orig
|
||||
return wrapper
|
||||
|
||||
|
||||
_SHADOWS = {name: _to_dict_carrying(name) for name in _ORIG}
|
||||
|
||||
|
||||
_ORIG_TEST_ADAPTER = ra.test_adapter
|
||||
|
||||
|
||||
def _test_adapter_wrapper():
|
||||
saved = {n: getattr(ra, n) for n in _ORIG}
|
||||
for n in _ORIG:
|
||||
setattr(ra, n, _ORIG[n])
|
||||
try:
|
||||
return _ORIG_TEST_ADAPTER()
|
||||
finally:
|
||||
for n, w in saved.items():
|
||||
setattr(ra, n, w)
|
||||
|
||||
|
||||
for _name, _shadow in _SHADOWS.items():
|
||||
setattr(ra, _name, _shadow)
|
||||
ra.test_adapter = _test_adapter_wrapper
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust response_adapter core (dichotomy wave
|
||||
gap-close). Mirrors lc_loader.py's contract:
|
||||
|
||||
FFI contract (to_dict-value law):
|
||||
* ra_parse_response({"fn", "response", "provider"?}) -> parser to_dict form
|
||||
* ra_replay({fn, args, kwargs, recv?}) -> frozen-truth replay value
|
||||
|
||||
Override the .so with RA_RUST_LIB (a FILE path).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
_REPO = Path(__file__).resolve().parents[2]
|
||||
_CRATE_DIR = _REPO / "src" / "rust" / "response_adapter"
|
||||
_LIB_PATH = Path(os.environ.get("RA_RUST_LIB", _CRATE_DIR / "target" / "release" / "libresponse_adapter.so"))
|
||||
|
||||
_lib = None
|
||||
|
||||
_SYMBOLS = ("ra_parse_response", "ra_replay")
|
||||
|
||||
|
||||
class ResponseAdapterUnavailable(FileNotFoundError):
|
||||
"""The Rust response core is not built. Callers fall back to Python."""
|
||||
|
||||
|
||||
def available() -> bool:
|
||||
return _LIB_PATH.is_file()
|
||||
|
||||
|
||||
def _load() -> ctypes.CDLL:
|
||||
global _lib
|
||||
if _lib is not None:
|
||||
return _lib
|
||||
if not _LIB_PATH.is_file():
|
||||
raise ResponseAdapterUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in _SYMBOLS:
|
||||
fn = getattr(lib, sym)
|
||||
fn.argtypes = [ctypes.c_void_p]
|
||||
fn.restype = ctypes.c_void_p
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call(sym: str, payload: dict):
|
||||
lib = _load()
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{sym} returned null")
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
try:
|
||||
scoring = ctypes.CDLL(str(
|
||||
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
|
||||
))
|
||||
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
|
||||
scoring.cs_free_string(ptr)
|
||||
except OSError:
|
||||
pass
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
raise RuntimeError(doc.get("err", f"{sym} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def _parse(fn: str, response: dict, provider=None):
|
||||
payload = {"fn": fn, "response": response}
|
||||
if provider is not None:
|
||||
payload["provider"] = provider
|
||||
return _call("ra_parse_response", payload)
|
||||
|
||||
|
||||
def parse_mimo_response(response: dict) -> dict:
|
||||
return _parse("parse_mimo_response", response)
|
||||
|
||||
|
||||
def parse_qwen_response(response: dict) -> dict:
|
||||
return _parse("parse_qwen_response", response)
|
||||
|
||||
|
||||
def parse_local_response(response: dict) -> dict:
|
||||
return _parse("parse_local_response", response)
|
||||
|
||||
|
||||
def parse_plain_response(response: dict) -> dict:
|
||||
return _parse("parse_plain_response", response)
|
||||
|
||||
|
||||
def parse_response(response: dict, provider: str | None = None) -> dict:
|
||||
return _parse("parse_response", response, provider)
|
||||
|
||||
|
||||
def replay(case: dict):
|
||||
return _call("ra_replay", case)
|
||||
@@ -17,11 +17,27 @@ Usage:
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
def _rust_core():
|
||||
"""The Rust response core when built (dichotomy wave gap-close).
|
||||
RUNE_RESPONSE_ADAPTER_CORE=python pins the fallback."""
|
||||
if os.environ.get("RUNE_RESPONSE_ADAPTER_CORE", "auto") == "python":
|
||||
return None
|
||||
try:
|
||||
from rune import ra_loader
|
||||
except Exception: # noqa: BLE001 — any loader failure means fallback
|
||||
return None
|
||||
try:
|
||||
return ra_loader if ra_loader.available() else None
|
||||
except Exception: # noqa: BLE001 — probe must never crash the fallback
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuneResponse:
|
||||
"""Canonical response from any provider."""
|
||||
@@ -47,7 +63,16 @@ class RuneResponse:
|
||||
|
||||
|
||||
def parse_mimo_response(response: dict) -> RuneResponse:
|
||||
"""Parse MiMo response with reasoning_content + content."""
|
||||
"""Parse MiMo response with reasoning_content + content.
|
||||
|
||||
Dichotomy wave gap-close: delegates to the Rust kernel when built
|
||||
(RUNE_RESPONSE_ADAPTER_CORE=python pins the Python oracle below)."""
|
||||
core = _rust_core()
|
||||
if core is not None:
|
||||
try:
|
||||
return RuneResponse(**core.parse_mimo_response(response))
|
||||
except Exception: # noqa: BLE001 — kernel failure falls back
|
||||
pass
|
||||
reasoning = response.get("reasoning_content", "")
|
||||
content = response.get("content", "")
|
||||
|
||||
@@ -69,7 +94,16 @@ def parse_mimo_response(response: dict) -> RuneResponse:
|
||||
|
||||
|
||||
def parse_qwen_response(response: dict) -> RuneResponse:
|
||||
"""Parse Qwen response with <think>...</think> blocks."""
|
||||
"""Parse Qwen response with <think>...</think> blocks.
|
||||
|
||||
Dichotomy wave gap-close: delegates to the Rust kernel when built
|
||||
(RUNE_RESPONSE_ADAPTER_CORE=python pins the Python oracle below)."""
|
||||
core = _rust_core()
|
||||
if core is not None:
|
||||
try:
|
||||
return RuneResponse(**core.parse_qwen_response(response))
|
||||
except Exception: # noqa: BLE001 — kernel failure falls back
|
||||
pass
|
||||
content = response.get("content", "")
|
||||
|
||||
# Extract <think> blocks
|
||||
@@ -97,7 +131,16 @@ def parse_qwen_response(response: dict) -> RuneResponse:
|
||||
|
||||
|
||||
def parse_local_response(response: dict) -> RuneResponse:
|
||||
"""Parse local text model response with Thinking Process / Final."""
|
||||
"""Parse local text model response with Thinking Process / Final.
|
||||
|
||||
Dichotomy wave gap-close: delegates to the Rust kernel when built
|
||||
(RUNE_RESPONSE_ADAPTER_CORE=python pins the Python oracle below)."""
|
||||
core = _rust_core()
|
||||
if core is not None:
|
||||
try:
|
||||
return RuneResponse(**core.parse_local_response(response))
|
||||
except Exception: # noqa: BLE001 — kernel failure falls back
|
||||
pass
|
||||
content = response.get("content", "")
|
||||
|
||||
# Try to extract "Thinking Process: ... Final: ..." pattern
|
||||
@@ -128,7 +171,16 @@ def parse_local_response(response: dict) -> RuneResponse:
|
||||
|
||||
|
||||
def parse_plain_response(response: dict) -> RuneResponse:
|
||||
"""Parse non-reasoning model response (plain content)."""
|
||||
"""Parse non-reasoning model response (plain content).
|
||||
|
||||
Dichotomy wave gap-close: delegates to the Rust kernel when built
|
||||
(RUNE_RESPONSE_ADAPTER_CORE=python pins the Python oracle below)."""
|
||||
core = _rust_core()
|
||||
if core is not None:
|
||||
try:
|
||||
return RuneResponse(**core.parse_plain_response(response))
|
||||
except Exception: # noqa: BLE001 — kernel failure falls back
|
||||
pass
|
||||
content = response.get("content", "")
|
||||
|
||||
return RuneResponse(
|
||||
@@ -144,7 +196,15 @@ def parse_response(response: dict, provider: str = None) -> RuneResponse:
|
||||
"""Parse any provider response into canonical RuneResponse.
|
||||
|
||||
Auto-detects provider if not specified.
|
||||
"""
|
||||
|
||||
Dichotomy wave gap-close: delegates to the Rust kernel when built
|
||||
(RUNE_RESPONSE_ADAPTER_CORE=python pins the Python oracle below)."""
|
||||
core = _rust_core()
|
||||
if core is not None:
|
||||
try:
|
||||
return RuneResponse(**core.parse_response(response, provider))
|
||||
except Exception: # noqa: BLE001 — kernel failure falls back
|
||||
pass
|
||||
if provider is None:
|
||||
# Auto-detect from response structure
|
||||
if "reasoning_content" in response:
|
||||
|
||||
Generated
+23
@@ -11,6 +11,28 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -115,6 +137,7 @@ version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
|
||||
@@ -8,5 +8,8 @@ crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
# preserve_order: to_dict and provenance follow the oracle's insertion
|
||||
# order (the json insertion-order quirk); the default BTreeMap would
|
||||
# silently sort those keys.
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
regex = "1"
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,237 @@
|
||||
//! gap_battery — replay the FROZEN gap truth VERBATIM
|
||||
//! (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).
|
||||
//!
|
||||
//! The battery never imports the Python oracle — parity stays provable
|
||||
//! after the strangler exits. serde_json preserve_order keeps dict
|
||||
//! insertion order stable, so comparisons are ORDER-SENSITIVE (serialized
|
||||
//! forms must byte-match the frozen expected, which carries the oracle's
|
||||
//! insertion order). Branch reachability beyond the frozen corpus is
|
||||
//! proven directly (key order, auto-detect, unknown-provider dispatch,
|
||||
//! assert-parity self-test).
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
const TRUTH: &str = include_str!("../frozen_ra_gap_truth.json");
|
||||
|
||||
fn cases() -> Vec<Value> {
|
||||
serde_json::from_str::<Value>(TRUTH)
|
||||
.expect("frozen truth parses")
|
||||
.as_array()
|
||||
.cloned()
|
||||
.expect("truth is a case list (legacy list format)")
|
||||
}
|
||||
|
||||
fn case_id(c: &Value) -> String {
|
||||
c.get("fn")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("?")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn ordered(v: &Value) -> String {
|
||||
serde_json::to_string(v).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_gap_truth_replays_verbatim() {
|
||||
let mut n = 0;
|
||||
for case in cases() {
|
||||
let got = crate::dispatch(&case).expect("case dispatches");
|
||||
let want = case.get("expected").cloned().unwrap_or(Value::Null);
|
||||
assert_eq!(
|
||||
ordered(&got),
|
||||
ordered(&want),
|
||||
"case {} ({}) diverged",
|
||||
case_id(&case),
|
||||
n
|
||||
);
|
||||
n += 1;
|
||||
}
|
||||
assert!(n >= 30, "frozen corpus too thin: {n} cases");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ra_replay_ffi_roundtrip() {
|
||||
// the FFI surface returns the same dispatch computation the parity
|
||||
// script drives through ra_loader
|
||||
let case = &cases()[0];
|
||||
let payload = serde_json::to_vec(case).expect("case serializes");
|
||||
let cstr = CString::new(payload).expect("no NUL in JSON");
|
||||
let ptr = crate::ra_replay(cstr.as_ptr());
|
||||
let text = unsafe { CStr::from_ptr(ptr) }.to_str().expect("utf8");
|
||||
let env: Value = serde_json::from_str(text).expect("envelope parses");
|
||||
assert_eq!(env.get("ok"), Some(&json!(true)), "envelope ok: {text}");
|
||||
assert_eq!(
|
||||
ordered(env.get("value").unwrap_or(&Value::Null)),
|
||||
ordered(&case.get("expected").cloned().unwrap_or(Value::Null)),
|
||||
"ra_replay value must equal the frozen expected"
|
||||
);
|
||||
unsafe { drop(CString::from_raw(ptr)) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ra_parse_response_ffi_dispatches_all_parsers() {
|
||||
let lib = [
|
||||
(
|
||||
"parse_mimo_response",
|
||||
json!({"reasoning_content": "r", "content": "c", "usage": {"reasoning_tokens": 3}}),
|
||||
),
|
||||
(
|
||||
"parse_qwen_response",
|
||||
json!({"content": "<think>a b</think>ok"}),
|
||||
),
|
||||
(
|
||||
"parse_local_response",
|
||||
json!({"content": "Thinking Process: t Final: done"}),
|
||||
),
|
||||
(
|
||||
"parse_plain_response",
|
||||
json!({"content": "hi", "provider": "x", "model": "m"}),
|
||||
),
|
||||
];
|
||||
for (fn_name, response) in lib {
|
||||
let payload = json!({"fn": fn_name, "response": response});
|
||||
let cstr =
|
||||
CString::new(serde_json::to_vec(&payload).expect("payload serializes")).expect("utf8");
|
||||
let ptr = crate::ra_parse_response(cstr.as_ptr());
|
||||
let text = unsafe { CStr::from_ptr(ptr) }.to_str().expect("utf8");
|
||||
let env: Value = serde_json::from_str(text).expect("envelope parses");
|
||||
assert_eq!(env.get("ok"), Some(&json!(true)), "envelope ok: {text}");
|
||||
let v = env.get("value").expect("value present");
|
||||
assert_eq!(
|
||||
v.get("provenance")
|
||||
.and_then(|p| p.get("provider"))
|
||||
.and_then(Value::as_str),
|
||||
Some(match fn_name {
|
||||
"parse_mimo_response" => "mimo",
|
||||
"parse_qwen_response" => "qwen",
|
||||
"parse_local_response" => "local",
|
||||
_ => "x",
|
||||
}),
|
||||
"{fn_name} dispatch"
|
||||
);
|
||||
unsafe { drop(CString::from_raw(ptr)) };
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_dict_emits_oracle_key_order() {
|
||||
let r = crate::parse_mimo_response(&json!({"content": "x"}));
|
||||
let d = r.to_dict();
|
||||
let keys: Vec<&str> = d
|
||||
.as_object()
|
||||
.expect("object")
|
||||
.keys()
|
||||
.map(String::as_str)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
keys,
|
||||
vec![
|
||||
"user_text",
|
||||
"work_events",
|
||||
"tool_events",
|
||||
"status_updates",
|
||||
"warnings",
|
||||
"debug_refs",
|
||||
"provenance"
|
||||
],
|
||||
"to_dict key order must follow the dataclass field order"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provenance_insertion_order_per_parser() {
|
||||
let keys = |v: Value| -> Vec<String> {
|
||||
v.get("provenance")
|
||||
.and_then(|p| p.as_object())
|
||||
.expect("provenance object")
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
assert_eq!(
|
||||
keys(crate::parse_mimo_response(&json!({})).to_dict()),
|
||||
vec![
|
||||
"provider",
|
||||
"model",
|
||||
"reasoning_tokens",
|
||||
"completion_tokens",
|
||||
"total_tokens"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
keys(crate::parse_qwen_response(&json!({})).to_dict()),
|
||||
vec![
|
||||
"provider",
|
||||
"model",
|
||||
"reasoning_words",
|
||||
"completion_tokens",
|
||||
"total_tokens"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
keys(
|
||||
crate::parse_local_response(&json!({"content": "Thinking Process: a Final: b"}))
|
||||
.to_dict()
|
||||
),
|
||||
vec!["provider", "model", "reasoning_tokens"]
|
||||
);
|
||||
assert_eq!(
|
||||
keys(crate::parse_local_response(&json!({"content": "nope"})).to_dict()),
|
||||
vec!["provider", "model"]
|
||||
);
|
||||
assert_eq!(
|
||||
keys(crate::parse_plain_response(&json!({})).to_dict()),
|
||||
vec!["provider", "model"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_response_auto_detect_all_four() {
|
||||
let auto = |resp: Value| -> String {
|
||||
crate::parse_response(&resp, None)
|
||||
.provenance
|
||||
.get("provider")
|
||||
.and_then(Value::as_str)
|
||||
.expect("provider")
|
||||
.to_string()
|
||||
};
|
||||
assert_eq!(
|
||||
auto(json!({"reasoning_content": "r", "content": "c"})),
|
||||
"mimo",
|
||||
"reasoning_content key presence routes to mimo"
|
||||
);
|
||||
assert_eq!(auto(json!({"content": "x <think>y</think> z"})), "qwen");
|
||||
assert_eq!(
|
||||
auto(json!({"content": "Thinking Process: a Final: b"})),
|
||||
"local"
|
||||
);
|
||||
assert_eq!(auto(json!({"content": "plain words"})), "unknown",
|
||||
"plain route: parse_plain_response reads provider from the response (default unknown), not the route name");
|
||||
// explicit provider overrides the shape; unknown provider → plain
|
||||
assert_eq!(
|
||||
crate::parse_response(&json!({"reasoning_content": "r"}), Some("plain"))
|
||||
.provenance
|
||||
.get("provider"),
|
||||
Some(&json!("unknown")),
|
||||
"plain parser's provider comes from response.get('provider', 'unknown')"
|
||||
);
|
||||
assert_eq!(
|
||||
crate::parse_response(&json!({"content": "x"}), Some("weird"))
|
||||
.provenance
|
||||
.get("provider"),
|
||||
Some(&json!("unknown")),
|
||||
"unknown provider falls to the plain parser"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adapter_assert_parity() {
|
||||
// the self-test holds on the ported kernels (crash parity: an assert
|
||||
// failure panics like the oracle's AssertionError)
|
||||
assert_eq!(crate::test_adapter(), Value::Null);
|
||||
}
|
||||
@@ -1,151 +1,362 @@
|
||||
//! response_adapter — normalizes provider output (W6).
|
||||
//! PARITY port of src/rune/response_adapter.py.
|
||||
//! PARITY port of src/rune/response_adapter.py (dichotomy wave gap-close).
|
||||
//!
|
||||
//! serde_json preserve_order: to_dict and provenance emit the oracle's
|
||||
//! insertion order (the json insertion-order quirk); the default BTreeMap
|
||||
//! would silently sort those keys.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct RuneResponse {
|
||||
#[serde(default)]
|
||||
pub user_text: String,
|
||||
#[serde(default)]
|
||||
pub work_events: Vec<serde_json::Value>,
|
||||
pub work_events: Vec<Value>,
|
||||
#[serde(default)]
|
||||
pub tool_events: Vec<serde_json::Value>,
|
||||
pub tool_events: Vec<Value>,
|
||||
#[serde(default)]
|
||||
pub status_updates: Vec<serde_json::Value>,
|
||||
pub status_updates: Vec<Value>,
|
||||
#[serde(default)]
|
||||
pub warnings: Vec<serde_json::Value>,
|
||||
pub warnings: Vec<Value>,
|
||||
#[serde(default)]
|
||||
pub debug_refs: Vec<serde_json::Value>,
|
||||
pub debug_refs: Vec<Value>,
|
||||
#[serde(default)]
|
||||
pub provenance: std::collections::BTreeMap<String, serde_json::Value>,
|
||||
pub provenance: Map<String, Value>,
|
||||
}
|
||||
|
||||
pub fn parse_mimo_response(response: &serde_json::Value) -> RuneResponse {
|
||||
let content = response
|
||||
.get("content")
|
||||
.and_then(|c| c.as_str())
|
||||
impl RuneResponse {
|
||||
/// The canonical dict form — key order follows the dataclass field
|
||||
/// order (serde emits struct fields in declaration order).
|
||||
pub fn to_dict(&self) -> Value {
|
||||
serde_json::to_value(self).expect("RuneResponse serializes")
|
||||
}
|
||||
}
|
||||
|
||||
fn str_field(response: &Value, key: &str) -> String {
|
||||
response
|
||||
.get(key)
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let reasoning = response
|
||||
.get("reasoning_content")
|
||||
.and_then(|r| r.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let model = response
|
||||
.get("model")
|
||||
.and_then(|m| m.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let mut provenance = std::collections::BTreeMap::new();
|
||||
provenance.insert("provider".to_string(), serde_json::json!("mimo"));
|
||||
provenance.insert("model".to_string(), serde_json::json!(model));
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Python `response.get(key, default)` pass-through: ANY JSON value rides
|
||||
/// when the key exists (ints/bools/null included), the default otherwise.
|
||||
fn raw_field(response: &Value, key: &str, default: &str) -> Value {
|
||||
response.get(key).cloned().unwrap_or_else(|| json!(default))
|
||||
}
|
||||
|
||||
fn usage_field(usage: Option<&Value>, key: &str) -> Value {
|
||||
usage.and_then(|u| u.get(key)).cloned().unwrap_or(json!(0))
|
||||
}
|
||||
|
||||
pub fn parse_mimo_response(response: &Value) -> RuneResponse {
|
||||
let reasoning = str_field(response, "reasoning_content");
|
||||
let content = str_field(response, "content");
|
||||
let usage = response.get("usage");
|
||||
|
||||
let mut provenance = Map::new();
|
||||
provenance.insert("provider".to_string(), json!("mimo"));
|
||||
provenance.insert("model".to_string(), raw_field(response, "model", "unknown"));
|
||||
provenance.insert(
|
||||
"reasoning_tokens".to_string(),
|
||||
usage_field(usage, "reasoning_tokens"),
|
||||
);
|
||||
provenance.insert(
|
||||
"completion_tokens".to_string(),
|
||||
usage_field(usage, "completion_tokens"),
|
||||
);
|
||||
provenance.insert(
|
||||
"total_tokens".to_string(),
|
||||
usage_field(usage, "total_tokens"),
|
||||
);
|
||||
|
||||
RuneResponse {
|
||||
user_text: content,
|
||||
debug_refs: if !reasoning.is_empty() {
|
||||
vec![serde_json::json!({"type": "reasoning", "content": reasoning})]
|
||||
} else {
|
||||
debug_refs: if reasoning.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![json!({"type": "reasoning", "content": reasoning})]
|
||||
},
|
||||
provenance,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_qwen_response(response: &serde_json::Value) -> RuneResponse {
|
||||
let content = response
|
||||
.get("content")
|
||||
.and_then(|c| c.as_str())
|
||||
.unwrap_or("");
|
||||
let think_re = regex::Regex::new(r"(?s)<think>(.*?)</think>").unwrap();
|
||||
pub fn parse_qwen_response(response: &Value) -> RuneResponse {
|
||||
let content = str_field(response, "content");
|
||||
let think_re = regex::Regex::new(r"(?s)<think>(.*?)</think>").expect("think pattern");
|
||||
let think_matches: Vec<String> = think_re
|
||||
.captures_iter(content)
|
||||
.captures_iter(&content)
|
||||
.map(|c| c[1].to_string())
|
||||
.collect();
|
||||
let visible = think_re.replace_all(content, "").trim().to_string();
|
||||
let model = response
|
||||
.get("model")
|
||||
.and_then(|m| m.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let mut provenance = std::collections::BTreeMap::new();
|
||||
provenance.insert("provider".to_string(), serde_json::json!("qwen"));
|
||||
provenance.insert("model".to_string(), serde_json::json!(model));
|
||||
let visible = think_re.replace_all(&content, "").trim().to_string();
|
||||
let usage = response.get("usage");
|
||||
|
||||
let reasoning_words: usize = think_matches
|
||||
.iter()
|
||||
.map(|t| t.split_whitespace().count())
|
||||
.sum();
|
||||
|
||||
let mut provenance = Map::new();
|
||||
provenance.insert("provider".to_string(), json!("qwen"));
|
||||
provenance.insert("model".to_string(), raw_field(response, "model", "unknown"));
|
||||
provenance.insert("reasoning_words".to_string(), json!(reasoning_words));
|
||||
provenance.insert(
|
||||
"completion_tokens".to_string(),
|
||||
usage_field(usage, "completion_tokens"),
|
||||
);
|
||||
provenance.insert(
|
||||
"total_tokens".to_string(),
|
||||
usage_field(usage, "total_tokens"),
|
||||
);
|
||||
|
||||
RuneResponse {
|
||||
user_text: visible,
|
||||
debug_refs: think_matches
|
||||
.iter()
|
||||
.map(|t| serde_json::json!({"type": "think", "content": t}))
|
||||
.map(|t| json!({"type": "think", "content": t}))
|
||||
.collect(),
|
||||
provenance,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_local_response(response: &serde_json::Value) -> RuneResponse {
|
||||
let content = response
|
||||
.get("content")
|
||||
.and_then(|c| c.as_str())
|
||||
.unwrap_or("");
|
||||
let thinking_re = regex::Regex::new(r"(?s)Thinking Process:\s*(.*?)\s*Final:\s*(.*)").unwrap();
|
||||
let model = response
|
||||
.get("model")
|
||||
.and_then(|m| m.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let mut provenance = std::collections::BTreeMap::new();
|
||||
provenance.insert("provider".to_string(), serde_json::json!("local"));
|
||||
provenance.insert("model".to_string(), serde_json::json!(model));
|
||||
if let Some(m) = thinking_re.captures(content) {
|
||||
pub fn parse_local_response(response: &Value) -> RuneResponse {
|
||||
let content = str_field(response, "content");
|
||||
let thinking_re = regex::Regex::new(r"(?s)Thinking Process:\s*(.*?)\s*Final:\s*(.*)")
|
||||
.expect("thinking pattern");
|
||||
|
||||
let base_provenance = || {
|
||||
let mut p = Map::new();
|
||||
p.insert("provider".to_string(), json!("local"));
|
||||
p.insert("model".to_string(), raw_field(response, "model", "unknown"));
|
||||
p
|
||||
};
|
||||
|
||||
if let Some(m) = thinking_re.captures(&content) {
|
||||
let thinking = m[1].trim().to_string();
|
||||
let final_text = m[2].trim().to_string();
|
||||
let mut provenance = base_provenance();
|
||||
provenance.insert(
|
||||
"reasoning_tokens".to_string(),
|
||||
json!(thinking.split_whitespace().count()),
|
||||
);
|
||||
return RuneResponse {
|
||||
user_text: m[2].trim().to_string(),
|
||||
debug_refs: vec![serde_json::json!({"type": "thinking", "content": m[1].trim()})],
|
||||
user_text: final_text,
|
||||
debug_refs: vec![json!({"type": "thinking", "content": thinking})],
|
||||
provenance,
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
RuneResponse {
|
||||
user_text: content.to_string(),
|
||||
user_text: content,
|
||||
provenance: base_provenance(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_plain_response(response: &Value) -> RuneResponse {
|
||||
let content = str_field(response, "content");
|
||||
|
||||
let mut provenance = Map::new();
|
||||
provenance.insert(
|
||||
"provider".to_string(),
|
||||
raw_field(response, "provider", "unknown"),
|
||||
);
|
||||
provenance.insert("model".to_string(), raw_field(response, "model", "unknown"));
|
||||
|
||||
RuneResponse {
|
||||
user_text: content,
|
||||
provenance,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_mimo() {
|
||||
let r =
|
||||
parse_mimo_response(&json!({"content": "hello", "reasoning_content": "thinking..."}));
|
||||
assert_eq!(r.user_text, "hello");
|
||||
assert_eq!(r.debug_refs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qwen() {
|
||||
let r = parse_qwen_response(&json!({"content": "<think>reasoning</think>answer"}));
|
||||
assert_eq!(r.user_text, "answer");
|
||||
assert_eq!(r.debug_refs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local() {
|
||||
let r = parse_local_response(
|
||||
&json!({"content": "Thinking Process: analyze\nFinal: the answer"}),
|
||||
);
|
||||
assert_eq!(r.user_text, "the answer");
|
||||
pub fn parse_response(response: &Value, provider: Option<&str>) -> RuneResponse {
|
||||
let provider = match provider {
|
||||
Some(p) => p.to_string(),
|
||||
None => {
|
||||
if response.get("reasoning_content").is_some() {
|
||||
"mimo".to_string()
|
||||
} else if str_field(response, "content").contains("<think>") {
|
||||
"qwen".to_string()
|
||||
} else if str_field(response, "content").contains("Thinking Process:") {
|
||||
"local".to_string()
|
||||
} else {
|
||||
"plain".to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
match provider.as_str() {
|
||||
"mimo" => parse_mimo_response(response),
|
||||
"qwen" => parse_qwen_response(response),
|
||||
"local" => parse_local_response(response),
|
||||
_ => parse_plain_response(response),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod branch_battery {
|
||||
use super::*;
|
||||
/// The oracle self-test fn: the same four provider samples through
|
||||
/// parse_response with the same asserts (crash parity — an assert failure
|
||||
/// panics here like AssertionError aborts the oracle).
|
||||
pub fn test_adapter() -> Value {
|
||||
let mimo_response = json!({
|
||||
"reasoning_content": "Let me think about this...",
|
||||
"content": "Hello! How can I help?",
|
||||
"model": "mimo-v2.5-pro",
|
||||
"usage": {"reasoning_tokens": 10, "completion_tokens": 8, "total_tokens": 18},
|
||||
});
|
||||
let result = parse_response(&mimo_response, Some("mimo"));
|
||||
assert_eq!(result.user_text, "Hello! How can I help?");
|
||||
assert_eq!(result.debug_refs.len(), 1);
|
||||
|
||||
#[test]
|
||||
fn parse_plain_response_shape() {
|
||||
let resp =
|
||||
parse_local_response(&serde_json::json!({"content": "hello world", "model": "test"}));
|
||||
assert_eq!(resp.user_text, "hello world");
|
||||
let qwen_response = json!({
|
||||
"content": "<think>\nLet me think about this...\n</think>\n\nHello! How can I help?",
|
||||
"model": "qwen3:14b",
|
||||
"usage": {"completion_tokens": 20},
|
||||
});
|
||||
let result = parse_response(&qwen_response, Some("qwen"));
|
||||
assert_eq!(result.user_text, "Hello! How can I help?");
|
||||
assert_eq!(result.debug_refs.len(), 1);
|
||||
|
||||
let local_response = json!({"content": "Thinking Process: I need to respond helpfully. Final: Hello!", "model": "qwen2.5:3b"});
|
||||
let result = parse_response(&local_response, Some("local"));
|
||||
assert_eq!(result.user_text, "Hello!");
|
||||
|
||||
let plain_response = json!({"content": "Hello!", "model": "gpt-4"});
|
||||
let result = parse_response(&plain_response, Some("plain"));
|
||||
assert_eq!(result.user_text, "Hello!");
|
||||
|
||||
Value::Null
|
||||
}
|
||||
|
||||
// ── frozen-truth dispatch (battery + parity share this) ────────────────────
|
||||
|
||||
fn resp_arg(case: &Value) -> Result<Value, String> {
|
||||
case.get("args")
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|a| a.first().cloned())
|
||||
.ok_or_else(|| "args[0] (response) required".to_string())
|
||||
}
|
||||
|
||||
fn provider_arg(case: &Value) -> Option<String> {
|
||||
let args = case.get("args").and_then(Value::as_array);
|
||||
let kw = case.get("kwargs");
|
||||
// positional args[1] wins over kwargs["provider"]; explicit null = None
|
||||
match args
|
||||
.and_then(|a| a.get(1))
|
||||
.or_else(|| kw.and_then(|k| k.get("provider")))
|
||||
{
|
||||
Some(v) if !v.is_null() => v.as_str().map(str::to_string),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Replay one frozen-truth case ({fn, args, kwargs, recv?}) exactly the
|
||||
/// way the shared harness froze it: parse fns return the to_dict form;
|
||||
/// to_dict rows carry {result, <recv attrs…>} (the harness's post-call
|
||||
/// receiver snapshot — to_dict never mutates, so attrs mirror the input).
|
||||
pub fn dispatch(case: &Value) -> Result<Value, String> {
|
||||
let fn_name = case
|
||||
.get("fn")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| "case.fn required".to_string())?;
|
||||
match fn_name {
|
||||
"parse_mimo_response" => Ok(parse_mimo_response(&resp_arg(case)?).to_dict()),
|
||||
"parse_qwen_response" => Ok(parse_qwen_response(&resp_arg(case)?).to_dict()),
|
||||
"parse_local_response" => Ok(parse_local_response(&resp_arg(case)?).to_dict()),
|
||||
"parse_plain_response" => Ok(parse_plain_response(&resp_arg(case)?).to_dict()),
|
||||
"parse_response" => {
|
||||
let provider = provider_arg(case);
|
||||
Ok(parse_response(&resp_arg(case)?, provider.as_deref()).to_dict())
|
||||
}
|
||||
"to_dict" | "RuneResponse.to_dict" => {
|
||||
let recv = case
|
||||
.get("recv")
|
||||
.and_then(Value::as_object)
|
||||
.ok_or_else(|| "to_dict rows need recv".to_string())?;
|
||||
let mut state = Map::new();
|
||||
for (k, v) in recv {
|
||||
if k != "cls" {
|
||||
state.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
let inst: RuneResponse = serde_json::from_value(Value::Object(state))
|
||||
.map_err(|e| format!("recv rebuild: {e}"))?;
|
||||
let mut expected = Map::new();
|
||||
expected.insert("result".to_string(), inst.to_dict());
|
||||
let snapshot = serde_json::to_value(&inst).map_err(|e| e.to_string())?;
|
||||
if let Value::Object(snapshot) = snapshot {
|
||||
for (k, v) in snapshot {
|
||||
expected.insert(k, v);
|
||||
}
|
||||
}
|
||||
Ok(Value::Object(expected))
|
||||
}
|
||||
"test_adapter" => Ok(test_adapter()),
|
||||
other => Err(format!("unknown fn: {other}")),
|
||||
}
|
||||
}
|
||||
|
||||
// ── FFI ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
|
||||
if ptr.is_null() {
|
||||
return Err("null argument pointer".into());
|
||||
}
|
||||
let x = unsafe { CStr::from_ptr(ptr) }
|
||||
.to_str()
|
||||
.map_err(|e| e.to_string())?;
|
||||
serde_json::from_str(x).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn envelope(r: Result<Value, String>) -> *mut c_char {
|
||||
let v = match r {
|
||||
Ok(x) => json!({"ok": true, "value": x}),
|
||||
Err(e) => json!({"ok": false, "err": e}),
|
||||
};
|
||||
CString::new(serde_json::to_string(&v).unwrap_or_default())
|
||||
.map(|c| c.into_raw())
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
/// ra_parse_response({"fn", "response", "provider"?}) → the parser's
|
||||
/// to_dict form (the facade's delegation entry).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ra_parse_response(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).and_then(|a| {
|
||||
let fn_name = a
|
||||
.get("fn")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| "arg.fn required".to_string())?
|
||||
.to_string();
|
||||
let response = a
|
||||
.get("response")
|
||||
.cloned()
|
||||
.ok_or_else(|| "arg.response required".to_string())?;
|
||||
let case = if a.get("provider").is_some() {
|
||||
json!({"fn": fn_name, "args": [response, a.get("provider").cloned().unwrap_or(Value::Null)]})
|
||||
} else {
|
||||
json!({"fn": fn_name, "args": [response]})
|
||||
};
|
||||
dispatch(&case)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// ra_replay(case) — the frozen-truth replay surface: {fn, args, kwargs,
|
||||
/// recv?} → the frozen expected form. The parity script drives this.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ra_replay(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).and_then(|a| dispatch(&a));
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
// NOTE: no free-symbol export (capability_scoring's cs_free_string serves
|
||||
// the whole process — duplicate symbols break the cdylib link).
|
||||
|
||||
#[cfg(test)]
|
||||
mod gap_battery;
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,84 @@
|
||||
# response_adapter dichotomy gap-close — verification summary
|
||||
|
||||
Module: response_adapter (audit 5/7 PARTIAL → 7/7 FULL, RUST).
|
||||
Worktree: rune-wave-dg. Parity commit: see `git log` feat(response_adapter).
|
||||
|
||||
## Port surface (name-preserving, src/rust/response_adapter/src/lib.rs)
|
||||
- `RuneResponse` + `to_dict` — ordered dict form (serde struct field order
|
||||
= dataclass field order; serde_json preserve_order keeps provenance /
|
||||
debug_refs insertion order — the json insertion-order quirk).
|
||||
- `parse_mimo_response` — FULL parity this pass: usage pass-through fields
|
||||
reasoning_tokens/completion_tokens/total_tokens (Python `.get(k, 0)`
|
||||
semantics — ANY JSON value rides, not just ints), model pass-through.
|
||||
- `parse_qwen_response` — reasoning_words (whitespace-run word count sum),
|
||||
completion_tokens/total_tokens; (?s) lazy think-block regex identical
|
||||
capture semantics.
|
||||
- `parse_local_response` — greedy group-2 (text after the FIRST `Final:`
|
||||
to end of string), `\s*` sandwich trims, reasoning_tokens word count.
|
||||
- `parse_plain_response` — NOW A REAL FN (previously only a test name in
|
||||
the corpus — the audit's substring match over-credited it); provider
|
||||
pass-through from the response (default "unknown").
|
||||
- `parse_response` — NOW A REAL FN: auto-detect (reasoning_content key
|
||||
presence → mimo; "<think>" substring → qwen; "Thinking Process:" →
|
||||
local; else plain), explicit provider dispatch, unknown provider →
|
||||
plain parser.
|
||||
- `test_adapter` — assert-parity self-test (same four samples, same
|
||||
asserts; panic ≅ AssertionError crash parity).
|
||||
|
||||
## FFI + facade
|
||||
- cdylib exports `ra_parse_response` ({fn, response, provider?} → parser
|
||||
to_dict form) and `ra_replay` (frozen-truth case → expected form).
|
||||
cs_free_string (capability_scoring) frees — no duplicate free symbol.
|
||||
- `src/rune/ra_loader.py` (ctypes loader, RA_RUST_LIB override) +
|
||||
facade delegation in `src/rune/response_adapter.py` for the 5 parse
|
||||
fns with `RUNE_RESPONSE_ADAPTER_CORE=python` pin; `to_dict` /
|
||||
`test_adapter` stay Python on the facade (pure projection / stdout
|
||||
contract — W6 FFI-overhead law); kernel failures fall back, never
|
||||
crash the caller.
|
||||
|
||||
## Truth (shared harness + rig)
|
||||
- 36-case corpus `src/rust/response_adapter/frozen_ra_gap_truth.json`
|
||||
via scripts/freeze_truth.py + scripts/ra_freeze_seams rig (pinned
|
||||
RUNE_RESPONSE_ADAPTER_CORE=python; to_dict-carrying wrappers; the
|
||||
cross-call law — parse_response resolves its parser dict from module
|
||||
globals at call time and test_adapter reads .user_text, so the rig
|
||||
restores originals around the self-test). No clock/HOME seams (module
|
||||
is pure — no-clock law holds by construction).
|
||||
- Battery `gap_battery.rs`: frozen corpus embedded VERBATIM, replayed
|
||||
ORDER-SENSITIVE (serialized forms byte-match), 7 tests.
|
||||
|
||||
## Gates (evidence: verification/ra-*.exit, logs local *.log gitignored)
|
||||
- ra-freeze-verify-1/2.exit = 0 — `freeze_truth.py --verify` byte-identical, twice
|
||||
- ra-cargo-test.exit = 0 — battery 7/7 (36-case verbatim replay + FFI
|
||||
roundtrip + order/auto-detect/assert branch proofs)
|
||||
- ra-clippy.exit = 0 — clippy --all-targets -D warnings clean
|
||||
- ra-fmt.exit = 0 — cargo fmt --check clean
|
||||
- ra-build.exit = 0 — cargo build --release (cdylib builds)
|
||||
- ra-parity.exit = 0 — scripts/check_response_adapter_parity.py:
|
||||
FROZEN 36/36 verbatim through ra_replay (ordered compare) + FUZZ 2000
|
||||
op comparisons seed 2479 (unicode, floats, nulls, nested tags,
|
||||
whitespace runs) + PINS 10 facade calls identical under
|
||||
RUNE_RESPONSE_ADAPTER_CORE=python vs live delegation
|
||||
- ra-pysmoke-{python,auto}.exit = 0 — contracts run_all_contracts
|
||||
response_adapter PASS + 7-fn facade smoke, both pins
|
||||
- ra-pytest-{python,auto}.exit = 0 — tests/test_contracts_port.py green
|
||||
both pins (11 skipped: the contracts crate itself not built — vacuous
|
||||
for this module)
|
||||
- ra-audit.exit = 0 — dichotomy_audit.py --write: response_adapter
|
||||
7/7 FULL RUST; programme FULL 110/192
|
||||
|
||||
## Divergences (documented, out-of-contract inputs only)
|
||||
1. Non-string `content`/`reasoning_content`: Python passes the raw value
|
||||
through into user_text (dataclass untyped); Rust coerces to "" (the
|
||||
struct field is String). Provider responses carry string content per
|
||||
the module contract; fuzz corpus keeps strings.
|
||||
2. Non-dict `usage` (e.g. a list): Python raises AttributeError at
|
||||
`usage.get`; Rust treats it as absent (zeros). Crash parity for this
|
||||
class is documented rather than reproduced — the frozen corpus keeps
|
||||
dict/missing usage only.
|
||||
3. Rust `str::trim` (Unicode White_Space) vs Python `str.strip()`
|
||||
(isspace(): also \x1c-\x1f file separators). Frozen corpus and fuzz
|
||||
alphabet use standard whitespace; the boundary chars are control-code
|
||||
noise, not provider output.
|
||||
4. Facade `to_dict`/`test_adapter` do not delegate (W6 FFI-overhead
|
||||
law); the Rust counterparts exist and are battery/parity-proven.
|
||||
Reference in new issue
Block a user