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

parity: every public function of drift_resume ported branch-for-branch —
classify_drift (the 'matches is True' / 'matches is None' IDENTITY
checks — truthy non-True falls through to reality-differs; missing key
and explicit null both mean unverifiable), the stale_ok/user_locked
truthiness lattice, Python str() evidence renderings (str(None)='None',
str(True)='True', ints bare) with dict.get defaults applying ONLY on a
missing key (present-empty renders verbatim, present-null renders
'None'); reconcile (observer stays in the Python facade — the kernel
receives the observations; blocking-class filter preserves drift order,
resume = no blocking); detect_structural_drift (§18H: component filter,
the ext→identities falsy-source or-chain, identities.user_locked 'is
True' lock — truthy non-True is NOT locked, the exists check treats a
DIRECTORY source as present, moved-twin = first basename match over the
facade's sorted relative-path listing — unique-basename scenarios make
the oracle's rglob-first-match order-independent, evidence +
reconciliation strings exact). Replaces the old 11-line resume_state
stub crate (no consumers). Boundary law: the observer callable and the
filesystem walk stay in the facade; the kernel is a pure function of
their results. dr_loader mirrors dc_loader (doc-envelope FFI,
cs_free_string, DR_RUST_LIB override); facade delegation with
RUNE_DR_CORE=python pin. Battery: 3 tests replay 35 frozen-oracle cases
VERBATIM (include_str!). Live parity 35/35 facade + 35/35 kernel;
clippy 0; test_drift_resume + the §18H held-out suites green live AND
pinned (the one pre-existing failure on master, evidence_provenance
ep_loader FileNotFoundError, is unrelated to this module).
This commit is contained in:
Nick committed 2026-10-13 02:51:26 +01:00
1 parent 7b86d15e67
commit 29ce279702
8 files changed
+2244 -21

No files matched your search

