240 lines
11 KiB
Python
240 lines
11 KiB
Python
#!/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}")
|