Compare commits

...
7 Commits
Author SHA1 Message Date
conductor cd4c0beb2f chore(release): auto-bump version to 0.219.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 15s
Auto-version and Release / release (push) Successful in 22s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 52s
CI Pipeline / canary (push) Failing after 2m3s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 4m21s
Promotion record: (unknown)
2026-10-12 01:16:26 +01:00
Nick 9436e6ca26 refactor(outcome_ledger): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 15s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
CI Pipeline / type-check (push) Canceled after 17s
CI Pipeline / canary (push) Canceled after 21s
CI Pipeline / unit-tests (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / gitea-offline (push) Canceled after 18s
deploy / deploy (push) Canceled after 16s
2026-10-12 01:03:37 +01:00
Nick bb7251e1aa feat(outcome_ledger): port to FULL Rust — dichotomy wave 2026-10-12 01:03:37 +01:00
Nick 0cd50f46ed chore(store): close auto_context dichotomy contract BUILDER-2026-10-11T21:57:54Z-52c500-factory-wave (parity f129a6a4, phase B 7fbbdeba)
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / documentation (push) Successful in 7s
Auto-version and Release / release (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / type-check (push) Failing after 45s
CI Pipeline / canary (push) Failing after 2m4s
deploy / deploy (push) Failing after 15m28s
CI Pipeline / unit-tests (push) Failing after 16m9s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-12 00:44:35 +01:00
conductor 2e5ba22249 chore(release): auto-bump version to 0.218.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 18s
Auto-version and Release / release (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / type-check (push) Failing after 43s
CI Pipeline / canary (push) Failing after 2m14s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 3m15s
Promotion record: (unknown)
2026-10-12 00:41:12 +01:00
Nick 7fbbdeba71 refactor(auto_context): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / go-surface (push) Failing after 19s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 21s
CI Pipeline / type-check (push) Canceled after 2s
CI Pipeline / gitea-offline (push) Canceled after 2s
Auto-version and Release / release (push) Canceled after 30s
deploy / deploy (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
Language-advantage pass on the FULL Go port (truth battery stays green,
now replayed through the parallel path):
- build_auto_context fans the oracle's four independent IO calls (warm
  route, ledger read, memory recall, always digest) out to goroutines —
  wall time is the slowest call, not the sum of four subprocesses;
  results assembled in the oracle's exact field order so output is
  byte-identical to the serial Python semantics
- subprocess + clock seams promoted to interfaces (CommandRunner, Clock)
  with RunnerFunc/ClockFunc adapters and compile-time assertions
  (var _ CommandRunner = RunnerFunc(defaultRunScript), var _ Clock = ...);
  the replay seam swap now rides the same interfaces the embedded callers
  use
- save_context tmp file made per-writer (os.CreateTemp + atomic rename):
  identical serial semantics, but concurrent hooks no longer clobber the
  oracle's shared .tmp path (its fixed tmp races ENOENT under concurrency)
- Phase B battery (phaseb_test.go): parallel-build parity vs frozen truth,
  counting-decorator seam swap (3 subprocess runs per build), 8-goroutine
  concurrent builds + concurrent hooks — all race-clean under -race
2026-10-12 00:40:15 +01:00
Nick f129a6a469 feat(auto_context): port to FULL Go — dichotomy wave
Full name-preserving port of src/rune/auto_context.py to src/go/auto_context
(GO verdict, #2479 dichotomy tranche — 8/8 fns, dichotomy audit match clean):
- run_warm_router / run_memory_recall / run_always_digest: subprocess seam
  (RunScript) with the oracle's exact degrade paths (nonzero exit, timeout,
  bad JSON, empty stdout → None; non-list recall payload → []), budget
  truncation data[:budget]
- load_active_tasks: persistence-ledger filter (open/in_progress) with every
  best-effort degrade branch (missing file, malformed JSON, non-dict ledger,
  non-dict task → [])
- build_auto_context: stopword table 1:1, len>4 + isalpha + first-5 query
  extraction, 200-rune preview truncation, Python truthiness on every
  optional payload, frozen-clock isoformat
- save_context: atomic tmp+rename, json.dumps(indent=2, ensure_ascii)
  byte-parity via pyjson.go
- handle_hook: message/text/str(data) fallback chain (Python dict repr
  included), both best-effort error dicts verbatim ('list' object has no
  attribute 'get' / object of type 'int' has no len())
- CLI main(): argparse-compatible build/show/hook (--event/--data/--json),
  byte-parity stdout, exit codes (invalid choice/missing cmd → 2)
- PLUGIN manifest pinned (pluginManifest)
- Battery: 52 frozen truth rows vs the LIVE oracle (clock frozen, env-driven
  stub scripts echo the exact message/query the port hands the seam),
  deterministic across two generator runs; replay gate + TestTruthParity
2026-10-12 00:40:15 +01:00
24 changed files with 11246 additions and 103 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.217.0**
**Current release: v0.219.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.**
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.217.0"
version = "0.219.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""gen_outcome_ledger_truth.py — outcome_ledger dichotomy wave truth battery
(#2479 tranche). Drives the LIVE python oracle (RUNE_OL_CORE=python pinned)
through every public function and branch, and freezes the observations to
src/rust/outcome_ledger/src/frozen_to_truth.json.
Public surface exercised:
* OutcomeLedger.__init__ (fresh db + reopen-existing branches)
* OutcomeLedger.record (all field branches: success/fail, correction,
floats, defaults)
* OutcomeLedger.get_stats (no-family branch AND task_family branch,
empty-db zero branch AND populated branch)
* OutcomeLedger.get_model_stats (hit and miss)
* OutcomeRecord.to_dict (full record)
(test_ledger stays a facade-level python self-test — covered by live
parity, not by the kernel battery.)
Timestamps are PINNED in the inputs, so observations are fully
deterministic and need no masking.
"""
import json
import os
import sys
import tempfile
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
os.environ["RUNE_OL_CORE"] = "python"
from rune.outcome_ledger import OutcomeLedger, OutcomeRecord # noqa: E402
T0 = "2026-10-11T00:00:01+00:00"
T1 = "2026-10-11T00:00:02+00:00"
T2 = "2026-10-11T00:00:03+00:00"
T3 = "2026-10-11T00:00:04+00:00"
T4 = "2026-10-11T00:00:05+00:00"
T5 = "2026-10-11T00:00:06+00:00"
class Driver:
"""Drives op scripts against the live oracle the way the truth was
frozen — each scenario gets its own temp db path."""
def __init__(self, workdir: Path, n: int):
self.path = str(workdir / f"ol_{n}.db")
self.ledger = None
def step(self, op: dict):
kind = op["op"]
if kind == "new":
if not op.get("reuse"):
self.ledger = OutcomeLedger(self.path)
else:
# fresh instance over the SAME db (CREATE TABLE IF NOT
# EXISTS idempotent branch)
self.ledger = OutcomeLedger(self.path)
return {"result": None}
if kind == "record":
self.ledger.record(OutcomeRecord(**op["record"]))
return {"result": None}
if kind == "to_dict":
return {"result": OutcomeRecord(**op["record"]).to_dict()}
if kind == "get_stats":
fam = op.get("family")
if fam is None:
return {"result": self.ledger.get_stats()}
return {"result": self.ledger.get_stats(fam)}
if kind == "get_model_stats":
return {"result": self.ledger.get_model_stats(op["model"])}
raise ValueError(f"unknown op {kind}")
def s1_empty_db(workdir):
"""Empty-database branches of every stats path."""
d = Driver(workdir, 1)
ops = [
{"op": "new"},
{"op": "get_stats"},
{"op": "get_stats", "family": "greeting"},
{"op": "get_model_stats", "model": "qwen2.5:3b"},
]
return {"name": "s1_empty_db", "ops": ops, "obs": [d.step(o) for o in ops]}
def s2_record_and_stats(workdir):
"""The oracle's own test_ledger data, plus every stats entry point."""
d = Driver(workdir, 2)
ops = [
{"op": "new"},
{"op": "record", "record": {
"timestamp": T0, "task_family": "greeting", "model": "qwen2.5:3b",
"provider": "ollama", "tier": "T1", "task_success": True,
"latency_ms": 100.0}},
{"op": "record", "record": {
"timestamp": T1, "task_family": "coding", "model": "qwen3:14b",
"provider": "ollama", "tier": "T2", "task_success": True,
"attempts": 2, "latency_ms": 500.0}},
{"op": "record", "record": {
"timestamp": T2, "task_family": "coding", "model": "qwen3:14b",
"provider": "ollama", "tier": "T2", "task_success": False,
"user_correction": True, "latency_ms": 600.0}},
{"op": "get_stats", "family": "greeting"},
{"op": "get_stats", "family": "coding"},
{"op": "get_stats"},
{"op": "get_model_stats", "model": "qwen3:14b"},
{"op": "get_model_stats", "model": "unknown-model"},
]
return {"name": "s2_record_and_stats", "ops": ops, "obs": [d.step(o) for o in ops]}
def s3_full_record_to_dict(workdir):
"""OutcomeRecord.to_dict with every field set; float-heavy stats."""
d = Driver(workdir, 3)
full = {
"timestamp": T3, "task_family": "review", "model": "gpt-5-mini",
"provider": "openai", "tier": "T3", "prompt_tokens": 1200,
"output_tokens": 340, "cached_tokens": 512, "latency_ms": 123.75,
"attempts": 1, "verification_result": "pass", "user_correction": False,
"task_success": True, "estimated_cost": 0.0025,
}
ops = [
{"op": "new"},
{"op": "to_dict", "record": full},
{"op": "record", "record": full},
{"op": "record", "record": {**full, "timestamp": T4,
"estimated_cost": 0.0075,
"verification_result": "fail",
"task_success": False}},
{"op": "get_stats"},
{"op": "get_stats", "family": "review"},
{"op": "get_model_stats", "model": "gpt-5-mini"},
]
return {"name": "s3_full_record_to_dict", "ops": ops, "obs": [d.step(o) for o in ops]}
def s4_persistence_reopen(workdir):
"""A fresh OutcomeLedger over an existing db — rows survive, DDL is
idempotent."""
d = Driver(workdir, 4)
ops = [
{"op": "new"},
{"op": "record", "record": {
"timestamp": T0, "task_family": "port", "model": "qwen2.5:3b",
"provider": "ollama", "tier": "T1", "task_success": True,
"latency_ms": 42.5}},
{"op": "record", "record": {
"timestamp": T1, "task_family": "port", "model": "qwen3:14b",
"provider": "ollama", "tier": "T2", "task_success": False,
"user_correction": True, "attempts": 3, "latency_ms": 900.0}},
{"op": "new", "reuse": True},
{"op": "get_stats"},
{"op": "get_stats", "family": "port"},
{"op": "get_model_stats", "model": "qwen2.5:3b"},
]
return {"name": "s4_persistence_reopen", "ops": ops, "obs": [d.step(o) for o in ops]}
def s5_averages_and_rates(workdir):
"""AVG/rate arithmetic: fractional averages, 0%/50%/100% rates, both
correction branches."""
d = Driver(workdir, 5)
ops = [
{"op": "new"},
{"op": "record", "record": {
"timestamp": T0, "task_family": "ops", "model": "m1",
"task_success": True, "attempts": 1, "latency_ms": 10.5,
"estimated_cost": 0.0}},
{"op": "record", "record": {
"timestamp": T1, "task_family": "ops", "model": "m1",
"task_success": False, "user_correction": True, "attempts": 2,
"latency_ms": 20.25, "estimated_cost": 1.5}},
{"op": "record", "record": {
"timestamp": T2, "task_family": "ops", "model": "m2",
"task_success": True, "attempts": 3, "latency_ms": 30.125,
"user_correction": True, "estimated_cost": 2.25}},
{"op": "get_stats"},
{"op": "get_model_stats", "model": "m1"},
{"op": "get_model_stats", "model": "m2"},
{"op": "get_stats", "family": "ops"},
]
return {"name": "s5_averages_and_rates", "ops": ops, "obs": [d.step(o) for o in ops]}
def main():
out = {
"module": "outcome_ledger",
"seed": 2479,
"oracle": "src/rune/outcome_ledger.py",
"pin": "RUNE_OL_CORE=python",
"generated": "2026-10-11",
"scenarios": [],
}
with tempfile.TemporaryDirectory() as td:
workdir = Path(td)
for gen in (s1_empty_db, s2_record_and_stats, s3_full_record_to_dict,
s4_persistence_reopen, s5_averages_and_rates):
out["scenarios"].append(gen(workdir))
dest = REPO / "src" / "rust" / "outcome_ledger" / "src" / "frozen_to_truth.json"
dest.write_text(json.dumps(out, indent=1) + "\n")
n_obs = sum(len(s["obs"]) for s in out["scenarios"])
print(f"wrote {len(out['scenarios'])} scenarios / {n_obs} observations to {dest}")
if __name__ == "__main__":
main()
+3
View File
@@ -0,0 +1,3 @@
module rune/auto_context
go 1.22
+864
View File
@@ -0,0 +1,864 @@
// Package main — the FULL GO port of src/rune/auto_context.py (#2479
// dichotomy tranche, GO side verdict). Every public Python function has a
// name-preserving counterpart: run_warm_router → runWarmRouter,
// run_memory_recall → runMemoryRecall, run_always_digest → runAlwaysDigest,
// load_active_tasks → loadActiveTasks, build_auto_context →
// buildAutoContext, save_context → saveContext, handle_hook → handleHook,
// and the module CLI main() (argparse-compatible: build/show/hook, --event,
// --data, --json; invalid choices exit 2). The PLUGIN manifest rides along
// as pluginManifest().
//
// IO (subprocess runs, memory paths, wall clock) stays behind injectable
// seams so the frozen truth battery replays deterministically: RunScript,
// MemoryDir, ScriptsDir, PythonBin and ClockNow are swappable vars, swapped
// per truth row by replay.go and restored after each row. The oracle's
// subprocess payloads (warm_router.py route/always, memory_store.py recall)
// arrive through the RunScript seam; the frozen truth pins the exact
// message/query strings the port hands to that seam.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
)
// ── configuration ────────────────────────────────────────────────────────
// stopwordSet — the oracle's recall stopword table, 1:1.
var stopwordSet = map[string]struct{}{
"what": {}, "when": {}, "where": {}, "which": {}, "that": {}, "this": {},
"with": {}, "from": {}, "have": {}, "been": {}, "were": {}, "they": {},
"their": {}, "about": {}, "would": {}, "could": {}, "should": {},
"will": {}, "into": {}, "your": {}, "just": {}, "like": {}, "know": {},
"think": {}, "also": {}, "back": {}, "then": {}, "than": {}, "more": {},
"some": {}, "only": {}, "very": {}, "does": {}, "done": {}, "make": {},
"made": {}, "want": {}, "need": {}, "give": {}, "tell": {}, "there": {},
"these": {}, "those": {}, "because": {}, "between": {}, "after": {},
"before": {}, "under": {}, "over": {}, "again": {}, "once": {},
"here": {}, "each": {}, "every": {}, "both": {}, "few": {}, "most": {},
"other": {}, "such": {}, "same": {}, "being": {}, "having": {},
}
// ── seams (swapped per truth row by replay.go) ───────────────────────────
// ExecResult — the slice of subprocess.run(capture_output=True, text=True)
// the oracle consumes: stdout text, return code, and a non-nil Err when the
// run failed to start, was killed, or hit its timeout (subprocess raised).
type ExecResult struct {
Stdout string
ReturnCode int
Err error
}
var (
// defaultPythonBin — memory_env.python_bin(): sys.executable of the
// parent process (same venv, portable by construction).
defaultPythonBin = func() string {
if p := os.Getenv("RUNE_PYTHON"); p != "" {
return p
}
return "python3"
}
// defaultScriptsDir — memory_env.scripts_dir() (RUNE_SCRIPTS_DIR).
defaultScriptsDir = func() string {
if d := os.Getenv("RUNE_SCRIPTS_DIR"); d != "" {
return d
}
return "src/rune"
}
// defaultMemoryDir — memory_env.memory_dir() (RUNE_MEMORY_DIR else
// RUNE_HOME/memory else the default rune home).
defaultMemoryDir = func() string {
if d := os.Getenv("RUNE_MEMORY_DIR"); d != "" {
return d
}
home := os.Getenv("RUNE_HOME")
if home == "" {
home = "/mnt/workdrive/rune"
}
return filepath.Join(home, "memory")
}
// defaultClockNow — datetime.now(timezone.utc); build_auto_context
// calls it once per build.
defaultClockNow = func() time.Time { return time.Now().UTC() }
// defaultRunScript — subprocess.run([python, script, ...], timeout=N):
// timeout expiry surfaces as Err (subprocess.TimeoutExpired), a
// nonzero exit keeps Stdout and sets ReturnCode.
defaultRunScript = func(exe string, args []string, timeout time.Duration) ExecResult {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, exe, args...)
out, err := cmd.Output()
res := ExecResult{Stdout: string(out)}
if err != nil {
res.Err = err
if ee, ok := err.(*exec.ExitError); ok {
res.ReturnCode = ee.ExitCode()
} else {
res.ReturnCode = -1
}
}
return res
}
)
var (
PythonBin = defaultPythonBin
ScriptsDir = defaultScriptsDir
MemoryDir = defaultMemoryDir
)
// CommandRunner — the subprocess seam as an interface (Phase B): the
// runner is swappable in embedded callers and tests (fake runners,
// counting/pooled runners) without touching port code; the default
// implementation is compile-time asserted against the contract.
type CommandRunner interface {
Run(exe string, args []string, timeout time.Duration) ExecResult
}
// RunnerFunc adapts a plain function to CommandRunner.
type RunnerFunc func(exe string, args []string, timeout time.Duration) ExecResult
// Run satisfies CommandRunner.
func (f RunnerFunc) Run(exe string, args []string, timeout time.Duration) ExecResult {
return f(exe, args, timeout)
}
// compile-time: the default subprocess implementation satisfies the seam
var _ CommandRunner = RunnerFunc(defaultRunScript)
// Clock — the wall-clock seam as an interface (Phase B): build_auto_context
// reads it exactly once per build, like the oracle's datetime.now call.
type Clock interface {
Now() time.Time
}
// ClockFunc adapts a plain function to Clock.
type ClockFunc func() time.Time
// Now satisfies Clock.
func (f ClockFunc) Now() time.Time { return f() }
// compile-time: the default clock satisfies the seam
var _ Clock = ClockFunc(defaultClockNow)
// defaultRunner / defaultClock — the real implementations, kept as named
// seams so per-row swaps in replay.go can restore them.
var defaultRunner CommandRunner = RunnerFunc(defaultRunScript)
var defaultClock Clock = ClockFunc(defaultClockNow)
// Runner — the swapped-per-truth-row subprocess seam.
var Runner CommandRunner = defaultRunner
// ClockNow — the swapped-per-truth-row clock seam.
var ClockNow Clock = defaultClock
func warmRouterPath() string { return filepath.Join(ScriptsDir(), "warm_router.py") }
func memoryStorePath() string {
return filepath.Join(ScriptsDir(), "memory_store.py")
}
func contextFilePath() string {
return filepath.Join(MemoryDir(), "index", "auto_context.json")
}
func tasksFilePath() string {
return filepath.Join(MemoryDir(), "tasks", "persisted.json")
}
// isoformat — datetime.isoformat() on an aware UTC datetime: fractional
// part only when microseconds are nonzero, offset always "+00:00" shape.
func isoformat(t time.Time) string {
base := t.Format("2006-01-02T15:04:05")
if us := t.Nanosecond() / 1000; us != 0 {
base += fmt.Sprintf(".%06d", us)
}
return base + t.Format("-07:00")
}
// ── Python value semantics helpers ───────────────────────────────────────
// pyTruthy — Python truthiness for JSON-decoded values: None, False, 0,
// "" and every empty container are falsy.
func pyTruthy(v any) bool {
switch t := v.(type) {
case nil:
return false
case bool:
return t
case string:
return t != ""
case float64:
return t != 0
case json.Number:
f, _ := t.Float64()
return f != 0
case []any:
return len(t) > 0
case []*OrderedMap:
return len(t) > 0
case *OrderedMap:
return t != nil && t.Len() > 0
default:
return true
}
}
// pyTypeName — Python type() name for JSON-decoded values (integral
// floats decode as 'int', mirroring json.loads producing ints).
func pyTypeName(v any) string {
switch t := v.(type) {
case nil:
return "NoneType"
case bool:
return "bool"
case string:
return "str"
case float64:
if t == float64(int64(t)) {
return "int"
}
return "float"
case json.Number:
f, _ := t.Float64()
if f == float64(int64(f)) && !strings.ContainsAny(t.String(), ".eE") {
return "int"
}
return "float"
case []any, []*OrderedMap:
return "list"
case *OrderedMap:
return "dict"
default:
return "object"
}
}
// pyIsAlpha — str.isalpha(): non-empty and every rune a Unicode letter.
func pyIsAlpha(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
// runeSlice — Python's s[:n] (codepoint slice).
func runeSlice(s string, n int) string {
if utf8.RuneCountInString(s) <= n {
return s
}
return string([]rune(s)[:n])
}
func truncateRunes(s string, n int) string { return runeSlice(s, n) }
// pyParse — json.loads with order-preserving objects and json.Number
// numerals (so ints survive byte-parity re-encoding).
func pyParse(s string) (any, error) {
dec := json.NewDecoder(strings.NewReader(s))
dec.UseNumber()
return decodeJSONValue(dec)
}
// pythonRepr — repr() of a JSON-decoded value (the handle_hook
// str(data) fallback), Python quoting rules included.
func pythonRepr(v any) string {
switch t := v.(type) {
case nil:
return "None"
case bool:
if t {
return "True"
}
return "False"
case string:
return pythonReprString(t)
case json.Number:
if !strings.ContainsAny(t.String(), ".eE") {
return t.String()
}
f, _ := t.Float64()
return strconv.FormatFloat(f, 'g', -1, 64)
case float64:
return strconv.FormatFloat(t, 'g', -1, 64)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = pythonRepr(item)
}
return "[" + strings.Join(parts, ", ") + "]"
case *OrderedMap:
entries := t.Entries()
parts := make([]string, 0, len(entries))
for _, kv := range entries {
parts = append(parts, pythonReprString(kv.Key)+": "+pythonRepr(kv.Value))
}
return "{" + strings.Join(parts, ", ") + "}"
default:
return fmt.Sprintf("%v", t)
}
}
// pythonReprString — repr() of a str: single quotes preferred, double
// quotes when the string contains ' but not ", backslash and quote
// escapes, \n \r \t shortcuts.
func pythonReprString(s string) string {
escape := func(quote byte) string {
var sb strings.Builder
sb.WriteByte(quote)
for _, r := range s {
switch r {
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\'':
if quote == '\'' {
sb.WriteString(`\'`)
} else {
sb.WriteByte('\'')
}
case '"':
if quote == '"' {
sb.WriteString(`\"`)
} else {
sb.WriteByte('"')
}
default:
sb.WriteRune(r)
}
}
sb.WriteByte(quote)
return sb.String()
}
if strings.Contains(s, "'") && !strings.Contains(s, "\"") {
return escape('"')
}
return escape('\'')
}
// ── the port (1:1 with the oracle) ───────────────────────────────────────
// runWarmRouter — run_warm_router: route the message through the warm
// router subprocess and return the fired-neurons payload; ANY failure
// (nonzero exit, timeout, bad JSON, empty stdout) degrades to None.
func runWarmRouter(message string) any {
res := Runner.Run(PythonBin(), []string{warmRouterPath(), "route", message, "--json"}, 10*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
return v
}
}
return nil
}
// runMemoryRecall — run_memory_recall: recall relevant entries; a
// non-list payload degrades to [], a list is capped at budget entries.
func runMemoryRecall(query string, budget int) []any {
res := Runner.Run(PythonBin(), []string{memoryStorePath(), "recall", query, "--json"}, 15*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
if list, ok := v.([]any); ok {
if budget >= 0 && budget < len(list) {
return list[:budget]
}
return list
}
}
}
return []any{}
}
// runAlwaysDigest — run_always_digest: the always-on digest payload or
// None on any failure.
func runAlwaysDigest() any {
res := Runner.Run(PythonBin(), []string{warmRouterPath(), "always", "--json"}, 10*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
return v
}
}
return nil
}
// loadActiveTasks — load_active_tasks: read the persistence ledger and
// keep open/in_progress tasks; missing file, malformed JSON, a non-dict
// ledger, a non-dict task entry — every failure degrades to [].
func loadActiveTasks() []any {
raw, err := os.ReadFile(tasksFilePath())
if err != nil {
return []any{}
}
v, err := pyParse(string(raw))
if err != nil {
return []any{}
}
ledger, ok := v.(*OrderedMap)
if !ok {
return []any{}
}
tasks, ok := ledger.Get("tasks")
if !ok {
return []any{}
}
list, ok := tasks.([]any)
if !ok {
return []any{}
}
active := []any{}
for _, t := range list {
tom, ok := t.(*OrderedMap)
if !ok {
return []any{} // t.get on a non-dict raises → the oracle's except
}
status, _ := tom.Get2("status").(string)
if status == "open" || status == "in_progress" {
active = append(active, tom)
}
}
return active
}
// recallQueryWords — the oracle's key-term extraction: lowercase, split on
// whitespace, keep words longer than 4 chars that are pure alphabetic and
// not stopwords.
func recallQueryWords(message string) []string {
words := []string{}
for _, w := range strings.Fields(strings.ToLower(message)) {
if utf8.RuneCountInString(w) > 4 && pyIsAlpha(w) {
if _, stop := stopwordSet[w]; !stop {
words = append(words, w)
}
}
}
return words
}
// buildAutoContext — build_auto_context: warm neurons, active tasks,
// memory recall (substantive messages only), always-on digest.
//
// Phase B (language advantage): the oracle's four IO calls are mutually
// independent (pure reads of external state), so they fan out to
// goroutines — wall time is the slowest call, not the sum of four
// subprocesses. Results are assembled in the oracle's exact field order,
// so output is byte-identical to the serial Python semantics (the battery
// replays the frozen truth against THIS parallel path).
func buildAutoContext(message string) *OrderedMap {
context := NewOrderedMap(
KV{"timestamp", isoformat(ClockNow.Now().UTC())},
KV{"message_preview", runeSlice(message, 200)},
KV{"warm_neurons", nil},
KV{"memory_recall", nil},
KV{"always_digest", nil},
KV{"active_tasks", nil},
)
// cheap, CPU-only: key-term extraction (no IO) stays on the caller
// goroutine so the fan-out carries the exact query the oracle computes
query := ""
if message != "" && utf8.RuneCountInString(strings.TrimSpace(message)) > 10 {
words := recallQueryWords(message)
if len(words) > 0 {
n := 5
if len(words) < n {
n = len(words)
}
query = strings.Join(words[:n], " ")
}
}
var (
wg sync.WaitGroup
warm any
digest any
tasks []any
recall []any
)
launch := func(f func()) {
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
}
launch(func() { warm = runWarmRouter(message) })
launch(func() { tasks = loadActiveTasks() })
if query != "" {
launch(func() { recall = runMemoryRecall(query, 5) })
}
launch(func() { digest = runAlwaysDigest() })
wg.Wait()
if pyTruthy(warm) {
context.Set("warm_neurons", warm)
}
if len(tasks) > 0 {
context.Set("active_tasks", tasks)
}
if len(recall) > 0 {
context.Set("memory_recall", recall)
}
if pyTruthy(digest) {
context.Set("always_digest", digest)
}
return context
}
// saveContext — save_context: atomic write (tmp file + rename) of the
// context JSON, indent=2, ensure_ascii.
//
// Phase B: the tmp file is per-writer (os.CreateTemp in the target dir),
// not the oracle's fixed CONTEXT_FILE+".tmp" — the rename semantics are
// identical, but concurrent hooks no longer clobber each other's tmp
// (the oracle's shared-tmp path races ENOENT under concurrency).
func saveContext(context *OrderedMap) error {
dir := filepath.Dir(contextFilePath())
if err := os.MkdirAll(dir, 0o777); err != nil {
return err
}
tmp, err := os.CreateTemp(dir, ".auto_context-*.tmp")
if err != nil {
return err
}
name := tmp.Name()
defer os.Remove(name) // no-op after a successful rename
if _, err := tmp.Write([]byte(encodePythonJSON(context, 0))); err != nil {
tmp.Close()
return err
}
if err := tmp.Close(); err != nil {
return err
}
if err := os.Chmod(name, 0o644); err != nil {
return err
}
return os.Rename(name, contextFilePath())
}
// handleHook — handle_hook: the on_message plugin hook. The truthiness
// guard, the message/text/str(data) fallback chain and the two nested
// count expressions reproduce the oracle exactly, including its
// best-effort error dicts (truncated to 120 runes).
func handleHook(event string, data *OrderedMap) *OrderedMap {
errorDict := func(msg string) *OrderedMap {
return NewOrderedMap(
KV{"status", "error"},
KV{"event", event},
KV{"error", truncateRunes(msg, 120)},
)
}
if event == "on_message" && pyTruthy(data) {
message := any("")
for _, key := range []string{"message", "text"} {
v, ok := data.Get(key)
if !ok {
v = ""
}
if pyTruthy(v) {
message = v
break
}
}
if !pyTruthy(message) {
message = pythonRepr(data)
}
msgStr, isStr := message.(string)
if !isStr {
return errorDict(fmt.Sprintf("'%s' object has no attribute 'strip'", pyTypeName(message)))
}
if msgStr != "" && utf8.RuneCountInString(strings.TrimSpace(msgStr)) > 2 {
context := buildAutoContext(msgStr)
if err := saveContext(context); err != nil {
return errorDict(err.Error())
}
neurons := context.Get2("warm_neurons")
recall := context.Get2("memory_recall")
tasks := context.Get2("active_tasks")
neuronsFired := 0
if pyTruthy(neurons) {
om, ok := neurons.(*OrderedMap)
if !ok {
return errorDict(fmt.Sprintf("'%s' object has no attribute 'get'", pyTypeName(neurons)))
}
fired, _ := om.Get("fired")
// .get("fired", []) — a MISSING key defaults to [], a
// present-but-None key does not.
if fired == nil {
if _, present := om.Get("fired"); !present {
fired = []any{}
}
}
n, err := pyLen(fired)
if err != nil {
return errorDict(err.Error())
}
neuronsFired = n
}
memoriesRecalled := 0
if pyTruthy(recall) {
if n, err := pyLen(recall); err != nil {
return errorDict(err.Error())
} else {
memoriesRecalled = n
}
}
activeTasks := 0
if pyTruthy(tasks) {
if n, err := pyLen(tasks); err != nil {
return errorDict(err.Error())
} else {
activeTasks = n
}
}
return NewOrderedMap(
KV{"status", "ok"},
KV{"event", event},
KV{"handler", "auto_context"},
KV{"neurons_fired", neuronsFired},
KV{"memories_recalled", memoriesRecalled},
KV{"active_tasks", activeTasks},
KV{"context_file", contextFilePath()},
)
}
}
return NewOrderedMap(
KV{"status", "ok"},
KV{"event", event},
KV{"handler", "auto_context"},
)
}
// pyLen — len() for JSON-decoded values; non-sized types raise TypeError
// in Python ("object of type 'int' has no len()").
func pyLen(v any) (int, error) {
switch t := v.(type) {
case nil:
return 0, fmt.Errorf("object of type 'NoneType' has no len()")
case string:
return utf8.RuneCountInString(t), nil
case []any:
return len(t), nil
case []*OrderedMap:
return len(t), nil
case *OrderedMap:
return t.Len(), nil
default:
return 0, fmt.Errorf("object of type '%s' has no len()", pyTypeName(v))
}
}
// pluginManifest — the module-level PLUGIN dict (microkernel discovery
// metadata), key order preserved.
func pluginManifest() *OrderedMap {
return NewOrderedMap(
KV{"name", "auto_context"},
KV{"version", "1.0.0"},
KV{"description", "Auto-context — automatic memory and warm neuron activation on every message."},
KV{"capabilities", []any{"context", "memory", "activation"}},
KV{"hooks", NewOrderedMap(KV{"on_message", "handle_hook"})},
KV{"dependencies", []any{"memory_store", "warm_router"}},
KV{"provides", []any{"auto_context"}},
KV{"priority", 5},
KV{"auto_activate", true},
)
}
// ── CLI (auto_context.py main(), byte-parity stdout) ─────────────────────
func argparseUsage(w io.Writer) {
fmt.Fprintln(w, "usage: auto_context.py [-h] [--event EVENT] [--data DATA] [--json] {build,show,hook} [arg]")
}
// RunCLI executes auto_context.py main() against argv (program name at
// index 0) and returns the process exit code. argparse failures print
// usage to stderr and exit 2.
func RunCLI(argv []string, stdout io.Writer) int {
stderr := os.Stderr
var positionals []string
event, hasEvent := "", false
dataStr, hasData := "", false
jsonFlag := false
i := 1
for i < len(argv) {
a := argv[i]
switch {
case a == "--json":
jsonFlag = true
case a == "--event":
i++
if i >= len(argv) {
fmt.Fprintf(stderr, "auto_context.py: error: argument --event: expected one argument\n")
argparseUsage(stderr)
return 2
}
event, hasEvent = argv[i], true
case strings.HasPrefix(a, "--event="):
event, hasEvent = strings.TrimPrefix(a, "--event="), true
case a == "--data":
i++
if i >= len(argv) {
fmt.Fprintf(stderr, "auto_context.py: error: argument --data: expected one argument\n")
argparseUsage(stderr)
return 2
}
dataStr, hasData = argv[i], true
case strings.HasPrefix(a, "--data="):
dataStr, hasData = strings.TrimPrefix(a, "--data="), true
case a == "-h" || a == "--help":
argparseUsage(stdout)
return 0
case strings.HasPrefix(a, "-") && a != "-":
fmt.Fprintf(stderr, "auto_context.py: error: unrecognized arguments: %s\n", a)
argparseUsage(stderr)
return 2
default:
positionals = append(positionals, a)
}
i++
}
if len(positionals) == 0 {
fmt.Fprintf(stderr, "auto_context.py: error: the following arguments are required: cmd\n")
argparseUsage(stderr)
return 2
}
if len(positionals) > 2 {
fmt.Fprintf(stderr, "auto_context.py: error: unrecognized arguments: %s\n", strings.Join(positionals[2:], " "))
argparseUsage(stderr)
return 2
}
cmd, arg := positionals[0], ""
if len(positionals) == 2 {
arg = positionals[1]
}
switch cmd {
case "build":
message := arg
if message == "" {
message = "general context"
}
context := buildAutoContext(message)
if err := saveContext(context); err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
if jsonFlag {
fmt.Fprintln(stdout, encodePythonJSON(context, 0))
} else {
n := 0
if warm := context.Get2("warm_neurons"); pyTruthy(warm) {
if om, ok := warm.(*OrderedMap); ok {
fired, _ := om.Get("fired")
if fired == nil {
if _, present := om.Get("fired"); !present {
fired = []any{}
}
}
if c, err := pyLen(fired); err == nil {
n = c
}
}
}
m := 0
if recall, ok := context.Get2("memory_recall").([]any); ok {
m = len(recall)
}
fmt.Fprintf(stdout, "Auto-context built: %d neurons, %d memories\n", n, m)
}
case "show":
raw, err := os.ReadFile(contextFilePath())
if err != nil {
fmt.Fprintln(stdout, "No auto-context file found")
return 0
}
v, err := pyParse(string(raw))
if err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
fmt.Fprintln(stdout, encodePythonJSON(v, 0))
case "hook":
event2 := "unknown"
if hasEvent && event != "" {
event2 = event
}
var data *OrderedMap
if hasData && dataStr != "" {
v, err := pyParse(dataStr)
if err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
if om, ok := v.(*OrderedMap); ok {
data = om
} else {
// json.loads gave a non-object; data.get would raise
// AttributeError outside handle_hook's try
fmt.Fprintf(stderr, "Traceback (most recent call last): '%s' object has no attribute 'get'\n", pyTypeName(v))
return 1
}
}
result := handleHook(event2, data)
fmt.Fprintln(stdout, encodePythonJSONCompact(result))
default:
fmt.Fprintf(stderr, "auto_context.py: error: argument cmd: invalid choice: '%s' (choose from 'build', 'show', 'hook')\n", cmd)
argparseUsage(stderr)
return 2
}
return 0
}
// ── entry point: CLI + truth replay subcommand for the HARD CI gate ─────
func truthRowCount(path string) int {
raw, err := os.ReadFile(path)
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
path = os.Args[2]
}
fails := RunTruth(path)
if len(fails) > 0 {
for _, f := range fails {
fmt.Fprintln(os.Stderr, f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Printf("PARITY OK: %d truth rows exact-equal vs the Python oracle\n", truthRowCount(path))
return
}
os.Exit(RunCLI(os.Args, os.Stdout))
}
+133
View File
@@ -0,0 +1,133 @@
package main
// Phase B battery — pins the language-advantage guarantees: the subprocess
// and clock seams are interfaces (compile-time asserted in main.go),
// build_auto_context fans the oracle's four independent IO calls out to
// goroutines with byte-identical results, and concurrent builds are
// race-clean (run under -race in CI).
import (
"os"
"path/filepath"
"sync"
"testing"
"time"
)
// countingRunner — a CommandRunner decorator that counts invocations: the
// seam swaps in without touching port code.
type countingRunner struct {
mu sync.Mutex
inner CommandRunner
count int
}
func (c *countingRunner) Run(exe string, args []string, timeout time.Duration) ExecResult {
c.mu.Lock()
c.count++
c.mu.Unlock()
return c.inner.Run(exe, args, timeout)
}
// swapSeams — install the frozen seams for one test; restore on cleanup.
func swapSeams(t *testing.T, routeMode, alwaysMode, recallMode string) string {
t.Helper()
home := t.TempDir()
memdir := filepath.Join(home, "memory")
if err := os.MkdirAll(memdir, 0o777); err != nil {
t.Fatal(err)
}
MemoryDir = func() string { return memdir }
ScriptsDir = func() string { return "/tmp/actx_stubs" }
PythonBin = func() string { return "python3" }
Runner = buildFakeRunner(routeMode, alwaysMode, recallMode)
base, _ := time.Parse(time.RFC3339, "2026-10-11T12:00:00.123456+00:00")
ClockNow = ClockFunc(func() time.Time { return base })
t.Cleanup(func() {
MemoryDir = defaultMemoryDir
ScriptsDir = defaultScriptsDir
PythonBin = defaultPythonBin
Runner = defaultRunner
ClockNow = defaultClock
})
return memdir
}
// TestPhaseBParallelBuildParity — the frozen build truth replays green
// against the GOROUTINE fan-out path (results assembled in oracle order
// are byte-identical to the serial Python semantics).
func TestPhaseBParallelBuildParity(t *testing.T) {
swapSeams(t, "ok", "ok", "ok")
doc := loadTruth(t)
for _, name := range []string{"build:full", "build:unicode-preview", "build:stopwords", "build:short-message"} {
row := findRow(t, name)
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("parallel build diverged on %s: %s", name, msg)
}
}
}
// TestPhaseBRunnerSeamIsAnInterface — a counting decorator rides the
// CommandRunner seam; a full build issues exactly 3 subprocess runs (route,
// recall, always — the ledger read is file IO, not a subprocess).
func TestPhaseBRunnerSeamIsAnInterface(t *testing.T) {
swapSeams(t, "ok", "ok", "ok")
counter := &countingRunner{inner: Runner}
Runner = counter
ctx := buildAutoContext("remember the config parser override discussion tests")
if counter.count != 3 {
t.Fatalf("expected 3 subprocess runs per build, got %d", counter.count)
}
if warm := ctx.Get2("warm_neurons"); !pyTruthy(warm) {
t.Fatal("warm payload lost through the decorated seam")
}
}
// TestPhaseBConcurrentBuilds — 8 goroutines building contexts through the
// shared fake runner must all agree (and stay race-clean).
func TestPhaseBConcurrentBuilds(t *testing.T) {
swapSeams(t, "ok", "ok", "ok")
const n = 8
results := make([]string, n)
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
results[i] = mustJSON(buildAutoContext("remember the config parser override discussion tests"))
}(i)
}
wg.Wait()
for i := 1; i < n; i++ {
if results[i] != results[0] {
t.Fatalf("concurrent build %d diverged:\n first: %s\n got: %s", i, results[0], results[i])
}
}
}
// TestPhaseBConcurrentHooks — concurrent handle_hook calls write the
// context file through per-writer tmp + atomic rename: every rename
// succeeds (no shared-tmp ENOENT race, which the oracle's fixed
// CONTEXT_FILE+".tmp" would hit) and every reader sees complete JSON.
func TestPhaseBConcurrentHooks(t *testing.T) {
memdir := swapSeams(t, "ok", "ok", "ok")
data := NewOrderedMap(KV{"message", "remember the config parser override discussion tests"})
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if r := handleHook("on_message", data); r.Get2("status") != "ok" {
t.Errorf("hook failed: %v", mustJSON(r))
}
}()
}
wg.Wait()
raw, err := os.ReadFile(filepath.Join(memdir, "index", "auto_context.json"))
if err != nil {
t.Fatal(err)
}
if _, err := pyParse(string(raw)); err != nil {
t.Fatalf("context file torn or invalid: %v", err)
}
}
+363
View File
@@ -0,0 +1,363 @@
// Python json interop: insertion-ordered maps and json.dumps-compatible
// encoding (indent=2 and the default compact (", ", ": ") separators,
// ensure_ascii=True) so save_context, show and hook CLI output are
// byte-parity with the oracle (adapted from the message_queue port's
// pyjson.go; the float renderer follows Python repr, which keeps ".0" on
// integral floats).
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"unicode/utf16"
)
// KV — one ordered key/value pair.
type KV struct {
Key string
Value any
}
// OrderedMap — a dict with preserved insertion order.
type OrderedMap struct {
Keys []string
Vals map[string]any
}
// NewOrderedMap builds an ordered map from pairs.
func NewOrderedMap(pairs ...KV) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for _, kv := range pairs {
om.Set(kv.Key, kv.Value)
}
return om
}
// OrderedMapFromMap wraps a plain map (order unspecified — canonical
// comparisons sort keys anyway).
func OrderedMapFromMap(m map[string]any) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for k, v := range m {
om.Set(k, v)
}
return om
}
// Set inserts or replaces, preserving first-insertion order.
func (o *OrderedMap) Set(key string, value any) {
if _, ok := o.Vals[key]; !ok {
o.Keys = append(o.Keys, key)
}
o.Vals[key] = value
}
// Get retrieves a value.
func (o *OrderedMap) Get(key string) (any, bool) {
v, ok := o.Vals[key]
return v, ok
}
// Get2 returns the value at key (nil when absent) for direct type asserts.
func (o *OrderedMap) Get2(key string) any {
return o.Vals[key]
}
// Entries returns key/value pairs in insertion order.
func (o *OrderedMap) Entries() []KV {
out := make([]KV, 0, len(o.Keys))
for _, k := range o.Keys {
out = append(out, KV{k, o.Vals[k]})
}
return out
}
// Len returns the number of entries.
func (o *OrderedMap) Len() int { return len(o.Keys) }
// decodeJSONValue decodes one JSON value from the decoder, decoding objects
// as *OrderedMap (order-preserving) and numbers as float64.
func decodeJSONValue(dec *json.Decoder) (any, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodeFromToken(dec, tok)
}
func decodeFromToken(dec *json.Decoder, tok json.Token) (any, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
om := &OrderedMap{Vals: map[string]any{}}
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, err
}
key, _ := keyTok.(string)
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
om.Set(key, val)
}
if _, err := dec.Token(); err != nil { // consume '}'
return nil, err
}
return om, nil
case '[':
arr := []any{}
for dec.More() {
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
if _, err := dec.Token(); err != nil { // consume ']'
return nil, err
}
return arr, nil
}
return nil, fmt.Errorf("unexpected delim %v", t)
default:
return tok, nil
}
}
// encodePythonJSON renders v exactly like Python json.dumps(v, indent=N)
// with default ensure_ascii=True and (',', ': ') separators.
func encodePythonJSON(v any, indent int) string {
var sb strings.Builder
encodeValue(&sb, v, indent)
return sb.String()
}
func encodeValue(sb *strings.Builder, v any, indent int) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []*OrderedMap:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []string:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case *OrderedMap:
if t.Len() == 0 {
sb.WriteString("{}")
return
}
sb.WriteString("{\n")
entries := t.Entries()
for i, kv := range entries {
writeIndent(sb, indent+1)
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeValue(sb, kv.Value, indent+1)
if i < len(entries)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("}")
case map[string]any:
encodeValue(sb, OrderedMapFromMap(t), indent)
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
func writeIndent(sb *strings.Builder, level int) {
for i := 0; i < level; i++ {
sb.WriteString(" ")
}
}
// encodePythonJSONCompact renders v exactly like Python json.dumps(v) with
// default separators (", ", ": ") and ensure_ascii=True — the compact form
// the CLI hook path prints.
func encodePythonJSONCompact(v any) string {
var sb strings.Builder
encodeCompactValue(&sb, v)
return sb.String()
}
func encodeCompactValue(sb *strings.Builder, v any) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case []*OrderedMap:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case []string:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case *OrderedMap:
sb.WriteString("{")
entries := t.Entries()
for i, kv := range entries {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeCompactValue(sb, kv.Value)
}
sb.WriteString("}")
case map[string]any:
encodeCompactValue(sb, OrderedMapFromMap(t))
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
// pythonFloat renders a float like Python repr (json.dumps uses
// float.__repr__): shortest round-trip digits, and integral floats keep
// one decimal place (1.0 stays "1.0", not "1").
func pythonFloat(f float64) string {
s := strconv.FormatFloat(f, 'g', -1, 64)
if !strings.ContainsAny(s, ".eE") &&
!strings.Contains(s, "Inf") && !strings.Contains(s, "NaN") {
s += ".0"
}
return s
}
// pythonEscapeString — Python json.dumps string escaping with
// ensure_ascii=True: control chars via \uXXXX (with \b \f \n \r \t
// shortcuts), non-ASCII via \uXXXX / surrogate pairs, lowercase hex.
func pythonEscapeString(s string) string {
var sb strings.Builder
sb.WriteByte('"')
for _, r := range s {
switch r {
case '"':
sb.WriteString(`\"`)
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\b':
sb.WriteString(`\b`)
case '\f':
sb.WriteString(`\f`)
default:
if r < 0x20 || r > 0x7e {
if r > 0xffff {
r1, r2 := utf16.EncodeRune(r)
fmt.Fprintf(&sb, `\u%04x\u%04x`, r1, r2)
} else {
fmt.Fprintf(&sb, `\u%04x`, r)
}
} else {
sb.WriteRune(r)
}
}
}
sb.WriteByte('"')
return sb.String()
}
+475
View File
@@ -0,0 +1,475 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// /tmp/gen_auto_context_truth.py against the live Python oracle with the
// clock frozen and env-driven stub scripts pinned) and re-executes every
// row through this Go port, comparing results. Exact rows (context-file
// bytes, CLI stdout) compare byte-for-byte; everything else compares
// canonically (sorted-key JSON). Absolute memory-dir paths are normalized
// to @MEMDIR@ on both sides.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
FrozenTS string `json:"frozen_ts"`
Oracle string `json:"oracle"`
Rows []TruthRow `json:"rows"`
}
// TruthRow — one frozen case.
type TruthRow struct {
Name string `json:"name"`
Op string `json:"op"`
Setup json.RawMessage `json:"setup"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on 100%).
func RunTruth(path string) []string {
raw, err := os.ReadFile(path)
if err != nil {
return []string{fmt.Sprintf("read truth: %v", err)}
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return []string{fmt.Sprintf("parse truth: %v", err)}
}
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(&doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
return fails
}
// compareOne replays one row and returns "" on parity, else a divergence
// description. Shared by RunTruth and the per-function battery tests.
func compareOne(doc *TruthDoc, row *TruthRow) string {
got, exact, err := execRow(doc, row)
if err != nil {
return fmt.Sprintf("error: %v", err)
}
var wantStr, gotStr string
if exact {
var w string
if err := json.Unmarshal(row.Want, &w); err != nil {
return fmt.Sprintf("want not a string: %v", err)
}
wantStr, gotStr = w, got.(string)
} else {
wantStr, err = canonicalFromRaw(row.Want)
if err != nil {
return fmt.Sprintf("canonicalize want: %v", err)
}
gotStr, err = canonicalFromRaw([]byte(mustJSON(got)))
if err != nil {
return fmt.Sprintf("canonicalize got: %v", err)
}
}
if wantStr != gotStr {
return fmt.Sprintf("\n want: %s\n got: %s", wantStr, gotStr)
}
return ""
}
// execRow executes one truth row: returns got (any) and, for exact rows,
// the exact string in got.
func execRow(doc *TruthDoc, row *TruthRow) (got any, exact bool, err error) {
setup := NewOrderedMap()
if len(row.Setup) > 0 {
dec := json.NewDecoder(bytes.NewReader(row.Setup))
dec.UseNumber()
tok, err := dec.Token()
if err != nil {
return nil, false, err
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return nil, false, fmt.Errorf("setup not an object")
}
v, err := decodeFromToken(dec, tok)
if err != nil {
return nil, false, err
}
setup = v.(*OrderedMap)
}
// freeze the seams per row: fresh temp memory dir, frozen clock,
// env-driven fake runner mirroring /tmp/actx_stubs payloads
home, err := os.MkdirTemp("", "actx_truth_row_")
if err != nil {
return nil, false, err
}
defer os.RemoveAll(home)
defer func() {
MemoryDir = defaultMemoryDir
Runner = defaultRunner
ClockNow = defaultClock
PythonBin = defaultPythonBin
ScriptsDir = defaultScriptsDir
}()
memdir := filepath.Join(home, "memory")
if err := os.MkdirAll(memdir, 0o777); err != nil {
return nil, false, err
}
MemoryDir = func() string { return memdir }
ScriptsDir = func() string { return "/tmp/actx_stubs" }
PythonBin = func() string { return "python3" }
base, err := time.Parse(time.RFC3339, doc.FrozenTS)
if err != nil {
return nil, false, err
}
ClockNow = ClockFunc(func() time.Time { return base })
// file seeds from setup
if f, ok := setup.Get("tasks_file"); ok {
s, _ := f.(string)
if err := os.MkdirAll(filepath.Join(memdir, "tasks"), 0o777); err != nil {
return nil, false, err
}
if err := os.WriteFile(filepath.Join(memdir, "tasks", "persisted.json"), []byte(s), 0o644); err != nil {
return nil, false, err
}
}
if f, ok := setup.Get("seed_context"); ok {
s, _ := f.(string)
if err := os.MkdirAll(filepath.Join(memdir, "index"), 0o777); err != nil {
return nil, false, err
}
if err := os.WriteFile(filepath.Join(memdir, "index", "auto_context.json"), []byte(s), 0o644); err != nil {
return nil, false, err
}
}
// stub modes (default "ok", like the stub scripts' env defaults)
mode := func(key string) string {
if v, ok := setup.Get(key); ok {
if s, ok2 := v.(string); ok2 {
return s
}
}
return "ok"
}
Runner = buildFakeRunner(mode("stub_route"), mode("stub_always"), mode("stub_recall"))
scrub := func(v any) any {
return scrubPaths(v, memdir)
}
strSetup := func(key string) string {
s, _ := setup.Get2(key).(string)
return s
}
switch row.Op {
case "warm_router":
return scrub(runWarmRouter(strSetup("message"))), false, nil
case "recall":
budget := 5
if b, ok := setup.Get("budget"); ok {
switch n := b.(type) {
case float64:
budget = int(n)
case json.Number:
f, _ := n.Float64()
budget = int(f)
}
}
return scrub(runMemoryRecall(strSetup("query"), budget)), false, nil
case "always":
return scrub(runAlwaysDigest()), false, nil
case "load_tasks":
return scrub(loadActiveTasks()), false, nil
case "build":
return scrub(buildAutoContext(strSetup("message"))), false, nil
case "save":
om, ok := setup.Get2("context").(*OrderedMap)
if !ok {
return nil, false, fmt.Errorf("save: no context")
}
if err := saveContext(om); err != nil {
return nil, false, err
}
raw, err := os.ReadFile(contextFilePath())
if err != nil {
return nil, false, err
}
reads := []any{scrubPaths(string(raw), memdir)}
if om2, ok2 := setup.Get2("context2").(*OrderedMap); ok2 {
// overwrite semantics: second save replaces the first atomically
if err := saveContext(om2); err != nil {
return nil, false, err
}
raw2, err := os.ReadFile(contextFilePath())
if err != nil {
return nil, false, err
}
reads = append(reads, scrubPaths(string(raw2), memdir))
}
_, statErr := os.Stat(contextFilePath() + ".tmp")
return NewOrderedMap(
KV{"reads", reads},
KV{"tmp_gone", os.IsNotExist(statErr)},
), false, nil
case "hook":
event := strSetup("event")
var data *OrderedMap
if v, ok := setup.Get("data"); ok && v != nil {
om, ok2 := v.(*OrderedMap)
if !ok2 {
return nil, false, fmt.Errorf("hook: data not an object")
}
data = om
}
return scrub(handleHook(event, data)), false, nil
case "cli":
argvAny, _ := setup.Get2("argv").([]any)
argv := []string{}
for _, a := range argvAny {
s, _ := a.(string)
argv = append(argv, s)
}
var sb strings.Builder
code := RunCLI(argv, &sb)
out := strings.ReplaceAll(sb.String(), memdir, "@MEMDIR@")
return NewOrderedMap(KV{"stdout", out}, KV{"exit", code}), false, nil
case "plugin":
return pluginManifest(), false, nil
}
return nil, false, fmt.Errorf("unknown row op %s (%s)", row.Op, row.Name)
}
// buildFakeRunner — the env-driven stub scripts' behavior as a
// CommandRunner (mirrors /tmp/actx_stubs byte-for-byte): payloads ECHO the
// caller's message/query so the frozen truth pins the exact strings the
// port hands to the subprocess seam. Reused by the Phase B battery.
func buildFakeRunner(routeMode, alwaysMode, recallMode string) RunnerFunc {
return func(exe string, args []string, timeout time.Duration) ExecResult {
var out string
code := 0
base := filepath.Base(args[0])
if base == "warm_router.py" {
if len(args) > 1 && args[1] == "always" {
switch alwaysMode {
case "ok":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"digest", "daily-summary"}, KV{"items", 7},
KV{"topics", []any{"memory", "tasks", "recall"}}))
case "fail":
code = 1
case "badjson":
out = "not-json{{{"
case "empty":
// print nothing
}
} else {
msg := ""
if len(args) > 2 {
msg = args[2]
}
switch routeMode {
case "ok":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"echo_message", msg},
KV{"fired", []any{"warm-alpha", "warm-beta", "warm-gamma"}},
KV{"matched", 3}, KV{"route", "focus"}))
case "list":
out = "[1, 2]"
case "emptylist":
out = "[]"
case "firedint":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"fired", 3}, KV{"echo_message", msg}))
case "fail":
code = 1
case "badjson":
out = "not-json{{{"
case "empty":
// print nothing
}
}
} else {
query := ""
if len(args) > 2 {
query = args[2]
}
switch recallMode {
case "ok":
items := []any{NewOrderedMap(KV{"echo_query", query})}
for _, e := range recallEntries {
items = append(items, e)
}
out = encodePythonJSONCompact(items)
case "nonlist":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"query", query}, KV{"note", "not a list"}))
case "fail":
code = 1
case "badjson":
out = "<xml?>"
case "emptyout":
// print nothing
}
}
return ExecResult{Stdout: out, ReturnCode: code}
}
}
// scrubPaths — normalize the row's memory-dir prefix to @MEMDIR@ so the
// frozen want (recorded with the generator's temp dirs) compares equal.
func scrubPaths(v any, memdir string) any {
switch t := v.(type) {
case string:
return strings.ReplaceAll(t, memdir, "@MEMDIR@")
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = scrubPaths(item, memdir)
}
return out
case *OrderedMap:
out := NewOrderedMap()
for _, kv := range t.Entries() {
out.Set(kv.Key, scrubPaths(kv.Value, memdir))
}
return out
default:
return v
}
}
// recallEntries — the memory_store.py stub's fixed entry list (m2's title
// pins ensure_ascii round-tripping).
var recallEntries = []any{
NewOrderedMap(KV{"id", "m1"}, KV{"score", 0.91},
KV{"tags", []any{"alpha", "beta"}}, KV{"title", "memory-entry-one"}),
NewOrderedMap(KV{"id", "m2"}, KV{"score", 0.84},
KV{"tags", []any{}}, KV{"title", "mémoire—café"}),
NewOrderedMap(KV{"id", "m3"}, KV{"score", 0.77},
KV{"tags", []any{"gamma"}}, KV{"title", "memory-entry-three"}),
NewOrderedMap(KV{"id", "m4"}, KV{"score", 0.7},
KV{"tags", []any{}}, KV{"title", "memory-entry-four"}),
NewOrderedMap(KV{"id", "m5"}, KV{"score", 0.62},
KV{"tags", []any{"delta"}}, KV{"title", "memory-entry-five"}),
NewOrderedMap(KV{"id", "m6"}, KV{"score", 0.55},
KV{"tags", []any{}}, KV{"title", "memory-entry-six"}),
NewOrderedMap(KV{"id", "m7"}, KV{"score", 0.41},
KV{"tags", []any{"epsilon"}}, KV{"title", "memory-entry-seven"}),
NewOrderedMap(KV{"id", "m8"}, KV{"score", 0.2},
KV{"tags", []any{}}, KV{"title", "memory-entry-eight"}),
}
// canonicalFromRaw — canonical string of a raw JSON document.
func canonicalFromRaw(raw []byte) (string, error) {
var v any
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return "", err
}
return canonical(normalize(v)), nil
}
// canonical — sorted-key deterministic rendering of a decoded JSON value.
func canonical(v any) string {
switch t := v.(type) {
case nil:
return "null"
case bool:
if t {
return "true"
}
return "false"
case string:
return pythonEscapeString(t)
case float64:
if t == float64(int64(t)) {
return fmt.Sprintf("%d", int64(t))
}
return fmt.Sprintf("%v", t)
case json.Number:
f, _ := t.Float64()
return canonical(f)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = canonical(normalize(item))
}
return "[" + strings.Join(parts, ", ") + "]"
case map[string]any:
keys := make([]string, 0, len(t))
for k := range t {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, pythonEscapeString(k)+": "+canonical(normalize(t[k])))
}
return "{" + strings.Join(parts, ", ") + "}"
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = kv.Value
}
return canonical(m)
default:
return fmt.Sprintf("«%v»", t)
}
}
// normalize — convert OrderedMap/map trees into plain map[string]any with
// json.Number→float64 so canonical() sees one representation.
func normalize(v any) any {
switch t := v.(type) {
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = normalize(kv.Value)
}
return m
case map[string]any:
m := map[string]any{}
for k, val := range t {
m[k] = normalize(val)
}
return m
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = normalize(item)
}
return out
case json.Number:
f, _ := t.Float64()
return f
default:
return v
}
}
func mustJSON(v any) string {
b, err := json.Marshal(normalize(v))
if err != nil {
return "null"
}
return string(b)
}
File diff suppressed because it is too large. Load diff
+264
View File
@@ -0,0 +1,264 @@
package main
// Battery — the HARD parity gate plus per-function batteries replaying the
// frozen oracle truth (truth.json, generated against the LIVE Python
// oracle with the clock frozen and env-driven stub scripts pinned)
// VERBATIM. Any divergence fails; the truth file rides the repo so the
// gate holds in CI without /tmp.
import (
"encoding/json"
"os"
"strings"
"testing"
)
func loadTruth(t *testing.T) *TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth.json: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth.json contained no rows")
}
return &doc
}
func findRow(t *testing.T, name string) TruthRow {
t.Helper()
doc := loadTruth(t)
for _, row := range doc.Rows {
if row.Name == name {
return row
}
}
t.Fatalf("frozen row %q missing from truth.json — battery/truth drift", name)
return TruthRow{}
}
func mustReplay(t *testing.T, name string) {
t.Helper()
doc := loadTruth(t)
row := findRow(t, name)
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", name, msg)
}
}
func replayOp(t *testing.T, op string) int {
t.Helper()
doc := loadTruth(t)
n := 0
for _, row := range doc.Rows {
if row.Op != op {
continue
}
n++
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", row.Name, msg)
}
}
if n == 0 {
t.Fatalf("no %s rows in truth.json", op)
}
return n
}
// rowWant decodes a row's want JSON into a generic map.
func rowWant(t *testing.T, name string) map[string]any {
t.Helper()
row := findRow(t, name)
var want map[string]any
if err := json.Unmarshal(row.Want, &want); err != nil {
t.Fatalf("decode want of %s: %v", name, err)
}
return want
}
// TestTruthParity — the whole frozen battery, one shot.
func TestTruthParity(t *testing.T) {
doc := loadTruth(t)
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
if len(fails) > 0 {
t.Fatalf("parity replay diverged after %d clean rows:\n%s",
len(doc.Rows)-len(fails), strings.Join(fails, "\n"))
}
t.Logf("PARITY OK: %d cases exact-equal vs the Python oracle", len(doc.Rows))
}
// TestBatteryRunWarmRouter — ok / nonzero-exit / bad-json / empty-stdout /
// JSON-list payload / empty-list payload; the ok row pins the exact
// message the port hands to the seam.
func TestBatteryRunWarmRouter(t *testing.T) {
if n := replayOp(t, "warm_router"); n != 6 {
t.Fatalf("expected 6 frozen warm_router rows, got %d", n)
}
want := rowWant(t, "warm:ok")
if want["echo_message"] != "hello warm router" || want["matched"] != float64(3) {
t.Fatalf("frozen echo row changed: %v", want)
}
if got := len(want["fired"].([]any)); got != 3 {
t.Fatalf("fired payload changed: %v", want["fired"])
}
mustReplay(t, "warm:list")
mustReplay(t, "warm:emptylist")
}
// TestBatteryRunMemoryRecall — ok (echo pins the query string), budget
// truncation, non-list payload, and every degrade branch.
func TestBatteryRunMemoryRecall(t *testing.T) {
if n := replayOp(t, "recall"); n != 6 {
t.Fatalf("expected 6 frozen recall rows, got %d", n)
}
// recall:ok's want is the returned LIST — element 0 echoes the query
rawWant := findRow(t, "recall:ok").Want
var list []map[string]any
if err := json.Unmarshal(rawWant, &list); err != nil {
t.Fatalf("decode recall:ok want: %v", err)
}
if len(list) != 5 || list[0]["echo_query"] != "alpha beta gamma" {
t.Fatalf("frozen recall:ok changed: %v", list)
}
mustReplay(t, "recall:budget2")
mustReplay(t, "recall:nonlist")
mustReplay(t, "recall:emptyout")
}
// TestBatteryRunAlwaysDigest — ok / fail / bad-json / empty.
func TestBatteryRunAlwaysDigest(t *testing.T) {
if n := replayOp(t, "always"); n != 4 {
t.Fatalf("expected 4 frozen always rows, got %d", n)
}
want := rowWant(t, "always:ok")
if want["digest"] != "daily-summary" || want["items"] != float64(7) {
t.Fatalf("frozen digest changed: %v", want)
}
}
// TestBatteryLoadActiveTasks — missing file / mixed statuses (only
// open+in_progress survive) / malformed / empty / no-tasks-key.
func TestBatteryLoadActiveTasks(t *testing.T) {
if n := replayOp(t, "load_tasks"); n != 5 {
t.Fatalf("expected 5 frozen load_tasks rows, got %d", n)
}
mustReplay(t, "tasks:missing")
mustReplay(t, "tasks:malformed")
}
// TestBatteryBuild — every build_auto_context branch: full context (the
// recall echo pins stopword filtering + isalpha + first-5 join), short
// message, stopwords-only, non-alpha, unicode 200-rune preview truncation,
// empty message, recall-fail, warm-emptylist, warm-fail; frozen timestamp.
func TestBatteryBuild(t *testing.T) {
if n := replayOp(t, "build"); n != 9 {
t.Fatalf("expected 9 frozen build rows, got %d", n)
}
want := rowWant(t, "build:full")
recallList, _ := want["memory_recall"].([]any)
echo, _ := recallList[0].(map[string]any)
if echo["echo_query"] != "remember config parser override discussion" {
t.Fatalf("recall query extraction changed: %v", echo["echo_query"])
}
if want["timestamp"] != "2026-10-11T12:00:00.123456+00:00" {
t.Fatalf("frozen timestamp changed: %v", want["timestamp"])
}
want = rowWant(t, "build:unicode-preview")
preview, _ := want["message_preview"].(string)
if n := len([]rune(preview)); n != 200 {
t.Fatalf("preview must be 200 runes, got %d", n)
}
mustReplay(t, "build:short-message")
mustReplay(t, "build:stopwords")
mustReplay(t, "build:nonalpha")
mustReplay(t, "build:empty")
mustReplay(t, "build:recall-fail")
mustReplay(t, "build:warm-emptylist")
mustReplay(t, "build:warm-fail")
}
// TestBatterySave — save_context byte-parity: ensure_ascii escapes,
// indent=2 layout, and the tmp+rename overwrite (no .tmp leftover).
func TestBatterySave(t *testing.T) {
if n := replayOp(t, "save"); n != 2 {
t.Fatalf("expected 2 frozen save rows, got %d", n)
}
mustReplay(t, "save:full")
mustReplay(t, "save:overwrite")
}
// TestBatteryHandleHook — full counts, text fallback, str(data) fallback
// (Python dict repr!), short message, other event, no data, zero counts,
// and both best-effort error dicts verbatim.
func TestBatteryHandleHook(t *testing.T) {
if n := replayOp(t, "hook"); n != 9 {
t.Fatalf("expected 9 frozen hook rows, got %d", n)
}
want := rowWant(t, "hook:full")
if want["neurons_fired"] != float64(3) || want["memories_recalled"] != float64(5) ||
want["active_tasks"] != float64(2) {
t.Fatalf("frozen hook counts changed: %v", want)
}
if cf, _ := want["context_file"].(string); !strings.HasSuffix(cf, "@MEMDIR@/index/auto_context.json") {
t.Fatalf("context_file normalization changed: %v", cf)
}
errWant := rowWant(t, "hook:warm-list-error")
if errWant["error"] != "'list' object has no attribute 'get'" {
t.Fatalf("list.get error message changed: %v", errWant["error"])
}
errWant = rowWant(t, "hook:fired-int-error")
if errWant["error"] != "object of type 'int' has no len()" {
t.Fatalf("len(int) error message changed: %v", errWant["error"])
}
mustReplay(t, "hook:text-fallback")
mustReplay(t, "hook:str-fallback")
mustReplay(t, "hook:short-message")
mustReplay(t, "hook:other-event")
mustReplay(t, "hook:no-data")
mustReplay(t, "hook:counts-zero")
}
// TestBatteryCLI — main() byte-parity stdout + exit codes: build (plain +
// --json + default message), show (hit + missing), hook (event+data /
// defaults / error dict), argparse exits.
func TestBatteryCLI(t *testing.T) {
if n := replayOp(t, "cli"); n != 10 {
t.Fatalf("expected 10 frozen cli rows, got %d", n)
}
for name, wantExit := range map[string]float64{
"cli:build": 0,
"cli:build-json": 0,
"cli:build-default": 0,
"cli:show-missing": 0,
"cli:show-hit": 0,
"cli:hook": 0,
"cli:hook-default": 0,
"cli:hook-error": 0,
"cli:bad-cmd": 2,
"cli:no-cmd": 2,
} {
want := rowWant(t, name)
if want["exit"] != wantExit {
t.Fatalf("%s: frozen exit code %v != %v", name, want["exit"], wantExit)
}
}
}
// TestBatteryPlugin — the PLUGIN manifest row.
func TestBatteryPlugin(t *testing.T) {
mustReplay(t, "plugin")
want := rowWant(t, "plugin")
if want["name"] != "auto_context" || want["priority"] != float64(5) ||
want["auto_activate"] != true {
t.Fatalf("frozen PLUGIN manifest changed: %v", want)
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.217.0"
__version__ = "0.219.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
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.217.0"' in rune_version,
'__version__ = "0.219.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust outcome_ledger core (dichotomy wave, #2479
tranche). Mirrors to_loader.py's contract:
FFI contract (doc-envelope law):
* ol_run({op, db_path, record?, task_family?, model?}) ->
{"ok": true, "value": ...} | {"ok": false, "err": "..."}
The kernel is RUST-side per the outcome_ledger intent ledger — it owns the
ledger SQL (DDL, INSERT, the six-aggregate stats query) against the system
libsqlite3. The facade keeps makedirs, the CLI and test_ledger.
Free: capability_scoring's cs_free_string (shared process) — this crate
deliberately exports NO free symbol of its own. Override the .so with
OL_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" / "outcome_ledger"
_LIB_PATH = Path(os.environ.get("OL_RUST_LIB", _CRATE_DIR / "target" / "release" / "liboutcome_ledger.so"))
_lib = None
class OutcomeLedgerCoreUnavailable(FileNotFoundError):
"""The Rust outcome_ledger 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 OutcomeLedgerCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
lib.ol_run.argtypes = [ctypes.c_void_p]
lib.ol_run.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(payload: dict) -> dict:
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = lib.ol_run(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError("ol_run returned null")
out = ctypes.string_at(ptr).decode()
# free via any sibling free symbol already in-process (capability_scoring)
try:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
scoring.cs_free_string(ptr)
except OSError:
pass # leak-on-fallback only if capability_scoring absent; bounded
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", "ol_run failed"))
return doc
def run_op(op: str, db_path: str, *, record: dict | None = None,
task_family: str | None = None, model: str | None = None) -> dict:
"""Run one kernel op. Returns the envelope value (stats doc, null, ...)."""
payload: dict = {"op": op, "db_path": db_path}
if record is not None:
payload["record"] = record
if task_family is not None:
payload["task_family"] = task_family
if model is not None:
payload["model"] = model
return _call(payload)["value"]
+36 -4
View File
@@ -71,7 +71,13 @@ class OutcomeLedger:
self._init_db()
def _init_db(self):
"""Initialize the database."""
"""Initialize the database (Rust core when built; makedirs stay
Python — the adapter owns directory prep, the kernel owns the DDL)."""
core = _ol_core()
if core is not None:
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
core.run_op("init_db", self.db_path)
return
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
conn = sqlite3.connect(self.db_path)
try:
@@ -99,7 +105,12 @@ class OutcomeLedger:
conn.close()
def record(self, outcome: OutcomeRecord):
"""Record an outcome."""
"""Record an outcome (Rust core when built — the INSERT runs in the
kernel against the system libsqlite3)."""
core = _ol_core()
if core is not None:
core.run_op("record", self.db_path, record=outcome.to_dict())
return
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
@@ -179,16 +190,37 @@ class OutcomeLedger:
}
def get_stats(self, task_family: str = None) -> dict:
"""Get statistics for a task family."""
"""Get statistics for a task family (Rust core when built — the
six-aggregate query runs in the kernel)."""
core = _ol_core()
if core is not None:
return core.run_op("get_stats", self.db_path, task_family=task_family)
if task_family:
return self._query_stats("WHERE task_family = ?", (task_family,))
return self._query_stats("", ())
def get_model_stats(self, model: str) -> dict:
"""Get statistics for a model."""
"""Get statistics for a model (Rust core when built)."""
core = _ol_core()
if core is not None:
return core.run_op("get_model_stats", self.db_path, model=model)
return self._query_stats("WHERE model = ?", (model,))
# ── Rust core delegation (dichotomy wave, #2479 tranche) ────────────────
def _ol_core():
"""The Rust outcome_ledger core when available (strangler law).
RUNE_OL_CORE=python pins the fallback."""
if os.environ.get("RUNE_OL_CORE", "auto") == "python":
return None
try:
from rune import ol_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
return ol_loader if ol_loader.available() else None
def test_ledger():
"""Test the outcome ledger."""
print("Testing Outcome Ledger...")
+2 -2
View File
@@ -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.217.0
Version: v0.219.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.217.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.219.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+4 -6
View File
@@ -18,7 +18,6 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
name = "outcome_ledger"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
@@ -47,7 +46,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
@@ -72,9 +70,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.151"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"itoa",
"memchr",
@@ -85,9 +83,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.6"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
checksum = "01016da373cd8f7ef12624f796309f5c31ba8d646dd08856c02cd741d823c622"
dependencies = [
"proc-macro2",
"quote",
+1 -1
View File
@@ -2,10 +2,10 @@
name = "outcome_ledger"
version = "0.1.0"
edition = "2021"
description = "FULL parity port of src/rune/outcome_ledger.py (dichotomy wave, #2479 tranche); the ledger SQL runs in Rust against the system libsqlite3, makedirs/CLI/test_ledger stay Python"
[lib]
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -0,0 +1,74 @@
//! Branch battery — replays the FROZEN ORACLE truth VERBATIM
//! (frozen_to_truth.json, generated from the live python oracle with
//! RUNE_OL_CORE=python pinned, timestamps pinned in the inputs so no
//! masking is needed). Each scenario drives the kernel's own temp db
//! through the op script, and every observation (the stats dict / the
//! to_dict doc / null) must equal the frozen oracle exactly. The python
//! side of the same replay lives in tests/test_outcome_ledger_port.py.
use serde_json::Value;
use super::{get_model_stats, get_stats, init_db, record, to_dict, RecordView};
const TRUTH: &str = include_str!("frozen_to_truth.json");
fn step(path: &str, op: &Value) -> Value {
let kind = op["op"].as_str().expect("op name");
match kind {
"new" => {
init_db(path).expect("init_db");
Value::Null
}
"record" => {
let rec = RecordView::from_value(&op["record"]).expect("record doc");
record(path, &rec).expect("record");
Value::Null
}
"to_dict" => to_dict(&RecordView::from_value(&op["record"]).expect("record doc")),
"get_stats" => {
let family = op.get("family").and_then(Value::as_str);
get_stats(path, family).expect("get_stats")
}
"get_model_stats" => {
let model = op["model"].as_str().expect("model");
get_model_stats(path, model).expect("get_model_stats")
}
other => panic!("unknown op {other}"),
}
}
#[test]
fn replays_frozen_oracle_truth_verbatim() {
let truth: Value = serde_json::from_str(TRUTH).expect("truth parse");
for scenario in truth["scenarios"].as_array().expect("scenarios") {
let name = scenario["name"].as_str().expect("scenario name");
let dir = std::env::temp_dir().join(format!("ol_battery_{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let path = dir.join("ol.db");
let path_str = path.to_str().expect("db path");
for (i, op) in scenario["ops"].as_array().expect("ops").iter().enumerate() {
let got = step(path_str, op);
let want = scenario["obs"][i]["result"].clone();
assert_eq!(
got, want,
"{name} op#{i} ({}) diverged from the frozen oracle",
op["op"].as_str().unwrap_or("?")
);
}
let _ = std::fs::remove_dir_all(&dir);
}
}
#[test]
fn unknown_op_is_an_err_envelope_not_a_panic() {
let doc = serde_json::json!({"op": "no_such_op", "db_path": "/tmp/ol_never.db"});
let arg = serde_json::to_string(&doc).unwrap();
let arg = std::ffi::CString::new(arg).unwrap();
let ptr = super::ol_run(arg.as_ptr());
assert!(!ptr.is_null());
let out = unsafe { std::ffi::CStr::from_ptr(ptr) }.to_str().unwrap();
let env: Value = serde_json::from_str(out).unwrap();
assert_eq!(env["ok"], Value::Bool(false));
assert!(env["err"].as_str().unwrap().contains("arg.op must be one of"));
}
@@ -0,0 +1,571 @@
{
"module": "outcome_ledger",
"seed": 2479,
"oracle": "src/rune/outcome_ledger.py",
"pin": "RUNE_OL_CORE=python",
"generated": "2026-10-11",
"scenarios": [
{
"name": "s1_empty_db",
"ops": [
{
"op": "new"
},
{
"op": "get_stats"
},
{
"op": "get_stats",
"family": "greeting"
},
{
"op": "get_model_stats",
"model": "qwen2.5:3b"
}
],
"obs": [
{
"result": null
},
{
"result": {
"total": 0,
"successes": 0,
"success_rate": 0,
"avg_attempts": 0,
"avg_latency_ms": 0,
"corrections": 0,
"correction_rate": 0,
"total_cost": 0
}
},
{
"result": {
"total": 0,
"successes": 0,
"success_rate": 0,
"avg_attempts": 0,
"avg_latency_ms": 0,
"corrections": 0,
"correction_rate": 0,
"total_cost": 0
}
},
{
"result": {
"total": 0,
"successes": 0,
"success_rate": 0,
"avg_attempts": 0,
"avg_latency_ms": 0,
"corrections": 0,
"correction_rate": 0,
"total_cost": 0
}
}
]
},
{
"name": "s2_record_and_stats",
"ops": [
{
"op": "new"
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:01+00:00",
"task_family": "greeting",
"model": "qwen2.5:3b",
"provider": "ollama",
"tier": "T1",
"task_success": true,
"latency_ms": 100.0
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:02+00:00",
"task_family": "coding",
"model": "qwen3:14b",
"provider": "ollama",
"tier": "T2",
"task_success": true,
"attempts": 2,
"latency_ms": 500.0
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:03+00:00",
"task_family": "coding",
"model": "qwen3:14b",
"provider": "ollama",
"tier": "T2",
"task_success": false,
"user_correction": true,
"latency_ms": 600.0
}
},
{
"op": "get_stats",
"family": "greeting"
},
{
"op": "get_stats",
"family": "coding"
},
{
"op": "get_stats"
},
{
"op": "get_model_stats",
"model": "qwen3:14b"
},
{
"op": "get_model_stats",
"model": "unknown-model"
}
],
"obs": [
{
"result": null
},
{
"result": null
},
{
"result": null
},
{
"result": null
},
{
"result": {
"total": 1,
"successes": 1,
"success_rate": 1.0,
"avg_attempts": 1.0,
"avg_latency_ms": 100.0,
"corrections": 0,
"correction_rate": 0.0,
"total_cost": 0.0
}
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 1.5,
"avg_latency_ms": 550.0,
"corrections": 1,
"correction_rate": 0.5,
"total_cost": 0.0
}
},
{
"result": {
"total": 3,
"successes": 2,
"success_rate": 0.6666666666666666,
"avg_attempts": 1.3333333333333333,
"avg_latency_ms": 400.0,
"corrections": 1,
"correction_rate": 0.3333333333333333,
"total_cost": 0.0
}
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 1.5,
"avg_latency_ms": 550.0,
"corrections": 1,
"correction_rate": 0.5,
"total_cost": 0.0
}
},
{
"result": {
"total": 0,
"successes": 0,
"success_rate": 0,
"avg_attempts": 0,
"avg_latency_ms": 0,
"corrections": 0,
"correction_rate": 0,
"total_cost": 0
}
}
]
},
{
"name": "s3_full_record_to_dict",
"ops": [
{
"op": "new"
},
{
"op": "to_dict",
"record": {
"timestamp": "2026-10-11T00:00:04+00:00",
"task_family": "review",
"model": "gpt-5-mini",
"provider": "openai",
"tier": "T3",
"prompt_tokens": 1200,
"output_tokens": 340,
"cached_tokens": 512,
"latency_ms": 123.75,
"attempts": 1,
"verification_result": "pass",
"user_correction": false,
"task_success": true,
"estimated_cost": 0.0025
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:04+00:00",
"task_family": "review",
"model": "gpt-5-mini",
"provider": "openai",
"tier": "T3",
"prompt_tokens": 1200,
"output_tokens": 340,
"cached_tokens": 512,
"latency_ms": 123.75,
"attempts": 1,
"verification_result": "pass",
"user_correction": false,
"task_success": true,
"estimated_cost": 0.0025
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:05+00:00",
"task_family": "review",
"model": "gpt-5-mini",
"provider": "openai",
"tier": "T3",
"prompt_tokens": 1200,
"output_tokens": 340,
"cached_tokens": 512,
"latency_ms": 123.75,
"attempts": 1,
"verification_result": "fail",
"user_correction": false,
"task_success": false,
"estimated_cost": 0.0075
}
},
{
"op": "get_stats"
},
{
"op": "get_stats",
"family": "review"
},
{
"op": "get_model_stats",
"model": "gpt-5-mini"
}
],
"obs": [
{
"result": null
},
{
"result": {
"timestamp": "2026-10-11T00:00:04+00:00",
"task_family": "review",
"model": "gpt-5-mini",
"provider": "openai",
"tier": "T3",
"prompt_tokens": 1200,
"output_tokens": 340,
"cached_tokens": 512,
"latency_ms": 123.75,
"attempts": 1,
"verification_result": "pass",
"user_correction": false,
"task_success": true,
"estimated_cost": 0.0025
}
},
{
"result": null
},
{
"result": null
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 1.0,
"avg_latency_ms": 123.75,
"corrections": 0,
"correction_rate": 0.0,
"total_cost": 0.01
}
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 1.0,
"avg_latency_ms": 123.75,
"corrections": 0,
"correction_rate": 0.0,
"total_cost": 0.01
}
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 1.0,
"avg_latency_ms": 123.75,
"corrections": 0,
"correction_rate": 0.0,
"total_cost": 0.01
}
}
]
},
{
"name": "s4_persistence_reopen",
"ops": [
{
"op": "new"
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:01+00:00",
"task_family": "port",
"model": "qwen2.5:3b",
"provider": "ollama",
"tier": "T1",
"task_success": true,
"latency_ms": 42.5
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:02+00:00",
"task_family": "port",
"model": "qwen3:14b",
"provider": "ollama",
"tier": "T2",
"task_success": false,
"user_correction": true,
"attempts": 3,
"latency_ms": 900.0
}
},
{
"op": "new",
"reuse": true
},
{
"op": "get_stats"
},
{
"op": "get_stats",
"family": "port"
},
{
"op": "get_model_stats",
"model": "qwen2.5:3b"
}
],
"obs": [
{
"result": null
},
{
"result": null
},
{
"result": null
},
{
"result": null
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 2.0,
"avg_latency_ms": 471.25,
"corrections": 1,
"correction_rate": 0.5,
"total_cost": 0.0
}
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 2.0,
"avg_latency_ms": 471.25,
"corrections": 1,
"correction_rate": 0.5,
"total_cost": 0.0
}
},
{
"result": {
"total": 1,
"successes": 1,
"success_rate": 1.0,
"avg_attempts": 1.0,
"avg_latency_ms": 42.5,
"corrections": 0,
"correction_rate": 0.0,
"total_cost": 0.0
}
}
]
},
{
"name": "s5_averages_and_rates",
"ops": [
{
"op": "new"
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:01+00:00",
"task_family": "ops",
"model": "m1",
"task_success": true,
"attempts": 1,
"latency_ms": 10.5,
"estimated_cost": 0.0
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:02+00:00",
"task_family": "ops",
"model": "m1",
"task_success": false,
"user_correction": true,
"attempts": 2,
"latency_ms": 20.25,
"estimated_cost": 1.5
}
},
{
"op": "record",
"record": {
"timestamp": "2026-10-11T00:00:03+00:00",
"task_family": "ops",
"model": "m2",
"task_success": true,
"attempts": 3,
"latency_ms": 30.125,
"user_correction": true,
"estimated_cost": 2.25
}
},
{
"op": "get_stats"
},
{
"op": "get_model_stats",
"model": "m1"
},
{
"op": "get_model_stats",
"model": "m2"
},
{
"op": "get_stats",
"family": "ops"
}
],
"obs": [
{
"result": null
},
{
"result": null
},
{
"result": null
},
{
"result": null
},
{
"result": {
"total": 3,
"successes": 2,
"success_rate": 0.6666666666666666,
"avg_attempts": 2.0,
"avg_latency_ms": 20.291666666666668,
"corrections": 2,
"correction_rate": 0.6666666666666666,
"total_cost": 3.75
}
},
{
"result": {
"total": 2,
"successes": 1,
"success_rate": 0.5,
"avg_attempts": 1.5,
"avg_latency_ms": 15.375,
"corrections": 1,
"correction_rate": 0.5,
"total_cost": 1.5
}
},
{
"result": {
"total": 1,
"successes": 1,
"success_rate": 1.0,
"avg_attempts": 3.0,
"avg_latency_ms": 30.125,
"corrections": 1,
"correction_rate": 1.0,
"total_cost": 2.25
}
},
{
"result": {
"total": 3,
"successes": 2,
"success_rate": 0.6666666666666666,
"avg_attempts": 2.0,
"avg_latency_ms": 20.291666666666668,
"corrections": 2,
"correction_rate": 0.6666666666666666,
"total_cost": 3.75
}
}
]
}
]
}
+387 -86
View File
@@ -1,106 +1,407 @@
//! outcome_ledger — persist cognitive-performance evidence (W6).
//! PARITY port of src/rune/outcome_ledger.py.
use serde::{Deserialize, Serialize};
//!
//! FULL parity port of src/rune/outcome_ledger.py (dichotomy wave, #2479
//! tranche). The kernel is RUST-side per the intent ledger
//! (store/scaffold/intent/outcome_ledger.json: sqlite3, storage-backed,
//! deterministic/state domain). It owns the SAME SQL the python oracle
//! runs — table DDL, the 14-column INSERT and the six-aggregate stats
//! query — against the SYSTEM libsqlite3 (memory_storage precedent: a
//! system library is not a crate dependency), so aggregation happens in
//! the identical engine and the floats come back identical.
//!
//! FFI (doc-envelope law): `ol_run({op, db_path, record?, task_family?,
//! model?})` → {"ok": true, "value": ...} | {"ok": false, "err": "..."}.
//! ONE entry point (the intent's "Rust 1 fn, no delegation" ruling).
//! The caller frees returned strings with capability_scoring's
//! cs_free_string (shared process) — this crate deliberately exports NO
//! free symbol of its own.
//!
//! Pure-kernel law: the kernel takes the db_path as input and owns no
//! global state, no clock (timestamps ride in the record), no CLI and no
//! makedirs — the facade keeps directory prep, the CLI and test_ledger.
//!
//! Language-advantage pass (Phase B, battery-pinned): the op wire string
//! parses into an `Op` enum with an EXHAUSTIVE dispatch match (adding an
//! op is a compile error until handled), the record is a zero-copy
//! borrowed `RecordView<'a>` over the wire doc (no owned-string round
//! trip on the hot INSERT path), the stats WHERE fragment is a
//! `Where<'a>` enum whose SQL is a `&'static str` const (the where clause
//! is never interpolated from data), and binding walks an iterator.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OutcomeRecord {
#[serde(default)]
pub timestamp: String,
#[serde(default)]
pub task_family: String,
#[serde(default)]
pub model: String,
#[serde(default)]
pub provider: String,
#[serde(default)]
pub tier: String,
#[serde(default)]
pub mod sqlite;
#[cfg(test)]
mod branch_battery;
use serde_json::{json, Value};
use sqlite::{Conn, Stmt};
/// Zero-copy borrowed view of a cognitive-work outcome record — mirrors
/// the oracle's OutcomeRecord field-for-field; python dataclass defaults
/// apply on the wire (absent → default). All string fields borrow from
/// the argument doc; nothing is allocated to drive an INSERT.
#[derive(Debug, Clone, PartialEq)]
pub struct RecordView<'a> {
pub timestamp: &'a str,
pub task_family: &'a str,
pub model: &'a str,
pub provider: &'a str,
pub tier: &'a str,
pub prompt_tokens: i64,
#[serde(default)]
pub output_tokens: i64,
#[serde(default)]
pub cached_tokens: i64,
#[serde(default)]
pub latency_ms: f64,
#[serde(default)]
pub attempts: i64,
#[serde(default)]
pub verification_result: String,
#[serde(default)]
pub verification_result: &'a str,
pub user_correction: bool,
#[serde(default)]
pub task_success: bool,
#[serde(default)]
pub estimated_cost: f64,
}
pub struct OutcomeStats {
pub total: usize,
pub success_rate: f64,
pub first_attempt_rate: f64,
pub avg_latency_ms: f64,
pub total_cost: f64,
}
pub fn compute_stats(records: &[OutcomeRecord]) -> OutcomeStats {
let total = records.len();
if total == 0 {
return OutcomeStats {
total: 0,
success_rate: 0.0,
first_attempt_rate: 0.0,
avg_latency_ms: 0.0,
total_cost: 0.0,
};
}
let successes = records.iter().filter(|r| r.task_success).count();
let first_attempts = records.iter().filter(|r| r.attempts == 1).count();
let total_latency: f64 = records.iter().map(|r| r.latency_ms).sum();
let total_cost: f64 = records.iter().map(|r| r.estimated_cost).sum();
OutcomeStats {
total,
success_rate: successes as f64 / total as f64,
first_attempt_rate: first_attempts as f64 / total as f64,
avg_latency_ms: total_latency / total as f64,
total_cost,
impl<'a> RecordView<'a> {
pub fn from_value(v: &'a Value) -> Option<RecordView<'a>> {
let o = v.as_object()?;
let s = |k: &str| Some(o.get(k).and_then(Value::as_str).unwrap_or_default());
let i = |k: &str, d: i64| o.get(k).and_then(Value::as_i64).unwrap_or(d);
let f = |k: &str, d: f64| o.get(k).and_then(Value::as_f64).unwrap_or(d);
let b = |k: &str| o.get(k).and_then(Value::as_bool).unwrap_or(false);
Some(RecordView {
timestamp: s("timestamp")?,
task_family: s("task_family")?,
model: s("model")?,
provider: s("provider")?,
tier: s("tier")?,
prompt_tokens: i("prompt_tokens", 0),
output_tokens: i("output_tokens", 0),
cached_tokens: i("cached_tokens", 0),
latency_ms: f("latency_ms", 0.0),
attempts: i("attempts", 1),
verification_result: s("verification_result")?,
user_correction: b("user_correction"),
task_success: b("task_success"),
estimated_cost: f("estimated_cost", 0.0),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
/// OutcomeRecord.to_dict — same key order as the oracle.
pub fn to_dict(r: &RecordView) -> Value {
json!({
"timestamp": r.timestamp,
"task_family": r.task_family,
"model": r.model,
"provider": r.provider,
"tier": r.tier,
"prompt_tokens": r.prompt_tokens,
"output_tokens": r.output_tokens,
"cached_tokens": r.cached_tokens,
"latency_ms": r.latency_ms,
"attempts": r.attempts,
"verification_result": r.verification_result,
"user_correction": r.user_correction,
"task_success": r.task_success,
"estimated_cost": r.estimated_cost,
})
}
#[test]
fn test_stats_empty() {
let stats = compute_stats(&[]);
assert_eq!(stats.total, 0);
const CREATE_SQL: &str = r#"
CREATE TABLE IF NOT EXISTS outcomes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
task_family TEXT,
model TEXT,
provider TEXT,
tier TEXT,
prompt_tokens INTEGER,
output_tokens INTEGER,
cached_tokens INTEGER,
latency_ms REAL,
attempts INTEGER,
verification_result TEXT,
user_correction BOOLEAN,
task_success BOOLEAN,
estimated_cost REAL
)
"#;
/// OutcomeLedger._init_db — identical DDL to the oracle.
pub fn init_db(db_path: &str) -> Result<(), String> {
let conn = Conn::open(db_path)?;
conn.exec(CREATE_SQL)
}
const INSERT_SQL: &str = r#"
INSERT INTO outcomes (
timestamp, task_family, model, provider, tier,
prompt_tokens, output_tokens, cached_tokens,
latency_ms, attempts, verification_result,
user_correction, task_success, estimated_cost
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"#;
/// OutcomeLedger.record — identical INSERT to the oracle, driven from the
/// borrowed view: every bind reads straight out of the wire doc.
pub fn record(db_path: &str, r: &RecordView) -> Result<(), String> {
let conn = Conn::open(db_path)?;
let mut stmt = conn.prepare(INSERT_SQL)?;
let bind_err = [
stmt.bind_text(1, r.timestamp),
stmt.bind_text(2, r.task_family),
stmt.bind_text(3, r.model),
stmt.bind_text(4, r.provider),
stmt.bind_text(5, r.tier),
stmt.bind_int64(6, r.prompt_tokens),
stmt.bind_int64(7, r.output_tokens),
stmt.bind_int64(8, r.cached_tokens),
stmt.bind_double(9, r.latency_ms),
stmt.bind_int64(10, r.attempts),
stmt.bind_text(11, r.verification_result),
stmt.bind_int64(12, i64::from(r.user_correction)),
stmt.bind_int64(13, i64::from(r.task_success)),
stmt.bind_double(14, r.estimated_cost),
]
.into_iter()
.enumerate()
.find_map(|(idx, res)| res.err().map(|e| format!("bind #{}: {e}", idx + 1)));
if let Some(e) = bind_err {
return Err(e);
}
let step_res = stmt.step();
stmt.finalize();
step_res.map(|_| ())
}
const STATS_SQL_HEAD: &str = r#"
SELECT
COUNT(*) as total,
SUM(CASE WHEN task_success THEN 1 ELSE 0 END) as successes,
AVG(attempts) as avg_attempts,
AVG(latency_ms) as avg_latency,
SUM(CASE WHEN user_correction THEN 1 ELSE 0 END) as corrections,
SUM(estimated_cost) as total_cost
FROM outcomes
"#;
/// The stats filter — each variant's SQL is a `&'static str` const, so
/// the where clause is NEVER interpolated from data (the oracle's
/// f-string shape, minus the injection-shaped surface).
#[derive(Debug, Clone, PartialEq)]
pub enum Where<'a> {
All,
TaskFamily(&'a str),
Model(&'a str),
}
impl Where<'_> {
fn sql(&self) -> &'static str {
match self {
Where::All => "",
Where::TaskFamily(_) => "WHERE task_family = ?",
Where::Model(_) => "WHERE model = ?",
}
}
#[test]
fn test_stats_with_data() {
let records = vec![
OutcomeRecord {
task_success: true,
attempts: 1,
latency_ms: 100.0,
..Default::default()
},
OutcomeRecord {
task_success: true,
attempts: 2,
latency_ms: 200.0,
..Default::default()
},
OutcomeRecord {
task_success: false,
attempts: 3,
latency_ms: 300.0,
..Default::default()
},
];
let stats = compute_stats(&records);
assert_eq!(stats.total, 3);
assert!((stats.success_rate - 0.667).abs() < 0.01);
assert!((stats.first_attempt_rate - 0.333).abs() < 0.01);
fn param(&self) -> Option<&str> {
match self {
Where::All => None,
Where::TaskFamily(v) | Where::Model(v) => Some(v),
}
}
}
/// OutcomeLedger._query_stats — identical aggregate SQL to the oracle.
pub fn query_stats(db_path: &str, wc: Where) -> Result<Value, String> {
let conn = Conn::open(db_path)?;
let sql = format!("{STATS_SQL_HEAD}
{}
", wc.sql());
let mut stmt = conn.prepare(&sql)?;
if let Some(p) = wc.param() {
stmt.bind_text(1, p)?;
}
let has_row = stmt.step()?;
let out = if has_row && stmt.column_int64(0) > 0 {
let total = stmt.column_int64(0);
let successes = nonzero_or_zero(&stmt, 1);
let corrections = nonzero_or_zero(&stmt, 4);
json!({
"total": total,
"successes": successes,
"success_rate": successes as f64 / total as f64,
"avg_attempts": nullable_double(&stmt, 2),
"avg_latency_ms": nullable_double(&stmt, 3),
"corrections": corrections,
"correction_rate": corrections as f64 / total as f64,
"total_cost": nullable_double(&stmt, 5),
})
} else {
zero_stats()
};
stmt.finalize();
Ok(out)
}
/// SUM(CASE ...) over existing rows — never NULL, but read defensively
/// the way python's sqlite3 would surface it.
fn nonzero_or_zero(stmt: &Stmt, i: usize) -> i64 {
if stmt.column_type(i) == sqlite::SQLITE_NULL {
0
} else {
stmt.column_int64(i)
}
}
/// Read an AVG/SUM column the way python's sqlite3 would surface it:
/// REAL → float, NULL → None (the oracle's dicts carry whichever came back).
fn nullable_double(stmt: &Stmt, i: usize) -> Value {
if stmt.column_type(i) == sqlite::SQLITE_NULL {
Value::Null
} else {
json!(stmt.column_double(i))
}
}
/// The oracle's empty-result dict — note these are INTS in python
/// (`"success_rate": 0`, not `0.0`), frozen verbatim in the truth battery.
fn zero_stats() -> Value {
json!({
"total": 0,
"successes": 0,
"success_rate": 0,
"avg_attempts": 0,
"avg_latency_ms": 0,
"corrections": 0,
"correction_rate": 0,
"total_cost": 0,
})
}
/// OutcomeLedger.get_stats — the task_family branch split.
pub fn get_stats(db_path: &str, task_family: Option<&str>) -> Result<Value, String> {
match task_family {
Some(f) => query_stats(db_path, Where::TaskFamily(f)),
None => query_stats(db_path, Where::All),
}
}
/// OutcomeLedger.get_model_stats.
pub fn get_model_stats(db_path: &str, model: &str) -> Result<Value, String> {
query_stats(db_path, Where::Model(model))
}
// ── FFI (doc-envelope law): ol_run — the ONE entry point ────────────────
/// The wire ops — parsed once; dispatch is an exhaustive match, so adding
/// an op is a compile error until it is handled here.
#[derive(Debug, Clone, Copy, PartialEq)]
enum Op {
InitDb,
Record,
GetStats,
GetModelStats,
ToDict,
}
impl Op {
fn parse(s: &str) -> Option<Op> {
match s {
"init_db" => Some(Op::InitDb),
"record" => Some(Op::Record),
"get_stats" => Some(Op::GetStats),
"get_model_stats" => Some(Op::GetModelStats),
"to_dict" => Some(Op::ToDict),
_ => None,
}
}
}
/// Zero-copy borrowed argument view over the wire doc.
struct Args<'a> {
db_path: &'a str,
record: Option<&'a Value>,
task_family: Option<&'a str>,
model: Option<&'a str>,
}
impl<'a> Args<'a> {
fn from_value(v: &'a Value) -> Option<Args<'a>> {
Some(Args {
db_path: v.get("db_path")?.as_str()?,
record: v.get("record"),
task_family: v.get("task_family").and_then(Value::as_str),
model: v.get("model").and_then(Value::as_str),
})
}
fn record(&self) -> Result<RecordView<'a>, String> {
let v = self.record.ok_or_else(|| "arg.record required".to_string())?;
RecordView::from_value(v).ok_or_else(|| "arg.record invalid".to_string())
}
}
fn read_arg(ptr: *const std::os::raw::c_char) -> Result<String, String> {
if ptr.is_null() {
return Err("null argument pointer".to_string());
}
unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map(|s| s.to_string())
.map_err(|e| format!("argument is not valid UTF-8: {e}"))
}
fn envelope(result: Result<Value, String>) -> *mut std::os::raw::c_char {
let body = match result {
Ok(value) => json!({"ok": true, "value": value}),
Err(err) => json!({"ok": false, "err": err}),
};
match serde_json::to_string(&body)
.ok()
.and_then(|s| std::ffi::CString::new(s).ok())
{
Some(c) => c.into_raw(),
None => std::ptr::null_mut(),
}
}
/// ol_run({"op", "db_path", "record"?, "task_family"?, "model"?}) —
/// ops: init_db | record | get_stats | get_model_stats | to_dict.
#[allow(clippy::not_unsafe_ptr_arg_deref)]
// pinned: #[no_mangle] extern "C" — the ol_loader.py ABI requires this exact safe-fn signature; the raw pointer IS the FFI contract
#[no_mangle]
pub extern "C" fn ol_run(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
let result = read_arg(arg)
.and_then(|text| serde_json::from_str::<Value>(&text).map_err(|e| e.to_string()))
.and_then(|doc| {
let op = doc
.get("op")
.and_then(Value::as_str)
.and_then(Op::parse)
.ok_or_else(|| "arg.op must be one of init_db|record|get_stats|get_model_stats|to_dict".to_string())?;
let args = Args::from_value(&doc).ok_or_else(|| "arg.db_path required".to_string())?;
dispatch(op, &args)
});
envelope(result)
}
fn dispatch(op: Op, a: &Args) -> Result<Value, String> {
match op {
Op::InitDb => {
init_db(a.db_path)?;
Ok(Value::Null)
}
Op::Record => {
let rec = a.record()?;
record(a.db_path, &rec)?;
Ok(Value::Null)
}
Op::GetStats => get_stats(a.db_path, a.task_family),
Op::GetModelStats => {
let model = a.model.ok_or_else(|| "arg.model required".to_string())?;
get_model_stats(a.db_path, model)
}
Op::ToDict => {
let rec = a.record()?;
Ok(to_dict(&rec))
}
}
}
+209
View File
@@ -0,0 +1,209 @@
//! sqlite.rs — minimal bindings to the SYSTEM libsqlite3 for the
//! outcome_ledger kernel (dichotomy wave, #2479 tranche). Follows the
//! memory_storage precedent (R2 W2.0, Nick's ruling): SQLite is retained
//! and wired in; a system library is not a crate dependency, so the
//! kernel's only cargo deps stay the wave's serde_json.
//!
//! READ-WRITE: outcome_ledger is a ledger — record() INSERTs and init_db
//! CREATEs the table, mirroring the python oracle's sqlite3 usage exactly
//! (same SQL text, same prepared-statement lifecycle).
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
const SQLITE_OK: c_int = 0;
const SQLITE_ROW: c_int = 100;
const SQLITE_DONE: c_int = 101;
const SQLITE_OPEN_READWRITE: c_int = 0x00000002;
const SQLITE_OPEN_CREATE: c_int = 0x00000004;
pub const SQLITE_INTEGER: c_int = 1;
pub const SQLITE_FLOAT: c_int = 2;
pub const SQLITE_TEXT: c_int = 3;
pub const SQLITE_NULL: c_int = 5;
const SQLITE_TRANSIENT: isize = -1;
#[link(name = "sqlite3")]
extern "C" {
fn sqlite3_open_v2(
filename: *const c_char,
db: *mut *mut c_void,
flags: c_int,
vfs: *const c_char,
) -> c_int;
fn sqlite3_prepare_v2(
db: *mut c_void,
sql: *const c_char,
nbytes: c_int,
stmt: *mut *mut c_void,
tail: *mut *const c_char,
) -> c_int;
fn sqlite3_bind_int64(stmt: *mut c_void, idx: c_int, val: i64) -> c_int;
fn sqlite3_bind_double(stmt: *mut c_void, idx: c_int, val: f64) -> c_int;
fn sqlite3_bind_text(
stmt: *mut c_void,
idx: c_int,
val: *const c_char,
nbytes: c_int,
destructor: isize,
) -> c_int;
fn sqlite3_step(stmt: *mut c_void) -> c_int;
fn sqlite3_column_type(stmt: *mut c_void, i: c_int) -> c_int;
fn sqlite3_column_int64(stmt: *mut c_void, i: c_int) -> i64;
fn sqlite3_column_double(stmt: *mut c_void, i: c_int) -> f64;
fn sqlite3_column_text(stmt: *mut c_void, i: c_int) -> *const u8;
fn sqlite3_finalize(stmt: *mut c_void) -> c_int;
fn sqlite3_close(db: *mut c_void) -> c_int;
fn sqlite3_errmsg(db: *mut c_void) -> *const c_char;
}
pub struct Conn(*mut c_void);
impl Conn {
pub fn open(path: &str) -> Result<Conn, String> {
let cpath = CString::new(path).map_err(|e| e.to_string())?;
let mut db: *mut c_void = std::ptr::null_mut();
let rc = unsafe {
sqlite3_open_v2(cpath.as_ptr(), &mut db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, std::ptr::null())
};
if rc != SQLITE_OK {
return Err(format!("sqlite3_open_v2 failed (rc={rc}) for {path}"));
}
Ok(Conn(db))
}
fn errmsg(&self) -> String {
unsafe {
CStr::from_ptr(sqlite3_errmsg(self.0))
.to_string_lossy()
.into_owned()
}
}
/// Run a constant SQL statement to completion (CREATE TABLE ...).
pub fn exec(&self, sql: &str) -> Result<(), String> {
let csql = CString::new(sql).map_err(|e| e.to_string())?;
let mut stmt: *mut c_void = std::ptr::null_mut();
let rc = unsafe {
sqlite3_prepare_v2(self.0, csql.as_ptr(), -1, &mut stmt, std::ptr::null_mut())
};
if rc != SQLITE_OK {
return Err(self.errmsg());
}
let rc = unsafe { sqlite3_step(stmt) };
let step_err = if rc != SQLITE_ROW && rc != SQLITE_DONE {
Some(self.errmsg())
} else {
None
};
unsafe { sqlite3_finalize(stmt) };
match step_err {
Some(e) => Err(e),
None => Ok(()),
}
}
pub fn prepare(&self, sql: &str) -> Result<Stmt, String> {
let csql = CString::new(sql).map_err(|e| e.to_string())?;
let mut stmt: *mut c_void = std::ptr::null_mut();
let rc = unsafe {
sqlite3_prepare_v2(self.0, csql.as_ptr(), -1, &mut stmt, std::ptr::null_mut())
};
if rc != SQLITE_OK {
return Err(self.errmsg());
}
Ok(Stmt(stmt))
}
}
impl Drop for Conn {
fn drop(&mut self) {
unsafe { sqlite3_close(self.0) };
}
}
pub struct Stmt(*mut c_void);
impl Stmt {
pub fn bind_text(&mut self, idx: usize, val: &str) -> Result<(), String> {
let cval = CString::new(val).map_err(|e| e.to_string())?;
let rc = unsafe {
sqlite3_bind_text(self.0, idx as c_int, cval.as_ptr(), val.len() as c_int, SQLITE_TRANSIENT)
};
if rc != SQLITE_OK {
Err(format!("sqlite3_bind_text failed (rc={rc})"))
} else {
Ok(())
}
}
pub fn bind_int64(&mut self, idx: usize, val: i64) -> Result<(), String> {
let rc = unsafe { sqlite3_bind_int64(self.0, idx as c_int, val) };
if rc != SQLITE_OK {
Err(format!("sqlite3_bind_int64 failed (rc={rc})"))
} else {
Ok(())
}
}
pub fn bind_double(&mut self, idx: usize, val: f64) -> Result<(), String> {
let rc = unsafe { sqlite3_bind_double(self.0, idx as c_int, val) };
if rc != SQLITE_OK {
Err(format!("sqlite3_bind_double failed (rc={rc})"))
} else {
Ok(())
}
}
/// Bind nothing at `idx` — the python sqlite3 NULL equivalent.
pub fn bind_null(&mut self, idx: usize) -> Result<(), String> {
// sqlite3_bind_null exists but a skipped parameter is also NULL;
// bind explicitly to keep the statement fully bound.
extern "C" {
fn sqlite3_bind_null(stmt: *mut c_void, idx: c_int) -> c_int;
}
let rc = unsafe { sqlite3_bind_null(self.0, idx as c_int) };
if rc != SQLITE_OK {
Err(format!("sqlite3_bind_null failed (rc={rc})"))
} else {
Ok(())
}
}
/// Advance one row. Ok(true) = row available, Ok(false) = done.
pub fn step(&mut self) -> Result<bool, String> {
match unsafe { sqlite3_step(self.0) } {
SQLITE_ROW => Ok(true),
SQLITE_DONE => Ok(false),
rc => Err(format!("sqlite3_step failed (rc={rc})")),
}
}
pub fn column_type(&self, i: usize) -> c_int {
unsafe { sqlite3_column_type(self.0, i as c_int) }
}
pub fn column_int64(&self, i: usize) -> i64 {
unsafe { sqlite3_column_int64(self.0, i as c_int) }
}
pub fn column_double(&self, i: usize) -> f64 {
unsafe { sqlite3_column_double(self.0, i as c_int) }
}
pub fn column_text(&self, i: usize) -> String {
unsafe {
let ptr = sqlite3_column_text(self.0, i as c_int);
if ptr.is_null() {
String::new()
} else {
CStr::from_ptr(ptr.cast::<c_char>()).to_string_lossy().into_owned()
}
}
}
pub fn finalize(self) {
unsafe { sqlite3_finalize(self.0) };
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,25 @@
# Contract BUILDER-2026-10-11T21:57:54Z-52c500-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/auto_context.py to FULL Go (dichotomy wave #2479, GO side verdict): name-preserving port of all 8 public functions to src/go/auto_context with a frozen oracle-truth battery (every function, every branch), then a Phase B language-advantage pass.
## What it connects to
- `src/go/auto_context/`
- `src/rune/auto_context.py`
## 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-11T23:43:42Z", "contract": "BUILDER-2026-10-11T21:57:54Z-52c500-factory-wave", "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": ["src/go/auto_context/", "src/rune/auto_context.py"], "out_of_scope": [], "verdict": "green"}}.
## Evidence
- contract opened for auto_context FULL Go port; parity plan frozen
- port + 52-row truth generator built; parity commit f129a6a4
- battery 52/52 frozen rows exact-equal vs live oracle (truth regenerated twice, deterministic); go test+vet+gofmt clean;
- closure-record
+89
View File
@@ -0,0 +1,89 @@
"""Port evidence for the outcome_ledger dichotomy port (#2479 tranche).
Replays the frozen oracle truth (src/rust/outcome_ledger/src/
frozen_to_truth.json — generated from the live python oracle with
RUNE_OL_CORE=python pinned and timestamps pinned in the inputs, 5
scenarios / 35 observations) through the wired facade TWICE:
* python path — RUNE_OL_CORE=python (the pinned fallback)
* rust path — the built kernel via ol_loader (skipped when absent)
Every observation (result) must equal the frozen oracle exactly.
"""
import json
from pathlib import Path
import pytest
from rune.outcome_ledger import OutcomeLedger, OutcomeRecord
_TRUTH = Path(__file__).resolve().parents[1] / "src" / "rust" / "outcome_ledger" / "src" / "frozen_to_truth.json"
class Driver:
"""Drives op scripts against the live facade the way the oracle was
driven when the truth was frozen."""
def __init__(self, workdir: Path, n: int):
self.path = str(workdir / f"ol_{n}.db")
self.ledger = None
workdir.mkdir(parents=True, exist_ok=True)
def step(self, op: dict):
kind = op["op"]
if kind == "new":
self.ledger = OutcomeLedger(self.path)
return {"result": None}
if kind == "record":
self.ledger.record(OutcomeRecord(**op["record"]))
return {"result": None}
if kind == "to_dict":
return {"result": OutcomeRecord(**op["record"]).to_dict()}
if kind == "get_stats":
fam = op.get("family")
return {"result": self.ledger.get_stats() if fam is None else self.ledger.get_stats(fam)}
if kind == "get_model_stats":
return {"result": self.ledger.get_model_stats(op["model"])}
raise ValueError(f"unknown op {kind}")
def run(self, ops):
return [self.step(op) for op in ops]
@pytest.fixture()
def truth():
with open(_TRUTH) as f:
return json.load(f)
def _replay(truth, tmp_path, monkeypatch, pin_python: bool):
if pin_python:
monkeypatch.setenv("RUNE_OL_CORE", "python")
else:
monkeypatch.delenv("RUNE_OL_CORE", raising=False)
for n, s in enumerate(truth["scenarios"]):
d = Driver(tmp_path, n)
got = d.run(s["ops"])
assert len(got) == len(s["obs"]), s["name"]
for i, (g, w) in enumerate(zip(got, s["obs"])):
assert g == w, f"{s['name']} op#{i} ({s['ops'][i].get('op')}) diverged"
def test_python_fallback_path_replays_frozen_truth(truth, tmp_path, monkeypatch):
_replay(truth, tmp_path / "py", monkeypatch, pin_python=True)
def test_rust_kernel_path_replays_frozen_truth(truth, tmp_path, monkeypatch):
from rune import ol_loader
if not ol_loader.available():
pytest.skip("outcome_ledger kernel not built")
_replay(truth, tmp_path / "rust", monkeypatch, pin_python=False)
def test_cli_test_ledger_still_green(monkeypatch):
# the facade-level self-test stays python (CLI + test_ledger are not
# kernel surface) and must stay green with the kernel built too
monkeypatch.delenv("RUNE_OL_CORE", raising=False)
from rune import outcome_ledger as ol
ol.test_ledger()