Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5882cd46fa | ||
|
|
6344dfb45d | ||
|
|
acafb72178 | ||
|
|
47d3931071 | ||
|
|
9618cf7a50 | ||
|
|
097916411d | ||
|
|
14805261d2 | ||
|
|
678b390cb9 | ||
|
|
d57ad585b5 | ||
|
|
17fa6cd933 | ||
|
|
2b87a88aa2 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.252.0**
|
||||
**Current release: v0.255.0**
|
||||
|
||||
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
|
||||
|
||||
|
||||
@@ -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._
|
||||
|
||||
**Function-level completion: 46%** (1083/2366 runtime functions matched)
|
||||
**Function-level completion: 47%** (1116/2369 runtime functions matched)
|
||||
|
||||
| Status | Modules | Lines | Meaning |
|
||||
|---|---|---|---|
|
||||
| FULL | 40 | 13895 | 80%+ of functions ported |
|
||||
| PARTIAL | 98 | 46825 | 20–80% — real logic exists, gaps named below |
|
||||
| NOT TOUCHED | 54 | 12187 | no name-preserving implementation |
|
||||
| Total runtime | 192 | 72907 | |
|
||||
| FULL | 42 | 14824 | 80%+ of functions ported |
|
||||
| PARTIAL | 99 | 46362 | 20–80% — real logic exists, gaps named below |
|
||||
| NOT TOUCHED | 51 | 11859 | no name-preserving implementation |
|
||||
| Total runtime | 192 | 73045 | |
|
||||
|
||||
## NOT TOUCHED — the port queue, in priority order (largest first)
|
||||
|
||||
@@ -37,11 +37,9 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| canary_tui_drift | 170 | 2 | TEST-INFRA |
|
||||
| canary_gitea_pipeline | 159 | 2 | TEST-INFRA |
|
||||
| rune_graphic | 145 | 5 | TUI-LAST |
|
||||
| project_registry | 139 | 11 | RUST |
|
||||
| canary_rune_entry | 137 | 3 | TEST-INFRA |
|
||||
| tui_backlog | 125 | 5 | TUI-LAST |
|
||||
| state_checkpoint | 123 | 8 | unclassified |
|
||||
| drift_resume | 118 | 3 | RUST |
|
||||
| canary_conversation_state | 115 | 2 | TEST-INFRA |
|
||||
| canary_query_memory | 111 | 2 | TEST-INFRA |
|
||||
| tui_transport | 108 | 10 | TUI-LAST |
|
||||
@@ -49,7 +47,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| message_widgets | 104 | 6 | TUI-LAST |
|
||||
| inference_paths | 95 | 2 | unclassified |
|
||||
| canary_schema_migration | 86 | 1 | TEST-INFRA |
|
||||
| execution_trace | 71 | 4 | unclassified |
|
||||
| pane_authority | 67 | 2 | unclassified |
|
||||
| skill_compiler | 64 | 4 | unclassified |
|
||||
| meaningful_events | 61 | 3 | unclassified |
|
||||
@@ -80,7 +77,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| local_tool_runtime | 809 | 8/30 | _find_capability_executor, _extract_context_length, context_length, reset_context_length_cache, supports_tools, supports_thinking |
|
||||
| construction_interface | 1193 | 6/27 | _to_stage_check, _guard_specs_from_obligations, t_layout, _obligations_projection, _obligation_scope_files, main |
|
||||
| structural_store | 690 | 13/34 | _ss_core, _sorted, new_doc, _redact_secrets, save, canonical_digest |
|
||||
| persona_gate | 724 | 7/26 | _now, _cc_pg_core, _py_lex, _lex_input, _py_overlap, _embed_raw |
|
||||
| sleep_time | 984 | 8/26 | _ctx_budget, _ensure_dirs, db_connect, fetch_sessions, retain_to_store, reflect_local |
|
||||
| provider_registry | 679 | 11/28 | _ctx_budget, _format_tools_openai, _execute_tool_calls, _execute_tool_calls_anthropic, get_registry, connect_provider |
|
||||
| warm_router | 1518 | 19/36 | _ensure_use_history_table, record_outcome, record_neuron_fire, _py_base_level_from_uses, base_level_activation, _db |
|
||||
@@ -160,6 +156,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| warm_neuron_store | 122 | 3/6 | _db, neuron_slugs, handle_hook |
|
||||
| cognitive_budgeter | 221 | 1/3 | test_budgeter, to_dict |
|
||||
| event_bus | 244 | 5/7 | _store_path, handle_hook |
|
||||
| execution_trace | 108 | 3/5 | _kernel, _journal_path |
|
||||
| experience_encoding | 113 | 1/3 | _ee_core, _py_evaluate |
|
||||
| inference_trace | 204 | 4/6 | record_trace, read_traces |
|
||||
| iteration | 261 | 7/9 | _kernel, _now |
|
||||
@@ -171,6 +168,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| voice_injector | 138 | 3/5 | test_injection, to_dict |
|
||||
| abstraction_ladder | 102 | 3/4 | _core |
|
||||
| construction_depth | 172 | 2/3 | _core |
|
||||
| drift_resume | 153 | 3/4 | _dr_core |
|
||||
| recall_router | 165 | 2/3 | handle_hook |
|
||||
|
||||
## FULL — ported, parity-gated
|
||||
@@ -178,6 +176,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| Module | Lines | Fns | Side |
|
||||
|---|---|---|---|
|
||||
| curator | 1838 | 43/43 | GO |
|
||||
| persona_gate | 724 | 26/26 | GO |
|
||||
| abstraction | 656 | 17/17 | GO |
|
||||
| system_registry | 603 | 14/16 | RUST |
|
||||
| construction_intent | 580 | 13/16 | RUST |
|
||||
@@ -204,6 +203,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| construction_patterns_builtin | 266 | 1/1 | RUST |
|
||||
| openai_compat_transport | 227 | 9/9 | GO |
|
||||
| evidence_probe | 213 | 9/9 | GO |
|
||||
| project_registry | 205 | 10/12 | RUST |
|
||||
| forge_client | 203 | 18/18 | GO |
|
||||
| behaviour_ledger | 195 | 11/13 | RUST |
|
||||
| self_modify | 149 | 7/8 | unclassified |
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"drift_ledger",
|
||||
"event_wait",
|
||||
"evidence_probe",
|
||||
"execution_trace",
|
||||
"failure_recovery",
|
||||
"forge",
|
||||
"forge_client",
|
||||
@@ -25,8 +26,10 @@
|
||||
"model_routing",
|
||||
"openai_compat_transport",
|
||||
"outcome_ledger",
|
||||
"persona_gate",
|
||||
"programme_continuation",
|
||||
"programme_gates",
|
||||
"project_registry",
|
||||
"provider_context",
|
||||
"release_notes",
|
||||
"route_risk_flags",
|
||||
@@ -42,11 +45,9 @@
|
||||
"worthiness"
|
||||
],
|
||||
"in_flight": {
|
||||
"project_registry": "rune-wave-ba",
|
||||
"state_checkpoint": "rune-wave-ba",
|
||||
"drift_resume": "rune-wave-bb",
|
||||
"inference_paths": "rune-wave-bb",
|
||||
"execution_trace": "rune-wave-bc",
|
||||
"pane_authority": "rune-wave-bc",
|
||||
"skill_compiler": "rune-wave-bc",
|
||||
"meaningful_events": "rune-wave-bc",
|
||||
@@ -62,7 +63,6 @@
|
||||
"context_broker": "rune-wave-bf",
|
||||
"local_tool_runtime": "rune-wave-bg",
|
||||
"structural_store": "rune-wave-bh",
|
||||
"persona_gate": "rune-wave-bi",
|
||||
"sleep_time": "rune-wave-bj",
|
||||
"work_manager": "rune-wave-bk",
|
||||
"growth_delta": "rune-wave-bl",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.252.0"
|
||||
version = "0.255.0"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Live parity: pane_authority Rust kernel vs Python oracle (dichotomy
|
||||
wave). Layers:
|
||||
1. FROZEN — the 26 frozen-oracle cases replayed through the facade in
|
||||
BOTH core modes (RUNE_PA_CORE=python floor vs auto kernel); exact
|
||||
compare, inputs deep-copied.
|
||||
2. FUZZ — 400 seeded random utterances/panes/tasks (seed 2479)
|
||||
driven through both modes; exact compare.
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
|
||||
import pane_authority as pa # noqa: E402
|
||||
from rune import pa_loader # noqa: E402
|
||||
|
||||
if not pa_loader.available():
|
||||
print("FATAL: Rust pane_authority core not built")
|
||||
sys.exit(2)
|
||||
|
||||
TRUTH = json.loads(
|
||||
(REPO / "src" / "rust" / "pane_authority" / "frozen_truth.json")
|
||||
.read_text())
|
||||
|
||||
fails = []
|
||||
counts = {"frozen": 0, "fuzz": 0}
|
||||
|
||||
|
||||
def fail(layer, name, detail):
|
||||
fails.append((layer, name, detail))
|
||||
print(f"FAIL[{layer}] {name}: {detail}")
|
||||
|
||||
|
||||
def drive(mode, fn, *args):
|
||||
os.environ["RUNE_PA_CORE"] = mode
|
||||
try:
|
||||
return fn(*copy.deepcopy(args))
|
||||
finally:
|
||||
os.environ["RUNE_PA_CORE"] = "auto"
|
||||
|
||||
|
||||
def frozen():
|
||||
for c in TRUTH["classify_cases"]:
|
||||
counts["frozen"] += 1
|
||||
py = drive("python", pa.classify_utterance, c["args"]["text"])
|
||||
rs = drive("auto", pa.classify_utterance, c["args"]["text"])
|
||||
if py != rs:
|
||||
fail("frozen", f"classify/{c['name']}", f"{py!r} != {rs!r}")
|
||||
for c in TRUTH["apply_cases"]:
|
||||
counts["frozen"] += 1
|
||||
args = (c["args"]["pane"], c["args"]["task"], c["args"]["utterance"])
|
||||
py = drive("python", pa.apply_to_task, *args)
|
||||
rs = drive("auto", pa.apply_to_task, *args)
|
||||
if py != rs:
|
||||
fail("frozen", f"apply/{c['name']}", f"{py!r} != {rs!r}")
|
||||
|
||||
|
||||
FRAGMENTS = [
|
||||
"stop", "pause", "cancel", "stop the task", "pause everything",
|
||||
"cancel the task", "change the task", "amend", "update the task",
|
||||
"instead do", "actually do", "hello", "what about y?", "",
|
||||
" STOP ", "Mixed Case AMEND here", "the task", "everything",
|
||||
"instead", "actually", "stop", "\tpause\n", "cancel everything",
|
||||
]
|
||||
PANES = ["work", "chat", "review", "", None]
|
||||
TASKS = [
|
||||
{}, {"status": "running"}, {"status": "running", "directive": "do x"},
|
||||
{"directive": "keep"}, {"status": "", "extra": [1, 2]},
|
||||
]
|
||||
|
||||
|
||||
def fuzz():
|
||||
rng = random.Random(2479)
|
||||
for i in range(400):
|
||||
text = " ".join(rng.choice(FRAGMENTS)
|
||||
for _ in range(rng.randint(0, 4)))
|
||||
if rng.random() < 0.05:
|
||||
text = None
|
||||
pane = rng.choice(PANES)
|
||||
task = rng.choice(TASKS)
|
||||
py = drive("python", pa.classify_utterance, text)
|
||||
rs = drive("auto", pa.classify_utterance, text)
|
||||
counts["fuzz"] += 1
|
||||
if py != rs:
|
||||
fail("fuzz", f"classify-{i}", f"{text!r}: {py!r} != {rs!r}")
|
||||
continue
|
||||
py = drive("python", pa.apply_to_task, pane, task, text)
|
||||
rs = drive("auto", pa.apply_to_task, pane, task, text)
|
||||
counts["fuzz"] += 1
|
||||
if py != rs:
|
||||
fail("fuzz", f"apply-{i}", f"{pane!r}/{text!r}: {py!r} != {rs!r}")
|
||||
|
||||
|
||||
frozen()
|
||||
fuzz()
|
||||
total = counts["frozen"] + counts["fuzz"]
|
||||
print(f"parity: {total - len(fails)}/{total} "
|
||||
f"(frozen {counts['frozen']}, fuzz {counts['fuzz']})")
|
||||
sys.exit(1 if fails else 0)
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Close the #2479 dichotomy-wave persona_gate contract with SHA evidence.
|
||||
Run on top of CURRENT master (a worktree store close never reaches master
|
||||
by itself — build_basis/message_queue precedent)."""
|
||||
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
|
||||
|
||||
CID = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
PARITY_SHA = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
PHASEB_SHA = sys.argv[3] if len(sys.argv) > 3 else ""
|
||||
if not (CID and PARITY_SHA and PHASEB_SHA):
|
||||
sys.exit("usage: close_contract_persona_gate.py <contract_id> <parity_sha> <phaseb_sha>")
|
||||
|
||||
iface = ConstructionInterface(builder="factory-gap-pg")
|
||||
|
||||
c = iface.close(CID, observed_evidence=[{
|
||||
"kind": "port_evidence",
|
||||
"detail": (
|
||||
f"persona_gate GO gap-closing port — parity commit {PARITY_SHA}, "
|
||||
f"Phase B commit {PHASEB_SHA}; audit fn coverage 7/26 → 26/26 "
|
||||
"(19 audit-missing functions ported name-preserving); frozen-truth "
|
||||
"battery 92/92 rows vs live oracle (scripts/gen_persona_gate_truth.py, "
|
||||
"RUNE_COGNITION_CORE=python pinned, deep-copied inputs), live parity "
|
||||
"31/31, go test -race green, gofmt/vet clean, Python seam tests 13 "
|
||||
"passed."
|
||||
),
|
||||
}], scope_files=[
|
||||
"src/rune/persona_gate.py",
|
||||
"src/go/persona-gate/gaps.go",
|
||||
"src/go/persona-gate/main.go",
|
||||
"src/go/persona-gate/phaseb.go",
|
||||
"src/go/persona-gate/truth_test.go",
|
||||
"src/go/persona-gate/phaseb_test.go",
|
||||
"src/go/persona-gate/truth.json",
|
||||
"scripts/gen_persona_gate_truth.py",
|
||||
"scripts/parity_persona_gate.py",
|
||||
"scripts/open_contract_persona_gate.py",
|
||||
])
|
||||
print("close:", json.dumps(c)[:400])
|
||||
print("CONTRACT", CID, "closed" if c.get("success") else "CLOSE_FAILED")
|
||||
@@ -14,7 +14,14 @@ LANG_="${3:-rust}"
|
||||
REPO=/mnt/workdrive/rune
|
||||
PASS=0; FAIL=0
|
||||
|
||||
check() { echo " [$1] $2"; if [ "$1" = "PASS" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo " FAILED: $2"; fi; }
|
||||
check() {
|
||||
echo " [$1] $2"
|
||||
case "$1" in
|
||||
PASS) PASS=$((PASS+1));;
|
||||
FAIL) FAIL=$((FAIL+1)); echo " FAILED: $2";;
|
||||
WARN) ;; # neutral: logged, counted neither way
|
||||
esac
|
||||
}
|
||||
|
||||
echo "=== verifying $SHA ($CRATE, $LANG_) ==="
|
||||
|
||||
@@ -67,5 +74,63 @@ else
|
||||
check "WARN" "no module-specific Python tests (may use consumer tests)"
|
||||
fi
|
||||
|
||||
# Layer 7: connection completeness — MECHANICAL portion (2026-10-13).
|
||||
# What grep can prove, grep proves — coordinator LLM review now only
|
||||
# judges what remains (Phase B genuineness, layer 6).
|
||||
MOD=$(basename "$CRATE" | tr '-' '_') # crate dir -> module stem
|
||||
STEM="${MOD%_loader}"; STEM="${STEM%-loader}"
|
||||
LOADER="src/rune/${STEM}_loader.py"
|
||||
FACADE="src/rune/${STEM}.py"
|
||||
# 7a/7b: connection completeness. Language-dependent law:
|
||||
# RUST — facade delegates through a ctypes *_loader.py (doc-envelope FFI).
|
||||
# GO — Go kernels are services/binaries by verdict; the facade keeps
|
||||
# pure-Python logic and the Go side is exercised by its own
|
||||
# battery. Connection proof = Go binary builds + battery truth.
|
||||
CRATE_DIR="$CRATE"
|
||||
if [ "$LANG_" = "rust" ]; then
|
||||
# 7a: loader exists — discovered from the facade's own import (loader
|
||||
# naming uses abbreviated stems: subsystem_health -> sh_loader).
|
||||
LOADER_REF=$(grep -oE "[a-z]+_loader" "$FACADE" 2>/dev/null | head -1)
|
||||
if [ -n "$LOADER_REF" ] && [ -f "src/rune/${LOADER_REF}.py" ]; then
|
||||
check "PASS" "7a loader exists: src/rune/${LOADER_REF}.py (per facade import)"
|
||||
elif [ -f "$FACADE" ] && ! grep -qE "import.*_loader|from.*_loader" "$FACADE"; then
|
||||
check "FAIL" "7a facade imports no loader"
|
||||
else
|
||||
check "FAIL" "7a loader referenced as $LOADER_REF but file missing"
|
||||
fi
|
||||
# 7b: facade delegation wired (core-selection seam)
|
||||
if [ -f "$FACADE" ] && grep -qE "RUNE_[A-Z_]+_CORE|_loader" "$FACADE" 2>/dev/null; then
|
||||
check "PASS" "7b facade has core-selection seam"
|
||||
else
|
||||
check "FAIL" "7b facade not wired to implementation"
|
||||
fi
|
||||
else
|
||||
# GO: connection = go build succeeds (main package) + truth-replay tests exist
|
||||
if (cd "$CRATE_DIR" && go build ./... >/dev/null 2>&1); then
|
||||
check "PASS" "7a go build succeeds (service/binary connection)"
|
||||
else
|
||||
check "FAIL" "7a go build fails"
|
||||
fi
|
||||
if grep -qE 'RUNE_GO|exec\.Command|go run|os/exec' "$FACADE" 2>/dev/null; then
|
||||
check "PASS" "7b facade reaches Go via exec"
|
||||
else
|
||||
check "WARN" "7b facade does not exec Go (may be service-dispatched)"
|
||||
fi
|
||||
fi
|
||||
# 7c: battery test references the truth FILE (not self-generated truth)
|
||||
if grep -rqE 'include_str!.*truth|frozen_.*truth|truth\.json' "$CRATE_DIR" \
|
||||
--include='*.rs' --include='*.go' 2>/dev/null; then
|
||||
check "PASS" "7c battery reads frozen truth"
|
||||
else
|
||||
check "FAIL" "7c battery does not reference a truth file"
|
||||
fi
|
||||
# 7d: CI gate exists for the crate (polyglot lint or module test in CI config)
|
||||
if grep -rqE "$(basename $CRATE)" .gitea/workflows/ .github/workflows/ 2>/dev/null \
|
||||
|| grep -rq 'polyglot_lint\|dichotomy' .gitea/workflows/ 2>/dev/null; then
|
||||
check "PASS" "7d CI gate present"
|
||||
else
|
||||
check "WARN" "7d no explicit CI gate (repo-level lint may cover)"
|
||||
fi
|
||||
|
||||
echo "=== result: $PASS PASS / $FAIL FAIL ==="
|
||||
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze the dichotomy-wave truth corpus for src/rune/outcome_utility.py.
|
||||
|
||||
LAW (poisoned-truth): RUNE_OU_CORE=python is pinned at the top — truth is
|
||||
generated by the PYTHON ORACLE ONLY, never by the kernel under test.
|
||||
|
||||
Every public function is exercised across ALL branches (helpful/ignored/
|
||||
unknown × cap/floor boundaries; stale 0/positive/negative/clamping;
|
||||
retrieval passthrough; record on new/existing/partial entries with
|
||||
driven cap and floor saturation). Inputs are DEEP-COPIED before each
|
||||
oracle call — the frozen corpus is byte-stable and carries no live
|
||||
state. Output: src/rust/outcome_utility/src/frozen_outcome_utility_truth.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["RUNE_OU_CORE"] = "python" # poisoned-truth law
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
import rune.outcome_utility as ou # noqa: E402
|
||||
|
||||
OUT = REPO / "src" / "rust" / "outcome_utility" / "src" / "frozen_outcome_utility_truth.json"
|
||||
|
||||
cases: list[dict] = []
|
||||
|
||||
|
||||
def case(fn: str, name: str, args: dict):
|
||||
"""Call the oracle on a DEEP COPY of the args; freeze the verdict."""
|
||||
frozen_args = copy.deepcopy(args)
|
||||
live_args = copy.deepcopy(args)
|
||||
if fn == "apply_outcome":
|
||||
expect = ou.apply_outcome(**live_args)
|
||||
elif fn == "apply_stale":
|
||||
expect = ou.apply_stale(**live_args)
|
||||
elif fn == "retrieval_alone":
|
||||
expect = ou.retrieval_alone(**live_args)
|
||||
elif fn == "record_verified_outcome":
|
||||
# store=None ≡ a fresh dict in the oracle; freeze the fresh dict so
|
||||
# the mutation is captured (the FFI returns the table it built).
|
||||
store_arg = live_args.get("store")
|
||||
if store_arg is None:
|
||||
store_arg = {}
|
||||
summary = ou.record_verified_outcome(
|
||||
live_args["entry_id"], live_args["outcome"], store_arg)
|
||||
expect = {"summary": summary, "store": store_arg}
|
||||
else: # pragma: no cover — generator typo guard
|
||||
raise ValueError(fn)
|
||||
cases.append({"fn": fn, "name": name, "args": frozen_args, "expect": expect})
|
||||
|
||||
|
||||
# ── apply_outcome: all three branches × cap/floor boundaries ────────────────
|
||||
for u in (0.0, 0.5, 0.95, 1.0, 1.0, -0.5, -1.0, 0.9499999, -1.0):
|
||||
case("apply_outcome", f"helpful u={u!r}", {"utility": u, "outcome": "helpful"})
|
||||
for u in (0.0, 0.5, -0.95, -1.0, -1.0, 1.0, -0.9499999):
|
||||
case("apply_outcome", f"ignored u={u!r}", {"utility": u, "outcome": "ignored"})
|
||||
for o in ("unknown", "", "HELPFUL", "helpful ", "partial", "verified"):
|
||||
case("apply_outcome", f"unknown o={o!r}", {"utility": 0.42, "outcome": o})
|
||||
|
||||
# ── apply_stale: zero/negative/positive turns, clamp boundaries ─────────────
|
||||
for u, t in ((0.5, 0), (0.5, 1), (0.5, 2), (0.5, 5), (1.0, 10), (-0.5, 3),
|
||||
(-0.5, -3), (0.0, 60), (0.0, -60), (1.0, 200), (-1.0, 0),
|
||||
(0.99, 100), (-0.02, 1000), (0.42, 1000000)):
|
||||
case("apply_stale", f"stale u={u!r} t={t}", {"utility": u, "stale_turns": t})
|
||||
|
||||
# ── retrieval_alone: the anti-salience passthrough ──────────────────────────
|
||||
for u in (0.0, 1.0, -1.0, 0.42, -0.77, 12345.678):
|
||||
case("retrieval_alone", f"retrieval u={u!r}", {"utility": u})
|
||||
|
||||
# ── record_verified_outcome: new / existing / partial entries ───────────────
|
||||
case("record_verified_outcome", "new entry default store",
|
||||
{"entry_id": 1, "outcome": "helpful", "store": None})
|
||||
case("record_verified_outcome", "new entry empty store",
|
||||
{"entry_id": 7, "outcome": "ignored", "store": {}})
|
||||
case("record_verified_outcome", "existing helpful",
|
||||
{"entry_id": 1, "outcome": "helpful",
|
||||
"store": {1: {"utility": 0.5, "confidence": 0.8}}})
|
||||
case("record_verified_outcome", "existing ignored",
|
||||
{"entry_id": 2, "outcome": "ignored",
|
||||
"store": {2: {"utility": 0.5, "confidence": 0.8}}})
|
||||
case("record_verified_outcome", "existing unknown outcome",
|
||||
{"entry_id": 3, "outcome": "mystery",
|
||||
"store": {3: {"utility": 0.25, "confidence": 0.9}}})
|
||||
case("record_verified_outcome", "existing no confidence key",
|
||||
{"entry_id": 4, "outcome": "helpful",
|
||||
"store": {4: {"utility": 0.3}}})
|
||||
case("record_verified_outcome", "existing no utility key",
|
||||
{"entry_id": 5, "outcome": "ignored",
|
||||
"store": {5: {"confidence": 0.1}}})
|
||||
case("record_verified_outcome", "entry id zero",
|
||||
{"entry_id": 0, "outcome": "helpful", "store": {}})
|
||||
case("record_verified_outcome", "negative entry id",
|
||||
{"entry_id": -3, "outcome": "ignored", "store": {}})
|
||||
case("record_verified_outcome", "large entry id",
|
||||
{"entry_id": 2**62, "outcome": "helpful", "store": {}})
|
||||
# driven saturation: repeated outcomes walk utility to cap and floor
|
||||
for i, outcome in enumerate(["helpful"] * 20):
|
||||
case("record_verified_outcome", f"cap walk {i}",
|
||||
{"entry_id": 10, "outcome": outcome,
|
||||
"store": {10: {"utility": min(1.0, 0.1 * i), "confidence": 0.5}}})
|
||||
for i, outcome in enumerate(["ignored"] * 25):
|
||||
case("record_verified_outcome", f"floor walk {i}",
|
||||
{"entry_id": 11, "outcome": outcome,
|
||||
"store": {11: {"utility": max(-1.0, -0.05 * i), "confidence": 0.5}}})
|
||||
|
||||
doc = {
|
||||
"module": "outcome_utility",
|
||||
"frozen_ts": "2026-10-13T00:00:00Z",
|
||||
"generator": "scripts/gen_outcome_utility_truth.py",
|
||||
"pins": {"RUNE_OU_CORE": "python"},
|
||||
"cases": cases,
|
||||
}
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text(json.dumps(doc, indent=1) + "\n")
|
||||
print(f"frozen {len(cases)} cases -> {OUT}")
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze the pane_authority oracle truth (dichotomy wave, #2479).
|
||||
|
||||
Generator law: RUNE_PA_CORE=python pinned, every input deep-copied, every
|
||||
public function exercised across ALL branches:
|
||||
* classify_utterance — control first-word (stop/pause/cancel), control
|
||||
phrases mid-utterance (incl. the verb-"stop" law for phrase matches),
|
||||
every amendment keyword, discussion, empty/None text, case+space
|
||||
* apply_to_task — control from either pane (status mapping law),
|
||||
CHAT amendment (needs_confirm=True, not applied), CHAT discussion,
|
||||
WORK amendment (directive set, applied), WORK discussion
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rune"))
|
||||
os.environ["RUNE_PA_CORE"] = "python"
|
||||
|
||||
import pane_authority as pa # noqa: E402
|
||||
|
||||
cases_classify = []
|
||||
cases_apply = []
|
||||
|
||||
|
||||
def case_classify(name, text):
|
||||
text = copy.deepcopy(text)
|
||||
cases_classify.append({
|
||||
"name": name,
|
||||
"args": {"text": text},
|
||||
"expect": pa.classify_utterance(copy.deepcopy(text)),
|
||||
})
|
||||
|
||||
|
||||
def case_apply(name, pane, task, utterance):
|
||||
pane = copy.deepcopy(pane)
|
||||
task = copy.deepcopy(task)
|
||||
utterance = copy.deepcopy(utterance)
|
||||
cases_apply.append({
|
||||
"name": name,
|
||||
"args": {"pane": pane, "task": task, "utterance": utterance},
|
||||
"expect": pa.apply_to_task(copy.deepcopy(pane),
|
||||
copy.deepcopy(task),
|
||||
copy.deepcopy(utterance)),
|
||||
})
|
||||
|
||||
|
||||
# ── classify_utterance: every branch ────────────────────────────────────────
|
||||
case_classify("control-first-stop", "stop everything")
|
||||
case_classify("control-first-pause", "pause the pipeline")
|
||||
case_classify("control-first-cancel", "cancel that")
|
||||
case_classify("control-phrase-stop-the-task", "please stop the task now")
|
||||
case_classify("control-phrase-cancel-everything", "actually, cancel everything for me")
|
||||
case_classify("control-phrase-pause-everything", "we should pause everything here")
|
||||
case_classify("control-case-and-spaces", " STOP ")
|
||||
case_classify("amendment-change-the-task", "change the task to do w")
|
||||
case_classify("amendment-amend", "amend the plan")
|
||||
case_classify("amendment-update-the-task", "can you update the task status?")
|
||||
case_classify("amendment-instead-do", "instead do the other thing")
|
||||
case_classify("amendment-actually-do", "actually do z instead")
|
||||
case_classify("discussion", "what if we tried y?")
|
||||
case_classify("empty-text", "")
|
||||
case_classify("none-text", None)
|
||||
case_classify("whitespace-only", " ")
|
||||
|
||||
# ── apply_to_task: every branch ─────────────────────────────────────────────
|
||||
TASK = {"status": "running", "directive": "do x"}
|
||||
case_apply("work-control-stop", "work", TASK, "stop everything")
|
||||
case_apply("chat-control-cancel", "chat", TASK, "cancel that")
|
||||
case_apply("work-control-pause", "work", {"status": "running"}, "pause the run")
|
||||
case_apply("chat-amendment-promotes-visibly", "chat", {"status": "running"},
|
||||
"actually do z instead")
|
||||
case_apply("chat-discussion-never-mutates", "chat", TASK, "what if we tried y?")
|
||||
case_apply("work-amendment-applies-directly", "work", {"status": "running"},
|
||||
"change the task to do w")
|
||||
case_apply("work-discussion", "work", TASK, "thoughts on the approach?")
|
||||
case_apply("unknown-pane-discussion", "review", TASK, "hmm")
|
||||
case_apply("empty-task-chat-discussion", "chat", {}, "hello there")
|
||||
case_apply("control-without-status-key", "chat", {}, "stop")
|
||||
|
||||
truth = {
|
||||
"module": "pane_authority",
|
||||
"core_pin": "RUNE_PA_CORE=python",
|
||||
"classify_cases": cases_classify,
|
||||
"apply_cases": cases_apply,
|
||||
}
|
||||
|
||||
out = REPO / "src" / "rust" / "pane_authority" / "frozen_truth.json"
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(json.dumps(truth, indent=1, sort_keys=True) + "\n",
|
||||
encoding="utf-8")
|
||||
n = len(cases_classify) + len(cases_apply)
|
||||
print(f"frozen {n} cases -> {out}")
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.252.0"
|
||||
__version__ = "0.255.0"
|
||||
|
||||
# One build identity (handoff 2026-09-10 §4): the semantic version above is
|
||||
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
|
||||
|
||||
@@ -233,7 +233,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.252.0"' in rune_version,
|
||||
'__version__ = "0.255.0"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust outcome_utility core (dichotomy wave,
|
||||
batch-h). Mirrors broker_loader.py's contract:
|
||||
|
||||
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
|
||||
* ou_apply_outcome({utility, outcome}) -> number
|
||||
* ou_apply_stale({utility, stale_turns}) -> number
|
||||
* ou_retrieval_alone({utility}) -> number
|
||||
* ou_record_verified_outcome({entry_id, outcome, store})
|
||||
-> {"summary": {...}, "store": {...}}
|
||||
|
||||
Free: ou_free_string (same crate). Override the .so with OU_RUST_LIB (a
|
||||
FILE path). RUNE_OU_CORE=python pins the Python oracle — the pin is
|
||||
honored HERE so every consumer gets it (strangler law: the fallback is
|
||||
load-bearing until deletion is proven).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_REPO = Path(__file__).resolve().parents[2]
|
||||
_CRATE_DIR = _REPO / "src" / "rust" / "outcome_utility"
|
||||
_LIB_PATH = Path(os.environ.get("OU_RUST_LIB", _CRATE_DIR / "target" / "release" / "liboutcome_utility.so"))
|
||||
|
||||
_lib = None
|
||||
|
||||
|
||||
class OutcomeUtilityUnavailable(FileNotFoundError):
|
||||
"""The Rust utility core is not built. Callers fall back to Python."""
|
||||
|
||||
|
||||
def available() -> bool:
|
||||
if os.environ.get("RUNE_OU_CORE") == "python":
|
||||
return False
|
||||
return _LIB_PATH.is_file()
|
||||
|
||||
|
||||
def core():
|
||||
"""The live Rust core probe — the loader MODULE itself (never the raw
|
||||
CDLL), or None when the Python pin is set or the crate is not built."""
|
||||
if not available():
|
||||
return None
|
||||
try:
|
||||
_load()
|
||||
except (OSError, OutcomeUtilityUnavailable):
|
||||
return None
|
||||
return sys.modules[__name__]
|
||||
|
||||
|
||||
def _load() -> ctypes.CDLL:
|
||||
global _lib
|
||||
if _lib is not None:
|
||||
return _lib
|
||||
if not _LIB_PATH.is_file():
|
||||
raise OutcomeUtilityUnavailable(
|
||||
f"Rust outcome_utility core not built: {_LIB_PATH} missing — run "
|
||||
f"`cargo build --release` in {_CRATE_DIR}"
|
||||
)
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in ("ou_apply_outcome", "ou_apply_stale", "ou_retrieval_alone",
|
||||
"ou_record_verified_outcome"):
|
||||
fn = getattr(lib, sym)
|
||||
fn.argtypes = [ctypes.c_void_p]
|
||||
fn.restype = ctypes.c_void_p
|
||||
lib.ou_free_string.argtypes = [ctypes.c_void_p]
|
||||
lib.ou_free_string.restype = None
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call(sym: str, payload: dict):
|
||||
lib = _load()
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{sym} returned null")
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
lib.ou_free_string(ptr)
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
raise RuntimeError(doc.get("err", f"{sym} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def apply_outcome(utility: float, outcome: str) -> float:
|
||||
return _call("ou_apply_outcome", {"utility": utility, "outcome": outcome})
|
||||
|
||||
|
||||
def apply_stale(utility: float, stale_turns: int) -> float:
|
||||
return _call("ou_apply_stale", {"utility": utility, "stale_turns": stale_turns})
|
||||
|
||||
|
||||
def retrieval_alone(utility: float) -> float:
|
||||
return _call("ou_retrieval_alone", {"utility": utility})
|
||||
|
||||
|
||||
def record_verified_outcome(entry_id: int, outcome: str, store: dict) -> dict:
|
||||
return _call("ou_record_verified_outcome", {
|
||||
"entry_id": entry_id, "outcome": outcome, "store": store,
|
||||
})
|
||||
@@ -14,10 +14,25 @@ Utility rule (deterministic):
|
||||
|
||||
Confidence (epistemic) is tracked separately and never mixed into the
|
||||
utility number.
|
||||
|
||||
Dichotomy wave (batch-h): the utility DECISION MATH is served by the
|
||||
Rust `outcome_utility` cdylib through ou_loader.py when built; the
|
||||
utility TABLE stays Python (storage stays in the caller's language —
|
||||
the injected `store` dict is never shipped over the FFI boundary).
|
||||
RUNE_OU_CORE=python pins the Python oracle below — the pin is honored
|
||||
inside ou_loader so every consumer gets it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from . import ou_loader
|
||||
except ImportError: # direct script execution
|
||||
try:
|
||||
import ou_loader # type: ignore[no-redef]
|
||||
except ImportError: # noqa: S110 — no loader on path → Python oracle below
|
||||
ou_loader = None # type: ignore[assignment]
|
||||
|
||||
UTILITY_CAP = 1.0
|
||||
UTILITY_FLOOR = -1.0
|
||||
HELPFUL_DELTA = 0.1
|
||||
@@ -26,8 +41,23 @@ STALE_DELTA = -0.02
|
||||
DECAY_PER_TURN = 0.99
|
||||
|
||||
|
||||
def _core():
|
||||
"""The Rust outcome-utility probe (strangler law). RUNE_OU_CORE=
|
||||
python pins the Python oracle — honored inside ou_loader. Returns
|
||||
the loader MODULE surface (never the raw CDLL)."""
|
||||
if ou_loader is None:
|
||||
return None
|
||||
try:
|
||||
return ou_loader if ou_loader.core() is not None else None
|
||||
except Exception: # noqa: BLE001 — kernel failure → oracle below
|
||||
return None
|
||||
|
||||
|
||||
def apply_outcome(utility: float, outcome: str) -> float:
|
||||
"""Apply a verified outcome to a utility value."""
|
||||
c = _core()
|
||||
if c is not None:
|
||||
return c.apply_outcome(utility, outcome)
|
||||
if outcome == "helpful":
|
||||
return min(UTILITY_CAP, utility + HELPFUL_DELTA)
|
||||
if outcome == "ignored":
|
||||
@@ -37,6 +67,9 @@ def apply_outcome(utility: float, outcome: str) -> float:
|
||||
|
||||
def apply_stale(utility: float, stale_turns: int) -> float:
|
||||
"""Bounded decay for stale activation."""
|
||||
c = _core()
|
||||
if c is not None:
|
||||
return c.apply_stale(utility, stale_turns)
|
||||
decayed = utility * (DECAY_PER_TURN ** max(0, stale_turns))
|
||||
decayed += STALE_DELTA * max(0, stale_turns)
|
||||
return max(UTILITY_FLOOR, min(UTILITY_CAP, decayed))
|
||||
@@ -44,16 +77,24 @@ def apply_stale(utility: float, stale_turns: int) -> float:
|
||||
|
||||
def retrieval_alone(utility: float) -> float:
|
||||
"""Mere retrieval never increases utility (the anti-salience rule)."""
|
||||
c = _core()
|
||||
if c is not None:
|
||||
return c.retrieval_alone(utility)
|
||||
return utility
|
||||
|
||||
|
||||
def record_verified_outcome(entry_id: int, outcome: str,
|
||||
store: dict | None = None) -> dict:
|
||||
"""Record a verified outcome for an entry's utility. `store` is the
|
||||
injected utility table (production passes the DB-backed one)."""
|
||||
injected utility table (production passes the DB-backed one). The
|
||||
table stays Python; the outcome math delegates to the Rust core."""
|
||||
store = store if store is not None else {}
|
||||
c = _core()
|
||||
current = store.get(entry_id, {"utility": 0.0, "confidence": 0.5})
|
||||
current["utility"] = apply_outcome(current.get("utility", 0.0), outcome)
|
||||
if c is not None:
|
||||
current["utility"] = c.apply_outcome(current.get("utility", 0.0), outcome)
|
||||
else:
|
||||
current["utility"] = apply_outcome(current.get("utility", 0.0), outcome)
|
||||
store[entry_id] = current
|
||||
return {"entry_id": entry_id, "utility": current["utility"],
|
||||
"confidence": current.get("confidence", 0.5)}
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust pane_authority core (dichotomy wave, #2479
|
||||
tranche). Mirrors broker_loader.py's contract:
|
||||
|
||||
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
|
||||
* pa_classify_utterance({text}) -> {kind, verb?, priority}
|
||||
* pa_apply_to_task({pane, task, utterance}) -> {task, amendment, applied}
|
||||
|
||||
Free: capability_scoring's cs_free_string (shared process). Override the
|
||||
.so with PA_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" / "pane_authority"
|
||||
_LIB_PATH = Path(
|
||||
os.environ.get("PA_RUST_LIB", _CRATE_DIR / "target" / "release" / "libpane_authority.so")
|
||||
)
|
||||
|
||||
_ERR_TYPES = {
|
||||
"ValueError": ValueError,
|
||||
"KeyError": KeyError,
|
||||
"AttributeError": AttributeError,
|
||||
"TypeError": TypeError,
|
||||
}
|
||||
|
||||
_lib = None
|
||||
|
||||
|
||||
class PaneAuthorityCoreUnavailable(FileNotFoundError):
|
||||
"""The Rust pane-authority 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 PaneAuthorityCoreUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in ("pa_classify_utterance", "pa_apply_to_task"):
|
||||
getattr(lib, sym).argtypes = [ctypes.c_void_p]
|
||||
getattr(lib, sym).restype = ctypes.c_void_p
|
||||
try:
|
||||
lib.cs_free_string
|
||||
except AttributeError:
|
||||
scoring = ctypes.CDLL(str(
|
||||
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
|
||||
))
|
||||
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
|
||||
lib._free = scoring.cs_free_string
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call(sym: str, payload: dict):
|
||||
lib = _load()
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{sym} returned null")
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
free = getattr(lib, "_free", None)
|
||||
if free is not None:
|
||||
free(ptr)
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
etype = doc.get("type", "RuntimeError")
|
||||
raise _ERR_TYPES.get(etype, RuntimeError)(doc.get("err", f"{sym} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def classify_utterance(text: str | None) -> dict:
|
||||
return _call("pa_classify_utterance", {"text": text})
|
||||
|
||||
|
||||
def apply_to_task(pane: str, task: dict, utterance: str | None) -> dict:
|
||||
return _call("pa_apply_to_task", {"pane": pane, "task": task,
|
||||
"utterance": utterance})
|
||||
@@ -15,6 +15,8 @@ Authority rules:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
WORK = "work"
|
||||
CHAT = "chat"
|
||||
|
||||
@@ -25,8 +27,31 @@ AUTHORITY = {WORK: 2, CHAT: 1}
|
||||
CONTROL = {"stop", "pause", "cancel"}
|
||||
|
||||
|
||||
def _kernel():
|
||||
"""The Rust pane-authority core (pa_loader) when RUNE_PA_CORE
|
||||
allows it, else None — the classification and apply laws delegate
|
||||
there first and fall back to the Python body below (same
|
||||
semantics). Pure deterministic law — the whole module crosses FFI."""
|
||||
if os.environ.get("RUNE_PA_CORE", "auto") == "python":
|
||||
return None
|
||||
try:
|
||||
from rune import pa_loader
|
||||
|
||||
if pa_loader.available():
|
||||
return pa_loader
|
||||
except Exception: # noqa: BLE001 — kernel failure → Python floor
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def classify_utterance(text: str) -> dict:
|
||||
"""Classify an utterance: control verb? amendment? discussion?"""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
return k.classify_utterance(text)
|
||||
except Exception: # noqa: BLE001 — kernel floor
|
||||
pass
|
||||
low = (text or "").lower().strip()
|
||||
first = low.split()[0] if low.split() else ""
|
||||
if first in CONTROL or any(f"{c} the task" in low or f"{c} everything" in low for c in CONTROL):
|
||||
@@ -42,6 +67,12 @@ def apply_to_task(pane: str, task: dict, utterance: str) -> dict:
|
||||
"""Apply an utterance from a pane to a task. Returns
|
||||
{task, amendment, applied}. CHAT discussion never mutates; CHAT
|
||||
amendment promotes visibly; WORK applies directly; control preempts."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
return k.apply_to_task(pane, task, utterance)
|
||||
except Exception: # noqa: BLE001 — kernel floor
|
||||
pass
|
||||
cls = classify_utterance(utterance)
|
||||
task = dict(task)
|
||||
if cls["kind"] == "control":
|
||||
|
||||
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
|
||||
|
||||
This is not an interface. This is how I see, speak, and interact with the world.
|
||||
|
||||
Version: v0.252.0
|
||||
Version: v0.255.0
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -2037,7 +2037,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.252.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.255.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
Generated
+135
@@ -0,0 +1,135 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "outcome_utility"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "outcome_utility"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
serde_json = { version = "1", features = ["preserve_order", "float_roundtrip"] }
|
||||
@@ -0,0 +1,76 @@
|
||||
//! Frozen-truth battery — the frozen_outcome_utility_truth.json corpus
|
||||
//! (generated against the LIVE Python oracle with RUNE_OU_CORE=python
|
||||
//! pinned) replayed VERBATIM through the Rust core. Bit-exact float
|
||||
//! comparison; the corpus is embedded at compile time.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
const TRUTH: &str = include_str!("frozen_outcome_utility_truth.json");
|
||||
|
||||
fn f(d: &Value, k: &str) -> f64 {
|
||||
d.get(k).expect(k).as_f64().expect(k)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_truth_replays_verbatim() {
|
||||
let doc: Value = serde_json::from_str(TRUTH).expect("frozen truth parses");
|
||||
assert_eq!(doc["module"], "outcome_utility");
|
||||
assert_eq!(doc["pins"]["RUNE_OU_CORE"], "python");
|
||||
let cases = doc["cases"].as_array().expect("cases array");
|
||||
assert!(!cases.is_empty(), "corpus must not be empty");
|
||||
let mut replayed = 0usize;
|
||||
for c in cases {
|
||||
let name = c["name"].as_str().unwrap_or("?").to_string();
|
||||
let args = &c["args"];
|
||||
let expect = &c["expect"];
|
||||
match c["fn"].as_str().expect("fn tag") {
|
||||
"apply_outcome" => {
|
||||
let got = crate::apply_outcome(f(args, "utility"), args["outcome"].as_str().unwrap());
|
||||
assert_eq!(Some(got), expect.as_f64(), "case {name}");
|
||||
}
|
||||
"apply_stale" => {
|
||||
let got = crate::apply_stale(
|
||||
f(args, "utility"),
|
||||
args["stale_turns"].as_i64().expect("stale_turns"),
|
||||
);
|
||||
assert_eq!(Some(got), expect.as_f64(), "case {name}");
|
||||
}
|
||||
"retrieval_alone" => {
|
||||
let got = crate::retrieval_alone(f(args, "utility"));
|
||||
assert_eq!(Some(got), expect.as_f64(), "case {name}");
|
||||
}
|
||||
"record_verified_outcome" => {
|
||||
let mut store = match args.get("store") {
|
||||
None | Some(Value::Null) => serde_json::Map::new(),
|
||||
Some(Value::Object(m)) => m.clone(),
|
||||
Some(_) => panic!("case {name}: store must be object/null"),
|
||||
};
|
||||
let got = crate::record_verified_outcome(
|
||||
args["entry_id"].as_i64().expect("entry_id"),
|
||||
args["outcome"].as_str().expect("outcome"),
|
||||
&mut store,
|
||||
);
|
||||
let want_summary = &expect["summary"];
|
||||
assert_eq!(got["entry_id"], want_summary["entry_id"], "case {name}");
|
||||
assert_eq!(
|
||||
got["utility"].as_f64(),
|
||||
want_summary["utility"].as_f64(),
|
||||
"case {name}"
|
||||
);
|
||||
assert_eq!(
|
||||
got["confidence"].as_f64(),
|
||||
want_summary["confidence"].as_f64(),
|
||||
"case {name}"
|
||||
);
|
||||
assert_eq!(
|
||||
Value::Object(store.clone()),
|
||||
expect["store"],
|
||||
"case {name}: store mutation diverged"
|
||||
);
|
||||
}
|
||||
other => panic!("unknown frozen fn {other}"),
|
||||
}
|
||||
replayed += 1;
|
||||
}
|
||||
assert_eq!(replayed, cases.len());
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,263 @@
|
||||
//! outcome_utility — #35: outcome-learned utility (not retrieval
|
||||
//! reinforcement). PARITY port of src/rune/outcome_utility.py
|
||||
//! (dichotomy wave, batch-h; Rust verdict per
|
||||
//! docs/language-dichotomy-ledger.md Law 4 / W6: deterministic
|
||||
//! scoring/clamping compute).
|
||||
//!
|
||||
//! A memory gains value ONLY after a VERIFIED helpful outcome; ignored
|
||||
//! outcomes earn NEGATIVE utility; stale activation decays bounded;
|
||||
//! mere retrieval NEVER changes utility. Epistemic confidence stays
|
||||
//! separate from attentional utility.
|
||||
//!
|
||||
//! FFI contract (doc-envelope law — every symbol takes one null-terminated
|
||||
//! JSON payload and returns one null-terminated JSON doc-envelope
|
||||
//! {"ok": true, "value": ...} | {"ok": false, "err": "..."}):
|
||||
//! * ou_apply_outcome({utility, outcome}) -> number
|
||||
//! * ou_apply_stale({utility, stale_turns}) -> number
|
||||
//! * ou_retrieval_alone({utility}) -> number
|
||||
//! * ou_record_verified_outcome({entry_id, outcome, store})
|
||||
//! -> {"summary": {...}, "store": {...}}
|
||||
//! * ou_free_string(ptr)
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
pub const UTILITY_CAP: f64 = 1.0;
|
||||
pub const UTILITY_FLOOR: f64 = -1.0;
|
||||
pub const HELPFUL_DELTA: f64 = 0.1;
|
||||
pub const IGNORED_DELTA: f64 = -0.05;
|
||||
pub const STALE_DELTA: f64 = -0.02;
|
||||
pub const DECAY_PER_TURN: f64 = 0.99;
|
||||
|
||||
/// A verified outcome, classified once and matched exhaustively — the
|
||||
/// unknown bucket can never silently grow a new arm.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Outcome {
|
||||
Helpful,
|
||||
Ignored,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&str> for Outcome {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"helpful" => Outcome::Helpful,
|
||||
"ignored" => Outcome::Ignored,
|
||||
_ => Outcome::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_utility(v: f64) -> f64 {
|
||||
UTILITY_FLOOR.max(UTILITY_CAP.min(v))
|
||||
}
|
||||
|
||||
/// Apply a verified outcome to a utility value.
|
||||
pub fn apply_outcome(utility: f64, outcome: &str) -> f64 {
|
||||
match Outcome::from(outcome) {
|
||||
Outcome::Helpful => UTILITY_CAP.min(utility + HELPFUL_DELTA),
|
||||
Outcome::Ignored => UTILITY_FLOOR.max(utility + IGNORED_DELTA),
|
||||
Outcome::Unknown => utility, // no change
|
||||
}
|
||||
}
|
||||
|
||||
/// Bounded decay for stale activation.
|
||||
pub fn apply_stale(utility: f64, stale_turns: i64) -> f64 {
|
||||
let turns = stale_turns.max(0) as f64;
|
||||
// CPython float ** int routes through C pow(); powf matches bit-for-bit.
|
||||
let decayed = utility * DECAY_PER_TURN.powf(turns) + STALE_DELTA * turns;
|
||||
clamp_utility(decayed)
|
||||
}
|
||||
|
||||
/// Mere retrieval never increases utility (the anti-salience rule).
|
||||
pub fn retrieval_alone(utility: f64) -> f64 {
|
||||
utility
|
||||
}
|
||||
|
||||
/// Record a verified outcome for an entry's utility. `store` is the
|
||||
/// injected utility table keyed by the entry id's decimal form.
|
||||
/// Byte-parity notes vs the Python oracle:
|
||||
/// * a NEW entry is seeded {"utility": 0.0, "confidence": 0.5};
|
||||
/// * an EXISTING entry keeps exactly the keys it had (a missing
|
||||
/// "confidence" reports 0.5 in the summary but is NOT written back);
|
||||
/// * "utility" defaults to 0.0 when absent.
|
||||
pub fn record_verified_outcome(
|
||||
entry_id: i64,
|
||||
outcome: &str,
|
||||
store: &mut Map<String, Value>,
|
||||
) -> Value {
|
||||
let key = entry_id.to_string();
|
||||
let current = if let Some(v) = store.get_mut(&key) {
|
||||
v
|
||||
} else {
|
||||
store.insert(key.clone(), json!({"utility": 0.0, "confidence": 0.5}));
|
||||
store.get_mut(&key).unwrap()
|
||||
};
|
||||
let util = current.get("utility").and_then(Value::as_f64).unwrap_or(0.0);
|
||||
let new_util = apply_outcome(util, outcome);
|
||||
current["utility"] = json!(new_util);
|
||||
let confidence = store[&key]
|
||||
.get("confidence")
|
||||
.and_then(Value::as_f64)
|
||||
.unwrap_or(0.5);
|
||||
json!({
|
||||
"entry_id": entry_id,
|
||||
"utility": new_util,
|
||||
"confidence": confidence,
|
||||
})
|
||||
}
|
||||
|
||||
// ── FFI (doc-envelope) ──────────────────────────────────────────────────────
|
||||
|
||||
fn parse(arg: *const i8) -> Result<Value, String> {
|
||||
if arg.is_null() {
|
||||
return Err("null payload".into());
|
||||
}
|
||||
let cstr = unsafe { std::ffi::CStr::from_ptr(arg) };
|
||||
let bytes = cstr.to_bytes();
|
||||
serde_json::from_slice(bytes).map_err(|e| format!("bad payload: {e}"))
|
||||
}
|
||||
|
||||
fn emit(value: Result<Value, String>) -> *mut i8 {
|
||||
let doc = match value {
|
||||
Ok(v) => json!({"ok": true, "value": v}),
|
||||
Err(e) => json!({"ok": false, "err": e}),
|
||||
};
|
||||
let s = std::ffi::CString::new(doc.to_string()).unwrap();
|
||||
s.into_raw()
|
||||
}
|
||||
|
||||
fn num(doc: &Map<String, Value>, key: &str) -> Result<f64, String> {
|
||||
doc.get(key)
|
||||
.and_then(Value::as_f64)
|
||||
.ok_or_else(|| format!("missing/invalid '{key}'"))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The payload must be a valid null-terminated UTF-8 JSON string.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ou_apply_outcome(arg: *const i8) -> *mut i8 {
|
||||
let out = parse(arg).and_then(|d| {
|
||||
let o = d.as_object().ok_or("payload must be an object")?;
|
||||
let u = num(o, "utility")?;
|
||||
let outcome = o
|
||||
.get("outcome")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("missing/invalid 'outcome'")?;
|
||||
Ok(json!(apply_outcome(u, outcome)))
|
||||
});
|
||||
emit(out)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The payload must be a valid null-terminated UTF-8 JSON string.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ou_apply_stale(arg: *const i8) -> *mut i8 {
|
||||
let out = parse(arg).and_then(|d| {
|
||||
let o = d.as_object().ok_or("payload must be an object")?;
|
||||
let u = num(o, "utility")?;
|
||||
let t = o
|
||||
.get("stale_turns")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or("missing/invalid 'stale_turns'")?;
|
||||
Ok(json!(apply_stale(u, t)))
|
||||
});
|
||||
emit(out)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The payload must be a valid null-terminated UTF-8 JSON string.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ou_retrieval_alone(arg: *const i8) -> *mut i8 {
|
||||
let out = parse(arg).and_then(|d| {
|
||||
let o = d.as_object().ok_or("payload must be an object")?;
|
||||
let u = num(o, "utility")?;
|
||||
Ok(json!(retrieval_alone(u)))
|
||||
});
|
||||
emit(out)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The payload must be a valid null-terminated UTF-8 JSON string.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ou_record_verified_outcome(arg: *const i8) -> *mut i8 {
|
||||
let out = parse(arg).and_then(|d| {
|
||||
let o = d.as_object().ok_or("payload must be an object")?;
|
||||
let id = o
|
||||
.get("entry_id")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or("missing/invalid 'entry_id'")?;
|
||||
let outcome = o
|
||||
.get("outcome")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("missing/invalid 'outcome'")?;
|
||||
let mut store = match o.get("store") {
|
||||
None | Some(Value::Null) => Map::new(),
|
||||
Some(Value::Object(m)) => m.clone(),
|
||||
Some(_) => return Err("'store' must be an object".into()),
|
||||
};
|
||||
let summary = record_verified_outcome(id, outcome, &mut store);
|
||||
Ok(json!({
|
||||
"summary": summary,
|
||||
"store": Value::Object(store.clone()),
|
||||
}))
|
||||
});
|
||||
emit(out)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `ptr` must have been returned by one of the ou_* symbols above.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ou_free_string(ptr: *mut i8) {
|
||||
if !ptr.is_null() {
|
||||
drop(std::ffi::CString::from_raw(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod battery;
|
||||
|
||||
#[cfg(test)]
|
||||
mod phaseb_units {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn outcome_classification_is_exhaustive() {
|
||||
assert_eq!(Outcome::from("helpful"), Outcome::Helpful);
|
||||
assert_eq!(Outcome::from("ignored"), Outcome::Ignored);
|
||||
for s in ["", "unknown", "HELPFUL", "partial", "verified"] {
|
||||
assert_eq!(Outcome::from(s), Outcome::Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helpful_never_exceeds_cap() {
|
||||
let mut u = 0.0f64;
|
||||
for _ in 0..1000 {
|
||||
u = apply_outcome(u, "helpful");
|
||||
}
|
||||
assert_eq!(u, UTILITY_CAP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignored_never_below_floor() {
|
||||
let mut u = 0.0f64;
|
||||
for _ in 0..1000 {
|
||||
u = apply_outcome(u, "ignored");
|
||||
}
|
||||
assert_eq!(u, UTILITY_FLOOR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_decay_is_bounded_both_ends() {
|
||||
assert_eq!(apply_stale(1.0, 0), 1.0);
|
||||
assert_eq!(apply_stale(0.0, 1_000_000), UTILITY_FLOOR);
|
||||
assert_eq!(apply_stale(0.0, -7), 0.0); // negative turns clamp to zero
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_is_identity() {
|
||||
for u in [0.0, 1.0, -1.0, 0.42, -0.77] {
|
||||
assert_eq!(retrieval_alone(u), u);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "evidence_provenance"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "pane_authority"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"evidence_provenance",
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "pane_authority"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "FULL parity port of src/rune/pane_authority.py (dichotomy wave, #2479 tranche)"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
evidence_provenance = { path = "../evidence_provenance" }
|
||||
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"apply_cases": [
|
||||
{
|
||||
"args": {
|
||||
"pane": "work",
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "stop everything"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": {
|
||||
"from_pane": "work",
|
||||
"kind": "control",
|
||||
"verb": "stop"
|
||||
},
|
||||
"applied": true,
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "stopped"
|
||||
}
|
||||
},
|
||||
"name": "work-control-stop"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "chat",
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "cancel that"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": {
|
||||
"from_pane": "chat",
|
||||
"kind": "control",
|
||||
"verb": "cancel"
|
||||
},
|
||||
"applied": true,
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "cancelped"
|
||||
}
|
||||
},
|
||||
"name": "chat-control-cancel"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "work",
|
||||
"task": {
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "pause the run"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": {
|
||||
"from_pane": "work",
|
||||
"kind": "control",
|
||||
"verb": "pause"
|
||||
},
|
||||
"applied": true,
|
||||
"task": {
|
||||
"status": "paused"
|
||||
}
|
||||
},
|
||||
"name": "work-control-pause"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "chat",
|
||||
"task": {
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "actually do z instead"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": {
|
||||
"from_pane": "chat",
|
||||
"kind": "amendment",
|
||||
"needs_confirm": true,
|
||||
"text": "actually do z instead"
|
||||
},
|
||||
"applied": false,
|
||||
"task": {
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
"name": "chat-amendment-promotes-visibly"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "chat",
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "what if we tried y?"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": null,
|
||||
"applied": false,
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
"name": "chat-discussion-never-mutates"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "work",
|
||||
"task": {
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "change the task to do w"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": {
|
||||
"from_pane": "work",
|
||||
"kind": "amendment",
|
||||
"needs_confirm": false,
|
||||
"text": "change the task to do w"
|
||||
},
|
||||
"applied": true,
|
||||
"task": {
|
||||
"directive": "change the task to do w",
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
"name": "work-amendment-applies-directly"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "work",
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "thoughts on the approach?"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": null,
|
||||
"applied": false,
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
"name": "work-discussion"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "review",
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
},
|
||||
"utterance": "hmm"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": null,
|
||||
"applied": false,
|
||||
"task": {
|
||||
"directive": "do x",
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
"name": "unknown-pane-discussion"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "chat",
|
||||
"task": {},
|
||||
"utterance": "hello there"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": null,
|
||||
"applied": false,
|
||||
"task": {}
|
||||
},
|
||||
"name": "empty-task-chat-discussion"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"pane": "chat",
|
||||
"task": {},
|
||||
"utterance": "stop"
|
||||
},
|
||||
"expect": {
|
||||
"amendment": {
|
||||
"from_pane": "chat",
|
||||
"kind": "control",
|
||||
"verb": "stop"
|
||||
},
|
||||
"applied": true,
|
||||
"task": {
|
||||
"status": "stopped"
|
||||
}
|
||||
},
|
||||
"name": "control-without-status-key"
|
||||
}
|
||||
],
|
||||
"classify_cases": [
|
||||
{
|
||||
"args": {
|
||||
"text": "stop everything"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "stop"
|
||||
},
|
||||
"name": "control-first-stop"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "pause the pipeline"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "pause"
|
||||
},
|
||||
"name": "control-first-pause"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "cancel that"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "cancel"
|
||||
},
|
||||
"name": "control-first-cancel"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "please stop the task now"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "stop"
|
||||
},
|
||||
"name": "control-phrase-stop-the-task"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "actually, cancel everything for me"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "stop"
|
||||
},
|
||||
"name": "control-phrase-cancel-everything"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "we should pause everything here"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "stop"
|
||||
},
|
||||
"name": "control-phrase-pause-everything"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": " STOP "
|
||||
},
|
||||
"expect": {
|
||||
"kind": "control",
|
||||
"priority": 3,
|
||||
"verb": "stop"
|
||||
},
|
||||
"name": "control-case-and-spaces"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "change the task to do w"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "amendment",
|
||||
"priority": 2
|
||||
},
|
||||
"name": "amendment-change-the-task"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "amend the plan"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "amendment",
|
||||
"priority": 2
|
||||
},
|
||||
"name": "amendment-amend"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "can you update the task status?"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "amendment",
|
||||
"priority": 2
|
||||
},
|
||||
"name": "amendment-update-the-task"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "instead do the other thing"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "amendment",
|
||||
"priority": 2
|
||||
},
|
||||
"name": "amendment-instead-do"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "actually do z instead"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "amendment",
|
||||
"priority": 2
|
||||
},
|
||||
"name": "amendment-actually-do"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": "what if we tried y?"
|
||||
},
|
||||
"expect": {
|
||||
"kind": "discussion",
|
||||
"priority": 1
|
||||
},
|
||||
"name": "discussion"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": ""
|
||||
},
|
||||
"expect": {
|
||||
"kind": "discussion",
|
||||
"priority": 1
|
||||
},
|
||||
"name": "empty-text"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": null
|
||||
},
|
||||
"expect": {
|
||||
"kind": "discussion",
|
||||
"priority": 1
|
||||
},
|
||||
"name": "none-text"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"text": " "
|
||||
},
|
||||
"expect": {
|
||||
"kind": "discussion",
|
||||
"priority": 1
|
||||
},
|
||||
"name": "whitespace-only"
|
||||
}
|
||||
],
|
||||
"core_pin": "RUNE_PA_CORE=python",
|
||||
"module": "pane_authority"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//! branch_battery — replay the FROZEN oracle truth VERBATIM
|
||||
//! (frozen_truth.json, generated by scripts/gen_pane_authority_truth.py
|
||||
//! against the LIVE Python oracle with RUNE_PA_CORE=python pinned and
|
||||
//! deep-copied inputs).
|
||||
|
||||
use crate::{apply_to_task, classify_utterance};
|
||||
use evidence_provenance::pyjson::{self, Json};
|
||||
|
||||
const TRUTH: &str = include_str!("../frozen_truth.json");
|
||||
|
||||
fn truth() -> Json {
|
||||
pyjson::parse(TRUTH).expect("frozen truth parses")
|
||||
}
|
||||
|
||||
fn cases(group: &str) -> Vec<Json> {
|
||||
match truth().get(group) {
|
||||
Some(Json::Arr(items)) => items.clone(),
|
||||
_ => panic!("truth group {group} missing"),
|
||||
}
|
||||
}
|
||||
|
||||
fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
|
||||
let mut cur = v;
|
||||
for k in path {
|
||||
cur = match cur {
|
||||
Json::Obj(pairs) => pairs
|
||||
.iter()
|
||||
.find(|(k2, _)| k2 == k)
|
||||
.map(|(_, x)| x)?,
|
||||
_ => return None,
|
||||
};
|
||||
}
|
||||
Some(cur)
|
||||
}
|
||||
|
||||
fn jstr(v: Option<&Json>) -> String {
|
||||
match v {
|
||||
Some(Json::Str(s)) => s.clone(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn opt_text(v: Option<&Json>) -> Option<String> {
|
||||
match v {
|
||||
Some(Json::Str(t)) => Some(t.clone()),
|
||||
_ => None, // JSON null → None (FFI opt_text law)
|
||||
}
|
||||
}
|
||||
|
||||
/// Python-value equality on Json (order-insensitive objects).
|
||||
fn peq(a: &Json, b: &Json) -> bool {
|
||||
match (a, b) {
|
||||
(Json::Null, Json::Null) => true,
|
||||
(Json::Bool(x), Json::Bool(y)) => x == y,
|
||||
(Json::Num(x), Json::Num(y)) | (Json::Float(x), Json::Float(y)) => x == y,
|
||||
(Json::Float(x), Json::Num(y)) | (Json::Num(x), Json::Float(y)) => x == y,
|
||||
(Json::Str(x), Json::Str(y)) => x == y,
|
||||
(Json::Arr(x), Json::Arr(y)) => {
|
||||
x.len() == y.len() && x.iter().zip(y).all(|(a, b)| peq(a, b))
|
||||
}
|
||||
(Json::Obj(x), Json::Obj(y)) => {
|
||||
x.len() == y.len()
|
||||
&& x.iter().all(|(k, v)| {
|
||||
y.iter()
|
||||
.find(|(k2, _)| k2 == k)
|
||||
.map(|(_, v2)| peq(v, v2))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_classify_utterance() {
|
||||
for c in cases("classify_cases") {
|
||||
let text = opt_text(at(&c, &["args", "text"]));
|
||||
let got = classify_utterance(text.as_deref());
|
||||
let want = at(&c, &["expect"]).expect("expect");
|
||||
assert!(
|
||||
peq(want, &got),
|
||||
"case {} diverged:\n want={}\n got={}",
|
||||
jstr(at(&c, &["name"])),
|
||||
pyjson::dumps(want),
|
||||
pyjson::dumps(&got),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_apply_to_task() {
|
||||
for c in cases("apply_cases") {
|
||||
let utterance = opt_text(at(&c, &["args", "utterance"]));
|
||||
let got = apply_to_task(
|
||||
&jstr(at(&c, &["args", "pane"])),
|
||||
at(&c, &["args", "task"]).expect("task"),
|
||||
utterance.as_deref(),
|
||||
);
|
||||
let want = at(&c, &["expect"]).expect("expect");
|
||||
assert!(
|
||||
peq(want, &got),
|
||||
"case {} diverged:\n want={}\n got={}",
|
||||
jstr(at(&c, &["name"])),
|
||||
pyjson::dumps(want),
|
||||
pyjson::dumps(&got),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
//! pane_authority — two-pane authority semantics (#49).
|
||||
//!
|
||||
//! FULL parity port of src/rune/pane_authority.py (dichotomy wave, #2479
|
||||
//! tranche). Pure deterministic classification/apply law — no I/O, no
|
||||
//! clock, the whole module crosses FFI.
|
||||
//!
|
||||
//! FFI (doc-envelope law): every call takes one JSON doc and returns
|
||||
//! {"ok": true, "value": ...} | {"ok": false, "err": ..., "type": ...}.
|
||||
//! * pa_classify_utterance({text}) -> {kind, verb?, priority}
|
||||
//! * pa_apply_to_task({pane, task, utterance}) -> {task, amendment,
|
||||
//! applied}
|
||||
//!
|
||||
//! Python laws replicated exactly:
|
||||
//! * `(text or "")` — null text classifies as "" (discussion)
|
||||
//! * control = first word in {stop,pause,cancel} OR any
|
||||
//! "{stop|pause|cancel} the task" / "{...} everything" substring;
|
||||
//! verb = first word when it is a control verb ELSE "stop" (even
|
||||
//! when the PHRASE matched a different control word — oracle law)
|
||||
//! * status mapping: verb + "ped" unless verb == "pause" → "paused"
|
||||
//! (so "cancel" → "cancelped" — frozen verbatim, not corrected)
|
||||
//! * CHAT amendment promotes visibly (needs_confirm=True, not
|
||||
//! applied); WORK amendment sets directive; task is a SHALLOW copy
|
||||
//! (dict(task)) in both floors
|
||||
//!
|
||||
//! NOTE: no pa_free_string export — callers free through capability_
|
||||
//! scoring's cs_free_string (shared process allocator).
|
||||
|
||||
use evidence_provenance::pyjson::{self, Json};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
fn obj(pairs: Vec<(&str, Json)>) -> Json {
|
||||
Json::Obj(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
|
||||
}
|
||||
|
||||
fn s(v: &str) -> Json {
|
||||
Json::Str(v.to_string())
|
||||
}
|
||||
|
||||
const CONTROL: [&str; 3] = ["stop", "pause", "cancel"];
|
||||
const AMENDMENT_KEYS: [&str; 5] = [
|
||||
"change the task",
|
||||
"amend",
|
||||
"update the task",
|
||||
"instead do",
|
||||
"actually do",
|
||||
];
|
||||
|
||||
/// Phase B (language advantage): the classification result is a typed
|
||||
/// verdict matched EXHAUSTIVELY downstream, not a stringly `kind` —
|
||||
/// adding a class becomes a compiler error instead of a silent fallthrough.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UtteranceKind {
|
||||
/// control verb: the effective verb (oracle law: phrase matches
|
||||
/// that are not a first-word control verb get "stop")
|
||||
Control(&'static str),
|
||||
Amendment,
|
||||
Discussion,
|
||||
}
|
||||
|
||||
/// classify_utterance law, typed core.
|
||||
pub fn classify_typed(text: Option<&str>) -> UtteranceKind {
|
||||
let low = text.unwrap_or("").to_lowercase();
|
||||
let low = low.trim();
|
||||
let first = low.split_whitespace().next().unwrap_or("");
|
||||
if CONTROL.contains(&first) {
|
||||
return UtteranceKind::Control(CONTROL.iter().find(|c| **c == first).unwrap());
|
||||
}
|
||||
for c in CONTROL {
|
||||
if low.contains(&format!("{c} the task")) || low.contains(&format!("{c} everything")) {
|
||||
return UtteranceKind::Control("stop");
|
||||
}
|
||||
}
|
||||
if AMENDMENT_KEYS.iter().any(|k| low.contains(k)) {
|
||||
return UtteranceKind::Amendment;
|
||||
}
|
||||
UtteranceKind::Discussion
|
||||
}
|
||||
|
||||
/// classify_utterance law.
|
||||
pub fn classify_utterance(text: Option<&str>) -> Json {
|
||||
match classify_typed(text) {
|
||||
UtteranceKind::Control(verb) => obj(vec![
|
||||
("kind", s("control")),
|
||||
("verb", s(verb)),
|
||||
("priority", Json::Num(3.0)),
|
||||
]),
|
||||
UtteranceKind::Amendment => {
|
||||
obj(vec![("kind", s("amendment")), ("priority", Json::Num(2.0))])
|
||||
}
|
||||
UtteranceKind::Discussion => {
|
||||
obj(vec![("kind", s("discussion")), ("priority", Json::Num(1.0))])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// apply_to_task law: {task, amendment, applied}.
|
||||
pub fn apply_to_task(pane: &str, task: &Json, utterance: Option<&str>) -> Json {
|
||||
let cls = classify_typed(utterance);
|
||||
// dict(task) — shallow copy
|
||||
let mut pairs: Vec<(String, Json)> = match task {
|
||||
Json::Obj(pairs) => pairs.clone(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
match cls {
|
||||
UtteranceKind::Control(verb) => {
|
||||
let status = if verb != "pause" {
|
||||
format!("{verb}ped")
|
||||
} else {
|
||||
"paused".to_string()
|
||||
};
|
||||
set_key(&mut pairs, "status", s(&status));
|
||||
obj(vec![
|
||||
("task", Json::Obj(pairs)),
|
||||
(
|
||||
"amendment",
|
||||
obj(vec![
|
||||
("kind", s("control")),
|
||||
("verb", s(verb)),
|
||||
("from_pane", s(pane)),
|
||||
]),
|
||||
),
|
||||
("applied", Json::Bool(true)),
|
||||
])
|
||||
}
|
||||
kind if pane == "chat" && kind == UtteranceKind::Amendment => {
|
||||
let text = utterance.unwrap_or("");
|
||||
obj(vec![
|
||||
("task", Json::Obj(pairs)),
|
||||
(
|
||||
"amendment",
|
||||
obj(vec![
|
||||
("kind", s("amendment")),
|
||||
("text", s(text)),
|
||||
("from_pane", s("chat")),
|
||||
("needs_confirm", Json::Bool(true)),
|
||||
]),
|
||||
),
|
||||
("applied", Json::Bool(false)),
|
||||
])
|
||||
}
|
||||
_ if pane == "chat" => no_amendment(pairs),
|
||||
UtteranceKind::Amendment => {
|
||||
let text = utterance.unwrap_or("");
|
||||
set_key(&mut pairs, "directive", s(text));
|
||||
obj(vec![
|
||||
("task", Json::Obj(pairs)),
|
||||
(
|
||||
"amendment",
|
||||
obj(vec![
|
||||
("kind", s("amendment")),
|
||||
("text", s(text)),
|
||||
("from_pane", s("work")),
|
||||
("needs_confirm", Json::Bool(false)),
|
||||
]),
|
||||
),
|
||||
("applied", Json::Bool(true)),
|
||||
])
|
||||
}
|
||||
_ => no_amendment(pairs),
|
||||
}
|
||||
}
|
||||
|
||||
fn no_amendment(pairs: Vec<(String, Json)>) -> Json {
|
||||
obj(vec![
|
||||
("task", Json::Obj(pairs)),
|
||||
("amendment", Json::Null),
|
||||
("applied", Json::Bool(false)),
|
||||
])
|
||||
}
|
||||
|
||||
fn set_key(pairs: &mut Vec<(String, Json)>, key: &str, value: Json) {
|
||||
if let Some(slot) = pairs.iter_mut().find(|(k, _)| k == key) {
|
||||
slot.1 = value;
|
||||
} else {
|
||||
pairs.push((key.to_string(), value));
|
||||
}
|
||||
}
|
||||
|
||||
// ── FFI (doc-envelope) ───────────────────────────────────────────────────
|
||||
|
||||
fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
|
||||
let body = match result {
|
||||
Ok(value) => obj(vec![("ok", Json::Bool(true)), ("value", value)]),
|
||||
Err((err, ty)) => obj(vec![
|
||||
("ok", Json::Bool(false)),
|
||||
("err", Json::Str(err)),
|
||||
("type", Json::Str(ty)),
|
||||
]),
|
||||
};
|
||||
let out = pyjson::dumps(&body);
|
||||
match CString::new(out) {
|
||||
Ok(c) => c.into_raw(),
|
||||
Err(_) => CString::new(
|
||||
r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#,
|
||||
)
|
||||
.unwrap()
|
||||
.into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_doc(raw: &str) -> Result<Json, (String, String)> {
|
||||
pyjson::parse(raw).map_err(|e| (e, "ValueError".to_string()))
|
||||
}
|
||||
|
||||
fn read_arg(ptr: *const c_char) -> Result<String, (String, String)> {
|
||||
if ptr.is_null() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
unsafe { CStr::from_ptr(ptr) }
|
||||
.to_str()
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|e| (e.to_string(), "ValueError".to_string()))
|
||||
}
|
||||
|
||||
fn opt_text(doc: &Json, key: &str) -> Result<Option<String>, (String, String)> {
|
||||
match doc.get(key) {
|
||||
None => Err((format!("{key} is required"), "KeyError".into())),
|
||||
Some(Json::Null) => Ok(None),
|
||||
Some(Json::Str(t)) => Ok(Some(t.clone())),
|
||||
Some(_) => Err((format!("{key} must be a string or null"), "TypeError".into())),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ffi {
|
||||
($name:ident, $body:expr) => {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn $name(arg: *const c_char) -> *mut c_char {
|
||||
let out: Result<Json, (String, String)> =
|
||||
read_arg(arg).and_then(|raw| $body(&raw));
|
||||
envelope(out)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ffi!(pa_classify_utterance, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let text = opt_text(&doc, "text")?;
|
||||
Ok(classify_utterance(text.as_deref()))
|
||||
});
|
||||
|
||||
ffi!(pa_apply_to_task, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let pane = match doc.get("pane") {
|
||||
Some(Json::Str(p)) => p.clone(),
|
||||
_ => return Err(("pane must be a string".into(), "TypeError".into())),
|
||||
};
|
||||
let task = match doc.get("task") {
|
||||
Some(t @ Json::Obj(_)) => t.clone(),
|
||||
_ => return Err(("task must be an object".into(), "TypeError".into())),
|
||||
};
|
||||
let utterance = opt_text(&doc, "utterance")?;
|
||||
Ok(apply_to_task(&pane, &task, utterance.as_deref()))
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod branch_battery;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,26 @@
|
||||
# Contract BUILDER-2026-10-13T00:04:22Z-4156e1-factory-batch-h
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Port src/rune/outcome_utility.py to FULL Rust (dichotomy wave, batch-h): doc-envelope FFI crate src/rust/outcome_utility, ctypes loader ou_loader.py mirroring broker_loader.py, facade delegation with RUNE_OU_CORE=python pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
|
||||
|
||||
## What it connects to
|
||||
|
||||
- `src/rune/ou_loader.py`
|
||||
- `src/rune/outcome_utility.py`
|
||||
- `src/rust/outcome_utility/`
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [{"entity": "commits", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}, {"entity": "frozen-truth battery", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}, {"entity": "live parity", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}, {"entity": "python tests", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}, {"entity": "rust gates", "note": "genuinely unexpected new surface \u2014 evidence required before store update", "status": "CANDIDATE"}], "captured_at": "2026-10-13T03:03:06Z", "contract": "BUILDER-2026-10-13T00:04:22Z-4156e1-factory-batch-h", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": ["commits", "frozen-truth battery", "live parity", "python tests", "rust gates"], "missed": ["commits", "frozen-truth battery", "live parity", "python tests", "rust gates"], "needless": ["module:deploy_qualify", "module:outcome_utility"], "observed": ["commits", "frozen-truth battery", "live parity", "python tests", "rust gates"], "observed_existing": [], "observed_new": ["commits", "frozen-truth battery", "live parity", "python tests", "rust gates"], "predicted_but_unaffected": ["module:deploy_qualify", "module:outcome_utility"], "predicted_existing_affected": ["module:deploy_qualify", "module:outcome_utility"], "scope_check": {"in_scope": ["src/rune/ou_loader.py", "src/rune/outcome_utility.py", "src/rust/outcome_utility/"], "out_of_scope": ["scripts/gen_outcome_utility_truth.py", "tests/test_outcome_utility_port.py"], "verdict": "red"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-batch-h
|
||||
- builder:factory-batch-h
|
||||
- builder:factory-batch-h
|
||||
- closure-record
|
||||
@@ -0,0 +1,24 @@
|
||||
# Contract BUILDER-2026-10-13T02:46:21Z-e4c445-factory-gap-pg
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Close the persona_gate dichotomy PARTIAL gap (issue #2479 tranche, GO): extend the existing Go port src/go/persona-gate with the audit-missing functions (_now, _cc_pg_core, _py_lex, _lex_input, _py_overlap, _embed_raw, _embed_vec, _cosine, _semantic_engagement, _rule_json, _py_decide, _journal, decide_and_journal, _user_rule_authorizes, _find_user_authorizing, _invoke_model, build_gate_prompt, report, handle_hook) name-preserving with logic 1:1 to the Python oracle src/rune/persona_gate.py, embedding/HTTP/subprocess/journal IO behind injectable seams, frozen-truth battery generated against the live oracle (RUNE_COGNITION_CORE=python pinned, deep-copied inputs), live parity 100% on the new functions, Phase B language-advantage pass.
|
||||
|
||||
## What it connects to
|
||||
|
||||
(no affected surface recorded)
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-13T03:10:03Z", "contract": "BUILDER-2026-10-13T02:46:21Z-e4c445-factory-gap-pg", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_persona_gate_truth.py", "scripts/open_contract_persona_gate.py", "scripts/parity_persona_gate.py", "src/go/persona-gate/gaps.go", "src/go/persona-gate/main.go", "src/go/persona-gate/phaseb.go", "src/go/persona-gate/phaseb_test.go", "src/go/persona-gate/truth.json", "src/go/persona-gate/truth_test.go", "src/rune/persona_gate.py"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-gap-pg
|
||||
- builder:factory-gap-pg
|
||||
- builder:factory-gap-pg
|
||||
- closure-record
|
||||
@@ -0,0 +1,173 @@
|
||||
"""Tests: outcome_utility.py Rust core port (dichotomy wave, batch-h).
|
||||
|
||||
Layers, all must be green for parity 100%:
|
||||
1. FROZEN — the frozen-oracle corpus (RUNE_OU_CORE=python pinned at
|
||||
generation) replayed VERBATIM through the Rust core.
|
||||
2. PIN — RUNE_OU_CORE=python pins the Python oracle; unset uses
|
||||
the Rust core when built (strangler law).
|
||||
3. LIVE — oracle vs kernel on a seeded random sweep, bit-exact.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys_path = str(ROOT / "src")
|
||||
if sys_path not in __import__("sys").path:
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, sys_path)
|
||||
|
||||
from rune import outcome_utility as ou # noqa: E402
|
||||
from rune import ou_loader # noqa: E402
|
||||
|
||||
TRUTH = json.loads(
|
||||
(ROOT / "src" / "rust" / "outcome_utility" / "src" / "frozen_outcome_utility_truth.json")
|
||||
.read_text()
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def core_built():
|
||||
if not ou_loader.available():
|
||||
pytest.skip("rust outcome_utility core not built")
|
||||
return True
|
||||
|
||||
|
||||
def _restore_keys(store):
|
||||
"""JSON froze the oracle's int-keyed store dicts with string keys —
|
||||
restore the original key types so replay is oracle-faithful."""
|
||||
out = {}
|
||||
for k, v in store.items():
|
||||
try:
|
||||
out[int(k)] = v
|
||||
except ValueError:
|
||||
out[k] = v
|
||||
return out
|
||||
|
||||
|
||||
def _replay_row(row, target):
|
||||
"""Replay one frozen row through `target` ('rust' loader or python
|
||||
oracle module) on a DEEP COPY of the frozen args."""
|
||||
a = copy.deepcopy(row["args"])
|
||||
if row["fn"] == "apply_outcome":
|
||||
got = target.apply_outcome(a["utility"], a["outcome"])
|
||||
want = row["expect"]
|
||||
assert got == want, f"{row['name']}: {got!r} != {want!r}"
|
||||
elif row["fn"] == "apply_stale":
|
||||
got = target.apply_stale(a["utility"], a["stale_turns"])
|
||||
want = row["expect"]
|
||||
assert got == want, f"{row['name']}: {got!r} != {want!r}"
|
||||
elif row["fn"] == "retrieval_alone":
|
||||
got = target.retrieval_alone(a["utility"])
|
||||
want = row["expect"]
|
||||
assert got == want, f"{row['name']}: {got!r} != {want!r}"
|
||||
elif row["fn"] == "record_verified_outcome":
|
||||
store = a.get("store")
|
||||
if store is None:
|
||||
store = {}
|
||||
store = _restore_keys(store)
|
||||
summary = target.record_verified_outcome(a["entry_id"], a["outcome"], store)
|
||||
want = row["expect"]
|
||||
# JSON freezes int dict keys as strings — compare normalized.
|
||||
norm = lambda d: {str(k): v for k, v in d.items()}
|
||||
assert summary == want["summary"], f"{row['name']}: {summary!r} != {want['summary']!r}"
|
||||
assert norm(store) == norm(want["store"]), f"{row['name']}: store mutation diverged"
|
||||
else:
|
||||
raise AssertionError(f"unknown frozen fn {row['fn']}")
|
||||
|
||||
|
||||
class _RustSurface:
|
||||
"""The Rust core shaped like the Python module (store stays Python)."""
|
||||
|
||||
@staticmethod
|
||||
def apply_outcome(utility, outcome):
|
||||
return ou_loader.apply_outcome(utility, outcome)
|
||||
|
||||
@staticmethod
|
||||
def apply_stale(utility, stale_turns):
|
||||
return ou_loader.apply_stale(utility, stale_turns)
|
||||
|
||||
@staticmethod
|
||||
def retrieval_alone(utility):
|
||||
return ou_loader.retrieval_alone(utility)
|
||||
|
||||
@staticmethod
|
||||
def record_verified_outcome(entry_id, outcome, store=None):
|
||||
store = store if store is not None else {}
|
||||
current = store.get(entry_id, {"utility": 0.0, "confidence": 0.5})
|
||||
current["utility"] = ou_loader.apply_outcome(
|
||||
current.get("utility", 0.0), outcome)
|
||||
store[entry_id] = current
|
||||
return {"entry_id": entry_id, "utility": current["utility"],
|
||||
"confidence": current.get("confidence", 0.5)}
|
||||
|
||||
|
||||
RUST = _RustSurface()
|
||||
|
||||
|
||||
def test_frozen_truth_replays_verbatim_rust(core_built):
|
||||
for row in TRUTH["cases"]:
|
||||
_replay_row(row, RUST)
|
||||
|
||||
|
||||
def test_frozen_truth_replays_verbatim_python_oracle_sanity(core_built, monkeypatch):
|
||||
monkeypatch.setenv("RUNE_OU_CORE", "python") # the oracle, not the kernel
|
||||
import rune.outcome_utility as oracle
|
||||
|
||||
for row in TRUTH["cases"]:
|
||||
_replay_row(row, oracle)
|
||||
|
||||
|
||||
def test_pin_python_and_kernel_probe(core_built, monkeypatch):
|
||||
monkeypatch.setenv("RUNE_OU_CORE", "python")
|
||||
assert ou_loader.core() is None
|
||||
assert ou._core() is None
|
||||
monkeypatch.delenv("RUNE_OU_CORE", raising=False)
|
||||
assert ou_loader.core() is not None
|
||||
assert ou._core() is not None
|
||||
|
||||
|
||||
def test_facade_delegates_to_rust(core_built, monkeypatch):
|
||||
monkeypatch.delenv("RUNE_OU_CORE", raising=False)
|
||||
assert ou.apply_outcome(0.5, "helpful") == 0.6
|
||||
assert ou.apply_stale(0.5, 2) == ou_loader.apply_stale(0.5, 2)
|
||||
assert ou.retrieval_alone(0.42) == 0.42
|
||||
store = {1: {"utility": 0.5, "confidence": 0.8}}
|
||||
r = ou.record_verified_outcome(1, "helpful", store)
|
||||
assert r == {"entry_id": 1, "utility": 0.6, "confidence": 0.8}
|
||||
assert store[1]["utility"] == 0.6
|
||||
|
||||
|
||||
def test_facade_pin_uses_python_oracle(core_built, monkeypatch):
|
||||
monkeypatch.setenv("RUNE_OU_CORE", "python")
|
||||
assert ou.apply_outcome(0.5, "helpful") == 0.6
|
||||
assert ou.apply_stale(-0.5, 3) == -0.5451495
|
||||
store = {}
|
||||
r = ou.record_verified_outcome(9, "ignored", store)
|
||||
assert r == {"entry_id": 9, "utility": -0.05, "confidence": 0.5}
|
||||
|
||||
|
||||
def test_live_parity_seeded_sweep(core_built, monkeypatch):
|
||||
"""Oracle vs kernel, bit-exact, across a seeded random sweep."""
|
||||
monkeypatch.delenv("RUNE_OU_CORE", raising=False)
|
||||
rnd = random.Random(2479)
|
||||
for _ in range(500):
|
||||
u = rnd.uniform(-1.5, 1.5)
|
||||
outcome = rnd.choice(["helpful", "ignored", "unknown", "", "verified"])
|
||||
assert ou.apply_outcome(u, outcome) == ou_loader.apply_outcome(u, outcome)
|
||||
t = rnd.randint(-5, 400)
|
||||
assert ou.apply_stale(u, t) == ou_loader.apply_stale(u, t)
|
||||
assert ou.retrieval_alone(u) == ou_loader.retrieval_alone(u)
|
||||
store_o, store_r = {}, {}
|
||||
entry = rnd.randint(0, 3)
|
||||
out_o = ou.record_verified_outcome(entry, outcome, store_o)
|
||||
out_r = RUST.record_verified_outcome(entry, outcome, store_r)
|
||||
assert out_o == out_r
|
||||
assert store_o == store_r
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Tests: pane_authority.py Rust core port (dichotomy wave, #2479).
|
||||
|
||||
Frozen-truth battery: the 26 frozen-oracle cases replayed VERBATIM
|
||||
through the facade in BOTH core modes (RUNE_PA_CORE=python floor vs the
|
||||
Rust kernel), plus loader pin discipline and input-copy semantics.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
sys.path.insert(0, str(ROOT / "src" / "rune"))
|
||||
|
||||
import pane_authority as pa # noqa: E402
|
||||
from rune import pa_loader # noqa: E402
|
||||
|
||||
TRUTH = json.loads(
|
||||
(ROOT / "src" / "rust" / "pane_authority" / "frozen_truth.json")
|
||||
.read_text())
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def core_built():
|
||||
if not pa_loader.available():
|
||||
pytest.skip("rust pane_authority core not built")
|
||||
return True
|
||||
|
||||
|
||||
def drive(mode, fn, *args):
|
||||
os.environ["RUNE_PA_CORE"] = mode
|
||||
try:
|
||||
return fn(*copy.deepcopy(args))
|
||||
finally:
|
||||
os.environ["RUNE_PA_CORE"] = "auto"
|
||||
|
||||
|
||||
def test_loader_probe_and_pin(core_built, monkeypatch):
|
||||
monkeypatch.delenv("RUNE_PA_CORE", raising=False)
|
||||
assert pa_loader.available()
|
||||
assert pa._kernel() is not None
|
||||
monkeypatch.setenv("RUNE_PA_CORE", "python")
|
||||
assert pa._kernel() is None
|
||||
|
||||
|
||||
def test_frozen_classify_both_ways(core_built):
|
||||
for c in TRUTH["classify_cases"]:
|
||||
py = drive("python", pa.classify_utterance, c["args"]["text"])
|
||||
rs = drive("auto", pa.classify_utterance, c["args"]["text"])
|
||||
assert py == rs, c["name"]
|
||||
assert py == c["expect"], c["name"]
|
||||
|
||||
|
||||
def test_frozen_apply_both_ways(core_built):
|
||||
for c in TRUTH["apply_cases"]:
|
||||
args = (c["args"]["pane"], c["args"]["task"], c["args"]["utterance"])
|
||||
py = drive("python", pa.apply_to_task, *args)
|
||||
rs = drive("auto", pa.apply_to_task, *args)
|
||||
assert py == rs, c["name"]
|
||||
assert py == c["expect"], c["name"]
|
||||
|
||||
|
||||
def test_inputs_not_mutated(core_built):
|
||||
task = {"status": "running", "directive": "do x"}
|
||||
frozen_task = copy.deepcopy(task)
|
||||
drive("auto", pa.apply_to_task, "work", task, "stop everything")
|
||||
drive("auto", pa.apply_to_task, "work", task, "actually do z")
|
||||
assert task == frozen_task
|
||||
|
||||
|
||||
def test_control_preempts_from_either_pane(core_built):
|
||||
for pane in ("chat", "work"):
|
||||
r = drive("auto", pa.apply_to_task, pane, {"status": "running"},
|
||||
"stop everything")
|
||||
assert r["applied"] is True
|
||||
assert "stop" in r["task"]["status"]
|
||||
Reference in new issue
Block a user