FULL parity port of src/rune/capability_ledger.py to the capability_ledger Rust core (RUST verdict, issue #2479 tranche, contract BUILDER-2026-10-11T15:50:51Z-bde08d-factory-wave): - 17 FFI symbols (cl_new/cl_define/cl_get/cl_material_change/cl_move/ cl_supersede/cl_retire/cl_active/cl_by_component/cl_context_packet/ cl_save/cl_classify_version_bump/cl_bump_version/cl_attach_spec/ cl_revise_spec/cl_spec_drift_status/cl_spec_context) via doc-envelope law; the ledger doc travels as a sibling envelope key so "value" stays oracle-verbatim; free via capability_scoring cs_free_string (no re-export); serde_json preserve_order for dict-position parity - stale partial port replaced (old CapabilityEntry struct had no Python counterpart); classify_version_bump/bump_version kept name-compatible - REAL divergences fixed pre-wiring (battery caught all three): MINOR/PATCH bumps must normalize untouched components through int() (leading zeros collapse everywhere — '01.02.03' MINOR = '1.3.0'); bump arithmetic is arbitrary-precision like Python ints (u64/u128 boundary); SEMVER_RE \\d matches Unicode Nd + int() accepts them (full Nd range table, verified against unicodedata) - py_repr() mirrors Python repr() in error envelopes (str(KeyError) is repr'd); note/reason truncation by codepoints (py_slice) - ctypes loader cl_loader.py mirrors broker_loader.py (LEDGER_RUST_LIB override); facade delegation on all 18 public fns with RUNE_LEDGER_CORE =python pin and Python-body fallback (kernel errors fall back so the proper exception types re-raise) - battery: 16 sequences / 155 ops frozen from the live oracle (frozen_cl_truth.json, replayed VERBATIM in-crate) + save() byte-parity pinned (indent=1 sort_keys identical); 500-op live fuzz seed 2479: 500/500 + final-file-bytes equal - FIX-FIRST (pre-existing latent regression surfaced by this batch): spine unseeded path bypassed the monkeypatchable ingest_umbrellas seam once the rune_spine kernel was built — the generated-view refusal fired inside the kernel where isolation tests cannot intercept it (test_spine_seeds_entry_point red on master with kernel built). build() now routes the manifest ingest through the public seam first and hands the kernel a carry-attempted doc; the kernel skips its manifest branch when existing was provided as an object (true cold path keeps the load-bearing refusal). 198 ledger/spine Python tests green.
441 lines
20 KiB
Python
441 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""Freeze capability_ledger oracle truth for the Rust port battery
|
|
(dichotomy factory batch, #2479 tranche).
|
|
|
|
Exercises EVERY public function across all branches: CapabilityLedger
|
|
(new/load, save, define defaults + full + duplicate, get prefixed/plain/
|
|
unknown, material_change revisions + generation + source_locations + note
|
|
truncation, move, supersede (plain + prefixed by, note truncation), retire,
|
|
active, by_component, context_packet known/unknown) plus the module-level
|
|
spec machinery (classify_version_bump full 4-bool matrix, bump_version
|
|
valid/invalid/leading-zero/overflow, attach_spec defaults + errors,
|
|
revise_spec classification + proposed-kind governance + unjustified bump +
|
|
KeyError + covers_material_revision, spec_drift_status all outcomes,
|
|
spec_context with/without spec).
|
|
|
|
The wall clock is FROZEN: every op records the exact _now() value it was
|
|
given, and the kernel receives that same string via the FFI payload — the
|
|
kernel itself never reads a clock.
|
|
|
|
Output: /tmp/cl_truth.json — replayed VERBATIM by
|
|
src/rust/capability_ledger/src/branch_battery.rs.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
|
|
os.environ["RUNE_LEDGER_CORE"] = "python" # freeze the ORACLE, never the kernel
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
|
|
from rune import capability_ledger as cl # noqa: E402
|
|
|
|
# ── deterministic frozen clock ───────────────────────────────────────────
|
|
_T0 = datetime(2026, 1, 1, tzinfo=timezone.utc)
|
|
_tick = [0]
|
|
|
|
|
|
def _next_now() -> str:
|
|
_tick[0] += 1
|
|
return (_T0 + timedelta(seconds=_tick[0] - 1)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
|
|
def _pin_clock(now: str) -> None:
|
|
"""All _now() calls WITHIN one op return the same value (the kernel
|
|
receives exactly one now per op; live wall-clock is same-second)."""
|
|
cl._now = lambda n=now: n # noqa: E731
|
|
|
|
|
|
class Seq:
|
|
"""One replayable op sequence against one ledger document."""
|
|
|
|
def __init__(self, name: str, sequences: list):
|
|
self.name = name
|
|
self.ops: list[dict] = []
|
|
self.expected: list[dict] = []
|
|
self.saved_files: dict[str, str] = {}
|
|
self.ledger: cl.CapabilityLedger | None = None
|
|
self.tmpdir = tempfile.mkdtemp(prefix=f"cltruth-{name}-")
|
|
sequences.append(self)
|
|
|
|
# -- op plumbing -------------------------------------------------------
|
|
|
|
def _call(self, opname: str, **args) -> None:
|
|
op = {"op": opname, **args}
|
|
now = _next_now()
|
|
_pin_clock(now)
|
|
if opname not in ("spec_context", "classify_version_bump",
|
|
"bump_version"):
|
|
op["now"] = now
|
|
self.ops.append(op)
|
|
try:
|
|
out = getattr(self, f"_{opname}")(op)
|
|
# oracle returns LIVE references (rec dicts) — snapshot NOW or
|
|
# later ops mutate the frozen expectation
|
|
out = json.loads(json.dumps(out))
|
|
self.expected.append({"ok": True, "value": out})
|
|
except Exception as e: # ValueError/SpecError/KeyError — message parity
|
|
self.expected.append({"ok": False, "err": str(e)})
|
|
|
|
def _new(self, op) -> dict:
|
|
file_text = op.get("file_text")
|
|
if file_text is not None:
|
|
(Path(self.tmpdir) / "capability_ledger.json").write_text(file_text)
|
|
self.ledger = cl.CapabilityLedger(self.tmpdir)
|
|
return self.ledger.doc
|
|
|
|
def _define(self, op) -> dict:
|
|
kwargs = {k: v for k, v in op.items()
|
|
if k not in ("op", "now")}
|
|
return self.ledger.define(**kwargs)
|
|
|
|
def _get(self, op) -> dict | None:
|
|
return self.ledger.get(op["capability_id"])
|
|
|
|
def _material_change(self, op) -> dict:
|
|
kwargs = {"note": op["note"]}
|
|
if "generation" in op:
|
|
kwargs["generation"] = op["generation"]
|
|
if "source_locations" in op:
|
|
kwargs["source_locations"] = op["source_locations"]
|
|
return self.ledger.material_change(op["capability_id"], **kwargs)
|
|
|
|
def _move(self, op) -> dict:
|
|
return self.ledger.move(op["capability_id"], op["new_locations"])
|
|
|
|
def _supersede(self, op) -> dict:
|
|
return self.ledger.supersede(op["capability_id"], op["by"],
|
|
note=op.get("note", ""))
|
|
|
|
def _retire(self, op) -> dict:
|
|
return self.ledger.retire(op["capability_id"],
|
|
note=op.get("note", ""))
|
|
|
|
def _active(self, op) -> list:
|
|
return self.ledger.active()
|
|
|
|
def _by_component(self, op) -> list:
|
|
return self.ledger.by_component(op["component"])
|
|
|
|
def _context_packet(self, op) -> dict:
|
|
return self.ledger.context_packet(op["capability_id"])
|
|
|
|
def _save(self, op) -> None:
|
|
self.ledger.save()
|
|
self.saved_files["capability_ledger.json"] = (
|
|
Path(self.tmpdir) / "capability_ledger.json").read_text()
|
|
return None
|
|
|
|
def _classify_version_bump(self, op) -> str:
|
|
return cl.classify_version_bump(
|
|
contract_changed=op["contract_changed"],
|
|
behaviour_added=op["behaviour_added"],
|
|
consumer_break=op["consumer_break"],
|
|
implementation_only=op["implementation_only"])
|
|
|
|
def _bump_version(self, op) -> str:
|
|
return cl.bump_version(op["version"], op["kind"])
|
|
|
|
def _attach_spec(self, op) -> dict:
|
|
kwargs = {k: v for k, v in op.items()
|
|
if k not in ("op", "now", "capability_id")}
|
|
return cl.attach_spec(self.ledger, op["capability_id"], **kwargs)
|
|
|
|
def _revise_spec(self, op) -> dict:
|
|
kwargs = {k: v for k, v in op.items()
|
|
if k not in ("op", "now", "capability_id")}
|
|
return cl.revise_spec(self.ledger, op["capability_id"], **kwargs)
|
|
|
|
def _spec_drift_status(self, op) -> dict:
|
|
return cl.spec_drift_status(self.ledger, op["capability_id"])
|
|
|
|
def _spec_context(self, op) -> dict:
|
|
rec = self.ledger.get(op["capability_id"])
|
|
return cl.spec_context(rec)
|
|
|
|
|
|
sequences: list[Seq] = []
|
|
LONG_NOTE = "x" * 260
|
|
LONG_REASON = "r" * 260
|
|
|
|
|
|
def frozen_json(doc: dict) -> str:
|
|
"""A saved ledger file EXACTLY as save() writes it (indent=1, sorted)."""
|
|
return json.dumps(doc, indent=1, sort_keys=True)
|
|
|
|
|
|
# ── S1: fresh ledger — defaults, queries, save ───────────────────────────
|
|
s = Seq("fresh-defaults-queries", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="auth-token", name="Auth Token",
|
|
purpose="issue + verify session tokens", owning_project="rune",
|
|
component="component:auth-core")
|
|
s._call("define", capability_id="cap:prefixed-given", name="Prefixed",
|
|
purpose="id given with prefix", owning_project="rune",
|
|
component="component:auth-core")
|
|
s._call("get", capability_id="auth-token")
|
|
s._call("get", capability_id="capability:auth-token")
|
|
s._call("get", capability_id="nope")
|
|
s._call("active")
|
|
s._call("by_component", component="component:auth-core")
|
|
s._call("by_component", component="component:nowhere")
|
|
s._call("context_packet", capability_id="auth-token")
|
|
s._call("context_packet", capability_id="ghost")
|
|
s._call("save")
|
|
|
|
# ── S2: define with every field provided ─────────────────────────────────
|
|
s = Seq("define-full-fields", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="full-rec", name="Full", purpose="all fields",
|
|
owning_project="rune", component="component:c1",
|
|
source_locations=["src/a.py", "src/b.py"],
|
|
contract="contract text", state_touched="store/scaffold",
|
|
dependencies=["dep-x"], consumers=["consumer-y"],
|
|
invariants=["inv-1"], behaviour_specs=["bs-1"],
|
|
constraints="no raw tokens", generation="gen-7")
|
|
s._call("define", capability_id="empty-lists", name="Empty", purpose="empties",
|
|
owning_project="rune", component="component:c1",
|
|
source_locations=[], dependencies=[], consumers=[],
|
|
invariants=[], behaviour_specs=[])
|
|
s._call("get", capability_id="full-rec")
|
|
s._call("get", capability_id="empty-lists")
|
|
s._call("context_packet", capability_id="full-rec")
|
|
|
|
# ── S3: duplicate define raises ──────────────────────────────────────────
|
|
s = Seq("duplicate-define", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="dup", name="D", purpose="first",
|
|
owning_project="rune", component="component:c")
|
|
s._call("define", capability_id="dup", name="D2", purpose="second",
|
|
owning_project="rune", component="component:c")
|
|
|
|
# ── S4: unknown-capability errors for every mutator/query ───────────────
|
|
s = Seq("unknown-cap-errors", sequences)
|
|
s._call("new")
|
|
s._call("material_change", capability_id="ghost", note="n")
|
|
s._call("move", capability_id="ghost", new_locations=["a.py"])
|
|
s._call("supersede", capability_id="ghost", by="other")
|
|
s._call("retire", capability_id="ghost")
|
|
s._call("attach_spec", capability_id="ghost", requirements=["r"],
|
|
expected_behaviours=["b"], non_goals=["n"], acceptance_criteria=["a"])
|
|
s._call("revise_spec", capability_id="ghost", reason="x")
|
|
s._call("spec_drift_status", capability_id="ghost")
|
|
|
|
# ── S5: supersede / retire / move lifecycle ──────────────────────────────
|
|
s = Seq("supersede-retire-move", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="old-cap", name="Old", purpose="p",
|
|
owning_project="rune", component="component:c",
|
|
source_locations=["src/old.py"])
|
|
s._call("define", capability_id="doomed", name="Doomed", purpose="p",
|
|
owning_project="rune", component="component:c")
|
|
s._call("supersede", capability_id="old-cap", by="new-cap", note="replaced")
|
|
s._call("supersede", capability_id="doomed", by="capability:explicit-prefixed",
|
|
note=LONG_NOTE)
|
|
s._call("retire", capability_id="doomed", note="dead")
|
|
s._call("retire", capability_id="doomed", note=LONG_NOTE) # already retired: still appends history
|
|
s._call("active")
|
|
s._call("move", capability_id="old-cap", new_locations=["src/moved/old.py", "src/moved/old2.py"])
|
|
s._call("context_packet", capability_id="old-cap") # packet still served for superseded
|
|
s._call("by_component", component="component:c")
|
|
|
|
# ── S6: material_change revision counter + branches ─────────────────────
|
|
s = Seq("material-change-revisions", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="rev-cap", name="Rev", purpose="p",
|
|
owning_project="rune", component="component:c",
|
|
source_locations=["src/a.py"], constraints="c0")
|
|
s._call("material_change", capability_id="rev-cap", note="first change")
|
|
s._call("material_change", capability_id="rev-cap", note=LONG_NOTE,
|
|
generation="gen-9")
|
|
s._call("material_change", capability_id="rev-cap", note="third",
|
|
generation="", source_locations=["src/b.py"])
|
|
s._call("material_change", capability_id="rev-cap", note="fourth",
|
|
source_locations=[])
|
|
s._call("get", capability_id="rev-cap")
|
|
s._call("context_packet", capability_id="rev-cap")
|
|
|
|
# ── S7: classify_version_bump — full 4-bool matrix ───────────────────────
|
|
s = Seq("classify-bump-matrix", sequences)
|
|
s._call("new")
|
|
for cc in (False, True):
|
|
for ba in (False, True):
|
|
for cb in (False, True):
|
|
for io in (False, True):
|
|
s._call("classify_version_bump", contract_changed=cc,
|
|
behaviour_added=ba, consumer_break=cb,
|
|
implementation_only=io)
|
|
|
|
# ── S8: bump_version — valid / invalid / zeros / overflow ────────────────
|
|
s = Seq("bump-version-cases", sequences)
|
|
s._call("new")
|
|
for version, kind in [
|
|
("0.1.0", "MAJOR"), ("0.1.0", "MINOR"), ("0.1.0", "PATCH"),
|
|
("1.2.3", "MAJOR"), ("1.2.3", "MINOR"), ("1.2.3", "PATCH"),
|
|
("0.0.9", "PATCH"), ("999.999.999", "MAJOR"), ("01.02.03", "MINOR"),
|
|
("007.0.0", "PATCH"),
|
|
("18446744073709551615.0.0", "MAJOR"), # u64 boundary
|
|
("99999999999999999999999999.0.0", "PATCH"), # beyond u128
|
|
("1.2", "PATCH"), ("1.2.3.4", "PATCH"), ("abc", "MAJOR"),
|
|
("", "PATCH"), ("1..3", "MINOR"), ("-1.2.3", "MAJOR"),
|
|
("1.2.x", "PATCH"), (" 1.2.3", "PATCH"), ("1.2.3 ", "PATCH"),
|
|
("1.2.3", "BOGUS"), ("1.2.3", ""), ("1.2.3", "major"),
|
|
]:
|
|
s._call("bump_version", version=version, kind=kind)
|
|
|
|
# ── S9: attach_spec — defaults + explicit + errors ───────────────────────
|
|
s = Seq("attach-spec-branches", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="specd", name="S", purpose="spec'd",
|
|
owning_project="rune", component="component:c")
|
|
s._call("spec_drift_status", capability_id="specd") # no spec attached
|
|
s._call("attach_spec", capability_id="specd", requirements=["r1", "r2"],
|
|
expected_behaviours=["b1"], non_goals=["ng1"],
|
|
acceptance_criteria=["ac1"])
|
|
s._call("spec_drift_status", capability_id="specd") # baseline covers rev 0
|
|
s._call("define", capability_id="specd2", name="S2", purpose="x",
|
|
owning_project="rune", component="component:c")
|
|
s._call("attach_spec", capability_id="specd2", requirements=[],
|
|
expected_behaviours=[], non_goals=[], acceptance_criteria=[],
|
|
baseline_version="1.0.0", baseline_reason="stable, evidenced",
|
|
governance_version_started_at="2025-12-31T23:59:59Z")
|
|
s._call("get", capability_id="specd2")
|
|
# errors
|
|
s._call("define", capability_id="badbase", name="B", purpose="x",
|
|
owning_project="rune", component="component:c")
|
|
s._call("attach_spec", capability_id="badbase", requirements=[],
|
|
expected_behaviours=[], non_goals=[], acceptance_criteria=[],
|
|
baseline_version="1.0")
|
|
s._call("attach_spec", capability_id="badbase", requirements=[],
|
|
expected_behaviours=[], non_goals=[], acceptance_criteria=[],
|
|
baseline_version="not-semver")
|
|
s._call("attach_spec", capability_id="ghost", requirements=[],
|
|
expected_behaviours=[], non_goals=[], acceptance_criteria=[])
|
|
|
|
# ── S10: revise_spec — classification, governance, drift closure ─────────
|
|
s = Seq("revise-spec-governance", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="gov", name="G", purpose="governed",
|
|
owning_project="rune", component="component:c")
|
|
s._call("attach_spec", capability_id="gov", requirements=["r"],
|
|
expected_behaviours=["b"], non_goals=["n"], acceptance_criteria=["a"])
|
|
s._call("material_change", capability_id="gov", note="uncovered rev 1")
|
|
s._call("spec_drift_status", capability_id="gov") # DRIFT: rev 1 > covered 0
|
|
s._call("revise_spec", capability_id="gov", implementation_only=True,
|
|
reason="internal tidy-up")
|
|
s._call("spec_drift_status", capability_id="gov") # drift closed
|
|
s._call("material_change", capability_id="gov", note="rev 2")
|
|
s._call("material_change", capability_id="gov", note="rev 3")
|
|
s._call("spec_drift_status", capability_id="gov") # 2 uncovered
|
|
s._call("revise_spec", capability_id="gov", behaviour_added=True,
|
|
reason=LONG_REASON, changed_sections=["requirements"],
|
|
affected_consumers=["consumer-x"],
|
|
validation_consequences=["retest suite B"])
|
|
s._call("spec_drift_status", capability_id="gov")
|
|
s._call("revise_spec", capability_id="gov", consumer_break=True,
|
|
reason="breaking contract change")
|
|
s._call("spec_drift_status", capability_id="gov")
|
|
s._call("get", capability_id="gov")
|
|
|
|
# ── S11: revise_spec — proposed-kind governance branches ─────────────────
|
|
s = Seq("revise-proposed-kind", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="pk", name="PK", purpose="p",
|
|
owning_project="rune", component="component:c")
|
|
s._call("attach_spec", capability_id="pk", requirements=["r"],
|
|
expected_behaviours=["b"], non_goals=["n"], acceptance_criteria=["a"])
|
|
s._call("revise_spec", capability_id="pk", implementation_only=True,
|
|
reason="ok patch, proposed matches", proposed_kind="PATCH")
|
|
s._call("revise_spec", capability_id="pk", implementation_only=True,
|
|
reason="patch proposed but minor earned", proposed_kind="MINOR")
|
|
s._call("revise_spec", capability_id="pk", behaviour_added=True,
|
|
reason="minor earned, downgraded to patch on purpose",
|
|
proposed_kind="PATCH")
|
|
s._call("revise_spec", capability_id="pk", implementation_only=True,
|
|
reason="major proposed for metadata churn — unjustified",
|
|
proposed_kind="MAJOR")
|
|
s._call("revise_spec", capability_id="pk", implementation_only=True,
|
|
reason="minor proposed for metadata churn — unjustified",
|
|
proposed_kind="MINOR")
|
|
s._call("revise_spec", capability_id="pk", implementation_only=True,
|
|
reason="garbage proposed kind", proposed_kind="HUGE")
|
|
s._call("revise_spec", capability_id="pk", implementation_only=True,
|
|
reason="empty proposed kind falls back to classify",
|
|
proposed_kind="")
|
|
s._call("revise_spec", capability_id="pk", reason="no flags at all — churn")
|
|
s._call("get", capability_id="pk")
|
|
|
|
# ── S12: revise_spec on a capability with no spec ────────────────────────
|
|
s = Seq("revise-no-spec", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="bare", name="B", purpose="p",
|
|
owning_project="rune", component="component:c")
|
|
s._call("revise_spec", capability_id="bare", implementation_only=True,
|
|
reason="x")
|
|
|
|
# ── S13: save/reload roundtrip (sorted file bytes pinned) ────────────────
|
|
s = Seq("save-reload-roundtrip", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="zzz-last", name="Z", purpose="p",
|
|
owning_project="rune", component="component:c")
|
|
s._call("define", capability_id="aaa-first", name="A", purpose="p",
|
|
owning_project="rune", component="component:c")
|
|
s._call("save")
|
|
saved = s.saved_files["capability_ledger.json"]
|
|
s2 = Seq("reload-from-saved-file", sequences)
|
|
s2._call("new", file_text=saved)
|
|
s2._call("active") # sorted-key order from the file
|
|
s2._call("get", capability_id="aaa-first")
|
|
s2._call("save")
|
|
|
|
# ── S14: unicode + repr edge cases ───────────────────────────────────────
|
|
s = Seq("unicode-repr-edges", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="café-x", name="Café", purpose="rôle",
|
|
owning_project="rune", component="component:coffeé")
|
|
s._call("context_packet", capability_id="café-x")
|
|
s._call("material_change", capability_id="café-x", note="naïve change → ünïcode ✓")
|
|
s._call("material_change", capability_id="ghost-cap", note="x")
|
|
s._call("bump_version", version="café.1.0", kind="PATCH")
|
|
s._call("context_packet", capability_id="quo'te")
|
|
s._call("context_packet", capability_id='dq"te')
|
|
|
|
# ── S15: spec_context with and without spec ──────────────────────────────
|
|
s = Seq("spec-context", sequences)
|
|
s._call("new")
|
|
s._call("define", capability_id="ctx-bare", name="CB", purpose="purpose-y",
|
|
owning_project="rune", component="component:c",
|
|
contract="contract-y", invariants=["i1"], behaviour_specs=["bs1"])
|
|
s._call("spec_context", capability_id="ctx-bare") # no spec — empty spec half
|
|
s._call("attach_spec", capability_id="ctx-bare", requirements=["req1"],
|
|
expected_behaviours=["eb1"], non_goals=["ngo1"],
|
|
acceptance_criteria=["ac1"])
|
|
s._call("spec_context", capability_id="ctx-bare")
|
|
s._call("material_change", capability_id="ctx-bare", note="m")
|
|
s._call("spec_context", capability_id="ctx-bare") # last_bump = BASELINE entry
|
|
|
|
truth = {
|
|
"frozen": "capability_ledger full public surface + spec machinery",
|
|
"oracle": "src/rune/capability_ledger.py",
|
|
"note": "Every op records the exact _now() it was given; the kernel "
|
|
"receives that string via the FFI payload and never reads a "
|
|
"clock. saved_files pin save() byte output (Python facade "
|
|
"serializes with indent=1 sort_keys=True).",
|
|
"sequences": [
|
|
{"name": s.name, "ops": s.ops, "expected": s.expected,
|
|
"saved_files": s.saved_files,
|
|
"final_doc": s.ledger.doc}
|
|
for s in sequences
|
|
],
|
|
}
|
|
|
|
out = Path("/tmp/cl_truth.json")
|
|
out.write_text(json.dumps(truth, indent=1, ensure_ascii=False) + "\n")
|
|
n_ops = sum(len(s.ops) for s in sequences)
|
|
print(f"froze {len(sequences)} sequences / {n_ops} ops -> {out}")
|