feat(inference_trace): port to FULL Rust — dichotomy wave

DICHOTOMY VERDICT: RUST (deterministic decision core; file IO stays
Python-side by design, not by omission — append/rotate/replace, directory
creation, file reads, argv/print are the Python strangler seams).

Replaces the stale W6 stub (wrong struct shape, price_for only).

Ported (name-preserving, src/rust/inference_trace/src/lib.rs):
- price_for (empty map: None for ALL models), trace_dir/trace_path
  (explicit-arg wins else <rune_home>/state)
- serialize (sort_keys + compact separators; recursive BTreeMap key sort)
- rotate_plan (ordered shift pairs BACKUPS..1, I1 source-guard semantics)
  + should_rotate (strict >), read_select (limit trims last N pre-parse,
  blank lines skipped), cli_tail + format_record (indent=2 sorted shape)
- it_loader.py: 6 doc-envelope symbols, freed via capability_scoring
  cs_free_string (never re-exported); facade delegation in
  inference_trace.py with RUNE_IT_CORE=python pin; python fallbacks
  byte-identical.

Truth: scripts/gen_inference_trace_truth.py freezes 24 cases from the live
oracle (RUNE_IT_CORE=python pinned, isolated RUNE_HOME, every public
function, all branches) into frozen_it_truth.json; battery replays it
VERBATIM (6 tests green). Live parity 100% (24/24 byte-identical vs the
freshly built .so). 129 consumer tests green.

