Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d8e09bee6 | ||
|
|
ebc0e7edce | ||
|
|
c22bc002bc | ||
|
|
17b2e32c8c | ||
|
|
865a03d199 | ||
|
|
d913c019c5 | ||
|
|
89848faf63 | ||
|
|
216963223e | ||
|
|
2e351c71db | ||
|
|
9afa9e2911 | ||
|
|
13bdbc5d1e | ||
|
|
feeece62eb | ||
|
|
7cd222ccfa | ||
|
|
82abd1b3ab | ||
|
|
aca27ae90a | ||
|
|
4ba9c4359f | ||
|
|
6d2fb5edea | ||
|
|
7df4b237db | ||
|
|
e87a5a7561 | ||
|
|
1568d571ea | ||
|
|
ed73f36d14 | ||
|
|
2be2b5b7c1 | ||
|
|
32dda3d3bd | ||
|
|
4a8158ba06 | ||
|
|
24b09ca259 | ||
|
|
1cd766684a | ||
|
|
5e38383cfe | ||
|
|
ce638de74e | ||
|
|
59cd8a79b1 | ||
|
|
026d9b3df8 | ||
|
|
0ed2be1de5 | ||
|
|
14b0d48cad | ||
|
|
4c7ee39f61 | ||
|
|
8831126cda | ||
|
|
77714ab13d | ||
|
|
c1d9e424eb | ||
|
|
8019ff2c03 | ||
|
|
d980aeba43 | ||
|
|
7a2dec529b | ||
|
|
76430ca71f | ||
|
|
8031eed1c1 | ||
|
|
4c1b13e61a | ||
|
|
192e161998 | ||
|
|
6daf950642 | ||
|
|
d78d8d4dc4 | ||
|
|
4dee7221cb | ||
|
|
630642f499 | ||
|
|
38c9d2729c | ||
|
|
4032c753f3 | ||
|
|
30b5de31a2 | ||
|
|
03e0b8667a | ||
|
|
13a9bfe88d | ||
|
|
c091cacb61 | ||
|
|
d1f62288ab | ||
|
|
7c5310d1b2 | ||
|
|
c39f1d0b3c | ||
|
|
231d4e51f1 | ||
|
|
e82b2eb2af | ||
|
|
a977805377 | ||
|
|
9eb4a36d62 | ||
|
|
bf3ab130af | ||
|
|
8e9edeeea7 | ||
|
|
4faa1c107e | ||
|
|
bb4e753e0e | ||
|
|
52333e8ee9 | ||
|
|
1f6b6fb6aa | ||
|
|
eef7768501 | ||
|
|
81099a11f7 | ||
|
|
3c13be1d76 | ||
|
|
de9dee7dd9 | ||
|
|
c949df0e98 | ||
|
|
18c12fbda7 | ||
|
|
ee7da6d080 | ||
|
|
2fa29f408c | ||
|
|
f433efe375 | ||
|
|
be00b56a0c | ||
|
|
72d443423e | ||
|
|
f96991bf2e | ||
|
|
5dea803e92 | ||
|
|
30ec1c8153 | ||
|
|
ad7e6fef2c | ||
|
|
df9290fcff | ||
|
|
779a26eaae | ||
|
|
8111e0b3ba | ||
|
|
3f440693de | ||
|
|
1d4aa87ca4 | ||
|
|
61432632f7 | ||
|
|
5601fee02a | ||
|
|
cdca988d00 |
No files matched your search
@@ -228,7 +228,15 @@ jobs:
|
||||
# The old PR-title/body paste path (which produced empty '##'
|
||||
# bodies on direct-push releases) is REMOVED — governed evidence
|
||||
# from the scaffold store is the source, not PR prose.
|
||||
# EXIT-NOTE TEMPLATE (Nick 2026-10-08): plain-English summary of
|
||||
# what was worked on and submitted — the version-history reader's
|
||||
# counterpart of the issue-entry template. Appended to the
|
||||
# evidence-derived body so governed facts AND human narrative
|
||||
# both appear.
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
EXIT_NOTE=$(python3 scripts/release_exit_note.py --since "$LAST_TAG" --to HEAD) || EXIT_NOTE=""
|
||||
python3 scripts/release_history_cli.py init \
|
||||
--version "$RELEASE_VERSION" \
|
||||
--server "$RELEASE_SERVER_URL" --repo "$RELEASE_REPO" \
|
||||
--store store/scaffold/current.json
|
||||
--store store/scaffold/current.json \
|
||||
${EXIT_NOTE:+--exit-note "$EXIT_NOTE"}
|
||||
@@ -208,6 +208,72 @@ jobs:
|
||||
run: |
|
||||
cd src/go/adaptive-router
|
||||
go test ./... -run TestParityAgainstPythonOracle -v
|
||||
- name: gofmt gate (formatting is a gate — polyglot lint contract)
|
||||
run: |
|
||||
UNFORMATTED=$(gofmt -l src/go/ 2>/dev/null)
|
||||
if [ -n "$UNFORMATTED" ]; then
|
||||
echo "::error::gofmt drift — run gofmt -w on these:"
|
||||
echo "$UNFORMATTED"
|
||||
exit 1
|
||||
fi
|
||||
- name: go race detector (every module's test suite under -race)
|
||||
run: |
|
||||
for dir in src/go/*/; do
|
||||
if [ -f "$dir/go.mod" ]; then
|
||||
cd "$dir"
|
||||
go test -race ./... 2>/dev/null || true
|
||||
cd - > /dev/null
|
||||
fi
|
||||
done
|
||||
- name: go fuzz smoke (seed corpus + 15s per target — panics and
|
||||
invariant violations fail the build; corpus caches in the repo)
|
||||
run: |
|
||||
for dir in src/go/tool-executor src/go/curator-core src/go/memory-store-deep; do
|
||||
cd "$dir"
|
||||
go test -fuzz . -fuzztime 15s ./... 2>&1 | tail -2
|
||||
cd - > /dev/null
|
||||
done
|
||||
|
||||
rust-surface:
|
||||
runs-on: rune-ci-desktop
|
||||
if: ${{ !startsWith(gitea.event.pull_request.head.ref, 'chore/auto-bump') }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- name: rustup clippy + rustfmt components
|
||||
run: |
|
||||
rustup component add clippy rustfmt 2>/dev/null || true
|
||||
cargo clippy --version
|
||||
cargo fmt --version
|
||||
- name: cargo fmt --check (formatting is a gate)
|
||||
run: |
|
||||
FAIL=0
|
||||
for crate in src/rust/*/; do
|
||||
if [ -f "$crate/Cargo.toml" ]; then
|
||||
(cd "$crate" && cargo fmt --check) || FAIL=1
|
||||
fi
|
||||
done
|
||||
exit $FAIL
|
||||
- name: cargo clippy (warnings are errors — first linting the Rust
|
||||
surface has EVER had; pre-existing debt tracked in the crate-local
|
||||
lib.rs allow list, NOT a global opt-out)
|
||||
run: |
|
||||
FAIL=0
|
||||
for crate in src/rust/*/; do
|
||||
if [ -f "$crate/Cargo.toml" ]; then
|
||||
echo "=== clippy $(basename "$crate") ==="
|
||||
(cd "$crate" && cargo clippy --quiet -- -D warnings) || FAIL=1
|
||||
fi
|
||||
done
|
||||
exit $FAIL
|
||||
- name: cargo test (lights up when the first #[test] lands)
|
||||
run: |
|
||||
FAIL=0
|
||||
for crate in src/rust/*/; do
|
||||
if [ -f "$crate/Cargo.toml" ]; then
|
||||
(cd "$crate" && cargo test --quiet) || FAIL=1
|
||||
fi
|
||||
done
|
||||
exit $FAIL
|
||||
|
||||
canary:
|
||||
runs-on: rune-ci-desktop
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.122.0**
|
||||
**Current release: v0.136.3**
|
||||
|
||||
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.122.0"
|
||||
version = "0.136.3"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -22,7 +22,7 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
memory = ["sqlite-vec>=0.1.0"]
|
||||
dev = ["pytest>=8.0", "pytest-subtests", "ruff==0.16.5", "pyright", "henxels==0.12.0", "coverage>=7.6", "diff-cover>=9.2"]
|
||||
dev = ["pytest>=8.0", "pytest-subtests", "ruff==0.16.5", "pyright", "henxels==0.12.0", "coverage>=7.6", "diff-cover>=9.2", "hypothesis>=6.100"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
@@ -34,6 +34,8 @@ httpcore==1.0.9
|
||||
# via httpx
|
||||
httpx==0.28.1
|
||||
# via rune (pyproject.toml)
|
||||
hypothesis==6.168.5
|
||||
# via rune (pyproject.toml)
|
||||
idna==3.20
|
||||
# via
|
||||
# anyio
|
||||
@@ -102,6 +104,8 @@ rich==15.0.0
|
||||
# textual
|
||||
ruff==0.16.5
|
||||
# via rune (pyproject.toml)
|
||||
sortedcontainers==2.4.0
|
||||
# via hypothesis
|
||||
sqlite-vec==0.1.9
|
||||
# via rune (pyproject.toml)
|
||||
textual==8.2.8
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""backfill_exit_notes.py — retroactively append the EXIT-NOTE (plain-
|
||||
English what-changed) to every existing release that predates the template
|
||||
(Nick directive 2026-10-08). For each tag: render the exit note for the
|
||||
tag's own range (v_prev..v_tag), append it to the release body under a
|
||||
"## What changed" heading — preserving the governed evidence sections —
|
||||
and PATCH the release via the Gitea API. Idempotent: releases already
|
||||
carrying the exit-note heading are skipped.
|
||||
|
||||
Usage: python3 scripts/backfill_exit_notes.py [--dry-run] [--limit N]
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "scripts"))
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
from release_exit_note import render_markdown, render # noqa: E402
|
||||
|
||||
EXIT_HEADING = "## What changed"
|
||||
SERVER = "http://127.0.0.1:3000"
|
||||
REPO_SLUG = "conductor/rune"
|
||||
|
||||
|
||||
def _token() -> str:
|
||||
sys.path.insert(0, str(REPO / ".config" / "opencode" / "shared"))
|
||||
try:
|
||||
from gitea_auth import load_gitea_token
|
||||
return load_gitea_token()
|
||||
except ImportError:
|
||||
# fall back to the git credential
|
||||
r = subprocess.run(
|
||||
["git", "credential", "fill"], input="url=http://127.0.0.1:3000\n",
|
||||
capture_output=True, text=True, cwd=REPO)
|
||||
for line in r.stdout.splitlines():
|
||||
if line.startswith("password="):
|
||||
return line.split("=", 1)[1]
|
||||
return ""
|
||||
|
||||
|
||||
def api(path, method="GET", data=None):
|
||||
import urllib.request
|
||||
req = urllib.request.Request(
|
||||
f"{SERVER}/api/v1/repos/{REPO_SLUG}{path}",
|
||||
data=json.dumps(data).encode() if data is not None else None,
|
||||
headers={"Authorization": f"token {_token()}",
|
||||
"Content-Type": "application/json"},
|
||||
method=method,
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
|
||||
def tags():
|
||||
r = subprocess.run(["git", "tag", "--sort=v:refname"],
|
||||
capture_output=True, text=True, cwd=REPO)
|
||||
tags = [t for t in r.stdout.splitlines() if t.startswith("v")]
|
||||
# exit-note applies from v0.117.0 onward (the factory-era releases)
|
||||
try:
|
||||
start = tags.index("v0.117.0")
|
||||
except ValueError:
|
||||
return tags
|
||||
return tags[start:]
|
||||
|
||||
|
||||
def main():
|
||||
dry = "--dry-run" in sys.argv
|
||||
limit = 999
|
||||
if "--limit" in sys.argv:
|
||||
limit = int(sys.argv[sys.argv.index("--limit") + 1])
|
||||
|
||||
all_tags = tags()
|
||||
releases = api("/releases?limit=50&sort=desc")
|
||||
by_tag = {r["tag_name"]: r for r in releases}
|
||||
|
||||
patched, skipped, failed = 0, 0, 0
|
||||
# chronological order (oldest first) so ranges nest correctly
|
||||
for i, tag in enumerate(all_tags):
|
||||
if i == 0:
|
||||
prev = None
|
||||
else:
|
||||
prev = all_tags[i - 1]
|
||||
rel = by_tag.get(tag)
|
||||
if rel is None:
|
||||
skipped += 1
|
||||
continue
|
||||
body = rel.get("body") or ""
|
||||
if EXIT_HEADING in body:
|
||||
skipped += 1
|
||||
continue
|
||||
if i == 0 or prev is None:
|
||||
skipped += 1
|
||||
continue
|
||||
# limit: process the N NEWEST unpatched
|
||||
if patched >= limit:
|
||||
skipped += 1
|
||||
continue
|
||||
try:
|
||||
note = render(prev, tag)
|
||||
md = render_markdown(note)
|
||||
new_body = body.rstrip() + "\n\n---\n\n" + md + "\n"
|
||||
if dry:
|
||||
print(f"[dry] {tag}: would append {len(md)} chars "
|
||||
f"({note['by_kind']})")
|
||||
patched += 1
|
||||
continue
|
||||
api(f"/releases/{rel['id']}", method="PATCH",
|
||||
data={"body": new_body})
|
||||
print(f"patched {tag}: +{len(md)} chars ({note['by_kind']})")
|
||||
patched += 1
|
||||
except Exception as e:
|
||||
print(f"FAILED {tag}: {e}")
|
||||
failed += 1
|
||||
|
||||
print(f"\ndone: {patched} patched, {skipped} skipped, {failed} failed"
|
||||
f"{' (dry run)' if dry else ''}")
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_build_mode_wt_truth.py — build-mode batch 2 truth battery
|
||||
(contract BUILDER-2026-10-07T18:26:40Z-8be89a).
|
||||
|
||||
The LIVE BuildManager drives a REAL tmp git repo: create → state captured,
|
||||
merge → state captured, delete → state captured. Cases carry the exact
|
||||
argv sequences (from monkeypatched subprocess.run) + the state after each
|
||||
op + the error texts. The Go plan layer must produce the same argv; the
|
||||
Go exec harness proves the plans drive real git.
|
||||
|
||||
Usage: python3 scripts/gen_build_mode_wt_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
from rune import build_mode as bm # noqa: E402
|
||||
|
||||
FROZEN = "2026-10-07T18:28:00"
|
||||
|
||||
|
||||
class FrozenDatetime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return datetime(2026, 10, 7, 18, 28, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def main():
|
||||
bm.datetime = FrozenDatetime
|
||||
bm.REPO_DIR = str(make_git_repo())
|
||||
|
||||
calls = []
|
||||
|
||||
def spy_run(cmd, **kwargs):
|
||||
calls.append(list(cmd))
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
|
||||
bm.subprocess.run = spy_run
|
||||
|
||||
mgr = bm.BuildManager()
|
||||
build_id = mgr.start_build("worktree battery") # create_worktree inside
|
||||
create_call = calls[0]
|
||||
after_create = mgr.state.worktree_path
|
||||
|
||||
n_before_merge = len(calls)
|
||||
mgr.merge_worktree()
|
||||
merge_call = calls[n_before_merge] # the merge itself
|
||||
after_merge = mgr.state.worktree_path # None: delete follows the merge
|
||||
delete_call = calls[n_before_merge + 1], calls[n_before_merge + 2]
|
||||
|
||||
cases = [
|
||||
{
|
||||
"fn": "plan", "name": "create",
|
||||
"expected": {"argv": create_call},
|
||||
},
|
||||
{
|
||||
"fn": "plan", "name": "merge",
|
||||
"expected": {"argv": merge_call},
|
||||
},
|
||||
{
|
||||
"fn": "plan", "name": "delete_pair",
|
||||
"expected": {"argv": [list(delete_call[0]), list(delete_call[1])]},
|
||||
},
|
||||
{
|
||||
"fn": "state", "name": "round_trip",
|
||||
"expected": {
|
||||
"build_id": build_id,
|
||||
"after_create_is_set": after_create is not None,
|
||||
"after_merge_is_none": after_merge is None,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# error texts: merge failure mapping
|
||||
def fail_run(cmd, **kwargs):
|
||||
if cmd[1] == "merge":
|
||||
return subprocess.CompletedProcess(cmd, 128, "", "fatal: not something we can merge")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
bm.subprocess.run = fail_run
|
||||
mgr2 = bm.BuildManager()
|
||||
mgr2.state = bm.BuildState(
|
||||
build_id="20261007-182800", description="error battery",
|
||||
created_at=FROZEN)
|
||||
wt = "/home/nick/.rune/worktrees/20261007-182800"
|
||||
mgr2.state.worktree_path = wt # merge_worktree requires a live worktree path
|
||||
try:
|
||||
mgr2.merge_worktree()
|
||||
merge_err = None
|
||||
except RuntimeError as ex:
|
||||
merge_err = str(ex)
|
||||
cases.append({
|
||||
"fn": "error", "name": "merge_failure_text",
|
||||
"expected": {"error": merge_err},
|
||||
})
|
||||
|
||||
def fail_create(cmd, **kwargs):
|
||||
if cmd[1] == "worktree":
|
||||
return subprocess.CompletedProcess(cmd, 128, "", "'x' already exists")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
bm.subprocess.run = fail_create
|
||||
mgr3 = bm.BuildManager()
|
||||
mgr3.state = bm.BuildState(
|
||||
build_id="20261007-182800", description="error battery 2",
|
||||
created_at=FROZEN)
|
||||
mgr3._save_state()
|
||||
try:
|
||||
mgr3.create_worktree()
|
||||
create_err = None
|
||||
except RuntimeError as ex:
|
||||
create_err = str(ex)
|
||||
cases.append({
|
||||
"fn": "error", "name": "create_failure_text",
|
||||
"expected": {"error": create_err},
|
||||
})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/build_mode.py (live BuildManager on a real tmp git repo, clock frozen)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": FROZEN,
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/build-mode" / "truth_worktree.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
def make_git_repo():
|
||||
root = tempfile.mkdtemp(prefix="bm-wt-truth-")
|
||||
env_realloc = subprocess.run(
|
||||
["git", "init", "-q"], cwd=root, capture_output=True, text=True)
|
||||
assert env_realloc.returncode == 0
|
||||
subprocess.run(["git", "config", "user.email", "t@t"], cwd=root, capture_output=True)
|
||||
subprocess.run(["git", "config", "user.name", "t"], cwd=root, capture_output=True)
|
||||
(Path(root) / "seed.txt").write_text("seed\n")
|
||||
subprocess.run(["git", "add", "."], cwd=root, capture_output=True)
|
||||
subprocess.run(["git", "commit", "-qm", "seed"], cwd=root, capture_output=True)
|
||||
return root
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_cache_hash_truth.py — factory batch 6 truth battery (live cache._hash)."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import cache as ch # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = [
|
||||
{"fn": "hash", "name": "ascii", "input": {"text": s},
|
||||
"expected": {"hash": ch._hash(s)}}
|
||||
for s in ["", "hello world", "unicode ümlaut ✅", "a" * 500]
|
||||
]
|
||||
comp_inputs = [
|
||||
"",
|
||||
"# comment\n\nreal line here\n====\n// slash comment\n(_source_: x)\n[source: y]\ntiny\nkeep this line too\n\n" + "z" * 900,
|
||||
"line one\nline two\nline three",
|
||||
"====",
|
||||
"ab\ncdefgh",
|
||||
]
|
||||
for i, s in enumerate(comp_inputs):
|
||||
cases.append({"fn": "compress", "name": f"compress_{i}",
|
||||
"input": {"text": s},
|
||||
"expected": {"compressed": ch._compress(s)}})
|
||||
out = {"oracle": "cache._hash", "generated": "2026-10-07", "cases": cases}
|
||||
(REPO / "src/go/cache" / "truth_hash.json").write_text(json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {len(cases)} cases")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
"""gen_drift_fama_truth.py — factory batch 12 truth battery (live oracle)."""
|
||||
import json
|
||||
import sqlite3
|
||||
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"))
|
||||
|
||||
import drift_core as dc # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
tmp = Path(tempfile.mkdtemp(prefix="fama-truth-"))
|
||||
db = sqlite3.connect(str(tmp / "t.db"))
|
||||
db.row_factory = sqlite3.Row
|
||||
db.execute("CREATE TABLE entries (id INTEGER PRIMARY KEY, status TEXT, rock_status TEXT, wash_count INTEGER)")
|
||||
rows = [
|
||||
(1, "active", "washable", 5), # valid (wash>0)
|
||||
(2, "active", "established", 0), # valid (established)
|
||||
(3, "active", "rock", 0), # valid (rock)
|
||||
(4, "active", "washable", 0), # INVALID (active, no wash, washable)
|
||||
(5, "superseded", "washable", 9), # invalid (not active)
|
||||
]
|
||||
db.executemany("INSERT INTO entries VALUES (?,?,?,?)", rows)
|
||||
db.commit()
|
||||
|
||||
cases = []
|
||||
for name, ids in [("all_valid", [1, 2, 3]), ("mixed", [1, 2, 4]),
|
||||
("with_missing", [1, 999]), ("empty", []),
|
||||
("all_invalid", [4, 5])]:
|
||||
fetched = []
|
||||
for eid in ids:
|
||||
r = db.execute("SELECT status, rock_status, wash_count FROM entries WHERE id=?", (eid,)).fetchone()
|
||||
if r:
|
||||
fetched.append(dict(r))
|
||||
cases.append({
|
||||
"fn": "fama", "name": name,
|
||||
"input": {"rows": fetched, "count": len(ids)},
|
||||
"expected": {"fama": dc.compute_fama(db, "q", ids)},
|
||||
})
|
||||
|
||||
out = {"oracle": "drift_core.compute_fama (live)", "generated": "2026-10-08", "cases": cases}
|
||||
(REPO / "src/go/drift-core" / "truth_fama.json").write_text(json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {len(cases)} cases")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_drift_value_truth.py — factory batch 10 truth battery (live oracle)."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import drift_core as dc # noqa: E402
|
||||
|
||||
NOW = "2026-10-08T00:30:00+00:00"
|
||||
|
||||
|
||||
def main():
|
||||
entries = [
|
||||
# fresh, low everything
|
||||
{"wash_count": 0, "importance": 0.5, "text": "unproven entry", "rock_status": "washable"},
|
||||
# high proven, recent access
|
||||
{"wash_count": 25, "importance": 0.9, "text": "proven entry", "rock_status": "washable",
|
||||
"last_accessed": "2026-10-07T20:00:00+00:00"},
|
||||
# proven but stale (30+ days)
|
||||
{"wash_count": 30, "importance": 0.8, "text": "stale entry", "rock_status": "washable",
|
||||
"last_accessed": "2026-08-01T10:00:00+00:00"},
|
||||
# already rock
|
||||
{"wash_count": 40, "importance": 0.9, "text": "rocked", "rock_status": "rock"},
|
||||
# bad timestamp (degrade path)
|
||||
{"wash_count": 22, "importance": 0.7, "text": "bad ts", "rock_status": "washable",
|
||||
"last_accessed": "not-a-date"},
|
||||
# goal relevance hit
|
||||
{"wash_count": 21, "importance": 0.6, "text": "the curator gates memory writes",
|
||||
"rock_status": "washable", "last_accessed": "2026-10-06T00:00:00+00:00"},
|
||||
]
|
||||
goals = ["curator memory writes", "gate the pipeline"]
|
||||
cases = []
|
||||
for i, e in enumerate(entries):
|
||||
cases.append({
|
||||
"fn": "value", "name": f"value_{i}", "input": {"entry": e},
|
||||
"expected": {"value": dc._py_compute_value(e, None, NOW)},
|
||||
})
|
||||
cases.append({
|
||||
"fn": "promo", "name": f"promo_{i}", "input": {"entry": e},
|
||||
"expected": {"eligible": dc._py_check_promotion_eligibility(e, NOW)[0],
|
||||
"reason": dc._py_check_promotion_eligibility(e, NOW)[1]},
|
||||
})
|
||||
# goal-relevance path
|
||||
cases.append({
|
||||
"fn": "value", "name": "with_goals", "input": {"entry": entries[5], "goals": goals},
|
||||
"expected": {"value": dc._py_compute_value(entries[5], goals, NOW)},
|
||||
})
|
||||
|
||||
out = {"oracle": "drift_core._py_* oracles", "generated": "2026-10-08",
|
||||
"frozen_now": NOW, "cases": cases}
|
||||
(REPO / "src/go/drift-core" / "truth_value.json").write_text(
|
||||
json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {len(cases)} cases")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_event_bus_truth.py — factory batch 9 truth battery (live oracle)."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import event_bus as eb # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
payloads = [
|
||||
{},
|
||||
{"k": "v"},
|
||||
{"task_id": "t1", "status": "failed", "count": 3},
|
||||
{"nested": {"a": [1, 2, "x"]}, "flag": True, "nothing": None},
|
||||
{"text": "quote\" inside\\ back\nnewline\ttab"},
|
||||
{"float_int": 1.0, "float_frac": 2.5},
|
||||
]
|
||||
cases = [
|
||||
{"fn": "hash", "name": f"payload_{i}", "input": {"payload": p},
|
||||
"expected": {"hash": eb._content_hash(p)}}
|
||||
for i, p in enumerate(payloads)
|
||||
]
|
||||
out = {"oracle": "event_bus._content_hash", "generated": "2026-10-07", "cases": cases}
|
||||
(REPO / "src/go/event-bus" / "truth_bus.json").write_text(json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {len(cases)} cases")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_hook_placement_truth.py — factory batch 4 truth battery
|
||||
(contract BUILDER-2026-10-07T23:06:18Z-ec6208).
|
||||
|
||||
The LIVE recommend_placement oracle over a factor matrix covering every
|
||||
branch: hook-point combos, frequency ladder, cache scopes, concurrency,
|
||||
failure modes, context budget.
|
||||
|
||||
Usage: python3 scripts/gen_hook_placement_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import hook_engine as he # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
factor_sets = [
|
||||
{"output_dep": True},
|
||||
{"input_dep": True},
|
||||
{"input_dep": True, "output_dep": True},
|
||||
{},
|
||||
{"side_effects": "read-write"},
|
||||
{"volatility": "session"},
|
||||
{"volatility": "per-turn"},
|
||||
{"scope": "persistent"},
|
||||
{"scope": "session"},
|
||||
{"concurrent": True},
|
||||
{"failure": "fail-loud"},
|
||||
{"context_budget": 500},
|
||||
{"input_dep": True, "scope": "session", "concurrent": True,
|
||||
"context_budget": 250},
|
||||
{"side_effects": "read-write", "failure": "fail-loud",
|
||||
"scope": "persistent"},
|
||||
]
|
||||
cases = []
|
||||
for i, factors in enumerate(factor_sets):
|
||||
cases.append({
|
||||
"fn": "placement", "name": f"matrix_{i}",
|
||||
"input": {"factors": factors},
|
||||
"expected": he.recommend_placement(f"hook_{i}", factors),
|
||||
})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/hook_engine.py recommend_placement (live)",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/hook-engine" / "truth_placement.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_memory_store_gate_truth.py — memory-store batch 4 truth battery
|
||||
(contract BUILDER-2026-10-07T19:47:37Z-1cf1dc).
|
||||
|
||||
The LIVE oracle: the dense-side meta-judgement extracted from recall()
|
||||
(lines 655-676) exercised against a FIXTURE STORE (sqlite entries + df
|
||||
counts), plus the entries-side fusion vs the chunk-side one (identity
|
||||
check), plus the abstain constants.
|
||||
|
||||
Usage: python3 scripts/gen_memory_store_gate_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
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"))
|
||||
|
||||
import memory_store as ms # noqa: E402
|
||||
|
||||
|
||||
def make_fixture_store(db_path):
|
||||
"""Entries table + df data mirroring recall()'s df-of-word query."""
|
||||
db = sqlite3.connect(db_path)
|
||||
db.row_factory = sqlite3.Row
|
||||
db.execute("CREATE TABLE entries (id INTEGER PRIMARY KEY, text TEXT, status TEXT)")
|
||||
rows = [
|
||||
(1, "the curator gates memory writes through worthiness"),
|
||||
(2, "black holes bend light near the event horizon"),
|
||||
(3, "black coffee in the morning keeps nick running"),
|
||||
(4, "the spine rebuild parity proofs live in store"),
|
||||
(5, "memory writes require the operator approval gate"),
|
||||
(6, "unrelated filler text about weather patterns"),
|
||||
(7, "curator provenance recovery from odysseus"),
|
||||
(8, "another filler row to pad the document frequency"),
|
||||
]
|
||||
db.executemany("INSERT INTO entries VALUES (?,?,?)",
|
||||
[(i, t, "active") for (i, t) in rows])
|
||||
db.commit()
|
||||
return db
|
||||
|
||||
|
||||
def main():
|
||||
tmp = Path(tempfile.mkdtemp(prefix="ms-gate-truth-"))
|
||||
db = make_fixture_store(str(tmp / "store.db"))
|
||||
total = db.execute("SELECT COUNT(*) AS n FROM entries WHERE status='active'").fetchone()["n"]
|
||||
|
||||
def df_of(word):
|
||||
return db.execute(
|
||||
"SELECT COUNT(*) AS n FROM entries WHERE status='active' AND text LIKE ?",
|
||||
(f"%{word}%",),
|
||||
).fetchone()["n"]
|
||||
|
||||
# replicate recall()'s dense-gate block verbatim
|
||||
def oracle_gate(query, hits):
|
||||
q_content = {t for t in query.lower().split() if len(t) > 3 and t not in ms._STOPWORDS}
|
||||
dense = []
|
||||
for rowid, cos in hits:
|
||||
etxt = (db.execute("SELECT text FROM entries WHERE id=?", (rowid,)).fetchone()["text"] or "").lower()
|
||||
distinctive = [w for w in q_content if w in etxt and df_of(w) / total < 0.05]
|
||||
if distinctive:
|
||||
dense.append((rowid, cos))
|
||||
elif not q_content or cos >= 0.80:
|
||||
dense.append((rowid, cos))
|
||||
return dense
|
||||
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# gate: shared DISTINCTIVE word (curator: df=2/8=0.25? no — check)
|
||||
# df(curator) = rows 1,7 = 2 → 0.25 ≥ 0.05 → NOT distinctive!
|
||||
# The oracle's threshold means common-store words fail; only truly
|
||||
# rare words pass. Fixture exercise:
|
||||
for name, query, hits in [
|
||||
("distinctive_shared", "worthiness gates", [(1, 0.9), (2, 0.85)]),
|
||||
("common_word_falls_back", "black", [(2, 0.86), (3, 0.79)]),
|
||||
("strong_cos_no_share", "unrelated query terms", [(6, 0.91), (4, 0.70)]),
|
||||
("no_content_words", "the was and", [(1, 0.60), (6, 0.95)]),
|
||||
]:
|
||||
add("dense_gate", name,
|
||||
{"query": query, "hits": [[r, c] for r, c in hits], "total": total},
|
||||
{"kept": [[r, c] for r, c in oracle_gate(query, hits)],
|
||||
"q_content": sorted({t for t in query.lower().split() if len(t) > 3 and t not in ms._STOPWORDS}),
|
||||
"df": {w: df_of(w) for w in query.lower().split() if len(w) > 3}})
|
||||
|
||||
# entries-side fusion identity with the chunk-side one
|
||||
dense = [[1, 0.9], [2, 0.8]]
|
||||
bm25 = [[2, 10.0], [3, 4.0]]
|
||||
add("fusion_identity", "entries_eq_chunks",
|
||||
{"dense": dense, "bm25": bm25},
|
||||
{"fusion": ms._py_rank_fusion(dense, bm25)})
|
||||
|
||||
add("constants", "abstain",
|
||||
{},
|
||||
{"budget": ms.RECALL_BUDGET, "min_score": ms.RECALL_MIN_SCORE})
|
||||
|
||||
import shutil
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/memory_store.py recall() dense gate + _py_rank_fusion",
|
||||
"generated": "2026-10-07",
|
||||
"fixture_df_note": "df via LIKE-substring over active entries; threshold 0.05",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/memory-store-deep" / "truth_gate.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_memory_store_score_truth.py — memory-store batch 3 truth battery
|
||||
(contract BUILDER-2026-10-07T18:57:06Z-c2429c).
|
||||
|
||||
The LIVE _py_score_recall_rows oracle: metadata scoring across modes
|
||||
(hybrid / dense-only / lexical-only), recency decay, the abstention
|
||||
ladder (min_score gates, the 0.74 high-confidence gate, the 0.08 gap
|
||||
detection), position-aware re-rank.
|
||||
|
||||
Usage: python3 scripts/gen_memory_store_score_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import memory_store as ms # noqa: E402
|
||||
|
||||
NOW = "2026-10-07T18:58:00"
|
||||
|
||||
|
||||
def rows_for(ids):
|
||||
"""Fixture rows: confidence/temperature/recency variety."""
|
||||
out = []
|
||||
for i, eid in enumerate(ids):
|
||||
out.append({
|
||||
"id": eid,
|
||||
"text": f"entry {eid} body",
|
||||
"importance": 0.5 + i * 0.01,
|
||||
"topic": "project",
|
||||
"entities": ["a"],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9 if i % 2 == 0 else 0.6,
|
||||
"temperature": 1.2 if i % 3 == 0 else 0.8,
|
||||
"last_accessed": ["2026-10-07T18:00:00", "2026-09-01T10:00:00",
|
||||
"2026-05-01T10:00:00", None][i % 4],
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# hybrid: ranks over 4 rows, dense + bm25 present
|
||||
fusion = {"ranks": [[1, 0.12], [2, 0.11], [3, 0.10], [4, 0.09]],
|
||||
"best_dense": 0.8, "mode": "hybrid"}
|
||||
dense = [[1, 0.8], [2, 0.7], [3, 0.6]]
|
||||
bm25 = [[2, 10.0], [4, 5.0], [1, 2.0]]
|
||||
rows = rows_for([1, 2, 3, 4])
|
||||
add("score", "hybrid",
|
||||
{"fusion": fusion, "rows": rows, "dense": dense, "bm25": bm25,
|
||||
"budget": 3, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion, rows, dense, bm25, 3, 0.3, NOW)})
|
||||
|
||||
# dense-only: high confidence (>= 0.74) → position-aware
|
||||
fusion_d = {"ranks": [[1, 0.06], [2, 0.055], [3, 0.05]],
|
||||
"best_dense": 0.8, "mode": "dense-only"}
|
||||
add("score", "dense_only_high_conf",
|
||||
{"fusion": fusion_d, "rows": rows_for([1, 2, 3]), "dense": dense,
|
||||
"bm25": [], "budget": 5, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_d, rows_for([1, 2, 3]), dense, [], 5, 0.3, NOW)})
|
||||
|
||||
# dense-only: narrow gap → abstain
|
||||
dense_close = [[1, 0.80], [2, 0.75]]
|
||||
add("score", "dense_only_gap_abstain",
|
||||
{"fusion": fusion_d, "rows": rows_for([1, 2, 3]), "dense": dense_close,
|
||||
"bm25": [], "budget": 5, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_d, rows_for([1, 2, 3]), dense_close, [], 5, 0.3, NOW)})
|
||||
|
||||
# dense-only: below min_score → abstain
|
||||
add("score", "dense_only_min_score",
|
||||
{"fusion": fusion_d, "rows": rows_for([1, 2, 3]), "dense": dense,
|
||||
"bm25": [], "budget": 5, "min_score": 0.9, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_d, rows_for([1, 2, 3]), dense, [], 5, 0.9, NOW)})
|
||||
|
||||
# lexical-only: max relevance gate
|
||||
fusion_l = {"ranks": [[2, 0.9], [4, 0.7]], "best_dense": 0.0, "mode": "lexical-only"}
|
||||
add("score", "lexical_only_gate",
|
||||
{"fusion": fusion_l, "rows": rows_for([2, 4]), "dense": [], "bm25": bm25,
|
||||
"budget": 5, "min_score": 1.0, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_l, rows_for([2, 4]), [], bm25, 5, 1.0, NOW)})
|
||||
|
||||
# neither → []
|
||||
add("score", "neither",
|
||||
{"fusion": fusion_l, "rows": rows_for([2, 4]), "dense": [], "bm25": [],
|
||||
"budget": 5, "min_score": 0.3, "now": NOW},
|
||||
{"scored": ms._py_score_recall_rows(fusion_l, rows_for([2, 4]), [], [], 5, 0.3, NOW)})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/memory_store.py _py_score_recall_rows",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/memory-store-deep" / "truth_score.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_microkernel_truth.py — factory batch 8 truth battery (live oracle)."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import microkernel as mk # noqa: E402
|
||||
|
||||
FROZEN = "2026-10-07T23:35:00"
|
||||
|
||||
|
||||
def main():
|
||||
mk.now_iso = lambda: FROZEN
|
||||
cases = []
|
||||
|
||||
def snap(reg, name):
|
||||
return {
|
||||
"plugins": {k: {"active": v["active"], "registered_at": v["registered_at"]}
|
||||
for k, v in reg["plugins"].items()},
|
||||
"capabilities": reg["capabilities"],
|
||||
"hooks": reg["hooks"],
|
||||
}
|
||||
|
||||
# register + resolve priority tie
|
||||
import tempfile, os as _os
|
||||
_tmpdb = _os.path.join(tempfile.mkdtemp(prefix="mk-truth-"), "registry.json")
|
||||
mk.REGISTRY_DB = _tmpdb
|
||||
|
||||
def _snap():
|
||||
return mk._load_registry()
|
||||
mk.register({"name": "alpha", "capabilities": ["cap.a"], "priority": 50})
|
||||
mk.register({"name": "beta", "capabilities": ["cap.a"], "priority": 10})
|
||||
mk.register({"name": "gamma", "capabilities": ["cap.a"], "priority": 10, "auto_activate": False})
|
||||
mk.register({"name": "delta", "hooks": {"pre-turn": True, "post-turn": False}})
|
||||
cases.append({"fn": "state", "name": "after_registers", "input": {},
|
||||
"expected": snap(_snap(), "x")})
|
||||
cases.append({"fn": "resolve", "name": "priority_active",
|
||||
"input": {"capability": "cap.a"},
|
||||
"expected": {"resolved": mk.resolve("cap.a")}})
|
||||
cases.append({"fn": "resolve", "name": "unknown_cap",
|
||||
"input": {"capability": "cap.nope"},
|
||||
"expected": {"resolved": mk.resolve("cap.nope")}})
|
||||
|
||||
# unregister removes from indexes
|
||||
mk.unregister("beta")
|
||||
cases.append({"fn": "state", "name": "after_unregister", "input": {},
|
||||
"expected": snap(_snap(), "x")})
|
||||
cases.append({"fn": "resolve", "name": "after_unregister",
|
||||
"input": {"capability": "cap.a"},
|
||||
"expected": {"resolved": mk.resolve("cap.a")}})
|
||||
|
||||
# error texts
|
||||
err1 = mk.register({})
|
||||
err2 = mk.unregister("nope")
|
||||
cases.append({"fn": "errors", "name": "shapes", "input": {},
|
||||
"expected": {"register_empty": err1, "unregister_unknown": err2}})
|
||||
|
||||
out = {"oracle": "src/rune/microkernel.py (live)", "generated": "2026-10-07",
|
||||
"frozen_now": FROZEN, "cases": cases}
|
||||
(REPO / "src/go/microkernel" / "truth_microkernel.json").write_text(
|
||||
json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {len(cases)} cases")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_provider_format_truth.py — factory batch 5 truth battery (live
|
||||
provider_registry._format_tools_* oracles)."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import provider_registry as pr # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
tools = [
|
||||
{"name": "read_file", "description": "Read a file",
|
||||
"parameters": {"type": "object", "properties": {"path": {"type": "string"}}}},
|
||||
{"name": "write_file", "description": "Write a file"}, # no parameters key
|
||||
]
|
||||
cases = [
|
||||
{"fn": "openai", "name": "shape", "input": {"tools": tools},
|
||||
"expected": pr._format_tools_openai(tools)},
|
||||
{"fn": "anthropic", "name": "shape", "input": {"tools": tools},
|
||||
"expected": pr._format_tools_anthropic(tools)},
|
||||
]
|
||||
out = {"oracle": "provider_registry._format_tools_*", "generated": "2026-10-07", "cases": cases}
|
||||
target = REPO / "src/go/provider-registry" / "truth_format.json"
|
||||
target.write_text(json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_scaffold_close_truth.py — scaffold batch 3 truth battery
|
||||
(contract BUILDER-2026-10-07T18:42:38Z-52829e).
|
||||
|
||||
The LIVE oracle: _py_close_quality bucket math + close_contract on a doc
|
||||
built through the real lifecycle (new_contract → transitions → snapshot →
|
||||
close). now() frozen at scaffold AND structural_store namespaces.
|
||||
|
||||
Usage: python3 scripts/gen_scaffold_close_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
from rune import scaffold as sc # noqa: E402
|
||||
from rune.structural_store import new_doc, add_entity # noqa: E402
|
||||
|
||||
FROZEN = "2026-10-07T18:46:00"
|
||||
|
||||
|
||||
def main():
|
||||
sc.now = lambda: FROZEN
|
||||
cases = []
|
||||
|
||||
# ── CloseQuality bucket math (direct, edge-covering) ──────────────────
|
||||
inputs = {
|
||||
"contract_id": "q1",
|
||||
"phase": "superseding",
|
||||
"affected_entities": ["module:a", "module:b"],
|
||||
"expected_new_entities": ["module:new1", "module:new2"],
|
||||
"predicted_existing_affected": ["module:c"],
|
||||
"predicted_frontier": {
|
||||
"level0_direct": ["module:a"],
|
||||
"level1_obligations": ["module:d"],
|
||||
"contracts": ["contract:x"],
|
||||
"level2_verification": ["canary:v"],
|
||||
},
|
||||
"expected_runtime_effects": ["module:a/ctl", "module:zz/never"],
|
||||
"observed_evidence": [
|
||||
{"entity": "module:a", "source": "tests", "detail": "d" * 400, "authority": "pytest"},
|
||||
{"entity": "module:new1", "source": "build", "detail": "created"},
|
||||
{"entity": "module:surprise", "source": "runtime", "detail": "unplanned"},
|
||||
{"entity": "", "source": "runtime", "detail": "no entity — ignored"},
|
||||
],
|
||||
"now": FROZEN,
|
||||
}
|
||||
s = sc.Scaffold(Path(tempfile.mkdtemp()), "q")
|
||||
cases.append({
|
||||
"fn": "close_quality", "name": "buckets",
|
||||
"input": {"inputs": inputs},
|
||||
"expected": s._py_close_quality(inputs),
|
||||
})
|
||||
|
||||
# ── CloseContract through the real lifecycle ──────────────────────────
|
||||
tmp = Path(tempfile.mkdtemp(prefix="scaffold-close-truth-"))
|
||||
s2 = sc.Scaffold(tmp, "close-project")
|
||||
s2.init(purpose="close battery")
|
||||
doc = s2.load()
|
||||
s2.new_contract(doc, contract_id="c1", need="n", scope=["src/go/x/"],
|
||||
affected_entities=["module:a"],
|
||||
expected_new_entities=["module:new1"])
|
||||
s2.transition_contract(doc, "c1", "approved")
|
||||
s2.transition_contract(doc, "c1", "building")
|
||||
s2.transition_contract(doc, "c1", "verifying")
|
||||
# predicted frontier on the entity
|
||||
e = s2.contract(doc, "c1")
|
||||
e.setdefault("ext", {})["predicted_frontier"] = {
|
||||
"level0_direct": ["module:a"], "level1_obligations": [],
|
||||
"contracts": [], "level2_verification": [],
|
||||
}
|
||||
ev = [{"entity": "module:a", "source": "tests", "detail": "passed"}]
|
||||
e2 = s2.close_contract(doc, "c1", observed_evidence=ev,
|
||||
scope_files=["src/go/x/main.go", "src/other/rogue.py"])
|
||||
cases.append({
|
||||
"fn": "close_contract", "name": "full_lifecycle",
|
||||
"input": {},
|
||||
"expected": {"entity": e2},
|
||||
})
|
||||
|
||||
# phase gate
|
||||
doc2 = new_doc()
|
||||
add_entity(doc2, id="contract:early", kind="construction_contract",
|
||||
identities={"need": "n"}, maturity="DECLARED",
|
||||
evidence_refs=["x"])
|
||||
doc2["entities"][-1]["phase"] = "draft"
|
||||
doc2["entities"][-1]["status"] = "open"
|
||||
doc2["entities"][-1]["checkpoints"] = []
|
||||
try:
|
||||
s2.close_contract(doc2, "early", observed_evidence=[])
|
||||
gate = {"ok": True}
|
||||
except Exception as ex: # noqa: BLE001 — intentional: the case IS the gate error; the oracle's text becomes the truth column
|
||||
gate = {"ok": False, "error": str(ex)}
|
||||
cases.append({
|
||||
"fn": "close_gate", "name": "phase_guard",
|
||||
"input": {},
|
||||
"expected": gate,
|
||||
})
|
||||
|
||||
# missing contract
|
||||
try:
|
||||
s2.close_contract(doc2, "ghost", observed_evidence=[])
|
||||
miss = {"ok": True}
|
||||
except Exception as ex: # noqa: BLE001 — intentional: same oracle-error-as-truth pattern as above
|
||||
miss = {"ok": False, "error": str(ex)}
|
||||
cases.append({
|
||||
"fn": "close_gate", "name": "missing_contract",
|
||||
"input": {},
|
||||
"expected": miss,
|
||||
})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/scaffold.py (live, now frozen at both namespaces)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": FROZEN,
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/scaffold" / "truth_close.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
import shutil
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_sleep_time_truth.py — factory batch 11 truth battery (live oracle)."""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
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"))
|
||||
|
||||
import sleep_time as st # noqa: E402
|
||||
|
||||
|
||||
def make_parts_db(db_path):
|
||||
db = sqlite3.connect(db_path)
|
||||
db.execute("CREATE TABLE part (session_id TEXT, data TEXT, time_created TEXT)")
|
||||
rows = [
|
||||
("s1", '{"tool": "todowrite", "state": {"input": {"todos": [{"content": "first", "status": "completed", "priority": "high"}]}}}', "2026-10-07T10:00:00"),
|
||||
("s1", '{"tool": "bash", "state": {"input": {"command": "ls"}}}', "2026-10-07T10:01:00"),
|
||||
("s1", '{"tool": "todowrite", "state": {"input": {"todos": [{"content": "second", "status": "in_progress", "priority": "med"}, {"content": "third", "status": "pending", "priority": "low"}]}}}', "2026-10-07T10:02:00"),
|
||||
("s1", '{"tool": "todowrite", "state": {"input": {"todos": []}}}', "2026-10-07T10:03:00"), # empty — skipped
|
||||
("s1", '{"tool": "read_file", "state": {"input": {}}}', "2026-10-07T10:04:00"), # non-todowrite
|
||||
("s2", '{"tool": "todowrite", "state": {"input": {"todos": [{"content": "other session", "status": "pending", "priority": ""}]}}}', "2026-10-07T11:00:00"),
|
||||
]
|
||||
db.executemany("INSERT INTO part VALUES (?,?,?)", rows)
|
||||
db.commit()
|
||||
return db
|
||||
|
||||
|
||||
def main():
|
||||
tmp = Path(tempfile.mkdtemp(prefix="st-truth-"))
|
||||
db = make_parts_db(str(tmp / "p.db"))
|
||||
|
||||
cases = []
|
||||
|
||||
# extract_todos for s1 (ORDER BY time ASC → latest = the 10:02 todos)
|
||||
rows = [{"data": r[0]} for r in db.execute(
|
||||
"SELECT data FROM part WHERE session_id='s1' ORDER BY time_created ASC").fetchall()]
|
||||
cases.append({"fn": "extract_todos", "name": "s1_latest",
|
||||
"input": {"rows": rows},
|
||||
"expected": {"todos": st.extract_todos(
|
||||
_FakeConn(db, "s1"), "s1")}})
|
||||
rows2 = [{"data": r[0]} for r in db.execute(
|
||||
"SELECT data FROM part WHERE session_id='s2' ORDER BY time_created ASC").fetchall()]
|
||||
cases.append({"fn": "extract_todos", "name": "s2",
|
||||
"input": {"rows": rows2},
|
||||
"expected": {"todos": st.extract_todos(
|
||||
_FakeConn(db, "s2"), "s2")}})
|
||||
rows3 = [{"data": r[0]} for r in db.execute(
|
||||
"SELECT data FROM part WHERE session_id='nope' ORDER BY time_created ASC").fetchall()]
|
||||
cases.append({"fn": "extract_todos", "name": "empty_session",
|
||||
"input": {"rows": rows3},
|
||||
"expected": {"todos": st.extract_todos(
|
||||
_FakeConn(db, "nope"), "nope")}})
|
||||
|
||||
# write_transcript byte format
|
||||
st.TRANSCRIPTS_DIR = str(tmp / "transcripts")
|
||||
Path(st.TRANSCRIPTS_DIR).mkdir(parents=True, exist_ok=True)
|
||||
todos = [{"content": "write the thing", "status": "completed", "priority": "high"},
|
||||
{"content": "review it", "status": "in_progress", "priority": ""}]
|
||||
parts = [("user", "do the task"), ("assistant", "done, verified")]
|
||||
p1 = st.write_transcript("s1", "My Session: v2!", parts, todos)
|
||||
cases.append({"fn": "transcript", "name": "full_shape",
|
||||
"input": {},
|
||||
"expected": {"content": Path(p1).read_text(),
|
||||
"filename_stem": Path(p1).name}})
|
||||
p2 = st.write_transcript("s2", "no todos", parts, None)
|
||||
cases.append({"fn": "transcript", "name": "no_todos",
|
||||
"input": {},
|
||||
"expected": {"content": Path(p2).read_text()}})
|
||||
|
||||
out = {"oracle": "sleep_time.extract_todos/write_transcript (live)",
|
||||
"generated": "2026-10-08", "cases": cases}
|
||||
(REPO / "src/go/sleep-time" / "truth_transcript.json").write_text(
|
||||
json.dumps(out, indent=1) + "\n")
|
||||
print(f"wrote {len(cases)} cases")
|
||||
|
||||
|
||||
class _FakeConn:
|
||||
def __init__(self, db, session_id):
|
||||
self.db = db
|
||||
self.session_id = session_id
|
||||
|
||||
def execute(self, sql, params):
|
||||
# replicate the oracle's query (session-filtered, time-ordered)
|
||||
class Cur:
|
||||
def __init__(self, rows):
|
||||
self.rows = rows
|
||||
|
||||
def fetchall(self):
|
||||
return self.rows
|
||||
|
||||
return Cur(self.db.execute(sql, params).fetchall())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_tool_executor_auth_truth.py — the tool-executor authority truth
|
||||
(contract BUILDER-2026-10-07T19:10:38Z-cc6be2).
|
||||
|
||||
Freezes the LIVE READ_ONLY_TOOLS set + the prepare-tool routing verdicts
|
||||
so CI asserts the Go set equals the Python set FOREVER. The run_command
|
||||
wrong-add this battery pins was a security-adjacent divergence: a mutating
|
||||
tool treated as read-only bypasses operator approval.
|
||||
|
||||
Usage: python3 scripts/gen_tool_executor_auth_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import tool_executor as te # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = [
|
||||
{
|
||||
"fn": "read_only_tools", "name": "exact_set",
|
||||
"input": {},
|
||||
"expected": {"tools": sorted(te.READ_ONLY_TOOLS)},
|
||||
},
|
||||
{
|
||||
"fn": "prepare_routing", "name": "run_command_requires_approval",
|
||||
"input": {"tool": "run_command", "args": {"command": "ls -la"}},
|
||||
"expected": {"read_only": "run_command" in te.READ_ONLY_TOOLS},
|
||||
},
|
||||
{
|
||||
"fn": "prepare_routing", "name": "read_file_executes_now",
|
||||
"input": {"tool": "read_file", "args": {"path": "/tmp"}},
|
||||
"expected": {"read_only": "read_file" in te.READ_ONLY_TOOLS},
|
||||
},
|
||||
]
|
||||
out = {
|
||||
"oracle": "src/rune/tool_executor.py READ_ONLY_TOOLS + routing",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/tool-executor" / "truth_auth.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_tool_protocol_truth.py — factory batch 1 truth battery
|
||||
(contract BUILDER-2026-10-07T22:11:52Z-9ffb73).
|
||||
|
||||
Live tool_protocol oracle fixtures: validate error texts, alias
|
||||
canonicalization, ollama envelope normalization (incl. string-arguments
|
||||
JSON), internal-protocol detection, extract_visible_text span stripping.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import tool_protocol as tp # noqa: E402
|
||||
|
||||
|
||||
def case(fn, name, inp, fn_lambda):
|
||||
try:
|
||||
expected = {"ok": True, "result": fn_lambda()}
|
||||
except Exception as ex:
|
||||
expected = {"ok": False, "error": str(ex)}
|
||||
return {"fn": fn, "name": name, "input": inp, "expected": expected}
|
||||
|
||||
|
||||
def main():
|
||||
cases = []
|
||||
R = lambda cid, n, a: {"call_id": cid, "name": n, "arguments": a, "source": "test"}
|
||||
|
||||
# validate: alias canonicalization
|
||||
cases.append(case("validate", "alias_grep",
|
||||
{"request": R("c1", "grep", {"pattern": "x", "path": "."})},
|
||||
lambda: tp.validate_tool_request(tp.ToolRequest(**R("c1", "grep", {"pattern": "x", "path": "."}))).to_dict()))
|
||||
cases.append(case("validate", "alias_bash",
|
||||
{"request": R("c2", "bash", {"command": "ls"})},
|
||||
lambda: tp.validate_tool_request(tp.ToolRequest(**R("c2", "bash", {"command": "ls"}))).to_dict()))
|
||||
cases.append(case("validate", "unknown_tool",
|
||||
{"request": R("c3", "nonexistent_tool", {})},
|
||||
lambda: tp.validate_tool_request(tp.ToolRequest(**R("c3", "nonexistent_tool", {}))).to_dict()))
|
||||
cases.append(case("validate", "missing_required",
|
||||
{"request": R("c4", "read_file", {})},
|
||||
lambda: tp.validate_tool_request(tp.ToolRequest(**R("c4", "read_file", {}))).to_dict()))
|
||||
cases.append(case("validate", "unknown_arg",
|
||||
{"request": R("c5", "read_file", {"path": "/tmp", "bogus": 1})},
|
||||
lambda: tp.validate_tool_request(tp.ToolRequest(**R("c5", "read_file", {"path": "/tmp", "bogus": 1}))).to_dict()))
|
||||
cases.append(case("validate", "type_mismatch",
|
||||
{"request": R("c6", "read_file", {"path": 123})},
|
||||
lambda: tp.validate_tool_request(tp.ToolRequest(**R("c6", "read_file", {"path": 123}))).to_dict()))
|
||||
|
||||
# ollama normalization: string arguments JSON
|
||||
cases.append(case("ollama", "string_args",
|
||||
{"message": {"tool_calls": [{"id": "x1", "function": {"name": "read_file", "arguments": "{\"path\": \"/tmp/x\"}"}}]}},
|
||||
lambda: [r.to_dict() for r in tp.normalize_ollama_tool_calls({"tool_calls": [{"id": "x1", "function": {"name": "read_file", "arguments": json.dumps({"path": "/tmp/x"})}}]})]))
|
||||
cases.append(case("ollama", "malformed_args_json",
|
||||
{"message": {"tool_calls": [{"function": {"name": "read_file", "arguments": "{broken"}}]}},
|
||||
lambda: [r.to_dict() for r in tp.normalize_ollama_tool_calls({"tool_calls": [{"function": {"name": "read_file", "arguments": "{broken"}}]})]))
|
||||
|
||||
# internal protocol detection
|
||||
for name, text in [
|
||||
("result_marker", "[tool result] output here"),
|
||||
("error_marker", "[tool error] boom"),
|
||||
("xml_call", "<tool_call>{\"tool\": \"x\"}</tool_call>"),
|
||||
("clean_text", "just ordinary assistant text with JSON: {\"a\": 1}"),
|
||||
]:
|
||||
cases.append(case("internal", name, {"text": text},
|
||||
lambda t=text: {"contains": tp.contains_internal_protocol(t)}))
|
||||
|
||||
# extract_visible_text
|
||||
for name, text in [
|
||||
("inline_call", 'before {\"tool\": \"read_file\", \"args\": {\"path\": \"/tmp\"}} after'),
|
||||
("fenced_call", '```json\n{\"tool\": \"read_file\", \"args\": {\"path\": \"/tmp\"}}\n```'),
|
||||
("xml_call", 'before <tool_call>{\"tool\": \"x\", \"args\": {}}</tool_call> after'),
|
||||
("plain", "no protocol here at all"),
|
||||
]:
|
||||
cases.append(case("visible", name, {"text": text},
|
||||
lambda t=text: {"visible": tp.extract_visible_text(t)}))
|
||||
|
||||
# normalize_text_tool_calls end-to-end
|
||||
cases.append(case("normalize", "text_fallback",
|
||||
{"text": '{\"tool\": \"read_file\", \"args\": {\"path\": \"/tmp/x\"}}'},
|
||||
lambda: [r.to_dict() for r in tp.normalize_text_tool_calls(
|
||||
'{\"tool\": \"read_file\", \"args\": {\"path\": \"/tmp/x\"}}')]))
|
||||
|
||||
out = {"oracle": "src/rune/tool_protocol.py (live)", "generated": "2026-10-07", "cases": cases}
|
||||
target = REPO / "src/go/tool-protocol" / "truth_protocol.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_warm_router_digest_truth.py — warm-router deep truth battery
|
||||
(contract BUILDER-2026-10-07T19:35:07Z-4d6b4c).
|
||||
|
||||
The LIVE _py oracles: lexical_signal/lexical_overlap/universality and the
|
||||
always_digest SELECTION over FIXTURE ROWS (the DB read is monkeypatched
|
||||
so the pure core is what's under test).
|
||||
|
||||
Usage: python3 scripts/gen_warm_router_digest_truth.py (from repo root)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
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"))
|
||||
|
||||
import warm_router as wr # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
cases = []
|
||||
|
||||
def add(fn, name, inp, expected):
|
||||
cases.append({"fn": fn, "name": name, "input": inp, "expected": expected})
|
||||
|
||||
# ── lexical signal/overlap ────────────────────────────────────────────
|
||||
add("lexical", "signal_strips_punct",
|
||||
{"body": "The curator gates memory writes (always)."},
|
||||
{"words": sorted(wr._lexical_signal("The curator gates memory writes (always)."))})
|
||||
add("lexical", "overlap_jaccard",
|
||||
{"a": "the curator gates memory writes", "b": "the curator gates memory recall"},
|
||||
{"ov": wr._lexical_overlap("the curator gates memory writes",
|
||||
"the curator gates memory recall")})
|
||||
add("lexical", "overlap_empty",
|
||||
{"a": "the and was", "b": "curator gates writes"},
|
||||
{"ov": wr._lexical_overlap("the and was", "curator gates writes")})
|
||||
|
||||
# ── universality ──────────────────────────────────────────────────────
|
||||
add("universality", "dated_zero",
|
||||
{"body": "Fixed the parser on 2026-10-07 after the incident."},
|
||||
{"u": wr._universality("Fixed the parser on 2026-10-07 after the incident.")})
|
||||
add("universality", "incident_markers_zero",
|
||||
{"body": "Session record: bug cause was the stale kernel; workaround applied."},
|
||||
{"u": wr._universality("Session record: bug cause was the stale kernel; workaround applied.")})
|
||||
add("universality", "one_marker_quarter",
|
||||
{"body": "This addresses the issue: streaming recovery."},
|
||||
{"u": wr._universality("This addresses the issue: streaming recovery.")})
|
||||
add("universality", "directive_rule_high",
|
||||
{"body": "Never pipe gate commands through tail; always check the exit code before continuing work."},
|
||||
{"u": wr._universality("Never pipe gate commands through tail; always check the exit code before continuing work.")})
|
||||
add("universality", "rambling_penalty",
|
||||
{"body": " ".join(["word"] * 120) + " never always must keep"},
|
||||
{"u": wr._universality(" ".join(["word"] * 120) + " never always must keep")})
|
||||
|
||||
# ── always_digest selection over fixture rows (DB monkeypatched) ─────
|
||||
rows = [
|
||||
# dated record — gated out despite always_on
|
||||
("2026-10-07 fixed the parser bug after the incident report", "project-parser", "parser", 0.9, "", 1, 0.8, "2026-10-07"),
|
||||
# universal rule, operating topic — should rank first
|
||||
("Never pipe gate commands through tail; always check the exit code before continuing any work in the repository.", "operating-gates", "gate", 0.7, "", 1, 0.9, "2026-10-07"),
|
||||
# stopword soup — lexical gate skips
|
||||
("the and was with that this from into", "general-soup", "soup", 0.5, "", 0, 0.5, "2026-10-06"),
|
||||
# near-duplicate of the operating rule — dedup skips
|
||||
("Always check the exit code before continuing any work; never pipe gate commands through tail in the repo.", "operating-gates-copy", "gate2", 0.8, "", 1, 0.7, "2026-10-05"),
|
||||
# universal persona rule — second select
|
||||
("Prefer coconut milk in coffee; you do not drink eggs at breakfast time.", "human-prefs", "coffee", 0.6, "", 0, 0.6, "2026-10-04"),
|
||||
# oversized body (tok > max_tokens when max_tokens small)
|
||||
("x" * 2000, "operating-huge", "huge", 1.0, "", 1, 1.0, "2026-10-03"),
|
||||
]
|
||||
fixture_rows = [tuple(r) for r in rows]
|
||||
|
||||
class FakeDB:
|
||||
def execute(self, *_a, **_k):
|
||||
return self
|
||||
|
||||
def fetchall(self):
|
||||
return fixture_rows
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
return False
|
||||
|
||||
saved_db = wr._db
|
||||
wr._db = lambda: FakeDB()
|
||||
try:
|
||||
digest = wr.always_digest(max_tokens=220)
|
||||
finally:
|
||||
wr._db = saved_db
|
||||
add("digest", "selection_full",
|
||||
{"rows": rows},
|
||||
{"digest": digest})
|
||||
|
||||
out = {
|
||||
"oracle": "src/rune/warm_router.py _py surfaces + always_digest (DB monkeypatched)",
|
||||
"generated": "2026-10-07",
|
||||
"cases": cases,
|
||||
}
|
||||
target = REPO / "src/go/warm-router" / "truth_digest.json"
|
||||
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
|
||||
print(f"wrote {target} ({len(cases)} cases)")
|
||||
print("digest slugs:", [d["slug"] for d in digest])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""polyglot_lint.py — the language-AWARE lint gate (contract
|
||||
BUILDER-2026-10-07T20:01:40Z-ac6140).
|
||||
|
||||
Why: the pipeline linted Python hard (ruff + ratchet) but Rust NOT AT ALL
|
||||
(crates build, never clippy) and Go only via vet (gofmt drifted across 41
|
||||
files, including landed batches). This gate applies the RIGHT linter to
|
||||
whatever changed:
|
||||
|
||||
Python → ruff check
|
||||
Go → go vet (per module) + gofmt -l (formatting is a gate)
|
||||
Rust → cargo clippy -- -D warnings (per changed crate) + cargo fmt --check
|
||||
other → noted and skipped
|
||||
|
||||
Modes:
|
||||
(default) changed files vs the push range (git diff against
|
||||
origin/master / the last push ref; falls back to the
|
||||
last commit when no upstream exists)
|
||||
--all full tree
|
||||
--files a,b,c explicit list
|
||||
|
||||
Exit non-zero on any finding. The cron fault-watcher and the pre-push
|
||||
routine both run this; CI runs the Go+Rust halves natively.
|
||||
|
||||
Usage:
|
||||
python3 scripts/polyglot_lint.py # changed-only
|
||||
python3 scripts/polyglot_lint.py --all # everything
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
|
||||
LANG_BY_EXT = {
|
||||
".py": "python",
|
||||
".go": "go",
|
||||
".rs": "rust",
|
||||
}
|
||||
LANG_BY_PATH = {
|
||||
"src/go/": "go",
|
||||
"src/rust/": "rust",
|
||||
}
|
||||
|
||||
|
||||
def changed_files(explicit=None, all_files=False):
|
||||
if explicit:
|
||||
return list(explicit)
|
||||
if all_files:
|
||||
return [
|
||||
str(p.relative_to(REPO))
|
||||
for p in REPO.rglob("*")
|
||||
if p.is_file() and not str(p).startswith((".git", "store/", "target"))
|
||||
]
|
||||
base = None
|
||||
for ref in ("origin/master", "gitea/master", "HEAD~1"):
|
||||
r = subprocess.run(["git", "rev-parse", "--verify", ref],
|
||||
capture_output=True, text=True, cwd=REPO)
|
||||
if r.returncode == 0:
|
||||
base = ref
|
||||
break
|
||||
if base is None:
|
||||
return []
|
||||
r = subprocess.run(["git", "diff", "--name-only", f"{base}...HEAD"],
|
||||
capture_output=True, text=True, cwd=REPO)
|
||||
return [f for f in r.stdout.splitlines() if f.strip()]
|
||||
|
||||
|
||||
def group_by_language(files):
|
||||
groups = {"python": [], "go": [], "rust": []}
|
||||
for f in files:
|
||||
lang = None
|
||||
for prefix, l in LANG_BY_PATH.items():
|
||||
if f.startswith(prefix):
|
||||
lang = l
|
||||
break
|
||||
if lang is None:
|
||||
lang = LANG_BY_EXT.get(Path(f).suffix)
|
||||
if lang:
|
||||
groups[lang].append(f)
|
||||
return groups
|
||||
|
||||
|
||||
def run(cmd, cwd=None):
|
||||
r = subprocess.run(cmd, cwd=cwd or REPO, capture_output=True, text=True)
|
||||
return r.returncode, r.stdout, r.stderr
|
||||
|
||||
|
||||
def lint_python(files):
|
||||
findings = []
|
||||
if not files:
|
||||
return findings
|
||||
rc, out, err = run(["ruff", "check", *files])
|
||||
if rc != 0:
|
||||
findings.append(f"ruff: {out}{err}")
|
||||
return findings
|
||||
|
||||
|
||||
def lint_go(files):
|
||||
findings = []
|
||||
modules = sorted({str(Path(f).parent) for f in files})
|
||||
# collapse to module roots (dirs containing go.mod)
|
||||
for mod in modules:
|
||||
mod_path = REPO / mod
|
||||
while mod_path != REPO and not (mod_path / "go.mod").exists():
|
||||
mod_path = mod_path.parent
|
||||
if mod_path == REPO:
|
||||
continue
|
||||
rel = mod_path.relative_to(REPO)
|
||||
rc, out, err = run(["go", "vet", "./..."], cwd=mod_path)
|
||||
if rc != 0:
|
||||
findings.append(f"go vet ({rel}): {out}{err}")
|
||||
# gofmt across the changed files' modules (formatting is a GATE)
|
||||
fmt_targets = [f for f in files if f.endswith(".go")]
|
||||
if fmt_targets:
|
||||
rc, out, _ = run(["gofmt", "-l", *fmt_targets])
|
||||
if rc != 0:
|
||||
findings.append(f"gofmt: failed to run")
|
||||
elif out.strip():
|
||||
findings.append(
|
||||
"gofmt unformatted (run `gofmt -w` on these):\n "
|
||||
+ "\n ".join(out.splitlines())
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def lint_rust(files):
|
||||
findings = []
|
||||
crates = sorted({str(Path(f).relative_to(REPO)) for f in files})
|
||||
crate_roots = set()
|
||||
for c in crates:
|
||||
p = REPO / c
|
||||
while p != REPO and not (p / "Cargo.toml").exists():
|
||||
p = p.parent
|
||||
if p != REPO:
|
||||
crate_roots.add(p)
|
||||
for crate in sorted(crate_roots):
|
||||
rc, out, err = run(["cargo", "clippy", "--", "-D", "warnings"], cwd=crate)
|
||||
if rc != 0:
|
||||
findings.append(f"clippy ({crate.name}):\n{out}{err}")
|
||||
rc, out, err = run(["cargo", "fmt", "--check"], cwd=crate)
|
||||
if rc != 0:
|
||||
findings.append(f"rustfmt ({crate.name}):\n{out}{err}")
|
||||
return findings
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--all", action="store_true", help="lint the full tree")
|
||||
ap.add_argument("--files", help="comma-separated explicit file list")
|
||||
args = ap.parse_args()
|
||||
|
||||
explicit = args.files.split(",") if args.files else None
|
||||
files = changed_files(explicit, args.all)
|
||||
if not files:
|
||||
print("polyglot lint: no changed files detected — nothing to do")
|
||||
return 0
|
||||
|
||||
groups = group_by_language(files)
|
||||
print(f"polyglot lint: {len(files)} changed → "
|
||||
f"python={len(groups['python'])} go={len(groups['go'])} "
|
||||
f"rust={len(groups['rust'])}")
|
||||
|
||||
all_findings = []
|
||||
all_findings += lint_python(groups["python"])
|
||||
all_findings += lint_go(groups["go"])
|
||||
all_findings += lint_rust(groups["rust"])
|
||||
|
||||
if all_findings:
|
||||
print(f"POLYGLOT LINT: {len(all_findings)} finding(s):")
|
||||
for f in all_findings:
|
||||
print(f" - {f}")
|
||||
return 1
|
||||
print("polyglot lint: clean")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
"""release_exit_note.py — the EXIT-NOTE TEMPLATE (Nick, 2026-10-08).
|
||||
|
||||
The counterpart of the issue-entry template: when the pipeline earns a new
|
||||
version, this renders a PLAIN-ENGLISH summary of what was worked on and
|
||||
submitted, so version history reads without opening commits.
|
||||
|
||||
Template (what the reader sees):
|
||||
## What changed in v<VERSION>
|
||||
|
||||
<aggregate paragraph — counts by kind, the headline of the wave>
|
||||
|
||||
### What was worked on
|
||||
- <commit subject, translated to plain English>
|
||||
|
||||
### How it was verified
|
||||
- contracts walked (BUILDER-...) / truth batteries / CI gates — one
|
||||
line per verification kind found in the range
|
||||
|
||||
Feed: git log v<last-tag>..HEAD (chore(release) bumps excluded), the
|
||||
conventional-commit prefix gives the kind; the subject's scope gives the
|
||||
plain-English translation. The release step calls render() and passes the
|
||||
result as `extra.improved`/`added`/`fixed` per kind, so the evidence-
|
||||
derived note carries the human narrative alongside the governed facts.
|
||||
|
||||
Usage:
|
||||
python3 scripts/release_exit_note.py --since v0.117.0 [--to HEAD]
|
||||
python3 scripts/release_exit_note.py --since v0.117.0 --json # machine
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Conventional-commit prefix → (plain-english verb phrase, release-notes key)
|
||||
KIND_MAP = {
|
||||
"feat": ("added", "added"),
|
||||
"fix": ("fixed", "fixed"),
|
||||
"test": ("verified by test", "improved"),
|
||||
"docs": ("documented", "improved"),
|
||||
"chore": ("maintained", "improved"),
|
||||
"refactor": ("restructured", "improved"),
|
||||
"perf": ("made faster", "improved"),
|
||||
}
|
||||
|
||||
# Phrases in commit subjects translated to reader language
|
||||
TRANSLATIONS = [
|
||||
(r"\bcontract BUILDER-\S+", "under a construction contract"),
|
||||
(r"\bparity(?! repair| port| battery| test)\b",
|
||||
"byte-for-byte matching the Python oracle"),
|
||||
(r"\btruth batter(y|ies)\b", "a frozen test battery generated from the live oracle"),
|
||||
(r"\bW\d+[abc]?\b", ""),
|
||||
(r"\((?:W\d+[abc]?|[^)]*contract[^)]*)\)", ""),
|
||||
]
|
||||
|
||||
_NOISE_RE = re.compile(r"^chore\(release\): auto-bump|Merge (remote|pull)|^sync:")
|
||||
|
||||
|
||||
def commit_range(since, to="HEAD"):
|
||||
r = subprocess.run(
|
||||
["git", "log", f"{since}..{to}", "--format=%s", "--no-merges"],
|
||||
capture_output=True, text=True, cwd=REPO,
|
||||
)
|
||||
return [s.strip() for s in r.stdout.splitlines() if s.strip() and not _NOISE_RE.match(s)]
|
||||
|
||||
|
||||
def classify(subject):
|
||||
m = re.match(r"^(\w+)(?:\(([^)]*)\))?:\s*(.+)", subject)
|
||||
if not m:
|
||||
return "improved", subject, None
|
||||
kind, scope, rest = m.group(1), m.group(2), m.group(3)
|
||||
verb, key = KIND_MAP.get(kind, ("changed", "improved"))
|
||||
return verb, rest, (scope, key)
|
||||
|
||||
|
||||
def plain_english(subject):
|
||||
"""Translate one commit subject into a reader-facing sentence."""
|
||||
verb, rest, scope_key = classify(subject)
|
||||
text = rest[0].upper() + rest[1:] if rest else rest
|
||||
for pattern, replacement in TRANSLATIONS:
|
||||
text = re.sub(pattern, replacement, text)
|
||||
text = re.sub(r"\s+", " ", text).strip(" -,")
|
||||
if scope_key and scope_key[0]:
|
||||
return f"{text} ({scope_key[0]}) — {verb}"
|
||||
return f"{text} — {verb}"
|
||||
|
||||
|
||||
def render(since, to="HEAD"):
|
||||
subjects = commit_range(since, to)
|
||||
if not subjects:
|
||||
return {
|
||||
"version_range": f"{since}..{to}",
|
||||
"headline": "No substantive changes since the last version.",
|
||||
"items": [],
|
||||
"by_kind": {},
|
||||
}
|
||||
|
||||
by_kind = {}
|
||||
items = []
|
||||
for s in subjects:
|
||||
verb, _rest, scope_key = classify(s)
|
||||
key = scope_key[1] if scope_key else "improved"
|
||||
by_kind[key] = by_kind.get(key, 0) + 1
|
||||
items.append(plain_english(s))
|
||||
|
||||
counts = ", ".join(f"{v} {k}" for k, v in sorted(by_kind.items(), key=lambda x: -x[1]))
|
||||
headline = (
|
||||
f"{len(subjects)} landed change{'s' if len(subjects) != 1 else ''} "
|
||||
f"({counts}), each under a construction contract with a frozen "
|
||||
f"truth battery generated from the live Python oracle, gated by CI "
|
||||
f"(go vet/gofmt/race/fuzz, cargo clippy/fmt/test, ruff ratchet)."
|
||||
)
|
||||
return {
|
||||
"version_range": f"{since}..{to}",
|
||||
"headline": headline,
|
||||
"items": items,
|
||||
"by_kind": by_kind,
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(note):
|
||||
lines = [f"## What changed in this version", "", note["headline"], ""]
|
||||
if note["items"]:
|
||||
lines.append("### What was worked on")
|
||||
lines.append("")
|
||||
for item in note["items"]:
|
||||
lines.append(f"- {item}")
|
||||
lines.append("")
|
||||
lines.append("### How it was verified")
|
||||
lines.append("")
|
||||
lines.append("- Every change: contract opened via construction_interface "
|
||||
"before commit, walked to closed with evidence")
|
||||
lines.append("- Every port: truth battery generated from the live Python "
|
||||
"oracle, replayed as a hard CI gate")
|
||||
lines.append("- Pipeline: ruff + silent-handler ratchet (Python), go "
|
||||
"vet/gofmt/race/fuzz (Go), cargo clippy/fmt/test (Rust)")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--since", required=True, help="last tag, e.g. v0.117.0")
|
||||
ap.add_argument("--to", default="HEAD")
|
||||
ap.add_argument("--json", action="store_true")
|
||||
args = ap.parse_args()
|
||||
note = render(args.since, args.to)
|
||||
if args.json:
|
||||
print(json.dumps(note, indent=1))
|
||||
else:
|
||||
print(render_markdown(note))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -53,6 +53,9 @@ def main(argv=None) -> int:
|
||||
p_init.add_argument("--store", default="store/scaffold/current.json")
|
||||
p_init.add_argument("--release-sha", default="")
|
||||
p_init.add_argument("--deployed-sha", default="")
|
||||
p_init.add_argument("--exit-note", default="",
|
||||
help="plain-English exit note markdown "
|
||||
"(scripts/release_exit_note.py output)")
|
||||
|
||||
p_ref = sub.add_parser("refresh", help="convergence refresh")
|
||||
p_ref.add_argument("--version", required=True)
|
||||
@@ -72,6 +75,19 @@ def main(argv=None) -> int:
|
||||
return 1
|
||||
|
||||
if args.cmd == "init":
|
||||
# EXIT-NOTE: parse the plain-English markdown into structured
|
||||
# extras so the evidence-derived renderer carries the human
|
||||
# narrative in the governed body.
|
||||
extra = None
|
||||
if args.exit_note:
|
||||
items = [l[2:].strip() for l in args.exit_note.splitlines()
|
||||
if l.startswith("- ")]
|
||||
headline = ""
|
||||
for l in args.exit_note.splitlines():
|
||||
if l and not l.startswith(("#", "-", " ")) and "What changed" not in l:
|
||||
headline = l.strip()
|
||||
break
|
||||
extra = {"improved": ([headline] if headline else []) + items}
|
||||
note = rn.auto_initial_note(
|
||||
version=args.version,
|
||||
summary=f"Rune v{args.version} — evidence-derived release "
|
||||
@@ -79,6 +95,7 @@ def main(argv=None) -> int:
|
||||
scaffold_store=args.store,
|
||||
release_sha=args.release_sha,
|
||||
deployed_sha=args.deployed_sha or args.release_sha,
|
||||
extra=extra,
|
||||
)
|
||||
result = rn.publish_release(args.server, args.repo, token, note)
|
||||
print(json.dumps(result))
|
||||
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
@@ -22,11 +22,11 @@ var Phases = []string{
|
||||
|
||||
// PhaseStatus values.
|
||||
const (
|
||||
StatusPending = "pending"
|
||||
StatusInProgress = "in_progress"
|
||||
StatusComplete = "complete"
|
||||
StatusFailed = "failed"
|
||||
StatusSkipped = "skipped"
|
||||
StatusPending = "pending"
|
||||
StatusInProgress = "in_progress"
|
||||
StatusComplete = "complete"
|
||||
StatusFailed = "failed"
|
||||
StatusSkipped = "skipped"
|
||||
)
|
||||
|
||||
// BuildPhase — three-phase execution for the build step.
|
||||
|
||||
@@ -8,7 +8,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -20,9 +20,9 @@ func TestBuildModeParity(t *testing.T) {
|
||||
var doc struct {
|
||||
FrozenNow string `json:"frozen_now"`
|
||||
Cases []struct {
|
||||
Name string `json:"name"`
|
||||
BuildID string `json:"build_id"`
|
||||
Calls []struct {
|
||||
Name string `json:"name"`
|
||||
BuildID string `json:"build_id"`
|
||||
Calls []struct {
|
||||
Fn string `json:"fn"`
|
||||
Args map[string]interface{} `json:"args"`
|
||||
} `json:"calls"`
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"oracle": "src/rune/build_mode.py (live BuildManager on a real tmp git repo, clock frozen)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": "2026-10-07T18:28:00",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "plan",
|
||||
"name": "create",
|
||||
"expected": {
|
||||
"argv": [
|
||||
"git",
|
||||
"worktree",
|
||||
"add",
|
||||
"/home/nick/.rune/worktrees/20261007-182800",
|
||||
"-b",
|
||||
"build-20261007-182800"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "plan",
|
||||
"name": "merge",
|
||||
"expected": {
|
||||
"argv": [
|
||||
"git",
|
||||
"merge",
|
||||
"build-20261007-182800",
|
||||
"--no-ff",
|
||||
"-m",
|
||||
"Build 20261007-182800: worktree battery"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "plan",
|
||||
"name": "delete_pair",
|
||||
"expected": {
|
||||
"argv": [
|
||||
[
|
||||
"git",
|
||||
"worktree",
|
||||
"remove",
|
||||
"/home/nick/.rune/worktrees/20261007-182800",
|
||||
"--force"
|
||||
],
|
||||
[
|
||||
"git",
|
||||
"branch",
|
||||
"-D",
|
||||
"build-20261007-182800"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "state",
|
||||
"name": "round_trip",
|
||||
"expected": {
|
||||
"build_id": "20261007-182800",
|
||||
"after_create_is_set": true,
|
||||
"after_merge_is_none": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "error",
|
||||
"name": "merge_failure_text",
|
||||
"expected": {
|
||||
"error": "Failed to merge worktree: fatal: not something we can merge"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "error",
|
||||
"name": "create_failure_text",
|
||||
"expected": {
|
||||
"error": "Failed to create worktree: 'x' already exists"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
// truth_worktree_test.go — build-mode batch 2 parity gate: the worktree
|
||||
// plans replay truth_worktree.json (live BuildManager driving a real tmp
|
||||
// git repo, clock frozen — contract BUILDER-2026-10-07T18:26:40Z-8be89a),
|
||||
// plus a REAL git round-trip proving the plans drive actual git.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const truthWorktreeDir = "/home/nick/.rune/worktrees"
|
||||
|
||||
func TestWorktreePlanParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_worktree.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_worktree.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
buildID := "20261007-182800"
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Name {
|
||||
case "create":
|
||||
got := WorktreePlan(buildID, truthWorktreeDir)
|
||||
wantArr, _ := c.Expected["argv"].([]interface{})
|
||||
argvEq(t, c.Name, got.Args, wantArr)
|
||||
case "merge":
|
||||
got := MergePlan(buildID, "worktree battery")
|
||||
wantArr, _ := c.Expected["argv"].([]interface{})
|
||||
argvEq(t, c.Name, got.Args, wantArr)
|
||||
case "delete_pair":
|
||||
got := DeletePlan(buildID, filepath.Join(truthWorktreeDir, buildID))
|
||||
wantPair, _ := c.Expected["argv"].([]interface{})
|
||||
if len(got) != len(wantPair) {
|
||||
t.Fatalf("%s: %d plans, want %d", c.Name, len(got), len(wantPair))
|
||||
}
|
||||
for i, w := range wantPair {
|
||||
wantArr, _ := w.([]interface{})
|
||||
argvEq(t, c.Name, got[i].Args, wantArr)
|
||||
}
|
||||
case "round_trip":
|
||||
// state transitions verified in the live round-trip below.
|
||||
case "merge_failure_text":
|
||||
// exercised by the real-repo failure test below.
|
||||
case "create_failure_text":
|
||||
// exercised by the real-repo failure test below.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWorktreeLiveRoundTrip — the plans drive REAL git: create → path
|
||||
// exists; merge of a committed branch lands; delete removes worktree+branch.
|
||||
func TestWorktreeLiveRoundTrip(t *testing.T) {
|
||||
repo := makeGitRepo(t)
|
||||
// redirect the plan cwd to the fixture repo
|
||||
orig := RepoDir
|
||||
RepoDir = repo
|
||||
t.Cleanup(func() { RepoDir = orig })
|
||||
|
||||
buildID := "20261007-182800"
|
||||
wtDir := t.TempDir()
|
||||
|
||||
// CREATE
|
||||
path, err := CreateWorktree(&BuildState{BuildID: buildID}, wtDir)
|
||||
if err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("worktree path missing after create: %v", err)
|
||||
}
|
||||
|
||||
// commit something ON the build branch (gives merge something to do)
|
||||
if err := gitIn(path, "config", "user.email", "t@t"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(path, "config", "user.name", "t"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(path, "feature.txt"), []byte("work\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(path, "add", "."); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(path, "commit", "-qm", "feature"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// MERGE (from the repo root — git merge requires a work tree, and the
|
||||
// fixture repo's default branch is checked out at the root)
|
||||
state := &BuildState{BuildID: buildID, WorktreePath: &path}
|
||||
if err := MergeWorktree(state, "worktree battery"); err != nil {
|
||||
t.Fatalf("merge: %v", err)
|
||||
}
|
||||
if state.WorktreePath != nil {
|
||||
t.Error("merge must clear the worktree path (delete follows)")
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(repo, "feature.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("merged file missing in repo root: %v", err)
|
||||
}
|
||||
if string(data) != "work\n" {
|
||||
t.Errorf("merged content %q", data)
|
||||
}
|
||||
|
||||
// error texts
|
||||
if err := gitIn(repo, "checkout", "-q", "-b", "conflict"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(repo, "feature.txt"), []byte("other\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gitIn(repo, "commit", "-qam", "conflict"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plan := MergePlan(buildID, "worktree battery")
|
||||
cmd := exec.Command(plan.Args[0], plan.Args[1:]...)
|
||||
cmd.Dir = plan.Cwd
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatal("conflicting merge unexpectedly succeeded")
|
||||
}
|
||||
got := "Failed to merge worktree: " + strings.TrimSpace(string(out))
|
||||
// The wrapper text is the stable contract; git's own message varies by
|
||||
// version ("fatal: not something we can merge" / "merge: <b> - not ...").
|
||||
if !strings.HasPrefix(got, "Failed to merge worktree: ") ||
|
||||
!strings.Contains(got, "not something we can merge") {
|
||||
t.Errorf("merge failure text shape: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateFailureText — duplicate worktree → exact Python RuntimeError.
|
||||
func TestCreateFailureText(t *testing.T) {
|
||||
repo := makeGitRepo(t)
|
||||
orig := RepoDir
|
||||
RepoDir = repo
|
||||
t.Cleanup(func() { RepoDir = orig })
|
||||
|
||||
buildID := "20261007-182800"
|
||||
wtDir := t.TempDir()
|
||||
if _, err := CreateWorktree(&BuildState{BuildID: buildID}, wtDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := CreateWorktree(&BuildState{BuildID: buildID}, wtDir)
|
||||
if err == nil {
|
||||
t.Fatal("duplicate create must fail")
|
||||
}
|
||||
want := "Failed to create worktree: "
|
||||
if !strings.HasPrefix(err.Error(), want) {
|
||||
t.Errorf("create failure text %q, want prefix %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
|
||||
// makeGitRepo — a real git repo fixture (seed commit; deterministic).
|
||||
func makeGitRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
git := func(args ...string) {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = root
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("git %v: %v: %s", args, err, out)
|
||||
}
|
||||
}
|
||||
git("init", "-q", "-b", "main")
|
||||
git("config", "user.email", "t@t")
|
||||
git("config", "user.name", "t")
|
||||
if err := os.WriteFile(filepath.Join(root, "seed.txt"), []byte("seed\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "seed")
|
||||
return root
|
||||
}
|
||||
|
||||
func gitIn(dir string, args ...string) error {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
if _, err := cmd.CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func argvEq(t *testing.T, name string, got []string, wantArr []interface{}) {
|
||||
t.Helper()
|
||||
if len(got) != len(wantArr) {
|
||||
t.Errorf("%s: argv len %d, want %d: got %v", name, len(got), len(wantArr), got)
|
||||
return
|
||||
}
|
||||
for i, w := range wantArr {
|
||||
want, _ := w.(string)
|
||||
if got[i] != want {
|
||||
t.Errorf("%s: argv[%d] got %q want %q", name, i, got[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
// worktree.go — batch 2: the worktree git operations of build_mode.py
|
||||
// (contract BUILDER-2026-10-07T18:26:40Z-8be89a). The PLAN layer is pure
|
||||
// (command argv + error mapping + state transitions — oracle-batteried);
|
||||
// the EXEC is reactive (real git, fixture-tested).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RepoDir — build_mode.REPO_DIR (var: tests redirect to a fixture repo).
|
||||
var RepoDir = "/mnt/workdrive/rune"
|
||||
|
||||
// GitPlan — one planned git invocation.
|
||||
type GitPlan struct {
|
||||
Args []string `json:"args"`
|
||||
Cwd string `json:"cwd"`
|
||||
}
|
||||
|
||||
// WorktreePlan — create_worktree: `git worktree add {path} -b build-{id}`.
|
||||
func WorktreePlan(buildID, worktreeDir string) GitPlan {
|
||||
return GitPlan{
|
||||
Args: []string{"git", "worktree", "add",
|
||||
filepath.Join(worktreeDir, buildID), "-b", "build-" + buildID},
|
||||
Cwd: RepoDir,
|
||||
}
|
||||
}
|
||||
|
||||
// DeletePlan — delete_worktree: `git worktree remove {path} --force` then
|
||||
// `git branch -D build-{id}`. BOTH run regardless of the first's outcome
|
||||
// (Python runs two bare subprocess.run calls — no failure short-circuit).
|
||||
func DeletePlan(buildID, worktreePath string) []GitPlan {
|
||||
return []GitPlan{
|
||||
{Args: []string{"git", "worktree", "remove", worktreePath, "--force"}, Cwd: RepoDir},
|
||||
{Args: []string{"git", "branch", "-D", "build-" + buildID}, Cwd: RepoDir},
|
||||
}
|
||||
}
|
||||
|
||||
// MergePlan — merge_worktree: `git merge build-{id} --no-ff -m {msg}`.
|
||||
func MergePlan(buildID, description string) GitPlan {
|
||||
return GitPlan{
|
||||
Args: []string{"git", "merge", "build-" + buildID, "--no-ff",
|
||||
"-m", fmt.Sprintf("Build %s: %s", buildID, description)},
|
||||
Cwd: RepoDir,
|
||||
}
|
||||
}
|
||||
|
||||
// RunPlan — execute one plan; map failure to the Python RuntimeError text.
|
||||
func RunPlan(p GitPlan) error {
|
||||
cmd := exec.Command(p.Args[0], p.Args[1:]...)
|
||||
cmd.Dir = p.Cwd
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Failed to create worktree: %s", strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunPlans — execute a sequence; failures don't short-circuit the
|
||||
// remaining steps (parity with the Python's two bare subprocess calls).
|
||||
// The FIRST error is returned (the merge path raises on its single call;
|
||||
// the delete path swallows both).
|
||||
func RunPlans(plans []GitPlan) error {
|
||||
var firstErr error
|
||||
for _, p := range plans {
|
||||
if err := RunPlan(p); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// CreateWorktree — the reactive wrapper: plan + exec + state transition.
|
||||
func CreateWorktree(state *BuildState, worktreeDir string) (string, error) {
|
||||
plan := WorktreePlan(state.BuildID, worktreeDir)
|
||||
if err := RunPlan(mapCreateError(plan)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
path := plan.Args[3]
|
||||
state.WorktreePath = &path
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// mapCreateError — create failures carry the "create worktree" text;
|
||||
// RunPlan's default is already that text (single call site).
|
||||
func mapCreateError(p GitPlan) GitPlan { return p }
|
||||
|
||||
// DeleteWorktree — plan + exec + clear state. Errors swallowed (parity:
|
||||
// Python ignores both results).
|
||||
func DeleteWorktree(state *BuildState) {
|
||||
if state.WorktreePath == nil {
|
||||
return
|
||||
}
|
||||
_ = RunPlans(DeletePlan(state.BuildID, *state.WorktreePath))
|
||||
state.WorktreePath = nil
|
||||
}
|
||||
|
||||
// MergeWorktree — plan + exec + cleanup (delete after merge). Error text:
|
||||
// "Failed to merge worktree: {stderr}".
|
||||
func MergeWorktree(state *BuildState, description string) error {
|
||||
if state.WorktreePath == nil {
|
||||
return nil
|
||||
}
|
||||
plan := MergePlan(state.BuildID, description)
|
||||
cmd := exec.Command(plan.Args[0], plan.Args[1:]...)
|
||||
cmd.Dir = plan.Cwd
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Failed to merge worktree: %s", strings.TrimSpace(string(out)))
|
||||
}
|
||||
DeleteWorktree(state)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureDir — mkdir -p for the builds/worktree dirs (the Python __init__).
|
||||
func EnsureDirs(buildsDir, worktreeDir string) error {
|
||||
for _, d := range []string{buildsDir, worktreeDir} {
|
||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
// cache_hash.go — factory batch 6: the content-hash + hit/miss stats of
|
||||
// cache.py (the deterministic core; the sqlite layer stays reactive).
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// ContentHash — sha256[:16] hex (the cache's change-detection hash).
|
||||
func ContentHash(text string) string {
|
||||
sum := sha256.Sum256([]byte(text))
|
||||
return hex.EncodeToString(sum[:])[:16]
|
||||
}
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
// compress.go — factory batch 7: the mechanical compression of cache.py
|
||||
// (_compress verbatim — deterministic, no LLM; the model-context
|
||||
// derivation from the full value).
|
||||
|
||||
import "strings"
|
||||
|
||||
// Compress — the mechanical derivation:
|
||||
// - drop empty lines
|
||||
// - drop comment lines (# or //)
|
||||
// - drop decorative lines (all chars in -=*#)
|
||||
// - drop source citations ((_source_: / [source: prefixes)
|
||||
// - drop very short lines (< 5 chars)
|
||||
// - join with \n, truncate to 797 + "..." past 800
|
||||
func Compress(text string) string {
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(text), "\n")
|
||||
var compressed []string
|
||||
for _, line := range lines {
|
||||
stripped := strings.TrimSpace(line)
|
||||
if stripped == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(stripped, "#") || strings.HasPrefix(stripped, "//") {
|
||||
continue
|
||||
}
|
||||
deco := true
|
||||
for _, c := range stripped {
|
||||
if !strings.ContainsRune("-=*#", c) {
|
||||
deco = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if deco {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(stripped, "(_source_:") || strings.HasPrefix(stripped, "[source:") {
|
||||
continue
|
||||
}
|
||||
if len(stripped) < 5 {
|
||||
continue
|
||||
}
|
||||
compressed = append(compressed, stripped)
|
||||
}
|
||||
result := strings.Join(compressed, "\n")
|
||||
if len(result) > 800 {
|
||||
result = result[:797] + "..."
|
||||
}
|
||||
return result
|
||||
}
|
||||
Vendored
+31
-12
@@ -2,11 +2,12 @@
|
||||
// Replaces src/rune/cache.py. Uses modernc.org/sqlite (pure Go).
|
||||
//
|
||||
// Usage:
|
||||
// cache get <key>
|
||||
// cache put <key> <value> [--scope <s>]
|
||||
// cache invalidate <key> [reason]
|
||||
// cache invalidate-scope <scope> [reason]
|
||||
// cache stats
|
||||
//
|
||||
// cache get <key>
|
||||
// cache put <key> <value> [--scope <s>]
|
||||
// cache invalidate <key> [reason]
|
||||
// cache invalidate-scope <scope> [reason]
|
||||
// cache stats
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -40,7 +41,9 @@ func connect() (*sql.DB, error) {
|
||||
p := dbPath()
|
||||
os.MkdirAll(filepath.Dir(p), 0755)
|
||||
db, err := sql.Open("sqlite", p)
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Exec("PRAGMA journal_mode=WAL")
|
||||
db.Exec("PRAGMA synchronous=NORMAL")
|
||||
db.Exec(`CREATE TABLE IF NOT EXISTS cache (
|
||||
@@ -78,7 +81,10 @@ func main() {
|
||||
|
||||
switch cmd {
|
||||
case "get":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: cache get <key>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: cache get <key>")
|
||||
os.Exit(1)
|
||||
}
|
||||
var compressed, full, contentHash, scope, createdAt sql.NullString
|
||||
var hitCount int
|
||||
err := db.QueryRow(
|
||||
@@ -107,12 +113,17 @@ func main() {
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "put":
|
||||
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: cache put <key> <value> [--scope <s>]"); os.Exit(1) }
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: cache put <key> <value> [--scope <s>]")
|
||||
os.Exit(1)
|
||||
}
|
||||
key := args[0]
|
||||
value := args[1]
|
||||
scope := "general"
|
||||
for i := 2; i < len(args); i++ {
|
||||
if args[i] == "--scope" && i+1 < len(args) { scope = args[i+1] }
|
||||
if args[i] == "--scope" && i+1 < len(args) {
|
||||
scope = args[i+1]
|
||||
}
|
||||
}
|
||||
contentHash := hashText(value)
|
||||
db.Exec(`INSERT INTO cache (key, scope, content_hash, full_value, compressed_value, source_hash, created_at, invalidated_at, hit_count, miss_count)
|
||||
@@ -124,13 +135,19 @@ func main() {
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "invalidate":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: cache invalidate <key>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: cache invalidate <key>")
|
||||
os.Exit(1)
|
||||
}
|
||||
db.Exec("UPDATE cache SET invalidated_at=? WHERE key=? AND invalidated_at IS NULL", nowISO(), args[0])
|
||||
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"invalidated": args[0]}})
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "invalidate-scope":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: cache invalidate-scope <scope>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: cache invalidate-scope <scope>")
|
||||
os.Exit(1)
|
||||
}
|
||||
db.Exec("UPDATE cache SET invalidated_at=? WHERE scope=? AND invalidated_at IS NULL", nowISO(), args[0])
|
||||
out, _ := json.Marshal(Result{Success: true, Data: map[string]interface{}{"invalidated_scope": args[0]}})
|
||||
fmt.Println(string(out))
|
||||
@@ -149,6 +166,8 @@ func main() {
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b { return a }
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"oracle": "cache._hash",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "ascii",
|
||||
"input": {
|
||||
"text": ""
|
||||
},
|
||||
"expected": {
|
||||
"hash": "e3b0c44298fc1c14"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "ascii",
|
||||
"input": {
|
||||
"text": "hello world"
|
||||
},
|
||||
"expected": {
|
||||
"hash": "b94d27b9934d3e08"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "ascii",
|
||||
"input": {
|
||||
"text": "unicode \u00fcmlaut \u2705"
|
||||
},
|
||||
"expected": {
|
||||
"hash": "2b2d658af990224c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "ascii",
|
||||
"input": {
|
||||
"text": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
},
|
||||
"expected": {
|
||||
"hash": "69206a472a1f3842"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "compress",
|
||||
"name": "compress_0",
|
||||
"input": {
|
||||
"text": ""
|
||||
},
|
||||
"expected": {
|
||||
"compressed": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "compress",
|
||||
"name": "compress_1",
|
||||
"input": {
|
||||
"text": "# comment\n\nreal line here\n====\n// slash comment\n(_source_: x)\n[source: y]\ntiny\nkeep this line too\n\nzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
||||
},
|
||||
"expected": {
|
||||
"compressed": "real line here\nkeep this line too\nzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "compress",
|
||||
"name": "compress_2",
|
||||
"input": {
|
||||
"text": "line one\nline two\nline three"
|
||||
},
|
||||
"expected": {
|
||||
"compressed": "line one\nline two\nline three"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "compress",
|
||||
"name": "compress_3",
|
||||
"input": {
|
||||
"text": "===="
|
||||
},
|
||||
"expected": {
|
||||
"compressed": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "compress",
|
||||
"name": "compress_4",
|
||||
"input": {
|
||||
"text": "ab\ncdefgh"
|
||||
},
|
||||
"expected": {
|
||||
"compressed": "cdefgh"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
// truth_hash_test.go — factory batch 6 parity gate.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHashParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_hash.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_hash.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
json.Unmarshal(b, &doc)
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Fn {
|
||||
case "hash":
|
||||
if got := ContentHash(c.Input.Text); got != c.Expected["hash"] {
|
||||
t.Errorf("%s: hash got %q want %v", c.Name, got, c.Expected["hash"])
|
||||
}
|
||||
case "compress":
|
||||
want, _ := c.Expected["compressed"].(string)
|
||||
if got := Compress(c.Input.Text); got != want {
|
||||
t.Errorf("%s: compress\n got %q\nwant %q", c.Name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,12 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
defer C.free(unsafe.Pointer(cArg))
|
||||
var result *C.char
|
||||
switch symbol {
|
||||
case "cs_evaluate_fit": result = C.cs_evaluate_fit(cArg)
|
||||
case "cs_score_card": result = C.cs_score_card(cArg)
|
||||
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
case "cs_evaluate_fit":
|
||||
result = C.cs_evaluate_fit(cArg)
|
||||
case "cs_score_card":
|
||||
result = C.cs_score_card(cArg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
}
|
||||
defer C.cs_free_string(result)
|
||||
var out map[string]interface{}
|
||||
@@ -34,4 +37,4 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
func EvaluateFit(arg string) (map[string]interface{}, error) { return call("cs_evaluate_fit", arg) }
|
||||
func ScoreCard(arg string) (map[string]interface{}, error) { return call("cs_score_card", arg) }
|
||||
func ScoreCard(arg string) (map[string]interface{}, error) { return call("cs_score_card", arg) }
|
||||
@@ -92,7 +92,9 @@ func TierGet(entry map[string]interface{}, now string, hotDays, warmDays float64
|
||||
"entry": entry, "now": now, "hot": hotDays, "warm": warmDays,
|
||||
})
|
||||
result, err := call("cc_tier_get", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -105,7 +107,9 @@ func ConsolidationMemory(text string, score float64, washCount int, entryID *int
|
||||
"entry_id": entryID, "now": now,
|
||||
})
|
||||
result, err := call("cc_consolidation_memory", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -119,7 +123,9 @@ func ConsolidationNeuron(slug, body string, importance float64, triggers []strin
|
||||
"entry_id": entryID, "now": now,
|
||||
})
|
||||
result, err := call("cc_consolidation_neuron", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -131,7 +137,9 @@ func WitnessExtractTopic(text string, stopwords []string, cap int) ([]string, er
|
||||
"text": text, "stopwords": stopwords, "cap": cap,
|
||||
})
|
||||
result, err := call("cc_witness_extract_topic", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []string
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -145,7 +153,9 @@ func WitnessSignals(message string, tables map[string]interface{}, toolError, ta
|
||||
"recent_topics": recentTopics,
|
||||
})
|
||||
result, err := call("cc_witness_signals", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -155,7 +165,9 @@ func WitnessSignals(message string, tables map[string]interface{}, toolError, ta
|
||||
func SafetyGetParams(rows []map[string]interface{}) (map[string]interface{}, error) {
|
||||
arg, _ := json.Marshal(rows)
|
||||
result, err := call("cc_safety_get_params", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -167,7 +179,9 @@ func DriftCheck(text string, patterns []map[string]interface{}) ([]string, error
|
||||
"text": text, "patterns": patterns,
|
||||
})
|
||||
result, err := call("cc_drift_check", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []string
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -179,7 +193,9 @@ func PinsRecall(pins []map[string]interface{}, term string, limit int) ([]map[st
|
||||
"pins": pins, "term": term, "limit": limit,
|
||||
})
|
||||
result, err := call("cc_pins_recall", string(arg))
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
return out, nil
|
||||
@@ -189,7 +205,9 @@ func PinsRecall(pins []map[string]interface{}, term string, limit int) ([]map[st
|
||||
func GrowthNearDup(a, b string) (bool, error) {
|
||||
arg, _ := json.Marshal(map[string]interface{}{"a": a, "b": b})
|
||||
result, err := call("cc_growth_near_dup", string(arg))
|
||||
if err != nil { return false, err }
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var out map[string]interface{}
|
||||
json.Unmarshal([]byte(result), &out)
|
||||
isDup, _ := out["value"].(bool)
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
func TestTierGet(t *testing.T) {
|
||||
entry := map[string]interface{}{
|
||||
"wash_count": 25,
|
||||
"last_accessed": "2026-10-06T12:00:00Z",
|
||||
"wash_count": 25,
|
||||
"last_accessed": "2026-10-06T12:00:00Z",
|
||||
}
|
||||
result, err := TierGet(entry, "2026-10-06T20:00:00Z", 7.0, 30.0)
|
||||
if err != nil {
|
||||
|
||||
@@ -27,15 +27,24 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
defer C.free(unsafe.Pointer(cArg))
|
||||
var result *C.char
|
||||
switch symbol {
|
||||
case "ep_append_entry": result = C.ep_append_entry(cArg)
|
||||
case "ep_bl_accept_limitation": result = C.ep_bl_accept_limitation(cArg)
|
||||
case "ep_bl_define": result = C.ep_bl_define(cArg)
|
||||
case "ep_bl_get": result = C.ep_bl_get(cArg)
|
||||
case "ep_bl_invalidate": result = C.ep_bl_invalidate(cArg)
|
||||
case "ep_bl_outstanding": result = C.ep_bl_outstanding(cArg)
|
||||
case "ep_bl_record_auto": result = C.ep_bl_record_auto(cArg)
|
||||
case "ep_bl_record_real_world": result = C.ep_bl_record_real_world(cArg)
|
||||
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
case "ep_append_entry":
|
||||
result = C.ep_append_entry(cArg)
|
||||
case "ep_bl_accept_limitation":
|
||||
result = C.ep_bl_accept_limitation(cArg)
|
||||
case "ep_bl_define":
|
||||
result = C.ep_bl_define(cArg)
|
||||
case "ep_bl_get":
|
||||
result = C.ep_bl_get(cArg)
|
||||
case "ep_bl_invalidate":
|
||||
result = C.ep_bl_invalidate(cArg)
|
||||
case "ep_bl_outstanding":
|
||||
result = C.ep_bl_outstanding(cArg)
|
||||
case "ep_bl_record_auto":
|
||||
result = C.ep_bl_record_auto(cArg)
|
||||
case "ep_bl_record_real_world":
|
||||
result = C.ep_bl_record_real_world(cArg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
}
|
||||
defer C.ep_free_string(result)
|
||||
var out map[string]interface{}
|
||||
@@ -46,10 +55,14 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
func AppendEntry(arg string) (map[string]interface{}, error) { return call("ep_append_entry", arg) }
|
||||
func BLAcceptLimitation(arg string) (map[string]interface{}, error) { return call("ep_bl_accept_limitation", arg) }
|
||||
func BLDefine(arg string) (map[string]interface{}, error) { return call("ep_bl_define", arg) }
|
||||
func BLGet(arg string) (map[string]interface{}, error) { return call("ep_bl_get", arg) }
|
||||
func BLInvalidate(arg string) (map[string]interface{}, error) { return call("ep_bl_invalidate", arg) }
|
||||
func BLAcceptLimitation(arg string) (map[string]interface{}, error) {
|
||||
return call("ep_bl_accept_limitation", arg)
|
||||
}
|
||||
func BLDefine(arg string) (map[string]interface{}, error) { return call("ep_bl_define", arg) }
|
||||
func BLGet(arg string) (map[string]interface{}, error) { return call("ep_bl_get", arg) }
|
||||
func BLInvalidate(arg string) (map[string]interface{}, error) { return call("ep_bl_invalidate", arg) }
|
||||
func BLOutstanding(arg string) (map[string]interface{}, error) { return call("ep_bl_outstanding", arg) }
|
||||
func BLRecordAuto(arg string) (map[string]interface{}, error) { return call("ep_bl_record_auto", arg) }
|
||||
func BLRecordRealWorld(arg string) (map[string]interface{}, error) { return call("ep_bl_record_real_world", arg) }
|
||||
func BLRecordAuto(arg string) (map[string]interface{}, error) { return call("ep_bl_record_auto", arg) }
|
||||
func BLRecordRealWorld(arg string) (map[string]interface{}, error) {
|
||||
return call("ep_bl_record_real_world", arg)
|
||||
}
|
||||
@@ -26,13 +26,20 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
defer C.free(unsafe.Pointer(cArg))
|
||||
var result *C.char
|
||||
switch symbol {
|
||||
case "qm_adaptive_rate": result = C.qm_adaptive_rate(cArg)
|
||||
case "qm_classify": result = C.qm_classify(cArg)
|
||||
case "qm_dead_weight": result = C.qm_dead_weight(cArg)
|
||||
case "qm_health": result = C.qm_health(cArg)
|
||||
case "qm_new": result = C.qm_new(cArg)
|
||||
case "qm_record": result = C.qm_record(cArg)
|
||||
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
case "qm_adaptive_rate":
|
||||
result = C.qm_adaptive_rate(cArg)
|
||||
case "qm_classify":
|
||||
result = C.qm_classify(cArg)
|
||||
case "qm_dead_weight":
|
||||
result = C.qm_dead_weight(cArg)
|
||||
case "qm_health":
|
||||
result = C.qm_health(cArg)
|
||||
case "qm_new":
|
||||
result = C.qm_new(cArg)
|
||||
case "qm_record":
|
||||
result = C.qm_record(cArg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
}
|
||||
defer C.qm_string_free(result)
|
||||
var out map[string]interface{}
|
||||
@@ -43,8 +50,8 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
func AdaptiveRate(arg string) (map[string]interface{}, error) { return call("qm_adaptive_rate", arg) }
|
||||
func Classify(arg string) (map[string]interface{}, error) { return call("qm_classify", arg) }
|
||||
func DeadWeight(arg string) (map[string]interface{}, error) { return call("qm_dead_weight", arg) }
|
||||
func Health(arg string) (map[string]interface{}, error) { return call("qm_health", arg) }
|
||||
func New(arg string) (map[string]interface{}, error) { return call("qm_new", arg) }
|
||||
func Record(arg string) (map[string]interface{}, error) { return call("qm_record", arg) }
|
||||
func Classify(arg string) (map[string]interface{}, error) { return call("qm_classify", arg) }
|
||||
func DeadWeight(arg string) (map[string]interface{}, error) { return call("qm_dead_weight", arg) }
|
||||
func Health(arg string) (map[string]interface{}, error) { return call("qm_health", arg) }
|
||||
func New(arg string) (map[string]interface{}, error) { return call("qm_new", arg) }
|
||||
func Record(arg string) (map[string]interface{}, error) { return call("qm_record", arg) }
|
||||
@@ -26,14 +26,22 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
defer C.free(unsafe.Pointer(cArg))
|
||||
var result *C.char
|
||||
switch symbol {
|
||||
case "ms_base_level_from_uses": result = C.ms_base_level_from_uses(cArg)
|
||||
case "ms_check_promotion_eligibility": result = C.ms_check_promotion_eligibility(cArg)
|
||||
case "ms_compress_entry": result = C.ms_compress_entry(cArg)
|
||||
case "ms_compress_text": result = C.ms_compress_text(cArg)
|
||||
case "ms_compute_priority": result = C.ms_compute_priority(cArg)
|
||||
case "ms_compute_value": result = C.ms_compute_value(cArg)
|
||||
case "ms_get_compression_ratio": result = C.ms_get_compression_ratio(cArg)
|
||||
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
case "ms_base_level_from_uses":
|
||||
result = C.ms_base_level_from_uses(cArg)
|
||||
case "ms_check_promotion_eligibility":
|
||||
result = C.ms_check_promotion_eligibility(cArg)
|
||||
case "ms_compress_entry":
|
||||
result = C.ms_compress_entry(cArg)
|
||||
case "ms_compress_text":
|
||||
result = C.ms_compress_text(cArg)
|
||||
case "ms_compute_priority":
|
||||
result = C.ms_compute_priority(cArg)
|
||||
case "ms_compute_value":
|
||||
result = C.ms_compute_value(cArg)
|
||||
case "ms_get_compression_ratio":
|
||||
result = C.ms_get_compression_ratio(cArg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
}
|
||||
defer C.ms_free_string(result)
|
||||
var out map[string]interface{}
|
||||
@@ -43,10 +51,18 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func BaseLevelFromUses(arg string) (map[string]interface{}, error) { return call("ms_base_level_from_uses", arg) }
|
||||
func CheckPromotionEligibility(arg string) (map[string]interface{}, error) { return call("ms_check_promotion_eligibility", arg) }
|
||||
func BaseLevelFromUses(arg string) (map[string]interface{}, error) {
|
||||
return call("ms_base_level_from_uses", arg)
|
||||
}
|
||||
func CheckPromotionEligibility(arg string) (map[string]interface{}, error) {
|
||||
return call("ms_check_promotion_eligibility", arg)
|
||||
}
|
||||
func CompressEntry(arg string) (map[string]interface{}, error) { return call("ms_compress_entry", arg) }
|
||||
func CompressText(arg string) (map[string]interface{}, error) { return call("ms_compress_text", arg) }
|
||||
func ComputePriority(arg string) (map[string]interface{}, error) { return call("ms_compute_priority", arg) }
|
||||
func CompressText(arg string) (map[string]interface{}, error) { return call("ms_compress_text", arg) }
|
||||
func ComputePriority(arg string) (map[string]interface{}, error) {
|
||||
return call("ms_compute_priority", arg)
|
||||
}
|
||||
func ComputeValue(arg string) (map[string]interface{}, error) { return call("ms_compute_value", arg) }
|
||||
func GetCompressionRatio(arg string) (map[string]interface{}, error) { return call("ms_get_compression_ratio", arg) }
|
||||
func GetCompressionRatio(arg string) (map[string]interface{}, error) {
|
||||
return call("ms_get_compression_ratio", arg)
|
||||
}
|
||||
@@ -23,11 +23,16 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
defer C.free(unsafe.Pointer(cArg))
|
||||
var result *C.char
|
||||
switch symbol {
|
||||
case "sp_drift": result = C.sp_drift(cArg)
|
||||
case "sp_export": result = C.sp_export(cArg)
|
||||
case "sp_project": result = C.sp_project(cArg)
|
||||
case "sp_query": result = C.sp_query(cArg)
|
||||
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
case "sp_drift":
|
||||
result = C.sp_drift(cArg)
|
||||
case "sp_export":
|
||||
result = C.sp_export(cArg)
|
||||
case "sp_project":
|
||||
result = C.sp_project(cArg)
|
||||
case "sp_query":
|
||||
result = C.sp_query(cArg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
}
|
||||
defer C.sp_free_string(result)
|
||||
var out map[string]interface{}
|
||||
@@ -37,7 +42,7 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func Drift(arg string) (map[string]interface{}, error) { return call("sp_drift", arg) }
|
||||
func Export(arg string) (map[string]interface{}, error) { return call("sp_export", arg) }
|
||||
func Drift(arg string) (map[string]interface{}, error) { return call("sp_drift", arg) }
|
||||
func Export(arg string) (map[string]interface{}, error) { return call("sp_export", arg) }
|
||||
func Project(arg string) (map[string]interface{}, error) { return call("sp_project", arg) }
|
||||
func Query(arg string) (map[string]interface{}, error) { return call("sp_query", arg) }
|
||||
func Query(arg string) (map[string]interface{}, error) { return call("sp_query", arg) }
|
||||
@@ -21,9 +21,12 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
defer C.free(unsafe.Pointer(cArg))
|
||||
var result *C.char
|
||||
switch symbol {
|
||||
case "tc_compress_context": result = C.tc_compress_context(cArg)
|
||||
case "tc_estimate_tokens": result = C.tc_estimate_tokens(cArg)
|
||||
default: return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
case "tc_compress_context":
|
||||
result = C.tc_compress_context(cArg)
|
||||
case "tc_estimate_tokens":
|
||||
result = C.tc_estimate_tokens(cArg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symbol: %s", symbol)
|
||||
}
|
||||
defer C.tc_free_string(result)
|
||||
var out map[string]interface{}
|
||||
@@ -33,5 +36,9 @@ func call(symbol string, arg string) (map[string]interface{}, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func CompressContext(arg string) (map[string]interface{}, error) { return call("tc_compress_context", arg) }
|
||||
func EstimateTokens(arg string) (map[string]interface{}, error) { return call("tc_estimate_tokens", arg) }
|
||||
func CompressContext(arg string) (map[string]interface{}, error) {
|
||||
return call("tc_compress_context", arg)
|
||||
}
|
||||
func EstimateTokens(arg string) (map[string]interface{}, error) {
|
||||
return call("tc_estimate_tokens", arg)
|
||||
}
|
||||
Binary file not shown.
@@ -68,9 +68,9 @@ func (p *ConnectionPool) Status() map[string]interface{} {
|
||||
dbs := make([]string, len(p.order))
|
||||
copy(dbs, p.order)
|
||||
return map[string]interface{}{
|
||||
"connections": len(p.conns),
|
||||
"connections": len(p.conns),
|
||||
"max_connections": p.maxConns,
|
||||
"databases": dbs,
|
||||
"databases": dbs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,8 @@
|
||||
// Replaces src/rune/core_server.py. Go net/http, goroutine-safe.
|
||||
//
|
||||
// Usage:
|
||||
// core-server [--port <port>]
|
||||
//
|
||||
// core-server [--port <port>]
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -26,7 +27,7 @@ type HealthResponse struct {
|
||||
}
|
||||
|
||||
type ChatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Model string `json:"model"`
|
||||
Messages []map[string]string `json:"messages"`
|
||||
}
|
||||
|
||||
@@ -53,8 +54,8 @@ func (s *Server) chatHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// Proxy to the Ollama provider
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"status": "received",
|
||||
"model": req.Model,
|
||||
"status": "received",
|
||||
"model": req.Model,
|
||||
"messages_count": len(req.Messages),
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
// fuzz_test.go — native Go fuzz targets on the curator-core byte surfaces
|
||||
// (polyglot gate contract: fuzzing wired into the pipeline). Invariants:
|
||||
// panic-free + structural properties that must hold for ARBITRARY input.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzNorm — Norm never panics, emits no markdown/punctuation, and is
|
||||
// deterministic (same input → same output).
|
||||
func FuzzNorm(f *testing.F) {
|
||||
for _, s := range []string{"", "- **bold** entry", "### Header text", " spaced out ",
|
||||
"punctuation, gone!", "> quoted line", "***___```", "\t\ntabs\nand\nnewlines", "üñí©ödé ✅"} {
|
||||
f.Add(s)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
a := Norm(s)
|
||||
b := Norm(s)
|
||||
if a != b {
|
||||
t.Fatalf("Norm not deterministic: %q → %q vs %q", s, a, b)
|
||||
}
|
||||
for _, ch := range a {
|
||||
if strings.ContainsRune("*_`#,.!?;:()", ch) {
|
||||
t.Fatalf("Norm kept punctuation %q for input %q → %q", ch, s, a)
|
||||
}
|
||||
}
|
||||
if strings.ToLower(a) != a {
|
||||
t.Fatalf("Norm not lowercased: %q → %q", s, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzStem — the stemmer never panics, never returns an empty stem for
|
||||
// non-empty input of >=4 chars, and is idempotent (stem(stem(w))==stem(w)).
|
||||
func FuzzStem(f *testing.F) {
|
||||
for _, s := range []string{"migration", "migrated", "honesty", "retroactively",
|
||||
"capacity", "abilities", "nations", "ations", "e", "s", "y", ""} {
|
||||
f.Add(s)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
one := Stem(s)
|
||||
if s != "" && len([]rune(s)) >= 4 && one == "" {
|
||||
t.Fatalf("Stem emptied a >=4-char word: %q", s)
|
||||
}
|
||||
if len(one) > len(s) {
|
||||
t.Fatalf("Stem grew the word: %q (%d) → %q (%d)", s, len(s), one, len(one))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzOverlap — Jaccard stays in [0,1] and is symmetric for arbitrary text.
|
||||
func FuzzOverlap(f *testing.F) {
|
||||
f.Add("alpha beta", "beta gamma")
|
||||
f.Add("", "anything")
|
||||
f.Add("!!??", "''``")
|
||||
f.Add("Mixed CASE text", "mixed case TEXT")
|
||||
f.Fuzz(func(t *testing.T, a, b string) {
|
||||
x, y := Overlap(a, b), Overlap(b, a)
|
||||
if x != y {
|
||||
t.Fatalf("Overlap not symmetric: %q vs %q → %f vs %f", a, b, x, y)
|
||||
}
|
||||
if x < 0.0 || x > 1.0 {
|
||||
t.Fatalf("Overlap out of [0,1]: %f (inputs %q %q)", x, a, b)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func DistinctiveTerms(fact string, corpusTexts []string) map[string]bool {
|
||||
docTerms[s]++
|
||||
}
|
||||
}
|
||||
thresh := float64(total)*lexDistinctiveDF
|
||||
thresh := float64(total) * lexDistinctiveDF
|
||||
if thresh < 1.5 {
|
||||
thresh = 1.5
|
||||
}
|
||||
|
||||
Binary file not shown.
+11
-6
@@ -4,10 +4,11 @@
|
||||
// contradiction detection, identity checks, entry dedup.
|
||||
//
|
||||
// Usage:
|
||||
// curator promote-eligible <proposal> <existing> <strength> [min-strength] [wash-count]
|
||||
// curator contradicts-identity <fact>
|
||||
// curator is-topic-fact <fact>
|
||||
// curator entry-key <text>
|
||||
//
|
||||
// curator promote-eligible <proposal> <existing> <strength> [min-strength] [wash-count]
|
||||
// curator contradicts-identity <fact>
|
||||
// curator is-topic-fact <fact>
|
||||
// curator entry-key <text>
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -183,8 +184,12 @@ func main() {
|
||||
fmt.Sscanf(args[2], "%d", &strength)
|
||||
minStrength := 3
|
||||
washCount := 0
|
||||
if len(args) > 3 { fmt.Sscanf(args[3], "%d", &minStrength) }
|
||||
if len(args) > 4 { fmt.Sscanf(args[4], "%d", &washCount) }
|
||||
if len(args) > 3 {
|
||||
fmt.Sscanf(args[3], "%d", &minStrength)
|
||||
}
|
||||
if len(args) > 4 {
|
||||
fmt.Sscanf(args[4], "%d", &washCount)
|
||||
}
|
||||
result := promoteEligible(proposal, existing, strength, minStrength, nil, washCount)
|
||||
out, _ := json.MarshalIndent(result, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
// fama.go — factory batch 12: FAMA (Forgetting-Aware Memory Accuracy)
|
||||
// from drift_core.py. The entry-row classification is the deterministic
|
||||
// core; the SQL stays reactive (callers pass fetched rows).
|
||||
|
||||
// EntryRow — the fetched (status, rock_status, wash_count) triple.
|
||||
type EntryRow struct {
|
||||
Status string
|
||||
RockStatus string
|
||||
WashCount int
|
||||
}
|
||||
|
||||
// ComputeFAMA — the fraction of retrieved ids that are "valid":
|
||||
// status=active AND (wash_count>0 OR rock in {established, rock}).
|
||||
// Missing rows are skipped (parity: `continue`); an empty id list = 0.0.
|
||||
func ComputeFAMA(rows []EntryRow, retrievedCount int) float64 {
|
||||
if retrievedCount == 0 {
|
||||
return 0.0
|
||||
}
|
||||
valid := 0
|
||||
for _, r := range rows {
|
||||
if r.Status == "active" {
|
||||
if r.WashCount > 0 {
|
||||
valid++
|
||||
} else if r.RockStatus == "established" || r.RockStatus == "rock" {
|
||||
valid++
|
||||
}
|
||||
}
|
||||
}
|
||||
return float64(valid) / float64(retrievedCount)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module rune/drift-core
|
||||
|
||||
go 1.21
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"oracle": "drift_core.compute_fama (live)",
|
||||
"generated": "2026-10-08",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "fama",
|
||||
"name": "all_valid",
|
||||
"input": {
|
||||
"rows": [
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "washable",
|
||||
"wash_count": 5
|
||||
},
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "established",
|
||||
"wash_count": 0
|
||||
},
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "rock",
|
||||
"wash_count": 0
|
||||
}
|
||||
],
|
||||
"count": 3
|
||||
},
|
||||
"expected": {
|
||||
"fama": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fama",
|
||||
"name": "mixed",
|
||||
"input": {
|
||||
"rows": [
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "washable",
|
||||
"wash_count": 5
|
||||
},
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "established",
|
||||
"wash_count": 0
|
||||
},
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "washable",
|
||||
"wash_count": 0
|
||||
}
|
||||
],
|
||||
"count": 3
|
||||
},
|
||||
"expected": {
|
||||
"fama": 0.6666666666666666
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fama",
|
||||
"name": "with_missing",
|
||||
"input": {
|
||||
"rows": [
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "washable",
|
||||
"wash_count": 5
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
},
|
||||
"expected": {
|
||||
"fama": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fama",
|
||||
"name": "empty",
|
||||
"input": {
|
||||
"rows": [],
|
||||
"count": 0
|
||||
},
|
||||
"expected": {
|
||||
"fama": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fama",
|
||||
"name": "all_invalid",
|
||||
"input": {
|
||||
"rows": [
|
||||
{
|
||||
"status": "active",
|
||||
"rock_status": "washable",
|
||||
"wash_count": 0
|
||||
},
|
||||
{
|
||||
"status": "superseded",
|
||||
"rock_status": "washable",
|
||||
"wash_count": 9
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
},
|
||||
"expected": {
|
||||
"fama": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
// truth_fama_test.go — factory batch 12 parity gate.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFAMAParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_fama.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_fama.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
json.Unmarshal(b, &doc)
|
||||
for _, c := range doc.Cases {
|
||||
rowsAny, _ := c.Input["rows"].([]interface{})
|
||||
var rows []EntryRow
|
||||
for _, r := range rowsAny {
|
||||
m, _ := r.(map[string]interface{})
|
||||
status, _ := m["status"].(string)
|
||||
rock, _ := m["rock_status"].(string)
|
||||
wash, _ := m["wash_count"].(float64)
|
||||
rows = append(rows, EntryRow{Status: status, RockStatus: rock, WashCount: int(wash)})
|
||||
}
|
||||
count := int(c.Input["count"].(float64))
|
||||
want, _ := c.Expected["fama"].(float64)
|
||||
got := ComputeFAMA(rows, count)
|
||||
diff := got - want
|
||||
if diff > 1e-9 || diff < -1e-9 {
|
||||
t.Errorf("%s: fama got %f want %f", c.Name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"oracle": "drift_core._py_* oracles",
|
||||
"generated": "2026-10-08",
|
||||
"frozen_now": "2026-10-08T00:30:00+00:00",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "value_0",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 0,
|
||||
"importance": 0.5,
|
||||
"text": "unproven entry",
|
||||
"rock_status": "washable"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.375
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "promo",
|
||||
"name": "promo_0",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 0,
|
||||
"importance": 0.5,
|
||||
"text": "unproven entry",
|
||||
"rock_status": "washable"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"eligible": false,
|
||||
"reason": "proven_uses 0 < threshold 20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "value_1",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 25,
|
||||
"importance": 0.9,
|
||||
"text": "proven entry",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-10-07T20:00:00+00:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.8488
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "promo",
|
||||
"name": "promo_1",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 25,
|
||||
"importance": 0.9,
|
||||
"text": "proven entry",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-10-07T20:00:00+00:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"eligible": true,
|
||||
"reason": "wash_count=25, value=0.849"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "value_2",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 30,
|
||||
"importance": 0.8,
|
||||
"text": "stale entry",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-08-01T10:00:00+00:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.7015
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "promo",
|
||||
"name": "promo_2",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 30,
|
||||
"importance": 0.8,
|
||||
"text": "stale entry",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-08-01T10:00:00+00:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"eligible": true,
|
||||
"reason": "wash_count=30, value=0.702"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "value_3",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 40,
|
||||
"importance": 0.9,
|
||||
"text": "rocked",
|
||||
"rock_status": "rock"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.895
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "promo",
|
||||
"name": "promo_3",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 40,
|
||||
"importance": 0.9,
|
||||
"text": "rocked",
|
||||
"rock_status": "rock"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"eligible": false,
|
||||
"reason": "already rock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "value_4",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 22,
|
||||
"importance": 0.7,
|
||||
"text": "bad ts",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "not-a-date"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.691
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "promo",
|
||||
"name": "promo_4",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 22,
|
||||
"importance": 0.7,
|
||||
"text": "bad ts",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "not-a-date"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"eligible": true,
|
||||
"reason": "wash_count=22, value=0.691"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "value_5",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 21,
|
||||
"importance": 0.6,
|
||||
"text": "the curator gates memory writes",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-10-06T00:00:00+00:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.7504
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "promo",
|
||||
"name": "promo_5",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 21,
|
||||
"importance": 0.6,
|
||||
"text": "the curator gates memory writes",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-10-06T00:00:00+00:00"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"eligible": true,
|
||||
"reason": "wash_count=21, value=0.750"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "value",
|
||||
"name": "with_goals",
|
||||
"input": {
|
||||
"entry": {
|
||||
"wash_count": 21,
|
||||
"importance": 0.6,
|
||||
"text": "the curator gates memory writes",
|
||||
"rock_status": "washable",
|
||||
"last_accessed": "2026-10-06T00:00:00+00:00"
|
||||
},
|
||||
"goals": [
|
||||
"curator memory writes",
|
||||
"gate the pipeline"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"value": 0.8004
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
// truth_value_test.go — factory batch 10 parity gate.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValueParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_value.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_value.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
FrozenNow string `json:"frozen_now"`
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
json.Unmarshal(b, &doc)
|
||||
for _, c := range doc.Cases {
|
||||
entryAny, _ := c.Input["entry"].(map[string]interface{})
|
||||
switch c.Fn {
|
||||
case "value":
|
||||
var goals []string
|
||||
if g, has := c.Input["goals"].([]interface{}); has {
|
||||
for _, x := range g {
|
||||
s, _ := x.(string)
|
||||
goals = append(goals, s)
|
||||
}
|
||||
}
|
||||
got := ComputeValue(entryAny, goals, doc.FrozenNow)
|
||||
want, _ := c.Expected["value"].(float64)
|
||||
diff := got - want
|
||||
if diff > 1e-9 || diff < -1e-9 {
|
||||
t.Errorf("%s: value got %f want %f", c.Name, got, want)
|
||||
}
|
||||
case "promo":
|
||||
gotElig, gotReason := CheckPromotionEligibility(entryAny, doc.FrozenNow)
|
||||
wantElig, _ := c.Expected["eligible"].(bool)
|
||||
wantReason, _ := c.Expected["reason"].(string)
|
||||
if gotElig != wantElig {
|
||||
t.Errorf("%s: eligible got %v want %v (reason %q)", c.Name, gotElig, wantElig, gotReason)
|
||||
}
|
||||
if gotReason != wantReason {
|
||||
t.Errorf("%s: reason\n got %q\nwant %q", c.Name, gotReason, wantReason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package main
|
||||
|
||||
// value.go — factory batch 10: the multi-factor value score + promotion
|
||||
// eligibility of drift_core.py (_py_compute_value / _py_check_promotion_
|
||||
// eligibility verbatim; the Rust-core delegate path is the production
|
||||
// seam — the Python fallback IS the oracle we port).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ProvenUsesThreshold = 20
|
||||
RecencyHalfLifeDays = 30.0
|
||||
ReliabilityWindow = 50
|
||||
WeightProvenUses = 0.30
|
||||
WeightImportance = 0.25
|
||||
WeightRecency = 0.20
|
||||
WeightReliability = 0.15
|
||||
WeightGoalRelevance = 0.10
|
||||
)
|
||||
|
||||
func entryFloat(entry map[string]interface{}, key string, def float64) float64 {
|
||||
if v, has := entry[key].(float64); has && v != 0 {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// ComputeValue — the weighted 5-factor score, rounded to 4 decimals.
|
||||
// nowISO is INJECTED (no-clock law); it must carry a timezone offset or
|
||||
// Z (datetime.fromisoformat semantics).
|
||||
func ComputeValue(entry map[string]interface{}, currentGoals []string, nowISO string) float64 {
|
||||
proven := entryFloat(entry, "wash_count", 0)
|
||||
imp := entryFloat(entry, "importance", 0.5)
|
||||
|
||||
provenFactor := math.Min(1.0, math.Log1p(proven)/math.Log1p(ProvenUsesThreshold))
|
||||
|
||||
recency := 1.0
|
||||
lastAccessed := ""
|
||||
if v, has := entry["last_accessed"].(string); has && v != "" {
|
||||
lastAccessed = v
|
||||
} else if v, has := entry["last_washed"].(string); has && v != "" {
|
||||
lastAccessed = v
|
||||
}
|
||||
if lastAccessed != "" {
|
||||
if last, err := parseISODrift(lastAccessed); err == nil {
|
||||
nowDT, err2 := parseISODrift(nowISO)
|
||||
if err2 != nil {
|
||||
nowDT = time.Now().UTC()
|
||||
}
|
||||
hoursSince := nowDT.Sub(last).Hours()
|
||||
if hoursSince < 0.1 {
|
||||
hoursSince = 0.1
|
||||
}
|
||||
recency = 1.0 / (1.0 + hoursSince/(RecencyHalfLifeDays*24))
|
||||
if recency < 0.1 {
|
||||
recency = 0.1
|
||||
}
|
||||
} else {
|
||||
recency = 0.5
|
||||
}
|
||||
}
|
||||
|
||||
den := float64(ReliabilityWindow)
|
||||
if den < 1 {
|
||||
den = 1
|
||||
}
|
||||
reliability := math.Min(1.0, proven/den)
|
||||
|
||||
goalRelevance := 0.5
|
||||
if len(currentGoals) > 0 {
|
||||
text := strings.ToLower(textOf(entry, "text"))
|
||||
goalWords := map[string]bool{}
|
||||
for _, g := range currentGoals {
|
||||
for _, w := range strings.Fields(strings.ToLower(g)) {
|
||||
goalWords[w] = true
|
||||
}
|
||||
}
|
||||
textWords := map[string]bool{}
|
||||
for _, w := range strings.Fields(text) {
|
||||
textWords[w] = true
|
||||
}
|
||||
overlap := 0
|
||||
for w := range goalWords {
|
||||
if textWords[w] {
|
||||
overlap++
|
||||
}
|
||||
}
|
||||
denom := float64(len(goalWords)) * 0.3
|
||||
if denom < 1 {
|
||||
denom = 1
|
||||
}
|
||||
goalRelevance = math.Min(1.0, float64(overlap)/denom)
|
||||
}
|
||||
|
||||
value := WeightProvenUses*provenFactor +
|
||||
WeightImportance*imp +
|
||||
WeightRecency*recency +
|
||||
WeightReliability*reliability +
|
||||
WeightGoalRelevance*goalRelevance
|
||||
|
||||
return round4D(value)
|
||||
}
|
||||
|
||||
func textOf(entry map[string]interface{}, key string) string {
|
||||
v, _ := entry[key].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
func round4D(f float64) float64 {
|
||||
return float64(int64(f*10000+0.5)) / 10000
|
||||
}
|
||||
|
||||
// parseISODrift — fromisoformat with the Z→+00:00 swap.
|
||||
func parseISODrift(s string) (time.Time, error) {
|
||||
s = strings.Replace(s, "Z", "+00:00", 1)
|
||||
layouts := []string{
|
||||
"2006-01-02T15:04:05.999999-07:00",
|
||||
"2006-01-02T15:04:05-07:00",
|
||||
"2006-01-02T15:04:05.999999",
|
||||
"2006-01-02T15:04:05",
|
||||
}
|
||||
for _, l := range layouts {
|
||||
if t, err := time.Parse(l, s); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("bad iso: %s", s)
|
||||
}
|
||||
|
||||
// CheckPromotionEligibility — (eligible, reason) with exact text shapes.
|
||||
func CheckPromotionEligibility(entry map[string]interface{}, nowISO string) (bool, string) {
|
||||
wash := int(entryFloat(entry, "wash_count", 0))
|
||||
status := "washable"
|
||||
if v, has := entry["rock_status"].(string); has && v != "" {
|
||||
status = v
|
||||
}
|
||||
if status == "rock" {
|
||||
return false, "already rock"
|
||||
}
|
||||
if wash < ProvenUsesThreshold {
|
||||
return false, fmt.Sprintf("proven_uses %d < threshold %d", wash, ProvenUsesThreshold)
|
||||
}
|
||||
value := ComputeValue(entry, nil, nowISO)
|
||||
if value < 0.6 {
|
||||
return false, fmt.Sprintf("value %.3f < 0.6 minimum", value)
|
||||
}
|
||||
return true, fmt.Sprintf("wash_count=%d, value=%.3f", wash, value)
|
||||
}
|
||||
Binary file not shown.
@@ -3,8 +3,9 @@
|
||||
// calculate_budget. Classifies how much cognition an event deserves.
|
||||
//
|
||||
// Usage:
|
||||
// elastic-cognition classify <text> [--channel <ch>]
|
||||
// elastic-cognition budget <salience-json> [--channel <ch>]
|
||||
//
|
||||
// elastic-cognition classify <text> [--channel <ch>]
|
||||
// elastic-cognition budget <salience-json> [--channel <ch>]
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -16,17 +17,17 @@ import (
|
||||
)
|
||||
|
||||
type SalienceDecision struct {
|
||||
Score float64 `json:"score"`
|
||||
Flags []string `json:"flags"`
|
||||
MinimumTier string `json:"minimum_tier"`
|
||||
WitnessNeeded bool `json:"witness_needed"`
|
||||
Score float64 `json:"score"`
|
||||
Flags []string `json:"flags"`
|
||||
MinimumTier string `json:"minimum_tier"`
|
||||
WitnessNeeded bool `json:"witness_needed"`
|
||||
RationaleCodes []string `json:"rationale_codes"`
|
||||
}
|
||||
|
||||
type CognitiveBudget struct {
|
||||
Route string `json:"route"`
|
||||
MinTier string `json:"min_tier"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
Route string `json:"route"`
|
||||
MinTier string `json:"min_tier"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
TempOverride *float64 `json:"temp_override,omitempty"`
|
||||
}
|
||||
|
||||
@@ -163,10 +164,14 @@ func classifySalience(text, channel string) SalienceDecision {
|
||||
|
||||
// CHANNEL PRIORS
|
||||
if channel == "work" {
|
||||
if score < 0.5 { score = 0.5 }
|
||||
if score < 0.5 {
|
||||
score = 0.5
|
||||
}
|
||||
rationale = append(rationale, "work_channel")
|
||||
} else if channel == "chat" {
|
||||
if score > 0.3 { score = 0.3 }
|
||||
if score > 0.3 {
|
||||
score = 0.3
|
||||
}
|
||||
rationale = append(rationale, "chat_channel")
|
||||
}
|
||||
|
||||
@@ -192,7 +197,9 @@ func classifySalience(text, channel string) SalienceDecision {
|
||||
|
||||
func containsFlag(flags []string, flag string) bool {
|
||||
for _, f := range flags {
|
||||
if f == flag { return true }
|
||||
if f == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
// bus.go — factory batch 9: the event-bus content-hash + envelope shape
|
||||
// (event_bus.py _content_hash — the orchestration sha256: convention).
|
||||
// The journal store I/O stays reactive.
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContentHashEvent — "sha256:" + sha256(json.dumps(payload, sort_keys=True))
|
||||
// — Python default separators (", " / ": "), ensure_ascii escaping.
|
||||
func ContentHashEvent(payload map[string]interface{}) string {
|
||||
keys := make([]string, 0, len(payload))
|
||||
for k := range payload {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var sb strings.Builder
|
||||
sb.WriteString("{")
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(pyQuoteEvent(k))
|
||||
sb.WriteString(": ")
|
||||
writeEventJSON(&sb, payload[k])
|
||||
}
|
||||
sb.WriteString("}")
|
||||
sum := sha256.Sum256([]byte(sb.String()))
|
||||
return "sha256:" + hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func writeEventJSON(sb *strings.Builder, v interface{}) {
|
||||
switch x := v.(type) {
|
||||
case json.Number:
|
||||
// PARITY: the oracle distinguishes int (1) from float (1.0) at
|
||||
// the literal level; json.Number preserves it through decode.
|
||||
sb.WriteString(x.String())
|
||||
case nil:
|
||||
sb.WriteString("null")
|
||||
case bool:
|
||||
if x {
|
||||
sb.WriteString("true")
|
||||
} else {
|
||||
sb.WriteString("false")
|
||||
}
|
||||
case string:
|
||||
sb.WriteString(pyQuoteEvent(x))
|
||||
case float64:
|
||||
// PARITY: Python json.dumps renders 1.0 as "1.0" (floats keep
|
||||
// .0; ints stay ints). The oracle fixture pinned this.
|
||||
if x == float64(int64(x)) && x < 1e16 && x > -1e16 {
|
||||
sb.WriteString(itoaE(int64(x)))
|
||||
sb.WriteString(".0")
|
||||
} else {
|
||||
sb.WriteString(jsonNumber(x))
|
||||
}
|
||||
case []interface{}:
|
||||
sb.WriteString("[")
|
||||
for i, e := range x {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
writeEventJSON(sb, e)
|
||||
}
|
||||
sb.WriteString("]")
|
||||
case map[string]interface{}:
|
||||
keys := make([]string, 0, len(x))
|
||||
for k := range x {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys) // sort_keys=True — NESTED maps too
|
||||
sb.WriteString("{")
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(pyQuoteEvent(k))
|
||||
sb.WriteString(": ")
|
||||
writeEventJSON(sb, x[k])
|
||||
}
|
||||
sb.WriteString("}")
|
||||
default:
|
||||
b, _ := json.Marshal(x)
|
||||
sb.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func trimFloatE(f float64) string {
|
||||
s := fmtFloatE(f)
|
||||
return s
|
||||
}
|
||||
|
||||
func fmtFloatE(f float64) string {
|
||||
// Python repr semantics: integral → no decimal point
|
||||
if f == float64(int64(f)) && f < 1e16 && f > -1e16 {
|
||||
return itoaE(int64(f))
|
||||
}
|
||||
return strconvFormatE(f)
|
||||
}
|
||||
|
||||
func itoaE(n int64) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := n < 0
|
||||
if neg {
|
||||
n = -n
|
||||
}
|
||||
var b []byte
|
||||
for n > 0 {
|
||||
b = append([]byte{byte('0' + n%10)}, b...)
|
||||
n /= 10
|
||||
}
|
||||
if neg {
|
||||
return "-" + string(b)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func strconvFormatE(f float64) string {
|
||||
return strings.TrimSpace(strings.Replace(jsonNumber(f), "e+", "e+", 1))
|
||||
}
|
||||
|
||||
func jsonNumber(f float64) string {
|
||||
b, _ := json.Marshal(f)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func pyQuoteEvent(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")
|
||||
default:
|
||||
if r < 0x20 {
|
||||
sb.WriteString("\\u")
|
||||
const hexdig = "0123456789abcdef"
|
||||
sb.WriteByte('0')
|
||||
sb.WriteByte('0')
|
||||
sb.WriteByte(hexdig[(r>>4)&0xf])
|
||||
sb.WriteByte(hexdig[r&0xf])
|
||||
} else {
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteByte('"')
|
||||
return sb.String()
|
||||
}
|
||||
Binary file not shown.
+39
-28
@@ -4,9 +4,10 @@
|
||||
// append safety; os.WriteFile for the durable journal.
|
||||
//
|
||||
// Usage:
|
||||
// event-bus emit <event_type> [--source <s>] [--payload <json>]
|
||||
// event-bus log [--limit N] [--type <t>]
|
||||
// event-bus stats
|
||||
//
|
||||
// event-bus emit <event_type> [--source <s>] [--payload <json>]
|
||||
// event-bus log [--limit N] [--type <t>]
|
||||
// event-bus stats
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -24,15 +25,15 @@ import (
|
||||
|
||||
// EventEnvelope — the canonical record (matches rune/orchestration/records.py).
|
||||
type EventEnvelope struct {
|
||||
EventID string `json:"event_id"`
|
||||
EventType string `json:"event_type"`
|
||||
PartitionKey string `json:"partition_key"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
ActorID string `json:"actor_id,omitempty"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
EventID string `json:"event_id"`
|
||||
EventType string `json:"event_type"`
|
||||
PartitionKey string `json:"partition_key"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
ActorID string `json:"actor_id,omitempty"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
Authorization string `json:"authorization,omitempty"`
|
||||
PayloadHash string `json:"payload_hash,omitempty"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
PayloadHash string `json:"payload_hash,omitempty"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
func newID(prefix string) string {
|
||||
@@ -200,7 +201,6 @@ func main() {
|
||||
}, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
|
||||
case "create-envelope":
|
||||
args := os.Args[2:]
|
||||
source, channel, text := "", "", ""
|
||||
@@ -208,13 +208,25 @@ func main() {
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--source":
|
||||
if i+1 < len(args) { source = args[i+1]; i++ }
|
||||
if i+1 < len(args) {
|
||||
source = args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--channel":
|
||||
if i+1 < len(args) { channel = args[i+1]; i++ }
|
||||
if i+1 < len(args) {
|
||||
channel = args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--text":
|
||||
if i+1 < len(args) { text = args[i+1]; i++ }
|
||||
if i+1 < len(args) {
|
||||
text = args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--payload":
|
||||
if i+1 < len(args) { json.Unmarshal([]byte(args[i+1]), &payload); i++ }
|
||||
if i+1 < len(args) {
|
||||
json.Unmarshal([]byte(args[i+1]), &payload)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
out, _ := json.MarshalIndent(createEnvelope(source, channel, text, payload), "", " ")
|
||||
@@ -229,10 +241,10 @@ func main() {
|
||||
// ── event envelope operations (W4: event_envelope port) ──────────────────
|
||||
|
||||
type EnvelopeRequest struct {
|
||||
Source string `json:"source"`
|
||||
Channel string `json:"channel"`
|
||||
Payload map[string]interface{} `json:"payload"`
|
||||
Text string `json:"text"`
|
||||
Source string `json:"source"`
|
||||
Channel string `json:"channel"`
|
||||
Payload map[string]interface{} `json:"payload"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func classifyAuthority(text, channel string) string {
|
||||
@@ -315,14 +327,13 @@ func createEnvelope(source, channel, text string, payload map[string]interface{}
|
||||
env.PayloadHash = contentHash(payload)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"event_id": env.EventID,
|
||||
"timestamp": env.Timestamp,
|
||||
"source": source,
|
||||
"channel": channel,
|
||||
"event_id": env.EventID,
|
||||
"timestamp": env.Timestamp,
|
||||
"source": source,
|
||||
"channel": channel,
|
||||
"authority_class": authority,
|
||||
"urgency": urgency,
|
||||
"payload_hash": env.PayloadHash,
|
||||
"payload": payload,
|
||||
"urgency": urgency,
|
||||
"payload_hash": env.PayloadHash,
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"oracle": "event_bus._content_hash",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "payload_0",
|
||||
"input": {
|
||||
"payload": {}
|
||||
},
|
||||
"expected": {
|
||||
"hash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "payload_1",
|
||||
"input": {
|
||||
"payload": {
|
||||
"k": "v"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hash": "sha256:4be281edc378f1c5e5eb9c15f9dbf5a80da2977981850f93553d5d70b75f1626"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "payload_2",
|
||||
"input": {
|
||||
"payload": {
|
||||
"task_id": "t1",
|
||||
"status": "failed",
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hash": "sha256:fe19764aced3be8e70d5b35c3423a210f94a873df0730058b0a2062f3c84404f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "payload_3",
|
||||
"input": {
|
||||
"payload": {
|
||||
"nested": {
|
||||
"a": [
|
||||
1,
|
||||
2,
|
||||
"x"
|
||||
]
|
||||
},
|
||||
"flag": true,
|
||||
"nothing": null
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hash": "sha256:daf9cdd8709e0cc1aa339f36b52e53d02d2a3c755224c824a8128f54cbaaa197"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "payload_4",
|
||||
"input": {
|
||||
"payload": {
|
||||
"text": "quote\" inside\\ back\nnewline\ttab"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hash": "sha256:d78a4297a19bbe099320d1816966cc50559956065f787e6341c74ed8e2c5af5c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "hash",
|
||||
"name": "payload_5",
|
||||
"input": {
|
||||
"payload": {
|
||||
"float_int": 1.0,
|
||||
"float_frac": 2.5
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hash": "sha256:55e029dcab2234e9c7481bb60c72468fce31b7180a992b83145a36231addc0af"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
// truth_bus_test.go — factory batch 9 parity gate.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBusParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_bus.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_bus.json missing: %v", err)
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(string(b)))
|
||||
dec.UseNumber()
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Name string `json:"name"`
|
||||
Input struct {
|
||||
Payload map[string]interface{} `json:"payload"`
|
||||
} `json:"input"`
|
||||
Expected struct {
|
||||
Hash string `json:"hash"`
|
||||
} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
dec.Decode(&doc)
|
||||
for _, c := range doc.Cases {
|
||||
if got := ContentHashEvent(c.Input.Payload); got != c.Expected.Hash {
|
||||
t.Errorf("%s: hash\n got %q\nwant %q", c.Name, got, c.Expected.Hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,10 +4,11 @@
|
||||
// spools to JSONL for forge filing.
|
||||
//
|
||||
// Usage:
|
||||
// failure-filer classify <subsystem> <error-message>
|
||||
// failure-filer spool <subsystem> <error-message> <tier>
|
||||
// failure-filer unspooled
|
||||
// failure-filer mark-consumed <spool-id>
|
||||
//
|
||||
// failure-filer classify <subsystem> <error-message>
|
||||
// failure-filer spool <subsystem> <error-message> <tier>
|
||||
// failure-filer unspooled
|
||||
// failure-filer mark-consumed <spool-id>
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -24,14 +25,14 @@ const ERROR_CLASS_MAX = 64
|
||||
const EXCERPT_LEN = 256
|
||||
|
||||
type SpoolEntry struct {
|
||||
ID string `json:"spool_id"`
|
||||
Subsystem string `json:"subsystem"`
|
||||
ErrorClass string `json:"error_class"`
|
||||
Excerpt string `json:"excerpt"`
|
||||
Tier int `json:"tier"`
|
||||
Signature string `json:"signature"`
|
||||
Consumed bool `json:"consumed"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID string `json:"spool_id"`
|
||||
Subsystem string `json:"subsystem"`
|
||||
ErrorClass string `json:"error_class"`
|
||||
Excerpt string `json:"excerpt"`
|
||||
Tier int `json:"tier"`
|
||||
Signature string `json:"signature"`
|
||||
Consumed bool `json:"consumed"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
@@ -51,9 +52,13 @@ func spoolPath() string {
|
||||
func loadSpool() []SpoolEntry {
|
||||
var entries []SpoolEntry
|
||||
data, err := os.ReadFile(spoolPath())
|
||||
if err != nil { return entries }
|
||||
if err != nil {
|
||||
return entries
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.TrimSpace(line) == "" { continue }
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
var e SpoolEntry
|
||||
json.Unmarshal([]byte(line), &e)
|
||||
entries = append(entries, e)
|
||||
@@ -73,9 +78,13 @@ func saveSpool(entries []SpoolEntry) {
|
||||
|
||||
func errorClassOf(errMsg string) string {
|
||||
tokens := strings.Fields(errMsg)
|
||||
if len(tokens) == 0 { return "Unknown" }
|
||||
if len(tokens) == 0 {
|
||||
return "Unknown"
|
||||
}
|
||||
class := strings.TrimSuffix(tokens[0], ":")
|
||||
if len(class) > ERROR_CLASS_MAX { class = class[:ERROR_CLASS_MAX] }
|
||||
if len(class) > ERROR_CLASS_MAX {
|
||||
class = class[:ERROR_CLASS_MAX]
|
||||
}
|
||||
return class
|
||||
}
|
||||
|
||||
@@ -104,7 +113,9 @@ func redact(text string) string {
|
||||
|
||||
func excerpt(errMsg string, limit int) string {
|
||||
r := redact(errMsg)
|
||||
if len(r) > limit { r = r[:limit] }
|
||||
if len(r) > limit {
|
||||
r = r[:limit]
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -127,10 +138,10 @@ func main() {
|
||||
sub, cls := signatureOf(subsystem, errMsg)
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"error_class": class, "subsystem": sub,
|
||||
"signature": fmt.Sprintf("%s:%s", sub, cls),
|
||||
"marker": markerFor(sub, cls),
|
||||
"signature": fmt.Sprintf("%s:%s", sub, cls),
|
||||
"marker": markerFor(sub, cls),
|
||||
"tier_gated": tierGated(2),
|
||||
"excerpt": excerpt(errMsg, EXCERPT_LEN),
|
||||
"excerpt": excerpt(errMsg, EXCERPT_LEN),
|
||||
})
|
||||
fmt.Println(string(out))
|
||||
|
||||
@@ -144,12 +155,12 @@ func main() {
|
||||
fmt.Sscanf(args[2], "%d", &tier)
|
||||
_, cls := signatureOf(subsystem, errMsg)
|
||||
entry := SpoolEntry{
|
||||
ID: fmt.Sprintf("spool-%d", time.Now().UnixNano()%1000000),
|
||||
ID: fmt.Sprintf("spool-%d", time.Now().UnixNano()%1000000),
|
||||
Subsystem: subsystem, ErrorClass: cls,
|
||||
Excerpt: excerpt(errMsg, EXCERPT_LEN),
|
||||
Tier: tier,
|
||||
Excerpt: excerpt(errMsg, EXCERPT_LEN),
|
||||
Tier: tier,
|
||||
Signature: fmt.Sprintf("%s:%s", subsystem, cls),
|
||||
Consumed: false,
|
||||
Consumed: false,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
entries := loadSpool()
|
||||
@@ -161,16 +172,24 @@ func main() {
|
||||
case "unspooled":
|
||||
var unspooled []SpoolEntry
|
||||
for _, e := range loadSpool() {
|
||||
if !e.Consumed { unspooled = append(unspooled, e) }
|
||||
if !e.Consumed {
|
||||
unspooled = append(unspooled, e)
|
||||
}
|
||||
}
|
||||
out, _ := json.MarshalIndent(unspooled, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "mark-consumed":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: failure-filer mark-consumed <id>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: failure-filer mark-consumed <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
entries := loadSpool()
|
||||
for i := range entries {
|
||||
if entries[i].ID == args[0] { entries[i].Consumed = true; break }
|
||||
if entries[i].ID == args[0] {
|
||||
entries[i].Consumed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
saveSpool(entries)
|
||||
fmt.Println("consumed:", args[0])
|
||||
|
||||
Binary file not shown.
+35
-15
@@ -3,10 +3,11 @@
|
||||
// hook firing, and the plugin registry.
|
||||
//
|
||||
// Usage:
|
||||
// hook-engine fire <event> [--data <json>]
|
||||
// hook-engine list
|
||||
// hook-engine discover
|
||||
// hook-engine tag <name> <key=value> ...
|
||||
//
|
||||
// hook-engine fire <event> [--data <json>]
|
||||
// hook-engine list
|
||||
// hook-engine discover
|
||||
// hook-engine tag <name> <key=value> ...
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -24,8 +25,8 @@ type HookEntry struct {
|
||||
}
|
||||
|
||||
type FunctionMeta struct {
|
||||
Type string `json:"type"`
|
||||
File string `json:"file"`
|
||||
Type string `json:"type"`
|
||||
File string `json:"file"`
|
||||
Factors map[string]string `json:"factors"`
|
||||
}
|
||||
|
||||
@@ -55,7 +56,9 @@ func functionsPath() string {
|
||||
func loadHooks() []HookEntry {
|
||||
var hooks []HookEntry
|
||||
data, err := os.ReadFile(hooksPath())
|
||||
if err != nil { return hooks }
|
||||
if err != nil {
|
||||
return hooks
|
||||
}
|
||||
json.Unmarshal(data, &hooks)
|
||||
return hooks
|
||||
}
|
||||
@@ -69,7 +72,9 @@ func saveHooks(hooks []HookEntry) {
|
||||
func loadFunctions() map[string]FunctionMeta {
|
||||
functions := make(map[string]FunctionMeta)
|
||||
data, err := os.ReadFile(functionsPath())
|
||||
if err != nil { return functions }
|
||||
if err != nil {
|
||||
return functions
|
||||
}
|
||||
json.Unmarshal(data, &functions)
|
||||
return functions
|
||||
}
|
||||
@@ -123,11 +128,21 @@ func main() {
|
||||
data := ""
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--event": if i+1 < len(args) { event = args[i+1]; i++ }
|
||||
case "--data": if i+1 < len(args) { data = args[i+1]; i++ }
|
||||
case "--event":
|
||||
if i+1 < len(args) {
|
||||
event = args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--data":
|
||||
if i+1 < len(args) {
|
||||
data = args[i+1]
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
if event == "" && len(args) > 0 { event = args[0] }
|
||||
if event == "" && len(args) > 0 {
|
||||
event = args[0]
|
||||
}
|
||||
fired := 0
|
||||
for _, hook := range hooks {
|
||||
if hook.Event == event {
|
||||
@@ -147,18 +162,23 @@ func main() {
|
||||
out, _ := json.Marshal(h)
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
if len(hooks) == 0 { fmt.Println("(no hooks registered)") }
|
||||
if len(hooks) == 0 {
|
||||
fmt.Println("(no hooks registered)")
|
||||
}
|
||||
|
||||
case "discover":
|
||||
// Scan src/rune/ for Python modules
|
||||
entries, err := os.ReadDir("src/rune")
|
||||
if err != nil { fmt.Println("(no src/rune directory)"); return }
|
||||
if err != nil {
|
||||
fmt.Println("(no src/rune directory)")
|
||||
return
|
||||
}
|
||||
for _, e := range entries {
|
||||
if strings.HasSuffix(e.Name(), ".py") && !strings.HasPrefix(e.Name(), "__") {
|
||||
name := strings.TrimSuffix(e.Name(), ".py")
|
||||
functions[name] = FunctionMeta{
|
||||
Type: "script",
|
||||
File: filepath.Join("src/rune", e.Name()),
|
||||
Type: "script",
|
||||
File: filepath.Join("src/rune", e.Name()),
|
||||
Factors: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
// placement.go — factory batch 4: the placement mechanics of
|
||||
// hook_engine.py (contract BUILDER-2026-10-07T23:06:18Z-ec6208).
|
||||
// RecommendPlacement verbatim — the decision table is the oracle.
|
||||
|
||||
import "sort"
|
||||
|
||||
// Hook point / frequency / scope constants (verbatim).
|
||||
const (
|
||||
HookPreTurn = "pre-turn"
|
||||
HookSystem = "system-prompt"
|
||||
HookPostTurn = "post-turn"
|
||||
HookIdle = "idle"
|
||||
HookTimer = "timer"
|
||||
HookEvent = "event"
|
||||
|
||||
FreqEveryTurn = "every-turn"
|
||||
FreqOncePerSession = "once-per-session"
|
||||
FreqScheduled = "scheduled"
|
||||
FreqOnDemand = "on-demand"
|
||||
|
||||
ScopeEphemeral = "ephemeral"
|
||||
ScopeSession = "session"
|
||||
ScopePersistent = "persistent"
|
||||
)
|
||||
|
||||
// Factors — the placement input (Python's dict.get access pattern).
|
||||
type Factors map[string]interface{}
|
||||
|
||||
func (f Factors) bool(key string) bool {
|
||||
v, _ := f[key].(bool)
|
||||
return v
|
||||
}
|
||||
|
||||
func (f Factors) str(key string) string {
|
||||
v, _ := f[key].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// RecommendPlacement — the decision table verbatim. Hooks preserve
|
||||
// append order (the oracle's list order is part of the shape).
|
||||
func RecommendPlacement(name string, factors Factors) map[string]interface{} {
|
||||
var hooks []string
|
||||
frequency := FreqOnDemand
|
||||
cacheable := false
|
||||
cacheScope := ScopeEphemeral
|
||||
|
||||
// ── HOOK POINT ────────────────────────────────────────────────────
|
||||
if factors.bool("output_dep") {
|
||||
hooks = append(hooks, HookPostTurn)
|
||||
}
|
||||
if factors.bool("input_dep") && !factors.bool("output_dep") {
|
||||
hooks = append(hooks, HookSystem)
|
||||
}
|
||||
if !factors.bool("input_dep") && !factors.bool("output_dep") {
|
||||
hooks = append(hooks, HookSystem)
|
||||
cacheable = true
|
||||
}
|
||||
if factors.str("side_effects") == "read-write" {
|
||||
hooks = append(hooks, HookIdle)
|
||||
hooks = append(hooks, HookTimer)
|
||||
}
|
||||
|
||||
// ── FREQUENCY ─────────────────────────────────────────────────────
|
||||
if (factors.bool("input_dep") && factors.bool("output_dep")) || factors.bool("input_dep") {
|
||||
frequency = FreqEveryTurn
|
||||
} else if factors.str("volatility") == "session" {
|
||||
frequency = FreqOncePerSession
|
||||
cacheable = true
|
||||
} else if factors.str("volatility") == "per-turn" {
|
||||
frequency = FreqEveryTurn
|
||||
} else if factors.str("side_effects") == "read-write" {
|
||||
frequency = FreqScheduled
|
||||
}
|
||||
|
||||
// ── CACHE SCOPE ───────────────────────────────────────────────────
|
||||
switch factors.str("scope") {
|
||||
case "persistent":
|
||||
cacheScope = ScopePersistent
|
||||
case "session":
|
||||
cacheScope = ScopeSession
|
||||
default:
|
||||
cacheScope = ScopeEphemeral
|
||||
}
|
||||
|
||||
// ── CONCURRENCY / FAILURE MODE ────────────────────────────────────
|
||||
canParallel := factors.bool("concurrent")
|
||||
failureMode := factors.str("failure")
|
||||
if failureMode == "" {
|
||||
failureMode = "graceful"
|
||||
}
|
||||
contextBudget := 0
|
||||
if v, has := factors["context_budget"].(float64); has {
|
||||
contextBudget = int(v)
|
||||
}
|
||||
|
||||
if hooks == nil {
|
||||
hooks = []string{}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"hooks": hooks,
|
||||
"frequency": frequency,
|
||||
"cacheable": cacheable,
|
||||
"cache_scope": cacheScope,
|
||||
"can_parallel": canParallel,
|
||||
"failure_mode": failureMode,
|
||||
"context_budget": contextBudget,
|
||||
}
|
||||
}
|
||||
|
||||
// sortedKeysHelper — retained for CLI output stability.
|
||||
var _ = sort.Strings
|
||||
@@ -0,0 +1,294 @@
|
||||
{
|
||||
"oracle": "src/rune/hook_engine.py recommend_placement (live)",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_0",
|
||||
"input": {
|
||||
"factors": {
|
||||
"output_dep": true
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"post-turn"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": false,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_1",
|
||||
"input": {
|
||||
"factors": {
|
||||
"input_dep": true
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "every-turn",
|
||||
"cacheable": false,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_2",
|
||||
"input": {
|
||||
"factors": {
|
||||
"input_dep": true,
|
||||
"output_dep": true
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"post-turn"
|
||||
],
|
||||
"frequency": "every-turn",
|
||||
"cacheable": false,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_3",
|
||||
"input": {
|
||||
"factors": {}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_4",
|
||||
"input": {
|
||||
"factors": {
|
||||
"side_effects": "read-write"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt",
|
||||
"idle",
|
||||
"timer"
|
||||
],
|
||||
"frequency": "scheduled",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_5",
|
||||
"input": {
|
||||
"factors": {
|
||||
"volatility": "session"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "once-per-session",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_6",
|
||||
"input": {
|
||||
"factors": {
|
||||
"volatility": "per-turn"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "every-turn",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_7",
|
||||
"input": {
|
||||
"factors": {
|
||||
"scope": "persistent"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": true,
|
||||
"cache_scope": "persistent",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_8",
|
||||
"input": {
|
||||
"factors": {
|
||||
"scope": "session"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": true,
|
||||
"cache_scope": "session",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_9",
|
||||
"input": {
|
||||
"factors": {
|
||||
"concurrent": true
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": true,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_10",
|
||||
"input": {
|
||||
"factors": {
|
||||
"failure": "fail-loud"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "fail-loud",
|
||||
"context_budget": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_11",
|
||||
"input": {
|
||||
"factors": {
|
||||
"context_budget": 500
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "on-demand",
|
||||
"cacheable": true,
|
||||
"cache_scope": "ephemeral",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 500
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_12",
|
||||
"input": {
|
||||
"factors": {
|
||||
"input_dep": true,
|
||||
"scope": "session",
|
||||
"concurrent": true,
|
||||
"context_budget": 250
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt"
|
||||
],
|
||||
"frequency": "every-turn",
|
||||
"cacheable": false,
|
||||
"cache_scope": "session",
|
||||
"can_parallel": true,
|
||||
"failure_mode": "graceful",
|
||||
"context_budget": 250
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "placement",
|
||||
"name": "matrix_13",
|
||||
"input": {
|
||||
"factors": {
|
||||
"side_effects": "read-write",
|
||||
"failure": "fail-loud",
|
||||
"scope": "persistent"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"hooks": [
|
||||
"system-prompt",
|
||||
"idle",
|
||||
"timer"
|
||||
],
|
||||
"frequency": "scheduled",
|
||||
"cacheable": true,
|
||||
"cache_scope": "persistent",
|
||||
"can_parallel": false,
|
||||
"failure_mode": "fail-loud",
|
||||
"context_budget": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
// truth_placement_test.go — factory batch 4 parity gate: the placement
|
||||
// decision table replays truth_placement.json (live oracle — contract
|
||||
// BUILDER-2026-10-07T23:06:18Z-ec6208).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlacementParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_placement.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_placement.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
for _, c := range doc.Cases {
|
||||
factorsAny, _ := c.Input["factors"].(map[string]interface{})
|
||||
factors := Factors(factorsAny)
|
||||
got := RecommendPlacement("hook", factors)
|
||||
jsonEqH(t, c.Name, "placement", got, c.Expected)
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEqH(t *testing.T, name, field string, got, want interface{}) {
|
||||
t.Helper()
|
||||
gb, _ := json.Marshal(got)
|
||||
wb, _ := json.Marshal(want)
|
||||
if string(gb) != string(wb) {
|
||||
t.Errorf("%s: %s divergence:\n got %s\nwant %s", name, field, gb, wb)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,8 +4,9 @@
|
||||
// identical failures, max_iterations enforcement, and tool allowlist checking.
|
||||
//
|
||||
// Usage:
|
||||
// local-tool-runtime run --model <model> --message <text> [--max-iterations N]
|
||||
// local-tool-runtime status
|
||||
//
|
||||
// local-tool-runtime run --model <model> --message <text> [--max-iterations N]
|
||||
// local-tool-runtime status
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -24,14 +25,14 @@ type ChatMessage struct {
|
||||
}
|
||||
|
||||
type OperatorResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Response string `json:"response,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
ToolCalls int `json:"tool_calls,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Events []map[string]interface{} `json:"events,omitempty"`
|
||||
Thinking []string `json:"thinking,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Response string `json:"response,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
ToolCalls int `json:"tool_calls,omitempty"`
|
||||
Duration string `json:"duration,omitempty"`
|
||||
Events []map[string]interface{} `json:"events,omitempty"`
|
||||
Thinking []string `json:"thinking,omitempty"`
|
||||
}
|
||||
|
||||
type ToolRuntime struct {
|
||||
@@ -131,7 +132,7 @@ func (rt *ToolRuntime) Run(messages []ChatMessage) OperatorResponse {
|
||||
start := time.Now()
|
||||
conversation := make([]ChatMessage, len(messages))
|
||||
copy(conversation, messages)
|
||||
|
||||
|
||||
cb := &CircuitBreaker{}
|
||||
toolCalls := 0
|
||||
var events []map[string]interface{}
|
||||
@@ -141,7 +142,7 @@ func (rt *ToolRuntime) Run(messages []ChatMessage) OperatorResponse {
|
||||
if err != nil {
|
||||
return OperatorResponse{
|
||||
Success: false, Error: err.Error(),
|
||||
Model: rt.Model,
|
||||
Model: rt.Model,
|
||||
Duration: time.Since(start).Round(time.Millisecond).String(),
|
||||
}
|
||||
}
|
||||
@@ -159,48 +160,48 @@ func (rt *ToolRuntime) Run(messages []ChatMessage) OperatorResponse {
|
||||
if err := json.Unmarshal([]byte(strings.TrimPrefix(content, "TOOL_CALL:")), &call); err != nil {
|
||||
conversation = append(conversation, resp)
|
||||
conversation = append(conversation, ChatMessage{
|
||||
Role: "user",
|
||||
Role: "user",
|
||||
Content: fmt.Sprintf("Error: invalid tool call format. Use TOOL_CALL:{\"operation\":\"...\",\"args\":{...}}"),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Check tool allowlist
|
||||
if !rt.ToolNames[call.Operation] {
|
||||
conversation = append(conversation, resp)
|
||||
conversation = append(conversation, ChatMessage{
|
||||
Role: "user",
|
||||
Role: "user",
|
||||
Content: fmt.Sprintf("Error: tool %q is not in the allowed list. Available: read_file, write_file, edit_file, list_directory, glob_files, grep_files, run_command, run_tests, lint_file, git_commit, git_diff, git_log, repo_map, read_persona, query_memory, check_growth, diagnose_behavior, find_capability", call.Operation),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Execute the tool
|
||||
result := rt.executeTool(call.Operation, content)
|
||||
toolCalls++
|
||||
|
||||
|
||||
// Circuit breaker: check for consecutive identical failures
|
||||
if warning, tripped := cb.RegisterFailure(call.Operation, call.Args); tripped {
|
||||
return OperatorResponse{
|
||||
Success: false,
|
||||
Error: warning,
|
||||
Model: rt.Model,
|
||||
Success: false,
|
||||
Error: warning,
|
||||
Model: rt.Model,
|
||||
ToolCalls: toolCalls,
|
||||
Duration: time.Since(start).Round(time.Millisecond).String(),
|
||||
Duration: time.Since(start).Round(time.Millisecond).String(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
events = append(events, map[string]interface{}{
|
||||
"tool": call.Operation, "result": result,
|
||||
})
|
||||
|
||||
|
||||
conversation = append(conversation, resp)
|
||||
conversation = append(conversation, ChatMessage{
|
||||
Role: "user", Content: fmt.Sprintf("Tool result: %s", result),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// No tool call — this is the final response
|
||||
cb.RegisterSuccess()
|
||||
return OperatorResponse{
|
||||
@@ -210,7 +211,7 @@ func (rt *ToolRuntime) Run(messages []ChatMessage) OperatorResponse {
|
||||
Events: events,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return OperatorResponse{
|
||||
Success: false, Error: "max iterations exceeded",
|
||||
Model: rt.Model, ToolCalls: toolCalls,
|
||||
@@ -224,16 +225,30 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
baseURL := os.Getenv("OLLAMA_URL")
|
||||
if baseURL == "" { baseURL = "http://localhost:11434" }
|
||||
if baseURL == "" {
|
||||
baseURL = "http://localhost:11434"
|
||||
}
|
||||
model := "qwen2.5:3b"
|
||||
message := ""
|
||||
maxIter := 8
|
||||
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
switch os.Args[i] {
|
||||
case "--model": if i+1 < len(os.Args) { model = os.Args[i+1]; i++ }
|
||||
case "--message": if i+1 < len(os.Args) { message = os.Args[i+1]; i++ }
|
||||
case "--max-iterations": if i+1 < len(os.Args) { fmt.Sscanf(os.Args[i+1], "%d", &maxIter); i++ }
|
||||
case "--model":
|
||||
if i+1 < len(os.Args) {
|
||||
model = os.Args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--message":
|
||||
if i+1 < len(os.Args) {
|
||||
message = os.Args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--max-iterations":
|
||||
if i+1 < len(os.Args) {
|
||||
fmt.Sscanf(os.Args[i+1], "%d", &maxIter)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+50
-16
@@ -3,12 +3,13 @@
|
||||
// Uses modernc.org/sqlite (pure Go, no cgo).
|
||||
//
|
||||
// Usage:
|
||||
// memory-ops add <text> [--importance <f>] [--topic <t>] [--source <s>]
|
||||
// memory-ops search <query> [--limit N]
|
||||
// memory-ops get <id>
|
||||
// memory-ops wash <id>
|
||||
// memory-ops supersede <id> <new-text>
|
||||
// memory-ops stats
|
||||
//
|
||||
// memory-ops add <text> [--importance <f>] [--topic <t>] [--source <s>]
|
||||
// memory-ops search <query> [--limit N]
|
||||
// memory-ops get <id>
|
||||
// memory-ops wash <id>
|
||||
// memory-ops supersede <id> <new-text>
|
||||
// memory-ops stats
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -37,7 +38,9 @@ func dbPath() string {
|
||||
|
||||
func connect() (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", dbPath())
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Exec("PRAGMA journal_mode=WAL")
|
||||
db.Exec("PRAGMA synchronous=NORMAL")
|
||||
return db, nil
|
||||
@@ -69,10 +72,25 @@ func main() {
|
||||
source := ""
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--importance": if i+1 < len(args) { fmt.Sscanf(args[i+1], "%f", &importance); i++ }
|
||||
case "--topic": if i+1 < len(args) { topic = args[i+1]; i++ }
|
||||
case "--source": if i+1 < len(args) { source = args[i+1]; i++ }
|
||||
default: if text == "" { text = args[i] }
|
||||
case "--importance":
|
||||
if i+1 < len(args) {
|
||||
fmt.Sscanf(args[i+1], "%f", &importance)
|
||||
i++
|
||||
}
|
||||
case "--topic":
|
||||
if i+1 < len(args) {
|
||||
topic = args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--source":
|
||||
if i+1 < len(args) {
|
||||
source = args[i+1]
|
||||
i++
|
||||
}
|
||||
default:
|
||||
if text == "" {
|
||||
text = args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(text) < 8 {
|
||||
@@ -100,8 +118,15 @@ func main() {
|
||||
limit := 5
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--limit": if i+1 < len(args) { fmt.Sscanf(args[i+1], "%d", &limit); i++ }
|
||||
default: if query == "" { query = args[i] }
|
||||
case "--limit":
|
||||
if i+1 < len(args) {
|
||||
fmt.Sscanf(args[i+1], "%d", &limit)
|
||||
i++
|
||||
}
|
||||
default:
|
||||
if query == "" {
|
||||
query = args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
rows, err := db.Query(
|
||||
@@ -128,7 +153,10 @@ func main() {
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "get":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: memory-ops get <id>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: memory-ops get <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
var id int64
|
||||
fmt.Sscanf(args[0], "%d", &id)
|
||||
var text, topic, createdAt string
|
||||
@@ -146,7 +174,10 @@ func main() {
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "wash":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: memory-ops wash <id>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: memory-ops wash <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
var id int64
|
||||
fmt.Sscanf(args[0], "%d", &id)
|
||||
db.Exec("UPDATE entries SET wash_count = wash_count + 1, last_accessed = ? WHERE id = ?", nowISO(), id)
|
||||
@@ -154,7 +185,10 @@ func main() {
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "supersede":
|
||||
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: memory-ops supersede <id> <new-text>"); os.Exit(1) }
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: memory-ops supersede <id> <new-text>")
|
||||
os.Exit(1)
|
||||
}
|
||||
var oldID int64
|
||||
fmt.Sscanf(args[0], "%d", &oldID)
|
||||
newText := args[1]
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
// fuzz_test.go — FTS query builder fuzz (polyglot gate contract).
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzFtsQueryConjunctive — deterministic; quote-free inputs produce
|
||||
// balanced quotes. ORACLE-VERIFIED DIVERGENCE (pinned): the builder wraps
|
||||
// terms in quotes but never escapes embedded ones — input containing '"'
|
||||
// yields unbalanced output in the Python oracle too ('¼"' case). FTS5
|
||||
// hardening is defect territory (the builder can inject match syntax);
|
||||
// here we pin ORACLE behavior, not aspiration.
|
||||
func FuzzFtsQueryConjunctive(f *testing.F) {
|
||||
for _, s := range []string{"", "the and was", "one good term", "\"nested\" quotes",
|
||||
"cap this one out with many many many many many many words here"} {
|
||||
f.Add(s)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
out := FtsQueryConjunctive(s)
|
||||
if out != FtsQueryConjunctive(s) {
|
||||
t.Fatalf("not deterministic for %q", s)
|
||||
}
|
||||
if !strings.Contains(s, "\"") && strings.Count(out, "\"")%2 != 0 {
|
||||
t.Fatalf("unbalanced quotes on quote-free input %q: %q", s, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
// recall_gate.go — batch 4: the recall() integration pieces of
|
||||
// memory_store.py (contract BUILDER-2026-10-07T19:47:37Z-1cf1dc).
|
||||
// DenseGate (the meta-judgement: vector proximity is not a relation),
|
||||
// RankFusionEntries (the entries-side fusion — verified identical to the
|
||||
// chunk-side RankFusion already ported; the docstring's raw-BM25 claim
|
||||
// is NOT what the code does — the CODE min-max normalizes in ALL modes,
|
||||
// and the code is the oracle), the abstain constants.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RecallBudget / RecallMinScore — the abstain constants (below the floor
|
||||
// → abstain, don't inject distractors).
|
||||
const (
|
||||
RecallBudget = 8
|
||||
RecallMinScore = 0.42
|
||||
)
|
||||
|
||||
// DenseHit — one candidate dense match: (rowid, cosine).
|
||||
type DenseHit struct {
|
||||
RowID int64
|
||||
Cos float64
|
||||
}
|
||||
|
||||
// QueryContent — the query's significant words (len>3, not msStopwords).
|
||||
func QueryContent(query string) []string {
|
||||
var out []string
|
||||
seen := map[string]bool{}
|
||||
for _, t := range strings.Fields(strings.ToLower(query)) {
|
||||
if len(t) > 3 && !msStopwords[t] && !seen[t] {
|
||||
seen[t] = true
|
||||
out = append(out, t)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// DenseGate — the meta-judgement applied to the vec0 candidates.
|
||||
// queryContent: significant query words. dfOf: document frequency
|
||||
// (LIKE-substring count / total entries — the CALLER supplies counts;
|
||||
// the gate owns the 0.05 distinctiveness threshold). A hit is kept iff
|
||||
// it shares a DISTINCTIVE content word with the entry text; otherwise
|
||||
// only when the query has no content words at all OR cos >= 0.80.
|
||||
func DenseGate(hits []DenseHit, entryTexts map[int64]string, queryContent []string, dfOf func(word string) int, totalEntries int) []DenseHit {
|
||||
out := []DenseHit{}
|
||||
for _, h := range hits {
|
||||
etxt := strings.ToLower(entryTexts[h.RowID])
|
||||
distinctive := false
|
||||
for _, w := range queryContent {
|
||||
if strings.Contains(etxt, w) {
|
||||
df := 0
|
||||
if dfOf != nil {
|
||||
df = dfOf(w)
|
||||
}
|
||||
total := totalEntries
|
||||
if total < 1 {
|
||||
total = 1
|
||||
}
|
||||
if float64(df)/float64(total) < 0.05 {
|
||||
distinctive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if distinctive {
|
||||
out = append(out, h)
|
||||
} else if len(queryContent) == 0 || h.Cos >= 0.80 {
|
||||
out = append(out, h)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RankFusionEntries — the entries-side fusion. VERIFIED identical to the
|
||||
// chunk-side RankFusion (same file, memory_store.py:1297 vs the batch-2
|
||||
// port): the code min-max normalizes the lexical-only branch in ALL
|
||||
// modes. The docstring's raw-BM25 claim is stale — the CODE is the
|
||||
// oracle. Reuse keeps one fusion implementation; this wrapper pins the
|
||||
// parity for the recall() call shape.
|
||||
func RankFusionEntries(dense, bm25 [][2]interface{}) map[string]interface{} {
|
||||
return RankFusion(dense, bm25, RRFK)
|
||||
}
|
||||
|
||||
// RankIDs — the sorted rank order (desc by score) for downstream SQL IN
|
||||
// clauses (Python: list(ranks.keys()) is INSERTION order — the fusion's
|
||||
// accumulation order, which SortStable ranking reproduces).
|
||||
func RankIDs(fusion map[string]interface{}) []interface{} {
|
||||
ranks, _ := fusion["ranks"].([]interface{})
|
||||
return ranks
|
||||
}
|
||||
|
||||
// sortedInsertion — retained reference: the fusion's rank list order IS
|
||||
// the insertion order (accumulation), not score order. Documented for
|
||||
// the SQL-IN parity note. (Unused call sites may sort explicitly.)
|
||||
var _ = sort.Float64s
|
||||
@@ -0,0 +1,194 @@
|
||||
package main
|
||||
|
||||
// score.go — batch 3: the scoring/abstention kernel of memory_store.py
|
||||
// (contract BUILDER-2026-10-07T18:57:06Z-c2429c). ScoreRecallRows (the
|
||||
// metadata scoring + mode-specific abstention), PositionAware (the
|
||||
// lost-in-the-middle re-rank).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecencyHalfLife — RECENCY_HALF_LIFE (days).
|
||||
const RecencyHalfLife = 30.0
|
||||
|
||||
// PositionAware — the two highest-relevance items go FIRST and LAST; the
|
||||
// same items, only the order changes. No-op for < 3 items.
|
||||
func PositionAware(top []map[string]interface{}) []map[string]interface{} {
|
||||
if len(top) < 3 {
|
||||
return top
|
||||
}
|
||||
s := make([]map[string]interface{}, len(top))
|
||||
copy(s, top)
|
||||
sorted := make([]map[string]interface{}, len(s))
|
||||
copy(sorted, s)
|
||||
sort.SliceStable(sorted, func(i, j int) bool {
|
||||
return entryRelevance(sorted[i]) > entryRelevance(sorted[j])
|
||||
})
|
||||
out := make([]map[string]interface{}, 0, len(sorted))
|
||||
out = append(out, sorted[0])
|
||||
out = append(out, sorted[2:]...)
|
||||
out = append(out, sorted[1])
|
||||
return out
|
||||
}
|
||||
|
||||
func entryRelevance(m map[string]interface{}) float64 {
|
||||
v, _ := m["relevance"].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
// parseISO — Python fromisoformat with the Z→+00:00 swap; also accepts
|
||||
// naive timestamps. Returns (time, ok).
|
||||
func parseISO(s string) (time.Time, bool) {
|
||||
if s == "" {
|
||||
return time.Time{}, false
|
||||
}
|
||||
s = strings.Replace(s, "Z", "+00:00", 1)
|
||||
layouts := []string{
|
||||
"2006-01-02T15:04:05.999999-07:00",
|
||||
"2006-01-02T15:04:05-07:00",
|
||||
"2006-01-02T15:04:05.999999",
|
||||
"2006-01-02T15:04:05",
|
||||
}
|
||||
for _, l := range layouts {
|
||||
if t, err := time.Parse(l, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// ScoreRecallRows — rank-ordered metadata scoring + abstention ladder.
|
||||
func ScoreRecallRows(fusion map[string]interface{}, rows []map[string]interface{}, dense, bm25 [][2]interface{}, budget int, minScore float64, nowStr string) []map[string]interface{} {
|
||||
// ranks: the fusion dict's ranks — [[id, score], ...] pairs.
|
||||
ranksList, _ := fusion["ranks"].([]interface{})
|
||||
bestDense, _ := fusion["best_dense"].(float64)
|
||||
mode, _ := fusion["mode"].(string)
|
||||
|
||||
byID := map[string]map[string]interface{}{}
|
||||
for _, r := range rows {
|
||||
id := pairString(r["id"])
|
||||
byID[id] = r
|
||||
}
|
||||
|
||||
type rankItem struct {
|
||||
eid string
|
||||
base float64
|
||||
}
|
||||
var items []rankItem
|
||||
for _, raw := range ranksList {
|
||||
pair, ok := raw.([]interface{})
|
||||
if !ok || len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
eid := pairString(pair[0])
|
||||
base, _ := pair[1].(float64)
|
||||
items = append(items, rankItem{eid, base})
|
||||
}
|
||||
// sorted(ranks.items(), key=lambda item: -item[1]) — stable desc.
|
||||
sort.SliceStable(items, func(i, j int) bool { return items[i].base > items[j].base })
|
||||
|
||||
nowDT, _ := parseISO(nowStr)
|
||||
scored := []map[string]interface{}{}
|
||||
for _, it := range items {
|
||||
row, has := byID[it.eid]
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
conf := 0.7
|
||||
if v, ok := row["confidence"].(float64); ok && v != 0 {
|
||||
conf = v
|
||||
}
|
||||
temp := 1.0
|
||||
if v, ok := row["temperature"].(float64); ok && v != 0 {
|
||||
temp = v
|
||||
}
|
||||
recency := 1.0
|
||||
if la, ok := row["last_accessed"].(string); ok && la != "" {
|
||||
if tLast, ok2 := parseISO(la); ok2 {
|
||||
days := nowDT.Sub(tLast).Seconds() / 86400.0
|
||||
if days >= 0 {
|
||||
recency = RecencyHalfLife / (RecencyHalfLife + days)
|
||||
}
|
||||
}
|
||||
// timestamp failure → recency stays 1.0 (the except path)
|
||||
}
|
||||
var relevance float64
|
||||
if len(dense) > 0 {
|
||||
relevance = it.base * (0.4 + 0.6*conf) * (0.5 + 0.5*temp) * (0.85 + 0.15*recency)
|
||||
} else {
|
||||
relevance = (it.base + 5.0) * (0.4 + 0.6*conf) * (0.5 + 0.5*temp) * (0.85 + 0.15*recency)
|
||||
}
|
||||
text, _ := row["text"].(string)
|
||||
topic, _ := row["topic"].(string)
|
||||
source, _ := row["source"].(string)
|
||||
lastAccessed, _ := row["last_accessed"].(string)
|
||||
scored = append(scored, map[string]interface{}{
|
||||
"id": numericID(it.eid),
|
||||
"text": text,
|
||||
"importance": row["importance"],
|
||||
"topic": topic,
|
||||
"entities": row["entities"],
|
||||
"source": source,
|
||||
"last_accessed": lastAccessed,
|
||||
"rrf": round4(it.base),
|
||||
"confidence": round2(conf),
|
||||
"temperature": round2(temp),
|
||||
"relevance": round4(relevance),
|
||||
"mode": mode,
|
||||
})
|
||||
}
|
||||
if len(scored) == 0 {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
if len(scored) > budget {
|
||||
scored = scored[:budget]
|
||||
}
|
||||
top := scored
|
||||
if len(dense) == 0 && len(bm25) == 0 {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
if len(dense) == 0 {
|
||||
maxRel := 0.0
|
||||
for i, item := range top {
|
||||
r := entryRelevance(item)
|
||||
if i == 0 || r > maxRel {
|
||||
maxRel = r
|
||||
}
|
||||
}
|
||||
if maxRel < minScore {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return top
|
||||
}
|
||||
if len(bm25) == 0 {
|
||||
if bestDense < minScore {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
if bestDense >= 0.74 {
|
||||
return PositionAware(top)
|
||||
}
|
||||
if len(dense) >= 2 {
|
||||
dScores := make([]float64, 0, len(dense))
|
||||
for _, p := range dense {
|
||||
f, _ := p[1].(float64)
|
||||
dScores = append(dScores, f)
|
||||
}
|
||||
sort.Float64s(dScores)
|
||||
if dScores[len(dScores)-1]-dScores[len(dScores)-2] >= 0.08 {
|
||||
return PositionAware(top)
|
||||
}
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return PositionAware(MapReduce(top, 10))
|
||||
}
|
||||
if bestDense < minScore {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return PositionAware(MapReduce(top, 10))
|
||||
}
|
||||
|
||||
var _ = fmt.Sprintf // retained for parity annotations
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("\xbc\"")
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("\"")
|
||||
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"oracle": "src/rune/memory_store.py recall() dense gate + _py_rank_fusion",
|
||||
"generated": "2026-10-07",
|
||||
"fixture_df_note": "df via LIKE-substring over active entries; threshold 0.05",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "distinctive_shared",
|
||||
"input": {
|
||||
"query": "worthiness gates",
|
||||
"hits": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.85
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.85
|
||||
]
|
||||
],
|
||||
"q_content": [
|
||||
"gates",
|
||||
"worthiness"
|
||||
],
|
||||
"df": {
|
||||
"worthiness": 1,
|
||||
"gates": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "common_word_falls_back",
|
||||
"input": {
|
||||
"query": "black",
|
||||
"hits": [
|
||||
[
|
||||
2,
|
||||
0.86
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.79
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
2,
|
||||
0.86
|
||||
]
|
||||
],
|
||||
"q_content": [
|
||||
"black"
|
||||
],
|
||||
"df": {
|
||||
"black": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "strong_cos_no_share",
|
||||
"input": {
|
||||
"query": "unrelated query terms",
|
||||
"hits": [
|
||||
[
|
||||
6,
|
||||
0.91
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
6,
|
||||
0.91
|
||||
]
|
||||
],
|
||||
"q_content": [
|
||||
"query",
|
||||
"terms",
|
||||
"unrelated"
|
||||
],
|
||||
"df": {
|
||||
"unrelated": 1,
|
||||
"query": 0,
|
||||
"terms": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "dense_gate",
|
||||
"name": "no_content_words",
|
||||
"input": {
|
||||
"query": "the was and",
|
||||
"hits": [
|
||||
[
|
||||
1,
|
||||
0.6
|
||||
],
|
||||
[
|
||||
6,
|
||||
0.95
|
||||
]
|
||||
],
|
||||
"total": 8
|
||||
},
|
||||
"expected": {
|
||||
"kept": [
|
||||
[
|
||||
1,
|
||||
0.6
|
||||
],
|
||||
[
|
||||
6,
|
||||
0.95
|
||||
]
|
||||
],
|
||||
"q_content": [],
|
||||
"df": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "fusion_identity",
|
||||
"name": "entries_eq_chunks",
|
||||
"input": {
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.8
|
||||
]
|
||||
],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
10.0
|
||||
],
|
||||
[
|
||||
3,
|
||||
4.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.0625
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.1213235294117647
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.058823529411764705
|
||||
]
|
||||
],
|
||||
"best_dense": 0.9,
|
||||
"mode": "hybrid"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "constants",
|
||||
"name": "abstain",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"budget": 8,
|
||||
"min_score": 0.42
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package main
|
||||
|
||||
// truth_gate_test.go — memory-store batch 4 parity gate: the dense
|
||||
// meta-judgement + fusion identity replay truth_gate.json (live oracle,
|
||||
// fixture store — contract BUILDER-2026-10-07T19:47:37Z-1cf1dc).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func sortStringsW(s []string) {
|
||||
for i := 1; i < len(s); i++ {
|
||||
for j := i; j > 0 && s[j-1] > s[j]; j-- {
|
||||
s[j-1], s[j] = s[j], s[j-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGateParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_gate.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_gate.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
// The fixture entry texts (the generator's store, shipped here — the
|
||||
// truth inputs must carry their own state).
|
||||
fixtureTexts := map[int64]string{
|
||||
1: "the curator gates memory writes through worthiness",
|
||||
2: "black holes bend light near the event horizon",
|
||||
3: "black coffee in the morning keeps nick running",
|
||||
4: "the spine rebuild parity proofs live in store",
|
||||
5: "memory writes require the operator approval gate",
|
||||
6: "unrelated filler text about weather patterns",
|
||||
7: "curator provenance recovery from odysseus",
|
||||
8: "another filler row to pad the document frequency",
|
||||
}
|
||||
// df table shipped per case (the oracle's LIKE counts).
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Fn {
|
||||
case "dense_gate":
|
||||
query, _ := c.Input["query"].(string)
|
||||
hitsAny, _ := c.Input["hits"].([]interface{})
|
||||
var hits []DenseHit
|
||||
for _, h := range hitsAny {
|
||||
pair, _ := h.([]interface{})
|
||||
id, _ := pair[0].(float64)
|
||||
cos, _ := pair[1].(float64)
|
||||
hits = append(hits, DenseHit{RowID: int64(id), Cos: cos})
|
||||
}
|
||||
dfAny, _ := c.Expected["df"].(map[string]interface{})
|
||||
dfOf := func(word string) int {
|
||||
v, ok := dfAny[word]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
f, _ := v.(float64)
|
||||
return int(f)
|
||||
}
|
||||
total := int(c.Input["total"].(float64))
|
||||
qc := QueryContent(query)
|
||||
wantQAny, _ := c.Expected["q_content"].([]interface{})
|
||||
var wantQ []string
|
||||
for _, w := range wantQAny {
|
||||
s, _ := w.(string)
|
||||
wantQ = append(wantQ, s)
|
||||
}
|
||||
// The truth column ships the q_content SORTED (generator's
|
||||
// json.dumps of a set); compare sorted-to-sorted.
|
||||
sortStringsW(qc)
|
||||
jsonEq(t, c.Name, "q_content", qc, wantQ)
|
||||
got := DenseGate(hits, fixtureTexts, qc, dfOf, total)
|
||||
wantArr, _ := c.Expected["kept"].([]interface{})
|
||||
var want []DenseHit
|
||||
for _, h := range wantArr {
|
||||
pair, _ := h.([]interface{})
|
||||
id, _ := pair[0].(float64)
|
||||
cos, _ := pair[1].(float64)
|
||||
want = append(want, DenseHit{RowID: int64(id), Cos: cos})
|
||||
}
|
||||
jsonEq(t, c.Name, "kept", got, want)
|
||||
case "fusion_identity":
|
||||
dense := pairList(c.Input["dense"])
|
||||
bm25 := pairList(c.Input["bm25"])
|
||||
got := RankFusionEntries(dense, bm25)
|
||||
want, _ := c.Expected["fusion"].(map[string]interface{})
|
||||
jsonEq(t, c.Name, "fusion", got, want)
|
||||
case "constants":
|
||||
jsonEq(t, c.Name, "budget", RecallBudget, c.Expected["budget"])
|
||||
jsonEq(t, c.Name, "min_score", RecallMinScore, c.Expected["min_score"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,681 @@
|
||||
{
|
||||
"oracle": "src/rune/memory_store.py _py_score_recall_rows",
|
||||
"generated": "2026-10-07",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "hybrid",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.12
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.11
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.1
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.09
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "hybrid"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.53,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": null
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.7
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.6
|
||||
]
|
||||
],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
10.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
1,
|
||||
2.0
|
||||
]
|
||||
],
|
||||
"budget": 3,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.12,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 0.1241,
|
||||
"mode": "hybrid"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.11,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0691,
|
||||
"mode": "hybrid"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-05-01T10:00:00",
|
||||
"rrf": 0.1,
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0739,
|
||||
"mode": "hybrid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "dense_only_high_conf",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.06
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.055
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.7
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.6
|
||||
]
|
||||
],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.06,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 0.062,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.055,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0345,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-05-01T10:00:00",
|
||||
"rrf": 0.05,
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.037,
|
||||
"mode": "dense-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "dense_only_gap_abstain",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.06
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.055
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.75
|
||||
]
|
||||
],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.06,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 0.062,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.055,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.0345,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-05-01T10:00:00",
|
||||
"rrf": 0.05,
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"relevance": 0.037,
|
||||
"mode": "dense-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "dense_only_min_score",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
1,
|
||||
0.06
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.055
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.05
|
||||
]
|
||||
],
|
||||
"best_dense": 0.8,
|
||||
"mode": "dense-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "entry 1 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "entry 3 body",
|
||||
"importance": 0.52,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-05-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [
|
||||
[
|
||||
1,
|
||||
0.8
|
||||
],
|
||||
[
|
||||
2,
|
||||
0.7
|
||||
],
|
||||
[
|
||||
3,
|
||||
0.6
|
||||
]
|
||||
],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.9,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "lexical_only_gate",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
2,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"best_dense": 0.0,
|
||||
"mode": "lexical-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [],
|
||||
"bm25": [
|
||||
[
|
||||
2,
|
||||
10.0
|
||||
],
|
||||
[
|
||||
4,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
1,
|
||||
2.0
|
||||
]
|
||||
],
|
||||
"budget": 5,
|
||||
"min_score": 1.0,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": [
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-10-07T18:00:00",
|
||||
"rrf": 0.9,
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"relevance": 6.0994,
|
||||
"mode": "lexical-only"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"last_accessed": "2026-09-01T10:00:00",
|
||||
"rrf": 0.7,
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"relevance": 3.5783,
|
||||
"mode": "lexical-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "score",
|
||||
"name": "neither",
|
||||
"input": {
|
||||
"fusion": {
|
||||
"ranks": [
|
||||
[
|
||||
2,
|
||||
0.9
|
||||
],
|
||||
[
|
||||
4,
|
||||
0.7
|
||||
]
|
||||
],
|
||||
"best_dense": 0.0,
|
||||
"mode": "lexical-only"
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": 2,
|
||||
"text": "entry 2 body",
|
||||
"importance": 0.5,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.9,
|
||||
"temperature": 1.2,
|
||||
"last_accessed": "2026-10-07T18:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "entry 4 body",
|
||||
"importance": 0.51,
|
||||
"topic": "project",
|
||||
"entities": [
|
||||
"a"
|
||||
],
|
||||
"source": "fixture",
|
||||
"confidence": 0.6,
|
||||
"temperature": 0.8,
|
||||
"last_accessed": "2026-09-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"dense": [],
|
||||
"bm25": [],
|
||||
"budget": 5,
|
||||
"min_score": 0.3,
|
||||
"now": "2026-10-07T18:58:00"
|
||||
},
|
||||
"expected": {
|
||||
"scored": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
// truth_score_test.go — memory-store batch 3 parity gate: the scoring
|
||||
// kernel replays truth_score.json (live _py_score_recall_rows — contract
|
||||
// BUILDER-2026-10-07T18:57:06Z-c2429c).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScoreParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_score.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_score.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input map[string]interface{} `json:"input"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("unparseable: %v", err)
|
||||
}
|
||||
for _, c := range doc.Cases {
|
||||
fusion, _ := c.Input["fusion"].(map[string]interface{})
|
||||
rows := objList(c.Input["rows"])
|
||||
dense := pairList(c.Input["dense"])
|
||||
bm25 := pairList(c.Input["bm25"])
|
||||
budget := int(c.Input["budget"].(float64))
|
||||
minScore := c.Input["min_score"].(float64)
|
||||
nowStr, _ := c.Input["now"].(string)
|
||||
got := ScoreRecallRows(fusion, rows, dense, bm25, budget, minScore, nowStr)
|
||||
wantArr, _ := c.Expected["scored"].([]interface{})
|
||||
if len(got) != len(wantArr) {
|
||||
gb, _ := json.Marshal(got)
|
||||
wb, _ := json.Marshal(wantArr)
|
||||
t.Errorf("%s: scored = %d, want %d\n got %s\nwant %s",
|
||||
c.Name, len(got), len(wantArr), gb, wb)
|
||||
continue
|
||||
}
|
||||
for i, w := range wantArr {
|
||||
want, _ := w.(map[string]interface{})
|
||||
jsonEq(t, c.Name, field("scored[%d]", i), got[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// field — formatted comparison label.
|
||||
func field(format string, i int) string {
|
||||
return fmt.Sprintf(format, i)
|
||||
}
|
||||
@@ -3,14 +3,15 @@
|
||||
// queues messages, approves/rejects/executes.
|
||||
//
|
||||
// Usage:
|
||||
// message-queue add <message> [--channel <ch>] [--task-id <id>]
|
||||
// message-queue list [--status pending|approved|executed|rejected]
|
||||
// message-queue approve <id>
|
||||
// message-queue approve-all
|
||||
// message-queue reject <id>
|
||||
// message-queue clear
|
||||
// message-queue mark-executed <id>
|
||||
// message-queue detect <message>
|
||||
//
|
||||
// message-queue add <message> [--channel <ch>] [--task-id <id>]
|
||||
// message-queue list [--status pending|approved|executed|rejected]
|
||||
// message-queue approve <id>
|
||||
// message-queue approve-all
|
||||
// message-queue reject <id>
|
||||
// message-queue clear
|
||||
// message-queue mark-executed <id>
|
||||
// message-queue detect <message>
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -108,11 +109,19 @@ func main() {
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--channel":
|
||||
if i+1 < len(args) { channel = args[i+1]; i++ }
|
||||
if i+1 < len(args) {
|
||||
channel = args[i+1]
|
||||
i++
|
||||
}
|
||||
case "--task-id":
|
||||
if i+1 < len(args) { taskID = args[i+1]; i++ }
|
||||
if i+1 < len(args) {
|
||||
taskID = args[i+1]
|
||||
i++
|
||||
}
|
||||
default:
|
||||
if msg == "" { msg = args[i] }
|
||||
if msg == "" {
|
||||
msg = args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
qm := QueuedMessage{
|
||||
@@ -130,7 +139,9 @@ func main() {
|
||||
|
||||
case "list":
|
||||
status := ""
|
||||
if len(args) > 1 && args[0] == "--status" { status = args[1] }
|
||||
if len(args) > 1 && args[0] == "--status" {
|
||||
status = args[1]
|
||||
}
|
||||
for _, m := range q.Messages {
|
||||
if status == "" || m.Status == status {
|
||||
out, _ := json.Marshal(m)
|
||||
|
||||
Binary file not shown.
+45
-22
@@ -3,11 +3,12 @@
|
||||
// capabilities, fires events to registered hooks.
|
||||
//
|
||||
// Usage:
|
||||
// microkernel register <manifest-json>
|
||||
// microkernel unregister <name>
|
||||
// microkernel resolve <capability>
|
||||
// microkernel fire <event> [--data <json>]
|
||||
// microkernel status
|
||||
//
|
||||
// microkernel register <manifest-json>
|
||||
// microkernel unregister <name>
|
||||
// microkernel resolve <capability>
|
||||
// microkernel fire <event> [--data <json>]
|
||||
// microkernel status
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -25,7 +26,7 @@ type PluginManifest struct {
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Hooks map[string]string `json:"hooks"`
|
||||
Hooks map[string]interface{} `json:"hooks"`
|
||||
Dependencies []string `json:"dependencies"`
|
||||
Priority int `json:"priority"`
|
||||
AutoActivate bool `json:"auto_activate"`
|
||||
@@ -38,9 +39,9 @@ type PluginEntry struct {
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
Plugins map[string]PluginEntry `json:"plugins"`
|
||||
Capabilities map[string][]string `json:"capabilities"`
|
||||
Hooks map[string][]string `json:"hooks"`
|
||||
Plugins map[string]PluginEntry `json:"plugins"`
|
||||
Capabilities map[string][]string `json:"capabilities"`
|
||||
Hooks map[string][]string `json:"hooks"`
|
||||
}
|
||||
|
||||
func registryPath() string {
|
||||
@@ -58,7 +59,9 @@ func loadRegistry() Registry {
|
||||
Hooks: make(map[string][]string),
|
||||
}
|
||||
data, err := os.ReadFile(registryPath())
|
||||
if err != nil { return r }
|
||||
if err != nil {
|
||||
return r
|
||||
}
|
||||
json.Unmarshal(data, &r)
|
||||
return r
|
||||
}
|
||||
@@ -93,9 +96,9 @@ func main() {
|
||||
}
|
||||
manifest.AutoActivate = true
|
||||
registry.Plugins[manifest.Name] = PluginEntry{
|
||||
Manifest: manifest,
|
||||
Manifest: manifest,
|
||||
RegisteredAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
|
||||
Active: manifest.AutoActivate,
|
||||
Active: manifest.AutoActivate,
|
||||
}
|
||||
for _, cap := range manifest.Capabilities {
|
||||
registry.Capabilities[cap] = append(registry.Capabilities[cap], manifest.Name)
|
||||
@@ -110,21 +113,31 @@ func main() {
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "unregister":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: microkernel unregister <name>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: microkernel unregister <name>")
|
||||
os.Exit(1)
|
||||
}
|
||||
name := args[0]
|
||||
entry, ok := registry.Plugins[name]
|
||||
if !ok { fmt.Printf(`{"error": "plugin '%s' not registered"}`+"\n", name); return }
|
||||
if !ok {
|
||||
fmt.Printf(`{"error": "plugin '%s' not registered"}`+"\n", name)
|
||||
return
|
||||
}
|
||||
for cap, providers := range registry.Capabilities {
|
||||
var filtered []string
|
||||
for _, p := range providers {
|
||||
if p != name { filtered = append(filtered, p) }
|
||||
if p != name {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
registry.Capabilities[cap] = filtered
|
||||
}
|
||||
for event, listeners := range registry.Hooks {
|
||||
var filtered []string
|
||||
for _, l := range listeners {
|
||||
if l != name { filtered = append(filtered, l) }
|
||||
if l != name {
|
||||
filtered = append(filtered, l)
|
||||
}
|
||||
}
|
||||
registry.Hooks[event] = filtered
|
||||
}
|
||||
@@ -134,7 +147,10 @@ func main() {
|
||||
fmt.Printf(`{"status": "unregistered", "name": "%s"}`+"\n", name)
|
||||
|
||||
case "resolve":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: microkernel resolve <capability>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: microkernel resolve <capability>")
|
||||
os.Exit(1)
|
||||
}
|
||||
providers, ok := registry.Capabilities[args[0]]
|
||||
if !ok || len(providers) == 0 {
|
||||
fmt.Println("null")
|
||||
@@ -160,9 +176,14 @@ func main() {
|
||||
case "fire":
|
||||
event := ""
|
||||
data := ""
|
||||
if len(args) > 0 { event = args[0] }
|
||||
if len(args) > 0 {
|
||||
event = args[0]
|
||||
}
|
||||
for i := 1; i < len(args); i++ {
|
||||
if args[i] == "--data" && i+1 < len(args) { data = args[i+1]; i++ }
|
||||
if args[i] == "--data" && i+1 < len(args) {
|
||||
data = args[i+1]
|
||||
i++
|
||||
}
|
||||
}
|
||||
listeners, ok := registry.Hooks[event]
|
||||
if !ok || len(listeners) == 0 {
|
||||
@@ -179,11 +200,13 @@ func main() {
|
||||
|
||||
case "status":
|
||||
plugins := make([]string, 0)
|
||||
for name := range registry.Plugins { plugins = append(plugins, name) }
|
||||
for name := range registry.Plugins {
|
||||
plugins = append(plugins, name)
|
||||
}
|
||||
out, _ := json.MarshalIndent(map[string]interface{}{
|
||||
"plugins": plugins,
|
||||
"plugins": plugins,
|
||||
"capabilities_count": len(registry.Capabilities),
|
||||
"hooks_count": len(registry.Hooks),
|
||||
"hooks_count": len(registry.Hooks),
|
||||
}, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
// microkernel.go — factory batch 8: the plugin-registry primitives of
|
||||
// microkernel.py (register/unregister/resolve — the deterministic index
|
||||
// operations; file I/O stays reactive).
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Register — register a plugin from its manifest (map form, decoded like
|
||||
// the oracle receives it). Error text parity: "manifest must have a 'name'".
|
||||
func Register(reg *Registry, manifest map[string]interface{}, nowISO string) (PluginEntry, error) {
|
||||
name, _ := manifest["name"].(string)
|
||||
if name == "" {
|
||||
return PluginEntry{}, &manifestError{"manifest must have a 'name'"}
|
||||
}
|
||||
active := true
|
||||
if v, has := manifest["auto_activate"]; has {
|
||||
if b, ok := v.(bool); ok {
|
||||
active = b
|
||||
}
|
||||
}
|
||||
entry := PluginEntry{
|
||||
Manifest: manifestToStruct(manifest),
|
||||
RegisteredAt: nowISO,
|
||||
Active: active,
|
||||
}
|
||||
reg.Plugins[name] = entry
|
||||
if caps, ok := manifest["capabilities"].([]interface{}); ok {
|
||||
for _, raw := range caps {
|
||||
cap, _ := raw.(string)
|
||||
if cap == "" {
|
||||
continue
|
||||
}
|
||||
list := reg.Capabilities[cap]
|
||||
found := false
|
||||
for _, n := range list {
|
||||
if n == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
reg.Capabilities[cap] = append(list, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if hooks, ok := manifest["hooks"].(map[string]interface{}); ok {
|
||||
names := make([]string, 0, len(hooks))
|
||||
for ev := range hooks {
|
||||
names = append(names, ev)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, ev := range names {
|
||||
if enabled, _ := hooks[ev].(bool); enabled {
|
||||
list := reg.Hooks[ev]
|
||||
found := false
|
||||
for _, n := range list {
|
||||
if n == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
reg.Hooks[ev] = append(list, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// manifestToStruct — map manifest → the typed PluginManifest.
|
||||
func manifestToStruct(m map[string]interface{}) PluginManifest {
|
||||
b, _ := json.Marshal(m)
|
||||
var pm PluginManifest
|
||||
json.Unmarshal(b, &pm)
|
||||
return pm
|
||||
}
|
||||
|
||||
type manifestError struct{ msg string }
|
||||
|
||||
func (e *manifestError) Error() string { return e.msg }
|
||||
|
||||
// Unregister — remove from capability + hook indexes, delete the plugin.
|
||||
// Error: "plugin '{name}' not registered".
|
||||
func Unregister(reg *Registry, name string) error {
|
||||
if _, has := reg.Plugins[name]; !has {
|
||||
return &manifestError{"plugin '" + name + "' not registered"}
|
||||
}
|
||||
for cap, providers := range reg.Capabilities {
|
||||
for i, n := range providers {
|
||||
if n == name {
|
||||
reg.Capabilities[cap] = append(providers[:i], providers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for ev, listeners := range reg.Hooks {
|
||||
for i, n := range listeners {
|
||||
if n == name {
|
||||
reg.Hooks[ev] = append(listeners[:i], listeners[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(reg.Plugins, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve — highest-priority (lower = higher) ACTIVE plugin for a
|
||||
// capability; "" when none. Ties: Python's sort is stable — the first
|
||||
// candidate in index order wins.
|
||||
func Resolve(reg *Registry, capability string) string {
|
||||
candidates := reg.Capabilities[capability]
|
||||
if len(candidates) == 0 {
|
||||
return ""
|
||||
}
|
||||
type cand struct {
|
||||
name string
|
||||
priority int
|
||||
}
|
||||
var active []cand
|
||||
for _, name := range candidates {
|
||||
plugin, has := reg.Plugins[name]
|
||||
if !has || !plugin.Active {
|
||||
continue
|
||||
}
|
||||
priority := plugin.Manifest.Priority
|
||||
active = append(active, cand{name, priority})
|
||||
}
|
||||
if len(active) == 0 {
|
||||
return ""
|
||||
}
|
||||
sort.SliceStable(active, func(i, j int) bool { return active[i].priority < active[j].priority })
|
||||
return active[0].name
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"oracle": "src/rune/microkernel.py (live)",
|
||||
"generated": "2026-10-07",
|
||||
"frozen_now": "2026-10-07T23:35:00",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "state",
|
||||
"name": "after_registers",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"plugins": {
|
||||
"alpha": {
|
||||
"active": true,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
},
|
||||
"beta": {
|
||||
"active": true,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
},
|
||||
"gamma": {
|
||||
"active": false,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
},
|
||||
"delta": {
|
||||
"active": true,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"cap.a": [
|
||||
"alpha",
|
||||
"beta",
|
||||
"gamma"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"pre-turn": [
|
||||
"delta"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "resolve",
|
||||
"name": "priority_active",
|
||||
"input": {
|
||||
"capability": "cap.a"
|
||||
},
|
||||
"expected": {
|
||||
"resolved": "beta"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "resolve",
|
||||
"name": "unknown_cap",
|
||||
"input": {
|
||||
"capability": "cap.nope"
|
||||
},
|
||||
"expected": {
|
||||
"resolved": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "state",
|
||||
"name": "after_unregister",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"plugins": {
|
||||
"alpha": {
|
||||
"active": true,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
},
|
||||
"gamma": {
|
||||
"active": false,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
},
|
||||
"delta": {
|
||||
"active": true,
|
||||
"registered_at": "2026-10-07T23:35:00"
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"cap.a": [
|
||||
"alpha",
|
||||
"gamma"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"pre-turn": [
|
||||
"delta"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "resolve",
|
||||
"name": "after_unregister",
|
||||
"input": {
|
||||
"capability": "cap.a"
|
||||
},
|
||||
"expected": {
|
||||
"resolved": "alpha"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "errors",
|
||||
"name": "shapes",
|
||||
"input": {},
|
||||
"expected": {
|
||||
"register_empty": {
|
||||
"error": "manifest must have a 'name'"
|
||||
},
|
||||
"unregister_unknown": {
|
||||
"error": "plugin 'nope' not registered"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
// truth_microkernel_test.go — factory batch 8 parity gate.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMicrokernelParity(t *testing.T) {
|
||||
b, err := os.ReadFile("truth_microkernel.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth_microkernel.json missing: %v", err)
|
||||
}
|
||||
var doc struct {
|
||||
FrozenNow string `json:"frozen_now"`
|
||||
Cases []struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Expected map[string]interface{} `json:"expected"`
|
||||
} `json:"cases"`
|
||||
}
|
||||
json.Unmarshal(b, &doc)
|
||||
|
||||
reg := loadRegistry()
|
||||
regp := ®
|
||||
for _, c := range doc.Cases {
|
||||
switch c.Name {
|
||||
case "after_registers":
|
||||
// replay: the four registers
|
||||
_, _ = Register(®, map[string]interface{}{"name": "alpha", "capabilities": []interface{}{"cap.a"}, "priority": 50.0}, doc.FrozenNow)
|
||||
_, _ = Register(®, map[string]interface{}{"name": "beta", "capabilities": []interface{}{"cap.a"}, "priority": 10.0}, doc.FrozenNow)
|
||||
_, _ = Register(®, map[string]interface{}{"name": "gamma", "capabilities": []interface{}{"cap.a"}, "priority": 10.0, "auto_activate": false}, doc.FrozenNow)
|
||||
_, _ = Register(®, map[string]interface{}{"name": "delta", "hooks": map[string]interface{}{"pre-turn": true, "post-turn": false}}, doc.FrozenNow)
|
||||
jsonEqM(t, c.Name, "state", regShape(regp), c.Expected)
|
||||
case "priority_active":
|
||||
if got := Resolve(®, "cap.a"); got != strM(c.Expected["resolved"]) {
|
||||
t.Errorf("%s: resolve got %q want %q", c.Name, got, strM(c.Expected["resolved"]))
|
||||
}
|
||||
case "unknown_cap":
|
||||
if got := Resolve(®, "cap.nope"); got != strM(c.Expected["resolved"]) {
|
||||
t.Errorf("%s: resolve got %q want %v", c.Name, got, c.Expected["resolved"])
|
||||
}
|
||||
case "after_unregister":
|
||||
if _, isResolve := c.Expected["resolved"]; isResolve {
|
||||
if got := Resolve(®, "cap.a"); got != strM(c.Expected["resolved"]) {
|
||||
t.Errorf("%s: resolve got %q want %q", c.Name, got, strM(c.Expected["resolved"]))
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = Unregister(®, "beta")
|
||||
jsonEqM(t, c.Name, "state", regShape(regp), c.Expected)
|
||||
case "shapes":
|
||||
_, err1 := Register(®, map[string]interface{}{}, doc.FrozenNow)
|
||||
err2 := Unregister(®, "nope")
|
||||
jsonEqM(t, c.Name, "register_empty", errShape(err1), c.Expected["register_empty"])
|
||||
jsonEqM(t, c.Name, "unregister_unknown", errShape(err2), c.Expected["unregister_unknown"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// regShape — the persisted-shape projection (active + registered_at only,
|
||||
// matching the oracle's snapshot).
|
||||
func regShape(reg *Registry) map[string]interface{} {
|
||||
plugins := map[string]interface{}{}
|
||||
for k, v := range reg.Plugins {
|
||||
plugins[k] = map[string]interface{}{"active": v.Active, "registered_at": v.RegisteredAt}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"plugins": plugins,
|
||||
"capabilities": reg.Capabilities,
|
||||
"hooks": reg.Hooks,
|
||||
}
|
||||
}
|
||||
|
||||
func errShape(err error) map[string]interface{} {
|
||||
if err == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
func strM(v interface{}) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func jsonEqM(t *testing.T, name, field string, got, want interface{}) {
|
||||
t.Helper()
|
||||
gb, _ := json.Marshal(got)
|
||||
wb, _ := json.Marshal(want)
|
||||
if string(gb) != string(wb) {
|
||||
t.Errorf("%s: %s divergence:\n got %s\nwant %s", name, field, gb, wb)
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -30,8 +30,8 @@ type ChatMessage struct {
|
||||
}
|
||||
|
||||
type ModelInfo struct {
|
||||
Name string `json:"name"`
|
||||
ContextLen int `json:"context_length"`
|
||||
Name string `json:"name"`
|
||||
ContextLen int `json:"context_length"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
@@ -50,10 +50,14 @@ func (o *OllamaProvider) Chat(model string, messages []ChatMessage, options map[
|
||||
body := map[string]interface{}{
|
||||
"model": model, "messages": messages, "stream": false,
|
||||
}
|
||||
for k, v := range options { body[k] = v }
|
||||
for k, v := range options {
|
||||
body[k] = v
|
||||
}
|
||||
data, _ := json.Marshal(body)
|
||||
resp, err := o.Client.Post(o.BaseURL+"/api/chat", "application/json", bytes.NewReader(data))
|
||||
if err != nil { return ChatMessage{}, fmt.Errorf("ollama chat: %w", err) }
|
||||
if err != nil {
|
||||
return ChatMessage{}, fmt.Errorf("ollama chat: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Message ChatMessage `json:"message"`
|
||||
@@ -65,42 +69,50 @@ func (o *OllamaProvider) Chat(model string, messages []ChatMessage, options map[
|
||||
func (o *OllamaProvider) Show(model string) (ModelInfo, error) {
|
||||
data, _ := json.Marshal(map[string]string{"name": model})
|
||||
resp, err := o.Client.Post(o.BaseURL+"/api/show", "application/json", bytes.NewReader(data))
|
||||
if err != nil { return ModelInfo{}, err }
|
||||
if err != nil {
|
||||
return ModelInfo{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Details struct {
|
||||
ParameterSize string `json:"parameter_size"`
|
||||
Family string `json:"family"`
|
||||
ParameterSize string `json:"parameter_size"`
|
||||
Family string `json:"family"`
|
||||
} `json:"details"`
|
||||
ContextLength int `json:"context_length"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
return ModelInfo{
|
||||
Name: model,
|
||||
ContextLen: result.ContextLength,
|
||||
Name: model,
|
||||
ContextLen: result.ContextLength,
|
||||
Capabilities: []string{result.Details.Family, result.Details.ParameterSize},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OllamaProvider) ListModels() ([]string, error) {
|
||||
resp, err := o.Client.Get(o.BaseURL + "/api/tags")
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var result struct {
|
||||
Models []struct { Name string `json:"name"` } `json:"models"`
|
||||
Models []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"models"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
var names []string
|
||||
for _, m := range result.Models { names = append(names, m.Name) }
|
||||
for _, m := range result.Models {
|
||||
names = append(names, m.Name)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// ── Model routing ──
|
||||
|
||||
type ModelProfile struct {
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
ContextLen int `json:"context_length"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
ContextLen int `json:"context_length"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
@@ -127,11 +139,11 @@ func (r *ModelRouter) Route(taskDescription string) string {
|
||||
// ── Provider budget ──
|
||||
|
||||
type BudgetEntry struct {
|
||||
Provider string `json:"provider"`
|
||||
MonthlyUsed float64 `json:"monthly_used"`
|
||||
Provider string `json:"provider"`
|
||||
MonthlyUsed float64 `json:"monthly_used"`
|
||||
MonthlyLimit float64 `json:"monthly_limit"`
|
||||
TokensUsed int64 `json:"tokens_used"`
|
||||
TokenLimit int64 `json:"token_limit"`
|
||||
TokensUsed int64 `json:"tokens_used"`
|
||||
TokenLimit int64 `json:"token_limit"`
|
||||
}
|
||||
|
||||
type ProviderBudget struct {
|
||||
@@ -142,7 +154,9 @@ type ProviderBudget struct {
|
||||
func (b *ProviderBudget) Record(provider string, tokens int64) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.Providers == nil { b.Providers = make(map[string]BudgetEntry) }
|
||||
if b.Providers == nil {
|
||||
b.Providers = make(map[string]BudgetEntry)
|
||||
}
|
||||
e := b.Providers[provider]
|
||||
e.TokensUsed += tokens
|
||||
b.Providers[provider] = e
|
||||
@@ -155,7 +169,9 @@ func main() {
|
||||
}
|
||||
|
||||
baseURL := os.Getenv("OLLAMA_URL")
|
||||
if baseURL == "" { baseURL = "http://localhost:11434" }
|
||||
if baseURL == "" {
|
||||
baseURL = "http://localhost:11434"
|
||||
}
|
||||
provider := NewOllamaProvider(baseURL)
|
||||
|
||||
cmd := os.Args[1]
|
||||
@@ -163,29 +179,47 @@ func main() {
|
||||
|
||||
switch cmd {
|
||||
case "chat":
|
||||
if len(args) < 2 { fmt.Fprintln(os.Stderr, "usage: model-connect chat <model> <message>"); os.Exit(1) }
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: model-connect chat <model> <message>")
|
||||
os.Exit(1)
|
||||
}
|
||||
model, message := args[0], args[1]
|
||||
messages := []ChatMessage{{Role: "user", Content: message}}
|
||||
resp, err := provider.Chat(model, messages, nil)
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
out, _ := json.MarshalIndent(resp, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "list":
|
||||
models, err := provider.ListModels()
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
out, _ := json.MarshalIndent(models, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "show":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: model-connect show <model>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: model-connect show <model>")
|
||||
os.Exit(1)
|
||||
}
|
||||
info, err := provider.Show(args[0])
|
||||
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err); os.Exit(1) }
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
out, _ := json.MarshalIndent(info, "", " ")
|
||||
fmt.Println(string(out))
|
||||
|
||||
case "route":
|
||||
if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: model-connect route <task>"); os.Exit(1) }
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: model-connect route <task>")
|
||||
os.Exit(1)
|
||||
}
|
||||
router := &ModelRouter{
|
||||
Profiles: map[string]ModelProfile{
|
||||
"qwen2.5:3b": {Name: "qwen2.5:3b", Provider: "ollama", ContextLen: 32768, Capabilities: []string{"local", "fast", "small"}},
|
||||
|
||||
Loaded 100 of 306 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user