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:
1 parent
e3d0560ed5
commit
b5228ace36
9 files changed
+1531
-340
No files matched your search
@@ -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))
|
||||
@@ -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
@@ -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:]
|
||||
|
||||
@@ -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)
|
||||
Generated
+19
-284
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user