Files
rune/scripts/gen_pins_truth.py
T

240 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""gen_pins_truth.py — factory wave GAP truth for pins.
Freezes the oracle behavior of the 4 audit-gap functions (dichotomy wave
#2479: _store_path, _detail_blob, unpin, list_pins) through the SHARED
harness (scripts/freeze_truth.py — one law).
The store-mutating surfaces run through the freeze rig
(scripts/pn_freeze_seams/sitecustomize.py — PYTHONPATH-imported by BOTH
this generator and --verify): pinned floor (RUNE_COGNITION_CORE=python),
hermetic per-row scratch store seeded exactly as the oracle _save writes
it, frozen RUNE_HOME set BEFORE the oracle import (PINS_STORE is an
import-time derivation — pin_home law), and machine-path sentinelization
(__PN_FREEZE__ — no absolute paths ride frozen truth).
Run (from the repo root):
RUNE_COGNITION_CORE=python PYTHONPATH=scripts/pn_freeze_seams:src \
python3 scripts/gen_pins_truth.py
Verify (byte-identical, same rig):
PYTHONPATH=scripts/pn_freeze_seams:src python3 scripts/freeze_truth.py \
--verify src/rust/cognition_core/src/frozen_pins_truth.json \
--module rune.pins
Case map (every gap branch):
* _detail_blob — defaults / reason+source kwargs / unicode +
astral + control escape / empty / non-str
scalar values (int/bool/None/0.5-float).
* pn_freeze_store_path — plain / trailing-slash / empty / root / double
slash / nested-double-slash / relative.
* pn_freeze_canonical_store_path — the REAL _store_path() (frozen home,
sentinelized prefix — the import-time derivation, captured before the seam rebind).
* pn_freeze_unpin — found+reason / default reason / not-found /
duplicate topic (most recent open wins) /
resolved-only / junk blob / empty blob /
missing / None details / extra keys re-sorted /
"null"-int-list-str-float-bool blob crash rows /
non-str detail VALUES (json.loads TypeError) /
int + unicode reason / int topic.
* pn_freeze_list — empty store / full board (reversal) /
open_only filter / limit truncation (2, 0, 1,
99) / bool-as-int limit / junk + empty +
"{}" + partial + non-str values / "null"-int
-list-str blob AttributeError crash rows /
negative limit (-1, -99) / non-integral limit
TypeError / unicode board.
"""
import json
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "scripts"))
from freeze_truth import freeze, write_envelope # noqa: E402
NOW = "2026-10-13T12:00:00.123456+00:00"
def rec(iid, state, blob, i=0):
"""One canonical PinRecord dict (the _load() surface the kernel takes)."""
return {
"schema_version": 1,
"kind": "pin",
"item_id": iid,
"owner": "pins",
"state": state,
"evidence_ref": "",
"details": blob,
}
def rec_raw(iid, details, extra=None):
"""A record whose `details` is a NON-STRING value (corrupt-store class)."""
r = rec(iid, "open", None)
r["details"] = details
return r
def blob(question, outcome, context, reason="", source="session"):
return json.dumps(
{
"question": question,
"outcome": outcome,
"context": context,
"reason": reason,
"source": source,
},
sort_keys=True,
)
B1 = blob("q1", "o1", "c1")
B2 = blob("q2", "o2", "shared ctx")
B3 = blob("q3", "o3", "resolved ctx", reason="r3", source="wave")
cases = [
# ── _detail_blob (the store blob is a BYTE contract: separators,
# sort_keys, ensure_ascii) ──────────────────────────────────────────
("_detail_blob", ("who?", "fixed", "ctx"), {}),
("_detail_blob", ("who?", "fixed", "ctx"), {"reason": "r2", "source": "wave"}),
("_detail_blob", ("qué?", "out\U0001f600", "c<>&"), {}),
("_detail_blob", ('d"q', "b\\", "n\nl\tt"), {}),
("_detail_blob", ("", "", ""), {}),
("_detail_blob", ("q", "o", "c"), {"reason": "qué \U0001f600", "source": "éwave"}),
# non-str scalar payloads: raw (the blob serializes them verbatim)
("_detail_blob", ("q", 3, "c", True, "session"), {}),
("_detail_blob", ("q", 0.5, "c", None, None), {}),
# ── _store_path derivation (sentinelized; kernel replays the join) ──
("pn_freeze_store_path", ("__MEM__",), {}),
("pn_freeze_store_path", ("__MEM__/",), {}),
("pn_freeze_store_path", ("",), {}),
("pn_freeze_store_path", ("/",), {}),
("pn_freeze_store_path", ("a//b//",), {}),
("pn_freeze_store_path", ("/a//",), {}),
("pn_freeze_store_path", ("rel",), {}),
("pn_freeze_canonical_store_path", (), {}),
# ── pins_unpin: kernel contract = updated records + found flag;
# the seeded bytes come back as post-call store text ────────────────
# found resolution: state flip open → resolved, reason merged, blob
# re-encoded (sort_keys, ", "/": " separators).
("pn_freeze_unpin", ([rec("t1", "open", B1)], "t1"), {"reason": "done"}),
# default reason "" still merges ({"reason": ""} re-encoded).
("pn_freeze_unpin", ([rec("t1", "open", B1)], "t1"), {}),
# not-found: store unchanged (kernel must return the records untouched).
("pn_freeze_unpin", ([rec("absent", "open", B1)], "absent"), {"reason": "why"}),
# duplicate topic: only the MOST RECENT open pin flips (reverse scan).
(
"pn_freeze_unpin",
([rec("dup", "open", B1), rec("dup", "open", B2)], "dup"),
{"reason": "rr"},
),
# a resolved pin is never re-touched: open-only scan.
(
"pn_freeze_unpin",
([rec("t1", "open", B1), rec("gone", "resolved", B2)], "gone"),
{"reason": "again"},
),
# corrupt blobs fold to {} then merge the reason.
("pn_freeze_unpin", ([rec("j", "open", "junk{")], "j"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("e", "open", "")], "e"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("m", "open", None)], "m"), {"reason": "r"}),
(
"pn_freeze_unpin",
([rec("x", "open", json.dumps({"zz": "last", "aa": "first"}))], "x"),
{"reason": "r"},
),
# crash-parity rows: string blobs parsing to non-dicts raise the exact
# Python TypeError (store unchanged).
("pn_freeze_unpin", ([rec("n", "open", "null")], "n"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("i", "open", "5")], "i"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("l", "open", "[1,2]")], "l"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("s", "open", '"abc"')], "s"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("f", "open", "5.5")], "f"), {"reason": "r"}),
("pn_freeze_unpin", ([rec("b", "open", "true")], "b"), {"reason": "r"}),
# non-str detail VALUES: json.loads TypeError (uncatched in unpin).
("pn_freeze_unpin", ([rec_raw("v", 5)], "v"), {"reason": "r"}),
("pn_freeze_unpin", ([rec_raw("b2", True)], "b2"), {"reason": "r"}),
("pn_freeze_unpin", ([rec_raw("a2", [1])], "a2"), {"reason": "r"}),
("pn_freeze_unpin", ([rec_raw("d2", {"a": 1})], "d2"), {"reason": "r"}),
# raw reason values ride the blob verbatim.
("pn_freeze_unpin", ([rec("r3i", "open", B1)], "r3i"), {"reason": 3}),
("pn_freeze_unpin", ([rec("r3u", "open", B1)], "r3u"), {"reason": "qué \U0001f600"}),
# int topic (non-str): not-found decision + envelope built Python-side.
("pn_freeze_unpin", ([rec("t1", "open", B1)], 5), {}),
# ── pins_list: kernel contract = projected rows (project → filter →
# reverse → slice); the FULL projection surface ─────────────────────
("pn_freeze_list", ([],), {}),
(
"pn_freeze_list",
(
[
rec("t1", "open", B1),
rec("t2", "open", B2),
rec("t3", "resolved", B3),
rec("t4", "open", blob("q4", "o4", "t4 only", reason="r4")),
],
),
{},
),
(
"pn_freeze_list",
([rec("t1", "open", B1), rec("t2", "open", B2), rec("t3", "resolved", B3)],),
{"open_only": True, "limit": 2},
),
(
"pn_freeze_list",
([rec("t1", "open", B1), rec("t2", "open", B2), rec("t3", "resolved", B3)],),
{"limit": 0},
),
(
"pn_freeze_list",
([rec("t1", "open", B1), rec("t2", "open", B2), rec("t3", "resolved", B3)],),
{"limit": 1},
),
(
"pn_freeze_list",
([rec("t1", "open", B1), rec("t2", "open", B2), rec("t3", "resolved", B3)],),
{"limit": 99},
),
# bool-as-int limit ([:True] == [:1] — Python bool subclass quirk).
("pn_freeze_list", ([rec("t1", "open", B1), rec("t2", "open", B2)],), {"limit": True}),
# negative limits: slice-from-tail semantics.
("pn_freeze_list", ([rec("t1", "open", B1), rec("t2", "open", B2), rec("t3", "open", B3)],), {"limit": -1}),
("pn_freeze_list", ([rec("t1", "open", B1), rec("t2", "open", B2), rec("t3", "open", B3)],), {"limit": -99}),
# non-integral limit → TypeError (slice indices message).
("pn_freeze_list", ([rec("t1", "open", B1)],), {"limit": 1.5}),
# corrupt / empty / partial blobs project with defaults.
("pn_freeze_list", ([rec("j", "open", "junk{")],), {}),
("pn_freeze_list", ([rec("e", "open", "")],), {}),
("pn_freeze_list", ([rec("o", "open", "{}")],), {}),
("pn_freeze_list", ([rec("p", "open", '{"reason": "only-reason"}')],), {}),
("pn_freeze_list", ([rec_raw("v", 5)],), {}),
("pn_freeze_list", ([rec_raw("d", {})],), {}),
("pn_freeze_list", ([rec_raw("w", None)],), {}),
# crash rows: string blobs parsing to non-dicts → .get AttributeError.
("pn_freeze_list", ([rec("n", "open", "null")],), {}),
("pn_freeze_list", ([rec("i", "open", "5")],), {}),
("pn_freeze_list", ([rec("l", "open", "[1]")],), {}),
("pn_freeze_list", ([rec("s", "open", '"abc"')],), {}),
# unicode contents + state mix on one board.
(
"pn_freeze_list",
(
[
rec("té", "open", blob("qué", "o\U0001f600", "c")),
rec("t2", "resolved", blob("q", "o", "shared – dash")),
rec("t3", "open", B2),
],
),
{},
),
]
truth = freeze("rune.pins", cases, oracle_module="rune.pins")
dest = REPO / "src" / "rust" / "cognition_core" / "src" / "frozen_pins_truth.json"
write_envelope(NOW, truth, dest)
print(f"wrote {len(truth)} cases -> {dest}")