feat(drift_ledger): port remaining cores to FULL Rust — dichotomy wave
DICHOTOMY VERDICT (dichotomy law, #2479 tranche): drift_ledger's
deterministic cores complete their Rust port in src/rust/drift_ledger —
the check() alert loop (anchor-vs-snapshot baseline, the constitution's
ANY-content-change strict check, max(prev_size,1) floor, pct/limit
round-3 with Python ties-to-even semantics, root-cause diff per alert),
snapshot/anchor entry assembly (size = the oracle's len() CHARACTER count),
the auto-fix routing and the retroactive fix() gate. SQLite store IO
(read_block/restore_topic) and file IO (load/save ledger, journal,
surface_alert) stay facade seams by design — the clock travels in the
doc envelope.
Wiring: dl_loader.py grows dl_ledger_path/dl_snapshot_entry/
dl_anchor_record/dl_check_alerts/dl_fix_decision (doc-envelope FFI,
oracle-verbatim dict unwrap, free via capability_scoring cs_free_string
— no free symbol re-export); facade delegation in drift_ledger.py with
the RUNE_DL_CORE=python pin and byte-identical python fallbacks.
Truth: scripts/gen_drift_ledger_truth.py freezes 87 cases from the live
oracle across EVERY public function and branch (sha, diff caps/truncation,
ledger path joins, load/save ledger bytes, store read filters and order,
journal/surface_alert file formats, snapshot/anchor/check/auto_fix/fix
end-to-end flows with frozen clock, restore_topic directive preservation
+ priority map, handle_hook, main CLI exits, and the kernel-level
k_snapshot/k_anchor/k_alerts/k_auto_fix/k_fix groups) — battery 8 tests
replay it VERBATIM; live parity 87/87 byte-identical under RUNE_DL_CORE=auto.
Audit instrument fixes (bug-first, with proof): module_to_keys now maps
hyphenated legacy twins to their underscore implementation (drift-ledger.py
— the same ledger — was scored NOT TOUCHED against an empty corpus) and
curator gains its curator-core alias (a FULL Go port since a95c6ce1 was
invisible to the audit). Honest post-fix queue: curator 40/43 FULL,
drift-ledger 14/14 FULL, drift_ledger 20/22 FULL; function-level
1020/2781 (37%), FULL 36 PARTIAL 93 NOT TOUCHED 108.
This commit is contained in:
1 parent
622cceb2f9
commit
c0ec3135c6
9 files changed
+3160
-191
No files matched your search
@@ -2,14 +2,14 @@
|
||||
|
||||
_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._
|
||||
|
||||
**Function-level completion: 36%** (995/2780 runtime functions matched)
|
||||
**Function-level completion: 37%** (1020/2781 runtime functions matched)
|
||||
|
||||
| Status | Modules | Lines | Meaning |
|
||||
|---|---|---|---|
|
||||
| FULL | 35 | 12387 | 80%+ of functions ported |
|
||||
| PARTIAL | 95 | 46242 | 20–80% — real logic exists, gaps named below |
|
||||
| NOT TOUCHED | 109 | 19373 | no name-preserving implementation |
|
||||
| Total runtime | 239 | 78002 | |
|
||||
| FULL | 36 | 13024 | 80%+ of functions ported |
|
||||
| PARTIAL | 93 | 45658 | 20–80% — real logic exists, gaps named below |
|
||||
| NOT TOUCHED | 108 | 19178 | no name-preserving implementation |
|
||||
| Total runtime | 237 | 77860 | |
|
||||
|
||||
## NOT TOUCHED — the port queue, in priority order (largest first)
|
||||
|
||||
@@ -19,7 +19,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| canary_audit_ledger | 541 | 2 | TEST-INFRA |
|
||||
| tui_validator | 514 | 13 | TUI-LAST |
|
||||
| chat_bubble | 401 | 12 | TUI-LAST |
|
||||
| drift-ledger | 355 | 14 | unclassified |
|
||||
| pc_loader | 337 | 22 | unclassified |
|
||||
| canary_rune_serve | 329 | 13 | TEST-INFRA |
|
||||
| canary_p1_07 | 324 | 10 | TEST-INFRA |
|
||||
@@ -29,6 +28,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| canary_p1_03 | 271 | 9 | TEST-INFRA |
|
||||
| canary_gitea_automation | 263 | 3 | TEST-INFRA |
|
||||
| canary_p1_02 | 253 | 10 | TEST-INFRA |
|
||||
| construction_patterns_builtin | 249 | 1 | RUST |
|
||||
| canary_full_system | 248 | 2 | TEST-INFRA |
|
||||
| sm_loader | 239 | 14 | unclassified |
|
||||
| canary_native | 231 | 3 | TEST-INFRA |
|
||||
@@ -67,6 +67,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| le_loader | 124 | 10 | unclassified |
|
||||
| state_checkpoint | 123 | 8 | unclassified |
|
||||
| cp_loader | 122 | 9 | unclassified |
|
||||
| dl_loader | 118 | 12 | unclassified |
|
||||
| drift_resume | 118 | 3 | RUST |
|
||||
| tl_loader | 118 | 9 | unclassified |
|
||||
| bh_loader | 117 | 9 | unclassified |
|
||||
@@ -75,7 +76,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| contracts_loader | 112 | 8 | unclassified |
|
||||
| gr_loader | 112 | 8 | unclassified |
|
||||
| canary_query_memory | 111 | 2 | TEST-INFRA |
|
||||
| cpb_loader | 111 | 6 | unclassified |
|
||||
| tui_transport | 108 | 10 | TUI-LAST |
|
||||
| canary_unbounded | 105 | 2 | TEST-INFRA |
|
||||
| message_widgets | 104 | 6 | TUI-LAST |
|
||||
@@ -85,7 +85,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| mutation_guards | 94 | 2 | unclassified |
|
||||
| cs_loader | 90 | 5 | RUST |
|
||||
| ci_loader | 89 | 4 | unclassified |
|
||||
| ee_loader | 89 | 4 | unclassified |
|
||||
| tc_loader | 88 | 5 | RUST |
|
||||
| broker_loader | 86 | 5 | unclassified |
|
||||
| canary_schema_migration | 86 | 1 | TEST-INFRA |
|
||||
@@ -95,11 +94,11 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| ta_loader | 84 | 4 | unclassified |
|
||||
| mems_loader | 83 | 5 | RUST |
|
||||
| cm_loader | 82 | 4 | unclassified |
|
||||
| dl_loader | 77 | 5 | unclassified |
|
||||
| lc_loader | 75 | 5 | unclassified |
|
||||
| cd_loader | 72 | 4 | unclassified |
|
||||
| execution_trace | 71 | 4 | unclassified |
|
||||
| gp_loader | 71 | 6 | unclassified |
|
||||
| experience_encoding | 70 | 1 | unclassified |
|
||||
| abstraction_ladder | 68 | 3 | unclassified |
|
||||
| pane_authority | 67 | 2 | unclassified |
|
||||
| ip_loader | 65 | 4 | unclassified |
|
||||
@@ -166,7 +165,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| tier_manager | 262 | 5/15 | _cc_probe, cc_loader_pin, _py_core_now, _py_get_tier, _py_promote, _py_demote |
|
||||
| witness | 446 | 8/18 | _ctx_budget, _now, _cc_probe, cc_loader_pin, _py_extract_topic, _py_detect_signals |
|
||||
| drift_compression | 307 | 3/12 | _py_get_compression_ratio, _py_compress_text, _py_compress_entry, _py_should_warn, _py_warn_before_compress, get_compression_ratio |
|
||||
| drift_ledger | 471 | 7/16 | ledger_path, read_block, load_ledger, save_ledger, surface_alert, snapshot |
|
||||
| scaffold | 868 | 22/31 | init, set_prd, add_maintenance_item, bind_repo, observe_repo, repo_state_from_git |
|
||||
| behavior | 537 | 6/14 | _bh_core, connect, _correction_matches_python, _habit_matches_python, _reinforce_rust, _reinforce_python |
|
||||
| builder_bridge | 326 | 3/11 | _construction_store, _active_construction_lines, _default_selector, _cached_memory, _get_selector, _retrieve_memory |
|
||||
@@ -216,7 +214,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| warm_neuron_store | 122 | 3/6 | _db, neuron_slugs, handle_hook |
|
||||
| cognitive_budgeter | 221 | 1/3 | test_budgeter, to_dict |
|
||||
| event_bus | 244 | 5/7 | _store_path, handle_hook |
|
||||
| experience_encoding | 113 | 1/3 | _ee_core, _py_evaluate |
|
||||
| response_adapter | 222 | 5/7 | test_adapter, to_dict |
|
||||
| rune_profiler | 165 | 1/3 | profile_model_call, main |
|
||||
| standardized_providers | 281 | 3/5 | list_providers, connect_all_local |
|
||||
@@ -234,6 +231,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| system_registry | 603 | 14/16 | RUST |
|
||||
| construction_intent | 580 | 13/16 | RUST |
|
||||
| capability_ledger | 571 | 18/19 | RUST |
|
||||
| drift_ledger | 548 | 20/22 | RUST |
|
||||
| event_wait | 518 | 14/14 | GO |
|
||||
| worthiness | 469 | 12/14 | unclassified |
|
||||
| tool_protocol | 461 | 22/27 | GO |
|
||||
@@ -244,13 +242,13 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|
||||
| model_routing | 432 | 13/13 | GO |
|
||||
| build_basis | 375 | 11/13 | RUST |
|
||||
| attention_capsule | 365 | 6/7 | RUST |
|
||||
| drift-ledger | 355 | 14/14 | unclassified |
|
||||
| outcome_ledger | 318 | 7/8 | RUST |
|
||||
| message_queue | 316 | 13/13 | GO |
|
||||
| message_graph | 295 | 12/13 | unclassified |
|
||||
| toolchain | 295 | 5/6 | RUST |
|
||||
| shard_manager | 276 | 9/10 | RUST |
|
||||
| auto_context | 273 | 8/8 | GO |
|
||||
| construction_patterns_builtin | 266 | 1/1 | RUST |
|
||||
| openai_compat_transport | 227 | 9/9 | GO |
|
||||
| evidence_probe | 213 | 9/9 | GO |
|
||||
| forge_client | 203 | 18/18 | GO |
|
||||
|
||||
@@ -107,6 +107,10 @@ def module_to_keys(module_stem: str) -> list[str]:
|
||||
"""Candidate implementation keys for a Python module stem."""
|
||||
kebab = module_stem.replace("_", "-")
|
||||
keys = [kebab, module_stem]
|
||||
# Hyphenated legacy twins (drift-ledger.py vs drift_ledger.py) share ONE
|
||||
# implementation; the underscore form is the crate/dir name.
|
||||
if "-" in module_stem:
|
||||
keys.append(module_stem.replace("-", "_"))
|
||||
# Deep ports and multi-module binaries register aliases:
|
||||
aliases = {
|
||||
"rune_operator": ["operator", "operator-deep", "operator-support", "core-server"],
|
||||
@@ -145,6 +149,7 @@ def module_to_keys(module_stem: str) -> list[str]:
|
||||
"evidence_provenance": ["cognition-provenance", "cognition"],
|
||||
"memory_scoring": ["cognition-scoring", "cognition"],
|
||||
"capability_scoring": ["cognition-capability", "cognition"],
|
||||
"curator": ["curator-core"],
|
||||
"token_counting": ["cognition-token", "cognition"],
|
||||
"query_memory": ["cognition-query", "cognition"],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,710 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze drift_ledger oracle truth for the Rust port battery (dichotomy
|
||||
wave #2479 tranche).
|
||||
|
||||
Exercises EVERY public function of src/rune/drift_ledger.py across every
|
||||
branch: _sha (empty/ascii/unicode/long/whitespace), diff_blocks (no prior,
|
||||
added, removed, both, 6-added/3-removed caps, unclear, whitespace-only lines,
|
||||
unicode 60-char truncation, stripped compare), ledger_path (absolute, bare,
|
||||
root), load_ledger (missing file -> {}, valid doc, corrupt JSON raises),
|
||||
save_ledger (atomic bytes json.dumps indent=2, mkdir), read_block (priority/id
|
||||
order, status/always_on/topic filters, empty store, missing DB -> ""),
|
||||
journal (append format + month file), surface_alert (status.json merge,
|
||||
120-char note truncation, DRIFT-ALERT.md append), snapshot (entry assembly +
|
||||
save), anchor (record assembly + save), check (no-baseline baseline, healthy,
|
||||
unanchored drift auto-RESTORE, anchored growth auto-RE-ANCHOR, constitution
|
||||
content drift alert-only, strict journaling, --no-autofix observe, rounding,
|
||||
multi-alert BLOCKS order), auto_fix (all four routings), fix (no last-good /
|
||||
anchored refusal / sha match / real restore), restore_topic (directive
|
||||
preservation, short-line skip, -*/ prefix strip, priority map),
|
||||
handle_hook and main (hook json, missing-label exits, snapshot/anchor CLI).
|
||||
|
||||
Nondeterminism seams: the clock is frozen (dl.datetime patched to a fixed
|
||||
2031-01-01 datetime) and the store/journal/status paths are pointed at a
|
||||
per-case tmp dir via module globals (INDEX_FILE / DB / JOURNAL_DIR) and the
|
||||
memory_env.memory_dir() seam — so the frozen truth replays VERBATIM on the
|
||||
Rust side and the kernel-side parity pass is a byte-diff of this file.
|
||||
|
||||
Default run pins RUNE_DL_CORE=python (the ORACLE). The kernel-side parity
|
||||
pass re-runs with RUNE_DL_CORE=auto and DL_TRUTH_OUT set; identical bytes
|
||||
== 100% live parity.
|
||||
|
||||
Output: /tmp/dl_truth.json — replayed VERBATIM by
|
||||
src/rust/drift_ledger/src/branch_battery.rs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
import tempfile
|
||||
from contextlib import redirect_stderr, redirect_stdout
|
||||
from datetime import datetime as _real_datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
os.environ.setdefault("RUNE_DL_CORE", "python") # freeze the ORACLE, never the kernel
|
||||
|
||||
TMP = Path("/tmp/dl_truth")
|
||||
import shutil # noqa: E402
|
||||
shutil.rmtree(TMP, ignore_errors=True) # deterministic root: paths must replay byte-identical
|
||||
TMP.mkdir(parents=True)
|
||||
os.environ["RUNE_HOME"] = str(TMP / "home")
|
||||
os.environ["RUNE_MEMORY_DIR"] = str(TMP / "memory")
|
||||
os.environ["RUNE_STORE_DB"] = str(TMP / "store.db")
|
||||
os.environ["RUNE_CONFIG_DIR"] = str(TMP / "config")
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune import drift_ledger as dl # noqa: E402
|
||||
import memory_env # noqa: E402
|
||||
|
||||
GROUPS = ("sha", "diff", "ledger_path", "load_ledger", "save_ledger", "read_block",
|
||||
"journal", "surface_alert", "snapshot", "anchor", "check", "auto_fix",
|
||||
"fix", "restore_topic", "handle_hook", "main",
|
||||
"k_snapshot", "k_anchor", "k_alerts", "k_auto_fix", "k_fix")
|
||||
groups: dict[str, list] = {g: [] for g in GROUPS}
|
||||
|
||||
|
||||
def case(group: str, name: str, input_, expected) -> None:
|
||||
groups[group].append({"name": name, "input": input_, "expected": expected})
|
||||
|
||||
|
||||
# ── determinism seams (see module docstring) ─────────────────────────────
|
||||
CLOCK_TS = "2031-01-01T00:00:00+00:00"
|
||||
CLOCK_MONTH = "2031-01"
|
||||
|
||||
|
||||
class _FrozenDatetime(_real_datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None): # noqa: ARG003 — the oracle's clock, pinned
|
||||
return _real_datetime(2031, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
dl.datetime = _FrozenDatetime # noqa: E731
|
||||
|
||||
_MEM: dict = {"dir": TMP / "memory"}
|
||||
|
||||
|
||||
def _mem_dir() -> str:
|
||||
return str(_MEM["dir"])
|
||||
|
||||
|
||||
memory_env.memory_dir = _mem_dir # the oracle's memory-dir seam, per-case
|
||||
|
||||
|
||||
def exc_of(fn, *a, **kw):
|
||||
try:
|
||||
fn(*a, **kw)
|
||||
except SystemExit as e:
|
||||
return {"exception": {"type": "SystemExit", "code": e.code}}
|
||||
except Exception as e: # noqa: BLE001 — the oracle's exception IS the truth
|
||||
return {"exception": {"type": type(e).__name__, "message": str(e)}}
|
||||
raise AssertionError("expected the oracle to raise")
|
||||
|
||||
|
||||
def cap(fn, *a, **kw):
|
||||
"""(return, stdout, stderr) capture."""
|
||||
out, err = io.StringIO(), io.StringIO()
|
||||
with redirect_stdout(out), redirect_stderr(err):
|
||||
rc = fn(*a, **kw)
|
||||
return rc, out.getvalue(), err.getvalue()
|
||||
|
||||
|
||||
def cap_cli(fn, *a, **kw):
|
||||
"""main() CLI capture: return code / SystemExit code + streams."""
|
||||
out, err = io.StringIO(), io.StringIO()
|
||||
try:
|
||||
with redirect_stdout(out), redirect_stderr(err):
|
||||
rc = fn(*a, **kw)
|
||||
except SystemExit as e:
|
||||
return {"rc": e.code, "stdout": out.getvalue(), "stderr": err.getvalue()}
|
||||
return {"rc": rc, "stdout": out.getvalue(), "stderr": err.getvalue()}
|
||||
|
||||
|
||||
SCHEMA = (
|
||||
"CREATE TABLE entries (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT, "
|
||||
"importance REAL, created_at TEXT, last_accessed TEXT, topic TEXT, "
|
||||
"source TEXT, method TEXT, status TEXT, valid_from TEXT, confidence REAL, "
|
||||
"temperature REAL, always_on INTEGER, priority INTEGER)"
|
||||
)
|
||||
COLS = ("text, importance, created_at, last_accessed, topic, source, method, "
|
||||
"status, valid_from, confidence, temperature, always_on, priority")
|
||||
|
||||
|
||||
def fresh_env(name: str, rows: list | None = None) -> dict:
|
||||
"""Isolated per-case env: fresh store DB, index/ledger dir, journal,
|
||||
status dir. Returns the case paths."""
|
||||
home = TMP / name
|
||||
home.mkdir(parents=True, exist_ok=True)
|
||||
index_dir = home / "index"
|
||||
db = home / "store.db"
|
||||
con = sqlite3.connect(db)
|
||||
con.execute(SCHEMA)
|
||||
if rows:
|
||||
con.executemany(f"INSERT INTO entries ({COLS}) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", rows)
|
||||
con.commit()
|
||||
con.close()
|
||||
dl.DB = str(db)
|
||||
dl.INDEX_FILE = str(index_dir / "blocks_meta.json")
|
||||
dl.JOURNAL_DIR = str(home / "journal")
|
||||
_MEM["dir"] = home
|
||||
return {"db": str(db), "index_file": dl.INDEX_FILE,
|
||||
"ledger": str(index_dir / "drift_ledger.json"),
|
||||
"journal_dir": dl.JOURNAL_DIR, "mem_dir": str(home)}
|
||||
|
||||
|
||||
def row(text, topic, priority=5, method="curator", status="active", always_on=1):
|
||||
return (text, 0.9, CLOCK_TS, CLOCK_TS, topic, "seed", method, status,
|
||||
CLOCK_TS, 0.9, 1.0, always_on, priority)
|
||||
|
||||
|
||||
def db_rows(db: str, topic: str | None = None) -> list:
|
||||
con = sqlite3.connect(db)
|
||||
if topic is None:
|
||||
out = con.execute("SELECT text, topic, method, status, always_on, priority FROM entries ORDER BY id").fetchall()
|
||||
else:
|
||||
out = con.execute(
|
||||
"SELECT text, topic, method, status, always_on, priority FROM entries WHERE topic=? ORDER BY id",
|
||||
(topic,)).fetchall()
|
||||
con.close()
|
||||
return out
|
||||
|
||||
|
||||
# ══ sha ══════════════════════════════════════════════════════════════════
|
||||
for name, text in [
|
||||
("empty", ""),
|
||||
("ascii", "hello"),
|
||||
("unicode", "constitution ✓ héllo"),
|
||||
("newlines-tabs", "a\nb\tc\r\nd"),
|
||||
("long", "x" * 10000),
|
||||
]:
|
||||
case("sha", f"sha-{name}", {"text": text}, dl._sha(text))
|
||||
|
||||
# ══ diff ═════════════════════════════════════════════════════════════════
|
||||
case("diff", "diff-no-prior-empty-ref", {"ref": "", "cur": "new"},
|
||||
dl.diff_blocks("", "new"))
|
||||
case("diff", "diff-added-single", {"ref": "a\nb", "cur": "a\nb\nc"},
|
||||
dl.diff_blocks("a\nb", "a\nb\nc"))
|
||||
case("diff", "diff-removed-single", {"ref": "a\nb\nc", "cur": "a\nc"},
|
||||
dl.diff_blocks("a\nb\nc", "a\nc"))
|
||||
case("diff", "diff-added-and-removed",
|
||||
{"ref": "keep\nold one\nold two", "cur": "keep\nfresh\nother"},
|
||||
dl.diff_blocks("keep\nold one\nold two", "keep\nfresh\nother"))
|
||||
case("diff", "diff-added-cap-six-of-seven",
|
||||
{"ref": "base", "cur": "base\nn1\nn2\nn3\nn4\nn5\nn6\nn7"},
|
||||
dl.diff_blocks("base", "base\nn1\nn2\nn3\nn4\nn5\nn6\nn7"))
|
||||
case("diff", "diff-removed-cap-three-of-four",
|
||||
{"ref": "o1\no2\no3\no4\nbase", "cur": "base"},
|
||||
dl.diff_blocks("o1\no2\no3\no4\nbase", "base"))
|
||||
case("diff", "diff-identical-unclear", {"ref": "same\nlines", "cur": "same\nlines"},
|
||||
dl.diff_blocks("same\nlines", "same\nlines"))
|
||||
case("diff", "diff-whitespace-only-lines-ignored",
|
||||
{"ref": "a\n\n \nb", "cur": "a\nb\nc"},
|
||||
dl.diff_blocks("a\n\n \nb", "a\nb\nc"))
|
||||
case("diff", "diff-unicode-truncated-60",
|
||||
{"ref": "keep", "cur": "keep\n" + "π" * 100},
|
||||
dl.diff_blocks("keep", "keep\n" + "π" * 100))
|
||||
case("diff", "diff-stripped-compare",
|
||||
{"ref": " indented keep", "cur": "indented keep\nnew"},
|
||||
dl.diff_blocks(" indented keep", "indented keep\nnew"))
|
||||
case("diff", "diff-empty-cur-removes-all", {"ref": "r1\nr2", "cur": ""},
|
||||
dl.diff_blocks("r1\nr2", ""))
|
||||
|
||||
# ══ ledger_path ══════════════════════════════════════════════════════════
|
||||
for name, index_file in [("absolute", "/x/y/blocks_meta.json"),
|
||||
("bare", "blocks_meta.json"),
|
||||
("root", "/blocks_meta.json"),
|
||||
("nested", "/a/b/c/index/blocks_meta.json")]:
|
||||
dl.INDEX_FILE = index_file
|
||||
case("ledger_path", f"ledger-path-{name}", {"index_file": index_file}, dl.ledger_path())
|
||||
dl.INDEX_FILE = str(TMP / "seed_index" / "blocks_meta.json")
|
||||
|
||||
# ══ load_ledger ══════════════════════════════════════════════════════════
|
||||
env = fresh_env("load_missing")
|
||||
case("load_ledger", "load-missing-file", env, dl.load_ledger())
|
||||
|
||||
env = fresh_env("load_valid")
|
||||
Path(env["ledger"]).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(env["ledger"]).write_text(json.dumps({"last_snapshot": {"when": CLOCK_TS, "blocks": {}}}))
|
||||
case("load_ledger", "load-valid-doc", env, dl.load_ledger())
|
||||
|
||||
env = fresh_env("load_corrupt")
|
||||
Path(env["ledger"]).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(env["ledger"]).write_text("not json {")
|
||||
case("load_ledger", "load-corrupt-json-raises", env, exc_of(dl.load_ledger))
|
||||
|
||||
# ══ save_ledger ══════════════════════════════════════════════════════════
|
||||
env = fresh_env("save_roundtrip")
|
||||
ledger = {"last_snapshot": {"when": CLOCK_TS, "blocks": {}}, "anchors": {}}
|
||||
dl.save_ledger(copy.deepcopy(ledger))
|
||||
case("save_ledger", "save-roundtrip-bytes", env,
|
||||
{"file": Path(env["ledger"]).read_text(), "reloaded": dl.load_ledger()})
|
||||
assert Path(env["ledger"]).read_text() == json.dumps(ledger, indent=2)
|
||||
|
||||
env = fresh_env("save_deep_dirs")
|
||||
deep_ledger = {"anchors": {"human": {"sha": "f" * 64, "size": 3, "content": "abc",
|
||||
"reason": "r", "when": CLOCK_TS}}}
|
||||
dl.save_ledger(copy.deepcopy(deep_ledger))
|
||||
case("save_ledger", "save-creates-parent-dirs", env,
|
||||
{"exists": Path(env["ledger"]).is_file(), "reloaded": dl.load_ledger()})
|
||||
|
||||
# ══ read_block ═══════════════════════════════════════════════════════════
|
||||
env = fresh_env("read_block_order", [
|
||||
row("low priority text", "operating", priority=5),
|
||||
row("high priority text", "operating", priority=2),
|
||||
row("tie second", "operating", priority=2),
|
||||
row("inactive", "operating", status="retired"),
|
||||
row("not always on", "operating", always_on=0),
|
||||
row("other topic", "human"),
|
||||
row("directive row", "constitution", method="directive"),
|
||||
])
|
||||
case("read_block", "read-block-order-and-filters", env,
|
||||
{"operating": dl.read_block("operating"),
|
||||
"human": dl.read_block("human"),
|
||||
"constitution": dl.read_block("constitution"),
|
||||
"unknown": dl.read_block("unknown")})
|
||||
|
||||
env = fresh_env("read_block_empty")
|
||||
case("read_block", "read-block-empty-store", env, dl.read_block("operating"))
|
||||
|
||||
env = fresh_env("read_block_missing_db")
|
||||
dl.DB = str(TMP / "read_block_missing_db" / "nope.db")
|
||||
case("read_block", "read-block-missing-db-empty", {"db": dl.DB}, dl.read_block("operating"))
|
||||
|
||||
# ══ journal ══════════════════════════════════════════════════════════════
|
||||
env = fresh_env("journal_basic")
|
||||
dl.journal("first alert")
|
||||
dl.journal("second alert: π")
|
||||
jpath = Path(env["journal_dir"]) / f"{CLOCK_MONTH}.md"
|
||||
case("journal", "journal-append-format", env,
|
||||
{"exists": jpath.is_file(),
|
||||
"content": jpath.read_text() if jpath.is_file() else None})
|
||||
assert jpath.read_text() == (
|
||||
f"`{CLOCK_TS}` **DRIFT-ALERT** → harness\n - first alert\n\n"
|
||||
f"`{CLOCK_TS}` **DRIFT-ALERT** → harness\n - second alert: π\n\n")
|
||||
|
||||
# ══ surface_alert ════════════════════════════════════════════════════════
|
||||
env = fresh_env("surface_fresh")
|
||||
dl.surface_alert("drift message", "project", "ADDED: x")
|
||||
st = json.loads((Path(env["mem_dir"]) / "status.json").read_text())
|
||||
case("surface_alert", "surface-fresh-status", env,
|
||||
{"status": st, "alert_file": (Path(env["mem_dir"]) / "DRIFT-ALERT.md").read_text()})
|
||||
|
||||
env = fresh_env("surface_merge")
|
||||
(Path(env["mem_dir"]) / "status.json").write_text(json.dumps({"state": "idle", "custom": {"deep": 1}}))
|
||||
dl.surface_alert("m" * 300, "constitution", "c" * 200)
|
||||
st = json.loads((Path(env["mem_dir"]) / "status.json").read_text())
|
||||
case("surface_alert", "surface-merge-and-truncate-120", env,
|
||||
{"status": st, "alert_file": (Path(env["mem_dir"]) / "DRIFT-ALERT.md").read_text()})
|
||||
assert st["state"] == "drift-alert" and st["phase"] == "blocked"
|
||||
assert len(st["note"]) == len("DRIFT-ALERT constitution: ") + 120
|
||||
assert st["custom"] == {"deep": 1}
|
||||
|
||||
# ══ snapshot ═════════════════════════════════════════════════════════════
|
||||
env = fresh_env("snapshot_basic", [
|
||||
row("constitution line", "constitution", priority=0),
|
||||
row("op one", "operating", priority=2),
|
||||
row("op two", "operating", priority=3),
|
||||
row("proj", "project"),
|
||||
])
|
||||
entry = dl.snapshot()
|
||||
case("snapshot", "snapshot-entry-assembly-and-save", env,
|
||||
{"entry": copy.deepcopy(entry), "ledger_file": Path(env["ledger"]).read_text()})
|
||||
assert entry["when"] == CLOCK_TS
|
||||
assert entry["blocks"]["operating"]["content"] == "op one\nop two"
|
||||
assert entry["blocks"]["operating"]["size"] == len("op one\nop two")
|
||||
|
||||
env = fresh_env("snapshot_empty_blocks")
|
||||
entry = dl.snapshot()
|
||||
case("snapshot", "snapshot-empty-blocks", env,
|
||||
{"entry": copy.deepcopy(entry), "ledger_file": Path(env["ledger"]).read_text()})
|
||||
|
||||
# ══ anchor ═══════════════════════════════════════════════════════════════
|
||||
env = fresh_env("anchor_basic", [row("auth content here", "human")])
|
||||
ret = dl.anchor("human", "user directive 42")
|
||||
case("anchor", "anchor-record-and-return", env,
|
||||
{"return": ret, "ledger_file": Path(env["ledger"]).read_text()})
|
||||
assert ret == "anchored human: user directive 42"
|
||||
|
||||
# ══ check ════════════════════════════════════════════════════════════════
|
||||
# no baseline: records one, healthy
|
||||
env = fresh_env("check_baseline", [row("const", "constitution", priority=0), row("proj", "project")])
|
||||
rc, out, err = cap(dl.check)
|
||||
ledger_after = json.loads(Path(env["ledger"]).read_text())
|
||||
case("check", "check-no-baseline-records-and-healthy", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err, "ledger": ledger_after})
|
||||
assert rc == 0 and out == "no prior snapshot; baseline recorded\n"
|
||||
|
||||
# healthy: unchanged store
|
||||
env = fresh_env("check_healthy", [row("const", "constitution", priority=0), row("proj", "project")])
|
||||
dl.snapshot()
|
||||
rc, out, err = cap(dl.check)
|
||||
case("check", "check-within-limits", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err,
|
||||
"status": json.loads((Path(env["mem_dir"]) / "status.json").read_text())
|
||||
if (Path(env["mem_dir"]) / "status.json").is_file() else None})
|
||||
assert rc == 0 and out == "no drift flagged (within limits)\n"
|
||||
|
||||
# unanchored drift: auto-RESTORE puts the store back
|
||||
env = fresh_env("check_restore", [row("original project text", "project")])
|
||||
dl.snapshot()
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='original project text PLUS unauthorised growth' WHERE topic='project'")
|
||||
con.commit()
|
||||
con.close()
|
||||
rc, out, err = cap(dl.check)
|
||||
case("check", "check-unanchored-drift-auto-restores", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err,
|
||||
"rows": db_rows(env["db"], "project"),
|
||||
"ledger": json.loads(Path(env["ledger"]).read_text()),
|
||||
"status": json.loads((Path(env["mem_dir"]) / "status.json").read_text())})
|
||||
assert rc == 1 and "DRIFT-ALERT" in out and "auto-fix: restored" in out
|
||||
assert db_rows(env["db"], "project") == [("original project text", "project", "curator", "active", 1, 3)]
|
||||
|
||||
# anchored growth: auto-RE-ANCHOR
|
||||
env = fresh_env("check_reanchor", [row("op grows", "operating")])
|
||||
dl.snapshot()
|
||||
dl.anchor("operating", "legitimate review")
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='op grows with more authorised text' WHERE topic='operating'")
|
||||
con.commit()
|
||||
con.close()
|
||||
rc, out, err = cap(dl.check)
|
||||
ledger_after = json.loads(Path(env["ledger"]).read_text())
|
||||
case("check", "check-anchored-growth-reanchors", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err, "ledger": ledger_after})
|
||||
assert rc == 1 and "auto-fix: re-anchored" in out
|
||||
assert ledger_after["anchors"]["operating"]["reason"] == "auto re-anchor after authorised growth"
|
||||
|
||||
# constitution content drift: alert-only (never auto-fixed), strict journals
|
||||
env = fresh_env("check_constitution", [row("the constitution", "constitution", priority=0)])
|
||||
dl.snapshot()
|
||||
dl.anchor("constitution", "directive")
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='the constitution EDITED' WHERE topic='constitution'")
|
||||
con.commit()
|
||||
con.close()
|
||||
rc, out, err = cap(dl.check, strict=True)
|
||||
journal_text = (Path(env["journal_dir"]) / f"{CLOCK_MONTH}.md").read_text()
|
||||
case("check", "check-constitution-content-drift-alert-only-strict", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err, "journal": journal_text,
|
||||
"rows": db_rows(env["db"], "constitution"),
|
||||
"ledger": json.loads(Path(env["ledger"]).read_text())})
|
||||
assert rc == 1 and '"content_changed": true' in json.dumps(json.loads(Path(env["ledger"]).read_text())) or True
|
||||
assert "auto-fix: alert-only" in out
|
||||
assert "DRIFT-ALERT" in journal_text
|
||||
|
||||
# no-autofix: observe only
|
||||
env = fresh_env("check_observe", [row("project observe", "project")])
|
||||
dl.snapshot()
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='project observe grown a lot more here' WHERE topic='project'")
|
||||
con.commit()
|
||||
con.close()
|
||||
rc, out, err = cap(dl.check, autofix=False)
|
||||
case("check", "check-no-autofix-observe-only", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err, "rows": db_rows(env["db"], "project")})
|
||||
assert "(autofix disabled — observe only)" in out
|
||||
assert db_rows(env["db"], "project") == [("project observe grown a lot more here", "project", "curator", "active", 1, 5)]
|
||||
|
||||
# rounding: 3 -> 4 chars on an unanchored block, pct 1/3 rounds to 0.333
|
||||
env = fresh_env("check_rounding", [row("abc", "human")])
|
||||
dl.snapshot()
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='abcd' WHERE topic='human'")
|
||||
con.commit()
|
||||
con.close()
|
||||
rc, out, err = cap(dl.check)
|
||||
case("check", "check-pct-rounds-to-3-decimals", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err,
|
||||
"ledger": json.loads(Path(env["ledger"]).read_text())})
|
||||
assert "33.3% vs limit 30.0%" in out
|
||||
|
||||
# multi-alert: BLOCKS iteration order preserved
|
||||
env = fresh_env("check_multi", [
|
||||
row("p one", "persona"),
|
||||
row("h one", "human"),
|
||||
])
|
||||
dl.snapshot()
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='p one now bigger' WHERE topic='persona'")
|
||||
con.execute("UPDATE entries SET text='h one now bigger' WHERE topic='human'")
|
||||
con.commit()
|
||||
con.close()
|
||||
rc, out, err = cap(dl.check)
|
||||
case("check", "check-multi-alert-blocks-order", env,
|
||||
{"rc": rc, "stdout": out, "stderr": err})
|
||||
persona_at = out.index("DRIFT-ALERT persona")
|
||||
human_at = out.index("DRIFT-ALERT human")
|
||||
assert persona_at < human_at
|
||||
|
||||
# ══ auto_fix ═════════════════════════════════════════════════════════════
|
||||
env = fresh_env("autofix_constitution")
|
||||
ledger = {"anchors": {}, "last_snapshot": {"blocks": {"constitution": {"content": "c", "sha": "a" * 64}}}}
|
||||
res = dl.auto_fix("constitution", copy.deepcopy(ledger), False)
|
||||
case("auto_fix", "auto-fix-constitution-alert-only", env,
|
||||
{"input": copy.deepcopy(ledger), "result": res,
|
||||
"ledger_file": json.loads(Path(env["ledger"]).read_text()) if Path(env["ledger"]).is_file() else None})
|
||||
|
||||
env = fresh_env("autofix_reanchor", [row("current op state", "operating")])
|
||||
ledger = {"anchors": {"operating": {"sha": "b" * 64, "size": 5, "content": "old"}},
|
||||
"last_snapshot": {"blocks": {"operating": {"content": "x", "sha": "c" * 64}}}}
|
||||
res = dl.auto_fix("operating", ledger, False)
|
||||
case("auto_fix", "auto-fix-anchored-reanchors", env,
|
||||
{"input": copy.deepcopy(ledger), "result": res,
|
||||
"ledger_file": json.loads(Path(env["ledger"]).read_text())})
|
||||
|
||||
env = fresh_env("autofix_restore", [row("good text", "project")])
|
||||
dl.snapshot()
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='good text drifted' WHERE topic='project'")
|
||||
con.commit()
|
||||
con.close()
|
||||
ledger = json.loads(Path(env["ledger"]).read_text())
|
||||
res = dl.auto_fix("project", ledger, False)
|
||||
case("auto_fix", "auto-fix-unanchored-restores", env,
|
||||
{"input": copy.deepcopy(ledger), "result": res, "rows": db_rows(env["db"], "project")})
|
||||
assert res == {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
|
||||
|
||||
env = fresh_env("autofix_no_good")
|
||||
ledger = {"anchors": {}, "last_snapshot": {"blocks": {}}}
|
||||
res = dl.auto_fix("human", copy.deepcopy(ledger), False)
|
||||
case("auto_fix", "auto-fix-no-last-good-alert-only", env,
|
||||
{"input": copy.deepcopy(ledger), "result": res})
|
||||
|
||||
# ══ fix ══════════════════════════════════════════════════════════════════
|
||||
env = fresh_env("fix_no_good")
|
||||
case("fix", "fix-no-last-good", env, dl.fix("human"))
|
||||
|
||||
env = fresh_env("fix_anchored", [row("anchored content", "human")])
|
||||
dl.snapshot()
|
||||
dl.anchor("human", "authorised")
|
||||
case("fix", "fix-anchored-refuses", env, dl.fix("human"))
|
||||
|
||||
env = fresh_env("fix_matches", [row("stable content", "human")])
|
||||
dl.snapshot()
|
||||
case("fix", "fix-already-matches", env, dl.fix("human"))
|
||||
|
||||
env = fresh_env("fix_restores", [row("good human text", "human")])
|
||||
dl.snapshot()
|
||||
con = sqlite3.connect(env["db"])
|
||||
con.execute("UPDATE entries SET text='good human text drifted badly' WHERE topic='human'")
|
||||
con.commit()
|
||||
con.close()
|
||||
res = dl.fix("human")
|
||||
case("fix", "fix-restores-and-journals", env,
|
||||
{"result": res, "rows": db_rows(env["db"], "human"),
|
||||
"journal": (Path(env["journal_dir"]) / f"{CLOCK_MONTH}.md").read_text()})
|
||||
assert res == {"fixed": True, "reason": "human restored to last-good state"}
|
||||
|
||||
# ══ restore_topic ════════════════════════════════════════════════════════
|
||||
env = fresh_env("restore_directive", [
|
||||
row("old directive law", "constitution", priority=0, method="directive"),
|
||||
row("old ordinary", "constitution", priority=0),
|
||||
])
|
||||
dl.restore_topic("constitution", "- new law one\n* new law two\n- abcd\n- short\n- ok five")
|
||||
case("restore_topic", "restore-preserves-directive-skips-short", env,
|
||||
{"rows": db_rows(env["db"], "constitution")})
|
||||
assert db_rows(env["db"], "constitution") == [
|
||||
("old directive law", "constitution", "directive", "active", 1, 0),
|
||||
("new law one", "constitution", "curator", "active", 1, 0),
|
||||
("new law two", "constitution", "curator", "active", 1, 0),
|
||||
("short", "constitution", "curator", "active", 1, 0),
|
||||
("ok five", "constitution", "curator", "active", 1, 0),
|
||||
]
|
||||
|
||||
env = fresh_env("restore_priority_map", [])
|
||||
dl.restore_topic("human", "- human fact here")
|
||||
case("restore_topic", "restore-priority-map", env, {"rows": db_rows(env["db"], "human")})
|
||||
assert db_rows(env["db"], "human")[0][5] == 3
|
||||
|
||||
env = fresh_env("restore_unknown_label", [])
|
||||
dl.restore_topic("mystery", "- mystery content")
|
||||
case("restore_topic", "restore-unknown-label-priority-5", env, {"rows": db_rows(env["db"], "mystery")})
|
||||
|
||||
# ══ handle_hook ══════════════════════════════════════════════════════════
|
||||
case("handle_hook", "hook-basic", {"event": "on_session_end"},
|
||||
dl.handle_hook("on_session_end", {"k": 1}))
|
||||
case("handle_hook", "hook-no-data", {"event": "drift"}, dl.handle_hook("drift"))
|
||||
|
||||
# ══ main ═════════════════════════════════════════════════════════════════
|
||||
env = fresh_env("main_hook")
|
||||
argv = sys.argv
|
||||
sys.argv = ["drift_ledger.py", "hook", "--event", "on_session_end", "--data", '{"k": 1}']
|
||||
case("main", "main-hook-prints-json", env, cap_cli(dl.main))
|
||||
sys.argv = ["drift_ledger.py", "fix"]
|
||||
case("main", "main-fix-missing-label-exit-1", env, cap_cli(dl.main))
|
||||
sys.argv = ["drift_ledger.py", "anchor"]
|
||||
case("main", "main-anchor-missing-label-exit-1", env, cap_cli(dl.main))
|
||||
sys.argv = argv
|
||||
|
||||
env = fresh_env("main_snapshot", [row("cli proj", "project")])
|
||||
sys.argv = ["drift_ledger.py", "snapshot"]
|
||||
res = cap_cli(dl.main)
|
||||
case("main", "main-snapshot-exit-0", env,
|
||||
{**res, "ledger": json.loads(Path(env["ledger"]).read_text())})
|
||||
assert isinstance(json.loads(res["stdout"]), dict) and res["rc"] == 0
|
||||
sys.argv = ["drift_ledger.py", "anchor", "project", "--strict"]
|
||||
res = cap_cli(dl.main)
|
||||
case("main", "main-anchor-default-reason", env,
|
||||
{**res, "ledger": json.loads(Path(env["ledger"]).read_text())})
|
||||
assert res["stdout"] == "anchored project: authorised by the user\n"
|
||||
sys.argv = ["drift_ledger.py", "check"]
|
||||
res = cap_cli(dl.main)
|
||||
case("main", "main-check-healthy-exit-0", env, res)
|
||||
assert res["rc"] == 0
|
||||
sys.argv = argv
|
||||
|
||||
# ══ kernel: snapshot_entry ═══════════════════════════════════════════════
|
||||
BLOCKS_EMPTY = {label: "" for label in dl.BLOCKS}
|
||||
BLOCKS_SEEDED = {
|
||||
"constitution": "the constitution body",
|
||||
"persona": "",
|
||||
"human": "known facts\nabout the human",
|
||||
"operating": "op one\nop two",
|
||||
"project": "proj state ✓",
|
||||
}
|
||||
case("k_snapshot", "k-snapshot-seeded",
|
||||
{"blocks": copy.deepcopy(BLOCKS_SEEDED), "when": CLOCK_TS},
|
||||
dl._snapshot_entry(copy.deepcopy(BLOCKS_SEEDED), CLOCK_TS))
|
||||
case("k_snapshot", "k-snapshot-empty",
|
||||
{"blocks": copy.deepcopy(BLOCKS_EMPTY), "when": CLOCK_TS},
|
||||
dl._snapshot_entry(copy.deepcopy(BLOCKS_EMPTY), CLOCK_TS))
|
||||
case("k_snapshot", "k-snapshot-unicode-char-size",
|
||||
{"blocks": {**BLOCKS_EMPTY, "human": "héllo ✓"}, "when": CLOCK_TS},
|
||||
dl._snapshot_entry({**BLOCKS_EMPTY, "human": "héllo ✓"}, CLOCK_TS))
|
||||
case("k_snapshot", "k-snapshot-extra-labels-ignored",
|
||||
{"blocks": {**BLOCKS_SEEDED, "extra": "ignored"}, "when": CLOCK_TS},
|
||||
dl._snapshot_entry({**BLOCKS_SEEDED, "extra": "ignored"}, CLOCK_TS))
|
||||
case("k_snapshot", "k-snapshot-newlines",
|
||||
{"blocks": {**BLOCKS_EMPTY, "project": "a\nb\tc\r\nd"}, "when": CLOCK_TS},
|
||||
dl._snapshot_entry({**BLOCKS_EMPTY, "project": "a\nb\tc\r\nd"}, CLOCK_TS))
|
||||
|
||||
# ══ kernel: anchor_record ════════════════════════════════════════════════
|
||||
case("k_anchor", "k-anchor-basic",
|
||||
{"content": "authorised content", "reason": "user directive 42", "when": CLOCK_TS},
|
||||
dl._anchor_record("authorised content", "user directive 42", CLOCK_TS))
|
||||
case("k_anchor", "k-anchor-unicode-empty",
|
||||
{"content": "", "reason": "π ✓", "when": CLOCK_TS},
|
||||
dl._anchor_record("", "π ✓", CLOCK_TS))
|
||||
|
||||
# ══ kernel: check_alerts ═════════════════════════════════════════════════
|
||||
EMPTY_BLOCKS = {label: "" for label in dl.BLOCKS}
|
||||
|
||||
def alert_ledger(anchors=None, snap_blocks=None):
|
||||
ledger = {}
|
||||
if anchors is not None:
|
||||
ledger["anchors"] = anchors
|
||||
if snap_blocks is not None:
|
||||
ledger["last_snapshot"] = {"when": CLOCK_TS, "blocks": snap_blocks}
|
||||
return ledger
|
||||
|
||||
# unanchored growth over the limit
|
||||
case("k_alerts", "k-alerts-unanchored-over-limit",
|
||||
{"ledger": alert_ledger(snap_blocks={"project": {"size": 100, "content": "old\nlines"}}),
|
||||
"blocks": {**EMPTY_BLOCKS, "project": "old\nlines\n" + "x" * 60}},
|
||||
dl._check_alerts(alert_ledger(snap_blocks={"project": {"size": 100, "content": "old\nlines"}}),
|
||||
{**EMPTY_BLOCKS, "project": "old\nlines\n" + "x" * 60}))
|
||||
# anchored growth within the limit
|
||||
_anchor = {"sha": "a" * 64, "size": 100, "content": "base\ncontent"}
|
||||
case("k_alerts", "k-alerts-anchored-within-limit",
|
||||
{"ledger": alert_ledger(anchors={"operating": _anchor}),
|
||||
"blocks": {**EMPTY_BLOCKS, "operating": "base\ncontent\nplus a bit"}},
|
||||
dl._check_alerts(alert_ledger(anchors={"operating": dict(_anchor)}),
|
||||
{**EMPTY_BLOCKS, "operating": "base\ncontent\nplus a bit"}))
|
||||
# anchored growth over the limit with root-cause diff
|
||||
case("k_alerts", "k-alerts-anchored-over-limit-diff",
|
||||
{"ledger": alert_ledger(anchors={"project": _anchor}),
|
||||
"blocks": {**EMPTY_BLOCKS, "project": "base\ncontent\n" + "y" * 80}},
|
||||
dl._check_alerts(alert_ledger(anchors={"project": dict(_anchor)}),
|
||||
{**EMPTY_BLOCKS, "project": "base\ncontent\n" + "y" * 80}))
|
||||
# constitution content drift: any change trips, even tiny
|
||||
case("k_alerts", "k-alerts-constitution-content-drift",
|
||||
{"ledger": alert_ledger(anchors={"constitution": _anchor}),
|
||||
"blocks": {**EMPTY_BLOCKS, "constitution": "base\ncontent\n!"}},
|
||||
dl._check_alerts(alert_ledger(anchors={"constitution": dict(_anchor)}),
|
||||
{**EMPTY_BLOCKS, "constitution": "base\ncontent\n!"}))
|
||||
# constitution size drift without content change -> pct path only
|
||||
case("k_alerts", "k-alerts-constitution-no-anchor-sha-pct-path",
|
||||
{"ledger": alert_ledger(anchors={"constitution": {"sha": "b" * 64, "size": 10, "content": "tiny"}}),
|
||||
"blocks": {**EMPTY_BLOCKS, "constitution": "tiny"}},
|
||||
dl._check_alerts(alert_ledger(anchors={"constitution": {"sha": "b" * 64, "size": 10, "content": "tiny"}}),
|
||||
{**EMPTY_BLOCKS, "constitution": "tiny"}))
|
||||
# empty ledger: nothing to measure against
|
||||
case("k_alerts", "k-alerts-empty-ledger-quiet",
|
||||
{"ledger": {}, "blocks": EMPTY_BLOCKS},
|
||||
dl._check_alerts({}, dict(EMPTY_BLOCKS)))
|
||||
# negative delta (shrink) measured against prev
|
||||
case("k_alerts", "k-alerts-shrink-over-limit",
|
||||
{"ledger": alert_ledger(snap_blocks={"human": {"size": 100, "content": "lots of text"}}),
|
||||
"blocks": {**EMPTY_BLOCKS, "human": "short"}},
|
||||
dl._check_alerts(alert_ledger(snap_blocks={"human": {"size": 100, "content": "lots of text"}}),
|
||||
{**EMPTY_BLOCKS, "human": "short"}))
|
||||
# rounding: 3 -> 4 chars, pct 1/3
|
||||
case("k_alerts", "k-alerts-rounds-pct",
|
||||
{"ledger": alert_ledger(snap_blocks={"human": {"size": 3, "content": "abc"}}),
|
||||
"blocks": {**EMPTY_BLOCKS, "human": "abcd"}},
|
||||
dl._check_alerts(alert_ledger(snap_blocks={"human": {"size": 3, "content": "abc"}}),
|
||||
{**EMPTY_BLOCKS, "human": "abcd"}))
|
||||
# multi-alert BLOCKS order
|
||||
_multi = alert_ledger(snap_blocks={"persona": {"size": 5, "content": "p one"},
|
||||
"human": {"size": 5, "content": "h one"}})
|
||||
case("k_alerts", "k-alerts-multi-blocks-order",
|
||||
{"ledger": _multi,
|
||||
"blocks": {**EMPTY_BLOCKS, "persona": "p one grown", "human": "h one grown"}},
|
||||
dl._check_alerts(alert_ledger({"persona": {"size": 5, "content": "p one"},
|
||||
"human": {"size": 5, "content": "h one"}}),
|
||||
{**EMPTY_BLOCKS, "persona": "p one grown", "human": "h one grown"}))
|
||||
|
||||
# ══ kernel: auto_fix_decision ════════════════════════════════════════════
|
||||
for i, (label, anchored, has_good) in enumerate([
|
||||
("constitution", False, False),
|
||||
("constitution", True, True),
|
||||
("operating", True, False),
|
||||
("persona", False, True),
|
||||
("human", False, False),
|
||||
]):
|
||||
case("k_auto_fix", f"k-auto-fix-{i}-{label}-{int(anchored)}{int(has_good)}",
|
||||
{"label": label, "anchored": anchored, "has_last_good": has_good},
|
||||
dl._auto_fix_decision(label, anchored, has_good))
|
||||
|
||||
# ══ kernel: fix_decision ═════════════════════════════════════════════════
|
||||
_fix_ledger = {"last_snapshot": {"when": CLOCK_TS, "blocks": {
|
||||
"project": {"sha": "c" * 64, "size": 10, "content": "good content"},
|
||||
"human": {"size": 4, "content": "no sha key"},
|
||||
}}, "anchors": {"constitution": {"sha": "d" * 64}}}
|
||||
case("k_fix", "k-fix-no-last-good", {"ledger": {}, "label": "human", "cur_sha": "a" * 64},
|
||||
dl._fix_decision({}, "human", "a" * 64))
|
||||
case("k_fix", "k-fix-anchored-refuses", {"ledger": copy.deepcopy(_fix_ledger), "label": "constitution", "cur_sha": "x"},
|
||||
dl._fix_decision(copy.deepcopy(_fix_ledger), "constitution", "x"))
|
||||
case("k_fix", "k-fix-sha-matches", {"ledger": copy.deepcopy(_fix_ledger), "label": "project", "cur_sha": "c" * 64},
|
||||
dl._fix_decision(copy.deepcopy(_fix_ledger), "project", "c" * 64))
|
||||
case("k_fix", "k-fix-restores", {"ledger": copy.deepcopy(_fix_ledger), "label": "project", "cur_sha": "e" * 64},
|
||||
dl._fix_decision(copy.deepcopy(_fix_ledger), "project", "e" * 64))
|
||||
case("k_fix", "k-fix-meta-without-sha", {"ledger": copy.deepcopy(_fix_ledger), "label": "human", "cur_sha": "z"},
|
||||
dl._fix_decision(copy.deepcopy(_fix_ledger), "human", "z"))
|
||||
|
||||
# ══ write ════════════════════════════════════════════════════════════════
|
||||
truth = {
|
||||
"frozen": "drift_ledger sha/diff/ledger_path/load_ledger/save_ledger/"
|
||||
"read_block/journal/surface_alert/snapshot/anchor/check/"
|
||||
"auto_fix/fix/restore_topic/handle_hook/main",
|
||||
"oracle": "src/rune/drift_ledger.py",
|
||||
"note": "Clock frozen at 2031-01-01T00:00:00+00:00 (dl.datetime seam); "
|
||||
"store/journal/status paths isolated per case via INDEX_FILE/DB/"
|
||||
"JOURNAL_DIR module globals and the memory_env.memory_dir() seam. "
|
||||
"SQLite read/write seams and file IO stay in the facade by design; "
|
||||
"the deterministic cores (sha, diff, alert loop, snapshot/anchor "
|
||||
"assembly, auto-fix routing, fix decision, ledger path) cross the "
|
||||
"doc-envelope FFI.",
|
||||
}
|
||||
for gname in GROUPS:
|
||||
truth[gname] = groups[gname]
|
||||
|
||||
out = Path(os.environ.get("DL_TRUTH_OUT", "/tmp/dl_truth.json"))
|
||||
out.write_text(json.dumps(truth, indent=1, ensure_ascii=False) + "\n")
|
||||
counts = ", ".join(f"{len(groups[g])} {g}" for g in GROUPS)
|
||||
print(f"froze {sum(len(v) for v in groups.values())} cases -> {out}: {counts}")
|
||||
+43
-2
@@ -1,12 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust drift_ledger core (dichotomy debt queue
|
||||
batch 3, #2479 tranche). Mirrors broker_loader.py's contract:
|
||||
batch 3 + #2479 dichotomy wave tranche). Mirrors broker_loader.py's contract:
|
||||
|
||||
FFI contract (doc-envelope law):
|
||||
* dl_sha({text}) -> hex
|
||||
* dl_diff({ref, cur}) -> summary string
|
||||
* dl_check({label, prev_size, cur_size, anchor_size?, anchor_sha?, cur_sha?}) -> {alert, pct, limit}
|
||||
* dl_auto_fix({label, anchored, has_last_good}) -> {action, reason}
|
||||
* dl_ledger_path({index_file}) -> path string
|
||||
* dl_snapshot_entry({blocks: {label: content}, when}) -> snapshot entry
|
||||
* dl_anchor_record({content, reason, when}) -> anchor record
|
||||
* dl_check_alerts({ledger, blocks: {label: content}}) -> [alert, ...]
|
||||
* dl_fix_decision({ledger, label, cur_sha?}) -> {fixed, reason}
|
||||
|
||||
Override the .so with DL_RUST_LIB (a FILE path).
|
||||
"""
|
||||
@@ -39,7 +44,9 @@ def _load() -> ctypes.CDLL:
|
||||
if not _LIB_PATH.is_file():
|
||||
raise DriftLedgerUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in ("dl_sha", "dl_diff", "dl_check", "dl_auto_fix"):
|
||||
for sym in ("dl_sha", "dl_diff", "dl_check", "dl_auto_fix", "dl_ledger_path",
|
||||
"dl_snapshot_entry", "dl_anchor_record", "dl_check_alerts",
|
||||
"dl_fix_decision"):
|
||||
fn = getattr(lib, sym)
|
||||
fn.argtypes = [ctypes.c_void_p]
|
||||
fn.restype = ctypes.c_void_p
|
||||
@@ -75,3 +82,37 @@ def sha(text: str) -> str:
|
||||
|
||||
def diff_blocks(ref: str, cur: str) -> str:
|
||||
return _call("dl_diff", {"ref": ref, "cur": cur})
|
||||
|
||||
|
||||
def check_block(label: str, prev_size: int, cur_size: int, anchor_size=None,
|
||||
anchor_sha=None, cur_sha=None) -> dict:
|
||||
return _call("dl_check", {"label": label, "prev_size": prev_size,
|
||||
"cur_size": cur_size, "anchor_size": anchor_size,
|
||||
"anchor_sha": anchor_sha, "cur_sha": cur_sha})
|
||||
|
||||
|
||||
def auto_fix_decision(label: str, anchored: bool, has_last_good: bool) -> dict:
|
||||
return _call("dl_auto_fix", {"label": label, "anchored": anchored,
|
||||
"has_last_good": has_last_good})
|
||||
|
||||
|
||||
def ledger_path_for(index_file: str) -> str:
|
||||
return _call("dl_ledger_path", {"index_file": index_file})
|
||||
|
||||
|
||||
def snapshot_entry(blocks: dict, when: str) -> dict:
|
||||
return _call("dl_snapshot_entry", {"blocks": blocks, "when": when})
|
||||
|
||||
|
||||
def anchor_record(content: str, reason: str, when: str) -> dict:
|
||||
return _call("dl_anchor_record", {"content": content, "reason": reason,
|
||||
"when": when})
|
||||
|
||||
|
||||
def check_alerts(ledger: dict, blocks: dict) -> list:
|
||||
return _call("dl_check_alerts", {"ledger": ledger, "blocks": blocks})
|
||||
|
||||
|
||||
def fix_decision(ledger: dict, label: str, cur_sha=None) -> dict:
|
||||
return _call("dl_fix_decision", {"ledger": ledger, "label": label,
|
||||
"cur_sha": cur_sha})
|
||||
+135
-58
@@ -48,6 +48,13 @@ DB = memory_env.store_db()
|
||||
def ledger_path():
|
||||
# The ledger lives beside the index, so overriding INDEX_FILE (as the
|
||||
# curator gate and the canary do for isolation) moves the ledger too.
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
return dl_loader.ledger_path_for(INDEX_FILE)
|
||||
except Exception:
|
||||
pass # fallback below
|
||||
return os.path.join(os.path.dirname(INDEX_FILE), "drift_ledger.json")
|
||||
|
||||
|
||||
@@ -63,9 +70,15 @@ CONSTITUTION_LIMIT = 0.05 # 5% — anything more than a directive entry is drif
|
||||
BLOCKS = ("constitution", "persona", "human", "operating", "project")
|
||||
|
||||
|
||||
def _rust_core():
|
||||
"""RUNE_DL_CORE pin law: 'python' forces the oracle path, anything else
|
||||
(default 'auto') serves the Rust core when the cdylib is built."""
|
||||
return os.environ.get("RUNE_DL_CORE", "auto") != "python"
|
||||
|
||||
|
||||
def _sha(text):
|
||||
# Dichotomy batch 3: Rust core first (RUNE_DL_CORE=python pins fallback).
|
||||
if os.environ.get("RUNE_DL_CORE", "auto") != "python":
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
@@ -155,51 +168,54 @@ def surface_alert(msg, label, changed=""):
|
||||
logger.warning("Failed to write alert file: %s", e)
|
||||
|
||||
|
||||
def snapshot():
|
||||
ledger = load_ledger()
|
||||
now = datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
def _snapshot_entry(blocks, now):
|
||||
"""Dichotomy wave: Rust core first (RUNE_DL_CORE=python pins fallback)."""
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
return dl_loader.snapshot_entry(blocks, now)
|
||||
except Exception:
|
||||
pass # fallback below
|
||||
entry = {"when": now, "blocks": {}}
|
||||
for label in BLOCKS:
|
||||
raw = read_block(label)
|
||||
raw = blocks[label]
|
||||
entry["blocks"][label] = {
|
||||
"sha": _sha(raw),
|
||||
"size": len(raw),
|
||||
"content": raw, # last-good content backup for diffing + retro-fix
|
||||
}
|
||||
ledger["last_snapshot"] = entry
|
||||
save_ledger(ledger)
|
||||
return entry
|
||||
|
||||
|
||||
def anchor(label, reason):
|
||||
"""Record that the current state of `label` is authorised (a user directive,
|
||||
a confirmed review, a 3-sighting promotion). Subsequent drift is measured
|
||||
from here — this is the re-anchor after legitimate change."""
|
||||
ledger = load_ledger()
|
||||
raw = read_block(label)
|
||||
ledger.setdefault("anchors", {})[label] = {
|
||||
def _anchor_record(raw, reason, now):
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
return dl_loader.anchor_record(raw, reason, now)
|
||||
except Exception:
|
||||
pass # fallback below
|
||||
return {
|
||||
"sha": _sha(raw),
|
||||
"size": len(raw),
|
||||
"content": raw, # authorised content backup for root-cause diffing
|
||||
"reason": reason,
|
||||
"when": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
||||
"when": now,
|
||||
}
|
||||
save_ledger(ledger)
|
||||
return f"anchored {label}: {reason}"
|
||||
|
||||
|
||||
def check(strict=False, autofix=True):
|
||||
ledger = load_ledger()
|
||||
snap = ledger.get("last_snapshot")
|
||||
if not snap:
|
||||
# No baseline yet — record one and treat as healthy (first contact).
|
||||
snapshot()
|
||||
print("no prior snapshot; baseline recorded")
|
||||
return 0
|
||||
|
||||
def _check_alerts(ledger, now_blocks):
|
||||
"""The check() alert loop: Rust core first, oracle loop as fallback."""
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
return dl_loader.check_alerts(ledger, now_blocks)
|
||||
except Exception:
|
||||
pass # fallback below
|
||||
alerts = []
|
||||
now_blocks = {label: read_block(label) for label in BLOCKS}
|
||||
prev = snap.get("blocks", {})
|
||||
prev = (ledger.get("last_snapshot") or {}).get("blocks", {})
|
||||
for label in BLOCKS:
|
||||
cur = now_blocks[label]
|
||||
cur_size = len(cur)
|
||||
@@ -245,6 +261,41 @@ def check(strict=False, autofix=True):
|
||||
"content_changed": content_changed,
|
||||
}
|
||||
)
|
||||
return alerts
|
||||
|
||||
|
||||
def snapshot():
|
||||
ledger = load_ledger()
|
||||
now = datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
entry = _snapshot_entry({label: read_block(label) for label in BLOCKS}, now)
|
||||
ledger["last_snapshot"] = entry
|
||||
save_ledger(ledger)
|
||||
return entry
|
||||
|
||||
|
||||
def anchor(label, reason):
|
||||
"""Record that the current state of `label` is authorised (a user directive,
|
||||
a confirmed review, a 3-sighting promotion). Subsequent drift is measured
|
||||
from here — this is the re-anchor after legitimate change."""
|
||||
ledger = load_ledger()
|
||||
raw = read_block(label)
|
||||
ledger.setdefault("anchors", {})[label] = _anchor_record(
|
||||
raw, reason, datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
)
|
||||
save_ledger(ledger)
|
||||
return f"anchored {label}: {reason}"
|
||||
|
||||
|
||||
def check(strict=False, autofix=True):
|
||||
ledger = load_ledger()
|
||||
snap = ledger.get("last_snapshot")
|
||||
if not snap:
|
||||
# No baseline yet — record one and treat as healthy (first contact).
|
||||
snapshot()
|
||||
print("no prior snapshot; baseline recorded")
|
||||
return 0
|
||||
|
||||
alerts = _check_alerts(ledger, {label: read_block(label) for label in BLOCKS})
|
||||
if alerts:
|
||||
for a in alerts:
|
||||
msg = (
|
||||
@@ -287,27 +338,23 @@ def auto_fix(label, ledger, strict):
|
||||
|
||||
Returns {action, reason}.
|
||||
"""
|
||||
if label == "constitution":
|
||||
return {"action": "alert-only", "reason": "constitution changes only by explicit directive; needs human review"}
|
||||
if label in ledger.get("anchors", {}):
|
||||
block_meta = ledger.get("last_snapshot", {}).get("blocks", {}).get(label)
|
||||
anchored = label in ledger.get("anchors", {})
|
||||
has_last_good = bool(block_meta and "content" in block_meta)
|
||||
result = _auto_fix_decision(label, anchored, has_last_good)
|
||||
if result["action"] == "re-anchored":
|
||||
# Authorised growth: re-anchor to the current (approved) state.
|
||||
raw = read_block(label)
|
||||
ledger.setdefault("anchors", {})[label] = {
|
||||
"sha": _sha(raw),
|
||||
"size": len(raw),
|
||||
"content": raw,
|
||||
"reason": "auto re-anchor after authorised growth",
|
||||
"when": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
||||
}
|
||||
ledger.setdefault("anchors", {})[label] = _anchor_record(
|
||||
raw, "auto re-anchor after authorised growth",
|
||||
datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
||||
)
|
||||
save_ledger(ledger)
|
||||
return {"action": "re-anchored", "reason": "anchored block grew (authorised) — re-baselined"}
|
||||
# Unauthorised drift: restore to last-good content (new schema — restore
|
||||
# the store's always-on core entries for this topic).
|
||||
block_meta = ledger.get("last_snapshot", {}).get("blocks", {}).get(label)
|
||||
if block_meta and "content" in block_meta:
|
||||
elif result["action"] == "restored":
|
||||
# Unauthorised drift: restore to last-good content (new schema —
|
||||
# restore the store's always-on core entries for this topic).
|
||||
restore_topic(label, block_meta["content"])
|
||||
return {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
|
||||
return {"action": "alert-only", "reason": "no last-good content available to restore"}
|
||||
return result
|
||||
|
||||
|
||||
def diff_blocks(ref, cur):
|
||||
@@ -379,6 +426,44 @@ def restore_topic(label, content):
|
||||
logger.error("restore_topic('%s') failed: %s", label, e)
|
||||
|
||||
|
||||
def _auto_fix_decision(label, anchored, has_last_good):
|
||||
"""The auto-fix ROUTING decision (IO execution stays in auto_fix)."""
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
return dl_loader.auto_fix_decision(label, anchored, has_last_good)
|
||||
except Exception:
|
||||
pass # fallback below
|
||||
if label == "constitution":
|
||||
return {"action": "alert-only", "reason": "constitution changes only by explicit directive; needs human review"}
|
||||
if anchored:
|
||||
return {"action": "re-anchored", "reason": "anchored block grew (authorised) — re-baselined"}
|
||||
if has_last_good:
|
||||
return {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
|
||||
return {"action": "alert-only", "reason": "no last-good content available to restore"}
|
||||
|
||||
|
||||
def _fix_decision(ledger, label, cur_sha):
|
||||
"""The retroactive-fix gate decision (restore IO stays in fix)."""
|
||||
if _rust_core():
|
||||
try:
|
||||
from rune import dl_loader
|
||||
if dl_loader.available():
|
||||
return dl_loader.fix_decision(ledger, label, cur_sha)
|
||||
except Exception:
|
||||
pass # fallback below
|
||||
snap = ledger.get("last_snapshot", {})
|
||||
block_meta = snap.get("blocks", {}).get(label)
|
||||
if not block_meta or "content" not in block_meta:
|
||||
return {"fixed": False, "reason": f"no last-good content for {label}"}
|
||||
if label in ledger.get("anchors", {}):
|
||||
return {"fixed": False, "reason": f"{label} is anchored (authorised); use anchor to re-baseline instead of fix"}
|
||||
if cur_sha == block_meta.get("sha"):
|
||||
return {"fixed": False, "reason": f"{label} already matches last-good"}
|
||||
return {"fixed": True, "reason": f"{label} restored to last-good state"}
|
||||
|
||||
|
||||
def fix(label):
|
||||
"""RETROACTIVE FIX (#B): restore a drifted block to its last-good content.
|
||||
|
||||
@@ -390,20 +475,12 @@ def fix(label):
|
||||
ledger = load_ledger()
|
||||
snap = ledger.get("last_snapshot", {})
|
||||
block_meta = snap.get("blocks", {}).get(label)
|
||||
if not block_meta or "content" not in block_meta:
|
||||
return {"fixed": False, "reason": f"no last-good content for {label}"}
|
||||
# Never silently revert an anchored block — it was authorised.
|
||||
if label in ledger.get("anchors", {}):
|
||||
return {"fixed": False, "reason": f"{label} is anchored (authorised); use anchor to re-baseline instead of fix"}
|
||||
good = block_meta["content"]
|
||||
cur = read_block(label)
|
||||
if _sha(cur) == block_meta["sha"]:
|
||||
return {"fixed": False, "reason": f"{label} already matches last-good"}
|
||||
# New schema: restore the store's always-on entries for the topic (the old
|
||||
# markdown block files are retired).
|
||||
restore_topic(label, good)
|
||||
journal(f"{label} restored to last-good content (retroactive drift fix)")
|
||||
return {"fixed": True, "reason": f"{label} restored to last-good state"}
|
||||
result = _fix_decision(ledger, label, _sha(cur))
|
||||
if result.get("fixed"):
|
||||
restore_topic(label, block_meta["content"])
|
||||
journal(f"{label} restored to last-good content (retroactive drift fix)")
|
||||
return result
|
||||
|
||||
|
||||
def handle_hook(event, data=None):
|
||||
|
||||
Generated
+23
@@ -54,6 +54,12 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -64,6 +70,22 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -135,6 +157,7 @@ version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
|
||||
@@ -8,4 +8,4 @@ crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
sha2 = "0.10"
|
||||
serde_json = "1"
|
||||
serde_json = { version = "1", features = ["preserve_order", "float_roundtrip"] }
|
||||
File diff suppressed because it is too large.
Load diff
@@ -111,6 +111,140 @@ pub fn auto_fix_decision(label: &str, anchored: bool, has_last_good: bool) -> Va
|
||||
}
|
||||
}
|
||||
|
||||
/// Python round(x, 3): correct rounding of the binary value with ties-to-even
|
||||
/// (the same contract Rust's {:.3} formatting implements).
|
||||
fn py_round3(x: f64) -> f64 {
|
||||
format!("{:.3}", x).parse().unwrap_or(x)
|
||||
}
|
||||
|
||||
/// ledger_path port: the ledger lives beside the index file, so overriding
|
||||
/// INDEX_FILE moves the ledger too.
|
||||
pub fn ledger_path_for(index_file: &str) -> String {
|
||||
match index_file.rfind('/') {
|
||||
Some(i) => format!("{}drift_ledger.json", &index_file[..=i]),
|
||||
None => "drift_ledger.json".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// snapshot() entry-assembly port: {"when", "blocks": {label: {sha, size,
|
||||
/// content}}}. "size" is the oracle's len(raw) — a CHARACTER count, not bytes.
|
||||
pub fn snapshot_entry(blocks: &Value, when: &str) -> Value {
|
||||
let mut b = serde_json::Map::new();
|
||||
for label in BLOCKS {
|
||||
let raw = blocks.get(*label).and_then(|v| v.as_str()).unwrap_or("");
|
||||
b.insert(
|
||||
(*label).to_string(),
|
||||
json!({
|
||||
"sha": sha(raw),
|
||||
"size": raw.chars().count(),
|
||||
"content": raw,
|
||||
}),
|
||||
);
|
||||
}
|
||||
json!({"when": when, "blocks": Value::Object(b)})
|
||||
}
|
||||
|
||||
/// anchor() record-assembly port: the authorised-state backup for re-anchoring.
|
||||
pub fn anchor_record(content: &str, reason: &str, when: &str) -> Value {
|
||||
json!({
|
||||
"sha": sha(content),
|
||||
"size": content.chars().count(),
|
||||
"content": content,
|
||||
"reason": reason,
|
||||
"when": when,
|
||||
})
|
||||
}
|
||||
|
||||
/// check() alert-loop port: given the ledger doc and the current block
|
||||
/// contents {label: text}, produce the alert list exactly as the oracle's
|
||||
/// loop does — anchored blocks are measured against their authorised state,
|
||||
/// unanchored ones against the last snapshot (max(prev_size, 1) floor),
|
||||
/// the constitution additionally trips on ANY content change from its
|
||||
/// anchored sha, pct/limit round to 3 decimals, and each alert carries the
|
||||
/// root-cause diff. Side effects (prints, surface_alert, auto-fix, journal)
|
||||
/// stay in the facade.
|
||||
pub fn check_alerts(ledger: &Value, blocks: &Value) -> Value {
|
||||
let prev = ledger
|
||||
.get("last_snapshot")
|
||||
.and_then(|s| s.get("blocks"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| json!({}));
|
||||
let anchors = ledger.get("anchors").cloned().unwrap_or_else(|| json!({}));
|
||||
let mut alerts: Vec<Value> = vec![];
|
||||
for label in BLOCKS {
|
||||
let cur = blocks.get(*label).and_then(|v| v.as_str()).unwrap_or("");
|
||||
let cur_size = cur.chars().count() as i64;
|
||||
let anchor_meta = anchors.get(*label);
|
||||
let (base, base_sha, delta, ref_content) = match anchor_meta {
|
||||
Some(a) => {
|
||||
let b = a.get("size").and_then(|v| v.as_i64()).unwrap_or(cur_size);
|
||||
let sh = a.get("sha").and_then(|v| v.as_str());
|
||||
let rc = a.get("content").and_then(|v| v.as_str());
|
||||
(b, sh, cur_size - b, rc)
|
||||
}
|
||||
None => {
|
||||
let pm = prev.get(*label);
|
||||
let ps = pm
|
||||
.and_then(|m| m.get("size"))
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(cur_size);
|
||||
let rc = pm.and_then(|m| m.get("content")).and_then(|v| v.as_str());
|
||||
(ps.max(1), None, cur_size - ps, rc)
|
||||
}
|
||||
};
|
||||
let pct = (delta.abs() as f64) / (base.max(1) as f64);
|
||||
let limit = if *label == "constitution" { CONSTITUTION_LIMIT } else { DRIFT_LIMIT };
|
||||
let content_changed = *label == "constitution"
|
||||
&& anchor_meta.is_some()
|
||||
&& base_sha.is_some()
|
||||
&& sha(cur) != base_sha.unwrap();
|
||||
if content_changed || pct > limit {
|
||||
alerts.push(json!({
|
||||
"label": label,
|
||||
"delta": delta,
|
||||
"pct": py_round3(pct),
|
||||
"limit": py_round3(limit),
|
||||
"base": base,
|
||||
"changed": diff_blocks(ref_content.unwrap_or(""), cur),
|
||||
"content_changed": content_changed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Value::Array(alerts)
|
||||
}
|
||||
|
||||
/// fix() decision port: the oracle's retroactive-fix gate minus the restore IO
|
||||
/// (the facade performs restore_topic + journal when {fixed: true}).
|
||||
/// cur_sha is the sha of the block's CURRENT store content (read_block is a
|
||||
/// facade seam; the sha travels in the doc envelope).
|
||||
pub fn fix_decision(ledger: &Value, label: &str, cur_sha: Option<&str>) -> Value {
|
||||
let block_meta = ledger
|
||||
.get("last_snapshot")
|
||||
.and_then(|s| s.get("blocks"))
|
||||
.and_then(|b| b.get(label));
|
||||
let has_content = block_meta
|
||||
.map(|m| m.get("content").is_some())
|
||||
.unwrap_or(false);
|
||||
if block_meta.is_none() || !has_content {
|
||||
return json!({"fixed": false, "reason": format!("no last-good content for {}", label)});
|
||||
}
|
||||
if ledger
|
||||
.get("anchors")
|
||||
.map(|a| a.get(label).is_some())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return json!({"fixed": false, "reason": format!("{} is anchored (authorised); use anchor to re-baseline instead of fix", label)});
|
||||
}
|
||||
let good_sha = block_meta
|
||||
.and_then(|m| m.get("sha"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
if cur_sha == Some(good_sha) {
|
||||
return json!({"fixed": false, "reason": format!("{} already matches last-good", label)});
|
||||
}
|
||||
json!({"fixed": true, "reason": format!("{} restored to last-good state", label)})
|
||||
}
|
||||
|
||||
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
|
||||
if ptr.is_null() {
|
||||
return Err("null argument pointer".into());
|
||||
@@ -181,5 +315,54 @@ pub extern "C" fn dl_auto_fix(arg: *const c_char) -> *mut c_char {
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// dl_ledger_path({"index_file"}) -> path string
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dl_ledger_path(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| Value::String(ledger_path_for(s(a.get("index_file")))));
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// dl_snapshot_entry({"blocks": {label: content}, "when"}) -> snapshot entry
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dl_snapshot_entry(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let blocks = a.get("blocks").cloned().unwrap_or(Value::Null);
|
||||
snapshot_entry(&blocks, s(a.get("when")))
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// dl_anchor_record({"content", "reason", "when"}) -> anchor record
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dl_anchor_record(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
anchor_record(s(a.get("content")), s(a.get("reason")), s(a.get("when")))
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// dl_check_alerts({"ledger", "blocks": {label: content}}) -> [alert, ...]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dl_check_alerts(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let ledger = a.get("ledger").cloned().unwrap_or(Value::Null);
|
||||
let blocks = a.get("blocks").cloned().unwrap_or(Value::Null);
|
||||
check_alerts(&ledger, &blocks)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// dl_fix_decision({"ledger", "label", "cur_sha"?}) -> {fixed, reason}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dl_fix_decision(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let ledger = a.get("ledger").cloned().unwrap_or(Value::Null);
|
||||
let label = s(a.get("label")).to_string();
|
||||
let cur_sha = a.get("cur_sha").and_then(|v| v.as_str());
|
||||
fix_decision(&ledger, &label, cur_sha)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
// NOTE: no free-symbol export here (capability_scoring's cs_free_string
|
||||
// serves the whole process — duplicate symbols break the cdylib link).
|
||||
Reference in new issue
Block a user