Compare commits

..
9 Commits
Author SHA1 Message Date
conductor 8bb6ada8b9 chore(release): auto-bump version to 0.358.1 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 30s
CI Pipeline / lint (push) Failing after 43s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 8s
CI Pipeline / unit-tests (push) Failing after 1m12s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Failing after 36s
CI Pipeline / canary (push) Failing after 57s
CI Pipeline / gitea-offline (push) Successful in 16s
deploy / deploy (push) Failing after 13s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 06:26:46 +00:00
Nick 53ab710eb6 fix(cache): eval-surface flush + semantic parity battery — the live-parity battery caught deferred-stats loss on the eval surface (Gitea #2587 follow-through)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
CI Pipeline / lint (push) Canceled after 19s
ROOT CAUSE (found by the battery, red): cacheEvalOp created a fresh Env per
op and never flushed — the oracle's atexit.register(flush_stats) semantic
was missing from the eval surface, so hit accounting never reached the db
across CLI invocations (hits stayed 0 forever).
FIX: cacheEvalOps tracks every Env used and flushes each at batch end
(atexit semantic restored).
BATTERY: JSON outputs compare semantically (DeepEqual after age
normalization) — Go's alphabetical marshaller vs Python's insertion-order
dumps made byte comparison wrong; hit accounting fields normalized
(sequence-dependent by design under deferred flush, presence compared not
value, divergence documented in-test).
2026-10-25 06:26:28 +00:00
Nick 19664559c5 docs(pins): REBASE-COMPLETION wave 11/11 — the pins boundary verdict (Gitea #2587): VERDICT Python-boundary with Rust-kernel authority, NO Go wiring. Both audit premises were stale: a Go pins port exists (src/go/pins batch 58) but carries only a recall kernel over (state, item_id) — a parity artifact, not an authority; and pins is already delegated — recall/unpin/list/store-path resolve through the Rust cognition_core broker (live per the _cc_core probe), with the retired-Python-fallback RuntimeError law in force. Wiring Go would add a THIRD engine over the same semantics (no-duplicate-authority). The canonical PinRecord store (AtomicStore) is the reactive Python surface justifying the boundary. Verdict battery (4 tests) pins the topology so drift in either direction flips it; receipt verification/integrated/pins-verdict-integrated.json
Auto-version and Release / release (push) Failing after 32s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / unit-tests (push) Failing after 1m10s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 1m0s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / gitea-offline (push) Successful in 19s
deploy / deploy (push) Failing after 16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 8s
2026-10-25 04:28:11 +00:00
conductor 68e8f6a351 chore(release): auto-bump version to 0.358.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 37s
CI Pipeline / lint (push) Failing after 40s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 8s
CI Pipeline / unit-tests (push) Failing after 1m7s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / canary (push) Failing after 1m1s
deploy / deploy (push) Failing after 13s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 8s
Promotion record: (unknown)
2026-10-25 04:12:51 +00:00
Nick b4eb27a749 feat(paths): REBASE-COMPLETION wave 10/11 — fail-loud Go delegation wired for all six path resolvers (Gitea #2587): eval.go ops surface (repo_root/crate_dir/deploy_root/rune_home/harness_scripts/wait_resume_dir — the library previously had NO CLI) beside the frozen-truth replay (still green); every resolver in src/rune/paths.py resolves through the Go binary with identical env-override semantics; Python oracle bodies kept as RUNE_PATHS_ENGINE=python escape/oracle, RUNE_PATHS_BIN override, typed RuntimeError on missing binary; the Go side's Law 7 dichotomy verdict is honored by the receipt (delegation wired per the audit's zero-delegation finding, byte-identical resolution pinned both directions); red-first battery 9 tests; oracle default-path mutation flips the battery; receipt verification/integrated/paths-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
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 26s
deploy / deploy (push) Canceled after 1s
CI Pipeline / lint (push) Canceled after 26s
2026-10-25 04:12:05 +00:00
conductor 7d96be8f40 chore(release): auto-bump version to 0.357.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 43s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m49s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 14s
Auto-version and Release / release (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 14m30s
CI Pipeline / unit-tests (push) Canceled after 18m46s
Promotion record: (unknown)
2026-10-25 03:53:23 +00:00
Nick 690c1b4832 feat(voicebox): REBASE-COMPLETION wave 9/11 — fail-loud Go delegation wired for the hook seam (Gitea #2587): eval.go ops surface (handle_hook, manifest) beside the truth replay (still green); handle_hook in src/rune/voicebox.py resolves through the Go binary — the engine's documented empty-event validation improvement (error where the oracle lies ok) surfaces through the delegation, pinned both sides; Python oracle body kept as RUNE_VOICEBOX_ENGINE=python escape/oracle, RUNE_VOICEBOX_BIN override, typed RuntimeError on missing binary; speak/listen/talk stay Python (pynput/venv-bound — blocks_full_switchover recorded); red-first battery 6 tests; oracle handler mutation flips the battery; receipt verification/integrated/voicebox-integrated.json
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 26s
2026-10-25 03:52:54 +00:00
conductor 1a9cfd37d8 chore(release): auto-bump version to 0.356.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 49s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m38s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 36s
CI Pipeline / gitea-offline (push) Successful in 16s
Auto-version and Release / release (push) Failing after 26s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 14m6s
CI Pipeline / unit-tests (push) Canceled after 18m12s
Promotion record: (unknown)
2026-10-25 03:34:17 +00:00
Nick bee77e00e5 feat(auto-context): REBASE-COMPLETION wave 8/11 — fail-loud Go delegation wired for auto-context build (Gitea #2587): eval.go ops surface (build_auto_context, load_active_tasks; memory_dir-addressed; subprocess seams shell the same warm_router/memory_store scripts) beside the truth replay (still green); both functions in src/rune/auto_context.py resolve through the Go binary; Python oracle bodies kept as RUNE_AUTO_CONTEXT_ENGINE=python escape/oracle, RUNE_AUTO_CONTEXT_BIN override, typed RuntimeError on missing binary; red-first battery 8 tests; oracle task-filter mutation flips the battery; receipt verification/integrated/auto-context-integrated.json
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
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
deploy / deploy (push) Canceled after 14s
2026-10-25 03:34:00 +00:00
24 changed files with 1063 additions and 18 deletions

No files matched your search

+2 -2
View File
@@ -1,9 +1,9 @@
# Rune
**Current release: v0.355.0**
**Current release: v0.358.1**
<!-- highlights:start -->
## Highlights — from the latest release (v0.355.0)
## Highlights — from the latest release (v0.358.1)
- Plain-English jargon dictionary covers the v0.329 vocabulary, plus catch-all sentence simplification pass.
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.355.0"
version = "0.358.1"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+104
View File
@@ -0,0 +1,104 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/auto_context.py). `auto-context eval <ops.json>` reads a
// JSON array of op dicts and emits one result per op.
//
// Ops: build_auto_context (message; the subprocess seams — warm_router
// and memory_store — run through the engine's RunnerFunc seam, which
// shells the same Python scripts the oracle body calls), load_active_tasks.
// save_context/handle_hook stay oracle-side (the context file write is
// the caller's seam).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_AUTO_CONTEXT_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func autoCtxEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := autoCtxEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func autoCtxEvalOp(name string, op map[string]any) (any, string) {
if dir, ok := op["memory_dir"].(string); ok && dir != "" {
oldMemDir := os.Getenv("RUNE_MEMORY_DIR")
os.Setenv("RUNE_MEMORY_DIR", dir)
defer os.Setenv("RUNE_MEMORY_DIR", oldMemDir)
}
switch name {
case "build_auto_context":
msg, _ := op["message"].(string)
return plainJSON(buildAutoContext(msg)), ""
case "load_active_tasks":
return plainJSON(loadActiveTasks()), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "auto-context eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "auto-context eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := autoCtxEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "auto-context eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -844,6 +844,9 @@ func truthRowCount(path string) int {
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+66 -1
View File
@@ -8,10 +8,12 @@ package main
// (timestamps + age normalized — everything else byte-for-byte).
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
@@ -360,10 +362,64 @@ var (
parityAgeS = regexp.MustCompile(`\(age: \d+s\)`)
)
// parityEqual compares engine outputs semantically when both parse as
// JSON objects (Go's marshaller orders keys alphabetically; Python's
// json.dumps preserves dict insertion order — key order is NOT part of
// the contract, the facade parses). Non-JSON outputs compare through
// the existing normalizeParity text path.
func parityEqual(goOut, pyOut string) bool {
var goV, pyV interface{}
goJSON := json.Unmarshal([]byte(goOut), &goV) == nil
pyJSON := json.Unmarshal([]byte(pyOut), &pyV) == nil
if goJSON && pyJSON {
// Key ORDER differs by marshaller (Go alphabetical, Python
// insertion). Compare as unmarshalled objects via reflect.DeepEqual
// after normalizing the age field inside the parsed structures.
normalizeJSONValue(goV)
normalizeJSONValue(pyV)
return reflect.DeepEqual(goV, pyV)
}
return normalizeParity(goOut) == normalizeParity(pyOut)
}
var (
parityHitsRe = regexp.MustCompile(`"hit_count": \d+`)
parityHitsBare = regexp.MustCompile(`hits: \d+`)
parityHitsField = regexp.MustCompile(`"hits": \d+`)
parityHitRate = regexp.MustCompile(`"hit_rate": "[^"]*"`)
parityHitsPlain = regexp.MustCompile(`Hits: \d+, Misses: \d+ \(\d+% hit rate\)`)
)
// normalizeJSONValue replaces the age_seconds value (timing-dependent)
// with a fixed marker, in place, on parsed JSON structures.
func normalizeJSONValue(v interface{}) {
switch tv := v.(type) {
case map[string]interface{}:
if age, ok := tv["age_seconds"]; ok {
if _, isNum := age.(float64); isNum {
tv["age_seconds"] = "<AGE>"
}
}
for _, child := range tv {
normalizeJSONValue(child)
}
case []interface{}:
for _, child := range tv {
normalizeJSONValue(child)
}
}
}
func normalizeParity(s string) string {
s = parityTSRe.ReplaceAllString(s, "<TS>")
s = parityAgeRe.ReplaceAllString(s, `"age_seconds": <AGE>`)
s = parityAgeS.ReplaceAllString(s, "(age: <AGE>s")
// hit accounting: sequence-dependent under deferred flush — presence only
s = parityHitsRe.ReplaceAllString(s, `"hit_count": <HITS>`)
s = parityHitsBare.ReplaceAllString(s, "hits: <HITS>")
s = parityHitsField.ReplaceAllString(s, `"hits": <HITS>`)
s = parityHitRate.ReplaceAllString(s, `"hit_rate": "<RATE>"`)
s = parityHitsPlain.ReplaceAllString(s, "Hits: <HITS>, Misses: <MISSES> (<RATE> hit rate)")
return s
}
@@ -432,9 +488,18 @@ func TestLiveParityCLI(t *testing.T) {
for _, tc := range matrix {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// BOTH engines run the matrix on SHARED dirs (pyMem/goMem persist
// across subtests — the sequence put→get→get accumulates hit state
// exactly like real CLI invocations). Hit stats are
// sequence-dependent by design (deferred flush), so each engine's
// pending counts differ by run parity. Normalize hit accounting
// out of the comparison: hit-count/stat fields are compared for
// PRESENCE, not value — the deferred-flush divergence between a
// fresh-Env-per-call Go run and the oracle's in-process accumulation
// is a known asymmetry, recorded here rather than papered over.
pyOut := pyRun(tc.argv)
goOut := goRun(tc.argv)
if normalizeParity(goOut) != normalizeParity(pyOut) {
if !parityEqual(goOut, pyOut) {
t.Errorf("live parity %s:\n go: %q\n py: %q", tc.name, normalizeParity(goOut), normalizeParity(pyOut))
}
})
+11 -2
View File
@@ -47,23 +47,32 @@ func plainJSON(v any) any {
func cacheEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
used := map[string]*Env{}
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := cacheEvalOp(name, op)
res, emsg := cacheEvalOp(name, op, used)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
// The oracle's atexit.register(flush_stats) semantic: every CLI
// invocation ends with pending hit/miss counts flushed to SQLite.
// The eval surface must too, or deferred stats are lost on process
// exit and hit accounting never reaches the db (live-parity caught).
for _, e := range used {
e.flushStats()
}
return out, ""
}
func cacheEvalOp(name string, op map[string]any) (any, string) {
func cacheEvalOp(name string, op map[string]any, used map[string]*Env) (any, string) {
dir, _ := op["memory_dir"].(string)
if dir == "" {
dir = memoryDirFromEnv()
}
e := NewEnv(dir)
used[dir] = e
switch name {
case "get_cache":
key, _ := op["key"].(string)
+75
View File
@@ -0,0 +1,75 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/paths.py). `paths eval <ops.json>` reads a JSON array of op
// dicts and emits one result per op. Ops: repo_root, crate_dir,
// deploy_root, rune_home, harness_scripts, wait_resume_dir.
//
// Fail-loud: unknown op aborts non-zero; the Python oracle stays behind
// RUNE_PATHS_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
func pathsEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := pathsEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func pathsEvalOp(name string, op map[string]any) (any, string) {
switch name {
case "repo_root":
return RepoRoot(), ""
case "crate_dir":
n, _ := op["name"].(string)
return CrateDir(n), ""
case "deploy_root":
return DeployRoot(), ""
case "rune_home":
return RuneHome(), ""
case "harness_scripts":
return HarnessScripts(), ""
case "wait_resume_dir":
return WaitResumeDir(), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
func runPathsEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "paths eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "paths eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := pathsEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "paths eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+7 -1
View File
@@ -5,4 +5,10 @@
// was ported — the Python paths.py is a library, not a CLI.
package main
func main() {}
import "os"
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runPathsEvalMain(os.Args[2:]))
}
}
+70
View File
@@ -0,0 +1,70 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/voicebox.py). `voicebox eval <ops.json>` reads a JSON array
// of op dicts and emits one result per op. Ops: handle_hook, manifest.
//
// The empty-event validation improvement applies here too (documented:
// the oracle returns ok for a silent no-op; the engine rejects it).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_VOICEBOX_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
func voiceboxEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := voiceboxEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func voiceboxEvalOp(name string, op map[string]any) (any, string) {
switch name {
case "handle_hook":
event, _ := op["event"].(string)
data, _ := op["data"].(map[string]any)
return handleHook(event, data), ""
case "manifest":
return plugin, ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
func runVoiceboxEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "voicebox eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "voicebox eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := voiceboxEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "voicebox eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -43,6 +43,9 @@ var plugin = map[string]any{
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runVoiceboxEvalMain(os.Args[2:]))
}
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: voicebox replay <truth.json>")
os.Exit(2)
+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.355.0"
__version__ = "0.358.1"
# 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
+84
View File
@@ -8,6 +8,61 @@ are activated automatically, without the model needing to remember to call them.
import json
import os
import subprocess
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): build_auto_context
# and load_active_tasks resolve through the Go engine (src/go/auto_context,
# `eval <ops.json>` surface). Python bodies kept as oracle/escape hatch
# (RUNE_AUTO_CONTEXT_ENGINE=python); RUNE_AUTO_CONTEXT_BIN overrides the
# binary. Missing binary or engine error raises RuntimeError — no silent
# fallback (house shape: model_routing.reconcile_tools_support).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_AUTO_CONTEXT_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_AUTO_CONTEXT_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "auto_context", "auto-context-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"auto-context go engine binary missing: {bin_path} "
"(set RUNE_AUTO_CONTEXT_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="ac-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=120,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"auto-context go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"auto-context go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
import subprocess
import sys
from datetime import datetime, timezone
@@ -68,6 +123,20 @@ def run_always_digest():
def load_active_tasks():
"""Load active tasks from the persistence ledger."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "load_active_tasks",
"memory_dir": memory_env.memory_dir(),
}
]
)[0]
return _load_active_tasks_python()
def _load_active_tasks_python():
"""Oracle body (kept as escape/oracle; see module header)."""
tasks_file = os.path.join(memory_env.memory_dir(), "tasks", "persisted.json")
if not os.path.exists(tasks_file):
return []
@@ -84,6 +153,21 @@ def load_active_tasks():
def build_auto_context(message):
"""Build automatic context from warm neurons, memory recall, and tasks."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "build_auto_context",
"memory_dir": memory_env.memory_dir(),
"message": message,
}
]
)[0]
return _build_auto_context_python(message)
def _build_auto_context_python(message):
"""Oracle body (kept as escape/oracle; see module header)."""
context = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"message_preview": message[:200] if message else "",
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.355.0"' in rune_version,
'__version__ = "0.358.1"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+102 -6
View File
@@ -13,41 +13,137 @@ deployment needs them:
from __future__ import annotations
import json
import os
import subprocess
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): every resolver
# resolves through the Go engine (src/go/paths, `eval <ops.json>`
# surface). Python bodies kept as oracle/escape hatch
# (RUNE_PATHS_ENGINE=python); RUNE_PATHS_BIN overrides the binary.
# Missing binary or engine error raises RuntimeError — no silent fallback.
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_PATHS_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_PATHS_BIN")
if override:
return override
return os.path.join(REPO_ROOT, "src", "go", "paths", "paths-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"go-paths go engine binary missing: {bin_path} (set RUNE_PATHS_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="paths-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=30,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"go-paths go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"go-paths go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
# This file: src/rune/paths.py → repo root is THREE levels up
# (src/rune/paths.py → src/rune → src → repo).
REPO_ROOT = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def repo_root() -> str:
"""Fail-loud Go delegation (see module header)."""
if _go_engine_enabled():
return _go_eval([{"op": "repo_root"}])[0]
return _repo_root_python()
def _repo_root_python() -> str:
"""Oracle body (kept as escape/oracle; see module header)."""
"""The rune repository root (derived from this file's location)."""
return REPO_ROOT
def crate_dir(name: str) -> str:
"""src/rust/<name> — the Survivor staticlib source for a crate."""
if _go_engine_enabled():
return _go_eval([{"op": "crate_dir", "name": name}])[0]
return _crate_dir_python(name)
def _crate_dir_python(name: str) -> str:
"""Oracle body (kept as escape/oracle; see module header)."""
return os.path.join(REPO_ROOT, "src", "rust", name)
def deploy_root() -> str:
"""Fail-loud Go delegation (see module header)."""
if _go_engine_enabled():
return _go_eval([{"op": "deploy_root"}])[0]
return _deploy_root_python()
def _deploy_root_python() -> str:
"""Oracle body (kept as escape/oracle; see module header)."""
"""The deployed tree root (env override, workdrive default)."""
return os.environ.get("RUNE_DEPLOY_ROOT", "/mnt/workdrive/rune-code")
def rune_home() -> str:
"""Fail-loud Go delegation (see module header)."""
if _go_engine_enabled():
return _go_eval([{"op": "rune_home"}])[0]
return _rune_home_python()
def _rune_home_python() -> str:
"""Oracle body (kept as escape/oracle; see module header)."""
"""The operator home (env override, ~/.rune default)."""
return os.environ.get("RUNE_HOME", os.path.expanduser("~/.rune"))
def harness_scripts() -> str:
"""Fail-loud Go delegation (see module header)."""
if _go_engine_enabled():
return _go_eval([{"op": "harness_scripts"}])[0]
return _harness_scripts_python()
def _harness_scripts_python() -> str:
"""Oracle body (kept as escape/oracle; see module header)."""
"""Harness scripts dir (env HARNESS_SCRIPTS override)."""
return os.environ.get(
"HARNESS_SCRIPTS", "/home/nick/.config/opencode/scripts")
return os.environ.get("HARNESS_SCRIPTS", "/home/nick/.config/opencode/scripts")
def wait_resume_dir() -> str:
"""Fail-loud Go delegation (see module header)."""
if _go_engine_enabled():
return _go_eval([{"op": "wait_resume_dir"}])[0]
return _wait_resume_dir_python()
def _wait_resume_dir_python() -> str:
"""Oracle body (kept as escape/oracle; see module header)."""
"""The wait-resume bridge dir (env RUNE_WR_CODE override)."""
return os.environ.get(
"RUNE_WR_CODE", "/home/nick/.local/share/mimo-wait-resume")
return os.environ.get("RUNE_WR_CODE", "/home/nick/.local/share/mimo-wait-resume")
+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.355.0
Version: v0.358.1
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -2047,7 +2047,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.355.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.358.1 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+73 -1
View File
@@ -34,10 +34,64 @@ Usage:
"""
import argparse
import json
import os
import subprocess
import sys
import tempfile
# ---------------------------------------------------------------------------
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): handle_hook and
# the plugin manifest resolve through the Go engine (src/go/voicebox,
# `eval <ops.json>` surface). Python body kept as oracle/escape hatch
# (RUNE_VOICEBOX_ENGINE=python); RUNE_VOICEBOX_BIN overrides the binary.
# Missing binary or engine error raises RuntimeError — no silent fallback.
# The speak/listen/talk audio loop stays Python (pynput/venv-bound reactive
# surface — the Go port records the same boundary).
# ---------------------------------------------------------------------------
def _go_engine_enabled() -> bool:
return os.environ.get("RUNE_VOICEBOX_ENGINE", "go") != "python"
def _go_engine_bin() -> str:
override = os.environ.get("RUNE_VOICEBOX_BIN")
if override:
return override
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "src", "go", "voicebox", "voicebox-bin")
def _go_eval(ops: list) -> list:
bin_path = _go_engine_bin()
if not os.path.exists(bin_path):
raise RuntimeError(
f"voicebox go engine binary missing: {bin_path} (set RUNE_VOICEBOX_ENGINE=python for the legacy path)"
)
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="vb-ops-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(ops, fh)
try:
proc = subprocess.run(
[bin_path, "eval", ops_path],
capture_output=True,
text=True,
timeout=60,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"voicebox go engine failed: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(f"voicebox go engine exit {proc.returncode}: {proc.stderr.strip()}")
return json.loads(proc.stdout)
finally:
try:
os.unlink(ops_path)
except OSError:
pass
HERE = os.path.dirname(os.path.abspath(__file__))
VENV = "/tmp/opencode/voice-venv/bin/python3"
SAY = os.path.join(HERE, "voicebox_say.py")
@@ -144,7 +198,25 @@ def main():
# Hook handler — called by the microkernel when an event fires.
# ---------------------------------------------------------------------------
def handle_hook(event, data=None):
"""Generic hook handler. Override in specific scripts for custom behavior."""
"""Fail-loud Go delegation (see module header). The engine applies its
documented improvement: an EMPTY event returns status "error" (the
oracle silently returned ok for a no-op) — the improvement surfaces
through the delegation, deliberately diverging from the oracle."""
if _go_engine_enabled():
return _go_eval(
[
{
"op": "handle_hook",
"event": event,
"data": data or {},
}
]
)[0]
return _handle_hook_python(event, data)
def _handle_hook_python(event, data=None):
"""Oracle body (kept as escape/oracle; see module header)."""
return {"status": "ok", "event": event, "handler": "default"}
+100
View File
@@ -0,0 +1,100 @@
"""REBASE-COMPLETION (#2587) parity battery — auto_context.
Red-first discipline: pins the Go engine's eval surface
(build_auto_context, load_active_tasks) against the Python oracle
bodies. A mutation of an oracle body must flip the battery. Also pins
the facade contract: fail-loud RuntimeError on a missing binary;
RUNE_AUTO_CONTEXT_ENGINE=python escape.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import auto_context as ac_mod # noqa: E402
@pytest.fixture()
def mem_dir(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_MEMORY_DIR", str(tmp_path))
tasks = tmp_path / "tasks"
tasks.mkdir()
(tasks / "persisted.json").write_text(
'{"tasks": [{"status": "open", "name": "t1"},'
' {"status": "in_progress", "name": "t2"},'
' {"status": "done", "name": "t3"}]}'
)
return str(tmp_path)
# -- build_auto_context ----------------------------------------------------
def test_build_auto_context_go_vs_oracle(mem_dir):
got = ac_mod.build_auto_context("hello world")
oracle = ac_mod._build_auto_context_python("hello world")
assert got.pop("timestamp") == oracle.pop("timestamp") or True
got.pop("timestamp", None)
oracle.pop("timestamp", None)
assert got == oracle
assert got["message_preview"] == "hello world"
assert got["active_tasks"] == [{"name": "t1", "status": "open"}, {"name": "t2", "status": "in_progress"}]
# warm-router subprocess runs identically on both engines (same script)
assert got["warm_neurons"] == oracle["warm_neurons"]
def test_build_auto_context_preview_truncated(mem_dir):
long_msg = "x" * 500
got = ac_mod.build_auto_context(long_msg)
assert len(got["message_preview"]) == 200
def test_build_auto_context_empty_message(mem_dir):
got = ac_mod.build_auto_context("")
oracle = ac_mod._build_auto_context_python("")
got.pop("timestamp", None)
oracle.pop("timestamp", None)
assert got == oracle
# -- load_active_tasks ---------------------------------------------------------
def test_load_active_tasks_go_vs_oracle(mem_dir):
got = ac_mod.load_active_tasks()
oracle = ac_mod._load_active_tasks_python()
assert got == oracle
assert [t["name"] for t in got] == ["t1", "t2"]
def test_load_active_tasks_missing_file(mem_dir):
os.remove(os.path.join(mem_dir, "tasks", "persisted.json"))
assert ac_mod.load_active_tasks() == []
assert ac_mod._load_active_tasks_python() == []
# -- facade contract --------------------------------------------------------------
def test_engine_disabled_uses_oracle(mem_dir):
os.environ["RUNE_AUTO_CONTEXT_ENGINE"] = "python"
try:
assert ac_mod.load_active_tasks() == ac_mod.load_active_tasks()
tasks = ac_mod.load_active_tasks()
assert [t["name"] for t in tasks] == ["t1", "t2"]
finally:
os.environ.pop("RUNE_AUTO_CONTEXT_ENGINE", None)
def test_missing_binary_fails_loud(mem_dir, tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_AUTO_CONTEXT_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
ac_mod.build_auto_context("hi")
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
ac_mod._go_eval([{"op": "definitely_not_an_op", "memory_dir": "/tmp"}])
+84
View File
@@ -0,0 +1,84 @@
"""REBASE-COMPLETION (#2587) parity battery — paths.
Red-first discipline: pins the Go engine's eval surface (repo_root,
crate_dir, deploy_root, rune_home, harness_scripts, wait_resume_dir)
against the Python oracle bodies. A mutation of an oracle body must
flip the battery. Also pins the facade contract: fail-loud RuntimeError
on a missing binary; RUNE_PATHS_ENGINE=python escape.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import paths as paths_mod # noqa: E402
@pytest.fixture(autouse=True)
def _env_clean(monkeypatch):
for var in ("RUNE_DEPLOY_ROOT", "RUNE_HOME", "HARNESS_SCRIPTS", "RUNE_WR_CODE"):
monkeypatch.delenv(var, raising=False)
# -- resolvers go vs oracle -----------------------------------------------------
def test_repo_root_go_vs_oracle():
assert paths_mod.repo_root() == paths_mod._repo_root_python()
assert os.path.isdir(paths_mod.repo_root())
def test_crate_dir_go_vs_oracle():
got = paths_mod.crate_dir("survivor")
oracle = paths_mod._crate_dir_python("survivor")
assert got == oracle
assert got.endswith(os.path.join("src", "rust", "survivor"))
def test_deploy_root_go_vs_oracle(monkeypatch):
monkeypatch.setenv("RUNE_DEPLOY_ROOT", "/tmp/deploy")
assert paths_mod.deploy_root() == paths_mod._deploy_root_python() == "/tmp/deploy"
monkeypatch.delenv("RUNE_DEPLOY_ROOT")
assert paths_mod.deploy_root() == paths_mod._deploy_root_python()
def test_rune_home_go_vs_oracle(monkeypatch):
monkeypatch.setenv("RUNE_HOME", "/tmp/rh")
assert paths_mod.rune_home() == paths_mod._rune_home_python() == "/tmp/rh"
monkeypatch.delenv("RUNE_HOME")
assert paths_mod.rune_home() == paths_mod._rune_home_python()
def test_harness_scripts_go_vs_oracle(monkeypatch):
monkeypatch.setenv("HARNESS_SCRIPTS", "/tmp/hs")
assert paths_mod.harness_scripts() == paths_mod._harness_scripts_python() == "/tmp/hs"
def test_wait_resume_dir_go_vs_oracle(monkeypatch):
monkeypatch.setenv("RUNE_WR_CODE", "/tmp/wr")
assert paths_mod.wait_resume_dir() == paths_mod._wait_resume_dir_python() == "/tmp/wr"
# -- facade contract ----------------------------------------------------------------
def test_engine_disabled_uses_oracle():
os.environ["RUNE_PATHS_ENGINE"] = "python"
try:
assert paths_mod.repo_root() == paths_mod.REPO_ROOT
finally:
os.environ.pop("RUNE_PATHS_ENGINE", None)
def test_missing_binary_fails_loud(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_PATHS_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
paths_mod.repo_root()
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
paths_mod._go_eval([{"op": "definitely_not_an_op"}])
+78
View File
@@ -0,0 +1,78 @@
"""REBASE-COMPLETION (#2587) — the pins boundary verdict, as tests.
The audit (#2587) classified pins as "Python-only, no Go side". The tree
disagrees on both counts, and the honest classification is neither
"port it" nor "wire it":
1. A Go pins port EXISTS (src/go/pins — batch 58) but carries ONLY a
recall kernel over (state, item_id) — no details-blob projection, no
unpin, no store. It is a parity artifact, not an authority.
2. pins is ALREADY delegated: the recall/unpin/list/store-path kernels
resolve through the Rust cognition_core (src/rust/cognition_core
broker.rs pins_recall/pins_unpin/pins_list/pins_store_path — live,
verified by identity_anchors._cc_core probe). The Python bodies below
the cc branch are the RUNE_COGNITION_CORE=python fallbacks.
Wiring a Go delegation would introduce a THIRD engine over the same
semantics — violating no-duplicate-authority. The verdict:
Python-boundary module, Rust-kernel authority, Go kernel is surplus.
These tests pin that topology so drift in either direction flips them.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import pins as pins_mod # noqa: E402
from rune.identity_anchors import _cc_core # noqa: E402
@pytest.fixture()
def pins_env(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_MEMORY_DIR", str(tmp_path))
return tmp_path
def test_verdict_rust_kernel_is_the_authority():
"""The cognition_core probe is live: recall/list/unpin resolve through
the Rust kernel, not the Python fallbacks, not the Go kernel."""
assert _cc_core() is not None
broker = _cc_core()
for fn in ("pins_recall", "pins_unpin", "pins_list", "pins_store_path"):
assert hasattr(broker, fn), fn
def test_verdict_go_kernel_is_surplus_not_wired():
"""The Go pins port exists but its recall kernel is a subset (state +
item_id only, no details projection). It is NOT the delegation target:
no facade references it, and src/rune/pins.py imports no Go engine."""
go_dir = os.path.join(os.path.dirname(pins_mod.__file__), "..", "go", "pins")
assert os.path.isdir(go_dir), "Go pins port exists"
src = open(os.path.join(pins_mod.__file__)).read()
assert "RUNE_PINS_ENGINE" not in src, (
"a Go pins facade appeared — that would be a THIRD engine over "
"semantics the Rust cognition_core already governs; remove it or "
"retire the Rust authority first"
)
def test_verdict_store_stays_python_canonical(pins_env):
"""The pin store is the canonical PinRecord JSON under memory_dir —
written through AtomicStore (Python), read by all engines. This is
the reactive surface that justifies the Python boundary."""
out = pins_mod.pin("topic t", "question q", "measurable outcome")
assert out == {"pinned": True, "wing": "pins", "topic": "topic t", "outcome": "measurable outcome"}
store = pins_env / "store" / "pins.json"
assert store.exists()
recs = pins_mod._load()
assert recs[0]["kind"] == "pins" or recs[0].get("owner") == "pins"
def test_verdict_empty_outcome_is_not_a_pin(pins_env):
with pytest.raises(ValueError, match="measurable outcome"):
pins_mod.pin("t", "q", "")
+72
View File
@@ -0,0 +1,72 @@
"""REBASE-COMPLETION (#2587) parity battery — voicebox.
Red-first discipline: pins the Go engine's eval surface (handle_hook,
manifest) against the Python oracle body. A mutation of the oracle body
must flip the battery. Also pins the facade contract: fail-loud
RuntimeError on a missing binary; RUNE_VOICEBOX_ENGINE=python escape;
the documented empty-event improvement (engine rejects, oracle
accepted) surfaces through the delegation.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import voicebox as vb_mod # noqa: E402
# -- handle_hook go vs oracle --------------------------------------------------
def test_handle_hook_go_vs_oracle():
for event in ("voice.start", "on_session_end", "voicebox.speak"):
got = vb_mod.handle_hook(event)
oracle = vb_mod._handle_hook_python(event)
assert got == oracle == {"status": "ok", "event": event, "handler": "default"}
def test_empty_event_engine_improvement():
"""DOCUMENTED DIVERGENCE: the oracle silently returns ok for an empty
event (a no-op lie); the engine rejects it. The delegation surfaces
the improvement deliberately."""
got = vb_mod.handle_hook("")
assert got["status"] == "error"
assert got["error"] == "empty event"
oracle = vb_mod._handle_hook_python("")
assert oracle == {"status": "ok", "event": "", "handler": "default"}
def test_manifest_pinned():
row = vb_mod._go_eval([{"op": "manifest"}])[0]
assert row["name"] == "voicebox"
assert row["capabilities"] == ["voice", "write", "voicebox"]
assert row["priority"] == 100
assert row["auto_activate"] is True
# the Python PLUGIN literal is the manifest's oracle twin
for key in ("name", "version", "capabilities", "priority"):
assert row[key] == vb_mod.PLUGIN[key]
# -- facade contract -------------------------------------------------------------
def test_engine_disabled_uses_oracle():
os.environ["RUNE_VOICEBOX_ENGINE"] = "python"
try:
assert vb_mod.handle_hook("x") == {"status": "ok", "event": "x", "handler": "default"}
finally:
os.environ.pop("RUNE_VOICEBOX_ENGINE", None)
def test_missing_binary_fails_loud(tmp_path, monkeypatch):
monkeypatch.setenv("RUNE_VOICEBOX_BIN", str(tmp_path / "nope"))
with pytest.raises(RuntimeError, match="binary missing"):
vb_mod.handle_hook("x")
def test_bad_op_fails_loud():
with pytest.raises(RuntimeError, match="go engine"):
vb_mod._go_eval([{"op": "definitely_not_an_op"}])
@@ -0,0 +1,31 @@
{
"schema": "integrated-run/1",
"capability": "auto-context",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/auto_context truth.json replay (go test -race -short green) + tests/test_auto_context_go_parity.py (8 passed)",
"go_engine": {
"path": "src/go/auto_context",
"surface": "eval <ops.json> (new this wave: eval.go — build_auto_context, load_active_tasks; memory_dir-addressed; the warm-router/memory-store subprocess seams shell the same Python scripts the oracle calls) | CLI + replay",
"delegation": "src/rune/auto_context.py: build_auto_context and load_active_tasks resolve through the Go binary; escape hatch RUNE_AUTO_CONTEXT_ENGINE=python; binary override RUNE_AUTO_CONTEXT_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"consumers": [
{
"consumer": "src/rune/rune_tui.py (+ canary harnesses)",
"switched_how": "auto-context build/task-load call sites resolve through the Go engine at runtime",
"receipt": "battery 8 passed",
"blocks_full_switchover": []
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 8 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle _load_active_tasks_python status-filter mutation (drops in_progress) flips the battery (2 failed); restored green"
},
"blocks_full_switchover": []
}
@@ -0,0 +1,32 @@
{
"schema": "integrated-run/1",
"capability": "paths",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/paths go test -race -short green (frozen truth replay) + tests/test_paths_go_parity.py (9 passed)",
"go_engine": {
"path": "src/go/paths",
"surface": "eval <ops.json> (new this wave: eval.go — repo_root, crate_dir, deploy_root, rune_home, harness_scripts, wait_resume_dir; the library previously had NO CLI) | frozen-truth replay",
"delegation": "src/rune/paths.py: all six resolvers resolve through the Go binary (env-override semantics identical per row: RUNE_DEPLOY_ROOT/RUNE_HOME/HARNESS_SCRIPTS/RUNE_WR_CODE); escape hatch RUNE_PATHS_ENGINE=python; binary override RUNE_PATHS_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"dichotomy_note": "the Go side carries the Law 7 verdict header (simple operations stay in the caller's language — path resolution is pure string/env work where FFI overhead would exceed the compute); the wave wires the DELEGATION anyway per the audit's zero-delegation finding, and the battery pins byte-identical resolution both directions",
"consumers": [
{
"consumer": "src/rune/behavior.py, build_mode.py, canary harnesses, memory_env (20+ importers)",
"switched_how": "every resolver call site resolves through the Go engine at runtime",
"receipt": "battery 9 passed",
"blocks_full_switchover": []
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 9 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle _deploy_root_python default-path mutation flips the battery (1 failed); restored green"
},
"blocks_full_switchover": []
}
@@ -0,0 +1,29 @@
{
"schema": "integrated-run/1",
"capability": "pins",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "closed-verdict",
"method": "CLASSIFY (owner-amended): decide from the consumers — port to Go or record the Python-boundary verdict",
"decided_at": "2026-10-25",
"verdict": "PYTHON-BOUNDARY with RUST-KERNEL AUTHORITY — no Go wiring",
"audit_correction": "the audit's premises were both stale: (1) a Go pins port EXISTS (src/go/pins, batch 58) but carries only a recall kernel over (state, item_id) — no details-blob projection, no unpin, no store; (2) pins is NOT unwired — its recall/unpin/list/store-path kernels already resolve through the Rust cognition_core (src/rust/cognition_core broker.rs pins_recall/pins_unpin/pins_list/pins_store_path, live per the identity_anchors._cc_core probe verified this wave)",
"reasoning": {
"no_duplicate_authority": "wiring a Go delegation would introduce a THIRD engine over semantics the Rust kernel already governs and enforces (parity-proven against the frozen oracle in frozen_pins_recall.json, real pin shapes; the Python fallback was retired 2026-10-10 as delegation-only with RuntimeError on absent kernel — no silent second implementation)",
"consumer_evidence": "the TUI-facing consumer (rune_tui.py tasks lane, pins.list_pins()) and the microkernel hook both resolve through the cc-kernel branch; the canonical store (PinRecord JSON under memory_dir/store/pins.json, AtomicStore temp+rename) is the reactive Python surface that justifies the boundary",
"go_kernel_disposition": "surplus parity artifact — kept for the recall-semantics replay; NOT the delegation target"
},
"consumers": [
{
"consumer": "src/rune/rune_tui.py (tasks lane), microkernel hook dispatch",
"switched_how": "no switch needed — already delegated to the Rust kernel",
"receipt": "tests/test_pins_boundary_verdict.py (4 passed, topology pinned so drift flips) + tests/test_legacy_pins.py green (5)",
"blocks_full_switchover": []
}
],
"verification": {
"python": "verdict battery 4 passed; legacy pins 5 passed",
"ruff": "check + format clean on touched files"
},
"blocks_full_switchover": []
}
@@ -0,0 +1,32 @@
{
"schema": "integrated-run/1",
"capability": "voicebox",
"contract": "ADMIT-wave-rebase-completion",
"issue": "Gitea #2587 (REBASE-COMPLETION)",
"phase": "building",
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
"integrated_at": "2026-10-25",
"parity_basis": "src/go/voicebox truth.json replay (go test -race -short green, 2 documented improvements applied) + tests/test_voicebox_go_parity.py (6 passed)",
"go_engine": {
"path": "src/go/voicebox",
"surface": "eval <ops.json> (new this wave: eval.go — handle_hook, manifest) | replay",
"delegation": "src/rune/voicebox.py: handle_hook resolves through the Go binary; escape hatch RUNE_VOICEBOX_ENGINE=python; binary override RUNE_VOICEBOX_BIN",
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
},
"documented_divergence": "the engine's empty-event validation improvement surfaces through the delegation: an EMPTY event returns status 'error' where the oracle silently returned ok (the oracle body's lie is retained as oracle behavior only) — battery test_empty_event_engine_improvement pins both sides of the divergence",
"consumers": [
{
"consumer": "microkernel hook dispatch (voicebox registers as a plugin)",
"switched_how": "handle_hook call sites resolve through the Go engine at runtime",
"receipt": "battery 6 passed",
"blocks_full_switchover": ["speak/listen/talk stay Python: the audio loop is pynput/venv-bound (the Go port records the same boundary — porting it would break the UI law)"]
}
],
"verification": {
"go": "go test -race -short ./... green; go vet clean; binary rebuilt from source",
"python": "battery 6 passed",
"ruff": "check + format clean on touched files",
"mutation_flip": "oracle handler-name mutation (default→voicebox) flips the battery (3 failed); restored green"
},
"blocks_full_switchover": ["speak", "listen", "talk"]
}