#!/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())