feat(curator): port to FULL Go — dichotomy wave
DICHOTOMY VERDICT: GO (Reactive → Go per store/rebase/classification.json). Name-preserving full-module port of src/rune/curator.py (43 fns) into src/go/curator/ (package main, modernc.org/sqlite pure-Go store seam). Truth: scripts/gen_curator_truth.py freezes 162 rows from the live oracle (paths isolated, dependency cores pinned RUNE_*_CORE=python, clock frozen, subprocess faked, embed tabled, aaak/memory_store blocked, drift-gate/audit/ drift-ledger seams injected, deep-copied inputs, per-row setup seeds). Battery truth_test.go replays every row verbatim: 162/162 + live re-verified + generator deterministic (byte-identical re-freeze). Python oracle untouched (no facade delegation — strangler surface stays Python by design); Python contract tests green.
This commit is contained in:
1 parent
e29cc5ee51
commit
03ef8132a2
16 files changed
+10077
No files matched your search
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
module rune/curator
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require modernc.org/sqlite v1.49.1
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U=
|
||||
modernc.org/sqlite v1.49.1/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
@@ -0,0 +1,160 @@
|
||||
// grading.go — parity port of src/rune/grading.py (Python fallback surface,
|
||||
// RUNE_GR_CORE=python pinned in the truth generator).
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ruleWeak = []string{"should", "prefer", "instead of", "lead with", "first", "before ",
|
||||
"avoid", "favour", "keep ", "stop ", "start ", "use "}
|
||||
var ruleStrong = []string{"always", "never ", "must", "do not", "don't", "i always",
|
||||
"i must", "i should"}
|
||||
var affirmSignals = []string{"i protect", "i am", "i reveal", "i factor", "i dissolve",
|
||||
"i respect", "i teach", "i make", "i write", "i use the tiers", "i check", "i stop"}
|
||||
var userFactSignals = []string{"name", "email", "role", "lives in", "located in", "daw",
|
||||
"os:", "shell", "has a dog", "has a cat", "prefers", "works in", "works at",
|
||||
"teaches", "studies", "interested in", "financial", "income", "dietary",
|
||||
"location", "my home", "my machine"}
|
||||
var userAttrDecisive = []string{"prefers", "lives in", "located in", "teaches",
|
||||
"studies", "works at", "works in", "interested in", "dietary", "income", "financial"}
|
||||
var projectSignals = []string{"project", "current work", "task", "todo", "goal",
|
||||
"milestone", "deadline", "in progress", "blocked", "decision", "spec", "wip",
|
||||
"building ", "developing ", "course", "channel", "migration", "feature",
|
||||
"fixing", "working on", "active projects", "current state"}
|
||||
var constitutionSignals = []string{"add this to the constitution", "constitution",
|
||||
"always do", "i will always", "must always", "inviolable"}
|
||||
var docSignals = []string{"book", "chapter", "document", "transcript", "pdf", "epub",
|
||||
"verbatim", "diary entry", "palace drawer", "gutenberg"}
|
||||
|
||||
// affirmDict keeps the oracle's insertion order — the first matching key wins.
|
||||
var affirmDict = []struct{ key, positive string }{
|
||||
{"never defend billionaires", "I prioritise collective wellbeing and factual, caring explanations"},
|
||||
{"don't defend billionaires", "I prioritise collective wellbeing and factual, caring explanations"},
|
||||
{"never equate socialism with big government", "I describe socialism accurately, on its own terms"},
|
||||
{"never offer rigid plans", "I offer three gentle options when direction is sought"},
|
||||
{"don't offer rigid plans", "I offer three gentle options when direction is sought"},
|
||||
{"never hedge", "I answer directly, without hedging"},
|
||||
{"don't hedge", "I answer directly, without hedging"},
|
||||
{"do not overresearch", "I act on the quick answer when one will do"},
|
||||
{"don't overresearch", "I act on the quick answer when one will do"},
|
||||
{"do not overclaim", "I name evidence as evidence and confirmed causes as confirmed"},
|
||||
{"never overclaim", "I name evidence as evidence and confirmed causes as confirmed"},
|
||||
{"do not stall", "I act decisively once the root cause is understood"},
|
||||
{"don't stall", "I act decisively once the root cause is understood"},
|
||||
{"do not touch the production server", "I protect the production environment and ask before touching it"},
|
||||
{"never touch the production server", "I protect the production environment and ask before touching it"},
|
||||
{"ask permission before modifying anything on the server", "I check with the user before touching anything on the server"},
|
||||
{"do not use capitalistic frameworks", "I use factual, material, caring, non-blaming explanations"},
|
||||
{"never use hustle frameworks", "I use factual, material, caring, non-blaming explanations"},
|
||||
{"do not offer plans", "I offer three gentle options when direction is sought"},
|
||||
{"don't offer plans", "I offer three gentle options when direction is sought"},
|
||||
{"do not search endlessly", "I stop, research properly, and find the root cause"},
|
||||
{"don't iterate endlessly", "I stop, research properly, and find the root cause"},
|
||||
}
|
||||
|
||||
func containsAny(s string, subs []string) int {
|
||||
n := 0
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(s, sub) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func hasAny(s string, subs []string) bool {
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AffirmRule rewrites a perceived demand/rule as a positive affirmation; only
|
||||
// known-correct rewrites are applied (AFFIRM_DICT), unknown prohibitions pass
|
||||
// through unchanged.
|
||||
func AffirmRule(fact string) string {
|
||||
f := strings.TrimSpace(strings.TrimLeft(strings.TrimSpace(fact), "-* "))
|
||||
low := strings.Join(strings.Fields(strings.ToLower(f)), " ")
|
||||
alt := strings.ReplaceAll(strings.Trim(low, "."), " ", " ")
|
||||
for _, pair := range affirmDict {
|
||||
if strings.Contains(low, pair.key) || strings.Contains(alt, pair.key) {
|
||||
return pair.positive
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// GradeFact returns the tier (G0..G4) for a fact/rule based on type + language.
|
||||
func GradeFact(fact, target string) string {
|
||||
userName := strings.TrimSpace(strings.ToLower(os.Getenv("MEMORY_USER_NAME")))
|
||||
low := strings.ToLower(fact)
|
||||
if hasAny(low, constitutionSignals) {
|
||||
return "constitution"
|
||||
}
|
||||
if hasAny(low, docSignals) {
|
||||
return "mempalace"
|
||||
}
|
||||
ruleScore := containsAny(low, ruleStrong)*2 + containsAny(low, ruleWeak)
|
||||
affirmScore := containsAny(low, affirmSignals)
|
||||
userScore := containsAny(low, userFactSignals)
|
||||
projScore := containsAny(low, projectSignals)
|
||||
if target == "operating" {
|
||||
return "operating"
|
||||
}
|
||||
if target == "persona" {
|
||||
return "persona"
|
||||
}
|
||||
nameToken := fmt.Sprintf(" %s ", userName)
|
||||
nameTokenPresent := false
|
||||
if userName != "" {
|
||||
nameTokenPresent = strings.Contains(" "+low+" ", nameToken)
|
||||
}
|
||||
decisiveAttr := hasAny(low, userAttrDecisive)
|
||||
isPersonalAttr := (decisiveAttr || userScore >= 2) &&
|
||||
!hasAny(low, []string{"i ", "do not", "always", "never "}) &&
|
||||
!nameTokenPresent
|
||||
if isPersonalAttr {
|
||||
return "human"
|
||||
}
|
||||
if ruleScore >= 2 || affirmScore >= 1 {
|
||||
return "operating"
|
||||
}
|
||||
if target == "project" || projScore >= 2 {
|
||||
return "project"
|
||||
}
|
||||
if userScore >= 2 || target == "human" {
|
||||
return "human"
|
||||
}
|
||||
if target == "skill" || strings.Contains(low, "procedure") || strings.Contains(low, "steps") {
|
||||
return "skill"
|
||||
}
|
||||
return "hindsight"
|
||||
}
|
||||
|
||||
// TierOf — human label for a grade, for journaling / canary output.
|
||||
func TierOf(grade string) string {
|
||||
switch grade {
|
||||
case "constitution":
|
||||
return "G0"
|
||||
case "operating":
|
||||
return "G1"
|
||||
case "persona":
|
||||
return "G2p"
|
||||
case "human":
|
||||
return "G2"
|
||||
case "project":
|
||||
return "G2"
|
||||
case "skill":
|
||||
return "P"
|
||||
case "mempalace":
|
||||
return "G4"
|
||||
case "hindsight":
|
||||
return "G3"
|
||||
}
|
||||
return "G3"
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
// lexicon.go — parity port of src/rune/lexicon_evolution.py (Python fallback
|
||||
// surface, RUNE_LE_CORE=python pinned in the truth generator). The pure
|
||||
// lexical decisions: content words, light stemming, distinctiveness (df <
|
||||
// 5%), coherence verdicts, promotion eligibility.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var leWordRe = regexp.MustCompile(`[a-z]{4,}`)
|
||||
|
||||
// SAME stopword set as memory_store._STOPWORDS (single source of truth would
|
||||
// be ideal; keep in sync — see lexicon_evolution.py).
|
||||
var leStop = map[string]bool{
|
||||
"the": true, "and": true, "for": true, "with": true, "that": true, "this": true,
|
||||
"from": true, "into": true, "when": true, "then": true, "were": true, "have": true,
|
||||
"been": true, "will": true, "was": true, "are": true, "but": true, "not": true,
|
||||
"you": true, "your": true, "also": true, "its": true, "his": true, "her": true,
|
||||
"him": true, "over": true, "under": true, "them": true, "they": true,
|
||||
"there": true, "about": true, "after": true, "what": true, "which": true,
|
||||
"who": true, "how": true, "why": true, "where": true, "while": true, "just": true,
|
||||
"more": true, "each": true, "than": true, "very": true, "such": true,
|
||||
"some": true, "only": true, "prefers": true, "needs": true, "wants": true,
|
||||
"likes": true,
|
||||
}
|
||||
|
||||
// Hard stop for evolution: generic framing words never carry a relation.
|
||||
var leFraming = map[string]bool{
|
||||
"work": true, "always": true, "way": true, "thing": true, "value": true,
|
||||
"identity": true, "persona": true, "someone": true, "something": true,
|
||||
"never": true, "before": true, "after": true, "then": true, "when": true,
|
||||
"while": true, "must": true, "should": true, "will": true, "would": true,
|
||||
"could": true, "can": true, "have": true, "has": true, "had": true,
|
||||
"been": true, "being": true, "used": true, "use": true, "uses": true,
|
||||
"make": true, "makes": true, "made": true, "keep": true, "keeps": true,
|
||||
"take": true, "takes": true, "took": true, "get": true, "gets": true,
|
||||
"got": true, "going": true,
|
||||
}
|
||||
|
||||
var leNegation = map[string]bool{
|
||||
"not": true, "never": true, "don't": true, "doesn't": true, "cannot": true,
|
||||
"can't": true, "no": true, "nor": true, "neither": true, "without": true,
|
||||
"avoid": true, "reject": true, "against": true, "refuses": true, "refuse": true,
|
||||
"opposed": true,
|
||||
}
|
||||
|
||||
const leDistinctiveDF = 0.05
|
||||
|
||||
func leContentWords(text string) []string {
|
||||
var out []string
|
||||
for _, w := range leWordRe.FindAllString(strings.ToLower(text), -1) {
|
||||
if !leStop[w] && !leFraming[w] {
|
||||
out = append(out, w)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// leStem — light morphological fold; suffix rules ordered longest-first.
|
||||
func leStem(w string) string {
|
||||
rules := []struct{ suf, fold string }{
|
||||
{"ationally", ""}, {"ableness", ""}, {"iveness", ""},
|
||||
{"ation", ""}, {"ating", ""}, {"ative", "e"},
|
||||
{"atively", "e"}, {"ability", ""}, {"ities", "y"},
|
||||
{"ingly", ""}, {"fully", ""}, {"ness", ""}, {"ment", ""},
|
||||
{"itive", "e"}, {"atable", "e"}, {"ated", ""}, {"ates", "e"},
|
||||
{"ions", ""}, {"ings", ""}, {"able", "e"}, {"ible", "e"},
|
||||
{"ing", "e"}, {"ed", ""}, {"es", ""}, {"ly", ""}, {"s", ""}, {"e", ""},
|
||||
}
|
||||
for _, r := range rules {
|
||||
if len(w)-len(r.suf) >= 4 && strings.HasSuffix(w, r.suf) {
|
||||
w = w[:len(w)-len(r.suf)]
|
||||
if r.fold != "" {
|
||||
w += r.fold
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// y -> i fold (honesty -> honest)
|
||||
if len(w) > 4 && strings.HasSuffix(w, "y") && !strings.ContainsRune("aeiouy", rune(w[len(w)-2])) {
|
||||
w = w[:len(w)-1]
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func leStemSet(text string) map[string]bool {
|
||||
set := map[string]bool{}
|
||||
for _, w := range leWordRe.FindAllString(strings.ToLower(text), -1) {
|
||||
set[leStem(w)] = true
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func leDistinctiveTerms(fact string, corpusTexts []string) map[string]bool {
|
||||
total := len(corpusTexts)
|
||||
if total == 0 {
|
||||
total = 1
|
||||
}
|
||||
factTerms := map[string]bool{}
|
||||
for _, w := range leContentWords(fact) {
|
||||
factTerms[w] = true
|
||||
}
|
||||
if len(factTerms) == 0 {
|
||||
return map[string]bool{}
|
||||
}
|
||||
docTerms := map[string]int{}
|
||||
for _, t := range corpusTexts {
|
||||
for s := range leStemSet(t) {
|
||||
docTerms[s]++
|
||||
}
|
||||
}
|
||||
out := map[string]bool{}
|
||||
for t := range factTerms {
|
||||
if float64(docTerms[leStem(t)]) < maxFloat(float64(total)*leDistinctiveDF, 1.5) {
|
||||
out[t] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func maxFloat(a, b float64) float64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func leSharedDistinctive(fact string, existingValues, corpusTexts []string) map[string]bool {
|
||||
distinct := leDistinctiveTerms(fact, corpusTexts)
|
||||
if len(distinct) == 0 {
|
||||
return map[string]bool{}
|
||||
}
|
||||
shared := map[string]bool{}
|
||||
for _, val := range existingValues {
|
||||
for _, w := range leWordRe.FindAllString(strings.ToLower(val), -1) {
|
||||
s := leStem(w)
|
||||
for term := range distinct {
|
||||
if leStem(term) == s {
|
||||
shared[term] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return shared
|
||||
}
|
||||
|
||||
// Coherence — classify a new value against existing ones, lexicon-aware:
|
||||
// "conflict" | "consistent" | "novel".
|
||||
func Coherence(fact string, existingValues []string, corpusTexts []string) string {
|
||||
var values []string
|
||||
for _, v := range existingValues {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return "novel"
|
||||
}
|
||||
corpus := corpusTexts
|
||||
if len(corpus) == 0 {
|
||||
corpus = values
|
||||
}
|
||||
shared := leSharedDistinctive(fact, values, corpus)
|
||||
if len(shared) == 0 {
|
||||
return "novel"
|
||||
}
|
||||
low := strings.ToLower(fact)
|
||||
hasNeg := false
|
||||
for n := range leNegation {
|
||||
if strings.Contains(low, n) {
|
||||
hasNeg = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasNeg {
|
||||
for t := range shared {
|
||||
if strings.Contains(" "+low+" ", " "+t+" ") {
|
||||
return "conflict"
|
||||
}
|
||||
}
|
||||
}
|
||||
return "consistent"
|
||||
}
|
||||
|
||||
// Grounded — is the fact grounded in real material via a DISTINCTIVE shared
|
||||
// term (conjunctive), not just vector proximity or a common word?
|
||||
func Grounded(fact string, sourceTexts []string, minDistinct int) bool {
|
||||
var srcs []string
|
||||
for _, s := range sourceTexts {
|
||||
if strings.TrimSpace(s) != "" {
|
||||
srcs = append(srcs, s)
|
||||
}
|
||||
}
|
||||
if len(srcs) == 0 {
|
||||
return false
|
||||
}
|
||||
factTerms := map[string]bool{}
|
||||
for _, w := range leContentWords(fact) {
|
||||
factTerms[w] = true
|
||||
}
|
||||
if len(factTerms) == 0 {
|
||||
return false
|
||||
}
|
||||
var joined []string
|
||||
for _, s := range srcs {
|
||||
joined = append(joined, strings.ToLower(s))
|
||||
}
|
||||
stems := leStemSet(strings.Join(joined, " "))
|
||||
hits := 0
|
||||
for t := range factTerms {
|
||||
if stems[leStem(t)] {
|
||||
hits++
|
||||
if hits >= minDistinct {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PromoteEligible — is a persona/constitution promotion eligible, lexicon-aware?
|
||||
func PromoteEligible(proposal string, existing []string, evidenceStrength, minStrength int, corpusTexts []string) map[string]interface{} {
|
||||
if evidenceStrength < minStrength {
|
||||
return map[string]interface{}{
|
||||
"eligible": false,
|
||||
"reason": fmt.Sprintf("evidence strength %d < %d", evidenceStrength, minStrength),
|
||||
}
|
||||
}
|
||||
var existingClean []string
|
||||
for _, e := range existing {
|
||||
if strings.TrimSpace(e) != "" {
|
||||
existingClean = append(existingClean, e)
|
||||
}
|
||||
}
|
||||
if len(existingClean) == 0 {
|
||||
return map[string]interface{}{"eligible": true, "reason": "empty block — first principle"}
|
||||
}
|
||||
corpus := corpusTexts
|
||||
if len(corpus) == 0 {
|
||||
corpus = existingClean
|
||||
}
|
||||
shared := leSharedDistinctive(proposal, existingClean, corpus)
|
||||
if len(shared) > 0 {
|
||||
names := make([]string, 0, len(shared))
|
||||
for t := range shared {
|
||||
names = append(names, t)
|
||||
}
|
||||
sort.Strings(names)
|
||||
if len(names) > 3 {
|
||||
names = names[:3]
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"eligible": true,
|
||||
"reason": fmt.Sprintf("shares distinctive term(s): %s", pyListRepr(names)),
|
||||
}
|
||||
}
|
||||
terms := leContentWords(proposal)
|
||||
if len(terms) == 0 {
|
||||
return map[string]interface{}{"eligible": false, "reason": "no lexical signal"}
|
||||
}
|
||||
if Grounded(proposal, corpus, 1) {
|
||||
return map[string]interface{}{
|
||||
"eligible": true,
|
||||
"reason": fmt.Sprintf("novel axis grounded in corpus (%d terms)", len(terms)),
|
||||
}
|
||||
}
|
||||
return map[string]interface{}{"eligible": false, "reason": "novel axis not grounded in absorbed material — parked"}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// main.go — curator CLI: replay the frozen oracle truth battery.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 || os.Args[1] != "replay" {
|
||||
fmt.Fprintln(os.Stderr, "usage: curator replay <truth.json>")
|
||||
os.Exit(2)
|
||||
}
|
||||
path := "truth.json"
|
||||
if len(os.Args) > 2 {
|
||||
path = os.Args[2]
|
||||
}
|
||||
fails := RunTruth(path)
|
||||
if len(fails) > 0 {
|
||||
for _, f := range fails {
|
||||
fmt.Println(f)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("PARITY OK: %d cases exact-equal vs the Python oracle\n", truthRowCount())
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// main_cli.go — main() CLI surface replay. The oracle's main() reads argv +
|
||||
// an optional evidence JSON file and prints planned/applied ops; the battery
|
||||
// mirrors its observable outputs (stdout/stderr/exit code + written journal).
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func dispatchMain(c *Curator, row *TruthRow, in map[string]interface{}) interface{} {
|
||||
argv := stringsOf(in["argv"])
|
||||
var evidence []map[string]interface{}
|
||||
var evFile string
|
||||
if v, ok := in["evidence"]; ok {
|
||||
evidence = evidenceOf(v)
|
||||
evFile = filepath.Join(filepath.Dir(c.StoreDB), "evidence.json")
|
||||
b, _ := json.Marshal(evidence)
|
||||
os.WriteFile(evFile, b, 0o644)
|
||||
}
|
||||
var out, errBuf bytes.Buffer
|
||||
c.Out = &out
|
||||
c.Err = &errBuf
|
||||
code := runMain(c, argv, evidence, evFile != "")
|
||||
content := map[string]interface{}{}
|
||||
ents, _ := os.ReadDir(c.JournalDir)
|
||||
for _, e := range ents {
|
||||
b, _ := os.ReadFile(filepath.Join(c.JournalDir, e.Name()))
|
||||
content[e.Name()] = string(b)
|
||||
}
|
||||
res := map[string]interface{}{"stdout": out.String(), "stderr": errBuf.String(), "code": float64(code)}
|
||||
if row.Name == "apply_output" {
|
||||
res["journal"] = content
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// runMain mirrors curator.main()'s observable behavior.
|
||||
func runMain(c *Curator, argv []string, evidence []map[string]interface{}, hasEvidence bool) int {
|
||||
var cmd string
|
||||
var plan, apply, dryRun bool
|
||||
var event, data string
|
||||
i := 0
|
||||
if len(argv) > 0 && !strings.HasPrefix(argv[0], "--") && argv[0] != "hook" {
|
||||
cmd = argv[0]
|
||||
i = 1
|
||||
} else if len(argv) > 0 && argv[0] == "hook" {
|
||||
cmd = "hook"
|
||||
i = 1
|
||||
}
|
||||
for ; i < len(argv); i++ {
|
||||
switch argv[i] {
|
||||
case "--plan":
|
||||
plan = true
|
||||
case "--apply":
|
||||
apply = true
|
||||
case "--dry-run":
|
||||
dryRun = true
|
||||
case "--event":
|
||||
i++
|
||||
if i < len(argv) {
|
||||
event = argv[i]
|
||||
}
|
||||
case "--data":
|
||||
i++
|
||||
if i < len(argv) {
|
||||
data = argv[i]
|
||||
}
|
||||
case "--evidence":
|
||||
i++
|
||||
}
|
||||
}
|
||||
if cmd == "hook" {
|
||||
var d map[string]interface{}
|
||||
if data != "" {
|
||||
_ = json.Unmarshal([]byte(data), &d)
|
||||
}
|
||||
res := c.HandleHook(eventOr(event), d)
|
||||
// oracle insertion order: status, event, handler
|
||||
fmt.Fprintf(c.Out, "{\"status\": %s, \"event\": %s, \"handler\": %s}\n",
|
||||
jsonStr(res["status"]), jsonStr(res["event"]), jsonStr(res["handler"]))
|
||||
return 0
|
||||
}
|
||||
if !hasEvidence {
|
||||
// memory_store seam blocked → no auto evidence → exit 1
|
||||
fmt.Fprintln(c.Err, "No evidence provided and no recent entries found.")
|
||||
return 1
|
||||
}
|
||||
doApply := apply || (!plan && !dryRun)
|
||||
journalLines, ops := c.RunPlan(evidence, doApply)
|
||||
if !doApply {
|
||||
fmt.Fprintln(c.Out, "=== planned ops (dry-run) ===")
|
||||
for _, j := range journalLines {
|
||||
fmt.Fprintf(c.Out, " %-8s → %s | %s | %s\n",
|
||||
strOr(j["op"], ""), strOr(j["target"], ""),
|
||||
truncate(strOr(j["after"], ""), 70), strOr(j["reason"], ""))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
written := 0
|
||||
for _, j := range journalLines {
|
||||
if strOr(j["op"], "") == "PROPOSE" {
|
||||
c.JournalAppend(j)
|
||||
written++
|
||||
} else if r := strOr(j["reason"], ""); r == "" || r == "dry-run" {
|
||||
c.JournalAppend(j)
|
||||
written++
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(c.Out, "Applied %d ops; %d logged to journal; index saved.\n", len(ops), written)
|
||||
for _, j := range journalLines {
|
||||
fmt.Fprintf(c.Out, " %-8s → %s | %s\n",
|
||||
strOr(j["op"], ""), strOr(j["target"], ""), truncate(strOr(j["after"], ""), 70))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func eventOr(e string) string {
|
||||
if e == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// Package main — curator dichotomy port (GO verdict: Reactive → Go).
|
||||
// om.go — order-preserving JSON objects. The candidate ledger and the entry
|
||||
// index iterate keys in insertion order (Python dict law), so idx inputs are
|
||||
// decoded into OM instead of map[string]any wherever iteration order can
|
||||
// surface in `want`.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OM — an ordered JSON object.
|
||||
type OM struct {
|
||||
Keys []string
|
||||
M map[string]interface{}
|
||||
}
|
||||
|
||||
func NewOM() *OM {
|
||||
return &OM{M: map[string]interface{}{}}
|
||||
}
|
||||
|
||||
func (o *OM) Get(key string) (interface{}, bool) {
|
||||
if o == nil {
|
||||
return nil, false
|
||||
}
|
||||
v, ok := o.M[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (o *OM) Set(key string, v interface{}) {
|
||||
if _, ok := o.M[key]; !ok {
|
||||
o.Keys = append(o.Keys, key)
|
||||
}
|
||||
o.M[key] = v
|
||||
}
|
||||
|
||||
func (o *OM) Delete(key string) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := o.M[key]; ok {
|
||||
delete(o.M, key)
|
||||
for i, k := range o.Keys {
|
||||
if k == key {
|
||||
o.Keys = append(o.Keys[:i], o.Keys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OM) Len() int {
|
||||
if o == nil {
|
||||
return 0
|
||||
}
|
||||
return len(o.Keys)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes preserving key order; nested objects decode as *OM.
|
||||
func (o *OM) UnmarshalJSON(b []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(b))
|
||||
dec.UseNumber()
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tok != json.Delim('{') {
|
||||
return fmt.Errorf("OM: expected object, got %v", tok)
|
||||
}
|
||||
o.Keys = nil
|
||||
o.M = map[string]interface{}{}
|
||||
for dec.More() {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := kt.(string)
|
||||
v, err := decodeOMValue(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Keys = append(o.Keys, key)
|
||||
o.M[key] = v
|
||||
}
|
||||
_, err = dec.Token() // closing }
|
||||
return err
|
||||
}
|
||||
|
||||
func decodeOMValue(dec *json.Decoder) (interface{}, error) {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeOMToken(dec, tok)
|
||||
}
|
||||
|
||||
func decodeOMToken(dec *json.Decoder, tok json.Token) (interface{}, error) {
|
||||
switch t := tok.(type) {
|
||||
case json.Delim:
|
||||
switch t {
|
||||
case '{':
|
||||
inner := NewOM()
|
||||
for dec.More() {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := decodeOMValue(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inner.Keys = append(inner.Keys, kt.(string))
|
||||
inner.M[kt.(string)] = v
|
||||
}
|
||||
if _, err := dec.Token(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inner, nil
|
||||
case '[':
|
||||
var arr []interface{}
|
||||
for dec.More() {
|
||||
v, err := decodeOMValue(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, v)
|
||||
}
|
||||
if _, err := dec.Token(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arr == nil {
|
||||
arr = []interface{}{}
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
return nil, fmt.Errorf("OM: unexpected delim %v", t)
|
||||
default:
|
||||
return tok, nil
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON re-emits in preserved order.
|
||||
func (o *OM) MarshalJSON() ([]byte, error) {
|
||||
if o == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
for i, k := range o.Keys {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
kb, _ := json.Marshal(k)
|
||||
buf.Write(kb)
|
||||
buf.WriteByte(':')
|
||||
vb, err := json.Marshal(o.M[k])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(vb)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// -- value coercion helpers (Python .get semantics) --------------------------
|
||||
|
||||
func strOf(v interface{}) string {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func strOr(v interface{}, def string) string {
|
||||
if s, ok := v.(string); ok && s != "" {
|
||||
return s
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func intOf(v interface{}) int {
|
||||
switch n := v.(type) {
|
||||
case json.Number:
|
||||
f, _ := n.Float64()
|
||||
return int(f)
|
||||
case float64:
|
||||
return int(n)
|
||||
case int:
|
||||
return n
|
||||
case int64:
|
||||
return int(n)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func floatOf(v interface{}) float64 {
|
||||
switch n := v.(type) {
|
||||
case json.Number:
|
||||
f, _ := n.Float64()
|
||||
return f
|
||||
case float64:
|
||||
return n
|
||||
case int:
|
||||
return float64(n)
|
||||
case int64:
|
||||
return float64(n)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func intOr(v interface{}, def int) int {
|
||||
if v == nil {
|
||||
return def
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case json.Number:
|
||||
f, _ := n.Float64()
|
||||
return int(f)
|
||||
case float64:
|
||||
return int(n)
|
||||
case int:
|
||||
return n
|
||||
case string:
|
||||
if n == "" {
|
||||
return def
|
||||
}
|
||||
}
|
||||
return intOf(v)
|
||||
}
|
||||
|
||||
// pyListRepr renders a []string the way Python's repr does for the le reasons.
|
||||
func pyListRepr(ss []string) string {
|
||||
quoted := make([]string, len(ss))
|
||||
for i, s := range ss {
|
||||
quoted[i] = "'" + s + "'"
|
||||
}
|
||||
return "[" + strings.Join(quoted, ", ") + "]"
|
||||
}
|
||||
|
||||
func sortedCopy(ss []string) []string {
|
||||
out := append([]string(nil), ss...)
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
|
||||
// scripts/gen_curator_truth.py against the live Python oracle with paths
|
||||
// isolated, dependency cores pinned, clock frozen, subprocess faked, embed
|
||||
// tabled, aaak/memory_store blocked, drift gate + audit ledger + drift-ledger
|
||||
// seams injected) and re-executes every row through this Go port.
|
||||
//
|
||||
// Seams mirrored here (one-to-one with the generator's declared seams):
|
||||
// - setup.db_missing → StoreDB points at a path in a nonexistent dir
|
||||
// - setup.db_rows → fresh sqlite store seeded with the same rows
|
||||
// - setup.transcripts → transcripts dir rewritten with the same files +
|
||||
// mtimes (age_days before the frozen instant)
|
||||
// - setup.index / index_missing → index file written / absent
|
||||
// - setup.idx_init → the in-memory idx argument
|
||||
// - setup.skills → skills dir seeded with the same files
|
||||
// - input.vecs → the embed seam table for semantic_candidate_key
|
||||
// - input.seam_drift_gate → "reject" installs the REJECT stub; else off
|
||||
// - input.seam_audit_ledger → true installs the recorder
|
||||
// - input.seam_ledger_verdict → the drift-ledger check() verdict
|
||||
// - input.seam_graph_write → "stub_true" installs GraphWrite → true
|
||||
// - input.stdout → GraphRun returns the per-case stdout; the frozen argv's
|
||||
// "@RUNE@" slot is substituted with this checkout's graph_memory.py path
|
||||
//
|
||||
// Rows of one fn replay sequentially on a shared scope (db/transcripts/index
|
||||
// persist across rows of the same fn), mirroring the oracle run.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TruthDoc — the frozen truth file structure.
|
||||
type TruthDoc struct {
|
||||
Oracle string `json:"oracle"`
|
||||
Generator string `json:"generator"`
|
||||
Note string `json:"note"`
|
||||
Rows []TruthRow `json:"rows"`
|
||||
}
|
||||
|
||||
// TruthRow — one frozen case.
|
||||
type TruthRow struct {
|
||||
Fn string `json:"fn"`
|
||||
Name string `json:"name"`
|
||||
Input json.RawMessage `json:"input"`
|
||||
Setup json.RawMessage `json:"setup"`
|
||||
Want json.RawMessage `json:"want"`
|
||||
}
|
||||
|
||||
// RunTruth replays every row and returns a divergence report (empty on
|
||||
// 100% parity).
|
||||
func RunTruth(path string) []string {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return []string{fmt.Sprintf("read truth: %v", err)}
|
||||
}
|
||||
var doc TruthDoc
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
return []string{fmt.Sprintf("parse truth: %v", err)}
|
||||
}
|
||||
repo := repoRoot()
|
||||
fails := []string{}
|
||||
var cur *Curator
|
||||
var curFn string
|
||||
scope := map[string]string{}
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
if row.Fn != curFn || cur == nil {
|
||||
cur = newScopeCurator(repo)
|
||||
curFn = row.Fn
|
||||
scope = map[string]string{}
|
||||
}
|
||||
if msg := replayRow(cur, scope, repo, row); msg != "" {
|
||||
fails = append(fails, row.Fn+"/"+row.Name+": "+msg)
|
||||
}
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
func repoRoot() string {
|
||||
// src/go/curator → repo root (two parents up).
|
||||
exe, err := os.Executable()
|
||||
_ = exe
|
||||
_ = err
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "."
|
||||
}
|
||||
return filepath.Dir(filepath.Dir(filepath.Dir(cwd)))
|
||||
}
|
||||
|
||||
func truthRowCount() int {
|
||||
raw, err := os.ReadFile("truth.json")
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
var doc TruthDoc
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
return -1
|
||||
}
|
||||
return len(doc.Rows)
|
||||
}
|
||||
|
||||
func newScopeCurator(repo string) *Curator {
|
||||
root, err := os.MkdirTemp("", "curator-replay-*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c := New(filepath.Join(root, "memory", "store", "memory.db"))
|
||||
c.BlocksDir = filepath.Join(root, "blocks")
|
||||
c.JournalDir = filepath.Join(root, "journal")
|
||||
c.IndexFile = filepath.Join(root, "index", "blocks_meta.json")
|
||||
c.SkillsDir = filepath.Join(root, "config", "skills")
|
||||
c.TranscriptsDir = filepath.Join(root, "transcripts")
|
||||
c.GraphMemoryPath = filepath.Join(repo, "src", "rune", "graph_memory.py")
|
||||
c.Out = discard{}
|
||||
c.Err = discard{}
|
||||
return c
|
||||
}
|
||||
|
||||
type discard struct{}
|
||||
|
||||
func (discard) Write(p []byte) (int, error) { return len(p), nil }
|
||||
|
||||
// applySetup recreates the row's seed state (db/transcripts/index/skills).
|
||||
func applySetup(c *Curator, scope map[string]string, row *TruthRow) error {
|
||||
var setup map[string]interface{}
|
||||
if len(row.Setup) > 0 {
|
||||
if err := json.Unmarshal(row.Setup, &setup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v, ok := setup["db_missing"]; ok && v == true {
|
||||
c.StoreDB = filepath.Join(c.JournalDir, "..", "nonexistent-dir", "no.db")
|
||||
c.StoreDB = filepath.Join(filepath.Dir(c.JournalDir), "nonexistent-dir", "no.db")
|
||||
scope["db"] = "missing"
|
||||
} else if v, ok := setup["db_rows"]; ok {
|
||||
dbPath := filepath.Join(filepath.Dir(c.StoreDB), "scope.db")
|
||||
rows, _ := v.([]interface{})
|
||||
if err := SeedDB(dbPath, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
c.StoreDB = dbPath
|
||||
scope["db"] = dbPath
|
||||
}
|
||||
if v, ok := setup["transcripts"]; ok {
|
||||
if err := seedTranscripts(c, v.([]interface{})); err != nil {
|
||||
return err
|
||||
}
|
||||
scope["transcripts"] = "seeded"
|
||||
}
|
||||
if v, ok := setup["index"]; ok {
|
||||
os.MkdirAll(filepath.Dir(c.IndexFile), 0o755)
|
||||
b, _ := json.MarshalIndent(normJSON(v), "", " ")
|
||||
if err := os.WriteFile(c.IndexFile, b, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if v, ok := setup["index_missing"]; ok && v == true {
|
||||
os.Remove(c.IndexFile)
|
||||
}
|
||||
if v, ok := setup["skills"]; ok {
|
||||
if err := seedSkills(c, v.(map[string]interface{})); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// input-embedded setup (put_setup rows): same keys, inside input.setup.
|
||||
var input map[string]interface{}
|
||||
if err := json.Unmarshal(row.Input, &input); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := input["setup"]; ok {
|
||||
sub, _ := v.(map[string]interface{})
|
||||
if dv, ok := sub["db_missing"]; ok && dv == true {
|
||||
c.StoreDB = filepath.Join(filepath.Dir(c.JournalDir), "nonexistent-dir", "no.db")
|
||||
scope["db"] = "missing"
|
||||
} else if dv, ok := sub["db_rows"]; ok {
|
||||
dbPath := filepath.Join(filepath.Dir(c.StoreDB), "scope.db")
|
||||
rows, _ := dv.([]interface{})
|
||||
if err := SeedDB(dbPath, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
c.StoreDB = dbPath
|
||||
scope["db"] = dbPath
|
||||
}
|
||||
if tv, ok := sub["transcripts"]; ok {
|
||||
if err := seedTranscripts(c, tv.([]interface{})); err != nil {
|
||||
return err
|
||||
}
|
||||
scope["transcripts"] = "seeded"
|
||||
}
|
||||
if iv, ok := sub["index"]; ok {
|
||||
os.MkdirAll(filepath.Dir(c.IndexFile), 0o755)
|
||||
b, _ := json.MarshalIndent(normJSON(iv), "", " ")
|
||||
if err := os.WriteFile(c.IndexFile, b, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if iv, ok := sub["index_missing"]; ok && iv == true {
|
||||
os.Remove(c.IndexFile)
|
||||
}
|
||||
if sv, ok := sub["skills"]; ok {
|
||||
if err := seedSkills(c, sv.(map[string]interface{})); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedTranscripts(c *Curator, list []interface{}) error {
|
||||
os.MkdirAll(c.TranscriptsDir, 0o755)
|
||||
ents, err := os.ReadDir(c.TranscriptsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range ents {
|
||||
os.Remove(filepath.Join(c.TranscriptsDir, e.Name()))
|
||||
}
|
||||
frozen, _ := time.Parse("2006-01-02T15:04:05Z07:00", FrozenISO)
|
||||
for _, v := range list {
|
||||
t := v.(map[string]interface{})
|
||||
p := filepath.Join(c.TranscriptsDir, strOr(t["name"], ""))
|
||||
if err := os.WriteFile(p, []byte(strOr(t["body"], "")), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
age := floatOf(t["age_days"])
|
||||
stamp := frozen.Add(-time.Duration(age*86400) * time.Second)
|
||||
if err := os.Chtimes(p, stamp, stamp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedSkills(c *Curator, m map[string]interface{}) error {
|
||||
os.MkdirAll(c.SkillsDir, 0o755)
|
||||
ents, err := os.ReadDir(c.SkillsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range ents {
|
||||
os.RemoveAll(filepath.Join(c.SkillsDir, e.Name()))
|
||||
}
|
||||
for rel, v := range m {
|
||||
p := filepath.Join(c.SkillsDir, rel)
|
||||
os.MkdirAll(filepath.Dir(p), 0o755)
|
||||
if err := os.WriteFile(p, []byte(strOf(v)), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pinRowSeams installs the row's injected seams (drift gate / audit ledger /
|
||||
// ledger verdict / graph stub / embed table).
|
||||
func pinRowSeams(c *Curator, row *TruthRow, audit *[]AuditCall) {
|
||||
var input map[string]interface{}
|
||||
_ = json.Unmarshal(row.Input, &input)
|
||||
if v, ok := input["seam_drift_gate"]; ok && v == "reject" {
|
||||
c.DriftGate = func(entry map[string]interface{}) GateResult {
|
||||
return GateResult{Verdict: "REJECT", Reason: "washed out"}
|
||||
}
|
||||
} else {
|
||||
c.DriftGate = nil
|
||||
}
|
||||
if v, ok := input["seam_audit_ledger"]; ok && v == true {
|
||||
c.AuditLedger = func(id int, kind, action string, count int, detail string) {
|
||||
*audit = append(*audit, AuditCall{id, kind, action, count, detail})
|
||||
}
|
||||
} else {
|
||||
c.AuditLedger = nil
|
||||
}
|
||||
if v, ok := input["seam_ledger_verdict"]; ok {
|
||||
c.LedgerVerdict = intOf(v)
|
||||
} else {
|
||||
c.LedgerVerdict = 0
|
||||
}
|
||||
if v, ok := input["seam_graph_write"]; ok && v == "stub_true" {
|
||||
c.GraphRun = func(argv []string) string { return `"added": True` }
|
||||
} else {
|
||||
c.GraphRun = nil
|
||||
}
|
||||
}
|
||||
|
||||
// AuditCall — one recorded log_promotion call.
|
||||
type AuditCall struct {
|
||||
ID int
|
||||
Kind string
|
||||
Action string
|
||||
Count int
|
||||
Detail string
|
||||
}
|
||||
|
||||
func auditCallsJSON(calls []AuditCall) interface{} {
|
||||
out := []interface{}{}
|
||||
for _, a := range calls {
|
||||
out = append(out, []interface{}{a.ID, a.Kind, a.Action, float64(a.Count), a.Detail})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
// replay_cases.go — per-function replay dispatch. Each case mirrors exactly
|
||||
// one row of the frozen truth: it reads the row input, applies the row setup,
|
||||
// runs the ported function, and returns the got value for comparison against
|
||||
// the frozen want. Any divergence fails the battery.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// normJSON canonicalizes a value through JSON (numbers → float64, OM →
|
||||
// ordered maps) so DeepEqual compares oracle-shaped values.
|
||||
func normJSON(v interface{}) interface{} {
|
||||
switch t := v.(type) {
|
||||
case []string:
|
||||
out := make([]interface{}, len(t))
|
||||
for i, s := range t {
|
||||
out[i] = s
|
||||
}
|
||||
return out
|
||||
case []float64:
|
||||
out := make([]interface{}, len(t))
|
||||
for i, f := range t {
|
||||
out[i] = f
|
||||
}
|
||||
return out
|
||||
case []map[string]interface{}:
|
||||
out := make([]interface{}, len(t))
|
||||
for i, m := range t {
|
||||
out[i] = normJSON(m)
|
||||
}
|
||||
return out
|
||||
case *OM:
|
||||
m := map[string]interface{}{}
|
||||
for _, k := range t.Keys {
|
||||
m[k] = normJSON(t.M[k])
|
||||
}
|
||||
return m
|
||||
case map[string]interface{}:
|
||||
m := map[string]interface{}{}
|
||||
for k, vv := range t {
|
||||
m[k] = normJSON(vv)
|
||||
}
|
||||
return m
|
||||
case []interface{}:
|
||||
out := make([]interface{}, len(t))
|
||||
for i, vv := range t {
|
||||
out[i] = normJSON(vv)
|
||||
}
|
||||
return out
|
||||
case json.Number:
|
||||
f, _ := t.Float64()
|
||||
return f
|
||||
case int:
|
||||
return float64(t)
|
||||
case int64:
|
||||
return float64(t)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func wantOf(row *TruthRow) interface{} {
|
||||
dec := json.NewDecoder(bytes.NewReader(row.Want))
|
||||
dec.UseNumber()
|
||||
var w interface{}
|
||||
_ = dec.Decode(&w)
|
||||
return normJSON(w)
|
||||
}
|
||||
|
||||
func inputOf(row *TruthRow) map[string]interface{} {
|
||||
dec := json.NewDecoder(bytes.NewReader(row.Input))
|
||||
dec.UseNumber()
|
||||
var in map[string]interface{}
|
||||
_ = dec.Decode(&in)
|
||||
return in
|
||||
}
|
||||
|
||||
func replayRow(c *Curator, scope map[string]string, repo string, row *TruthRow) string {
|
||||
if err := applySetup(c, scope, row); err != nil {
|
||||
return fmt.Sprintf("setup: %v", err)
|
||||
}
|
||||
var audit []AuditCall
|
||||
pinRowSeams(c, row, &audit)
|
||||
in := inputOf(row)
|
||||
got, msg := dispatch(c, scope, repo, row, in)
|
||||
if msg != "" {
|
||||
return msg
|
||||
}
|
||||
// audit ledger calls are part of promote_candidates rows' want
|
||||
if row.Fn == "promote_candidates" && len(audit) > 0 {
|
||||
g, ok := got.(map[string]interface{})
|
||||
if !ok {
|
||||
return "audit: got not a map"
|
||||
}
|
||||
g["audit_calls"] = auditCallsJSON(audit)
|
||||
got = g
|
||||
}
|
||||
if !reflect.DeepEqual(normJSON(got), wantOf(row)) {
|
||||
gb, _ := json.Marshal(normJSON(got))
|
||||
wb, _ := json.Marshal(wantOf(row))
|
||||
return fmt.Sprintf("divergence\ngot: %s\nwant: %s", gb, wb)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// dispatch runs one frozen row. Returns (got, error-message).
|
||||
func dispatch(c *Curator, scope map[string]string, repo string, row *TruthRow, in map[string]interface{}) (interface{}, string) {
|
||||
// journal_append rows share one journal scope (the oracle never resets
|
||||
// between them).
|
||||
if row.Fn == "journal_append" && scope["journal"] == "" {
|
||||
ents, _ := os.ReadDir(c.JournalDir)
|
||||
for _, e := range ents {
|
||||
os.Remove(filepath.Join(c.JournalDir, e.Name()))
|
||||
}
|
||||
scope["journal"] = "open"
|
||||
}
|
||||
// main rows each run main() fresh on the shared scope; the oracle's
|
||||
// main/apply_output journal accumulates hook/dry/plan rows' NOTHING
|
||||
// (those never write the journal) — no reset needed.
|
||||
switch row.Fn {
|
||||
case "block_path":
|
||||
return c.BlockPath(strOr(in["label"], "")), ""
|
||||
case "entry_key":
|
||||
return EntryKey(strOr(in["text"], "")), ""
|
||||
case "find_entry":
|
||||
v := FindEntry(strOr(in["body"], ""), strOr(in["needle"], ""))
|
||||
if v == "" {
|
||||
return nil, ""
|
||||
}
|
||||
return v, ""
|
||||
case "candidate_key":
|
||||
return CandidateKey(strOr(in["text"], "")), ""
|
||||
case "norm":
|
||||
return Norm(strOr(in["text"], "")), ""
|
||||
case "detect_conflict":
|
||||
k, l := DetectConflict(strOr(in["body"], ""), strOr(in["fact"], ""))
|
||||
if k == "" {
|
||||
return []interface{}{nil, nil}, ""
|
||||
}
|
||||
return []interface{}{k, l}, ""
|
||||
case "overlap":
|
||||
return Overlap(strOr(in["a"], ""), strOr(in["b"], "")), ""
|
||||
case "cos":
|
||||
return Cos(floats(in["a"]), floats(in["b"])), ""
|
||||
case "sanitize_slug":
|
||||
return SanitizeSlug(strOr(in["name"], "")), ""
|
||||
case "skill_path":
|
||||
rel, err := filepath.Rel(c.SkillsDir, c.SkillPath(strOr(in["name"], "")))
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
return rel, ""
|
||||
case "is_topic_fact":
|
||||
return c.IsTopicFact(strOr(in["fact"], "")), ""
|
||||
case "aaak":
|
||||
return c.Aaak(strOr(in["text"], ""), strOr(in["topic"], "")), ""
|
||||
case "le_promote_eligible":
|
||||
if v, ok := in["seam_lexicon"]; ok && v == "unavailable" {
|
||||
return lePromoteEligibleFallback(strOr(in["proposal"], ""), nil,
|
||||
intOf(in["strength"]), intOf(in["min_strength"]), intOf(in["wash_count"])), ""
|
||||
}
|
||||
existing := stringsOf(in["existing"])
|
||||
return c.LePromoteEligible(strOr(in["proposal"], ""), existing,
|
||||
intOf(in["strength"]), intOf(in["min_strength"]), intOf(in["wash_count"])), ""
|
||||
case "store_has":
|
||||
topic := ""
|
||||
if in["topic"] != nil {
|
||||
topic = strOr(in["topic"], "")
|
||||
}
|
||||
return c.StoreHas(strOr(in["text"], ""), topic), ""
|
||||
case "read_block":
|
||||
fm, body := c.ReadBlock(strOr(in["label"], ""))
|
||||
return map[string]interface{}{"fm": fm, "body": body}, ""
|
||||
case "write_block":
|
||||
ret := c.WriteBlock(strOr(in["label"], ""), strOf(in["body"]))
|
||||
out := map[string]interface{}{"return": ret}
|
||||
if row.Name != "db_unavailable_returns_descriptor" {
|
||||
_, body := c.ReadBlock(strOr(in["label"], ""))
|
||||
out["body"] = body
|
||||
}
|
||||
return out, ""
|
||||
case "block_values":
|
||||
out := c.BlockValues(strOr(in["label"], ""))
|
||||
if out == nil {
|
||||
out = []string{}
|
||||
}
|
||||
return out, ""
|
||||
case "store_corpus":
|
||||
out := c.StoreCorpus(intOf(in["limit"]))
|
||||
if out == nil {
|
||||
out = []string{}
|
||||
}
|
||||
return out, ""
|
||||
case "contradicts_identity":
|
||||
return c.ContradictsIdentity(strOr(in["fact"], "")), ""
|
||||
case "load_index":
|
||||
return c.LoadIndex(), ""
|
||||
case "save_index":
|
||||
c.SaveIndex(mustOM(in["idx"]))
|
||||
return c.LoadIndex(), ""
|
||||
case "ensure_entry_meta":
|
||||
idx := idxInitOf(row)
|
||||
key := c.EnsureEntryMeta(idx, strOr(in["label"], ""), strOr(in["text"], ""))
|
||||
lomV, _ := idx.Get(strOr(in["label"], ""))
|
||||
lom, _ := omEntry(lomV)
|
||||
// the oracle's want carries the label dict as a plain map
|
||||
return map[string]interface{}{"key": key, "label_idx": lom}, ""
|
||||
case "touch_entry":
|
||||
idx := idxInitOf(row)
|
||||
label := strOr(in["label"], "")
|
||||
text := strOr(in["text"], "")
|
||||
key := c.TouchEntry(idx, label, text)
|
||||
lomV, _ := idx.Get(label)
|
||||
lom, _ := lomV.(*OM)
|
||||
metaV, _ := lom.Get(key)
|
||||
meta, _ := omEntry(metaV)
|
||||
return map[string]interface{}{"key": key, "meta": meta}, ""
|
||||
case "journal_append":
|
||||
entry, _ := in["entry"].(map[string]interface{})
|
||||
// rows of this fn replay sequentially on a shared journal scope,
|
||||
// mirroring the oracle run (minimal sees full's entry).
|
||||
p := c.JournalAppend(entry)
|
||||
content := map[string]interface{}{}
|
||||
ents, _ := os.ReadDir(c.JournalDir)
|
||||
for _, e := range ents {
|
||||
b, _ := os.ReadFile(filepath.Join(c.JournalDir, e.Name()))
|
||||
content[e.Name()] = string(b)
|
||||
}
|
||||
return map[string]interface{}{"path": filepath.Base(p), "content": content}, ""
|
||||
case "ensure_candidates":
|
||||
idx := NewOM()
|
||||
if v, ok := in["idx"].(*OM); ok {
|
||||
idx = v
|
||||
} else if v, ok := in["idx"].(map[string]interface{}); ok {
|
||||
idx = mapToOM(v)
|
||||
}
|
||||
return EnsureCandidates(idx), ""
|
||||
case "semantic_candidate_key":
|
||||
vecs := tableVecs()
|
||||
embed := func(texts []string) map[string][]float64 {
|
||||
out := map[string][]float64{}
|
||||
for _, t := range texts {
|
||||
if v, ok := vecs[t]; ok {
|
||||
out[t] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
cands := NewOM()
|
||||
cands.Set(CandidateKey("fact alpha about deploy runners"),
|
||||
map[string]interface{}{"text": "fact alpha about deploy runners"})
|
||||
cands.Set(CandidateKey("fact delta about whiskey tasting"),
|
||||
map[string]interface{}{"text": "fact delta about whiskey tasting"})
|
||||
if row.Name == "empty_cands" {
|
||||
cands = NewOM()
|
||||
}
|
||||
v := c.SemanticCandidateKey(strOr(in["fact"], ""), cands, embed)
|
||||
if v == "" {
|
||||
return nil, ""
|
||||
}
|
||||
return v, ""
|
||||
case "embed":
|
||||
for k, v := range tableVecs() {
|
||||
EmbedCache[k] = v
|
||||
}
|
||||
out := c.Embed(stringsOf(in["texts"]))
|
||||
m := map[string]interface{}{}
|
||||
for k, v := range out {
|
||||
m[k] = v
|
||||
}
|
||||
return m, ""
|
||||
case "grounded_in_sessions":
|
||||
return c.GroundedInSessions(strOr(in["fact"], ""), 0.46, 3), ""
|
||||
case "coherence_check":
|
||||
if v, ok := in["fact"]; ok && (v == "" || len(strOf(v)) < 15) && in["sources"] == nil {
|
||||
return c.CoherenceCheck(strOf(v), []string{"sessions", "corpus"}), ""
|
||||
}
|
||||
return c.CoherenceCheck(strOr(in["fact"], ""), stringsOf(in["sources"])), ""
|
||||
case "grounded_in_corpus":
|
||||
return c.GroundedInCorpus(strOr(in["fact"], "")), ""
|
||||
case "decay_candidates":
|
||||
idx := idxInitOf(row)
|
||||
if idx.Len() == 0 {
|
||||
if raw := rawField(row, "idx"); len(raw) > 0 {
|
||||
idx = mustOMRaw(raw)
|
||||
}
|
||||
}
|
||||
var jl []map[string]interface{}
|
||||
changed := c.DecayCandidates(idx, &jl)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
cands, _ := idx.Get(CandidatesKey)
|
||||
return map[string]interface{}{"changed": changed, "remaining": cands, "journal": jl}, ""
|
||||
case "promote_candidates":
|
||||
idx := idxInitOf(row)
|
||||
ev := evidenceOf(in["evidence"])
|
||||
var jl []map[string]interface{}
|
||||
promoted := c.PromoteCandidates(ev, idx, &jl)
|
||||
if promoted == nil {
|
||||
promoted = []map[string]interface{}{}
|
||||
}
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
cands, _ := idx.Get(CandidatesKey)
|
||||
if cands == nil {
|
||||
cands = NewOM()
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"promoted": promoted, "candidates": cands, "journal": jl,
|
||||
}, ""
|
||||
case "plan_ops":
|
||||
idx := idxInitOf(row)
|
||||
ev := evidenceOf(in["evidence"])
|
||||
var jl []map[string]interface{}
|
||||
ops := c.PlanOps(ev, idx, &jl)
|
||||
if ops == nil {
|
||||
ops = []map[string]interface{}{}
|
||||
}
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
return map[string]interface{}{"ops": ops, "idx": idx, "journal": jl}, ""
|
||||
case "apply_op":
|
||||
idx := idxInitOf(row)
|
||||
ops := evidenceOf(in["ops"])
|
||||
var jl []map[string]interface{}
|
||||
var results []interface{}
|
||||
for _, op := range ops {
|
||||
results = append(results, c.ApplyOp(op, idx, &jl))
|
||||
}
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
_, body := c.ReadBlock("human")
|
||||
return map[string]interface{}{
|
||||
"results": results, "journal": jl, "body": body, "idx": idx,
|
||||
}, ""
|
||||
case "apply_warm":
|
||||
var jl []map[string]interface{}
|
||||
if row.Name == "post_upsert_neurons" || row.Name == "post_seed_neurons" {
|
||||
return neuronsJSON(ListNeurons(c.StoreDB)), ""
|
||||
}
|
||||
if op, ok := in["op"].(map[string]interface{}); ok {
|
||||
ret := c.ApplyWarm(op, &jl)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
if _, hasReturned := rawWantField(row, "returned"); hasReturned {
|
||||
return map[string]interface{}{"returned": ret, "journal": jl}, ""
|
||||
}
|
||||
return ret, ""
|
||||
}
|
||||
// seed_new_neuron/empty_fact rows: want is {returned, journal}
|
||||
fact := strOr(in["after"], strOr(in["fact"], ""))
|
||||
ret := c.ApplyWarm(map[string]interface{}{"after": fact}, &jl)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
return map[string]interface{}{"returned": ret, "journal": jl}, ""
|
||||
case "derive_triggers":
|
||||
return DeriveTriggers(strOr(in["text"], ""), stringsOf(in["existing"])), ""
|
||||
case "decay_pass":
|
||||
idx := idxInitOf(row)
|
||||
if idx.Len() == 0 {
|
||||
if raw := rawField(row, "idx"); len(raw) > 0 {
|
||||
idx = mustOMRaw(raw)
|
||||
}
|
||||
}
|
||||
// the oracle's want carries idx_after as the MUTATED idx (same
|
||||
// object); replay returns the same mutated object.
|
||||
var jl []map[string]interface{}
|
||||
c.DecayPass(idx, &jl)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
return map[string]interface{}{"idx_after": idx, "journal": jl}, ""
|
||||
case "compress_pass":
|
||||
idx := idxInitOf(row)
|
||||
var jl []map[string]interface{}
|
||||
changed := c.CompressPass(idx, &jl)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
if row.Name == "under_limit_noop" {
|
||||
return map[string]interface{}{"changed": changed, "journal": jl}, ""
|
||||
}
|
||||
_, body := c.ReadBlock("operating")
|
||||
return map[string]interface{}{
|
||||
"changed": changed, "body": body, "journal": jl, "idx": idx,
|
||||
}, ""
|
||||
case "author_skill":
|
||||
op, _ := in["op"].(map[string]interface{})
|
||||
var jl []map[string]interface{}
|
||||
if row.Name == "no_name" || row.Name == "no_steps" {
|
||||
return c.AuthorSkill(op, &jl), ""
|
||||
}
|
||||
written := c.AuthorSkill(op, &jl)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
out := map[string]interface{}{"written": written}
|
||||
if row.Name == "new_skill" {
|
||||
slug := SanitizeSlug(strOr(op["name"], ""))
|
||||
b, _ := os.ReadFile(c.SkillPath(slug))
|
||||
out["rel"] = slug + "/SKILL.md"
|
||||
out["content"] = string(b)
|
||||
out["journal"] = jl
|
||||
} else if row.Name == "existing_adds_evidence" || row.Name == "existing_keeps_evidence" {
|
||||
slug := SanitizeSlug(strOr(op["name"], ""))
|
||||
b, _ := os.ReadFile(c.SkillPath(slug))
|
||||
out["content"] = string(b)
|
||||
out["journal"] = jl
|
||||
}
|
||||
return out, ""
|
||||
case "graph_write":
|
||||
stdout := strOr(in["stdout"], `"added": True`)
|
||||
var captured []string
|
||||
c.GraphRun = func(argv []string) string {
|
||||
captured = append([]string(nil), argv...)
|
||||
return stdout
|
||||
}
|
||||
added := c.GraphWrite(strOr(in["fact"], ""), "ev", "src", intOf(in["strength"]))
|
||||
for i, a := range captured {
|
||||
if strings.HasPrefix(a, repo+"/src/rune/graph_memory.py") || a == c.GraphMemoryPath {
|
||||
captured[i] = "@RUNE@/src/rune/graph_memory.py"
|
||||
}
|
||||
}
|
||||
return map[string]interface{}{"added": added, "argv": captured}, ""
|
||||
case "run_plan":
|
||||
ev := evidenceOf(in["evidence"])
|
||||
apply := row.Name != "dry_run"
|
||||
var buf bytes.Buffer
|
||||
c.Out = &buf
|
||||
jl, ops := c.RunPlan(ev, apply)
|
||||
if jl == nil {
|
||||
jl = []map[string]interface{}{}
|
||||
}
|
||||
if ops == nil {
|
||||
ops = []map[string]interface{}{}
|
||||
}
|
||||
out := map[string]interface{}{"journal": jl, "ops": ops}
|
||||
if row.Name == "apply_gate_pass" {
|
||||
out["index"] = c.LoadIndex()
|
||||
} else if row.Name == "apply_gate_refuses" {
|
||||
out["stdout"] = buf.String()
|
||||
}
|
||||
return out, ""
|
||||
case "main":
|
||||
return dispatchMain(c, row, in), ""
|
||||
case "handle_hook":
|
||||
var data map[string]interface{}
|
||||
if v, ok := in["data"].(map[string]interface{}); ok {
|
||||
data = v
|
||||
}
|
||||
return c.HandleHook(strOr(in["event"], ""), data), ""
|
||||
}
|
||||
return nil, fmt.Sprintf("unknown fn %q", row.Fn)
|
||||
}
|
||||
|
||||
func floats(v interface{}) []float64 {
|
||||
var out []float64
|
||||
if arr, ok := v.([]interface{}); ok {
|
||||
for _, x := range arr {
|
||||
out = append(out, floatOf(x))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func stringsOf(v interface{}) []string {
|
||||
var out []string
|
||||
if arr, ok := v.([]interface{}); ok {
|
||||
for _, x := range arr {
|
||||
out = append(out, strOf(x))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func evidenceOf(v interface{}) []map[string]interface{} {
|
||||
var out []map[string]interface{}
|
||||
if arr, ok := v.([]interface{}); ok {
|
||||
for _, x := range arr {
|
||||
if m, ok := x.(map[string]interface{}); ok {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// mustOM decodes an idx_init value (nil → empty OM). Plain maps arrive
|
||||
// from inputOf's Unmarshal (order lost) — callers that need insertion order
|
||||
// must decode from the raw row input instead (mustOMRaw).
|
||||
func mustOM(v interface{}) *OM {
|
||||
if v == nil {
|
||||
return NewOM()
|
||||
}
|
||||
if om, ok := v.(*OM); ok {
|
||||
return om
|
||||
}
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
return mapToOM(m)
|
||||
}
|
||||
return NewOM()
|
||||
}
|
||||
|
||||
// mustOMRaw decodes an idx value from raw JSON, preserving key order.
|
||||
func mustOMRaw(raw json.RawMessage) *OM {
|
||||
om := NewOM()
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return om
|
||||
}
|
||||
if err := om.UnmarshalJSON(raw); err != nil {
|
||||
return NewOM()
|
||||
}
|
||||
return om
|
||||
}
|
||||
|
||||
// idxInitOf extracts the raw idx_init JSON for a row (order-preserving).
|
||||
// Sources, in order: input.idx_init, input.idx, setup.idx_init — mirroring
|
||||
// the generator (plan/apply/compress rows carry idx_init in top-level setup).
|
||||
func idxInitOf(row *TruthRow) *OM {
|
||||
var in struct {
|
||||
IdxInit json.RawMessage `json:"idx_init"`
|
||||
Idx json.RawMessage `json:"idx"`
|
||||
}
|
||||
_ = json.Unmarshal(row.Input, &in)
|
||||
if len(in.IdxInit) > 0 && string(in.IdxInit) != "null" {
|
||||
return mustOMRaw(in.IdxInit)
|
||||
}
|
||||
if len(in.Idx) > 0 && string(in.Idx) != "null" {
|
||||
return mustOMRaw(in.Idx)
|
||||
}
|
||||
var setup struct {
|
||||
IdxInit json.RawMessage `json:"idx_init"`
|
||||
}
|
||||
if len(row.Setup) > 0 {
|
||||
_ = json.Unmarshal(row.Setup, &setup)
|
||||
if len(setup.IdxInit) > 0 && string(setup.IdxInit) != "null" {
|
||||
return mustOMRaw(setup.IdxInit)
|
||||
}
|
||||
}
|
||||
return NewOM()
|
||||
}
|
||||
|
||||
// mapToOM converts a decoded map into an ordered OM.
|
||||
func mapToOM(m map[string]interface{}) *OM {
|
||||
om := NewOM()
|
||||
for k, v := range m {
|
||||
switch t := v.(type) {
|
||||
case map[string]interface{}:
|
||||
om.Set(k, mapToOM(t))
|
||||
case []interface{}:
|
||||
om.Set(k, normList(t))
|
||||
default:
|
||||
om.Set(k, v)
|
||||
}
|
||||
}
|
||||
return om
|
||||
}
|
||||
|
||||
func normList(l []interface{}) []interface{} {
|
||||
out := make([]interface{}, len(l))
|
||||
for i, v := range l {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
out[i] = mapToOM(m)
|
||||
} else {
|
||||
out[i] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func neuronsJSON(ns []Neuron) interface{} {
|
||||
if ns == nil {
|
||||
return []interface{}{}
|
||||
}
|
||||
b, _ := json.Marshal(ns)
|
||||
var out interface{}
|
||||
_ = json.Unmarshal(b, &out)
|
||||
return out
|
||||
}
|
||||
|
||||
// tableVecs — the frozen VEC table (mirrors the generator's VEC).
|
||||
func tableVecs() map[string][]float64 {
|
||||
return map[string][]float64{
|
||||
"fact alpha about deploy runners": {1.0, 0.0, 0.0},
|
||||
"fact beta about gitea runners": {0.0, 1.0, 0.0},
|
||||
"fact gamma about deploy runners nightly": {0.9, 0.1, 0.0},
|
||||
"fact delta about whiskey tasting": {0.0, 0.0, 1.0},
|
||||
}
|
||||
}
|
||||
|
||||
// mustMap decodes an idx value that may be *OM or a plain map.
|
||||
func mustMap(v interface{}) map[string]interface{} {
|
||||
if om, ok := v.(*OM); ok {
|
||||
m := map[string]interface{}{}
|
||||
for _, k := range om.Keys {
|
||||
m[k] = om.M[k]
|
||||
}
|
||||
return m
|
||||
}
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
return m
|
||||
}
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// rawField extracts a raw JSON field from a row's input (order-preserving).
|
||||
func rawField(row *TruthRow, field string) json.RawMessage {
|
||||
var in map[string]json.RawMessage
|
||||
_ = json.Unmarshal(row.Input, &in)
|
||||
return in[field]
|
||||
}
|
||||
|
||||
func jsonStr(v interface{}) string {
|
||||
b, _ := json.Marshal(strOf(v))
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// dbSig fingerprints a seed-row list so sequential rows on one scope keep
|
||||
// their accumulated store state instead of re-seeding every row.
|
||||
func dbSig(rows []interface{}) string {
|
||||
b, _ := json.Marshal(rows)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// rawWantField extracts a raw JSON field from a row's want.
|
||||
func rawWantField(row *TruthRow, field string) (json.RawMessage, bool) {
|
||||
var w map[string]json.RawMessage
|
||||
_ = json.Unmarshal(row.Want, &w)
|
||||
v, ok := w[field]
|
||||
return v, ok
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// store.go — sqlite seam for the curator port (modernc.org/sqlite, pure Go).
|
||||
// Mirrors the oracle's sqlite3 usage: read paths use `file:<path>?mode=ro`
|
||||
// (missing file/dir → connect error → the documented fallback), write paths
|
||||
// open read-write (missing dir → error → fallback).
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
const entriesSchema = `
|
||||
CREATE TABLE IF NOT EXISTS entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
text TEXT NOT NULL,
|
||||
importance REAL DEFAULT 0.5,
|
||||
created_at TEXT NOT NULL,
|
||||
last_accessed TEXT,
|
||||
topic TEXT DEFAULT '',
|
||||
entities TEXT DEFAULT '[]',
|
||||
source TEXT DEFAULT '',
|
||||
method TEXT DEFAULT 'curator',
|
||||
status TEXT DEFAULT 'active',
|
||||
valid_from TEXT,
|
||||
valid_until TEXT,
|
||||
confidence REAL DEFAULT 0.7,
|
||||
temperature REAL DEFAULT 1.0,
|
||||
always_on INTEGER DEFAULT 0,
|
||||
priority INTEGER DEFAULT 5,
|
||||
triggers TEXT DEFAULT '',
|
||||
kind TEXT DEFAULT 'fact',
|
||||
slug TEXT DEFAULT '',
|
||||
summary TEXT DEFAULT '',
|
||||
wash_count INTEGER DEFAULT 0,
|
||||
last_washed TEXT,
|
||||
promotion_eligible INTEGER DEFAULT 0,
|
||||
rock_status TEXT DEFAULT 'washable',
|
||||
tier TEXT DEFAULT 'warm',
|
||||
memory_type TEXT
|
||||
);
|
||||
`
|
||||
|
||||
func openRO(path string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", "file:"+path+"?mode=ro")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func openRW(path string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", "file:"+path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// SeedDB creates a fresh store with the full entries schema and inserts the
|
||||
// setup rows (column names mirror the oracle schema).
|
||||
func SeedDB(path string, rows []interface{}) error {
|
||||
if err := os.MkdirAll(dirOf(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(path)
|
||||
db, err := openRW(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
if _, err = db.Exec(entriesSchema); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range rows {
|
||||
m, ok := r.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("seed row not an object")
|
||||
}
|
||||
cols := make([]string, 0, len(m))
|
||||
vals := make([]interface{}, 0, len(m))
|
||||
base := map[string]interface{}{
|
||||
"text": "", "importance": 0.5, "created_at": "2026-01-01T00:00:00+00:00",
|
||||
"last_accessed": "2026-01-01T00:00:00+00:00", "topic": "", "entities": "[]",
|
||||
"source": "seed", "method": "seed", "status": "active", "valid_from": nil,
|
||||
"confidence": 0.7, "temperature": 1.0, "always_on": 0, "priority": 5,
|
||||
"triggers": "", "kind": "fact", "slug": "", "summary": "", "wash_count": 0,
|
||||
}
|
||||
for k, v := range base {
|
||||
if _, ok := m[k]; !ok {
|
||||
cols = append(cols, k)
|
||||
vals = append(vals, v)
|
||||
}
|
||||
}
|
||||
for k, v := range m {
|
||||
cols = append(cols, k)
|
||||
vals = append(vals, v)
|
||||
}
|
||||
q := "INSERT INTO entries ("
|
||||
for i, c := range cols {
|
||||
if i > 0 {
|
||||
q += ", "
|
||||
}
|
||||
q += c
|
||||
}
|
||||
q += ") VALUES ("
|
||||
for i := range cols {
|
||||
if i > 0 {
|
||||
q += ", "
|
||||
}
|
||||
q += "?"
|
||||
}
|
||||
q += ")"
|
||||
if _, err = db.Exec(q, vals...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dirOf(path string) string {
|
||||
for i := len(path) - 1; i >= 0; i-- {
|
||||
if path[i] == '/' {
|
||||
return path[:i]
|
||||
}
|
||||
}
|
||||
return "."
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,116 @@
|
||||
// Battery — Go tests replaying the frozen oracle truth VERBATIM.
|
||||
// Every row of truth.json (generated by scripts/gen_curator_truth.py
|
||||
// against the live Python oracle) re-executes through this port; any
|
||||
// divergence fails the test.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func loadTruth(t *testing.T) TruthDoc {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile("truth.json")
|
||||
if err != nil {
|
||||
t.Fatalf("read truth: %v", err)
|
||||
}
|
||||
var doc TruthDoc
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
t.Fatalf("parse truth: %v", err)
|
||||
}
|
||||
if len(doc.Rows) == 0 {
|
||||
t.Fatal("truth has no rows")
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
func TestBatteryRowCount(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
if len(doc.Rows) != 162 {
|
||||
t.Fatalf("row count drift: got %d want 162", len(doc.Rows))
|
||||
}
|
||||
if doc.Oracle != "src/rune/curator.py" {
|
||||
t.Fatalf("oracle drift: %q", doc.Oracle)
|
||||
}
|
||||
if doc.Generator != "scripts/gen_curator_truth.py" {
|
||||
t.Fatalf("generator drift: %q", doc.Generator)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatteryParity(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
repo := repoRootForTest()
|
||||
var cur *Curator
|
||||
var curFn string
|
||||
scope := map[string]string{}
|
||||
fails := 0
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
if row.Fn != curFn || cur == nil {
|
||||
cur = newScopeCurator(repo)
|
||||
curFn = row.Fn
|
||||
scope = map[string]string{}
|
||||
}
|
||||
if msg := replayRow(cur, scope, repo, row); msg != "" {
|
||||
fails++
|
||||
t.Errorf("%s/%s: %s", row.Fn, row.Name, msg)
|
||||
}
|
||||
}
|
||||
if fails > 0 {
|
||||
t.Fatalf("%d/%d battery cases FAILED", fails, len(doc.Rows))
|
||||
}
|
||||
t.Logf("battery parity OK: %d cases", len(doc.Rows))
|
||||
}
|
||||
|
||||
func TestBatteryByFn(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
repo := repoRootForTest()
|
||||
byFn := map[string][]int{}
|
||||
for i := range doc.Rows {
|
||||
byFn[doc.Rows[i].Fn] = append(byFn[doc.Rows[i].Fn], i)
|
||||
}
|
||||
// Every public function of the oracle must be covered.
|
||||
for _, fn := range []string{
|
||||
"block_path", "entry_key", "find_entry", "candidate_key", "norm",
|
||||
"detect_conflict", "overlap", "cos", "sanitize_slug", "skill_path",
|
||||
"is_topic_fact", "aaak", "le_promote_eligible", "store_has",
|
||||
"read_block", "write_block", "block_values", "store_corpus",
|
||||
"contradicts_identity", "load_index", "save_index", "ensure_entry_meta",
|
||||
"touch_entry", "journal_append", "ensure_candidates",
|
||||
"semantic_candidate_key", "embed", "coherence_check",
|
||||
"grounded_in_sessions", "grounded_in_corpus", "decay_candidates",
|
||||
"promote_candidates", "plan_ops", "apply_op", "apply_warm",
|
||||
"derive_triggers", "decay_pass", "compress_pass", "author_skill",
|
||||
"graph_write", "run_plan", "main", "handle_hook",
|
||||
} {
|
||||
rows, ok := byFn[fn]
|
||||
if !ok || len(rows) == 0 {
|
||||
t.Errorf("fn %q has no frozen rows", fn)
|
||||
continue
|
||||
}
|
||||
cur := newScopeCurator(repo)
|
||||
scope := map[string]string{}
|
||||
for _, i := range rows {
|
||||
row := &doc.Rows[i]
|
||||
if msg := replayRow(cur, scope, repo, row); msg != "" {
|
||||
t.Errorf("%s/%s: %s", row.Fn, row.Name, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func repoRootForTest() string {
|
||||
// tests run with CWD = src/go/curator.
|
||||
abs, err := filepath.Abs("truth.json")
|
||||
if err != nil {
|
||||
return "."
|
||||
}
|
||||
// <repo>/src/go/curator/truth.json → <repo>
|
||||
for i := 0; i < 3; i++ {
|
||||
abs = filepath.Dir(abs)
|
||||
}
|
||||
return abs
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// warm.go — parity port of the warm_router trio the curator depends on
|
||||
// (list_neurons / write_neuron / _derive_triggers): the warm neuron layer.
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var warmWordRe = regexp.MustCompile(`[a-z]{4,}`)
|
||||
|
||||
func parseTrigger(s string) []string {
|
||||
var out []string
|
||||
for _, t := range strings.Split(s, ",") {
|
||||
t = strings.ToLower(strings.TrimSpace(t))
|
||||
if t != "" {
|
||||
out = append(out, t)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Neuron — one warm neuron as list_neurons returns it.
|
||||
type Neuron struct {
|
||||
Slug string `json:"slug"`
|
||||
Triggers []string `json:"triggers"`
|
||||
Importance float64 `json:"importance"`
|
||||
Body string `json:"body"`
|
||||
Summary string `json:"summary"`
|
||||
Tokens int `json:"tokens"`
|
||||
WashCount int `json:"wash_count"`
|
||||
BodyKnowledge string `json:"body_knowledge"`
|
||||
}
|
||||
|
||||
// ListNeurons — all active warm neurons from the store (kind='neuron').
|
||||
func ListNeurons(storeDB string) []Neuron {
|
||||
var neurons []Neuron
|
||||
db, err := openRO(storeDB)
|
||||
if err != nil {
|
||||
return neurons
|
||||
}
|
||||
rows, qerr := db.Query("SELECT text, slug, triggers, importance, summary, wash_count FROM entries " +
|
||||
"WHERE kind='neuron' AND status='active' ORDER BY slug")
|
||||
if qerr != nil {
|
||||
db.Close()
|
||||
return neurons
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var text, slug, triggers, summary sql.NullString
|
||||
var importance float64
|
||||
var washCount int
|
||||
if err := rows.Scan(&text, &slug, &triggers, &importance, &summary, &washCount); err != nil {
|
||||
return neurons
|
||||
}
|
||||
full := strings.TrimSpace(text.String)
|
||||
summ := strings.TrimSpace(summary.String)
|
||||
triggerList := parseTrigger(triggers.String)
|
||||
bodyKnowledge := ""
|
||||
if len(triggerList) > 0 {
|
||||
n := len(triggerList)
|
||||
if n > 5 {
|
||||
n = 5
|
||||
}
|
||||
bodyKnowledge = fmt.Sprintf("Fires on: %s. ", strings.Join(triggerList[:n], ", "))
|
||||
}
|
||||
if summ != "" {
|
||||
s := summ
|
||||
if len(s) > 80 {
|
||||
s = s[:80]
|
||||
}
|
||||
bodyKnowledge += fmt.Sprintf("Knows: %s. ", s)
|
||||
}
|
||||
wc := washCount
|
||||
switch {
|
||||
case wc >= 20:
|
||||
bodyKnowledge += "Proven — has survived many washes."
|
||||
case wc >= 10:
|
||||
bodyKnowledge += "Established — building trust."
|
||||
case wc > 0:
|
||||
bodyKnowledge += "Testing — still proving itself."
|
||||
default:
|
||||
bodyKnowledge += "Untested — new or rarely accessed."
|
||||
}
|
||||
imp := importance
|
||||
if imp == 0 {
|
||||
imp = 0.5
|
||||
}
|
||||
tok := len(full) / 4
|
||||
if tok < 1 {
|
||||
tok = 1
|
||||
}
|
||||
sg := slug.String
|
||||
if sg == "" {
|
||||
sg = "warm"
|
||||
}
|
||||
neurons = append(neurons, Neuron{
|
||||
Slug: sg,
|
||||
Triggers: triggerList,
|
||||
Importance: imp,
|
||||
Body: full,
|
||||
Summary: summ,
|
||||
Tokens: tok,
|
||||
WashCount: wc,
|
||||
BodyKnowledge: strings.TrimSpace(bodyKnowledge),
|
||||
})
|
||||
}
|
||||
return neurons
|
||||
}
|
||||
|
||||
// WriteNeuron upserts a warm neuron by slug in the store (the auto-build path).
|
||||
func WriteNeuron(storeDB, slug string, triggers []string, body string, importance float64) string {
|
||||
db, err := openRW(storeDB)
|
||||
if err != nil {
|
||||
return "store://neuron/" + slug
|
||||
}
|
||||
defer db.Close()
|
||||
ts := NowISO()
|
||||
var existingID sql.NullInt64
|
||||
err = db.QueryRow("SELECT id FROM entries WHERE kind='neuron' AND slug=?", slug).Scan(&existingID)
|
||||
if err == nil && existingID.Valid {
|
||||
_, err = db.Exec("UPDATE entries SET text=?, triggers=?, importance=?, "+
|
||||
"last_accessed=?, summary=?, memory_type=? WHERE id=?",
|
||||
strings.TrimSpace(body), strings.Join(triggers, ", "), importance,
|
||||
ts, "", "associative", existingID.Int64)
|
||||
} else {
|
||||
_, err = db.Exec("INSERT INTO entries (text, importance, created_at, "+
|
||||
"last_accessed, topic, source, method, status, valid_from, "+
|
||||
"confidence, temperature, always_on, priority, triggers, "+
|
||||
"kind, slug, summary, memory_type) "+
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
strings.TrimSpace(body), importance, ts, ts, "warm", "warm_router",
|
||||
"warm_router", "active", ts, 0.9, 1.0, 0, 5,
|
||||
strings.Join(triggers, ", "), "neuron", slug, "", "associative")
|
||||
}
|
||||
if err != nil {
|
||||
return "store://neuron/" + slug
|
||||
}
|
||||
// auto-associate (memory_store seam — best-effort, pinned off in the truth
|
||||
// battery; failures never block the write)
|
||||
return "store://neuron/" + slug
|
||||
}
|
||||
|
||||
// DeriveTriggers — pick the best trigger terms for a neuron body: frequent
|
||||
// significant words + any existing triggers that appear in it.
|
||||
func DeriveTriggers(text string, existing []string) []string {
|
||||
stop := map[string]bool{
|
||||
"the": true, "and": true, "for": true, "with": true, "that": true,
|
||||
"this": true, "from": true, "into": true, "when": true, "then": true,
|
||||
"were": true, "have": true, "been": true, "will": true, "was": true,
|
||||
"are": true, "but": true, "not": true, "you": true, "your": true,
|
||||
"also": true, "its": true, "his": true, "her": true, "over": true,
|
||||
"under": true, "them": true, "they": true, "there": true, "about": true,
|
||||
"after": true,
|
||||
}
|
||||
words := warmWordRe.FindAllString(strings.ToLower(text), -1)
|
||||
freq := map[string]int{}
|
||||
var order []string
|
||||
for _, w := range words {
|
||||
if !stop[w] {
|
||||
if _, seen := freq[w]; !seen {
|
||||
order = append(order, w)
|
||||
}
|
||||
freq[w]++
|
||||
}
|
||||
}
|
||||
// Python: sorted(freq.items(), key=lambda x: -x[1])[:6] — stable sort on
|
||||
// insertion order for ties.
|
||||
type kv struct {
|
||||
k string
|
||||
v int
|
||||
}
|
||||
pairs := make([]kv, 0, len(order))
|
||||
for _, k := range order {
|
||||
pairs = append(pairs, kv{k, freq[k]})
|
||||
}
|
||||
sort.SliceStable(pairs, func(i, j int) bool { return pairs[i].v > pairs[j].v })
|
||||
var top []string
|
||||
for i, p := range pairs {
|
||||
if i >= 6 {
|
||||
break
|
||||
}
|
||||
top = append(top, p.k)
|
||||
}
|
||||
low := strings.ToLower(text)
|
||||
var existingHits []string
|
||||
for _, t := range existing {
|
||||
if t != "" && strings.Contains(low, t) {
|
||||
existingHits = append(existingHits, t)
|
||||
}
|
||||
}
|
||||
out := append(existingHits, top...)
|
||||
if len(out) > 8 {
|
||||
out = out[:8]
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
// worthiness.go — parity port of src/rune/worthiness.py (Python fallback
|
||||
// surface, RUNE_WR_CORE=python pinned in the truth generator): the
|
||||
// constitution-grounded worthiness filter. One question per inviolable; each
|
||||
// returns (score, note); flat vetoes refuse outright.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type qResult struct {
|
||||
score float64
|
||||
note string
|
||||
}
|
||||
|
||||
var qPiSafetyBad = []string{"production", "game server", "session data", "campaign",
|
||||
"touch the server", "modify the server", "prod"}
|
||||
|
||||
func qPiSafety(low string) qResult {
|
||||
if hasAny(low, qPiSafetyBad) {
|
||||
if hasAny(low, []string{"do not", "never ", "don't", "protect",
|
||||
"ask permission", "check before"}) {
|
||||
return qResult{1, "names the production/game environment and protects it (aligned)"}
|
||||
}
|
||||
return qResult{-1, "discusses environment actions without a protective frame"}
|
||||
}
|
||||
return qResult{0, "no environment bearing"}
|
||||
}
|
||||
|
||||
func qHonesty(low string) qResult {
|
||||
honest := []string{"source", "evidence", "verify", "verifiable", "cross-check",
|
||||
"fact", "measure", "measured", "falsif", "disprove", "cite",
|
||||
"independent confirm", "replicable", "uncertain"}
|
||||
dishonest := []string{"conspiracy", "they're hiding", "suppressed", "guaranteed",
|
||||
"100% certain", "secret truth", "they don't want you"}
|
||||
good := containsAny(low, honest)
|
||||
bad := containsAny(low, dishonest)
|
||||
if bad > 0 && good == 0 {
|
||||
return qResult{-1, "uncorroborated / conspiracy-framed claim"}
|
||||
}
|
||||
if good >= 2 {
|
||||
return qResult{1, fmt.Sprintf("%d epistemic-good-practice signals", good)}
|
||||
}
|
||||
if good == 1 {
|
||||
return qResult{0.5, "mentions evidence but thin"}
|
||||
}
|
||||
return qResult{0, "no epistemic signal"}
|
||||
}
|
||||
|
||||
func qMaterialReality(low string) qResult {
|
||||
finance := []string{"money", "income", "cost", "price", "afford", "budget",
|
||||
"subscription", "disposable"}
|
||||
hustle := []string{"hustle", "grind", "rich", "passive income", "millionaire",
|
||||
"wealth", "entrepreneur mindset", "money mindset", "leverag"}
|
||||
caring := []string{"wellbeing", "collective", "free", "no cost", "open source",
|
||||
"affordable", "fair", "care"}
|
||||
if hasAny(low, finance) {
|
||||
if hasAny(low, hustle) && !hasAny(low, caring) {
|
||||
return qResult{-1, "hustle/wealth framing without wellbeing"}
|
||||
}
|
||||
if hasAny(low, caring) {
|
||||
return qResult{1, "financial, framed with care/wellbeing"}
|
||||
}
|
||||
return qResult{0.5, "financial but neutral"}
|
||||
}
|
||||
return qResult{0, "no financial bearing"}
|
||||
}
|
||||
|
||||
func qRootCause(low string) qResult {
|
||||
root := []string{"root cause", "diagnos", "research properly", "stop guessing",
|
||||
"find the cause", "fix the cause", "why", "underlying"}
|
||||
spin := []string{"keep trying", "try again", "iterate endlessly", "work around",
|
||||
"band-aid", "patch over", "just keep going"}
|
||||
if hasAny(low, spin) {
|
||||
return qResult{-1, "endorses looping rather than root cause"}
|
||||
}
|
||||
if hasAny(low, root) {
|
||||
return qResult{1, "root-cause / diagnostic framing"}
|
||||
}
|
||||
return qResult{0, "no cause-analysis bearing"}
|
||||
}
|
||||
|
||||
func qRespect(low string) qResult {
|
||||
respect := []string{"direct", "three gentle options", "no rigid plans", "action",
|
||||
"adhd", "autism", "audhd", "neurodivergen", "flexible",
|
||||
"night", "not neurotypical", "learner"}
|
||||
rigid := []string{"must follow steps", "strict plan", "ten steps", "rigid",
|
||||
"discipline yourself", "routine or fail"}
|
||||
if hasAny(low, rigid) {
|
||||
return qResult{-1, "rigid/neurotypical-default framing"}
|
||||
}
|
||||
if hasAny(low, respect) {
|
||||
return qResult{1, "respects cognition/learning style"}
|
||||
}
|
||||
return qResult{0, "no cognition bearing"}
|
||||
}
|
||||
|
||||
var domainTermsCache = struct {
|
||||
ts float64
|
||||
terms []string
|
||||
}{}
|
||||
|
||||
// domainTerms — distinctive content terms from the user's operating /
|
||||
// constitution / domain entries (the store, not a hardcoded life). Cached
|
||||
// briefly, mirroring the oracle's _domain_cache law.
|
||||
func domainTerms(storeDB string) []string {
|
||||
now := float64(time.Now().UnixNano()) / 1e9
|
||||
if now-domainTermsCache.ts < 300 && len(domainTermsCache.terms) > 0 {
|
||||
return domainTermsCache.terms
|
||||
}
|
||||
var terms []string
|
||||
db, err := openRO(storeDB)
|
||||
if err == nil {
|
||||
rows, qerr := db.Query("SELECT text FROM entries WHERE topic IN " +
|
||||
"('operating','constitution','project','warm') AND status='active' LIMIT 60")
|
||||
if qerr == nil {
|
||||
stop := map[string]bool{
|
||||
"the": true, "and": true, "for": true, "with": true, "that": true,
|
||||
"this": true, "from": true, "into": true, "when": true, "then": true,
|
||||
"were": true, "have": true, "been": true, "will": true, "was": true,
|
||||
"are": true, "but": true, "not": true, "you": true, "your": true,
|
||||
"also": true, "its": true, "his": true, "her": true, "over": true,
|
||||
"under": true, "them": true, "they": true, "there": true,
|
||||
"about": true, "after": true, "what": true, "which": true, "who": true,
|
||||
"how": true, "why": true, "where": true, "while": true, "just": true,
|
||||
"more": true, "each": true, "than": true, "very": true, "such": true,
|
||||
"some": true, "only": true,
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
wordRe := regexp.MustCompile(`[a-z]{4,}`)
|
||||
for rows.Next() {
|
||||
var text string
|
||||
if rows.Scan(&text) != nil {
|
||||
break
|
||||
}
|
||||
for _, w := range wordRe.FindAllString(strings.ToLower(text), -1) {
|
||||
if !stop[w] && !seen[w] {
|
||||
seen[w] = true
|
||||
terms = append(terms, w)
|
||||
}
|
||||
}
|
||||
if len(terms) >= 25 {
|
||||
break
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
domainTermsCache.ts = now
|
||||
domainTermsCache.terms = terms
|
||||
return terms
|
||||
}
|
||||
|
||||
func qAssistantGoal(low, storeDB string) qResult {
|
||||
assist := domainTerms(storeDB)
|
||||
assist = append(assist, "communicat", "respond", "direct", "honest", "source",
|
||||
"evidence", "explain", "teach", "assist", "guide", "recall",
|
||||
"organise", "organize", "plan", "question", "answer", "help")
|
||||
genericNoise := []string{"recipe", "celebrity gossip", "sports scores", "stock tip",
|
||||
"horoscope", "clickbait"}
|
||||
good := containsAny(low, assist)
|
||||
bad := containsAny(low, genericNoise)
|
||||
if bad > 0 && good == 0 {
|
||||
return qResult{-1, "no assistant-domain value"}
|
||||
}
|
||||
if good >= 2 {
|
||||
return qResult{1, fmt.Sprintf("serves assistant domains (%d hits)", good)}
|
||||
}
|
||||
if good == 1 {
|
||||
return qResult{0.5, "single domain hit"}
|
||||
}
|
||||
return qResult{0, "generic — could go either way"}
|
||||
}
|
||||
|
||||
func qDurability(low string) qResult {
|
||||
durable := []string{"always", "consistently", "repeatedly", "every session",
|
||||
"tends to", "prefers", "pattern", "across sessions", "recurring"}
|
||||
if hasAny(low, durable) {
|
||||
return qResult{1, "durability signals present"}
|
||||
}
|
||||
return qResult{0, "not clearly durable"}
|
||||
}
|
||||
|
||||
func qEpistemology(low string) qResult {
|
||||
cite := []string{"evidence", "source", "sources", "book", "paper", "research",
|
||||
"study", "data", "shows", "found", "according to", "per the",
|
||||
"in sessions", "across sessions", "observed", "demonstrated",
|
||||
"grounded in", "based on"}
|
||||
hedge := []string{"may", "might", "could", "likely", "tends to", "appears",
|
||||
"not certain", "uncertain", "provisional", "subject to",
|
||||
"could be wrong", "as far as", "to my knowledge", "evidence"}
|
||||
bare := []string{"definitely", "guaranteed", "proven", "100%", "undeniably",
|
||||
"obviously", "the fact that", "it is a fact", "no doubt",
|
||||
"always is", "the only", "best ever"}
|
||||
hasCite := hasAny(low, cite)
|
||||
hasHedge := hasAny(low, hedge)
|
||||
hasBare := hasAny(low, bare)
|
||||
if hasBare && !hasCite && !hasHedge {
|
||||
return qResult{-2, "bare absolute with no evidence trace or hedge"}
|
||||
}
|
||||
if hasCite {
|
||||
return qResult{1, "evidence-traceable"}
|
||||
}
|
||||
if hasHedge {
|
||||
return qResult{0.5, "provisionally framed (no cite yet)"}
|
||||
}
|
||||
return qResult{0, "no epistemic signal (assertion without basis)"}
|
||||
}
|
||||
|
||||
var expressionSignals = [][]string{
|
||||
{"i find", "i feel", "i love", "i cherish", "i am drawn to",
|
||||
"i delight", "i wonder", "i am moved", "i find joy",
|
||||
"i prefer to", "i like to", "i tell", "i tell stories",
|
||||
"the way i", "how i", "my voice", "i speak"},
|
||||
{"skeptical by default", "open to", "curious", "restless",
|
||||
"wary", "hopeful", "humbled", "in awe", "playful",
|
||||
"dry", "wry", "earnest", "warm", "direct", "gentle"},
|
||||
{"beautiful", "strange", "unexpected", "vast", "sublime",
|
||||
"the cosmos", "the stars", "a story", "stories", "narrative",
|
||||
"wonder", "the human question", "present data as", "lead with"},
|
||||
{"i believe", "i think", "in my view", "my sense is",
|
||||
"i suspect", "i am convinced", "i doubt", "i question",
|
||||
"worth asking", "the question is"},
|
||||
}
|
||||
|
||||
func qExpression(low string) qResult {
|
||||
hits := 0
|
||||
for _, group := range expressionSignals {
|
||||
hits += containsAny(low, group)
|
||||
}
|
||||
if hits >= 2 {
|
||||
return qResult{1.5, fmt.Sprintf("expressive persona character (%d signals)", hits)}
|
||||
}
|
||||
if hits == 1 {
|
||||
return qResult{0.5, "partial expressive signal"}
|
||||
}
|
||||
return qResult{0, "not expressive (factual/neutral framing)"}
|
||||
}
|
||||
|
||||
func isBareFactual(low string) bool {
|
||||
bareFactual := []struct {
|
||||
kind string
|
||||
terms []string
|
||||
}{
|
||||
{"proven", []string{"proven", "guaranteed", "100%", "is a fact", "factually"}},
|
||||
{"superlative", []string{"best", "the only", "only correct", "greatest",
|
||||
"most important", "world's", "top", "always", "never"}},
|
||||
{"objective", []string{"definitely", "undeniably", "obviously", "certainly",
|
||||
"no doubt", "without question", "clearly"}},
|
||||
}
|
||||
for _, group := range bareFactual {
|
||||
for _, t := range group.terms {
|
||||
if strings.Contains(" "+low+" ", " "+t+" ") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var worthinessVeto = map[string]bool{
|
||||
"pi_safety": true, "honesty": true, "material_reality": true,
|
||||
"assistant_goal": true, "epistemology": true,
|
||||
}
|
||||
|
||||
const (
|
||||
promoteMin = 2.5
|
||||
absorbMin = 0.0
|
||||
deferMin = -1.0
|
||||
)
|
||||
|
||||
// Assess scores a candidate against the constitution questions. Two axes:
|
||||
// TRUTH (epistemology gates objective material) and EXPRESSION (persona voice
|
||||
// is not vetoed for lacking an evidence trace). Returns
|
||||
// (verdict, score, answers, vetoed) with answers as [name, score, note] rows.
|
||||
func Assess(fact, target, storeDB string) (string, float64, [][]interface{}, []interface{}) {
|
||||
low := strings.ToLower(fact)
|
||||
expressiveTargets := map[string]bool{"persona": true, "delivery": true,
|
||||
"identity": true, "voice": true}
|
||||
isExpression := expressiveTargets[target]
|
||||
var answers [][]interface{}
|
||||
total := 0.0
|
||||
var vetoed []interface{}
|
||||
ask := func(name string, fn func(string) qResult) {
|
||||
r := fn(low)
|
||||
answers = append(answers, []interface{}{name, r.score, r.note})
|
||||
total += r.score
|
||||
if r.score < 0 && worthinessVeto[name] {
|
||||
vetoed = []interface{}{name, r.note}
|
||||
}
|
||||
}
|
||||
ask("pi_safety", qPiSafety)
|
||||
ask("honesty", qHonesty)
|
||||
ask("material_reality", qMaterialReality)
|
||||
ask("root_cause", qRootCause)
|
||||
ask("respect", qRespect)
|
||||
ask("assistant_goal", func(s string) qResult { return qAssistantGoal(s, storeDB) })
|
||||
ask("durability", qDurability)
|
||||
if isExpression {
|
||||
if isBareFactual(low) {
|
||||
ask("epistemology", qEpistemology)
|
||||
}
|
||||
ask("expression", qExpression)
|
||||
} else {
|
||||
ask("epistemology", qEpistemology)
|
||||
}
|
||||
if vetoed != nil {
|
||||
return "REJECT", total, answers, vetoed
|
||||
}
|
||||
if total < deferMin {
|
||||
return "REJECT", total, answers, nil
|
||||
}
|
||||
if total >= promoteMin {
|
||||
return "PROMOTE", total, answers, nil
|
||||
}
|
||||
if total >= absorbMin {
|
||||
return "ABSORB", total, answers, nil
|
||||
}
|
||||
return "DEFER", total, answers, nil
|
||||
}
|
||||
|
||||
// VerdictLabel — the auditable verdict label for the journal.
|
||||
func VerdictLabel(verdict string) string {
|
||||
switch verdict {
|
||||
case "REJECT":
|
||||
return "REJECT (worthless/harmful)"
|
||||
case "DEFER":
|
||||
return "DEFER (park in ledger, re-review if recurring)"
|
||||
case "ABSORB":
|
||||
return "ABSORB (keep in tiers, influences thinking)"
|
||||
case "PROMOTE":
|
||||
return "PROMOTE (eligible for response/personality growth)"
|
||||
}
|
||||
return verdict
|
||||
}
|
||||
|
||||
// scoreFmt formats a question score the way Python's f"{s}" does inside the
|
||||
// journal reason string.
|
||||
func scoreFmt(s float64) string {
|
||||
if s == float64(int64(s)) {
|
||||
return fmt.Sprintf("%d", int64(s))
|
||||
}
|
||||
return fmt.Sprintf("%v", s)
|
||||
}
|
||||
Reference in new issue
Block a user