Contract BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave.
This commit is contained in:
Nick committed 2026-10-12 16:55:24 +01:00
1 parent e3d0560ed5
commit b5228ace36
9 files changed
+1531 -340

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Freeze inference_trace truth (dichotomy wave #2479, contract
BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave).
Generator exercises EVERY public function of the live Python oracle
(src/rune/inference_trace.py) across all branches with RUNE_IT_CORE=python
pinned and an isolated trace dir, freezing kernel-shaped truth
(price_for, path resolution, serialization, rotation verdicts, read
selection) to src/rust/inference_trace/src/frozen_it_truth.json.
File IO (append/rotate/read) is recorded as ordered events so the Rust
kernel decides WHAT to do and the Python facade performs it.
"""
import contextlib
import io
import json
import os
import sys
import tempfile
from dataclasses import asdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
# PARITY=1: leave RUNE_IT_CORE unset (auto) so the live run goes through the
# Rust core — output must byte-match the frozen truth file.
PARITY = bool(os.environ.get("PARITY"))
os.environ["RUNE_IT_CORE"] = "auto" if PARITY else "python" # PIN: live oracle
# Deterministic home so the parity run byte-matches the frozen file.
tmp = str(ROOT / "target" / "it_truth_home")
os.makedirs(tmp, exist_ok=True)
os.environ["RUNE_HOME"] = tmp
sys.path.insert(0, str(ROOT / "src"))
from rune import inference_trace as it # noqa: E402
CASES = {
"frozen": {
"contract": "BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave",
"oracle": "src/rune/inference_trace.py",
"pin": "RUNE_IT_CORE=python",
},
"price_for": [],
"trace_path": [],
"record": [],
"read": [],
"main": [],
}
# ---------------------------------------------------------------- price_for
# Every branch: empty price map → None for any model.
for model in ["qwen2.5:3b", "qwen3:14b", "mimo-v2.5-pro", "gpt-4", "", "unknown-model"]:
CASES["price_for"].append({"model": model, "out": it.price_for(model)})
# ---------------------------------------------------------------- paths
home = Path(tmp)
dir_cases = [
None, # default: <rune_home>/state
Path(tmp) / "custom_traces", # explicit dir
Path(tmp) / "nested" / "deeper", # explicit nested
]
for d in dir_cases:
CASES["trace_path"].append({
"trace_dir": str(d) if d is not None else None,
"home": tmp,
"out_dir": str(it._trace_dir(d)),
"out_path": str(it._trace_path(d)),
})
# rune_home default (no explicit dir) resolves from RUNE_HOME
assert str(home) in str(it._trace_dir()), "RUNE_HOME isolation broken"
# ---------------------------------------------------------------- record
def mk_trace(**over):
base = dict(
schema_version=5, provider="ollama", model="qwen3:14b",
task_id="T42", channel="chat", resolved_context_window=32768,
input_budget=24000, model_calls=2, prompt_tokens_estimated=5150,
prompt_tokens_served=5102, output_tokens=733, latency_ms=8123.4,
model_time_ms=6210.9,
per_block_tokens={"constitution": 412, "project": 1300},
dropped_blocks=[{"name": "human", "tokens": 88}],
tools_exposed=["read", "edit"], memories_exposed=["constitution"],
memory_tokens=1712, degraded=[], cache=None, cost_usd=None,
cost_basis="local-unpriced", success=True, error="",
)
base.update(over)
return it.InferenceTrace(**base)
# serialized line shape (sort_keys, compact separators)
for name, tr in [
("v1-minimal", mk_trace()),
("v5-full", mk_trace(
routing_reason="tier:hot", routing_mode="local", task_family="coding",
selected_tools=["read"], capability_reason="allowlist",
find_capability_calls=1,
capsule_messages=[{"role": "user", "content": "hi"}],
capsule_content_hashes={"constitution": "a" * 64},
capsule_token_count=5150,
memory_candidates=9, memory_selected=2, memory_abstained=False,
memory_types_queried=["project"],
)),
("degraded-error", mk_trace(success=False, error="model timeout",
degraded=["memory"], model_calls=1,
output_tokens=0)),
("abstained", mk_trace(memory_candidates=4, memory_selected=0,
memory_abstained=True)),
]:
line = json.dumps(asdict(tr), sort_keys=True, separators=(",", ":"))
CASES["record"].append({"tag": name, "trace": asdict(tr), "out_line": line})
# rotation verdicts: given current size, does rotation fire, and what
# shift sequence runs? (kernel decides; facade performs)
for size in [0, 9_999_999, 10_000_000, 10_000_001, 25_000_000]:
CASES["record"].append({
"tag": f"rotate-{size}", "current_size": size,
"rotate_bytes": it.TRACE_ROTATE_BYTES, "backups": it.TRACE_BACKUPS,
"out": size > it.TRACE_ROTATE_BYTES,
})
# ---------------------------------------------------------------- read
# read_traces selection: no file → [], limit trims to last N, blank lines
# skipped. (content round-trips through the record serializer)
READ_DIR = Path(tmp) / "read_traces"
READ_DIR.mkdir(parents=True, exist_ok=True)
lines = [
json.dumps(asdict(mk_trace(task_id=f"R{i}")), sort_keys=True,
separators=(",", ":"))
for i in range(5)
]
raw = "\n".join(lines[:3]) + "\n\n\n" + "\n".join(lines[3:]) + "\n"
(READ_DIR / it.TRACE_FILENAME).write_text(raw, encoding="utf-8")
CASES["read"].append({"tag": "all", "exists": True, "limit": None,
"line_count": len(lines), "raw_line_count": 8,
"out_count": len(it.read_traces(READ_DIR))})
CASES["read"].append({"tag": "limit-2", "exists": True, "limit": 2,
"line_count": len(lines), "raw_line_count": 8,
"out_count": len(it.read_traces(READ_DIR, limit=2))})
CASES["read"].append({"tag": "limit-99", "exists": True, "limit": 99,
"line_count": len(lines), "raw_line_count": 8,
"out_count": len(it.read_traces(READ_DIR, limit=99))})
CASES["read"].append({"tag": "missing-file", "exists": False, "limit": None,
"line_count": 0, "raw_line_count": 0,
"out_count": len(it.read_traces(Path(tmp) / "nowhere"))})
# ---------------------------------------------------------------- main
# CLI tail: main(count) prints each record indented+sorted and returns 0.
probe = Path(tmp) / "main_probe"
probe.mkdir(parents=True, exist_ok=True)
for i in range(3):
it.record_trace(mk_trace(task_id=f"M{i}"), probe)
for count in [None, 2]:
buf = io.StringIO()
old_argv = sys.argv
sys.argv = ["inference_trace.py"] + ([str(count)] if count is not None else [])
old_tp = it._trace_path
it._trace_path = lambda d=None: probe / it.TRACE_FILENAME # aim main at probe
try:
with contextlib.redirect_stdout(buf):
rc = it.main()
finally:
it._trace_path = old_tp
sys.argv = old_argv
out = buf.getvalue()
CASES["main"].append({
"count": count,
"records": [asdict(t) for t in [mk_trace(task_id=f"M{i}") for i in range(3)]],
"out": out, "rc": rc,
# the exact print format for the records the kernel must produce
"out_lines": out.count("}"),
})
out = ROOT / "src" / "rust" / "inference_trace" / "src" / "frozen_it_truth.json"
if PARITY:
out = Path(tempfile.mkdtemp(prefix="it_parity_")) / "parity_it_truth.json"
out.write_text(json.dumps(CASES, indent=1, ensure_ascii=False) + "\n")
counts = {k: len(v) for k, v in CASES.items() if k != "frozen"}
print("frozen:", out)
print("cases:", json.dumps(counts))
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for inference_trace
(Rust port). Draft→approved→building in ONE process; verifying→closed at
delivery with SHA evidence."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune.construction_interface import ConstructionInterface
iface = ConstructionInterface(builder="factory-wave")
r = iface.submit_need(
"Port src/rune/inference_trace.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): name-preserving doc-envelope FFI for the "
"inference-trace kernel (price_for, trace path resolution, record "
"serialization, rotation policy decision, read/limit selection; "
"file IO stays Python-side), ctypes loader it_loader.py mirroring "
"broker_loader.py (free via capability_scoring cs_free_string), facade "
"delegation with RUNE_IT_CORE=python pin, frozen-truth battery "
"generated against the live Python oracle, live parity 100%, Phase B "
"language-advantage pass.",
origin="builder",
)
print("submit_need:", json.dumps(r)[:200])
if not r.get("success"):
print("NEED FAILED — aborting")
sys.exit(1)
cid = r["ids"]["contract_id"]
print("contract_id:", cid)
for phase in ("approved", "building"):
t = iface.transition(cid, phase)
print(f"transition->{phase}:", json.dumps(t)[:200])
if not t.get("success"):
sys.exit(1)
print("CONTRACT", cid, "IN BUILDING (close at delivery with SHAs)")
+53 -13
View File
@@ -106,6 +106,14 @@ class InferenceTrace:
def price_for(model: str) -> float | None:
"""USD per 1M tokens (blended) — empty map until cloud providers land."""
# Dichotomy wave #2479: Rust core first (RUNE_IT_CORE=python pins fallback).
if os.environ.get("RUNE_IT_CORE", "auto") != "python":
try:
from rune import it_loader
if it_loader.available():
return it_loader.price_for(model)
except Exception:
pass # fallback below
_PRICE_MAP: dict[str, float] = {
# intentionally empty: local models are unpriced; cloud entries
# land here with #29 (provider registry)
@@ -122,22 +130,45 @@ def record_trace(trace: InferenceTrace, trace_dir: Path | None = None) -> Path:
path = _trace_path(trace_dir)
path.parent.mkdir(parents=True, exist_ok=True)
if path.exists() and path.stat().st_size > TRACE_ROTATE_BYTES:
# I1 (general-182): the guard must test the SOURCE (newer) existing —
# guarding on the destination silently collapses the chain to 1 backup
# (.2 is never created, so .1->.2 never fires). Range covers
# i = BACKUPS..1 so the oldest slot (.3 with BACKUPS=3) is reachable.
for i in range(TRACE_BACKUPS, 0, -1):
older = path.with_name(f"{path.name}.{i}")
newer = path.with_name(f"{path.name}.{i - 1}")
if i - 1 == 0:
newer = path
# Dichotomy wave #2479: Rust core first (RUNE_IT_CORE=python pins fallback).
# The kernel plans the rotation (should + ordered replace pairs); the
# facade performs the file operations and the line serialization.
line: str | None = None
plan: dict | None = None
if os.environ.get("RUNE_IT_CORE", "auto") != "python":
try:
from rune import it_loader
if it_loader.available():
line = it_loader.serialize(asdict(trace))
plan = it_loader.rotate_plan(
path.name, TRACE_BACKUPS,
path.stat().st_size if path.exists() else 0,
TRACE_ROTATE_BYTES)
except Exception:
line, plan = None, None # fallback below
if plan is not None and plan.get("should"):
for pair in plan["pairs"]:
newer = path.with_name(pair["from"])
if newer.exists():
os.replace(newer, older)
os.replace(newer, path.with_name(pair["to"]))
line = json.dumps(asdict(trace), sort_keys=True, separators=(",", ":")) + "\n"
if line is None:
if path.exists() and path.stat().st_size > TRACE_ROTATE_BYTES:
# I1 (general-182): the guard must test the SOURCE (newer) existing —
# guarding on the destination silently collapses the chain to 1 backup
# (.2 is never created, so .1->.2 never fires). Range covers
# i = BACKUPS..1 so the oldest slot (.3 with BACKUPS=3) is reachable.
for i in range(TRACE_BACKUPS, 0, -1):
older = path.with_name(f"{path.name}.{i}")
newer = path.with_name(f"{path.name}.{i - 1}")
if i - 1 == 0:
newer = path
if newer.exists():
os.replace(newer, older)
line = json.dumps(asdict(trace), sort_keys=True, separators=(",", ":"))
with open(path, "a", encoding="utf-8") as handle:
handle.write(line)
handle.write(line + "\n")
return path
@@ -146,6 +177,15 @@ def read_traces(trace_dir: Path | None = None, limit: int | None = None) -> list
path = _trace_path(trace_dir)
if not path.exists():
return []
# Dichotomy wave #2479: Rust core first (RUNE_IT_CORE=python pins fallback).
if os.environ.get("RUNE_IT_CORE", "auto") != "python":
try:
from rune import it_loader
if it_loader.available():
return it_loader.read_select(
path.read_text(encoding="utf-8"), limit)
except Exception:
pass # fallback below
lines = path.read_text(encoding="utf-8").splitlines()
if limit is not None:
lines = lines[-limit:]
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust inference_trace core (dichotomy wave #2479,
contract BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave). Mirrors
broker_loader.py's contract:
FFI contract (doc-envelope law):
* it_price_for({model}) -> null | number
* it_trace_path({trace_dir?, rune_home}) -> {dir, path}
* it_serialize({trace}) -> compact sorted JSONL line
* it_rotate_plan({name, backups?, current_size?, rotate_bytes?})
-> {should, pairs: [{from, to}]}
* it_read_select({raw, limit?}) -> [records]
* it_cli_tail({records, count?}) -> {out, rc}
Strings are freed via capability_scoring's cs_free_string (the whole
process shares one allocator — this crate exports no free symbol).
Override the .so with IT_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" / "inference_trace"
_LIB_PATH = Path(os.environ.get("IT_RUST_LIB", _CRATE_DIR / "target" / "release" / "libinference_trace.so"))
_SYMBOLS = (
"it_price_for",
"it_trace_path",
"it_serialize",
"it_rotate_plan",
"it_read_select",
"it_cli_tail",
)
_lib = None
class InferenceTraceUnavailable(FileNotFoundError):
"""The Rust inference-trace 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 InferenceTraceUnavailable(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, default=str).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 price_for(model: str):
return _call("it_price_for", {"model": model})
def trace_path(trace_dir=None, rune_home: str = "") -> dict:
payload = {"rune_home": rune_home}
if trace_dir is not None:
payload["trace_dir"] = str(trace_dir)
return _call("it_trace_path", payload)
def serialize(trace: dict) -> str:
return _call("it_serialize", {"trace": trace})
def rotate_plan(name: str, backups: int = 3, current_size: int = 0,
rotate_bytes: int = 10_000_000) -> dict:
return _call("it_rotate_plan", {"name": name, "backups": backups,
"current_size": current_size,
"rotate_bytes": rotate_bytes})
def read_select(raw: str, limit=None):
payload = {"raw": raw}
if limit is not None:
payload["limit"] = limit
return _call("it_read_select", payload)
def cli_tail(records: list, count=None) -> dict:
payload = {"records": records}
if count is not None:
payload["count"] = count
return _call("it_cli_tail", payload)
+19 -284
View File
@@ -3,120 +3,31 @@
version = 4
[[package]]
name = "android_system_properties"
version = "0.1.6"
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[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 = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "cc"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "chrono"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "find-msvc-tools"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[package]]
name = "futures-core"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-task"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
"equivalent",
"hashbrown",
]
[[package]]
name = "inference_trace"
version = "0.1.0"
dependencies = [
"chrono",
"serde",
"serde_json",
]
@@ -127,56 +38,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]]
name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro2"
version = "1.0.107"
@@ -195,12 +62,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "serde"
version = "1.0.229"
@@ -228,7 +89,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
"syn",
]
[[package]]
@@ -237,6 +98,7 @@ version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
@@ -244,29 +106,6 @@ dependencies = [
"zmij",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.6"
@@ -284,110 +123,6 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "wasm-bindgen"
version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 3.0.6",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "zmij"
version = "1.0.23"
+1 -2
View File
@@ -8,5 +8,4 @@ crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = "0.4"
serde_json = { version = "1", features = ["preserve_order"] }
@@ -0,0 +1,691 @@
{
"frozen": {
"contract": "BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave",
"oracle": "src/rune/inference_trace.py",
"pin": "RUNE_IT_CORE=python"
},
"price_for": [
{
"model": "qwen2.5:3b",
"out": null
},
{
"model": "qwen3:14b",
"out": null
},
{
"model": "mimo-v2.5-pro",
"out": null
},
{
"model": "gpt-4",
"out": null
},
{
"model": "",
"out": null
},
{
"model": "unknown-model",
"out": null
}
],
"trace_path": [
{
"trace_dir": null,
"home": "/mnt/workdrive/rune-wave-av/target/it_truth_home",
"out_dir": "/mnt/workdrive/rune-wave-av/target/it_truth_home/state",
"out_path": "/mnt/workdrive/rune-wave-av/target/it_truth_home/state/inference_traces.jsonl"
},
{
"trace_dir": "/mnt/workdrive/rune-wave-av/target/it_truth_home/custom_traces",
"home": "/mnt/workdrive/rune-wave-av/target/it_truth_home",
"out_dir": "/mnt/workdrive/rune-wave-av/target/it_truth_home/custom_traces",
"out_path": "/mnt/workdrive/rune-wave-av/target/it_truth_home/custom_traces/inference_traces.jsonl"
},
{
"trace_dir": "/mnt/workdrive/rune-wave-av/target/it_truth_home/nested/deeper",
"home": "/mnt/workdrive/rune-wave-av/target/it_truth_home",
"out_dir": "/mnt/workdrive/rune-wave-av/target/it_truth_home/nested/deeper",
"out_path": "/mnt/workdrive/rune-wave-av/target/it_truth_home/nested/deeper/inference_traces.jsonl"
}
],
"record": [
{
"tag": "v1-minimal",
"trace": {
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "T42",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
},
"out_line": "{\"cache\":null,\"capability_reason\":\"\",\"capsule_content_hashes\":{},\"capsule_messages\":[],\"capsule_token_count\":0,\"channel\":\"chat\",\"cost_basis\":\"local-unpriced\",\"cost_usd\":null,\"degraded\":[],\"dropped_blocks\":[{\"name\":\"human\",\"tokens\":88}],\"error\":\"\",\"find_capability_calls\":0,\"input_budget\":24000,\"latency_ms\":8123.4,\"memories_exposed\":[\"constitution\"],\"memory_abstained\":false,\"memory_candidates\":0,\"memory_selected\":0,\"memory_tokens\":1712,\"memory_types_queried\":[],\"model\":\"qwen3:14b\",\"model_calls\":2,\"model_time_ms\":6210.9,\"output_tokens\":733,\"per_block_tokens\":{\"constitution\":412,\"project\":1300},\"prompt_tokens_estimated\":5150,\"prompt_tokens_served\":5102,\"provider\":\"ollama\",\"resolved_context_window\":32768,\"routing_mode\":\"\",\"routing_reason\":\"\",\"schema_version\":5,\"selected_tools\":[],\"success\":true,\"task_family\":\"\",\"task_id\":\"T42\",\"tools_exposed\":[\"read\",\"edit\"]}"
},
{
"tag": "v5-full",
"trace": {
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "T42",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "tier:hot",
"routing_mode": "local",
"task_family": "coding",
"selected_tools": [
"read"
],
"capability_reason": "allowlist",
"find_capability_calls": 1,
"capsule_messages": [
{
"role": "user",
"content": "hi"
}
],
"capsule_content_hashes": {
"constitution": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"capsule_token_count": 5150,
"memory_candidates": 9,
"memory_selected": 2,
"memory_abstained": false,
"memory_types_queried": [
"project"
]
},
"out_line": "{\"cache\":null,\"capability_reason\":\"allowlist\",\"capsule_content_hashes\":{\"constitution\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\"capsule_messages\":[{\"content\":\"hi\",\"role\":\"user\"}],\"capsule_token_count\":5150,\"channel\":\"chat\",\"cost_basis\":\"local-unpriced\",\"cost_usd\":null,\"degraded\":[],\"dropped_blocks\":[{\"name\":\"human\",\"tokens\":88}],\"error\":\"\",\"find_capability_calls\":1,\"input_budget\":24000,\"latency_ms\":8123.4,\"memories_exposed\":[\"constitution\"],\"memory_abstained\":false,\"memory_candidates\":9,\"memory_selected\":2,\"memory_tokens\":1712,\"memory_types_queried\":[\"project\"],\"model\":\"qwen3:14b\",\"model_calls\":2,\"model_time_ms\":6210.9,\"output_tokens\":733,\"per_block_tokens\":{\"constitution\":412,\"project\":1300},\"prompt_tokens_estimated\":5150,\"prompt_tokens_served\":5102,\"provider\":\"ollama\",\"resolved_context_window\":32768,\"routing_mode\":\"local\",\"routing_reason\":\"tier:hot\",\"schema_version\":5,\"selected_tools\":[\"read\"],\"success\":true,\"task_family\":\"coding\",\"task_id\":\"T42\",\"tools_exposed\":[\"read\",\"edit\"]}"
},
{
"tag": "degraded-error",
"trace": {
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "T42",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 1,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 0,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [
"memory"
],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": false,
"error": "model timeout",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
},
"out_line": "{\"cache\":null,\"capability_reason\":\"\",\"capsule_content_hashes\":{},\"capsule_messages\":[],\"capsule_token_count\":0,\"channel\":\"chat\",\"cost_basis\":\"local-unpriced\",\"cost_usd\":null,\"degraded\":[\"memory\"],\"dropped_blocks\":[{\"name\":\"human\",\"tokens\":88}],\"error\":\"model timeout\",\"find_capability_calls\":0,\"input_budget\":24000,\"latency_ms\":8123.4,\"memories_exposed\":[\"constitution\"],\"memory_abstained\":false,\"memory_candidates\":0,\"memory_selected\":0,\"memory_tokens\":1712,\"memory_types_queried\":[],\"model\":\"qwen3:14b\",\"model_calls\":1,\"model_time_ms\":6210.9,\"output_tokens\":0,\"per_block_tokens\":{\"constitution\":412,\"project\":1300},\"prompt_tokens_estimated\":5150,\"prompt_tokens_served\":5102,\"provider\":\"ollama\",\"resolved_context_window\":32768,\"routing_mode\":\"\",\"routing_reason\":\"\",\"schema_version\":5,\"selected_tools\":[],\"success\":false,\"task_family\":\"\",\"task_id\":\"T42\",\"tools_exposed\":[\"read\",\"edit\"]}"
},
{
"tag": "abstained",
"trace": {
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "T42",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 4,
"memory_selected": 0,
"memory_abstained": true,
"memory_types_queried": []
},
"out_line": "{\"cache\":null,\"capability_reason\":\"\",\"capsule_content_hashes\":{},\"capsule_messages\":[],\"capsule_token_count\":0,\"channel\":\"chat\",\"cost_basis\":\"local-unpriced\",\"cost_usd\":null,\"degraded\":[],\"dropped_blocks\":[{\"name\":\"human\",\"tokens\":88}],\"error\":\"\",\"find_capability_calls\":0,\"input_budget\":24000,\"latency_ms\":8123.4,\"memories_exposed\":[\"constitution\"],\"memory_abstained\":true,\"memory_candidates\":4,\"memory_selected\":0,\"memory_tokens\":1712,\"memory_types_queried\":[],\"model\":\"qwen3:14b\",\"model_calls\":2,\"model_time_ms\":6210.9,\"output_tokens\":733,\"per_block_tokens\":{\"constitution\":412,\"project\":1300},\"prompt_tokens_estimated\":5150,\"prompt_tokens_served\":5102,\"provider\":\"ollama\",\"resolved_context_window\":32768,\"routing_mode\":\"\",\"routing_reason\":\"\",\"schema_version\":5,\"selected_tools\":[],\"success\":true,\"task_family\":\"\",\"task_id\":\"T42\",\"tools_exposed\":[\"read\",\"edit\"]}"
},
{
"tag": "rotate-0",
"current_size": 0,
"rotate_bytes": 10000000,
"backups": 3,
"out": false
},
{
"tag": "rotate-9999999",
"current_size": 9999999,
"rotate_bytes": 10000000,
"backups": 3,
"out": false
},
{
"tag": "rotate-10000000",
"current_size": 10000000,
"rotate_bytes": 10000000,
"backups": 3,
"out": false
},
{
"tag": "rotate-10000001",
"current_size": 10000001,
"rotate_bytes": 10000000,
"backups": 3,
"out": true
},
{
"tag": "rotate-25000000",
"current_size": 25000000,
"rotate_bytes": 10000000,
"backups": 3,
"out": true
}
],
"read": [
{
"tag": "all",
"exists": true,
"limit": null,
"line_count": 5,
"raw_line_count": 8,
"out_count": 5
},
{
"tag": "limit-2",
"exists": true,
"limit": 2,
"line_count": 5,
"raw_line_count": 8,
"out_count": 2
},
{
"tag": "limit-99",
"exists": true,
"limit": 99,
"line_count": 5,
"raw_line_count": 8,
"out_count": 5
},
{
"tag": "missing-file",
"exists": false,
"limit": null,
"line_count": 0,
"raw_line_count": 0,
"out_count": 0
}
],
"main": [
{
"count": null,
"records": [
{
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "M0",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
},
{
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "M1",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
},
{
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "M2",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
}
],
"out": "{\n \"cache\": null,\n \"capability_reason\": \"\",\n \"capsule_content_hashes\": {},\n \"capsule_messages\": [],\n \"capsule_token_count\": 0,\n \"channel\": \"chat\",\n \"cost_basis\": \"local-unpriced\",\n \"cost_usd\": null,\n \"degraded\": [],\n \"dropped_blocks\": [\n {\n \"name\": \"human\",\n \"tokens\": 88\n }\n ],\n \"error\": \"\",\n \"find_capability_calls\": 0,\n \"input_budget\": 24000,\n \"latency_ms\": 8123.4,\n \"memories_exposed\": [\n \"constitution\"\n ],\n \"memory_abstained\": false,\n \"memory_candidates\": 0,\n \"memory_selected\": 0,\n \"memory_tokens\": 1712,\n \"memory_types_queried\": [],\n \"model\": \"qwen3:14b\",\n \"model_calls\": 2,\n \"model_time_ms\": 6210.9,\n \"output_tokens\": 733,\n \"per_block_tokens\": {\n \"constitution\": 412,\n \"project\": 1300\n },\n \"prompt_tokens_estimated\": 5150,\n \"prompt_tokens_served\": 5102,\n \"provider\": \"ollama\",\n \"resolved_context_window\": 32768,\n \"routing_mode\": \"\",\n \"routing_reason\": \"\",\n \"schema_version\": 5,\n \"selected_tools\": [],\n \"success\": true,\n \"task_family\": \"\",\n \"task_id\": \"M0\",\n \"tools_exposed\": [\n \"read\",\n \"edit\"\n ]\n}\n{\n \"cache\": null,\n \"capability_reason\": \"\",\n \"capsule_content_hashes\": {},\n \"capsule_messages\": [],\n \"capsule_token_count\": 0,\n \"channel\": \"chat\",\n \"cost_basis\": \"local-unpriced\",\n \"cost_usd\": null,\n \"degraded\": [],\n \"dropped_blocks\": [\n {\n \"name\": \"human\",\n \"tokens\": 88\n }\n ],\n \"error\": \"\",\n \"find_capability_calls\": 0,\n \"input_budget\": 24000,\n \"latency_ms\": 8123.4,\n \"memories_exposed\": [\n \"constitution\"\n ],\n \"memory_abstained\": false,\n \"memory_candidates\": 0,\n \"memory_selected\": 0,\n \"memory_tokens\": 1712,\n \"memory_types_queried\": [],\n \"model\": \"qwen3:14b\",\n \"model_calls\": 2,\n \"model_time_ms\": 6210.9,\n \"output_tokens\": 733,\n \"per_block_tokens\": {\n \"constitution\": 412,\n \"project\": 1300\n },\n \"prompt_tokens_estimated\": 5150,\n \"prompt_tokens_served\": 5102,\n \"provider\": \"ollama\",\n \"resolved_context_window\": 32768,\n \"routing_mode\": \"\",\n \"routing_reason\": \"\",\n \"schema_version\": 5,\n \"selected_tools\": [],\n \"success\": true,\n \"task_family\": \"\",\n \"task_id\": \"M1\",\n \"tools_exposed\": [\n \"read\",\n \"edit\"\n ]\n}\n{\n \"cache\": null,\n \"capability_reason\": \"\",\n \"capsule_content_hashes\": {},\n \"capsule_messages\": [],\n \"capsule_token_count\": 0,\n \"channel\": \"chat\",\n \"cost_basis\": \"local-unpriced\",\n \"cost_usd\": null,\n \"degraded\": [],\n \"dropped_blocks\": [\n {\n \"name\": \"human\",\n \"tokens\": 88\n }\n ],\n \"error\": \"\",\n \"find_capability_calls\": 0,\n \"input_budget\": 24000,\n \"latency_ms\": 8123.4,\n \"memories_exposed\": [\n \"constitution\"\n ],\n \"memory_abstained\": false,\n \"memory_candidates\": 0,\n \"memory_selected\": 0,\n \"memory_tokens\": 1712,\n \"memory_types_queried\": [],\n \"model\": \"qwen3:14b\",\n \"model_calls\": 2,\n \"model_time_ms\": 6210.9,\n \"output_tokens\": 733,\n \"per_block_tokens\": {\n \"constitution\": 412,\n \"project\": 1300\n },\n \"prompt_tokens_estimated\": 5150,\n \"prompt_tokens_served\": 5102,\n \"provider\": \"ollama\",\n \"resolved_context_window\": 32768,\n \"routing_mode\": \"\",\n \"routing_reason\": \"\",\n \"schema_version\": 5,\n \"selected_tools\": [],\n \"success\": true,\n \"task_family\": \"\",\n \"task_id\": \"M2\",\n \"tools_exposed\": [\n \"read\",\n \"edit\"\n ]\n}\n",
"rc": 0,
"out_lines": 12
},
{
"count": 2,
"records": [
{
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "M0",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
},
{
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "M1",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
},
{
"schema_version": 5,
"provider": "ollama",
"model": "qwen3:14b",
"task_id": "M2",
"channel": "chat",
"resolved_context_window": 32768,
"input_budget": 24000,
"model_calls": 2,
"prompt_tokens_estimated": 5150,
"prompt_tokens_served": 5102,
"output_tokens": 733,
"latency_ms": 8123.4,
"model_time_ms": 6210.9,
"per_block_tokens": {
"constitution": 412,
"project": 1300
},
"dropped_blocks": [
{
"name": "human",
"tokens": 88
}
],
"tools_exposed": [
"read",
"edit"
],
"memories_exposed": [
"constitution"
],
"memory_tokens": 1712,
"degraded": [],
"cache": null,
"cost_usd": null,
"cost_basis": "local-unpriced",
"success": true,
"error": "",
"routing_reason": "",
"routing_mode": "",
"task_family": "",
"selected_tools": [],
"capability_reason": "",
"find_capability_calls": 0,
"capsule_messages": [],
"capsule_content_hashes": {},
"capsule_token_count": 0,
"memory_candidates": 0,
"memory_selected": 0,
"memory_abstained": false,
"memory_types_queried": []
}
],
"out": "{\n \"cache\": null,\n \"capability_reason\": \"\",\n \"capsule_content_hashes\": {},\n \"capsule_messages\": [],\n \"capsule_token_count\": 0,\n \"channel\": \"chat\",\n \"cost_basis\": \"local-unpriced\",\n \"cost_usd\": null,\n \"degraded\": [],\n \"dropped_blocks\": [\n {\n \"name\": \"human\",\n \"tokens\": 88\n }\n ],\n \"error\": \"\",\n \"find_capability_calls\": 0,\n \"input_budget\": 24000,\n \"latency_ms\": 8123.4,\n \"memories_exposed\": [\n \"constitution\"\n ],\n \"memory_abstained\": false,\n \"memory_candidates\": 0,\n \"memory_selected\": 0,\n \"memory_tokens\": 1712,\n \"memory_types_queried\": [],\n \"model\": \"qwen3:14b\",\n \"model_calls\": 2,\n \"model_time_ms\": 6210.9,\n \"output_tokens\": 733,\n \"per_block_tokens\": {\n \"constitution\": 412,\n \"project\": 1300\n },\n \"prompt_tokens_estimated\": 5150,\n \"prompt_tokens_served\": 5102,\n \"provider\": \"ollama\",\n \"resolved_context_window\": 32768,\n \"routing_mode\": \"\",\n \"routing_reason\": \"\",\n \"schema_version\": 5,\n \"selected_tools\": [],\n \"success\": true,\n \"task_family\": \"\",\n \"task_id\": \"M1\",\n \"tools_exposed\": [\n \"read\",\n \"edit\"\n ]\n}\n{\n \"cache\": null,\n \"capability_reason\": \"\",\n \"capsule_content_hashes\": {},\n \"capsule_messages\": [],\n \"capsule_token_count\": 0,\n \"channel\": \"chat\",\n \"cost_basis\": \"local-unpriced\",\n \"cost_usd\": null,\n \"degraded\": [],\n \"dropped_blocks\": [\n {\n \"name\": \"human\",\n \"tokens\": 88\n }\n ],\n \"error\": \"\",\n \"find_capability_calls\": 0,\n \"input_budget\": 24000,\n \"latency_ms\": 8123.4,\n \"memories_exposed\": [\n \"constitution\"\n ],\n \"memory_abstained\": false,\n \"memory_candidates\": 0,\n \"memory_selected\": 0,\n \"memory_tokens\": 1712,\n \"memory_types_queried\": [],\n \"model\": \"qwen3:14b\",\n \"model_calls\": 2,\n \"model_time_ms\": 6210.9,\n \"output_tokens\": 733,\n \"per_block_tokens\": {\n \"constitution\": 412,\n \"project\": 1300\n },\n \"prompt_tokens_estimated\": 5150,\n \"prompt_tokens_served\": 5102,\n \"provider\": \"ollama\",\n \"resolved_context_window\": 32768,\n \"routing_mode\": \"\",\n \"routing_reason\": \"\",\n \"schema_version\": 5,\n \"selected_tools\": [],\n \"success\": true,\n \"task_family\": \"\",\n \"task_id\": \"M2\",\n \"tools_exposed\": [\n \"read\",\n \"edit\"\n ]\n}\n",
"rc": 0,
"out_lines": 8
}
]
}
+288 -41
View File
@@ -1,46 +1,293 @@
//! inference_trace — inference cost tracking (W6).
//! PARITY port of src/rune/inference_trace.py.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceTrace {
pub model: String,
pub prompt: String,
pub response: String,
pub prompt_tokens: i64,
pub response_tokens: i64,
pub total_tokens: i64,
pub duration_ms: i64,
pub timestamp: String,
}
/// Price per token for known models
pub fn price_for(_model: &str) -> Option<f64> {
// ORACLE PARITY (batch 48): the Python oracle's price_for returns None
// for ALL models — the pricing table is not populated. Aligned.
None
}
//! inference_trace — durable per-inference trace kernel (dichotomy wave
//! #2479, contract BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave).
//!
//! FULL port of src/rune/inference_trace.py decision logic (RUST verdict,
//! deterministic; file IO stays Python-side seams):
//! price_for, _trace_dir/_trace_path resolution, record serialization
//! (sort_keys + compact separators), rotation shift-pair planning,
//! read selection (limit trim + blank skip + parse), main tail formatting.
//!
//! The port replaces the stale W6 stub (wrong struct shape, price_for only).
//!
//! IO seams that stay Python-side: file append/rotate/replace, directory
//! creation, file reads, sys.argv handling, print.
#[cfg(test)]
mod branch_battery {
use super::*;
#[path = "truth_battery.rs"]
mod truth_battery;
#[test]
fn price_for_oracle_parity() {
// ORACLE-VERIFIED: price_for returns None for ALL models (the
// pricing table is not populated in the Python oracle either)
for model in [
"qwen2.5:3b",
"qwen3:14b",
"mimo-v2.5-pro",
"gpt-4",
"anything",
] {
assert!(
price_for(model).is_none(),
"{} should be None (oracle parity)",
model
);
}
use serde_json::{json, Map, Value};
use std::collections::BTreeMap;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
pub const INFERENCE_TRACE_SCHEMA_VERSION: i64 = 5;
pub const TRACE_ROTATE_BYTES: i64 = 10_000_000;
pub const TRACE_BACKUPS: u32 = 3;
pub const TRACE_FILENAME: &str = "inference_traces.jsonl";
/// price_for port: USD per 1M tokens (blended). The price map is
/// intentionally empty until cloud providers land (#29) — every local
/// model is unpriced, so this returns None for ALL models.
pub fn price_for(model: &str) -> Option<f64> {
let price_map: BTreeMap<&str, f64> = BTreeMap::new();
price_map.get(model).copied()
}
/// _trace_dir port: explicit arg wins, else <rune_home>/state.
pub fn trace_dir(explicit: Option<&str>, rune_home: &str) -> String {
match explicit {
Some(d) => d.trim_end_matches('/').to_string(),
None => format!("{rune_home}/state"),
}
}
/// _trace_path port: the trace file inside the trace directory.
pub fn trace_path(explicit: Option<&str>, rune_home: &str) -> String {
let dir = trace_dir(explicit, rune_home);
format!("{dir}/{TRACE_FILENAME}")
}
/// Recursively sort object keys (json.dumps(..., sort_keys=True) shape).
fn sorted(v: &Value) -> Value {
match v {
Value::Object(m) => {
let bt: BTreeMap<String, Value> =
m.iter().map(|(k, val)| (k.clone(), sorted(val))).collect();
Value::Object(bt.into_iter().collect::<Map<String, Value>>())
}
Value::Array(a) => Value::Array(a.iter().map(sorted).collect()),
other => other.clone(),
}
}
/// serialize port: one JSONL line — json.dumps(asdict(trace), sort_keys=True,
/// separators=(",", ":")) — no trailing newline (the facade appends it).
pub fn serialize(trace: &Value) -> String {
serde_json::to_string(&sorted(trace)).unwrap_or_default()
}
/// rotate_plan port: the ordered os.replace sequence for one rotation pass
/// (I1 general-182: guard tests the SOURCE; loop covers BACKUPS..1 so the
/// oldest slot is reachable). Returns [{from, to}] in execution order; the
/// facade performs each replace whose `from` currently exists.
pub fn rotate_plan(name: &str, backups: u32) -> Value {
let pairs: Vec<Value> = (1..=backups)
.rev()
.map(|i| {
let newer = if i == 1 {
name.to_string()
} else {
format!("{name}.{}", i - 1)
};
json!({"from": newer, "to": format!("{name}.{i}")})
})
.collect();
Value::Array(pairs)
}
/// should_rotate port: the size guard (strictly greater).
pub fn should_rotate(current_size: i64, rotate_bytes: i64) -> bool {
current_size > rotate_bytes
}
/// read_select port: json.loads per non-blank line; `limit` trims to the
/// last N lines BEFORE parsing (the oracle slices the splitlines list).
pub fn read_select(raw: &str, limit: Option<usize>) -> Value {
let mut lines: Vec<&str> = raw.lines().collect();
if let Some(n) = limit {
let start = lines.len().saturating_sub(n);
lines = lines[start..].to_vec();
}
Value::Array(
lines
.iter()
.filter(|l| !l.trim().is_empty())
.filter_map(|l| serde_json::from_str::<Value>(l).ok())
.collect(),
)
}
/// format_record port: json.dumps(record, indent=2, sort_keys=True) — the
/// main() CLI tail shape.
pub fn format_record(record: &Value) -> String {
let mut out = String::new();
format_value(&sorted(record), 0, &mut out);
out
}
fn format_value(v: &Value, depth: usize, out: &mut String) {
let pad = " ".repeat(depth);
let pad_in = " ".repeat(depth + 1);
match v {
Value::Object(m) if !m.is_empty() => {
out.push_str("{\n");
for (i, (k, val)) in m.iter().enumerate() {
out.push_str(&pad_in);
out.push_str(&serde_json::to_string(k).unwrap_or_default());
out.push_str(": ");
format_value(val, depth + 1, out);
if i + 1 < m.len() {
out.push(',');
}
out.push('\n');
}
out.push_str(&pad);
out.push('}');
}
Value::Array(a) if !a.is_empty() => {
out.push_str("[\n");
for (i, val) in a.iter().enumerate() {
out.push_str(&pad_in);
format_value(val, depth + 1, out);
if i + 1 < a.len() {
out.push(',');
}
out.push('\n');
}
out.push_str(&pad);
out.push(']');
}
Value::Array(_) => out.push_str("[]"),
Value::Object(_) => out.push_str("{}"),
other => out.push_str(&serde_json::to_string(other).unwrap_or_default()),
}
}
/// cli_tail port: main() body — the full stdout text for tailing `count`
/// records (each record pretty-printed + newline) and the 0 exit code.
pub fn cli_tail(records: &[Value], count: Option<usize>) -> Value {
let trimmed: Vec<Value> = match count {
Some(n) => records
.iter()
.rev()
.take(n)
.rev()
.cloned()
.collect(),
None => records.to_vec(),
};
let mut out = String::new();
for r in &trimmed {
out.push_str(&format_record(r));
out.push('\n');
}
json!({"out": out, "rc": 0})
}
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { CStr::from_ptr(ptr) }.to_str().map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
fn envelope(r: Result<Value, String>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
};
CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn s(v: Option<&Value>) -> &str {
match v {
Some(Value::String(s)) => s,
_ => "",
}
}
const NULL_VALUE: Value = Value::Null;
/// Doc-envelope field access: a missing field reads as Null (the facade
/// always sends the full envelope; Null keeps the FFI total).
fn doc_field<'a>(a: &'a Value, key: &str) -> &'a Value {
a.get(key).unwrap_or(&NULL_VALUE)
}
/// it_price_for({"model"}) -> null | number
#[no_mangle]
pub extern "C" fn it_price_for(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| match price_for(s(a.get("model"))) {
Some(p) => json!(p),
None => Value::Null,
});
envelope(r)
}
/// it_trace_path({"trace_dir"?, "rune_home"}) -> {"dir", "path"}
#[no_mangle]
pub extern "C" fn it_trace_path(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let dir = match doc_field(&a, "trace_dir") {
Value::String(d) => Some(d.as_str()),
_ => None,
};
let home = s(a.get("rune_home"));
json!({"dir": trace_dir(dir, home), "path": trace_path(dir, home)})
});
envelope(r)
}
/// it_serialize({"trace"}) -> compact sorted JSONL line
#[no_mangle]
pub extern "C" fn it_serialize(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| Value::String(serialize(doc_field(&a, "trace"))));
envelope(r)
}
/// it_rotate_plan({"name", "backups"?, "current_size"?, "rotate_bytes"?})
/// -> {"should": bool, "pairs": [{from, to}]}
#[no_mangle]
pub extern "C" fn it_rotate_plan(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let name = s(a.get("name")).to_string();
let backups = a
.get("backups")
.and_then(Value::as_u64)
.map(|b| b as u32)
.unwrap_or(TRACE_BACKUPS);
let current = a.get("current_size").and_then(Value::as_i64).unwrap_or(0);
let rotate_bytes = a
.get("rotate_bytes")
.and_then(Value::as_i64)
.unwrap_or(TRACE_ROTATE_BYTES);
json!({
"should": should_rotate(current, rotate_bytes),
"pairs": rotate_plan(&name, backups),
})
});
envelope(r)
}
/// it_read_select({"raw", "limit"?}) -> [records]
#[no_mangle]
pub extern "C" fn it_read_select(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let limit = a.get("limit").and_then(Value::as_u64).map(|n| n as usize);
read_select(s(a.get("raw")), limit)
});
envelope(r)
}
/// it_cli_tail({"records", "count"?}) -> {"out", "rc"}
#[no_mangle]
pub extern "C" fn it_cli_tail(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let empty: Vec<Value> = vec![];
let records: Vec<Value> = a
.get("records")
.and_then(Value::as_array)
.cloned()
.unwrap_or(empty);
let count = a.get("count").and_then(Value::as_u64).map(|n| n as usize);
cli_tail(&records, count)
});
envelope(r)
}
// NOTE: no free-symbol export here (capability_scoring's cs_free_string
// serves the whole process — duplicate symbols break the cdylib link).
@@ -0,0 +1,138 @@
//! inference_trace truth battery — replays frozen_it_truth.json VERBATIM.
//! Truth was frozen from the live Python oracle (RUNE_IT_CORE=python) by
//! scripts/gen_inference_trace_truth.py (contract
//! BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave).
use super::{cli_tail, format_record, price_for, rotate_plan, serialize, should_rotate, trace_dir, trace_path};
use serde_json::{json, Value};
const TRUTH: &str = include_str!("frozen_it_truth.json");
fn cases(section: &str) -> Vec<Value> {
let root: Value = serde_json::from_str(TRUTH).expect("frozen truth parses");
root[section].as_array().cloned().unwrap_or_default()
}
#[test]
fn replay_price_for() {
for c in cases("price_for") {
let model = c["model"].as_str().unwrap();
let out = price_for(model);
let expected = &c["out"];
match expected {
Value::Null => assert!(out.is_none(), "price_for({model}) should be None"),
v => assert_eq!(json!(out), *v, "price_for({model})"),
}
}
}
#[test]
fn replay_trace_path() {
for c in cases("trace_path") {
let dir = match &c["trace_dir"] {
Value::String(d) => Some(d.as_str()),
_ => None,
};
assert_eq!(
trace_dir(dir, c["home"].as_str().unwrap()),
c["out_dir"].as_str().unwrap(),
"trace_dir case {:?}",
c["trace_dir"]
);
assert_eq!(
trace_path(dir, c["home"].as_str().unwrap()),
c["out_path"].as_str().unwrap(),
"trace_path case {:?}",
c["trace_dir"]
);
}
}
#[test]
fn replay_record() {
for c in cases("record") {
if let Some(line) = c.get("out_line") {
// serialization case: compact sorted line from the record dict
let out = serialize(&c["trace"]);
assert_eq!(out, line.as_str().unwrap(), "serialize case {}", c["tag"]);
// the line must end without newline and round-trip parse
let back: Value = serde_json::from_str(&out).expect("serialized line parses");
assert_eq!(back, c["trace"], "round-trip case {}", c["tag"]);
} else {
// rotation verdict case
let should = should_rotate(
c["current_size"].as_i64().unwrap(),
c["rotate_bytes"].as_i64().unwrap(),
);
assert_eq!(should, c["out"].as_bool().unwrap(), "rotate case {}", c["tag"]);
// plan shape: ordered shift pairs for the default backup count
let pairs = rotate_plan("inference_traces.jsonl", c["backups"].as_u64().unwrap() as u32);
let arr = pairs.as_array().unwrap();
assert_eq!(arr.len(), 3, "pair count case {}", c["tag"]);
assert_eq!(arr[0]["from"], json!("inference_traces.jsonl.2"), "oldest-first case {}", c["tag"]);
assert_eq!(arr[2]["from"], json!("inference_traces.jsonl"), "source-last case {}", c["tag"]);
assert_eq!(arr[2]["to"], json!("inference_traces.jsonl.1"), "slot-1 case {}", c["tag"]);
}
}
}
#[test]
fn replay_read() {
// The frozen read cases record selection COUNTS against the oracle's
// fixture (5 records, 8 raw lines with blanks). Rebuild the fixture
// shape from the counts and verify the kernel selects identically.
let line_count = cases("read")[0]["line_count"].as_u64().unwrap() as usize;
let raw_line_count = cases("read")[0]["raw_line_count"].as_u64().unwrap() as usize;
let fixture_lines: Vec<String> = (0..line_count)
.map(|i| json!({"task_id": format!("R{i}")}).to_string())
.collect();
let mut raw = fixture_lines[..3].join("\n");
raw.push_str(&"\n".repeat(raw_line_count - line_count));
raw.push_str(&fixture_lines[3..].join("\n"));
raw.push('\n');
for c in cases("read") {
if !c["exists"].as_bool().unwrap() {
// missing file: the FACADE returns [] before the kernel runs —
// verify the kernel agrees empty input yields empty output.
assert_eq!(
super::read_select("", None),
json!([]),
"missing-file kernel agreement"
);
continue;
}
let limit = c.get("limit").and_then(serde_json::Value::as_u64).map(|n| n as usize);
let selected = super::read_select(&raw, limit);
assert_eq!(
selected.as_array().unwrap().len(),
c["out_count"].as_u64().unwrap() as usize,
"read selection case {}",
c["tag"]
);
}
}
#[test]
fn replay_main() {
for c in cases("main") {
let count = c.get("count").and_then(serde_json::Value::as_u64).map(|n| n as usize);
let records: Vec<Value> = c["records"]
.as_array()
.cloned()
.unwrap_or_default();
let out = cli_tail(&records, count);
assert_eq!(out["rc"], json!(0), "main rc case {:?}", c["count"]);
assert_eq!(out["out"], c["out"], "main stdout case {:?}", c["count"]);
}
}
#[test]
fn truth_file_is_frozen_shape() {
let root: Value = serde_json::from_str(TRUTH).expect("frozen truth parses");
assert_eq!(root["frozen"]["pin"], json!("RUNE_IT_CORE=python"));
assert_eq!(
root["frozen"]["contract"],
json!("BUILDER-2026-10-12T13:55:29Z-148b8f-factory-wave")
);
}