Full name-preserving port of src/rune/lexicon_evolution.py to src/rust/lexicon_evolution (RUST verdict, #2479 dichotomy tranche — 7/7 fns, dichotomy audit match clean): - content_words: regex-equivalent [a-z]{4,} scanner over the lowered text, stop/framing tables 1:1, (text or "") coercion - distinctive_terms: stem-aware document frequency, df < max(total*0.05, 1.5) - shared_distinctive: stem-matched share over raw existing values (None → "") - coherence: conflict/consistent/novel lattice — negation is a SUBSTRING test on the lowered fact, the shared-term test is the padded " term " form - grounded: conjunctive stem hits with the min_distinct counter - _stem: suffix-rule table quirk-for-quirk (migrate→migrat vs migration→migr; retroactive→retroactiv vs retroactively→retroactive) + y→i fold - promote_eligible: strength gate → empty block → distinctive share → no signal → grounded/parked, with the oracle's f-string reasons byte-for-byte (py_num_display: ints stay ints, integral floats keep .0) FFI doc-envelope law ({ok, value|err}), no le_free_string export (siblings share the process; callers free via capability_scoring cs_free_string). rune/le_loader.py mirrors broker_loader.py; facade delegation with RUNE_LE_CORE=python pin, _py_ variants kept verbatim as the load-bearing fallback. Set-valued fns cross the FFI SORTED; the facade restores set types. Parity: 76 frozen truth cases (every public fn, every branch) frozen via scripts/gen_lexicon_evolution_truth.py against the live oracle with RUNE_LE_CORE=python pinned; battery replays VERBATIM 7/7; live replay facade 54/54 + kernel 76/76; clippy 0; test_lexicon_contract 15/15 both modes (kernel active + pinned fallback); test_governance_quartet 15/15; pre-existing failures in test_constitutional_adversarial (7F) and test_property_invariants (collection) proven unrelated via stash-dance.
620 lines
23 KiB
Python
620 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
"""gen_lexicon_evolution_truth.py — freeze/replay parity truth for
|
|
lexicon_evolution (dichotomy wave, #2479 tranche).
|
|
|
|
Freeze mode (default): exercises EVERY public function of the LIVE Python
|
|
oracle (src/rune/lexicon_evolution.py) across every branch and records
|
|
(kernel_input, expected_kernel_output) pairs into
|
|
src/rust/lexicon_evolution/src/frozen_le_truth.json.
|
|
|
|
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
|
|
delegation ACTIVE (RUNE_LE_CORE unpinned) and asserts both kernel-level
|
|
(loader direct) and facade-level (oracle functions delegate) parity
|
|
against the frozen file.
|
|
|
|
Poisoned-truth law: freeze pins RUNE_LE_CORE=python BEFORE any import.
|
|
Mutation law: case inputs are deep-copied BEFORE they reach the oracle —
|
|
the frozen input must be the pre-call input, not a post-call mutation.
|
|
Set law: the oracle's set-valued functions (distinctive_terms,
|
|
shared_distinctive) are frozen and compared as SORTED lists — Python set
|
|
iteration order is not stable across processes; the facade restores set
|
|
types on its public surface.
|
|
Kernel-only group: `_stem` is private to the oracle (no facade symbol);
|
|
its truth group is replayed kernel-side only (the Python _stem body is
|
|
frozen verbatim and covered by tests/test_lexicon_contract.py).
|
|
"""
|
|
import copy
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
TRUTH_PATH = os.path.join(REPO, "src", "rust", "lexicon_evolution", "src", "frozen_le_truth.json")
|
|
REPLAY = "--replay" in sys.argv
|
|
|
|
if not REPLAY:
|
|
os.environ["RUNE_LE_CORE"] = "python" # poisoned-truth law (freeze side)
|
|
else:
|
|
os.environ.pop("RUNE_LE_CORE", None) # live-parity law (facade must delegate)
|
|
|
|
sys.path.insert(0, os.path.join(REPO, "src"))
|
|
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
|
|
|
|
import lexicon_evolution as oracle_mod # noqa: E402
|
|
|
|
if REPLAY:
|
|
from rune import le_loader # noqa: E402 (kernel-level replay side)
|
|
if not le_loader.available():
|
|
print("REPLAY ABORT: kernel not built —", le_loader.LIB_PATH)
|
|
sys.exit(2)
|
|
|
|
|
|
class Case:
|
|
def __init__(self, name, fn):
|
|
self.name = name
|
|
self.fn = fn
|
|
|
|
|
|
class _Unset:
|
|
pass
|
|
|
|
|
|
_UNSET = _Unset()
|
|
|
|
|
|
def _input(text=_UNSET, word=_UNSET, fact=_UNSET, proposal=_UNSET,
|
|
corpus_texts=_UNSET, existing_values=_UNSET, source_texts=_UNSET,
|
|
min_distinct=_UNSET, min_strength=_UNSET, evidence_strength=_UNSET):
|
|
payload = {}
|
|
if text is not _UNSET:
|
|
payload["text"] = text
|
|
if word is not _UNSET:
|
|
payload["word"] = word
|
|
if fact is not _UNSET:
|
|
payload["fact"] = fact
|
|
if proposal is not _UNSET:
|
|
payload["proposal"] = proposal
|
|
if corpus_texts is not _UNSET:
|
|
payload["corpus_texts"] = list(corpus_texts)
|
|
if existing_values is not _UNSET:
|
|
payload["existing_values"] = list(existing_values)
|
|
if source_texts is not _UNSET:
|
|
payload["source_texts"] = list(source_texts)
|
|
if min_distinct is not _UNSET and min_distinct is not None:
|
|
payload["min_distinct"] = min_distinct
|
|
if min_strength is not _UNSET and min_strength is not None:
|
|
payload["min_strength"] = min_strength
|
|
if evidence_strength is not _UNSET:
|
|
payload["evidence_strength"] = evidence_strength
|
|
return payload
|
|
|
|
|
|
def sorted_or(x):
|
|
return sorted(x) if isinstance(x, (set, frozenset)) else x
|
|
|
|
|
|
def facade_call(group, inp):
|
|
"""Run the LIVE facade (delegates to the Rust core when built)."""
|
|
if group == "content_words":
|
|
return oracle_mod.content_words(inp["text"])
|
|
if group == "distinctive_terms":
|
|
return oracle_mod.distinctive_terms(inp["fact"], inp["corpus_texts"])
|
|
if group == "shared_distinctive":
|
|
return oracle_mod.shared_distinctive(
|
|
inp["fact"], inp["existing_values"], inp["corpus_texts"])
|
|
if group == "coherence":
|
|
return oracle_mod.coherence(
|
|
inp["fact"], inp["existing_values"], inp["corpus_texts"])
|
|
if group == "grounded":
|
|
if "min_distinct" in inp:
|
|
return oracle_mod.grounded(inp["fact"], inp["source_texts"],
|
|
min_distinct=inp["min_distinct"])
|
|
return oracle_mod.grounded(inp["fact"], inp["source_texts"])
|
|
if group == "promote_eligible":
|
|
return oracle_mod.promote_eligible(
|
|
inp["proposal"], inp["existing"], inp["evidence_strength"],
|
|
min_strength=inp.get("min_strength", 3),
|
|
corpus_texts=inp["corpus_texts"])
|
|
raise KeyError(group)
|
|
|
|
|
|
def kernel_call(group, inp):
|
|
"""Run the Rust kernel directly through the ctypes loader."""
|
|
return getattr(le_loader, group)(**copy.deepcopy(inp))
|
|
|
|
|
|
# ── content_words ────────────────────────────────────────────────────────────
|
|
|
|
def cw_case(rec, name, text):
|
|
inp = _input(text=text)
|
|
got = oracle_mod.content_words(copy.deepcopy(text))
|
|
rec(name, inp, got, {"words": got})
|
|
|
|
|
|
def c_cw_basic(rec):
|
|
cw_case(rec, "cw-basic-stop-framing-filtered",
|
|
"The quick brown fox jumps over the lazy dog")
|
|
|
|
|
|
def c_cw_none(rec):
|
|
cw_case(rec, "cw-none-coerced-empty", None)
|
|
|
|
|
|
def c_cw_zero(rec):
|
|
# falsy non-string → (text or "") → ""
|
|
cw_case(rec, "cw-zero-falsy-coerced-empty", 0)
|
|
|
|
|
|
def c_cw_empty(rec):
|
|
cw_case(rec, "cw-empty-string", "")
|
|
|
|
|
|
def c_cw_uppercase(rec):
|
|
cw_case(rec, "cw-uppercase-folded", "PREFERS Oatmilk AND Celesta")
|
|
|
|
|
|
def c_cw_short(rec):
|
|
cw_case(rec, "cw-short-words-dropped", "a an the cat dog ox")
|
|
|
|
|
|
def c_cw_framing(rec):
|
|
cw_case(rec, "cw-framing-words-dropped",
|
|
"work always way thing value identity persona")
|
|
|
|
|
|
def c_cw_punct(rec):
|
|
cw_case(rec, "cw-punctuation-splits", "co-ffee! 1234 espresso's brew.")
|
|
|
|
|
|
def c_cw_mixed(rec):
|
|
cw_case(rec, "cw-mixed-stop-framing", "she will make the work better each day")
|
|
|
|
|
|
# ── distinctive_terms ────────────────────────────────────────────────────────
|
|
|
|
def dt_case(rec, name, fact, corpus):
|
|
inp = _input(fact=fact, corpus_texts=corpus)
|
|
got = oracle_mod.distinctive_terms(copy.deepcopy(fact), copy.deepcopy(corpus))
|
|
rec(name, inp, got, {"terms": sorted(got)})
|
|
|
|
|
|
def c_dt_empty_fact(rec):
|
|
dt_case(rec, "dt-empty-fact", "", ["some text here"])
|
|
|
|
|
|
def c_dt_all_stop(rec):
|
|
dt_case(rec, "dt-fact-all-stop-framing", "the that this work",
|
|
["some text here"])
|
|
|
|
|
|
def c_dt_rare(rec):
|
|
dt_case(rec, "dt-rare-terms-distinctive", "she drinks oatmilk daily",
|
|
["oatmilk is life", "coffee today", "tea later"])
|
|
|
|
|
|
def c_dt_common(rec):
|
|
dt_case(rec, "dt-common-term-not-distinctive", "physics of black holes",
|
|
["physics paper one", "physics paper two", "more physics here",
|
|
"unrelated text"])
|
|
|
|
|
|
def _filler_corpus(target_word, doc_ids, n=40):
|
|
corpus = []
|
|
for i in range(n):
|
|
if i in doc_ids:
|
|
corpus.append(f"the {target_word} session notes")
|
|
else:
|
|
corpus.append("filler text here")
|
|
return corpus
|
|
|
|
|
|
def c_dt_boundary_not_distinctive(rec):
|
|
# df 2 at total 40 → threshold max(2.0, 1.5) = 2.0 → 2 < 2 is False
|
|
dt_case(rec, "dt-boundary-df2-not-distinctive",
|
|
"celesta zephyr quantum", _filler_corpus("celesta", {0, 1}))
|
|
|
|
|
|
def c_dt_boundary_distinctive(rec):
|
|
# df 1 at total 40 → 1 < 2.0 → distinctive
|
|
dt_case(rec, "dt-boundary-df1-distinctive",
|
|
"celesta zephyr", _filler_corpus("celesta", {0}))
|
|
|
|
|
|
def c_dt_boundary_df3(rec):
|
|
dt_case(rec, "dt-boundary-df3-not-distinctive",
|
|
"celesta zephyr quantum", _filler_corpus("quantum", {0, 1, 2}))
|
|
|
|
|
|
def c_dt_stem_df(rec):
|
|
# migrate/migration share the stem "migr" → df counts both docs
|
|
dt_case(rec, "dt-stem-aware-document-frequency", "migrate north soon",
|
|
["the migration was huge", "another migration happened", "calm day"])
|
|
|
|
|
|
def c_dt_empty_corpus(rec):
|
|
dt_case(rec, "dt-empty-corpus-all-distinctive", "oatmilk ritual", [])
|
|
|
|
|
|
def c_dt_case_fold(rec):
|
|
dt_case(rec, "dt-case-folding", "OATMILK Rituals", ["oatmilk bar"])
|
|
|
|
|
|
# ── shared_distinctive ───────────────────────────────────────────────────────
|
|
|
|
def sd_case(rec, name, fact, existing, corpus):
|
|
inp = _input(fact=fact, existing_values=existing, corpus_texts=corpus)
|
|
got = oracle_mod.shared_distinctive(
|
|
copy.deepcopy(fact), copy.deepcopy(existing), copy.deepcopy(corpus))
|
|
rec(name, inp, got, {"shared": sorted(got)})
|
|
|
|
|
|
def c_sd_basic(rec):
|
|
sd_case(rec, "sd-shared-distinctive-term", "she never jokes about oatmilk",
|
|
["i love oatmilk a lot"], ["oatmilk is life", "tea later"])
|
|
|
|
|
|
def c_sd_none_shared(rec):
|
|
sd_case(rec, "sd-nothing-shared", "celesta practice daily",
|
|
["i love oatmilk"], ["oatmilk is life", "tea later"])
|
|
|
|
|
|
def c_sd_distinct_empty(rec):
|
|
# framing-only fact → distinctive_terms empty → short-circuit
|
|
sd_case(rec, "sd-distinct-empty-short-circuit", "work always thing",
|
|
["whatever corpus text"], ["whatever corpus text"])
|
|
|
|
|
|
def c_sd_stem_shared(rec):
|
|
sd_case(rec, "sd-stem-aware-shared", "migration policy debate",
|
|
["we migrated north"], ["migration news", "calm"])
|
|
|
|
|
|
def c_sd_none_value(rec):
|
|
sd_case(rec, "sd-none-existing-value-filtered", "she never jokes about oatmilk",
|
|
[None, "i love oatmilk"], ["oatmilk is life", "tea later"])
|
|
|
|
|
|
def c_sd_empty_existing(rec):
|
|
sd_case(rec, "sd-empty-existing", "oatmilk mornings", [], ["oatmilk is life"])
|
|
|
|
|
|
# ── coherence ────────────────────────────────────────────────────────────────
|
|
|
|
def co_case(rec, name, fact, existing, corpus=None):
|
|
inp = _input(fact=fact, existing_values=existing,
|
|
corpus_texts=[] if corpus is None else corpus)
|
|
if corpus is None:
|
|
got = oracle_mod.coherence(copy.deepcopy(fact), copy.deepcopy(existing))
|
|
else:
|
|
got = oracle_mod.coherence(copy.deepcopy(fact), copy.deepcopy(existing),
|
|
copy.deepcopy(corpus))
|
|
rec(name, inp, got, {"verdict": got})
|
|
|
|
|
|
def c_co_empty_existing(rec):
|
|
co_case(rec, "co-empty-existing-novel", "oatmilk mornings", [])
|
|
|
|
|
|
def c_co_blank_existing(rec):
|
|
co_case(rec, "co-blank-existing-filtered-novel", "oatmilk mornings",
|
|
["", " ", None])
|
|
|
|
|
|
def c_co_conflict(rec):
|
|
co_case(rec, "co-negation-on-shared-distinctive-conflict",
|
|
"she never jokes about oatmilk", ["i love oatmilk"],
|
|
["oatmilk bar", "tea"])
|
|
|
|
|
|
def c_co_negation_off_shared(rec):
|
|
# negation present but the shared term is not a standalone word in the
|
|
# fact ("oatmilk-lover" has no trailing space) → consistent
|
|
co_case(rec, "co-negation-not-on-shared-term-consistent",
|
|
"oatmilk-lover never smiles", ["i love oatmilk"],
|
|
["oatmilk bar", "tea"])
|
|
|
|
|
|
def c_co_consistent(rec):
|
|
co_case(rec, "co-shared-distinctive-consistent",
|
|
"i enjoy oatmilk mornings", ["i love oatmilk"],
|
|
["oatmilk bar", "tea"])
|
|
|
|
|
|
def c_co_novel(rec):
|
|
co_case(rec, "co-nothing-shared-novel", "celesta practice daily",
|
|
["i love oatmilk"], ["oatmilk bar", "tea"])
|
|
|
|
|
|
def c_co_corpus_kills_shared(rec):
|
|
# corpus override drives oatmilk df over the threshold → shared empty → novel
|
|
co_case(rec, "co-corpus-override-kills-shared-novel",
|
|
"she never jokes about oatmilk", ["i love oatmilk"],
|
|
["oatmilk"] * 30)
|
|
|
|
|
|
def c_co_default_corpus(rec):
|
|
# corpus_texts omitted → corpus = the (filtered) existing values
|
|
co_case(rec, "co-default-corpus-is-values", "i enjoy oatmilk mornings",
|
|
["i love oatmilk"])
|
|
|
|
|
|
# ── grounded ─────────────────────────────────────────────────────────────────
|
|
|
|
def gr_case(rec, name, fact, sources, min_distinct=None):
|
|
inp = _input(fact=fact, source_texts=sources, min_distinct=min_distinct)
|
|
if min_distinct is None:
|
|
got = oracle_mod.grounded(copy.deepcopy(fact), copy.deepcopy(sources))
|
|
else:
|
|
got = oracle_mod.grounded(copy.deepcopy(fact), copy.deepcopy(sources),
|
|
min_distinct=min_distinct)
|
|
rec(name, inp, got, {"grounded": got})
|
|
|
|
|
|
def c_gr_no_srcs(rec):
|
|
gr_case(rec, "gr-empty-sources-false", "she drinks oatmilk daily", [])
|
|
|
|
|
|
def c_gr_blank_srcs(rec):
|
|
gr_case(rec, "gr-blank-sources-false", "she drinks oatmilk daily", ["", " "])
|
|
|
|
|
|
def c_gr_no_fact_terms(rec):
|
|
gr_case(rec, "gr-fact-no-content-words-false", "the work thing",
|
|
["oatmilk is life"])
|
|
|
|
|
|
def c_gr_direct(rec):
|
|
gr_case(rec, "gr-direct-word-hit", "she drinks oatmilk daily",
|
|
["oatmilk is life"])
|
|
|
|
|
|
def c_gr_stem(rec):
|
|
gr_case(rec, "gr-stem-aware-hit", "migrated north",
|
|
["the migration of songbirds"])
|
|
|
|
|
|
def c_gr_miss(rec):
|
|
gr_case(rec, "gr-no-stem-overlap-false", "quantum flux patterns",
|
|
["oatmilk life"])
|
|
|
|
|
|
def c_gr_min2_one_hit(rec):
|
|
gr_case(rec, "gr-min-distinct-2-one-hit-false", "she drinks oatmilk with celesta",
|
|
["oatmilk life"], min_distinct=2)
|
|
|
|
|
|
def c_gr_min2_two_hits(rec):
|
|
gr_case(rec, "gr-min-distinct-2-two-hits-true", "she drinks oatmilk with celesta",
|
|
["oatmilk and celesta life"], min_distinct=2)
|
|
|
|
|
|
def c_gr_case(rec):
|
|
gr_case(rec, "gr-source-case-folded", "she drinks oatmilk daily",
|
|
["OATMILK IS LIFE"])
|
|
|
|
|
|
# ── _stem (kernel-only: private in the oracle) ───────────────────────────────
|
|
|
|
STEM_WORDS = [
|
|
"migration", "migrated", "migrating", "migrate", "migrates",
|
|
"retroactively", "retroactive", "honesty", "ability", "abilities",
|
|
"running", "kindness", "cats", "dogs", "hope", "oatmilk", "rituals",
|
|
"nation", "national", "carefully", "careful", "berries",
|
|
]
|
|
|
|
|
|
def st_case(rec, name, word):
|
|
inp = _input(word=word)
|
|
got = oracle_mod._stem(copy.deepcopy(word))
|
|
rec(name, inp, got, {"stem": got})
|
|
|
|
|
|
def c_stem_table(rec):
|
|
for w in STEM_WORDS:
|
|
st_case(rec, f"stem-{w}", w)
|
|
|
|
|
|
# ── promote_eligible ─────────────────────────────────────────────────────────
|
|
|
|
def pe_case(rec, name, proposal, existing, strength, min_strength=None, corpus=()):
|
|
inp = _input(proposal=proposal, existing_values=existing,
|
|
evidence_strength=strength, min_strength=min_strength,
|
|
corpus_texts=corpus)
|
|
# the oracle/loader param is `existing` (the promote signature)
|
|
inp["existing"] = inp.pop("existing_values")
|
|
if min_strength is None:
|
|
got = oracle_mod.promote_eligible(copy.deepcopy(proposal),
|
|
copy.deepcopy(existing), strength)
|
|
else:
|
|
got = oracle_mod.promote_eligible(
|
|
copy.deepcopy(proposal), copy.deepcopy(existing), strength,
|
|
min_strength=min_strength, corpus_texts=copy.deepcopy(corpus))
|
|
rec(name, inp, got, {"eligible": got["eligible"], "reason": got["reason"]})
|
|
|
|
|
|
def c_pe_below_strength(rec):
|
|
pe_case(rec, "pe-strength-gate-first", "oatmilk mornings", ["i love oatmilk"],
|
|
2, min_strength=3, corpus=["oatmilk bar"])
|
|
|
|
|
|
def c_pe_float_strength(rec):
|
|
pe_case(rec, "pe-float-strength-message", "oatmilk mornings", ["i love oatmilk"],
|
|
2.5, min_strength=3, corpus=["oatmilk bar"])
|
|
|
|
|
|
def c_pe_empty_block(rec):
|
|
pe_case(rec, "pe-empty-block-first-principle", "oatmilk mornings", [], 3,
|
|
min_strength=3)
|
|
|
|
|
|
def c_pe_blank_block(rec):
|
|
pe_case(rec, "pe-blank-block-filtered-first-principle", "oatmilk mornings",
|
|
["", " "], 3, min_strength=3)
|
|
|
|
|
|
def c_pe_shared(rec):
|
|
pe_case(rec, "pe-shared-distinctive-eligible", "i genuinely love oatmilk rituals",
|
|
["i love oatmilk"], 3, min_strength=3,
|
|
corpus=["oatmilk bar", "tea later"])
|
|
|
|
|
|
def c_pe_shared_multi(rec):
|
|
pe_case(rec, "pe-shared-multiple-sorted-top3", "oatmilk celesta mornings",
|
|
["oatmilk and celesta"], 3, min_strength=3,
|
|
corpus=["oatmilk bar", "celesta hall"])
|
|
|
|
|
|
def c_pe_no_terms(rec):
|
|
pe_case(rec, "pe-framing-only-no-lexical-signal", "work always thing way",
|
|
["i love oatmilk"], 3, min_strength=3, corpus=["oatmilk bar"])
|
|
|
|
|
|
def c_pe_novel_grounded(rec):
|
|
pe_case(rec, "pe-novel-axis-grounded-eligible", "celesta practice daily",
|
|
["i love oatmilk"], 3, min_strength=3,
|
|
corpus=["she plays celesta nightly"])
|
|
|
|
|
|
def c_pe_novel_parked(rec):
|
|
pe_case(rec, "pe-novel-axis-not-grounded-parked", "celesta practice daily",
|
|
["i love oatmilk"], 3, min_strength=3, corpus=["oatmilk bar only"])
|
|
|
|
|
|
def c_pe_min_strength_one(rec):
|
|
pe_case(rec, "pe-min-strength-1-passes-gate", "oatmilk mornings", [], 1,
|
|
min_strength=1)
|
|
|
|
|
|
def c_pe_corpus_override_grounded(rec):
|
|
# corpus override pushes oatmilk df over the threshold → shared empty →
|
|
# novel axis, but the proposal still grounds in the overridden corpus
|
|
pe_case(rec, "pe-corpus-override-novel-grounded", "oatmilk rituals",
|
|
["i love oatmilk"], 3, min_strength=3,
|
|
corpus=["oatmilk here", "oatmilk there", "oatmilk everywhere"])
|
|
|
|
|
|
def c_pe_default_corpus(rec):
|
|
# corpus_texts omitted → corpus = existing values
|
|
pe_case(rec, "pe-default-corpus-is-existing", "oatmilk rituals",
|
|
["i love oatmilk"], 3, min_strength=3)
|
|
|
|
|
|
# ── driver ───────────────────────────────────────────────────────────────────
|
|
|
|
CONTENT_WORDS = [c_cw_basic, c_cw_none, c_cw_zero, c_cw_empty, c_cw_uppercase,
|
|
c_cw_short, c_cw_framing, c_cw_punct, c_cw_mixed]
|
|
DISTINCTIVE = [c_dt_empty_fact, c_dt_all_stop, c_dt_rare, c_dt_common,
|
|
c_dt_boundary_not_distinctive, c_dt_boundary_distinctive,
|
|
c_dt_boundary_df3, c_dt_stem_df, c_dt_empty_corpus, c_dt_case_fold]
|
|
SHARED = [c_sd_basic, c_sd_none_shared, c_sd_distinct_empty, c_sd_stem_shared,
|
|
c_sd_none_value, c_sd_empty_existing]
|
|
COHERENCE = [c_co_empty_existing, c_co_blank_existing, c_co_conflict,
|
|
c_co_negation_off_shared, c_co_consistent, c_co_novel,
|
|
c_co_corpus_kills_shared, c_co_default_corpus]
|
|
GROUNDED = [c_gr_no_srcs, c_gr_blank_srcs, c_gr_no_fact_terms, c_gr_direct,
|
|
c_gr_stem, c_gr_miss, c_gr_min2_one_hit, c_gr_min2_two_hits,
|
|
c_gr_case]
|
|
STEM = [c_stem_table]
|
|
PROMOTE = [c_pe_below_strength, c_pe_float_strength, c_pe_empty_block,
|
|
c_pe_blank_block, c_pe_shared, c_pe_shared_multi, c_pe_no_terms,
|
|
c_pe_novel_grounded, c_pe_novel_parked, c_pe_min_strength_one,
|
|
c_pe_corpus_override_grounded, c_pe_default_corpus]
|
|
|
|
# group → (cases, facade_replayable, kernel_symbol, unwrap of expected)
|
|
GROUPS = {
|
|
"content_words": (CONTENT_WORDS, True, "le_content_words",
|
|
lambda w: w["words"]),
|
|
"distinctive_terms": (DISTINCTIVE, True, "le_distinctive_terms",
|
|
lambda w: w["terms"]),
|
|
"shared_distinctive": (SHARED, True, "le_shared_distinctive",
|
|
lambda w: w["shared"]),
|
|
"coherence": (COHERENCE, True, "le_coherence", lambda w: w["verdict"]),
|
|
"grounded": (GROUNDED, True, "le_grounded", lambda w: w["grounded"]),
|
|
"stem": (STEM, False, "le_stem", lambda w: w["stem"]),
|
|
"promote_eligible": (PROMOTE, True, "le_promote_eligible", lambda w: w),
|
|
}
|
|
|
|
|
|
def canon(x) -> str:
|
|
"""Content canonicalization (key-order independent)."""
|
|
return json.dumps(json.loads(json.dumps(x, default=str)), sort_keys=True)
|
|
|
|
|
|
def main():
|
|
frozen = None
|
|
if REPLAY:
|
|
with open(TRUTH_PATH) as f:
|
|
frozen = json.load(f)["cases"]
|
|
|
|
groups = {}
|
|
for group, (cases, facade_replayable, _sym, _unwrap) in GROUPS.items():
|
|
recs = []
|
|
|
|
def rec(name, inp, oracle_ret, expected, _r=recs):
|
|
entry = {"name": name, "input": inp,
|
|
"return": sorted_or(oracle_ret) if group in
|
|
("distinctive_terms", "shared_distinctive")
|
|
else oracle_ret,
|
|
"expected": expected}
|
|
if REPLAY and facade_replayable:
|
|
old = next(c for c in frozen[group] if c["name"] == name)
|
|
for key in ("input", "return", "expected"):
|
|
got, want = entry[key], old[key]
|
|
if canon(got) != canon(want):
|
|
print(f"FACADE DIVERGE [{group}] {name} {key}")
|
|
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
|
|
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
|
|
recs.append({"name": name, "diverged": key})
|
|
return
|
|
_r.append(entry)
|
|
|
|
for c in cases:
|
|
c(rec)
|
|
groups[group] = recs
|
|
|
|
total = sum(len(v) for v in groups.values())
|
|
if REPLAY:
|
|
facade_groups = [g for g, (_, r, _, _) in GROUPS.items() if r]
|
|
facade_total = sum(len(groups[g]) for g in facade_groups)
|
|
facade_failures = sum(1 for g in facade_groups for c in groups[g]
|
|
if "diverged" in c)
|
|
|
|
failures = 0
|
|
checked = 0
|
|
for group, cases in frozen.items():
|
|
_sym = GROUPS[group][2]
|
|
unwrap = GROUPS[group][3]
|
|
for case in cases:
|
|
checked += 1
|
|
got = kernel_call(group, case["input"])
|
|
want = unwrap(case["expected"])
|
|
if canon(got) != canon(want):
|
|
failures += 1
|
|
print(f"KERNEL DIVERGE [{group}] {case['name']}")
|
|
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
|
|
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
|
|
print(f"replay: facade {facade_total - facade_failures}/{facade_total}, "
|
|
f"kernel {checked - failures}/{checked}")
|
|
return 1 if (failures or facade_failures) else 0
|
|
|
|
doc = {
|
|
"frozen": datetime.now(timezone.utc).isoformat(),
|
|
"oracle": "src/rune/lexicon_evolution.py",
|
|
"pin": "RUNE_LE_CORE=python",
|
|
"note": "Frozen via scripts/gen_lexicon_evolution_truth.py against the "
|
|
"live Python oracle. Battery embeds this VERBATIM. Group "
|
|
"`stem` is kernel-only (_stem is private in the oracle).",
|
|
"cases": groups,
|
|
}
|
|
os.makedirs(os.path.dirname(TRUTH_PATH), exist_ok=True)
|
|
with open(TRUTH_PATH, "w") as f:
|
|
json.dump(doc, f, indent=1)
|
|
f.write("\n")
|
|
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|