feat(cognitive_budgeter): close PARTIAL gaps — dichotomy wave

Audit 1/3 PARTIAL -> 3/3 FULL (RUST). Ported the two missing fns
name-preserving: to_dict (CognitiveBudget struct with untyped Value
fields — the dataclass ctor validates nothing, so falsy/wrong-typed
kwargs ride through; from_kwargs pins the ctor-defaulting law; 9-field
projection in dataclass field order via preserve_order) and test_budgeter
(the embedded smoke test: 4 scenarios, oracle asserts, py_float_str law —
str(10.0) == '10.0'). New FFI: cb_to_dict (unknown-key TypeError crash
parity) + cb_test; cb_loader gains to_dict()/test() wrappers;
calculate_budget now builds the struct and returns to_dict() (output
identical, keys field-ordered like the Python dict).

Truth: 47-case truth_gap.json via the shared harness + cb_freeze_seams
(JSON-safe wrappers — calculate_budget returns a dataclass instance the
harness can't serialize) — byte-identical --verify AND fresh re-freeze.
PIN LAW: this oracle reads RUNE_CB_CORE, not the harness's stem-derived
pin. Battery 4/4 (legacy 14-case branch battery + gap battery incl. the
oracle stdout pin); clippy -D warnings + fmt clean; live parity 57/57
ops (incl. sentinel-proof the delegation path is live — the oracle masks
loader exceptions; ctor crash parity; CLI byte-identical both pins);
Python battery green both pins + elastic_cognition adjacency 21 passed.
Audit: cognitive_budgeter 3/3 FULL; programme 110/192 (57%). Evidence:
verification/cbud-*.
This commit is contained in:
Nick committed 2026-10-14 16:18:59 +01:00
1 parent 7f63d45d38
commit c5c64684b3
26 files changed
+2034 -34

No files matched your search

+5 -5
View File
@@ -2,14 +2,14 @@
_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._
**FULL completion: 61%** (118/192 runtime modules at FULL)
**FULL completion: 62%** (119/192 runtime modules at FULL)
Function-level detail: 73% (1678/2288 runtime functions matched) — secondary; includes in-flight work
Function-level detail: 73% (1680/2288 runtime functions matched) — secondary; includes in-flight work
| Status | Modules | Lines | Meaning |
|---|---|---|---|
| FULL | 118 | 48886 | 80%+ of functions ported |
| PARTIAL | 35 | 15146 | 20–80% — real logic exists, gaps named below |
| FULL | 119 | 49107 | 80%+ of functions ported |
| PARTIAL | 34 | 14925 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 39 | 11471 | no name-preserving implementation |
| Total runtime | 192 | 75503 | |
@@ -81,7 +81,6 @@ Function-level detail: 73% (1678/2288 runtime functions matched) — secondary;
| graph_projection | 134 | 3/6 | _model_to_dicts, write_all, _now |
| state_checkpoint | 182 | 6/9 | _sck_core, _load_ledger, _save_ledger |
| warm_neuron_store | 122 | 3/6 | _db, neuron_slugs, handle_hook |
| cognitive_budgeter | 221 | 1/3 | test_budgeter, to_dict |
| inference_trace | 204 | 4/6 | record_trace, read_traces |
| response_adapter | 222 | 5/7 | test_adapter, to_dict |
| structural_migration | 77 | 2/4 | apply_migration, consent_brief |
@@ -189,6 +188,7 @@ Function-level detail: 73% (1678/2288 runtime functions matched) — secondary;
| model_registry | 253 | 8/8 | GO |
| event_bus | 244 | 7/7 | GO |
| openai_compat_transport | 227 | 9/9 | GO |
| cognitive_budgeter | 221 | 3/3 | RUST |
| evidence_probe | 213 | 9/9 | GO |
| project_registry | 205 | 10/12 | RUST |
| forge_client | 203 | 18/18 | GO |
+24
View File
@@ -0,0 +1,24 @@
"""cb freeze seams — PYTHONPATH-registered (sitecustomize) patch for
rune.cognitive_budgeter during truth freeze/verify. The oracle's
calculate_budget returns a CognitiveBudget INSTANCE, which the shared
harness cannot JSON-serialize; these wrappers compose the same live calls
with .to_dict() so every row stays JSON-safe. The oracle fns run
unmodified underneath.
PIN LAW: this oracle reads RUNE_CB_CORE — NOT the harness's stem-derived
RUNE_COGNITIVE_BUDGETER_CORE — so the pin must come from the environment
(the generator sets it before import; --verify commands carry it too).
"""
import rune.cognitive_budgeter as _cb
def _cb_gap_calculate(salience, context=None):
return _cb.calculate_budget(salience, context).to_dict()
def _cb_gap_budget(**kwargs):
return _cb.CognitiveBudget(**kwargs).to_dict()
_cb.cb_gap_calculate = _cb_gap_calculate
_cb.cb_gap_budget = _cb_gap_budget
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""check_cognitive_budgeter_parity.py — LIVE parity for the
cognitive_budgeter gap-close (to_dict + test_budgeter + the calculate
seam end to end).
Oracle side: src/rune/cognitive_budgeter.py with RUNE_CB_CORE=python
(the pure-Python parity authority). Port side: the BUILT Rust .so reached
through rune.cb_loader with the delegation LIVE (RUNE_CB_CORE unset/auto).
The oracle checks the env var on every call, so one process flips the pin
between captures.
Sections (all structural == unless noted):
1. truth replay parity — every truth_gap.json row, oracle vs Rust
2. ctor matrix extras — falsy / partial / type-passthrough kwargs
3. crash parity — unknown ctor kwarg: Python TypeError message vs the
cb_loader RuntimeError envelope message (str(e) law: type prefix gone)
4. test_budgeter stdout — oracle subprocess (pinned `... test`) vs
cb_loader.test()
5. CLI budget subcommand — BYTE-identical stdout under both pins
Exit 0 iff 100% of ops match; exit 2 if the .so is not built (parity would
be vacuously oracle-vs-oracle).
"""
import json
import os
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import cb_loader # noqa: E402
OPS = [0, 0] # passed, total
def check(label: str, got, want) -> bool:
OPS[1] += 1
ok = got == want
if ok:
OPS[0] += 1
else:
print(f"MISMATCH {label}:\n got: {got!r}\n want: {want!r}")
return ok
def oracle(fn, *args, **kwargs):
"""Call the module with the Python oracle pinned."""
old = os.environ.get("RUNE_CB_CORE")
os.environ["RUNE_CB_CORE"] = "python"
try:
return fn(*args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_CB_CORE", None)
else:
os.environ["RUNE_CB_CORE"] = old
def rust(fn, *args, **kwargs):
"""Call the module with the delegation live (Rust .so via cb_loader)."""
old = os.environ.get("RUNE_CB_CORE")
os.environ["RUNE_CB_CORE"] = "auto"
try:
return fn(*args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_CB_CORE", None)
else:
os.environ["RUNE_CB_CORE"] = old
def main() -> int:
import rune.cognitive_budgeter as cb
if not cb_loader.available():
print("Rust core not built — run cargo build --release first "
"(parity would be vacuous)")
return 2
truth = json.loads(
(REPO / "src" / "rust" / "cognitive_budgeter" / "truth_gap.json").read_text()
)
# 0. delegation path PROVEN live (not silent Python fallback): a
# sentinel-returning loader must be picked up under auto. The oracle
# masks loader exceptions (`except Exception: pass`), so env flipping
# alone cannot distinguish Rust from fallback.
real_calc = cb_loader.calculate
cb_loader.calculate = lambda salience, context=None: {"start_tier": "RUST-SENTINEL"}
got = rust(cb.calculate_budget, {"score": 0.5, "minimum_tier": "T9", "flags": []}, {})
cb_loader.calculate = real_calc
check("delegation path live", got.start_tier, "RUST-SENTINEL")
# 1. truth replay parity (oracle pinned vs Rust delegation, per row)
for idx, case in enumerate(truth):
fn = case["fn"]
if fn == "cb_gap_calculate":
args = case["args"]
want = oracle(cb.calculate_budget, *args).to_dict()
got = rust(cb.calculate_budget, *args).to_dict()
elif fn == "cb_gap_budget":
want = oracle(lambda **kw: cb.CognitiveBudget(**kw).to_dict(), **case["kwargs"])
got = cb_loader.to_dict(case["kwargs"])
elif fn == "CognitiveBudget.to_dict":
state = {k: v for k, v in case["recv"].items() if k != "cls"}
want = oracle(lambda **kw: cb.CognitiveBudget(**kw).to_dict(), **state)
got = cb_loader.to_dict(state)
else:
raise AssertionError(f"unknown fn {fn}")
check(f"truth[{idx}] {fn}", got, want)
# 2. ctor matrix extras
for kwargs in (
{},
{"max_latency": 20}, # int in the float field rides through
{"start_tier": None},
{"cloud_allowed": 0}, # falsy int, not a bool
{f: False for f in (
"cloud_allowed", "verification_required", "tool_allowed")},
{"max_attempts": "three"}, # wrong type rides as string
):
want = oracle(lambda **kw: cb.CognitiveBudget(**kw).to_dict(), **kwargs)
got = cb_loader.to_dict(kwargs)
check(f"ctor matrix {kwargs}", got, want)
# 3. crash parity: unknown ctor kwarg
try:
cb.CognitiveBudget(bogus=1)
raise AssertionError("oracle ctor accepted an unknown kwarg")
except TypeError as e:
py_msg = str(e)
try:
cb_loader.to_dict({"bogus": 1})
raise AssertionError("Rust ctor accepted an unknown kwarg")
except RuntimeError as e:
rs_msg = str(e)
check("unknown-kwarg crash parity", rs_msg, py_msg)
# 4. test_budgeter stdout
cli = [sys.executable, str(REPO / "src" / "rune" / "cognitive_budgeter.py"), "test"]
env = dict(os.environ, RUNE_CB_CORE="python")
proc = subprocess.run(cli, capture_output=True, text=True, env=env, check=True)
want_lines = proc.stdout.splitlines()
got_lines = cb_loader.test()
check("test_budgeter stdout", got_lines, want_lines)
# 5. CLI budget subcommand — BYTE-identical under both pins
sal = '{"score": 0.75, "minimum_tier": "T2", "flags": ["TASK_FAILURE"]}'
ctx = '{"channel": "work", "urgency": "high", "risk": "high", "verification_available": true}'
cli = [sys.executable, str(REPO / "src" / "rune" / "cognitive_budgeter.py"),
"budget", sal, ctx]
pinned = subprocess.run(cli, capture_output=True, text=True,
env=dict(os.environ, RUNE_CB_CORE="python"), check=True)
live = subprocess.run(cli, capture_output=True, text=True,
env=dict(os.environ, RUNE_CB_CORE="auto"), check=True)
check("CLI budget byte-identical (pinned vs live)", live.stdout, pinned.stdout)
print(f"parity: {OPS[0]}/{OPS[1]} ops identical")
return 0 if OPS[0] == OPS[1] else 1
if __name__ == "__main__":
sys.exit(main())
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
"""gen_cognitive_budgeter_gap_truth.py — gap-close truth battery for
cognitive_budgeter (dichotomy wave #2479: to_dict + test_budgeter).
Freezes via the shared scripts/freeze_truth.py harness, with JSON-safe seam
wrappers from scripts/cb_freeze_seams (the oracle's calculate_budget
returns a CognitiveBudget instance the harness can't serialize):
* cb_gap_calculate — the full calculate_budget decision tree, incl. the
context=None default branch, every risk/verification, flag, channel,
urgency and score-threshold combination (plus the mixed-type flags
membership quirk and previous_failures-has-no-effect pin)
* cb_gap_budget — the CognitiveBudget(**kwargs) ctor-defaulting law:
missing -> default; falsy (0, 0.0, False, "") and wrong-typed values
ride through unchanged (the dataclass validates nothing)
* CognitiveBudget.to_dict — receiver cases pinning the 9-field projection
on explicitly reconstructed instance state
test_budgeter's stdout cannot ride the harness (it prints and returns
None): pinned by the Rust battery (src/gap_battery.rs ORACLE_TEST_STDOUT,
captured 2026-10-14) and re-captured live on every run by
scripts/check_cognitive_budgeter_parity.py.
Verify (byte-identical law):
PYTHONPATH=src:scripts/cb_freeze_seams RUNE_CB_CORE=python \\
python3 scripts/freeze_truth.py --verify \\
src/rust/cognitive_budgeter/truth_gap.json \\
--module rune.cognitive_budgeter
"""
import os
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(Path(__file__).resolve().parent))
# Pin BEFORE the oracle import: freeze_truth's stem-derived pin
# (RUNE_COGNITIVE_BUDGETER_CORE) does not reach this oracle.
os.environ["RUNE_CB_CORE"] = "python"
from freeze_truth import freeze, write # noqa: E402
DEFAULTS = {
"start_tier": "T0",
"max_tier": "T4",
"max_attempts": 3,
"max_tokens": 4096,
"max_latency": 30.0,
"cloud_allowed": True,
"verification_required": False,
"tool_allowed": True,
"attention_budget": 800,
}
def recv(**over):
r = {"cls": "CognitiveBudget"}
r.update(DEFAULTS)
r.update(over)
return r
def main():
cases = []
# -- cb_gap_budget: the ctor-defaulting law -----------------------------
cases.append(("cb_gap_budget", (), {})) # all defaults
cases.append(("cb_gap_budget", (), {"start_tier": "T3", "max_tokens": 8192}))
cases.append(("cb_gap_budget", (), {"max_attempts": 0, "max_tokens": 0, "attention_budget": 0})) # falsy ints
cases.append(("cb_gap_budget", (), {"max_latency": 0.0, "cloud_allowed": False, "verification_required": True}))
cases.append(("cb_gap_budget", (), {"start_tier": "", "max_tier": ""})) # falsy strings ride through
cases.append(("cb_gap_budget", (), {"start_tier": 5, "max_latency": 30, "cloud_allowed": None})) # type passthrough
cases.append(("cb_gap_budget", (), {"max_attempts": 4.5, "attention_budget": -1}))
cases.append(("cb_gap_budget", (), {"tool_allowed": "yes"})) # wrong-typed bool rides as string
# -- CognitiveBudget.to_dict: receiver projection ------------------------
cases.append({"fn": "CognitiveBudget.to_dict", "recv": recv(), "args": [], "kwargs": {}})
cases.append({
"fn": "CognitiveBudget.to_dict",
"recv": recv(start_tier="T4", max_tier="T3", max_attempts=5, max_tokens=2048,
max_latency=10.0, cloud_allowed=False, verification_required=True,
tool_allowed=False, attention_budget=1000),
"args": [], "kwargs": {},
})
cases.append({
"fn": "CognitiveBudget.to_dict",
"recv": recv(max_latency=20, start_tier=None, attention_budget=400),
"args": [], "kwargs": {},
})
# -- cb_gap_calculate: the decision tree ---------------------------------
# context omitted -> oracle's context=None branch
cases.append(("cb_gap_calculate", ({"score": 0.5, "minimum_tier": "T1", "flags": []},), {}))
cases.append(("cb_gap_calculate", ({},), {})) # empty salience -> every default
# max_tier: risk x verification_available
for ctx in (
{"risk": "high"},
{"risk": "high", "verification_available": True},
{"risk": "normal"},
{"risk": "weird"},
):
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": []}, ctx), {}))
# max_attempts: flags (mixed-type membership quirk)
for flags in (
[],
["UNKNOWN_FLAG"],
["TASK_FAILURE"],
["REPEATED_RETRY"],
["TASK_FAILURE", "REPEATED_RETRY"],
["TASK_FAILURE", 5, None], # non-str members: membership still True
):
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": flags}, {}), {}))
# max_tokens/tool_allowed: channel
for channel in ({"channel": "work"}, {"channel": "conversation"},
{"channel": "api"}, {}):
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": []}, channel), {}))
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": []}, {"tool_allowed": False, "channel": "work"}), {}))
# max_latency: urgency
for ctx in ({"urgency": "critical"}, {"urgency": "high"}, {"urgency": "normal"}, {"urgency": "whenever"}, {}):
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": []}, ctx), {}))
# verification_required / attention_budget: score thresholds
for score in (0.0, 0.59, 0.6, 0.79, 0.8, 1.0):
cases.append(("cb_gap_calculate", ({"score": score, "minimum_tier": "T0", "flags": []}, {}), {}))
# cloud_allowed: HIGH_RISK_IRREVERSIBLE x verification
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": ["HIGH_RISK_IRREVERSIBLE"]}, {"verification_available": False, "cloud_allowed": False}), {}))
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": ["HIGH_RISK_IRREVERSIBLE"]}, {"verification_available": True, "cloud_allowed": False}), {}))
# previous_failures is read but never used by the tree
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": []}, {"previous_failures": 3}), {}))
# start_tier passthrough
cases.append(("cb_gap_calculate", ({"score": 0.1, "minimum_tier": "T4", "flags": []}, {}), {}))
# the four embedded smoke scenarios
cases.append(("cb_gap_calculate", ({"score": 0.0, "minimum_tier": "T0", "flags": []}, {"channel": "conversation", "urgency": "normal"}), {}))
cases.append(("cb_gap_calculate", ({"score": 0.7, "minimum_tier": "T2", "flags": ["EXPLICIT_DIRECTIVE"]}, {"channel": "work", "urgency": "normal", "verification_available": True}), {}))
cases.append(("cb_gap_calculate", ({"score": 0.9, "minimum_tier": "T3", "flags": ["HIGH_RISK_IRREVERSIBLE"]}, {"channel": "work", "urgency": "critical", "verification_available": False}), {}))
cases.append(("cb_gap_calculate", ({"score": 0.8, "minimum_tier": "T3", "flags": ["TASK_FAILURE", "REPEATED_RETRY"]}, {"channel": "work", "urgency": "normal", "previous_failures": 2}), {}))
truth = freeze("rune.cognitive_budgeter", cases, oracle_module="rune.cognitive_budgeter")
dest = REPO / "src/rust/cognitive_budgeter/truth_gap.json"
write(truth, dest)
print(f"wrote {len(truth)} cases -> {dest}")
if __name__ == "__main__":
main()
+29 -5
View File
@@ -4,6 +4,10 @@ batch 5, #2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law):
* cb_calculate({salience, context}) -> budget dict
* cb_to_dict({kwargs}) -> CognitiveBudget(**kwargs).to_dict() dict
(unknown keys raise RuntimeError carrying the dataclass TypeError
message — crash parity)
* cb_test({}) -> the embedded smoke test's printed lines (test_budgeter)
Override the .so with CB_RUST_LIB (a FILE path).
"""
@@ -38,17 +42,23 @@ def _load() -> ctypes.CDLL:
lib = ctypes.CDLL(str(_LIB_PATH))
lib.cb_calculate.argtypes = [ctypes.c_void_p]
lib.cb_calculate.restype = ctypes.c_void_p
lib.cb_to_dict.argtypes = [ctypes.c_void_p]
lib.cb_to_dict.restype = ctypes.c_void_p
lib.cb_test.argtypes = [ctypes.c_void_p]
lib.cb_test.restype = ctypes.c_void_p
_lib = lib
return lib
def calculate(salience: dict, context: dict | None = None) -> dict:
def _call(fn_name: str, payload: dict):
"""Envelope call: serialize payload, invoke fn, free the returned buffer
via capability_scoring's shared cs_free_string, unpack {ok, value|err}."""
lib = _load()
data = json.dumps({"salience": salience, "context": context or {}}).encode()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = lib.cb_calculate(ctypes.cast(buf, ctypes.c_void_p))
ptr = getattr(lib, fn_name)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError("cb_calculate returned null")
raise RuntimeError(f"{fn_name} returned null")
out = ctypes.string_at(ptr).decode()
try:
scoring = ctypes.CDLL(str(
@@ -60,5 +70,19 @@ def calculate(salience: dict, context: dict | None = None) -> dict:
pass
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", "cb_calculate failed"))
raise RuntimeError(doc.get("err", f"{fn_name} failed"))
return doc["value"]
def calculate(salience: dict, context: dict | None = None) -> dict:
return _call("cb_calculate", {"salience": salience, "context": context or {}})
def to_dict(kwargs: dict | None = None) -> dict:
"""CognitiveBudget(**kwargs).to_dict() via the Rust core."""
return _call("cb_to_dict", {"kwargs": kwargs or {}})
def test() -> list[str]:
"""The embedded smoke test's printed lines (test_budgeter port)."""
return _call("cb_test", {})
+23
View File
@@ -9,6 +9,28 @@ dependencies = [
"serde_json",
]
[[package]]
name = "equivalent"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -74,6 +96,7 @@ version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
+5 -1
View File
@@ -7,4 +7,8 @@ edition = "2021"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde_json = "1"
# preserve_order: Python-dict insertion-order semantics — to_dict projects
# the 9 budget fields in dataclass field order and cb_calculate's envelope
# must serialize in that same order (byte-parity with the oracle CLI).
# float_roundtrip: exact binary64 round-trips for max_latency rows.
serde_json = { version = "1", features = ["preserve_order", "float_roundtrip"] }
@@ -313,9 +313,20 @@ const TRUTH: &str = r###"{
#[test]
fn calculate_matches_oracle() {
let truth: serde_json::Value = serde_json::from_str(TRUTH).expect("truth parses");
for (idx, case) in truth["calculate"].as_array().expect("calculate array").iter().enumerate() {
let salience = case.get("salience").cloned().unwrap_or(serde_json::Value::Null);
let context = case.get("context").cloned().unwrap_or(serde_json::Value::Null);
for (idx, case) in truth["calculate"]
.as_array()
.expect("calculate array")
.iter()
.enumerate()
{
let salience = case
.get("salience")
.cloned()
.unwrap_or(serde_json::Value::Null);
let context = case
.get("context")
.cloned()
.unwrap_or(serde_json::Value::Null);
let got = calculate_budget(&salience, &context);
assert_eq!(got, case["out"], "calculate case {}", idx);
}
@@ -0,0 +1,76 @@
//! cognitive_budgeter gap battery — closes the dichotomy audit gap
//! (to_dict, test_budgeter). Replays truth_gap.json, frozen from the Python
//! oracle via the shared scripts/freeze_truth.py harness (generator:
//! scripts/gen_cognitive_budgeter_gap_truth.py; seams:
//! scripts/cb_freeze_seams — cb_gap_calculate/cb_gap_budget JSON-safe
//! wrappers around the dataclass-returning oracle fns), and pins the
//! embedded smoke test's stdout.
use crate::{calculate_budget, test_budgeter, CognitiveBudget};
use serde_json::Value;
const TRUTH: &str = include_str!("../truth_gap.json");
// Captured from the oracle 2026-10-14:
// RUNE_CB_CORE=python python3 src/rune/cognitive_budgeter.py test (rc=0)
const ORACLE_TEST_STDOUT: &[&str] = &[
"Testing Cognitive Budgeter...",
"Greeting: start=T0, max=T4, tokens=2048",
"Directive: start=T2, max=T4, tokens=8192",
"Emergency: start=T3, max=T4, latency=10.0",
"Failure: start=T3, max_attempts=5",
"✅ All tests passed!",
];
#[test]
fn gap_truth_replay() {
let truth: Vec<Value> = serde_json::from_str(TRUTH).expect("truth parses");
assert!(!truth.is_empty(), "empty gap truth");
for (idx, case) in truth.iter().enumerate() {
let fn_name = case["fn"].as_str().unwrap_or_default();
match fn_name {
// seam wrapper: calculate_budget(salience, context=None).to_dict()
"cb_gap_calculate" => {
let args = case["args"].as_array().cloned().unwrap_or_default();
let salience = args.first().cloned().unwrap_or(Value::Null);
let context = args.get(1).cloned().unwrap_or(Value::Null);
let got = calculate_budget(&salience, &context);
assert_eq!(got, case["expected"], "case {} cb_gap_calculate", idx);
}
// seam wrapper: CognitiveBudget(**kwargs).to_dict() — the
// dataclass ctor-defaulting law (missing -> default, falsy and
// wrong-typed values ride through unchanged).
"cb_gap_budget" => {
let got = CognitiveBudget::from_kwargs(&case["kwargs"]).to_dict();
assert_eq!(got, case["expected"], "case {} cb_gap_budget", idx);
}
// the method itself, on an explicitly reconstructed receiver
"CognitiveBudget.to_dict" => {
let recv = &case["recv"];
let got = CognitiveBudget::from_kwargs(recv).to_dict();
assert_eq!(got, case["expected"]["result"], "case {} to_dict", idx);
for f in crate::BUDGET_FIELDS {
assert_eq!(recv[f], case["expected"][f], "case {} attr {}", idx, f);
}
}
other => panic!("unknown fn {} at case {}", other, idx),
}
}
}
#[test]
fn test_budgeter_matches_oracle_stdout() {
assert_eq!(test_budgeter(), ORACLE_TEST_STDOUT);
}
#[test]
fn to_dict_field_order_is_python_dict_order() {
let d = CognitiveBudget::default().to_dict();
let keys: Vec<&str> = d
.as_object()
.expect("object")
.keys()
.map(|k| k.as_str())
.collect();
assert_eq!(keys, crate::BUDGET_FIELDS.to_vec());
}
+224 -20
View File
@@ -1,11 +1,16 @@
//! cognitive_budgeter — budget kernels (dichotomy debt queue batch 5).
//! PARITY port of src/rune/cognitive_budgeter.py calculate_budget (GO
//! verdict, deterministic decision tree; salience/context stay Python).
//! PARITY port of src/rune/cognitive_budgeter.py (GO verdict, deterministic
//! decision tree; salience/context stay Python): calculate_budget,
//! CognitiveBudget.to_dict and the embedded smoke test test_budgeter.
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery;
#[path = "gap_battery.rs"]
#[cfg(test)]
mod gap_battery;
use serde_json::{json, Value};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
@@ -40,18 +45,100 @@ fn bool_of(v: Option<&Value>, dflt: bool) -> bool {
fn flags_of(v: Option<&Value>) -> Vec<String> {
match v {
Some(Value::Array(a)) => a.iter().filter_map(|i| i.as_str().map(String::from)).collect(),
Some(Value::Array(a)) => a
.iter()
.filter_map(|i| i.as_str().map(String::from))
.collect(),
_ => vec![],
}
}
const BUDGET_FIELDS: [&str; 9] = [
"start_tier",
"max_tier",
"max_attempts",
"max_tokens",
"max_latency",
"cloud_allowed",
"verification_required",
"tool_allowed",
"attention_budget",
];
/// CognitiveBudget — Python dataclass mirror. Fields are Values, not typed
/// Rust scalars, because the dataclass ctor validates nothing:
/// `CognitiveBudget(start_tier=5).to_dict()` emits the int 5 verbatim
/// (type-passthrough rows are frozen in truth_gap.json).
#[derive(Clone, Debug, PartialEq)]
pub struct CognitiveBudget {
pub start_tier: Value,
pub max_tier: Value,
pub max_attempts: Value,
pub max_tokens: Value,
pub max_latency: Value,
pub cloud_allowed: Value,
pub verification_required: Value,
pub tool_allowed: Value,
pub attention_budget: Value,
}
fn field_of(kwargs: &Value, key: &str, dflt: Value) -> Value {
kwargs.get(key).cloned().unwrap_or(dflt)
}
impl CognitiveBudget {
/// `CognitiveBudget(**kwargs)` — dataclass ctor law: a MISSING key takes
/// the field default; a PRESENT value rides through unchanged (no
/// coercion, falsy does NOT trigger the default).
pub fn from_kwargs(kwargs: &Value) -> Self {
CognitiveBudget {
start_tier: field_of(kwargs, "start_tier", json!("T0")),
max_tier: field_of(kwargs, "max_tier", json!("T4")),
max_attempts: field_of(kwargs, "max_attempts", json!(3)),
max_tokens: field_of(kwargs, "max_tokens", json!(4096)),
max_latency: field_of(kwargs, "max_latency", json!(30.0)),
cloud_allowed: field_of(kwargs, "cloud_allowed", json!(true)),
verification_required: field_of(kwargs, "verification_required", json!(false)),
tool_allowed: field_of(kwargs, "tool_allowed", json!(true)),
attention_budget: field_of(kwargs, "attention_budget", json!(800)),
}
}
/// `to_dict` — Python CognitiveBudget.to_dict: the 9-field projection in
/// dataclass field order (preserve_order keeps the map insertion-ordered,
/// matching the Python dict).
pub fn to_dict(&self) -> Value {
json!({
"start_tier": self.start_tier.clone(),
"max_tier": self.max_tier.clone(),
"max_attempts": self.max_attempts.clone(),
"max_tokens": self.max_tokens.clone(),
"max_latency": self.max_latency.clone(),
"cloud_allowed": self.cloud_allowed.clone(),
"verification_required": self.verification_required.clone(),
"tool_allowed": self.tool_allowed.clone(),
"attention_budget": self.attention_budget.clone(),
})
}
}
impl Default for CognitiveBudget {
fn default() -> Self {
CognitiveBudget::from_kwargs(&json!({}))
}
}
/// calculate_budget port. salience={score, minimum_tier, flags},
/// context={risk, verification_available, previous_failures, channel,
/// urgency, cloud_allowed, tool_allowed}.
pub fn calculate_budget(salience: &Value, context: &Value) -> Value {
let score = f64_of(salience.get("score"), 0.0);
let start_tier = s(salience.get("minimum_tier"));
let start_tier = if start_tier.is_empty() { "T0" } else { start_tier };
let start_tier = if start_tier.is_empty() {
"T0"
} else {
start_tier
};
let flags = flags_of(salience.get("flags"));
let risk = s(context.get("risk"));
@@ -76,7 +163,11 @@ pub fn calculate_budget(salience: &Value, context: &Value) -> Value {
};
let channel = s(context.get("channel"));
let channel = if channel.is_empty() { "conversation" } else { channel };
let channel = if channel.is_empty() {
"conversation"
} else {
channel
};
let max_tokens = if channel == "work" {
8192
} else if channel == "conversation" {
@@ -86,7 +177,11 @@ pub fn calculate_budget(salience: &Value, context: &Value) -> Value {
};
let urgency = s(context.get("urgency"));
let urgency = if urgency.is_empty() { "normal" } else { urgency };
let urgency = if urgency.is_empty() {
"normal"
} else {
urgency
};
let max_latency = if urgency == "critical" {
10.0
} else if urgency == "high" {
@@ -115,24 +210,96 @@ pub fn calculate_budget(salience: &Value, context: &Value) -> Value {
400
};
json!({
"start_tier": start_tier,
"max_tier": max_tier,
"max_attempts": max_attempts,
"max_tokens": max_tokens,
"max_latency": max_latency,
"cloud_allowed": cloud_allowed,
"verification_required": verification_required,
"tool_allowed": tool_allowed,
"attention_budget": attention_budget,
})
let budget = CognitiveBudget {
start_tier: json!(start_tier),
max_tier: json!(max_tier),
max_attempts: json!(max_attempts),
max_tokens: json!(max_tokens),
max_latency: json!(max_latency),
cloud_allowed: json!(cloud_allowed),
verification_required: json!(verification_required),
tool_allowed: json!(tool_allowed),
attention_budget: json!(attention_budget),
};
budget.to_dict()
}
/// str(float) law: Python renders integral floats with a trailing ".0"
/// (`str(10.0)` == "10.0"); Rust's Display for f64 gives "10".
fn py_float_str(v: f64) -> String {
if v == v.trunc() {
format!("{:.1}", v)
} else {
format!("{}", v)
}
}
/// test_budgeter — Python test_budgeter: the module's embedded smoke test
/// (greeting / directive / emergency / failure-retry scenarios). Asserts the
/// oracle's invariants (panics on mismatch) and returns the exact lines the
/// oracle prints.
pub fn test_budgeter() -> Vec<String> {
let mut out = vec!["Testing Cognitive Budgeter...".to_string()];
let salience = json!({"score": 0.0, "minimum_tier": "T0", "flags": []});
let context = json!({"channel": "conversation", "urgency": "normal"});
let budget = calculate_budget(&salience, &context);
out.push(format!(
"Greeting: start={}, max={}, tokens={}",
s(budget.get("start_tier")),
s(budget.get("max_tier")),
i64_of(budget.get("max_tokens"), 0),
));
assert_eq!(budget["start_tier"], json!("T0"));
assert!(i64_of(budget.get("max_tokens"), 0) <= 2048);
let salience = json!({"score": 0.7, "minimum_tier": "T2", "flags": ["EXPLICIT_DIRECTIVE"]});
let context = json!({"channel": "work", "urgency": "normal", "verification_available": true});
let budget = calculate_budget(&salience, &context);
out.push(format!(
"Directive: start={}, max={}, tokens={}",
s(budget.get("start_tier")),
s(budget.get("max_tier")),
i64_of(budget.get("max_tokens"), 0),
));
assert_eq!(budget["start_tier"], json!("T2"));
assert_eq!(budget["verification_required"], json!(true));
let salience = json!({"score": 0.9, "minimum_tier": "T3", "flags": ["HIGH_RISK_IRREVERSIBLE"]});
let context =
json!({"channel": "work", "urgency": "critical", "verification_available": false});
let budget = calculate_budget(&salience, &context);
out.push(format!(
"Emergency: start={}, max={}, latency={}",
s(budget.get("start_tier")),
s(budget.get("max_tier")),
py_float_str(f64_of(budget.get("max_latency"), 0.0)),
));
assert_eq!(budget["start_tier"], json!("T3"));
assert!(f64_of(budget.get("max_latency"), 0.0) <= 10.0);
let salience =
json!({"score": 0.8, "minimum_tier": "T3", "flags": ["TASK_FAILURE", "REPEATED_RETRY"]});
let context = json!({"channel": "work", "urgency": "normal", "previous_failures": 2});
let budget = calculate_budget(&salience, &context);
out.push(format!(
"Failure: start={}, max_attempts={}",
s(budget.get("start_tier")),
i64_of(budget.get("max_attempts"), 0),
));
assert!(i64_of(budget.get("max_attempts"), 0) >= 4);
out.push("✅ All tests passed!".to_string());
out
}
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())?;
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())
}
@@ -150,12 +317,49 @@ fn envelope(r: Result<Value, String>) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cb_calculate(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let salience = a.get("salience").cloned().unwrap_or(Value::Object(Default::default()));
let context = a.get("context").cloned().unwrap_or(Value::Object(Default::default()));
let salience = a
.get("salience")
.cloned()
.unwrap_or(Value::Object(Default::default()));
let context = a
.get("context")
.cloned()
.unwrap_or(Value::Object(Default::default()));
calculate_budget(&salience, &context)
});
envelope(r)
}
/// cb_to_dict({"kwargs": {...}}) -> `CognitiveBudget(**kwargs).to_dict()`.
/// Unknown keys reproduce the dataclass TypeError (crash parity); the first
/// offending key follows the caller's serialized order — Python reports
/// insertion order, so single-unknown-key calls are the pinned contract.
#[no_mangle]
pub extern "C" fn cb_to_dict(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let kwargs = a.get("kwargs").cloned().unwrap_or_else(|| json!({}));
if let Some(obj) = kwargs.as_object() {
for k in obj.keys() {
if !BUDGET_FIELDS.contains(&k.as_str()) {
return Err(format!(
"CognitiveBudget.__init__() got an unexpected keyword argument '{}'",
k
));
}
}
}
Ok(CognitiveBudget::from_kwargs(&kwargs).to_dict())
});
envelope(r)
}
/// cb_test({}) -> the embedded smoke test's printed lines (test_budgeter).
#[no_mangle]
pub extern "C" fn cb_test(_arg: *const c_char) -> *mut c_char {
envelope(Ok(Value::Array(
test_budgeter().into_iter().map(Value::String).collect(),
)))
}
// NOTE: no free-symbol export (capability_scoring's cs_free_string serves
// the whole process — duplicate symbols break the cdylib link).
File diff suppressed because it is too large. Load diff
+67
View File
@@ -0,0 +1,67 @@
"""cognitive_budgeter battery — replays truth_gap.json (frozen via the
shared freeze_truth harness, scripts/gen_cognitive_budgeter_gap_truth.py)
through the LIVE module. Green under BOTH pins: delegation live
(RUNE_CB_CORE unset with the .so built) and oracle pinned
(RUNE_CB_CORE=python) — the calculate rows ride the delegation chain when
live, proving the seam end to end.
"""
import json
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "src"))
from rune import cognitive_budgeter as cb # noqa: E402
TRUTH = json.loads(
(REPO / "src" / "rust" / "cognitive_budgeter" / "truth_gap.json").read_text()
)
def _ensure_shims():
"""The freeze seams (cb_gap_*) exist only under the seams PYTHONPATH;
re-register equivalents against the live module when absent."""
if hasattr(cb, "cb_gap_calculate"):
return
def cb_gap_calculate(salience, context=None):
return cb.calculate_budget(salience, context).to_dict()
def cb_gap_budget(**kwargs):
return cb.CognitiveBudget(**kwargs).to_dict()
cb.cb_gap_calculate = cb_gap_calculate
cb.cb_gap_budget = cb_gap_budget
_ensure_shims()
def _rebuild_receiver(recv):
inst = cb.CognitiveBudget.__new__(cb.CognitiveBudget)
for key, value in recv.items():
if key != "cls":
setattr(inst, key, json.loads(json.dumps(value)))
return inst
def test_gap_truth_replay():
assert TRUTH, "empty gap truth"
for idx, case in enumerate(TRUTH):
fn = case["fn"]
if fn == "cb_gap_calculate":
args = case["args"]
got = cb.cb_gap_calculate(*args, **case["kwargs"])
assert got == case["expected"], f"case {idx} cb_gap_calculate"
elif fn == "cb_gap_budget":
got = cb.cb_gap_budget(**case["kwargs"])
assert got == case["expected"], f"case {idx} cb_gap_budget"
elif fn == "CognitiveBudget.to_dict":
got = _rebuild_receiver(case["recv"]).to_dict()
assert got == case["expected"]["result"], f"case {idx} to_dict"
else:
raise AssertionError(f"unknown fn {fn} at case {idx}")
def test_embedded_smoke_test():
"""The oracle's own test fn passes against the live module."""
cb.test_budgeter()
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+1
View File
@@ -0,0 +1 @@
0
+78
View File
@@ -0,0 +1,78 @@
# cognitive_budgeter dichotomy gap-close — verification evidence (2026-10-14)
Module: src/rune/cognitive_budgeter.py (221L, 3 fns) →
src/rust/cognitive_budgeter (dichotomy wave #2479; worktree rune-wave-dh;
contract factory-gap-cbud). Evidence prefix cbud- (cb- belongs to
capability_broker and the original batch-5 port).
## Before / after
- Audit coverage: **1/3 PARTIAL → 3/3 FULL (RUST)** — missing were
`test_budgeter, to_dict`; both ported name-preserving
(`pub fn to_dict` / `pub fn test_budgeter` in lib.rs, snake-form exact).
- Programme: FULL 109/192 → 110/192 (57%); fn-level 1632→1634/2284.
## What was ported
- `CognitiveBudget` struct — untyped Value fields (the dataclass ctor
validates nothing; type-passthrough rows are frozen);
`from_kwargs` = `CognitiveBudget(**kwargs)` ctor-defaulting law (missing
→ default; falsy / wrong-typed values ride through unchanged);
`to_dict` = the 9-field projection in dataclass field order
(preserve_order feature added — precedent: behaviour_ledger et al.).
- `test_budgeter` — the embedded smoke test (4 scenarios, oracle asserts,
`py_float_str` law: Python `str(10.0)` == "10.0" vs Rust "10").
- FFI: `cb_to_dict` (unknown-key → `CognitiveBudget.__init__() got an
unexpected keyword argument '<k>'` — crash parity with the dataclass
TypeError, str(e) law) and `cb_test` (smoke-test lines).
- `calculate_budget` refactored to build the struct and return
`to_dict()` — output identical (keys now field-ordered, matching Python
dict order; previously BTreeMap-sorted).
## Verification gates (all green in THIS checkout)
| Gate | Result | Evidence |
|---|---|---|
| freeze_truth --verify (byte-identical vs live oracle) | OK: 47 cases | cbud-freeze-verify.{log,exit} |
| fresh regeneration byte-identity | REGEN BYTE-IDENTICAL (cmp) | cbud-freeze-regen.{log,exit} |
| cargo build --release (+ exported symbols) | clean; cb_calculate/cb_to_dict/cb_test exported | cbud-cargo-build.{log,exit} |
| cargo clippy --release --all-targets -D warnings | clean | cbud-clippy.{log,exit} |
| cargo fmt --check | clean | cbud-fmt.{log,exit} |
| cargo test --release (branch battery 14-case legacy + gap battery) | 4/4 | cbud-cargo-test.{log,exit} |
| LIVE parity (scripts/check_cognitive_budgeter_parity.py) | 57/57 ops identical | cbud-parity.{log,exit} |
| Python battery, delegation live | 2 passed | cbud-pytests-live.{log,exit} |
| Python battery, RUNE_CB_CORE=python pin | 2 passed | cbud-pytests-python.{log,exit} |
| Oracle CLI smoke (`cognitive_budgeter.py test`, pinned) | all tests passed | cbud-oracle-smoke.{log,exit} |
| Adjacency (tests/test_elastic_cognition.py — constructs CognitiveBudget) | 21 passed | cbud-adjacency.{log,exit} |
| dichotomy_audit.py --write | cognitive_budgeter 3/3 RUST row | cbud-audit-write.{log,exit} |
| ruff check (5 new/changed Python files) | all checks passed | cbud-ruff.{log,exit} |
## Truth-freeze notes
- Shared harness (scripts/freeze_truth.py) + seams
scripts/cb_freeze_seams/sitecustomize.py (JSON-safe wrappers
cb_gap_calculate / cb_gap_budget — the oracle's calculate_budget returns
a CognitiveBudget instance the harness can't serialize; the oracle fns
run unmodified underneath). 47 cases.
- **PIN LAW**: this oracle reads `RUNE_CB_CORE`, not the harness's
stem-derived `RUNE_COGNITIVE_BUDGETER_CORE` — pinned explicitly in the
generator (before import) and in the --verify command env.
- cb_gap_calculate rows cover the full decision tree incl. the
context=None branch, mixed-type flags membership (["TASK_FAILURE", 5,
None] → attempts 4) and the previous_failures-is-read-but-unused pin.
- cb_gap_budget rows pin the ctor-defaulting law incl. falsy (0 / 0.0 /
False / "") and type-passthrough (int in str field, None bool).
- test_budgeter's stdout can't ride the harness (prints, returns None):
pinned by the Rust battery (ORACLE_TEST_STDOUT, captured 2026-10-14,
rc=0) and re-captured live by the parity script every run.
- Parity section 0 PROVES the delegation path is live (sentinel loader
picked up under auto) — necessary because the oracle masks loader
exceptions with `except Exception: pass`.
## Representation divergences (documented, not bugs)
- cb_to_dict unknown-key error reports the first offending key in the
caller's serialized order; Python reports insertion order — identical
for single-unknown-key calls (the pinned contract).
- None (JSON null) field values ride through in both languages
(dataclass law) — frozen rows carry them as null.