+473
View File
@@ -0,0 +1,473 @@
#!/usr/bin/env python3
"""gen_drift_resume_truth.py — freeze/replay parity truth for drift_resume
(dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/drift_resume.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/drift_resume/src/frozen_dr_resume_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle functions delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_DR_CORE=python BEFORE any import.
Replay leaves it UNSET so delegation is live (the real parity claim);
the script aborts replay if the Rust core is not built.
Boundary law: the observer callable (reconcile) and the FILESYSTEM walk
(detect_structural_drift: exists + the moved-twin rglob) stay in the
Python facade — the kernel is a pure function of their results. Kernel
inputs:
* dr_classify_drift {claim, observation} → {record}
* dr_reconcile {claims, observations} → {resume, drifts, blocking}
* dr_detect_structural_drift {entities, files, dirs} → {drifts}
where files/dirs are the facade's SORTED relative listings of
repo_root (deterministic superset of the oracle's rglob order;
moved-twin scenarios use unique basenames so the oracle's
first-rglob-match is order-independent).
Deep-copy law: every input is deep-copied before it reaches the oracle
(the kernel must never observe mutation aliasing).
"""
import copy
import datetime
import json
import os
import pathlib
import sys
import tempfile
import types
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "drift_resume", "src",
"frozen_dr_resume_truth.json")
REPLAY = "--replay" in sys.argv
os.environ["RUNE_DR_CORE"] = "python" # poisoned-truth law (no-op pre-port,
# load-bearing post-port: the freeze must record the PYTHON oracle's truth)
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import drift_resume as oracle_mod # noqa: E402
if REPLAY:
del os.environ["RUNE_DR_CORE"] # delegation live for the replay pass
from rune import dr_loader # noqa: E402 (kernel-level replay side)
if not dr_loader.available():
print("REPLAY ABORT: Rust drift_resume core not built/available")
sys.exit(2)
# ── classify_drift ───────────────────────────────────────────────────────────
def _cd_case(rec, name, claim, observation):
got = oracle_mod.classify_drift(copy.deepcopy(claim),
copy.deepcopy(observation))
rec(name, {"claim": claim, "observation": observation}, got,
{"record": got})
def c_cd_matches_true_stale_ok(rec):
_cd_case(rec, "cd-matches-true-stale-ok-expected",
{"claim": "green", "stale_ok": True},
{"actual": "", "checked_by": "pytest", "matches": True})
def c_cd_matches_true_confirmed(rec):
_cd_case(rec, "cd-matches-true-confirmed",
{"claim": "green"},
{"actual": "", "checked_by": "pytest", "matches": True})
def c_cd_matches_true_stale_falsy(rec):
_cd_case(rec, "cd-matches-true-stale-falsy-confirmed",
{"claim": "green", "stale_ok": False},
{"actual": "", "checked_by": "pytest", "matches": True})
def c_cd_matches_true_stale_truthy_int(rec):
_cd_case(rec, "cd-matches-true-stale-truthy-int-expected",
{"claim": "green", "stale_ok": 1},
{"actual": "", "checked_by": "pytest", "matches": True})
def c_cd_matches_none_unknown(rec):
_cd_case(rec, "cd-matches-none-unknown",
{"claim": "x"},
{"actual": None, "checked_by": "pytest", "matches": None})
def c_cd_matches_missing_unknown(rec):
_cd_case(rec, "cd-matches-missing-unknown",
{"claim": "x"},
{"actual": None, "checked_by": "probe"})
def c_cd_matches_false_user_locked(rec):
_cd_case(rec, "cd-matches-false-user-locked-conflicting",
{"claim": "layout", "user_locked": True},
{"actual": "layout is Y", "checked_by": "ls",
"matches": False})
def c_cd_matches_false_material(rec):
_cd_case(rec, "cd-matches-false-material",
{"claim": "green"},
{"actual": "2 tests fail", "checked_by": "pytest",
"matches": False})
def c_cd_material_missing_actual(rec):
_cd_case(rec, "cd-material-actual-missing-empty",
{"claim": "green"},
{"checked_by": "pytest", "matches": False})
def c_cd_material_actual_null(rec):
# Python str(None) → "None" in the f-string
_cd_case(rec, "cd-material-actual-null-str-none",
{"claim": "green"},
{"actual": None, "checked_by": "pytest", "matches": False})
def c_cd_material_actual_int(rec):
_cd_case(rec, "cd-material-actual-int",
{"claim": "count"},
{"actual": 3, "checked_by": "wc", "matches": False})
def c_cd_unknown_checked_by_missing(rec):
_cd_case(rec, "cd-unknown-checked-by-missing-qmark",
{"claim": "x"},
{"matches": None})
def c_cd_unknown_checked_by_empty(rec):
# present-but-empty checked_by renders verbatim (get default only on
# a MISSING key)
_cd_case(rec, "cd-unknown-checked-by-empty-string",
{"claim": "x"},
{"checked_by": "", "matches": None})
def c_cd_claim_missing(rec):
_cd_case(rec, "cd-claim-key-missing-empty",
{},
{"actual": "", "checked_by": "pytest", "matches": True})
# ── reconcile ────────────────────────────────────────────────────────────────
def _rc_case(rec, name, claims, observer):
claims_c = copy.deepcopy(claims)
got = oracle_mod.reconcile(claims_c, observer)
observations = [observer(c) for c in claims_c]
rec(name, {"claims": claims_c, "observations": observations}, got, got)
def c_rc_empty(rec):
_rc_case(rec, "rc-empty-claims-resume", [], lambda c: {"matches": True})
def c_rc_all_confirmed(rec):
_rc_case(rec, "rc-all-confirmed-resume",
[{"claim": "a"}, {"claim": "b"}],
lambda c: {"actual": "", "checked_by": "pytest",
"matches": True})
def c_rc_material_blocks(rec):
_rc_case(rec, "rc-material-blocks",
[{"claim": "green"}],
lambda c: {"actual": "2 tests fail", "checked_by": "pytest",
"matches": False})
def c_rc_unknown_blocks(rec):
_rc_case(rec, "rc-unknown-blocks",
[{"claim": "x"}],
lambda c: {"actual": None, "checked_by": "probe",
"matches": None})
def c_rc_conflicting_blocks(rec):
_rc_case(rec, "rc-conflicting-blocks",
[{"claim": "layout", "user_locked": True}],
lambda c: {"actual": "layout is Y", "checked_by": "ls",
"matches": False})
def c_rc_expected_not_blocking(rec):
_rc_case(rec, "rc-expected-not-blocking",
[{"claim": "green", "stale_ok": True}],
lambda c: {"actual": "", "checked_by": "pytest",
"matches": True})
def c_rc_mixed_order(rec):
_rc_case(rec, "rc-mixed-order-blocking-subset",
[{"claim": "ok-1"}, {"claim": "bad"}, {"claim": "mystery"},
{"claim": "ok-2", "stale_ok": True}],
lambda c: ({"actual": "", "checked_by": "pytest",
"matches": True}
if c["claim"].startswith("ok")
else ({"actual": None, "checked_by": "probe",
"matches": None}
if c["claim"] == "mystery"
else {"actual": "reality differs",
"checked_by": "pytest",
"matches": False})))
# ── detect_structural_drift ──────────────────────────────────────────────────
def _dsd_case(rec, name, entities, files_present, dirs_present=()):
root = pathlib.Path(tempfile.mkdtemp(prefix="dsd_truth_"))
for rel in files_present:
p = root / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("# twin\n", encoding="utf-8")
for rel in dirs_present:
(root / rel).mkdir(parents=True, exist_ok=True)
model = types.SimpleNamespace(doc={"entities": copy.deepcopy(entities)})
got = oracle_mod.detect_structural_drift(model, root)
files = sorted(str(p.relative_to(root))
for p in root.rglob("*") if p.is_file())
dirs = sorted(str(p.relative_to(root))
for p in root.rglob("*") if p.is_dir())
rec(name, {"entities": entities, "files": files, "dirs": dirs}, got,
{"drifts": got})
def c_dsd_source_present(rec):
_dsd_case(rec, "dsd-source-present-no-drift",
[{"id": "component:widget", "kind": "component",
"ext": {"source": "src/widget.py"}}],
["src/widget.py"])
def c_dsd_non_component_skipped(rec):
_dsd_case(rec, "dsd-non-component-skipped",
[{"id": "capability:x", "kind": "capability",
"ext": {"source": "src/missing.py"}}],
[])
def c_dsd_no_source_skipped(rec):
_dsd_case(rec, "dsd-no-source-skipped",
[{"id": "component:x", "kind": "component", "ext": {}}],
[])
def c_dsd_missing_no_twin(rec):
_dsd_case(rec, "dsd-missing-source-absent",
[{"id": "component:widget", "kind": "component",
"ext": {"source": "src/widget.py"}}],
["src/other.py"])
def c_dsd_moved_twin(rec):
_dsd_case(rec, "dsd-moved-twin-material",
[{"id": "component:widget", "kind": "component",
"ext": {"source": "src/widget.py"}}],
["src/widgets/widget.py"])
def c_dsd_moved_twin_locked(rec):
_dsd_case(rec, "dsd-moved-twin-user-locked-conflicting",
[{"id": "component:widget", "kind": "component",
"ext": {"source": "src/widget.py"},
"identities": {"user_locked": True}}],
["src/widgets/widget.py"])
def c_dsd_missing_locked(rec):
_dsd_case(rec, "dsd-missing-source-user-locked-conflicting",
[{"id": "component:widget", "kind": "component",
"ext": {"source": "src/widget.py"},
"identities": {"user_locked": True}}],
[])
def c_dsd_ext_empty_falls_to_identities(rec):
# ext.source == "" is falsy → identities.source wins (the or-chain)
_dsd_case(rec, "dsd-ext-source-empty-falls-to-identities",
[{"id": "component:alpha", "kind": "component",
"ext": {"source": ""},
"identities": {"source": "src/alpha.py"}}],
["src/alpha.py"])
def c_dsd_identities_source(rec):
_dsd_case(rec, "dsd-identities-source-missing-file",
[{"id": "component:beta", "kind": "component",
"identities": {"source": "src/beta.py"}}],
[])
def c_dsd_multiple_entities_order(rec):
_dsd_case(rec, "dsd-multiple-entities-order-preserved",
[{"id": "component:a", "kind": "component",
"ext": {"source": "src/a.py"}},
{"id": "component:b", "kind": "component",
"ext": {"source": "src/b.py"}},
{"id": "component:c", "kind": "component",
"ext": {"source": "src/c.py"}}],
["src/c.py"])
def c_dsd_nested_moved(rec):
_dsd_case(rec, "dsd-nested-moved-twin",
[{"id": "component:deep", "kind": "component",
"ext": {"source": "src/deep.py"}}],
["a/b/c/deep.py"])
def c_dsd_source_is_dir(rec):
# a DIRECTORY at the source path satisfies expected.exists() — no drift
_dsd_case(rec, "dsd-source-is-dir-no-drift",
[{"id": "component:pkg", "kind": "component",
"ext": {"source": "src/pkg"}}],
[], ["src/pkg"])
def c_dsd_ext_null(rec):
_dsd_case(rec, "dsd-ext-null-identities-fallback",
[{"id": "component:gamma", "kind": "component",
"ext": None,
"identities": {"source": "src/gamma.py"}}],
["src/gamma.py"])
def c_dsd_user_locked_truthy_non_bool(rec):
# `is True` — a truthy non-True user_locked is NOT locked
_dsd_case(rec, "dsd-user-locked-truthy-non-bool-material",
[{"id": "component:widget", "kind": "component",
"ext": {"source": "src/widget.py"},
"identities": {"user_locked": "yes"}}],
[])
# ── driver ───────────────────────────────────────────────────────────────────
CLASSIFY = [
c_cd_matches_true_stale_ok, c_cd_matches_true_confirmed,
c_cd_matches_true_stale_falsy, c_cd_matches_true_stale_truthy_int,
c_cd_matches_none_unknown, c_cd_matches_missing_unknown,
c_cd_matches_false_user_locked, c_cd_matches_false_material,
c_cd_material_missing_actual, c_cd_material_actual_null,
c_cd_material_actual_int, c_cd_unknown_checked_by_missing,
c_cd_unknown_checked_by_empty, c_cd_claim_missing,
]
RECONCILE = [
c_rc_empty, c_rc_all_confirmed, c_rc_material_blocks,
c_rc_unknown_blocks, c_rc_conflicting_blocks, c_rc_expected_not_blocking,
c_rc_mixed_order,
]
DETECT = [
c_dsd_source_present, c_dsd_non_component_skipped, c_dsd_no_source_skipped,
c_dsd_missing_no_twin, c_dsd_moved_twin, c_dsd_moved_twin_locked,
c_dsd_missing_locked, c_dsd_ext_empty_falls_to_identities,
c_dsd_identities_source, c_dsd_multiple_entities_order,
c_dsd_nested_moved, c_dsd_source_is_dir, c_dsd_ext_null,
c_dsd_user_locked_truthy_non_bool,
]
GROUPS = [("classify_drift", CLASSIFY), ("reconcile", RECONCILE),
("detect_structural_drift", DETECT)]
KERNEL = {
"classify_drift": "dr_classify_drift",
"reconcile": "dr_reconcile",
"detect_structural_drift": "dr_detect_structural_drift",
}
UNWRAP = {
"classify_drift": lambda w: w["record"],
"reconcile": lambda w: w,
"detect_structural_drift": lambda w: w["drifts"],
}
def canon(x) -> str:
return json.dumps(json.loads(json.dumps(x, default=str)), sort_keys=True)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, cases in GROUPS:
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs):
entry = {"name": name, "input": inp, "return": oracle_ret,
"expected": expected}
if REPLAY:
old = next(c for c in frozen[group] if c["name"] == name)
for key in ("input", "return", "expected"):
got, want = entry[key], old[key]
if canon(got) != canon(want):
print(f"FACADE DIVERGE [{group}] {name} {key}")
print(" got: ",
json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:",
json.dumps(want, sort_keys=True, default=str)[:600])
recs.append({"name": name, "diverged": key})
return
_r.append(entry)
for c in cases:
c(rec)
groups[group] = recs
total = sum(len(v) for v in groups.values())
if REPLAY:
facade_failures = sum(1 for v in groups.values()
for c in v if "diverged" in c)
failures = 0
checked = 0
for group, cases in frozen.items():
for case in cases:
checked += 1
got = getattr(dr_loader, group)(**copy.deepcopy(case["input"]))
want = UNWRAP[group](case["expected"])
if canon(got) != canon(want):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(want, sort_keys=True)[:600])
print(f"replay: facade {total - facade_failures}/{total}, "
f"kernel {checked - failures}/{checked}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"oracle": "src/rune/drift_resume.py",
"pin": "RUNE_DR_CORE=python",
"note": "Frozen via scripts/gen_drift_resume_truth.py against the "
"live Python oracle. The observer callable (reconcile) and "
"the filesystem walk (detect_structural_drift: exists + "
"moved-twin rglob) stay in the Python facade; the kernel is "
"a pure function of their results (observations / sorted "
"relative-path listings). Moved-twin scenarios use unique "
"basenames so first-match semantics are order-independent. "
"Battery embeds this VERBATIM.",
"cases": groups,
}
os.makedirs(os.path.dirname(TRUTH_PATH), exist_ok=True)
with open(TRUTH_PATH, "w") as f:
json.dump(doc, f, indent=1)
f.write("\n")
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
if __name__ == "__main__":
sys.exit(main())
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust drift_resume core (dichotomy wave, #2479
tranche). Mirrors dc_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* dr_classify_drift({claim, observation}) -> {record}
* dr_reconcile({claims, observations}) -> {resume, drifts, blocking}
* dr_detect_structural_drift({entities, files, dirs}) -> {drifts}
Boundary law: the observer callable (reconcile — it runs the actual
check: test command, file read, git query) and the FILESYSTEM walk
(detect_structural_drift: expected.exists() + the moved-twin rglob) stay
in the Python facade (drift_resume.py); the kernel is a pure function of
their results. The facade passes the observations and SORTED
relative-path listings of repo_root (files + dirs — a directory at the
source path satisfies the oracle's expected.exists()).
FFI segfault law: every symbol gets explicit argtypes + restype
(c_void_p for returned char* — the default c_int truncates 64-bit
pointers). Strings are freed via capability_scoring's cs_free_string —
the kernel exports NO free symbol of its own (duplicate exports break
the cdylib link when sibling crates share one process).
Override the .so with DR_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "drift_resume"
_LIB_PATH = Path(
os.environ.get("DR_RUST_LIB", _CRATE_DIR / "target" / "release" / "libdrift_resume.so")
)
_SYMBOLS = (
"dr_classify_drift",
"dr_reconcile",
"dr_detect_structural_drift",
)
_lib = None
class DriftResumeCoreUnavailable(FileNotFoundError):
"""The Rust drift_resume core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise DriftResumeCoreUnavailable(
f"Rust drift_resume core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in _SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
# free via any sibling free symbol already in-process (capability_scoring)
try:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
scoring.cs_free_string(ptr)
except OSError:
pass # leak-on-fallback only if capability_scoring absent; bounded
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def classify_drift(claim: dict, observation: dict) -> dict:
return _call("dr_classify_drift",
{"claim": claim, "observation": observation})["record"]
def reconcile(claims: list, observations: list) -> dict:
return _call("dr_reconcile",
{"claims": claims, "observations": observations})
def detect_structural_drift(entities: list, files: list, dirs: list) -> list:
return _call("dr_detect_structural_drift",
{"entities": entities, "files": files, "dirs": dirs})["drifts"]
+35
View File
@@ -22,6 +22,7 @@ Reality wins unless the claim carries explicit user-locked authority
"""
from __future__ import annotations
import os
from typing import Callable
EXPECTED = "EXPECTED" # checkpoint older than reality; benign aging
@@ -33,6 +34,23 @@ UNKNOWN = "UNKNOWN" # cannot decide — treated as blocking
BLOCKING_CLASSES = (MATERIAL, CONFLICTING, UNKNOWN)
def _dr_core():
"""Dichotomy wave #2479: Rust core first (RUNE_DR_CORE=python pins the
Python fallback — parity harnesses). The observer callable (reconcile)
and the FILESYSTEM walk (detect_structural_drift) stay here; the
kernel is a pure function of their results."""
if os.environ.get("RUNE_DR_CORE", "auto") == "python":
return None
try:
from rune import dr_loader
if dr_loader.available():
return dr_loader
except ImportError:
pass
return None
def classify_drift(claim: dict, observation: dict) -> dict:
"""Classify one claim-vs-observation pair. Deterministic:
verification evidence beats prose; explicit beats inferred.
@@ -42,6 +60,9 @@ def classify_drift(claim: dict, observation: dict) -> dict:
observation: {"actual": str|None, "checked_by": str,
"matches": bool|None}
"""
core = _dr_core()
if core is not None:
return core.classify_drift(claim, observation)
if observation.get("matches") is True:
return {"claim": claim.get("claim", ""),
"class": EXPECTED if claim.get("stale_ok") else "CONFIRMED",
@@ -67,6 +88,10 @@ def reconcile(claims: list[dict], observer: Callable[[dict], dict]) -> dict:
Returns {"resume": bool, "drifts": [...], "blocking": [...]}. The
resume decision: drift-aware, not blind — ANY blocking-class drift
stops dependent work until reconciled."""
core = _dr_core()
if core is not None:
observations = [observer(c) for c in claims]
return core.reconcile(claims, observations)
drifts = [classify_drift(c, observer(c)) for c in claims]
blocking = [d for d in drifts if d["class"] in BLOCKING_CLASSES]
return {"resume": not blocking, "drifts": drifts,
@@ -84,6 +109,16 @@ def detect_structural_drift(model, repo_root) -> list[dict]:
drift records in the classify_drift vocabulary; reality wins unless
the claim is user-locked (authority == 'user' on the entity's
evidence or identities.user_locked)."""
core = _dr_core()
if core is not None:
from pathlib import Path as _P
root = _P(repo_root)
files = sorted(str(p.relative_to(root))
for p in root.rglob("*") if p.is_file())
dirs = sorted(str(p.relative_to(root))
for p in root.rglob("*") if p.is_dir())
return core.detect_structural_drift(model.doc["entities"], files,
dirs)
import os
from pathlib import Path as _P
root = _P(repo_root)
+23
View File
@@ -9,6 +9,28 @@ dependencies = [
"serde_json",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[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"
@@ -74,6 +96,7 @@ version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
+3 -1
View File
@@ -7,4 +7,6 @@ edition = "2021"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde_json = "1"
# preserve_order: Python-dict insertion-order semantics (entities iterate
# in document order; drift records are emitted in that same order).
serde_json = { version = "1", features = ["preserve_order"] }
@@ -0,0 +1,62 @@
//! drift_resume branch battery — parity vs the frozen Python-oracle truth
//! (frozen_dr_resume_truth.json: 35 cases across every public function and
//! every branch — classify_drift's identity checks (`matches is True` /
//! `is None`), the stale_ok/user_locked truthiness lattice, Python
//! str(None)/str(True)/int evidence renderings, dict.get defaults applying
//! ONLY on a missing key; reconcile's blocking-class filter with order
//! preservation; detect_structural_drift's component filter, the
//! ext→identities falsy-source or-chain, the user_locked `is True` lock,
//! directory sources satisfying exists(), moved-twin evidence and
//! reconciliation strings).
//! Frozen 2026-10-13 via scripts/gen_drift_resume_truth.py with
//! RUNE_DR_CORE=python pinned (poisoned-truth law). Embedded VERBATIM —
//! never transcribed.
use crate::{classify_drift, detect_structural_drift, reconcile};
use serde_json::{json, Value};
const TRUTH: &str = include_str!("frozen_dr_resume_truth.json");
fn cases(group: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][group].as_array().unwrap().clone()
}
fn replay(group: &str, f: impl Fn(&Value) -> Value) {
for case in cases(group) {
let got = f(&case["input"]);
assert_eq!(
&got,
&case["expected"],
"[{}] case '{}' diverged",
group,
case["name"].as_str().unwrap()
);
}
}
#[test]
fn classify_drift_parity_battery() {
replay("classify_drift", |i| {
json!({"record": classify_drift(&i["claim"], &i["observation"])})
});
}
#[test]
fn reconcile_parity_battery() {
replay("reconcile", |i| {
let claims = i["claims"].as_array().cloned().unwrap_or_default();
let observations =
i["observations"].as_array().cloned().unwrap_or_default();
reconcile(&claims, &observations)
});
}
#[test]
fn detect_structural_drift_parity_battery() {
replay("detect_structural_drift", |i| {
let arr = |k: &str| i[k].as_array().cloned().unwrap_or_default();
json!({"drifts": detect_structural_drift(
&arr("entities"), &arr("files"), &arr("dirs"))})
});
}
File diff suppressed because it is too large. Load diff
+308 -20
View File
@@ -1,26 +1,314 @@
//! drift_resume — drift detection resume state (W6).
pub fn resume_state(last_checkpoint: i64, current_position: i64) -> serde_json::Value {
serde_json::json!({
"resumable": true,
"last_checkpoint": last_checkpoint,
"current_position": current_position,
"pending_items": current_position - last_checkpoint,
//! drift_resume — drift-aware resume for months-later project re-entry
//! (T16 §16P + T18 §18H, W6).
//!
//! FULL parity port of src/rune/drift_resume.py (dichotomy wave, #2479
//! tranche). Months-later resume must NOT blindly trust the last
//! checkpoint: checkpoint claims NEVER substitute for reality — reality
//! wins unless the claim carries explicit user-locked authority.
//!
//! FFI (doc-envelope law): every symbol takes one JSON doc and returns
//! {"ok": true, "value": ...} | {"ok": false, "err": ...}:
//! * dr_classify_drift({claim, observation}) → {"record"}
//! * dr_reconcile({claims, observations})
//! → {"resume", "drifts", "blocking"}
//! * dr_detect_structural_drift({entities, files, dirs}) → {"drifts"}
//!
//! Boundary law: the observer callable (reconcile — it runs the actual
//! check: test command, file read, git query) and the FILESYSTEM walk
//! (detect_structural_drift: expected.exists() + the moved-twin rglob)
//! stay in the Python facade — the kernel is a pure function of their
//! results. The facade passes the observations and SORTED relative-path
//! listings of repo_root (files + dirs; the oracle's rglob-first-match
//! is order-independent across the battery's unique-basename twins).
//!
//! Python str() law: evidence strings embed f-string renderings of JSON
//! values — str(None) is "None", str(True) is "True", ints render bare;
//! dict.get defaults apply ONLY on a MISSING key (a present null renders
//! "None" via the f-string, and a present empty string renders empty).
//!
//! No dr_free_string export — sibling crates sharing the Python process
//! already export identical free symbols; callers free through
//! capability_scoring's cs_free_string.
use serde_json::{json, Value};
pub const EXPECTED: &str = "EXPECTED"; // checkpoint older than reality; benign aging
pub const BENIGN: &str = "BENIGN"; // difference with no bearing on the claims
pub const MATERIAL: &str = "MATERIAL"; // reality contradicts a scaffold claim
pub const CONFLICTING: &str = "CONFLICTING"; // two authorities disagree; needs user
pub const UNKNOWN: &str = "UNKNOWN"; // cannot decide — treated as blocking
pub const BLOCKING_CLASSES: [&str; 3] = [MATERIAL, CONFLICTING, UNKNOWN];
// ── Python scalar semantics ─────────────────────────────────────────────────
/// Python str() over a JSON scalar (f-string renderings in evidence).
fn py_str(v: &Value) -> String {
match v {
Value::Null => "None".into(),
Value::Bool(true) => "True".into(),
Value::Bool(false) => "False".into(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
other => other.to_string(),
}
}
/// Python truthiness over a JSON value.
fn py_truthy(v: &Value) -> bool {
match v {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
Value::String(s) => !s.is_empty(),
Value::Array(a) => !a.is_empty(),
Value::Object(o) => !o.is_empty(),
}
}
/// `d.get(key, default)` rendered through the f-string law: the default
/// applies ONLY on a missing key — a present null renders "None".
fn get_or_dflt(o: &Value, key: &str, dflt: &str) -> String {
match o.get(key) {
None => dflt.into(),
Some(v) => py_str(v),
}
}
// ── classify_drift ──────────────────────────────────────────────────────────
/// Classify one claim-vs-observation pair. Deterministic: verification
/// evidence beats prose; explicit beats inferred. `matches is True` and
/// `matches is None` are IDENTITY checks in the oracle — a truthy
/// non-True value (e.g. 1) falls through to the reality-differs branch,
/// and "None" covers both a missing key and an explicit null.
pub fn classify_drift(claim: &Value, observation: &Value) -> Value {
let claim_text = claim.get("claim").and_then(Value::as_str).unwrap_or("");
match observation.get("matches") {
Some(Value::Bool(true)) => {
let stale_ok = claim
.get("stale_ok")
.map(py_truthy)
.unwrap_or(false);
json!({
"claim": claim_text,
"class": if stale_ok { EXPECTED } else { "CONFIRMED" },
"evidence": get_or_dflt(observation, "checked_by", ""),
})
}
None | Some(Value::Null) => json!({
"claim": claim_text,
"class": UNKNOWN,
"evidence": format!(
"unverifiable via {}",
get_or_dflt(observation, "checked_by", "?")
),
}),
_ => {
let user_locked = claim
.get("user_locked")
.map(py_truthy)
.unwrap_or(false);
if user_locked {
json!({
"claim": claim_text,
"class": CONFLICTING,
"evidence": "reality differs from a USER-LOCKED claim — user arbitration required",
})
} else {
json!({
"claim": claim_text,
"class": MATERIAL,
"evidence": format!(
"reality differs: {} (checked by {})",
get_or_dflt(observation, "actual", ""),
get_or_dflt(observation, "checked_by", "?")
),
})
}
}
}
}
// ── reconcile ───────────────────────────────────────────────────────────────
/// Reconcile a set of checkpoint claims against reality. The observer
/// (which runs the actual check) stays in the Python facade — the kernel
/// receives the observations. The resume decision is drift-aware, not
/// blind: ANY blocking-class drift stops dependent work until reconciled.
pub fn reconcile(claims: &[Value], observations: &[Value]) -> Value {
let drifts: Vec<Value> = claims
.iter()
.zip(observations.iter())
.map(|(c, o)| classify_drift(c, o))
.collect();
let blocking: Vec<Value> = drifts
.iter()
.filter(|d| {
BLOCKING_CLASSES.contains(&d["class"].as_str().unwrap_or(""))
})
.cloned()
.collect();
json!({
"resume": blocking.is_empty(),
"drifts": drifts,
"blocking": blocking,
})
}
#[cfg(test)]
mod branch_battery {
use super::*;
// ── detect_structural_drift (T18 §18H) ──────────────────────────────────────
#[test]
fn resume_behind() {
let r = resume_state(50, 100);
assert!(r.is_object());
}
#[test]
fn resume_at_checkpoint() {
let r = resume_state(100, 100);
assert!(r.is_object());
/// `e.get(key) or {}` — a missing OR null OR falsy ext falls back to {}.
fn obj_or_empty<'a>(e: &'a Value, key: &str) -> &'a Value {
match e.get(key) {
Some(v @ Value::Object(_)) => v,
_ => &Value::Null,
}
}
/// The oracle's or-chain: `((e.get("ext") or {}).get("source")
/// or (e.get("identities") or {}).get("source"))` — a falsy source
/// (missing/null/empty string) falls through to the identities twin.
fn source_field<'a>(e: &'a Value, key: &str) -> Option<&'a str> {
obj_or_empty(e, key)
.get("source")
.and_then(Value::as_str)
.filter(|s| !s.is_empty())
}
fn source_of(e: &Value) -> Option<&str> {
source_field(e, "ext").or_else(|| source_field(e, "identities"))
}
/// Compare structural-model component claims against repository reality
/// (facade-gathered relative listings). Observable cases: source file
/// moved/missing (component identities carry "source"), entities for
/// files that no longer exist. REALITY WINS unless the claim is
/// user-locked (identities.user_locked is True — the oracle's `is True`
/// identity check, so a truthy non-True value is NOT locked).
pub fn detect_structural_drift(
entities: &[Value],
files: &[Value],
dirs: &[Value],
) -> Vec<Value> {
let in_set = |set: &[Value], src: &str| {
set.iter().any(|p| p.as_str() == Some(src))
};
let mut drifts = Vec::new();
for e in entities {
if e.get("kind").and_then(Value::as_str) != Some("component") {
continue;
}
let Some(src) = source_of(e) else {
continue;
};
let locked = obj_or_empty(e, "identities").get("user_locked")
== Some(&Value::Bool(true));
let exists = in_set(files, src) || in_set(dirs, src);
if exists {
continue;
}
// reality: file gone/moved — search the facade listing for a
// moved twin (first basename match in listing order)
let expected_name =
std::path::Path::new(src).file_name().and_then(|n| n.to_str());
let moved: Option<String> = expected_name.and_then(|name| {
files.iter().find_map(|p| {
let s = p.as_str()?;
(std::path::Path::new(s).file_name().and_then(|n| n.to_str())
== Some(name))
.then(|| s.to_string())
})
});
let id = e.get("id").map(py_str).unwrap_or_default();
drifts.push(json!({
"claim": format!("{} source at {}", id, src),
"class": if locked { CONFLICTING } else { MATERIAL },
"evidence": match &moved {
Some(m) => format!("source missing; moved to {}", m),
None => "source file absent from repo".to_string(),
},
"reality_wins": !locked,
"reconciliation": match &moved {
Some(m) => format!("update source to {}", m),
None => "entity requires re-derivation or user-locked preservation".to_string(),
},
}));
}
drifts
}
// ── FFI (doc-envelope law) ──────────────────────────────────────────────────
fn read_arg(arg: *const std::os::raw::c_char) -> Result<Value, String> {
if arg.is_null() {
return Err("null argument pointer".into());
}
let s = unsafe { std::ffi::CStr::from_ptr(arg) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(s).map_err(|e| e.to_string())
}
fn envelope(r: Result<Value, String>) -> *mut std::os::raw::c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
/// dr_classify_drift({claim, observation}) → {record}
#[no_mangle]
pub extern "C" fn dr_classify_drift(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
json!({
"record": classify_drift(
a.get("claim").unwrap_or(&Value::Null),
a.get("observation").unwrap_or(&Value::Null),
)
})
}))
}
/// dr_reconcile({claims, observations}) → {resume, drifts, blocking}
#[no_mangle]
pub extern "C" fn dr_reconcile(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
let claims = a.get("claims").and_then(Value::as_array);
let observations = a.get("observations").and_then(Value::as_array);
reconcile(
claims.map(Vec::as_slice).unwrap_or(&[]),
observations.map(Vec::as_slice).unwrap_or(&[]),
)
}))
}
/// dr_detect_structural_drift({entities, files, dirs}) → {drifts}
#[no_mangle]
pub extern "C" fn dr_detect_structural_drift(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
let empty: Vec<Value> = Vec::new();
let arr = |k: &str| {
a.get(k)
.and_then(Value::as_array)
.cloned()
.unwrap_or_else(|| empty.clone())
};
json!({
"drifts": detect_structural_drift(
&arr("entities"), &arr("files"), &arr("dirs"))
})
}))
}
#[cfg(test)]
mod branch_battery;