feat(state_checkpoint): port to FULL Rust — dichotomy wave

This commit is contained in:
Nick committed 2026-10-13 05:49:05 +01:00
1 parent 36db392a94
commit b278437a77
8 files changed
+2170 -17

No files matched your search

+568
View File
@@ -0,0 +1,568 @@
#!/usr/bin/env python3
"""gen_state_checkpoint_truth.py — freeze/replay parity truth for
state_checkpoint (dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/state_checkpoint.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/state_checkpoint/src/frozen_sck_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle functions delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_SCK_CORE=python BEFORE any import (a
no-op pre-port, load-bearing post-port — the facade must never delegate
into the kernel while truth is being frozen).
No-clock law: the wall clock is frozen by patching the `datetime` symbol
in state_checkpoint, so every kernel payload carries its own `ts`/`at`
and replay is deterministic regardless of run date.
Boundary law: the FILE IO (sqlite open/count, shutil copy, ledger
load/save, env resolution in _store_db/_ledger_path) stays in the Python
facade — the kernel is a pure function of the loaded ledger doc and
facade-computed counts. The `snapshot_io`, `restore_io`, `diff_io` and
`load_corrupt` truth groups are facade-only (no kernel symbol) and are
skipped by the kernel-level replay pass.
"""
import copy
import json
import os
import shutil
import sqlite3
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "state_checkpoint", "src",
"frozen_sck_truth.json")
REPLAY = "--replay" in sys.argv
os.environ["RUNE_SCK_CORE"] = "python" # poisoned-truth law
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import state_checkpoint as oracle_mod # noqa: E402
if REPLAY:
from rune import sck_loader # noqa: E402 (kernel-level replay side)
# replay law: the facade pass must exercise the LIVE Rust delegation
# path, so the pin is lifted AFTER import (the facade reads the env
# at call time). The kernel pass calls the loader directly.
del os.environ["RUNE_SCK_CORE"]
FROZEN_TS = 1791806400 # 2026-10-12T12:00:00Z
FROZEN_TS_STR = "20261012T120000"
FROZEN_AT = "2026-10-12T12:00:00+00:00"
class _FrozenDT:
@staticmethod
def now(tz=None):
return datetime.fromtimestamp(FROZEN_TS, tz=timezone.utc)
oracle_mod.datetime = _FrozenDT
_DIR_SEQ = [0]
def fresh_dir():
"""Deterministic per-case dir — the frozen truth embeds absolute
paths (snapshot records carry them), so replay must see the SAME
paths the freeze saw."""
_DIR_SEQ[0] += 1
d = os.path.join(tempfile.gettempdir(), f"sck_truth_frozen_{_DIR_SEQ[0]:03d}")
shutil.rmtree(d, ignore_errors=True)
os.makedirs(d)
return d
def make_db(path, rows):
conn = sqlite3.connect(path)
conn.execute("CREATE TABLE entries (id INTEGER PRIMARY KEY, status TEXT)")
conn.executemany("INSERT INTO entries VALUES (?,?)",
list(enumerate(rows)))
conn.commit()
conn.close()
def ledger_of(path):
return copy.deepcopy(oracle_mod._load_ledger(path))
def rec_kernel(rec, name, inp, expected, oracle_ret=None):
"""Record a kernel case; inputs deep-copied BEFORE oracle mutation."""
rec(name, copy.deepcopy(inp), oracle_ret, expected)
def rec_facade(rec, name, inp, expected, oracle_ret=None):
rec(name, inp, oracle_ret, expected)
# ── snapshot (kernel: record shaping + ledger append) ────────────────────────
def _snapshot_case(rec, name, label, rows, prior=None, db_text=False):
d = fresh_dir()
db = os.path.join(d, "store.db")
if db_text:
Path(db).write_text("not a database\n")
else:
make_db(db, rows)
ledger = os.path.join(d, "checkpoints.json")
if prior is not None:
Path(ledger).write_text(json.dumps({"snapshots": prior}, indent=1))
data_before = ledger_of(ledger)
ret = oracle_mod.snapshot(label, store_db=db, ledger_path=ledger)
inp = {"ledger_doc": data_before, "ts": FROZEN_TS_STR, "at": FROZEN_AT,
"label": label, "entries": ret["entries"], "path": ret["path"]}
rec_kernel(rec, name, inp,
{"record": ret, "ledger": ledger_of(ledger)}, oracle_ret=ret)
def c_snapshot_first(rec):
_snapshot_case(rec, "snapshot-first-empty-ledger", "nightly",
["active", "active", "retired"])
def c_snapshot_second(rec):
prior = [{"id": "20261012T110000-a", "label": "a",
"path": "/tmp/snapshot-20261012T110000-a.db", "entries": 1,
"at": "2026-10-12T11:00:00+00:00"}]
_snapshot_case(rec, "snapshot-second-appends", "nightly",
["active", "active"], prior=prior)
def c_snapshot_entries_unknown(rec):
_snapshot_case(rec, "snapshot-entries-minus-one-not-a-db", "weird",
[], db_text=True)
def c_snapshot_label_chars(rec):
_snapshot_case(rec, "snapshot-label-dashes-and-dots", "pre-restore.v2",
["active"])
# ── list_snapshots (kernel: newest-first stable sort) ────────────────────────
def _list_case(rec, name, snaps):
d = fresh_dir()
ledger = os.path.join(d, "checkpoints.json")
if snaps is not None:
Path(ledger).write_text(json.dumps({"snapshots": snaps}, indent=1))
out = oracle_mod.list_snapshots(ledger_path=ledger)
inp = {"snapshots": copy.deepcopy(snaps or [])}
rec_kernel(rec, name, inp, {"snapshots": out}, oracle_ret=out)
def c_list_empty(rec):
_list_case(rec, "list-missing-ledger-empty", None)
def c_list_newest_first(rec):
_list_case(rec, "list-newest-first", [
{"id": "s1", "at": "2026-10-12T10:00:00+00:00"},
{"id": "s2", "at": "2026-10-12T12:00:00+00:00"},
{"id": "s3", "at": "2026-10-12T11:00:00+00:00"},
])
def c_list_tie_stable(rec):
_list_case(rec, "list-tie-preserves-original-order", [
{"id": "first", "at": "2026-10-12T12:00:00+00:00"},
{"id": "second", "at": "2026-10-12T12:00:00+00:00"},
{"id": "third", "at": "2026-10-12T13:00:00+00:00"},
])
def c_list_missing_at(rec):
_list_case(rec, "list-missing-at-sorts-last", [
{"id": "no-at"},
{"id": "dated", "at": "2026-10-12T12:00:00+00:00"},
])
# ── restore decision (kernel) ────────────────────────────────────────────────
def _restore_case(rec, name, snapshot_id, dry_run, snaps, make_paths_real=False):
d = fresh_dir()
db = os.path.join(d, "store.db")
make_db(db, ["active"])
if make_paths_real:
for s in snaps:
make_db(s["path"], ["active", "active"])
ledger = os.path.join(d, "checkpoints.json")
Path(ledger).write_text(json.dumps({"snapshots": snaps}, indent=1))
decision = oracle_mod.restore(snapshot_id, ledger_path=ledger,
store_db=db, dry_run=dry_run)
inp = {"snapshots": copy.deepcopy(snaps), "snapshot_id": snapshot_id,
"dry_run": dry_run}
if "snapshot" in decision:
# proceed: the oracle's return carries facade IO results (safety
# snapshot id); the KERNEL surface is the decision only
expected = {"record": decision["snapshot"]}
else:
# error doc / dry-run doc ARE the kernel decision verbatim
expected = decision
rec_kernel(rec, name, inp, expected, oracle_ret=decision)
def c_restore_unknown(rec):
_restore_case(rec, "restore-unknown-error-doc", "nope", False,
[{"id": "s1", "path": "/x.db"}])
def c_restore_dry_run(rec):
snap = {"id": "s1", "label": "a", "path": "/tmp/s1.db", "entries": 2,
"at": "2026-10-12T12:00:00+00:00"}
_restore_case(rec, "restore-dry-run-would-restore", "s1", True, [snap])
def c_restore_found(rec):
d = fresh_dir()
snap = {"id": "s1", "label": "a", "path": os.path.join(d, "s1.db"),
"entries": 2, "at": "2026-10-12T12:00:00+00:00"}
_restore_case(rec, "restore-found-proceed-decision", "s1", False, [snap],
make_paths_real=True)
def c_restore_first_match(rec):
# restore uses the FIRST match (match[0]) — unlike diff's last-wins
snaps = [{"id": "s1", "path": "/first.db"},
{"id": "s1", "path": "/second.db"}]
_restore_case(rec, "restore-first-match-wins", "s1", True, snaps)
# ── restore_commit (kernel: final doc shaping) ───────────────────────────────
def _commit_case(rec, name):
d = fresh_dir()
db = os.path.join(d, "store.db")
make_db(db, ["active", "active"])
ledger = os.path.join(d, "checkpoints.json")
first = oracle_mod.snapshot("target", store_db=db, ledger_path=ledger)
ret = oracle_mod.restore(first["id"], ledger_path=ledger, store_db=db)
# the safety snapshot is the SECOND record in the ledger
snaps = oracle_mod._load_ledger(ledger)["snapshots"]
rec_kernel(rec, name, {"record": first, "safety_id": ret["safety_snapshot"]},
{"restored": True, "snapshot": first,
"safety_snapshot": ret["safety_snapshot"]},
oracle_ret=ret)
def c_commit_shape(rec):
_commit_case(rec, "restore-commit-final-doc")
# ── diff lookup (kernel: last-wins id map) ───────────────────────────────────
def _lookup_case(rec, name, snaps, before_id, after_id, dbs=None):
d = fresh_dir()
ledger = os.path.join(d, "checkpoints.json")
snaps = [dict(s, path=os.path.join(d, s["path"])) for s in snaps]
# the oracle diff OPENS every referenced snapshot db — back each path
for s in snaps:
make_db(s["path"], (dbs or {}).get(os.path.basename(s["path"]),
["active"]))
Path(ledger).write_text(json.dumps({"snapshots": snaps}, indent=1))
out = oracle_mod.diff(before_id, after_id, ledger_path=ledger)
inp = {"snapshots": copy.deepcopy(snaps), "before_id": before_id,
"after_id": after_id}
if "error" in out:
rec_kernel(rec, name, inp, {"error": out["error"]}, oracle_ret=out)
else:
# per-side sqlite counts are facade IO; the lookup doc is the
# kernel surface (by_id is built dict-comprehension style:
# LAST duplicate wins)
by_id = {}
for s in snaps:
by_id[s["id"]] = s
rec_kernel(rec, name, inp,
{"before_path": by_id[before_id]["path"],
"after_path": by_id[after_id]["path"]}, oracle_ret=out)
def c_lookup_both_known(rec):
_lookup_case(rec, "lookup-both-known", [
{"id": "a", "path": "a.db"}, {"id": "b", "path": "b.db"}], "a", "b")
def c_lookup_before_unknown(rec):
_lookup_case(rec, "lookup-before-unknown", [
{"id": "b", "path": "b.db"}], "ghost", "b")
def c_lookup_after_unknown(rec):
_lookup_case(rec, "lookup-after-unknown", [
{"id": "a", "path": "a.db"}], "a", "ghost")
def c_lookup_dup_last_wins(rec):
# diff's by_id map is built dict-comprehension style: LAST wins —
# real dbs with distinct counts make the choice observable
_lookup_case(rec, "lookup-duplicate-id-last-wins", [
{"id": "a", "path": "first.db"},
{"id": "a", "path": "second.db"},
{"id": "b", "path": "b.db"}], "a", "b",
dbs={"first.db": ["active"],
"second.db": ["active"] * 5,
"b.db": ["active"] * 2})
# ── diff shape (kernel: delta + per-side error tolerance) ────────────────────
def _shape_case(rec, name, before, after):
d = fresh_dir()
ledger = os.path.join(d, "checkpoints.json")
snaps = []
for sid, side, dbrows in (("a", "before", before), ("b", "after", after)):
db = os.path.join(d, f"{sid}.db")
if dbrows is None:
Path(db).write_text("not a database\n")
else:
make_db(db, dbrows)
snaps.append({"id": sid, "path": db})
Path(ledger).write_text(json.dumps({"snapshots": snaps}, indent=1))
out = oracle_mod.diff("a", "b", ledger_path=ledger)
inp = {"before": out["before"], "after": out["after"]}
rec_kernel(rec, name, inp, out, oracle_ret=out)
def c_shape_both_ok(rec):
_shape_case(rec, "shape-both-ok-delta",
["active", "active", "retired"],
["active", "active", "active", "retired"])
def c_shape_before_error(rec):
_shape_case(rec, "shape-before-error-side", None,
["active", "active"])
def c_shape_after_error(rec):
_shape_case(rec, "shape-after-error-side",
["active", "active"], None)
def c_shape_both_error(rec):
_shape_case(rec, "shape-both-error-sides", None, None)
def c_shape_zero_delta(rec):
_shape_case(rec, "shape-zero-delta",
["active", "retired"], ["active", "retired"])
# ── facade-only: FILE IO (sqlite, copy, ledger save, env resolution) ────────
def c_snapshot_io(rec):
d = fresh_dir()
db = os.path.join(d, "store.db")
make_db(db, ["active"] * 3)
ret = oracle_mod.snapshot("io", store_db=db,
ledger_path=os.path.join(d, "checkpoints.json"))
rec_facade(rec, "snapshot-io-real-count-and-file", {"label": "io"},
{"entries": ret["entries"], "path_exists":
Path(ret["path"]).is_file(),
"ledger_len": len(oracle_mod._load_ledger(
os.path.join(d, "checkpoints.json"))["snapshots"])},
oracle_ret={"entries": ret["entries"]})
def c_restore_io(rec):
d = fresh_dir()
db = os.path.join(d, "store.db")
make_db(db, ["active", "active"])
ledger = os.path.join(d, "checkpoints.json")
first = oracle_mod.snapshot("good", store_db=db, ledger_path=ledger)
conn = sqlite3.connect(db)
conn.execute("DELETE FROM entries")
conn.commit()
conn.close()
ret = oracle_mod.restore(first["id"], ledger_path=ledger, store_db=db)
conn = sqlite3.connect(db)
count = conn.execute("SELECT COUNT(*) FROM entries").fetchone()[0]
conn.close()
journal = os.path.join(d, "events.jsonl")
Path(journal).write_text("event\n")
before = Path(journal).read_text()
oracle_mod.restore(first["id"], ledger_path=ledger, store_db=db)
rec_facade(rec, "restore-io-db-repaired-journal-untouched",
{"snapshot_id": first["id"]},
{"restored": ret["restored"], "count_after": count,
"journal_untouched": Path(journal).read_text() == before,
"safety_in_ledger": ret["safety_snapshot"] in
{s["id"] for s in oracle_mod._load_ledger(ledger)["snapshots"]}},
oracle_ret=None)
def c_diff_io(rec):
d = fresh_dir()
db = os.path.join(d, "store.db")
make_db(db, ["active"])
ledger = os.path.join(d, "checkpoints.json")
a = oracle_mod.snapshot("a", store_db=db, ledger_path=ledger)
conn = sqlite3.connect(db)
conn.execute("INSERT INTO entries VALUES (99, 'active')")
conn.commit()
conn.close()
b = oracle_mod.snapshot("b", store_db=db, ledger_path=ledger)
out = oracle_mod.diff(a["id"], b["id"], ledger_path=ledger)
rec_facade(rec, "diff-io-real-sqlite-counts", {},
{"before": out["before"], "after": out["after"],
"delta_entries": out["delta_entries"]}, oracle_ret=None)
def c_load_corrupt(rec):
d = fresh_dir()
ledger = os.path.join(d, "checkpoints.json")
Path(ledger).write_text("{not json")
rec_facade(rec, "load-corrupt-ledger-reads-empty", {},
{"list": oracle_mod.list_snapshots(ledger_path=ledger),
"db": oracle_mod._load_ledger(ledger)}, oracle_ret=None)
# ── driver ───────────────────────────────────────────────────────────────────
SNAPSHOT = [c_snapshot_first, c_snapshot_second, c_snapshot_entries_unknown,
c_snapshot_label_chars]
LIST = [c_list_empty, c_list_newest_first, c_list_tie_stable,
c_list_missing_at]
RESTORE = [c_restore_unknown, c_restore_dry_run, c_restore_found,
c_restore_first_match]
COMMIT = [c_commit_shape]
LOOKUP = [c_lookup_both_known, c_lookup_before_unknown, c_lookup_after_unknown,
c_lookup_dup_last_wins]
SHAPE = [c_shape_both_ok, c_shape_before_error, c_shape_after_error,
c_shape_both_error, c_shape_zero_delta]
FACADE_IO = [c_snapshot_io, c_restore_io, c_diff_io, c_load_corrupt]
GROUPS = [("snapshot", SNAPSHOT), ("list", LIST), ("restore", RESTORE),
("restore_commit", COMMIT), ("diff_lookup", LOOKUP),
("diff_shape", SHAPE), ("facade_io", FACADE_IO)]
# kernel symbol per group (None → facade-only group, skipped kernel-side)
KERNEL = {
"snapshot": "sck_snapshot",
"list": "sck_list",
"restore": "sck_restore",
"restore_commit": "sck_restore_commit",
"diff_lookup": "sck_diff_lookup",
"diff_shape": "sck_diff_shape",
"facade_io": None,
}
# loader wrapper + slice of `expected` the unwrapped FFI value maps to
CALLS = {
"snapshot": (lambda w: w,
lambda i, ld: ld.snapshot(ledger_doc=i["ledger_doc"], ts=i["ts"],
at=i["at"], label=i["label"],
entries=i["entries"],
snap_path=i["path"])),
"list": (lambda w: w["snapshots"],
lambda i, ld: ld.list(snapshots=i["snapshots"])),
"restore": (lambda w: w,
lambda i, ld: ld.restore(snapshots=i["snapshots"],
snapshot_id=i["snapshot_id"],
dry_run=i["dry_run"])),
"restore_commit": (lambda w: w,
lambda i, ld: ld.restore_commit(record=i["record"],
safety_id=i["safety_id"])),
"diff_lookup": (lambda w: ({"error": w["error"]} if "error" in w else
{"before_path": w["before_path"],
"after_path": w["after_path"]}),
lambda i, ld: ld.diff_lookup(snapshots=i["snapshots"],
before_id=i["before_id"],
after_id=i["after_id"])),
"diff_shape": (lambda w: w,
lambda i, ld: ld.diff_shape(before=i["before"],
after=i["after"])),
}
def canon(x) -> str:
"""Content canonicalization: a sorted dump makes the comparison
key-order independent (int keys never occur here)."""
return json.dumps(json.loads(json.dumps(x)), sort_keys=True)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, cases in GROUPS:
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs):
entry = {"name": name, "input": inp, "return": oracle_ret,
"expected": expected}
if REPLAY:
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_failures = sum(1 for v in groups.values() for c in v if "diverged" in c)
failures = 0
checked = 0
for group, cases in frozen.items():
spec = CALLS.get(group)
if spec is None:
continue
unwrap, call = spec
for case in cases:
checked += 1
got = call(copy.deepcopy(case["input"]), sck_loader)
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 {total - facade_failures}/{total}, "
f"kernel {checked - failures}/{checked}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": FROZEN_AT,
"oracle": "src/rune/state_checkpoint.py",
"pin": "RUNE_SCK_CORE=python",
"note": "Frozen via scripts/gen_state_checkpoint_truth.py against the live "
"Python oracle; wall clock frozen at " + FROZEN_AT +
" (_FrozenDT patched into state_checkpoint.datetime; epoch " +
str(FROZEN_TS) + "). Battery embeds this VERBATIM. The "
"`facade_io` group is facade-only (sqlite/copy/ledger IO stays "
"in Python).",
"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())
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""ctypes loader for the FULL Rust state_checkpoint core (dichotomy wave,
#2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* sck_snapshot({ledger_doc, ts, at, label, entries, snap_path})
-> {record, ledger}
* sck_list({snapshots}) -> {snapshots}
* sck_restore({snapshots, snapshot_id, dry_run}) -> decision doc
* sck_restore_commit({record, safety_id}) -> final doc
* sck_diff_lookup({snapshots, before_id, after_id})
-> {error} | {before_path, after_path}
* sck_diff_shape({before, after}) -> {before, after, delta_entries}
No-clock law: `ts` (%Y%m%dT%H%M%S) and `at` (ISO-8601) are always
arguments — the kernel never reads the wall clock.
Boundary: the FILE IO (sqlite open/count, shutil copy2, ledger
load/save, env resolution in _store_db/_ledger_path) stays in the Python
facade (state_checkpoint.py); the kernel is a pure function of the
loaded ledger doc and facade-computed counts.
Exception law: the kernel's `err` string carries the oracle's Python
exception kind ("KeyError: <key>") and this loader re-raises the SAME
exception type the oracle raised.
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char* — the default c_int truncates 64-bit pointers).
Strings are freed via capability_scoring's cs_free_string — the kernel
exports NO free symbol of its own (duplicate exports break the cdylib
link when sibling crates share one process).
Override the .so with SCK_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "state_checkpoint"
_LIB_PATH = Path(
os.environ.get(
"SCK_RUST_LIB",
_CRATE_DIR / "target" / "release" / "libstate_checkpoint.so",
)
)
_SYMBOLS = (
"sck_snapshot",
"sck_list",
"sck_restore",
"sck_restore_commit",
"sck_diff_lookup",
"sck_diff_shape",
)
_lib = None
class StateCheckpointCoreUnavailable(FileNotFoundError):
"""The Rust state_checkpoint core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise StateCheckpointCoreUnavailable(
f"Rust state_checkpoint core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in _SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
# free via any sibling free symbol already in-process (capability_scoring)
try:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
scoring.cs_free_string(ptr)
except OSError:
pass # leak-on-fallback only if capability_scoring absent; bounded
doc = json.loads(out)
if not doc.get("ok"):
err = doc.get("err", f"{sym} failed")
if err.startswith("ValueError: "):
raise ValueError(err[len("ValueError: "):])
if err.startswith("KeyError: "):
raise KeyError(err[len("KeyError: "):])
raise RuntimeError(err)
return doc["value"]
def snapshot(ledger_doc: dict, *, ts: str, at: str, label: str,
entries: int, snap_path: str) -> dict:
return _call("sck_snapshot", {"ledger_doc": ledger_doc, "ts": ts,
"at": at, "label": label,
"entries": entries,
"snap_path": snap_path})
def list(snapshots: list) -> list:
return _call("sck_list", {"snapshots": snapshots})["snapshots"]
def restore(snapshots: list, *, snapshot_id: str, dry_run: bool) -> dict:
return _call("sck_restore", {"snapshots": snapshots,
"snapshot_id": snapshot_id,
"dry_run": dry_run})
def restore_commit(record: dict, *, safety_id: str) -> dict:
return _call("sck_restore_commit", {"record": record,
"safety_id": safety_id})
def diff_lookup(snapshots: list, *, before_id: str, after_id: str) -> dict:
return _call("sck_diff_lookup", {"snapshots": snapshots,
"before_id": before_id,
"after_id": after_id})
def diff_shape(before: dict, after: dict) -> dict:
return _call("sck_diff_shape", {"before": before, "after": after})
+76 -17
View File
@@ -12,6 +12,15 @@ be restored WITHOUT deleting raw historical evidence:
the causal event history (journal) is NEVER touched — restore changes
state, not evidence
- diff(before_id, after_id): entry-count + status-shape comparison
Dichotomy wave #2479: the pure decision surface (snapshot record
shaping + ledger append, newest-first listing, restore decision,
restore final doc, diff lookup/shape) lives in the Rust kernel
(src/rust/state_checkpoint) behind doc-envelope FFI; the FILE IO
(sqlite open/count, shutil copy2, ledger load/save, env resolution)
stays here — the kernel is a pure function of the loaded ledger doc
and facade-computed counts. RUNE_SCK_CORE=python pins the Python
fallback (parity harnesses).
"""
from __future__ import annotations
@@ -23,6 +32,22 @@ import sqlite3
from datetime import datetime, timezone
def _sck_core():
"""Dichotomy wave #2479: Rust core first (RUNE_SCK_CORE=python pins
the Python fallback — parity harnesses). The kernel owns the
decision surface; the FILE IO stays in this facade."""
if os.environ.get("RUNE_SCK_CORE", "auto") == "python":
return None
try:
from rune import sck_loader
if sck_loader.available():
return sck_loader
except ImportError:
pass
return None
def _store_db():
try:
import memory_env
@@ -65,11 +90,17 @@ def snapshot(label: str, store_db=None, ledger_path=None) -> dict:
count = -1
finally:
conn.close()
rec = {"id": snap_id, "label": label, "path": snap_path,
"entries": count,
"at": datetime.now(timezone.utc).isoformat()}
data = _load_ledger(ledger)
data["snapshots"].append(rec)
at = datetime.now(timezone.utc).isoformat()
core = _sck_core()
if core is not None:
out = core.snapshot(_load_ledger(ledger), ts=ts, at=at, label=label,
entries=count, snap_path=snap_path)
rec, data = out["record"], out["ledger"]
else:
rec = {"id": snap_id, "label": label, "path": snap_path,
"entries": count, "at": at}
data = _load_ledger(ledger)
data["snapshots"].append(rec)
_save_ledger(ledger, data)
return rec
@@ -78,6 +109,9 @@ def list_snapshots(ledger_path=None, store_db=None) -> list:
"""The ledger, newest first."""
ledger = ledger_path or _ledger_path(store_db)
snaps = _load_ledger(ledger).get("snapshots", [])
core = _sck_core()
if core is not None:
return core.list(snapshots=snaps)
return sorted(snaps, key=lambda s: s.get("at", ""), reverse=True)
@@ -87,16 +121,29 @@ def restore(snapshot_id: str, ledger_path=None, store_db=None,
first (rollback of the rollback). The journal is never touched."""
ledger = ledger_path or _ledger_path(store_db)
data = _load_ledger(ledger)
match = [s for s in data.get("snapshots", []) if s["id"] == snapshot_id]
if not match:
return {"restored": False, "error": f"unknown snapshot: {snapshot_id}"}
rec = match[0]
core = _sck_core()
if core is not None:
decision = core.restore(snapshots=data.get("snapshots", []),
snapshot_id=snapshot_id, dry_run=dry_run)
else:
match = [s for s in data.get("snapshots", [])
if s["id"] == snapshot_id]
if not match:
return {"restored": False,
"error": f"unknown snapshot: {snapshot_id}"}
rec = match[0]
decision = ({"restored": False, "dry_run": True,
"would_restore": rec}
if dry_run else {"record": rec})
if "record" not in decision:
return decision
rec = decision["record"]
db = store_db or _store_db()
if dry_run:
return {"restored": False, "dry_run": True, "would_restore": rec}
# safety: snapshot the live state first
# safety: snapshot the live state first (rollback of the rollback)
safety = snapshot("pre-restore", store_db=db, ledger_path=ledger)
shutil.copy2(rec["path"], db)
if core is not None:
return core.restore_commit(record=rec, safety_id=safety["id"])
return {"restored": True, "snapshot": rec, "safety_snapshot": safety["id"]}
@@ -104,12 +151,22 @@ def diff(before_id: str, after_id: str, ledger_path=None) -> dict:
"""Entry-count + status-shape comparison between two snapshots."""
ledger = ledger_path or _ledger_path()
data = _load_ledger(ledger)
by_id = {s["id"]: s for s in data.get("snapshots", [])}
if before_id not in by_id or after_id not in by_id:
return {"error": "unknown snapshot id"}
core = _sck_core()
if core is not None:
lookup = core.diff_lookup(snapshots=data.get("snapshots", []),
before_id=before_id, after_id=after_id)
if "error" in lookup:
return lookup
else:
by_id = {s["id"]: s for s in data.get("snapshots", [])}
if before_id not in by_id or after_id not in by_id:
return {"error": "unknown snapshot id"}
lookup = {"before_path": by_id[before_id]["path"],
"after_path": by_id[after_id]["path"]}
out = {}
for label, sid in (("before", before_id), ("after", after_id)):
conn = sqlite3.connect(by_id[sid]["path"])
for label, path in (("before", lookup["before_path"]),
("after", lookup["after_path"])):
conn = sqlite3.connect(path)
try:
out[label] = {
"entries": conn.execute("SELECT COUNT(*) FROM entries").fetchone()[0],
@@ -119,5 +176,7 @@ def diff(before_id: str, after_id: str, ledger_path=None) -> dict:
out[label] = {"error": str(e)}
finally:
conn.close()
if core is not None:
return core.diff_shape(before=out["before"], after=out["after"])
out["delta_entries"] = out["after"].get("entries", 0) - out["before"].get("entries", 0)
return out
+135
View File
@@ -0,0 +1,135 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"foldhash",
"indexmap",
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "state_checkpoint"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "syn"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "state_checkpoint"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
# preserve_order: Python-dict insertion-order semantics for the snapshot
# record ({id, label, path, entries, at}) and the pass-through ledger
# doc; float_roundtrip: correctly-rounded float parsing (entry counts
# ride along verbatim).
serde_json = { version = "1", features = ["preserve_order", "float_roundtrip"] }
@@ -0,0 +1,101 @@
//! state_checkpoint branch battery — parity vs the frozen Python-oracle
//! truth (frozen_sck_truth.json: 26 cases across every public function
//! and every branch — snapshot record shaping (fresh ledger, append,
//! entries −1 not-a-db, dashed/dotted label), newest-first listing
//! (empty, out-of-order, TIE stability, missing-at sorts last), restore
//! decision (unknown error doc, dry-run would_restore, proceed record,
//! FIRST-match-wins), restore_commit final-doc shaping, diff lookup
//! (both known, either side unknown, duplicate-id LAST-wins with real
//! distinct dbs), diff shape (both ok delta, per-side error tolerance,
//! zero delta)).
//! Frozen 2026-10-12 via scripts/gen_state_checkpoint_truth.py with
//! RUNE_SCK_CORE=python pinned (poisoned-truth law) and the wall clock
//! frozen at 2026-10-12T12:00:00+00:00 (epoch 1791806400). Embedded
//! VERBATIM — never transcribed. The `facade_io` group is facade-only
//! (sqlite/copy/ledger IO stays in Python) and is not replayed here.
use crate::{diff_lookup, diff_shape, list, restore, restore_commit, snapshot};
use serde_json::{json, Map, Value};
const TRUTH: &str = include_str!("frozen_sck_truth.json");
fn cases(group: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][group].as_array().unwrap().clone()
}
fn ledger_of(i: &Value) -> Map<String, Value> {
i["ledger_doc"].as_object().cloned().unwrap_or_default()
}
fn snaps_of(i: &Value) -> Vec<Value> {
i["snapshots"].as_array().cloned().unwrap_or_default()
}
fn replay(group: &str, f: impl Fn(&Value) -> Value) {
for case in cases(group) {
let got = f(&case["input"]);
assert_eq!(
&got,
&case["expected"],
"[{}] case '{}' diverged",
group,
case["name"].as_str().unwrap()
);
}
}
#[test]
fn snapshot_parity_battery() {
replay("snapshot", |i| {
let (record, ledger) = snapshot(
&ledger_of(i),
i["ts"].as_str().unwrap(),
i["at"].as_str().unwrap(),
i["label"].as_str().unwrap(),
i["entries"].as_i64().unwrap(),
i["path"].as_str().unwrap(),
)
.unwrap();
json!({"record": record, "ledger": ledger})
});
}
#[test]
fn list_parity_battery() {
replay("list", |i| json!({"snapshots": list(&snaps_of(i))}));
}
#[test]
fn restore_parity_battery() {
replay("restore", |i| {
restore(
&snaps_of(i),
i["snapshot_id"].as_str().unwrap(),
i["dry_run"].as_bool().unwrap(),
)
});
}
#[test]
fn restore_commit_parity_battery() {
replay("restore_commit", |i| {
restore_commit(&i["record"], i["safety_id"].as_str().unwrap())
});
}
#[test]
fn diff_lookup_parity_battery() {
replay("diff_lookup", |i| {
diff_lookup(
&snaps_of(i),
i["before_id"].as_str().unwrap(),
i["after_id"].as_str().unwrap(),
)
});
}
#[test]
fn diff_shape_parity_battery() {
replay("diff_shape", |i| diff_shape(&i["before"], &i["after"]));
}
@@ -0,0 +1,825 @@
{
"frozen": "2026-10-12T12:00:00+00:00",
"oracle": "src/rune/state_checkpoint.py",
"pin": "RUNE_SCK_CORE=python",
"note": "Frozen via scripts/gen_state_checkpoint_truth.py against the live Python oracle; wall clock frozen at 2026-10-12T12:00:00+00:00 (_FrozenDT patched into state_checkpoint.datetime; epoch 1791806400). Battery embeds this VERBATIM. The `facade_io` group is facade-only (sqlite/copy/ledger IO stays in Python).",
"cases": {
"snapshot": [
{
"name": "snapshot-first-empty-ledger",
"input": {
"ledger_doc": {
"snapshots": []
},
"ts": "20261012T120000",
"at": "2026-10-12T12:00:00+00:00",
"label": "nightly",
"entries": 3,
"path": "/tmp/sck_truth_frozen_001/snapshot-20261012T120000-nightly.db"
},
"return": {
"id": "20261012T120000-nightly",
"label": "nightly",
"path": "/tmp/sck_truth_frozen_001/snapshot-20261012T120000-nightly.db",
"entries": 3,
"at": "2026-10-12T12:00:00+00:00"
},
"expected": {
"record": {
"id": "20261012T120000-nightly",
"label": "nightly",
"path": "/tmp/sck_truth_frozen_001/snapshot-20261012T120000-nightly.db",
"entries": 3,
"at": "2026-10-12T12:00:00+00:00"
},
"ledger": {
"snapshots": [
{
"id": "20261012T120000-nightly",
"label": "nightly",
"path": "/tmp/sck_truth_frozen_001/snapshot-20261012T120000-nightly.db",
"entries": 3,
"at": "2026-10-12T12:00:00+00:00"
}
]
}
}
},
{
"name": "snapshot-second-appends",
"input": {
"ledger_doc": {
"snapshots": [
{
"id": "20261012T110000-a",
"label": "a",
"path": "/tmp/snapshot-20261012T110000-a.db",
"entries": 1,
"at": "2026-10-12T11:00:00+00:00"
}
]
},
"ts": "20261012T120000",
"at": "2026-10-12T12:00:00+00:00",
"label": "nightly",
"entries": 2,
"path": "/tmp/sck_truth_frozen_002/snapshot-20261012T120000-nightly.db"
},
"return": {
"id": "20261012T120000-nightly",
"label": "nightly",
"path": "/tmp/sck_truth_frozen_002/snapshot-20261012T120000-nightly.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
},
"expected": {
"record": {
"id": "20261012T120000-nightly",
"label": "nightly",
"path": "/tmp/sck_truth_frozen_002/snapshot-20261012T120000-nightly.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
},
"ledger": {
"snapshots": [
{
"id": "20261012T110000-a",
"label": "a",
"path": "/tmp/snapshot-20261012T110000-a.db",
"entries": 1,
"at": "2026-10-12T11:00:00+00:00"
},
{
"id": "20261012T120000-nightly",
"label": "nightly",
"path": "/tmp/sck_truth_frozen_002/snapshot-20261012T120000-nightly.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
}
]
}
}
},
{
"name": "snapshot-entries-minus-one-not-a-db",
"input": {
"ledger_doc": {
"snapshots": []
},
"ts": "20261012T120000",
"at": "2026-10-12T12:00:00+00:00",
"label": "weird",
"entries": -1,
"path": "/tmp/sck_truth_frozen_003/snapshot-20261012T120000-weird.db"
},
"return": {
"id": "20261012T120000-weird",
"label": "weird",
"path": "/tmp/sck_truth_frozen_003/snapshot-20261012T120000-weird.db",
"entries": -1,
"at": "2026-10-12T12:00:00+00:00"
},
"expected": {
"record": {
"id": "20261012T120000-weird",
"label": "weird",
"path": "/tmp/sck_truth_frozen_003/snapshot-20261012T120000-weird.db",
"entries": -1,
"at": "2026-10-12T12:00:00+00:00"
},
"ledger": {
"snapshots": [
{
"id": "20261012T120000-weird",
"label": "weird",
"path": "/tmp/sck_truth_frozen_003/snapshot-20261012T120000-weird.db",
"entries": -1,
"at": "2026-10-12T12:00:00+00:00"
}
]
}
}
},
{
"name": "snapshot-label-dashes-and-dots",
"input": {
"ledger_doc": {
"snapshots": []
},
"ts": "20261012T120000",
"at": "2026-10-12T12:00:00+00:00",
"label": "pre-restore.v2",
"entries": 1,
"path": "/tmp/sck_truth_frozen_004/snapshot-20261012T120000-pre-restore.v2.db"
},
"return": {
"id": "20261012T120000-pre-restore.v2",
"label": "pre-restore.v2",
"path": "/tmp/sck_truth_frozen_004/snapshot-20261012T120000-pre-restore.v2.db",
"entries": 1,
"at": "2026-10-12T12:00:00+00:00"
},
"expected": {
"record": {
"id": "20261012T120000-pre-restore.v2",
"label": "pre-restore.v2",
"path": "/tmp/sck_truth_frozen_004/snapshot-20261012T120000-pre-restore.v2.db",
"entries": 1,
"at": "2026-10-12T12:00:00+00:00"
},
"ledger": {
"snapshots": [
{
"id": "20261012T120000-pre-restore.v2",
"label": "pre-restore.v2",
"path": "/tmp/sck_truth_frozen_004/snapshot-20261012T120000-pre-restore.v2.db",
"entries": 1,
"at": "2026-10-12T12:00:00+00:00"
}
]
}
}
}
],
"list": [
{
"name": "list-missing-ledger-empty",
"input": {
"snapshots": []
},
"return": [],
"expected": {
"snapshots": []
}
},
{
"name": "list-newest-first",
"input": {
"snapshots": [
{
"id": "s1",
"at": "2026-10-12T10:00:00+00:00"
},
{
"id": "s2",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "s3",
"at": "2026-10-12T11:00:00+00:00"
}
]
},
"return": [
{
"id": "s2",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "s3",
"at": "2026-10-12T11:00:00+00:00"
},
{
"id": "s1",
"at": "2026-10-12T10:00:00+00:00"
}
],
"expected": {
"snapshots": [
{
"id": "s2",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "s3",
"at": "2026-10-12T11:00:00+00:00"
},
{
"id": "s1",
"at": "2026-10-12T10:00:00+00:00"
}
]
}
},
{
"name": "list-tie-preserves-original-order",
"input": {
"snapshots": [
{
"id": "first",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "second",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "third",
"at": "2026-10-12T13:00:00+00:00"
}
]
},
"return": [
{
"id": "third",
"at": "2026-10-12T13:00:00+00:00"
},
{
"id": "first",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "second",
"at": "2026-10-12T12:00:00+00:00"
}
],
"expected": {
"snapshots": [
{
"id": "third",
"at": "2026-10-12T13:00:00+00:00"
},
{
"id": "first",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "second",
"at": "2026-10-12T12:00:00+00:00"
}
]
}
},
{
"name": "list-missing-at-sorts-last",
"input": {
"snapshots": [
{
"id": "no-at"
},
{
"id": "dated",
"at": "2026-10-12T12:00:00+00:00"
}
]
},
"return": [
{
"id": "dated",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "no-at"
}
],
"expected": {
"snapshots": [
{
"id": "dated",
"at": "2026-10-12T12:00:00+00:00"
},
{
"id": "no-at"
}
]
}
}
],
"restore": [
{
"name": "restore-unknown-error-doc",
"input": {
"snapshots": [
{
"id": "s1",
"path": "/x.db"
}
],
"snapshot_id": "nope",
"dry_run": false
},
"return": {
"restored": false,
"error": "unknown snapshot: nope"
},
"expected": {
"restored": false,
"error": "unknown snapshot: nope"
}
},
{
"name": "restore-dry-run-would-restore",
"input": {
"snapshots": [
{
"id": "s1",
"label": "a",
"path": "/tmp/s1.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
}
],
"snapshot_id": "s1",
"dry_run": true
},
"return": {
"restored": false,
"dry_run": true,
"would_restore": {
"id": "s1",
"label": "a",
"path": "/tmp/s1.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
}
},
"expected": {
"restored": false,
"dry_run": true,
"would_restore": {
"id": "s1",
"label": "a",
"path": "/tmp/s1.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
}
}
},
{
"name": "restore-found-proceed-decision",
"input": {
"snapshots": [
{
"id": "s1",
"label": "a",
"path": "/tmp/sck_truth_frozen_011/s1.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
}
],
"snapshot_id": "s1",
"dry_run": false
},
"return": {
"restored": true,
"snapshot": {
"id": "s1",
"label": "a",
"path": "/tmp/sck_truth_frozen_011/s1.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
},
"safety_snapshot": "20261012T120000-pre-restore"
},
"expected": {
"record": {
"id": "s1",
"label": "a",
"path": "/tmp/sck_truth_frozen_011/s1.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
}
}
},
{
"name": "restore-first-match-wins",
"input": {
"snapshots": [
{
"id": "s1",
"path": "/first.db"
},
{
"id": "s1",
"path": "/second.db"
}
],
"snapshot_id": "s1",
"dry_run": true
},
"return": {
"restored": false,
"dry_run": true,
"would_restore": {
"id": "s1",
"path": "/first.db"
}
},
"expected": {
"restored": false,
"dry_run": true,
"would_restore": {
"id": "s1",
"path": "/first.db"
}
}
}
],
"restore_commit": [
{
"name": "restore-commit-final-doc",
"input": {
"record": {
"id": "20261012T120000-target",
"label": "target",
"path": "/tmp/sck_truth_frozen_014/snapshot-20261012T120000-target.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
},
"safety_id": "20261012T120000-pre-restore"
},
"return": {
"restored": true,
"snapshot": {
"id": "20261012T120000-target",
"label": "target",
"path": "/tmp/sck_truth_frozen_014/snapshot-20261012T120000-target.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
},
"safety_snapshot": "20261012T120000-pre-restore"
},
"expected": {
"restored": true,
"snapshot": {
"id": "20261012T120000-target",
"label": "target",
"path": "/tmp/sck_truth_frozen_014/snapshot-20261012T120000-target.db",
"entries": 2,
"at": "2026-10-12T12:00:00+00:00"
},
"safety_snapshot": "20261012T120000-pre-restore"
}
}
],
"diff_lookup": [
{
"name": "lookup-both-known",
"input": {
"snapshots": [
{
"id": "a",
"path": "/tmp/sck_truth_frozen_015/a.db"
},
{
"id": "b",
"path": "/tmp/sck_truth_frozen_015/b.db"
}
],
"before_id": "a",
"after_id": "b"
},
"return": {
"before": {
"entries": 1,
"active": 1
},
"after": {
"entries": 1,
"active": 1
},
"delta_entries": 0
},
"expected": {
"before_path": "/tmp/sck_truth_frozen_015/a.db",
"after_path": "/tmp/sck_truth_frozen_015/b.db"
}
},
{
"name": "lookup-before-unknown",
"input": {
"snapshots": [
{
"id": "b",
"path": "/tmp/sck_truth_frozen_016/b.db"
}
],
"before_id": "ghost",
"after_id": "b"
},
"return": {
"error": "unknown snapshot id"
},
"expected": {
"error": "unknown snapshot id"
}
},
{
"name": "lookup-after-unknown",
"input": {
"snapshots": [
{
"id": "a",
"path": "/tmp/sck_truth_frozen_017/a.db"
}
],
"before_id": "a",
"after_id": "ghost"
},
"return": {
"error": "unknown snapshot id"
},
"expected": {
"error": "unknown snapshot id"
}
},
{
"name": "lookup-duplicate-id-last-wins",
"input": {
"snapshots": [
{
"id": "a",
"path": "/tmp/sck_truth_frozen_018/first.db"
},
{
"id": "a",
"path": "/tmp/sck_truth_frozen_018/second.db"
},
{
"id": "b",
"path": "/tmp/sck_truth_frozen_018/b.db"
}
],
"before_id": "a",
"after_id": "b"
},
"return": {
"before": {
"entries": 5,
"active": 5
},
"after": {
"entries": 2,
"active": 2
},
"delta_entries": -3
},
"expected": {
"before_path": "/tmp/sck_truth_frozen_018/second.db",
"after_path": "/tmp/sck_truth_frozen_018/b.db"
}
}
],
"diff_shape": [
{
"name": "shape-both-ok-delta",
"input": {
"before": {
"entries": 3,
"active": 2
},
"after": {
"entries": 4,
"active": 3
}
},
"return": {
"before": {
"entries": 3,
"active": 2
},
"after": {
"entries": 4,
"active": 3
},
"delta_entries": 1
},
"expected": {
"before": {
"entries": 3,
"active": 2
},
"after": {
"entries": 4,
"active": 3
},
"delta_entries": 1
}
},
{
"name": "shape-before-error-side",
"input": {
"before": {
"error": "file is not a database"
},
"after": {
"entries": 2,
"active": 2
}
},
"return": {
"before": {
"error": "file is not a database"
},
"after": {
"entries": 2,
"active": 2
},
"delta_entries": 2
},
"expected": {
"before": {
"error": "file is not a database"
},
"after": {
"entries": 2,
"active": 2
},
"delta_entries": 2
}
},
{
"name": "shape-after-error-side",
"input": {
"before": {
"entries": 2,
"active": 2
},
"after": {
"error": "file is not a database"
}
},
"return": {
"before": {
"entries": 2,
"active": 2
},
"after": {
"error": "file is not a database"
},
"delta_entries": -2
},
"expected": {
"before": {
"entries": 2,
"active": 2
},
"after": {
"error": "file is not a database"
},
"delta_entries": -2
}
},
{
"name": "shape-both-error-sides",
"input": {
"before": {
"error": "file is not a database"
},
"after": {
"error": "file is not a database"
}
},
"return": {
"before": {
"error": "file is not a database"
},
"after": {
"error": "file is not a database"
},
"delta_entries": 0
},
"expected": {
"before": {
"error": "file is not a database"
},
"after": {
"error": "file is not a database"
},
"delta_entries": 0
}
},
{
"name": "shape-zero-delta",
"input": {
"before": {
"entries": 2,
"active": 1
},
"after": {
"entries": 2,
"active": 1
}
},
"return": {
"before": {
"entries": 2,
"active": 1
},
"after": {
"entries": 2,
"active": 1
},
"delta_entries": 0
},
"expected": {
"before": {
"entries": 2,
"active": 1
},
"after": {
"entries": 2,
"active": 1
},
"delta_entries": 0
}
}
],
"facade_io": [
{
"name": "snapshot-io-real-count-and-file",
"input": {
"label": "io"
},
"return": {
"entries": 3
},
"expected": {
"entries": 3,
"path_exists": true,
"ledger_len": 1
}
},
{
"name": "restore-io-db-repaired-journal-untouched",
"input": {
"snapshot_id": "20261012T120000-good"
},
"return": null,
"expected": {
"restored": true,
"count_after": 2,
"journal_untouched": true,
"safety_in_ledger": true
}
},
{
"name": "diff-io-real-sqlite-counts",
"input": {},
"return": null,
"expected": {
"before": {
"entries": 1,
"active": 1
},
"after": {
"entries": 2,
"active": 2
},
"delta_entries": 1
}
},
{
"name": "load-corrupt-ledger-reads-empty",
"input": {},
"return": null,
"expected": {
"list": [],
"db": {
"snapshots": []
}
}
}
]
}
}
+304
View File
@@ -0,0 +1,304 @@
//! state_checkpoint — learned-state checkpoint/replay/rollback (#154).
//!
//! FULL parity port of src/rune/state_checkpoint.py (dichotomy wave,
//! #2479 tranche). Source code is recoverable from git; learned state
//! may not be. This kernel snapshots the cognitive store's CHECKPOINT
//! LEDGER so a known-good state can be restored WITHOUT deleting raw
//! historical evidence: snapshot record shaping + ledger append,
//! newest-first listing, restore decision (unknown / dry-run / proceed
//! — the live DB is itself snapshotted first: rollback of the
//! rollback), and diff (entry-count + status-shape comparison). The
//! causal event history (journal) is NEVER touched — restore changes
//! state, not evidence.
//!
//! FFI (doc-envelope law): every symbol takes one JSON doc and returns
//! {"ok": true, "value": ...} | {"ok": false, "err": ...}:
//! * sck_snapshot({ledger_doc, ts, at, label, entries, snap_path})
//! → {record, ledger}
//! * sck_list({snapshots}) → {snapshots} (newest first, stable)
//! * sck_restore({snapshots, snapshot_id, dry_run}) → decision doc:
//! {"restored": false, "error": ...} | {"restored": false,
//! "dry_run": true, "would_restore": rec} | {"record": rec} (the
//! facade executes the IO the decision implies: safety snapshot +
//! copy2 + final doc)
//! * sck_restore_commit({record, safety_id}) → {"restored": true,
//! "snapshot": rec, "safety_snapshot": id}
//! * sck_diff_lookup({snapshots, before_id, after_id}) →
//! {"error": ...} | {before_path, after_path}
//! * sck_diff_shape({before, after}) →
//! {before, after, delta_entries}
//!
//! No-clock law: `ts` (%Y%m%dT%H%M%S) and `at` (ISO-8601) are always
//! arguments; the kernel never reads the wall clock.
//!
//! Boundary: the FILE IO (sqlite open/count, shutil copy2, ledger
//! load/save, env resolution in _store_db/_ledger_path) stays in the
//! Python facade — the kernel is a pure function of the loaded ledger
//! doc and facade-computed counts.
//!
//! Lookup duality (oracle semantics, pinned by the battery): restore
//! matches the FIRST ledger entry with the id (match[0]); diff's by_id
//! map is built dict-comprehension style so the LAST duplicate wins.
//!
//! No sck_free_string export — sibling crates sharing the Python
//! process already export identical free symbols; callers free through
//! capability_scoring's cs_free_string.
use serde_json::{json, Map, Value};
use std::fmt;
use std::os::raw::c_char;
// ── oracle exceptions (typed, behind the doc envelope) ──────────────────────
/// The oracle raises KeyError when a ledger doc lacks the `snapshots`
/// array (data["snapshots"].append). The envelope's `err` string carries
/// the Python kind prefix so the loader re-raises the same type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CheckpointError {
Key(String),
}
impl fmt::Display for CheckpointError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CheckpointError::Key(k) => write!(f, "KeyError: {k}"),
}
}
}
fn snapshots_array(ledger: &Map<String, Value>) -> Result<&Vec<Value>, CheckpointError> {
ledger
.get("snapshots")
.and_then(Value::as_array)
.ok_or_else(|| CheckpointError::Key("snapshots".to_string()))
}
/// `s.get("at", "")` — the listing sort key (missing → sorts last).
fn sort_key(s: &Value) -> &str {
s.get("at").and_then(Value::as_str).unwrap_or("")
}
// ── checkpoint operations (pure functions of the ledger doc) ────────────────
/// `snapshot` — record shaping + ledger append. The DB copy and the
/// sqlite entry count are facade IO; `entries` rides in (−1 marks
/// unknown per the oracle's informational-count law).
pub fn snapshot(
ledger_doc: &Map<String, Value>,
ts: &str,
at: &str,
label: &str,
entries: i64,
snap_path: &str,
) -> Result<(Value, Map<String, Value>), CheckpointError> {
let snap_id = format!("{ts}-{label}");
let rec = json!({
"id": snap_id,
"label": label,
"path": snap_path,
"entries": entries,
"at": at,
});
let mut out = ledger_doc.clone();
let arr = snapshots_array(&out)?; // borrow ends before the mutable insert
let mut arr = arr.clone();
arr.push(rec.clone());
out.insert("snapshots".to_string(), Value::Array(arr));
Ok((rec, out))
}
/// `list_snapshots` — the ledger, newest first. Python's
/// `sorted(..., reverse=True)` is STABLE: equal `at` keys keep their
/// original order, and Rust's stable sort-by-reversed-key matches.
pub fn list(snapshots: &[Value]) -> Vec<Value> {
let mut out = snapshots.to_vec();
out.sort_by(|a, b| sort_key(b).cmp(sort_key(a)));
out
}
/// The restore decision — classified once, executed by the facade.
#[derive(Debug, Clone, PartialEq, Eq)]
enum RestoreOutcome {
Unknown(String),
DryRun(Value),
Proceed(Value),
}
/// `restore` decision core: FIRST ledger match with the id (match[0]).
fn classify_restore(snapshots: &[Value], snapshot_id: &str, dry_run: bool) -> RestoreOutcome {
let rec = snapshots
.iter()
.find(|s| s.get("id").and_then(Value::as_str) == Some(snapshot_id));
match rec {
None => RestoreOutcome::Unknown(format!("unknown snapshot: {snapshot_id}")),
Some(rec) if dry_run => RestoreOutcome::DryRun(rec.clone()),
Some(rec) => RestoreOutcome::Proceed(rec.clone()),
}
}
/// `restore` decision doc (see classify_restore).
pub fn restore(snapshots: &[Value], snapshot_id: &str, dry_run: bool) -> Value {
match classify_restore(snapshots, snapshot_id, dry_run) {
RestoreOutcome::Unknown(err) => json!({"restored": false, "error": err}),
RestoreOutcome::DryRun(rec) => {
json!({"restored": false, "dry_run": true, "would_restore": rec})
}
RestoreOutcome::Proceed(rec) => json!({"record": rec}),
}
}
/// `restore` final doc shaping — the facade supplies the safety
/// snapshot id after its rollback-of-the-rollback IO.
pub fn restore_commit(record: &Value, safety_id: &str) -> Value {
json!({"restored": true, "snapshot": record, "safety_snapshot": safety_id})
}
/// `diff` lookup core: by_id built dict-comprehension style — the LAST
/// duplicate id wins. Either side unknown → the oracle's error doc.
pub fn diff_lookup(snapshots: &[Value], before_id: &str, after_id: &str) -> Value {
let mut by_id: Map<String, Value> = Map::new();
for s in snapshots {
if let Some(id) = s.get("id").and_then(Value::as_str) {
by_id.insert(id.to_string(), s.clone());
}
}
let path_of = |sid: &str| {
by_id
.get(sid)
.and_then(|s| s.get("path"))
.and_then(Value::as_str)
.map(str::to_string)
};
match (path_of(before_id), path_of(after_id)) {
(Some(bp), Some(ap)) => json!({"before_path": bp, "after_path": ap}),
_ => json!({"error": "unknown snapshot id"}),
}
}
/// `diff` shape core: per-side buckets pass through (an error side has
/// no `entries` and contributes the oracle's .get("entries", 0) → 0).
pub fn diff_shape(before: &Value, after: &Value) -> Value {
let entries = |side: &Value| side.get("entries").and_then(Value::as_i64).unwrap_or(0);
json!({
"before": before,
"after": after,
"delta_entries": entries(after) - entries(before),
})
}
// ── FFI (doc-envelope law) ──────────────────────────────────────────────────
enum FfiError {
NullPtr,
Utf8(std::str::Utf8Error),
Json(serde_json::Error),
Checkpoint(CheckpointError),
}
impl fmt::Display for FfiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FfiError::NullPtr => write!(f, "null argument pointer"),
FfiError::Utf8(e) => write!(f, "{e}"),
FfiError::Json(e) => write!(f, "{e}"),
FfiError::Checkpoint(e) => write!(f, "{e}"),
}
}
}
fn read_arg(ptr: *const c_char) -> Result<Value, FfiError> {
if ptr.is_null() {
return Err(FfiError::NullPtr);
}
let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map_err(FfiError::Utf8)?;
serde_json::from_str(s).map_err(FfiError::Json)
}
fn envelope(r: Result<Value, FfiError>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e.to_string()}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn ledger_of(a: &Value) -> Map<String, Value> {
a.get("ledger_doc")
.and_then(Value::as_object)
.cloned()
.unwrap_or_default()
}
fn snaps_of(a: &Value) -> Vec<Value> {
a.get("snapshots")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default()
}
/// sck_snapshot({ledger_doc, ts, at, label, entries, snap_path})
/// → {record, ledger}
#[no_mangle]
pub extern "C" fn sck_snapshot(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let s = |k: &str| a.get(k).and_then(Value::as_str).unwrap_or("");
let entries = a.get("entries").and_then(Value::as_i64).unwrap_or(0);
snapshot(&ledger_of(&a), s("ts"), s("at"), s("label"), entries, s("snap_path"))
.map(|(record, ledger)| json!({"record": record, "ledger": ledger}))
.map_err(FfiError::Checkpoint)
}))
}
/// sck_list({snapshots}) → {snapshots}
#[no_mangle]
pub extern "C" fn sck_list(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| json!({"snapshots": list(&snaps_of(&a))})))
}
/// sck_restore({snapshots, snapshot_id, dry_run}) → decision doc
#[no_mangle]
pub extern "C" fn sck_restore(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let snapshot_id = a.get("snapshot_id").and_then(Value::as_str).unwrap_or("");
let dry_run = a.get("dry_run").and_then(Value::as_bool).unwrap_or(false);
restore(&snaps_of(&a), snapshot_id, dry_run)
}))
}
/// sck_restore_commit({record, safety_id}) → final doc
#[no_mangle]
pub extern "C" fn sck_restore_commit(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let record = a.get("record").cloned().unwrap_or(Value::Null);
let safety_id = a.get("safety_id").and_then(Value::as_str).unwrap_or("");
restore_commit(&record, safety_id)
}))
}
/// sck_diff_lookup({snapshots, before_id, after_id})
/// → {error} | {before_path, after_path}
#[no_mangle]
pub extern "C" fn sck_diff_lookup(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let before_id = a.get("before_id").and_then(Value::as_str).unwrap_or("");
let after_id = a.get("after_id").and_then(Value::as_str).unwrap_or("");
diff_lookup(&snaps_of(&a), before_id, after_id)
}))
}
/// sck_diff_shape({before, after}) → {before, after, delta_entries}
#[no_mangle]
pub extern "C" fn sck_diff_shape(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let before = a.get("before").cloned().unwrap_or(Value::Null);
let after = a.get("after").cloned().unwrap_or(Value::Null);
diff_shape(&before, &after)
}))
}
#[cfg(test)]
mod branch_battery;