feat(construction_projection): close PARTIAL gaps — dichotomy wave
Port all 4 oracle fns name-preserving to Rust (audit 1/4 -> 4/4):
_contract_entity, validate, write_mini_doc (markdown build only — the
file write stays adapter-side), and project (the W6-era facade path was
dead code: cp_loader.project never existed). cproj_loader ctypes FFI
(cp_project/cp_contract_entity/cp_validate/cp_write_mini_doc,
doc-envelope law); facade delegation under
RUNE_CONSTRUCTION_PROJECTION_CORE with crash-parity floor fallback; the
operation-wait bridge stays adapter-side (resolved scalars ride the
frozen rows — SPEC-grammar law). Dead W6 canary
(canary_construction_projection.py + frozen_projection.json, calling
the non-existent cp_loader.project, invoked by nothing) deleted.
Truth: 52-case shared-harness corpus (scripts/freeze_truth.py +
cproj_freeze_seams rig: pinned fake scaffold/bridge, hermetic scratch,
no absolute paths/wall clock), --verify byte-identical; gap_battery.rs
replays VERBATIM (include_str!, envelope pinned to verify file) 8/8.
Live 3-way parity 100% (843 comparisons: 52 frozen + 240 fuzz project +
40 mini-doc + 7 crash). clippy -D warnings + fmt clean. Python both
pins: test_contract_minidoc 3/3 + test_substrate_seam 12/12 (delegation
live + python pin). Fix-first with proof: minidoc fixture
raw-transition to closed refused since 2597b58b (verified failing at
pristine HEAD) — now closes via iface.close(), re-exercising mini-doc
emission end-to-end through the kernel. Evidence:
verification/cpj-summary.md + cpj-*.exit.
This commit is contained in:
1 parent
279eb5a316
commit
9ce798ec1d
23 files changed
+6829
-236
No files matched your search
@@ -0,0 +1,522 @@
|
||||
#!/usr/bin/env python3
|
||||
"""check_construction_projection_parity.py — live parity for the
|
||||
construction_projection dichotomy gap close (#2479): the Rust kernel
|
||||
(.so via cproj_loader) vs the live Python oracle floor vs the frozen
|
||||
truth — 100% on the full surface.
|
||||
|
||||
Structure (single oracle subprocess under the freeze rig, checker process
|
||||
with the kernel live):
|
||||
* FROZEN rows (52) — 3-way: kernel | oracle floor | frozen expected;
|
||||
* FACADE fuzz rows (240 project + 40 minidoc) — the facade called in
|
||||
the checker process (kernel live) AND in the subprocess (floor) with
|
||||
the same fake-scaffold/fake-bridge inputs — must agree with each
|
||||
other and with the loader-direct kernel calls;
|
||||
* VALIDATE rows — the fuzz projections validated under both kernels;
|
||||
* CRASH rows — shapes where the oracle raises: the kernel errs, the
|
||||
facade falls back to the floor and reproduces the identical
|
||||
exception on BOTH sides.
|
||||
|
||||
Usage: python3 scripts/check_construction_projection_parity.py
|
||||
Exit 0 iff every comparison matches.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
TRUTH = ROOT / "src" / "rust" / "construction_projection" / "src" / "frozen_truth.json"
|
||||
|
||||
ORACLE_HELPER = r"""
|
||||
import copy, json, os, sys, tempfile, shutil
|
||||
|
||||
for p in (%(scripts)r, %(src)r):
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
import rune.construction_projection as cp # floor pinned by the rig
|
||||
from rune import event_wait as ew
|
||||
from freeze_truth import _run_case
|
||||
|
||||
payload = json.load(sys.stdin)
|
||||
REAL_WAIT = ew.operation_wait
|
||||
|
||||
|
||||
class _FakeScaffold:
|
||||
def __init__(self, spec):
|
||||
self._doc = copy.deepcopy(spec.get("load") or {})
|
||||
if "project_id" in spec:
|
||||
self.project_id = spec["project_id"]
|
||||
|
||||
def load(self):
|
||||
return copy.deepcopy(self._doc)
|
||||
|
||||
|
||||
def _bridge(bridge):
|
||||
mode = (bridge or {}).get("__mode__", "none")
|
||||
|
||||
def _wait(operation_id):
|
||||
if mode == "oserr":
|
||||
raise OSError(2, "bridge data root unreadable (parity-pinned)")
|
||||
if mode == "runtimeerr":
|
||||
raise RuntimeError("waitstore not resolvable (parity-pinned)")
|
||||
if mode == "found":
|
||||
return dict((bridge or {}).get("record") or {})
|
||||
return None
|
||||
|
||||
return _wait
|
||||
|
||||
|
||||
out = {
|
||||
"rows": [
|
||||
_run_case(cp, {"fn": r["fn"], "args": r["args"],
|
||||
"kwargs": r.get("kwargs", {})})
|
||||
for r in payload["ROWS"]
|
||||
],
|
||||
"facade_project": [],
|
||||
"facade_entity": [],
|
||||
"facade_validate": [],
|
||||
"facade_minidoc": [],
|
||||
"crash": {},
|
||||
}
|
||||
|
||||
for spec, cid, bridge, kwargs in payload["FACADE_PROJECT"]:
|
||||
ew.operation_wait = _bridge(bridge)
|
||||
try:
|
||||
p = cp.project(_FakeScaffold(spec), cid, **kwargs)
|
||||
out["facade_project"].append(p)
|
||||
finally:
|
||||
ew.operation_wait = REAL_WAIT
|
||||
|
||||
for spec, cid, _bridge, _kwargs in payload["FACADE_PROJECT"]:
|
||||
out["facade_entity"].append(cp._contract_entity(copy.deepcopy(spec["load"] or {}), cid))
|
||||
|
||||
# validate rows: over the facade projections (real 24-field shapes)
|
||||
out["facade_validate"] = [cp.validate(p) for p in out["facade_project"]]
|
||||
|
||||
for spec, cid in payload["FACADE_MINIDOC"]:
|
||||
scratch = tempfile.mkdtemp(prefix="cpjfloor-")
|
||||
try:
|
||||
path = cp.write_mini_doc(_FakeScaffold(spec), cid, out_dir=scratch)
|
||||
out["facade_minidoc"].append({
|
||||
"file": os.path.basename(str(path)),
|
||||
"content": open(path, encoding="utf-8").read(),
|
||||
"projection": cp.project(_FakeScaffold(spec), cid),
|
||||
})
|
||||
finally:
|
||||
shutil.rmtree(scratch, ignore_errors=True)
|
||||
|
||||
for kind, args in payload["CRASH"]:
|
||||
try:
|
||||
if kind in ("entity_nondict", "entity_null_entities"):
|
||||
result = cp._contract_entity(args[0], args[1])
|
||||
elif kind == "validate_nondict":
|
||||
result = cp.validate(args[0])
|
||||
elif kind in ("blocker_int", "checkpoint_int"):
|
||||
result = cp.project(_FakeScaffold({"load": args[0]}), args[1])
|
||||
elif kind in ("surface_int", "evidence_int"):
|
||||
result = cp.write_mini_doc(_FakeScaffold({"load": args[0]}), args[1])
|
||||
else:
|
||||
raise AssertionError(kind)
|
||||
out["crash"][kind] = "NO_CRASH:" + json.dumps(result, sort_keys=True)
|
||||
except Exception as e: # noqa: BLE001 — the crash IS the check
|
||||
out["crash"][kind] = f"{type(e).__name__}: {e}"
|
||||
|
||||
print(json.dumps(out))
|
||||
"""
|
||||
|
||||
FAILS: list = [0]
|
||||
CHECKS: list = [0]
|
||||
|
||||
|
||||
def check(label, got, want) -> None:
|
||||
CHECKS[0] += 1
|
||||
gs = json.dumps(got, sort_keys=True, default=str)
|
||||
ws = json.dumps(want, sort_keys=True, default=str)
|
||||
if gs != ws:
|
||||
FAILS[0] += 1
|
||||
print(f"MISMATCH {label}\n got: {gs[:260]}\n want: {ws[:260]}")
|
||||
|
||||
|
||||
def fuzz_corpus(n: int, seed: int = 2026) -> list:
|
||||
"""(spec, contract_id, bridge, kwargs) fuzz rows — adversarial shapes."""
|
||||
rng = random.Random(seed)
|
||||
ALPH = "abcxyzΛ中é." # multibyte — the codepoint-truncation quirk
|
||||
rows = []
|
||||
|
||||
def rnd_str(maxlen=40):
|
||||
return "".join(rng.choice(ALPH) for _ in range(rng.randint(0, maxlen)))
|
||||
|
||||
for i in range(n):
|
||||
ents = []
|
||||
for k in range(rng.randint(0, 3)):
|
||||
e = {"id": f"contract:C-{i}-{k}"}
|
||||
if rng.random() < 0.8:
|
||||
e["phase"] = rng.choice(["draft", "approved", "verifying", "closed",
|
||||
"awaiting_approval"])
|
||||
ids: dict = {}
|
||||
if rng.random() < 0.7:
|
||||
ids["construction_mode"] = rng.choice(["BUILD_NEW", "PATCH", ""])
|
||||
if rng.random() < 0.7:
|
||||
ids["depth"] = rng.choice(["standard", "deep", 0])
|
||||
if rng.random() < 0.5:
|
||||
ids["next_action"] = rng.choice([rnd_str(15), None])
|
||||
if rng.random() < 0.4:
|
||||
ids["blocker"] = rng.choice([
|
||||
{"class": rng.choice(["SCOPE", "BLOCKED"]),
|
||||
"evidence": rnd_str(150)},
|
||||
rnd_str(20),
|
||||
"",
|
||||
])
|
||||
if rng.random() < 0.5:
|
||||
ids["objective"] = rnd_str(50)
|
||||
if rng.random() < 0.3:
|
||||
ids["affected_surface"] = "src/" + rnd_str(8) + ".py"
|
||||
if rng.random() < 0.4:
|
||||
ids["task_ref"] = "T" + str(rng.randint(0, 200))
|
||||
e["identities"] = ids
|
||||
ext: dict = {}
|
||||
if rng.random() < 0.6:
|
||||
ext["readiness_canary"] = rng.choice(["ready", None, ""])
|
||||
if rng.random() < 0.6:
|
||||
ext["liveness_canary"] = "live"
|
||||
if rng.random() < 0.6:
|
||||
ext["exit_canary"] = "exit"
|
||||
if rng.random() < 0.5:
|
||||
ext["contract_quality"] = {
|
||||
rng.choice(["correctly_predicted", "scope_check", "zz_last"]):
|
||||
rng.choice([0, 3, True, "green"]),
|
||||
}
|
||||
if rng.random() < 0.5:
|
||||
ext["decision_trace"] = {"approval": rng.choice([
|
||||
{"required": True},
|
||||
{"required": False, "state": "REQUIRED"},
|
||||
{"required": 0},
|
||||
{},
|
||||
])}
|
||||
if rng.random() < 0.4:
|
||||
ext["affected_paths"] = [
|
||||
"src/" + rnd_str(6) + ".py"
|
||||
for _ in range(rng.randint(0, 3))
|
||||
]
|
||||
if rng.random() < 0.3:
|
||||
ext["need"] = {"objective": rnd_str(30)}
|
||||
e["ext"] = ext
|
||||
if rng.random() < 0.6:
|
||||
e["checkpoints"] = [
|
||||
{"evidence": rng.choice(["", rnd_str(140)])}
|
||||
for _ in range(rng.randint(0, 3))
|
||||
]
|
||||
ents.append(e)
|
||||
spec = {"load": {"entities": ents}}
|
||||
if rng.random() < 0.8:
|
||||
spec["project_id"] = f"proj-{i}"
|
||||
cid = rng.choice([f"C-{i}-0", f"C-{i}-1", ""]) if ents else "C-404"
|
||||
bridge = rng.choice([
|
||||
None, {},
|
||||
{"__mode__": "found", "record": {"status": "W", "wait_id": "w7"}},
|
||||
{"__mode__": "oserr"},
|
||||
{"__mode__": "runtimeerr"},
|
||||
])
|
||||
kwargs: dict = {}
|
||||
for k in ("originating_goal_id", "stage", "builder_state",
|
||||
"verification_state", "checkpoint", "failure_class",
|
||||
"completion_state"):
|
||||
if rng.random() < 0.6:
|
||||
kwargs[k] = rnd_str(10)
|
||||
if rng.random() < 0.7:
|
||||
kwargs["operation_id"] = rng.choice(["op-a", ""])
|
||||
if rng.random() < 0.5:
|
||||
kwargs["correlation_id"] = "corr"
|
||||
rows.append((spec, cid, bridge, kwargs))
|
||||
return rows
|
||||
|
||||
|
||||
def rust_entity(doc, cid):
|
||||
from rune import cproj_loader
|
||||
|
||||
return cproj_loader.contract_entity(doc, cid)
|
||||
|
||||
|
||||
def rust_validate(p):
|
||||
from rune import cproj_loader
|
||||
|
||||
return cproj_loader.validate(p)
|
||||
|
||||
|
||||
def rust_project(entity, cid, waiting_state, live_correlation, kwargs):
|
||||
from rune import cproj_loader
|
||||
|
||||
return cproj_loader.project(
|
||||
entity if entity is not None else {},
|
||||
contract_id=cid,
|
||||
project_id=kwargs.get("project_id", ""),
|
||||
originating_goal_id=kwargs.get("originating_goal_id", ""),
|
||||
operation_id=kwargs.get("operation_id", ""),
|
||||
waiting_state=waiting_state,
|
||||
live_correlation=live_correlation,
|
||||
stage=kwargs.get("stage", ""),
|
||||
builder_state=kwargs.get("builder_state", ""),
|
||||
verification_state=kwargs.get("verification_state", ""),
|
||||
checkpoint=kwargs.get("checkpoint", ""),
|
||||
failure_class=kwargs.get("failure_class", ""),
|
||||
completion_state=kwargs.get("completion_state", ""),
|
||||
)
|
||||
|
||||
|
||||
def rust_write_mini_doc(entity, cid, projection):
|
||||
from rune import cproj_loader
|
||||
|
||||
return cproj_loader.write_mini_doc(
|
||||
entity if entity is not None else {}, cid, projection)
|
||||
|
||||
|
||||
class _KernelScaffold:
|
||||
"""Fake scaffold for the CHECK process (kernel live)."""
|
||||
|
||||
def __init__(self, spec):
|
||||
import copy
|
||||
|
||||
self._doc = copy.deepcopy(spec.get("load") or {})
|
||||
if "project_id" in spec:
|
||||
self.project_id = spec["project_id"]
|
||||
|
||||
def load(self):
|
||||
import copy
|
||||
|
||||
return copy.deepcopy(self._doc)
|
||||
|
||||
|
||||
def _check_bridge(bridge):
|
||||
"""Same fake-bridge rule as the rig (duplicated HERE — the parity
|
||||
contract pins both sides against the same fake, never the real store)."""
|
||||
mode = (bridge or {}).get("__mode__", "none")
|
||||
|
||||
def _wait(operation_id):
|
||||
if mode == "oserr":
|
||||
raise OSError(2, "bridge data root unreadable (parity-pinned)")
|
||||
if mode == "runtimeerr":
|
||||
raise RuntimeError("waitstore not resolvable (parity-pinned)")
|
||||
if mode == "found":
|
||||
return dict((bridge or {}).get("record") or {})
|
||||
return None
|
||||
|
||||
return _wait
|
||||
|
||||
|
||||
def run_oracle(payload: dict) -> dict:
|
||||
"""The floor side: one subprocess under the freeze rig (oracle = Python)."""
|
||||
code = ORACLE_HELPER % {"scripts": str(ROOT / "scripts"), "src": str(ROOT / "src")}
|
||||
env = {
|
||||
**os.environ,
|
||||
"PYTHONHASHSEED": "0",
|
||||
"PYTHONPATH": (
|
||||
f"{ROOT / 'scripts' / 'cproj_freeze_seams'}:{ROOT / 'scripts'}:{ROOT / 'src'}"
|
||||
),
|
||||
"RUNE_CONSTRUCTION_PROJECTION_CORE": "python",
|
||||
}
|
||||
r = subprocess.run([sys.executable, "-c", code], input=json.dumps(payload),
|
||||
capture_output=True, text=True, env=env, timeout=900)
|
||||
if r.returncode != 0:
|
||||
print("ORACLE SUBPROCESS FAILED:", r.stderr[-1500:])
|
||||
sys.exit(1)
|
||||
return json.loads(r.stdout)
|
||||
|
||||
|
||||
def crash_shapes() -> list:
|
||||
"""(kind, factory) — the factory builds the floor-call inputs."""
|
||||
LONG_INT = 5
|
||||
return [
|
||||
("entity_nondict", lambda: (42, "X")),
|
||||
("entity_null_entities", lambda: ({"entities": None}, "X")),
|
||||
("validate_nondict", lambda: (42,)),
|
||||
("blocker_int", lambda: (
|
||||
{"entities": [{"id": "contract:X",
|
||||
"identities": {"blocker": {"evidence": LONG_INT}}}]}, "X")),
|
||||
("checkpoint_int", lambda: (
|
||||
{"entities": [{"id": "contract:X",
|
||||
"checkpoints": [{"evidence": LONG_INT}]}]}, "X")),
|
||||
("surface_int", lambda: (
|
||||
{"entities": [{"id": "contract:X",
|
||||
"ext": {"affected_paths": LONG_INT}}]}, "X")),
|
||||
("evidence_int", lambda: (
|
||||
{"entities": [{"id": "contract:X", "identities": {},
|
||||
"checkpoints": [{"evidence": "ev"}]}]}, "X")),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
from rune import cproj_loader
|
||||
|
||||
if not cproj_loader.available():
|
||||
print("cproj_loader kernel not built — build "
|
||||
"src/rust/construction_projection first")
|
||||
return 1
|
||||
|
||||
envelope = json.loads(TRUTH.read_text())
|
||||
truth_rows = envelope["cases"]
|
||||
|
||||
fuzz = fuzz_corpus(240)
|
||||
facade_minidoc_rows = [(fz[0], fz[1]) for fz in fuzz[:40]]
|
||||
|
||||
payload = {
|
||||
"ROWS": truth_rows,
|
||||
"FACADE_PROJECT": fuzz,
|
||||
"FACADE_MINIDOC": facade_minidoc_rows,
|
||||
"CRASH": [(kind, factory()) for kind, factory in crash_shapes()],
|
||||
}
|
||||
oracle = run_oracle(payload)
|
||||
|
||||
total = len(truth_rows)
|
||||
|
||||
print(f"== frozen rows ({total}) 3-way: kernel | oracle floor | frozen ==")
|
||||
for i, row in enumerate(truth_rows):
|
||||
fn, expected = row["fn"], row["expected"]
|
||||
check(f"frozen[{i}] frozen-vs-oracle {fn}", expected,
|
||||
oracle["rows"][i]["expected"])
|
||||
if fn == "cpj_freeze_contract_entity":
|
||||
doc, cid = row["args"][0], row["args"][1]
|
||||
check(f"frozen[{i}] contract_entity kernel-vs-frozen",
|
||||
rust_entity(doc, cid), expected)
|
||||
check(f"frozen[{i}] contract_entity facade-kernel",
|
||||
cp_contract_entity(doc, cid), expected)
|
||||
elif fn == "cpj_freeze_validate":
|
||||
p = row["args"][0]
|
||||
check(f"frozen[{i}] validate kernel-vs-frozen",
|
||||
rust_validate(p), expected)
|
||||
check(f"frozen[{i}] validate facade-kernel", cp_validate(p), expected)
|
||||
elif fn == "cpj_freeze_project":
|
||||
spec, cid = row["args"][0], row["args"][1]
|
||||
bridge, kwargs = row["args"][2], row["kwargs"]
|
||||
wrapped = expected
|
||||
args_full = {**kwargs, "project_id": spec.get("project_id", "")}
|
||||
entity = rust_entity(spec["load"], cid)
|
||||
got_kernel = rust_project(entity, cid, wrapped["waiting_state"],
|
||||
wrapped["live_correlation"], args_full)
|
||||
check(f"frozen[{i}] project kernel-vs-frozen", got_kernel,
|
||||
wrapped["projection"])
|
||||
got_facade = facade_project(spec, cid, bridge, kwargs)
|
||||
check(f"frozen[{i}] project facade-kernel-vs-frozen", got_facade,
|
||||
wrapped["projection"])
|
||||
elif fn == "cpj_freeze_write_mini_doc":
|
||||
spec, cid = row["args"]
|
||||
entity = rust_entity(spec["load"], cid)
|
||||
check(f"frozen[{i}] mini-doc kernel-vs-frozen bytes",
|
||||
rust_write_mini_doc(entity, cid, row["expected"]["projection"]),
|
||||
row["expected"]["content"])
|
||||
check(f"frozen[{i}] mini-doc kernel-vs-frozen name",
|
||||
f"{cid}.md", row["expected"]["file"])
|
||||
|
||||
print(f"== facade both-pins fuzz ({len(fuzz)} project / "
|
||||
f"{len(facade_minidoc_rows)} mini-doc / {total} validate rows) ==")
|
||||
for i, (spec, cid, bridge, kwargs) in enumerate(fuzz):
|
||||
want = oracle["facade_project"][i]
|
||||
got_facade = facade_project(spec, cid, bridge, kwargs)
|
||||
check(f"facade fuzz[{i}]", got_facade, want)
|
||||
args_full = {**kwargs, "project_id": spec.get("project_id", "")}
|
||||
entity = rust_entity(spec["load"], cid)
|
||||
got_kernel = rust_project(entity, cid, want["waiting_state"],
|
||||
want["correlation_id"], args_full)
|
||||
check(f"kernel-direct fuzz[{i}]", got_kernel, want)
|
||||
for i, p in enumerate(oracle["facade_project"][:40]):
|
||||
check(f"validate fuzz[{i}]", cp_validate(p), oracle["facade_validate"][i])
|
||||
check(f"validate kernel-direct fuzz[{i}]", rust_validate(p),
|
||||
oracle["facade_validate"][i])
|
||||
for i, (spec, cid) in enumerate(facade_minidoc_rows):
|
||||
want = oracle["facade_minidoc"][i]
|
||||
scratch = tempfile.mkdtemp(prefix="cpjkern-")
|
||||
try:
|
||||
path = minidoc_kernel_facade(spec, cid, scratch)
|
||||
check(f"mini-doc facade-kernel[{i}] bytes", path.read_text(encoding="utf-8"),
|
||||
want["content"])
|
||||
check(f"mini-doc facade-kernel[{i}] name", path.name, want["file"])
|
||||
entity = rust_entity(spec["load"], cid)
|
||||
check(f"mini-doc kernel-direct[{i}] bytes",
|
||||
rust_write_mini_doc(entity, cid, want["projection"]),
|
||||
want["content"])
|
||||
finally:
|
||||
shutil.rmtree(scratch, ignore_errors=True)
|
||||
|
||||
print("== crash parity (kernel errs -> facade floor raises the oracle's exact exception) ==")
|
||||
for kind, factory in crash_shapes():
|
||||
args = factory()
|
||||
want = oracle["crash"][kind]
|
||||
try:
|
||||
got = facade_crash_call(kind, args)
|
||||
check(f"crash[{kind}]", f"NO_CRASH:{json.dumps(got, sort_keys=True)}", want)
|
||||
except Exception as e: # noqa: BLE001 — the crash IS the check
|
||||
check(f"crash[{kind}]", f"{type(e).__name__}: {e}", want)
|
||||
|
||||
if FAILS[0]:
|
||||
print(f"\nPARITY FAILED: {FAILS[0]} mismatches of {CHECKS[0]}")
|
||||
return 1
|
||||
print(f"\nPARITY OK: 100% — {CHECKS[0]} comparisons, rust == oracle == frozen "
|
||||
f"({total} frozen rows + {len(fuzz)} fuzz project + "
|
||||
f"{len(facade_minidoc_rows)} mini-doc + {len(crash_shapes())} crash)")
|
||||
return 0
|
||||
|
||||
|
||||
# ── facade calls in the CHECK process (kernel live — no pin set) ────────────
|
||||
def cp_contract_entity(doc, cid):
|
||||
from rune import construction_projection as cp
|
||||
|
||||
return cp._contract_entity(doc, cid)
|
||||
|
||||
|
||||
def cp_validate(p):
|
||||
from rune import construction_projection as cp
|
||||
|
||||
out = cp.validate(p)
|
||||
return out
|
||||
|
||||
|
||||
def _patch_bridge(bridge, fn, *args, **kwargs):
|
||||
from rune import event_wait as ew
|
||||
|
||||
real = ew.operation_wait
|
||||
ew.operation_wait = _check_bridge(bridge)
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
finally:
|
||||
ew.operation_wait = real
|
||||
|
||||
|
||||
def facade_project(spec, cid, bridge, kwargs):
|
||||
from rune import construction_projection as cp
|
||||
|
||||
p = _patch_bridge(bridge, cp.project, _KernelScaffold(spec), cid,
|
||||
**dict(kwargs))
|
||||
return p
|
||||
|
||||
|
||||
def minidoc_kernel_facade(spec, cid, out_dir):
|
||||
from rune import construction_projection as cp
|
||||
|
||||
return cp.write_mini_doc(_KernelScaffold(spec), cid, out_dir=out_dir)
|
||||
|
||||
|
||||
def facade_crash_call(kind, args):
|
||||
"""The facade path with the kernel live: kernel errs → floor raises."""
|
||||
from rune import construction_projection as cp
|
||||
|
||||
if kind in ("entity_nondict", "entity_null_entities"):
|
||||
return cp._contract_entity(args[0], args[1])
|
||||
if kind == "validate_nondict":
|
||||
return cp.validate(args[0])
|
||||
if kind in ("blocker_int", "checkpoint_int"):
|
||||
return cp.project(_KernelScaffold({"load": args[0]}), args[1])
|
||||
if kind in ("surface_int", "evidence_int"):
|
||||
return cp.write_mini_doc(_KernelScaffold({"load": args[0]}), args[1])
|
||||
raise AssertionError(kind)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,134 @@
|
||||
"""sitecustomize — the construction_projection freeze rig (dichotomy wave
|
||||
gap close).
|
||||
|
||||
Auto-imported via PYTHONPATH=scripts/cproj_freeze_seams by BOTH the truth
|
||||
generator and `freeze_truth.py --verify`, so the --verify law (replay
|
||||
against the pristine oracle) still holds: the seams below only make the
|
||||
oracle's already-existing behavior reachable with JSON-safe shapes.
|
||||
|
||||
* RUNE_CONSTRUCTION_PROJECTION_CORE=python pinned (the oracle floor; the
|
||||
harness's _pin_oracle_cores does this too — belt and braces).
|
||||
* Module seams bound onto rune.construction_projection:
|
||||
- cpj_freeze_contract_entity(doc, contract_id) — the REAL floor lookup
|
||||
with JSON-safe args.
|
||||
- cpj_freeze_project(scaffold_spec, contract_id, bridge, kwargs) —
|
||||
rebuilds a JSON-specified fake scaffold, pins
|
||||
rune.event_wait.operation_wait per the bridge row, drives the REAL
|
||||
floor project(), and echoes the adapter-resolved bridge scalars
|
||||
(waiting_state/live_correlation) the Rust battery needs as kernel
|
||||
inputs (the SPEC-grammar law: the battery rebuilds its calls from
|
||||
the same rows).
|
||||
- cpj_freeze_validate(projection) — the REAL floor validator.
|
||||
- cpj_freeze_write_mini_doc(scaffold_spec, contract_id) — drives the
|
||||
REAL floor write_mini_doc into a hermetic FIXED-path scratch dir
|
||||
(/tmp/cpj_gap_freeze, wiped per call), echoes the markdown bytes
|
||||
and the file NAME (never an absolute path — no machine-specific
|
||||
paths in frozen truth).
|
||||
|
||||
The fake bridge never touches the real waitstore: `bridge = None` (no
|
||||
lookup), {} (operation id points at no record → None), a record dict
|
||||
(rec found), or {"__raise__": "OSError"|"RuntimeError"} (the two degrading
|
||||
failure classes the oracle catches → BRIDGE_UNREACHABLE).
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["RUNE_CONSTRUCTION_PROJECTION_CORE"] = "python"
|
||||
|
||||
REPO = Path(os.environ.get("CPJ_FREEZE_REPO", "")) or Path.cwd()
|
||||
if str(REPO / "src") not in sys.path:
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
SCRATCH = "/tmp/cpj_gap_freeze"
|
||||
if os.path.isdir(SCRATCH):
|
||||
shutil.rmtree(SCRATCH)
|
||||
os.makedirs(SCRATCH, exist_ok=True)
|
||||
OUT_DIR = os.path.join(SCRATCH, "docs")
|
||||
|
||||
import rune.construction_projection as _cp # noqa: E402
|
||||
from rune import event_wait as _ew # noqa: E402
|
||||
|
||||
REAL_OPERATION_WAIT = _ew.operation_wait
|
||||
|
||||
|
||||
class _FakeScaffold:
|
||||
"""JSON-rebuildable scaffold stand-in: the two attributes the oracle
|
||||
touches (load() result, project_id). No store, no IO."""
|
||||
|
||||
def __init__(self, spec: dict):
|
||||
doc = spec.get("load")
|
||||
self._doc = {} if doc is None else doc
|
||||
if "project_id" in spec:
|
||||
self.project_id = spec["project_id"]
|
||||
|
||||
def load(self):
|
||||
import copy
|
||||
|
||||
return copy.deepcopy(self._doc)
|
||||
|
||||
|
||||
def _fake_operation_wait(bridge):
|
||||
mode = (bridge or {}).get("__mode__", "none")
|
||||
|
||||
def _wait(operation_id):
|
||||
if mode == "oserr":
|
||||
raise OSError(2, "bridge data root unreadable (freeze-pinned)")
|
||||
if mode == "runtimeerr":
|
||||
raise RuntimeError("waitstore not resolvable (freeze-pinned)")
|
||||
if mode == "found":
|
||||
return dict((bridge or {}).get("record") or {})
|
||||
if mode in ("none", "missing"):
|
||||
return None
|
||||
raise ValueError(f"unknown bridge mode {mode!r}")
|
||||
|
||||
return _wait
|
||||
|
||||
|
||||
def _with_bridge(bridge, fn, *args, **kwargs):
|
||||
_ew.operation_wait = _fake_operation_wait(bridge)
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
finally:
|
||||
_ew.operation_wait = REAL_OPERATION_WAIT
|
||||
|
||||
|
||||
def cpj_freeze_contract_entity(doc, contract_id):
|
||||
return _cp._contract_entity(doc, contract_id)
|
||||
|
||||
|
||||
def cpj_freeze_project(scaffold_spec, contract_id, bridge, **kwargs):
|
||||
scaffold = _FakeScaffold(scaffold_spec)
|
||||
result = _with_bridge(bridge, _cp.project, scaffold, contract_id, **kwargs)
|
||||
waiting_state = result.get("waiting_state", "")
|
||||
live_correlation = result.get("correlation_id", "")
|
||||
return {
|
||||
"projection": result,
|
||||
"waiting_state": waiting_state,
|
||||
"live_correlation": live_correlation,
|
||||
}
|
||||
|
||||
|
||||
def cpj_freeze_validate(projection):
|
||||
return _cp.validate(projection)
|
||||
|
||||
|
||||
def cpj_freeze_write_mini_doc(scaffold_spec, contract_id):
|
||||
scaffold = _FakeScaffold(scaffold_spec)
|
||||
if os.path.isdir(OUT_DIR):
|
||||
shutil.rmtree(OUT_DIR)
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
path = _cp.write_mini_doc(scaffold, contract_id, out_dir=OUT_DIR)
|
||||
return {
|
||||
"file": os.path.basename(str(path)),
|
||||
"content": Path(path).read_text(encoding="utf-8"),
|
||||
"projection": _cp.project(scaffold, contract_id),
|
||||
}
|
||||
|
||||
|
||||
_cp.cpj_freeze_contract_entity = cpj_freeze_contract_entity
|
||||
_cp.cpj_freeze_project = cpj_freeze_project
|
||||
_cp.cpj_freeze_validate = cpj_freeze_validate
|
||||
_cp.cpj_freeze_write_mini_doc = cpj_freeze_write_mini_doc
|
||||
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_construction_projection_truth.py — factory wave GAP truth for
|
||||
construction_projection.
|
||||
|
||||
Freezes the oracle behavior of ALL FOUR public functions (the audit named
|
||||
three gaps: _contract_entity, validate, write_mini_doc; project's
|
||||
pre-existing kernel path is replayed too) through the SHARED harness
|
||||
(scripts/freeze_truth.py — one law).
|
||||
|
||||
Runs under the freeze rig (scripts/cproj_freeze_seams/sitecustomize.py —
|
||||
PYTHONPATH-imported by BOTH this generator and --verify): pinned floor
|
||||
(RUNE_CONSTRUCTION_PROJECTION_CORE=python), a JSON-specified fake scaffold
|
||||
(load() doc + project_id), and a pinned fake bridge for
|
||||
rune.event_wait.operation_wait (found / no-record / OSError /
|
||||
RuntimeError / no-lookup rows). write_mini_doc rows write into a hermetic
|
||||
fixed-path scratch dir and freeze the markdown BYTES + the file NAME —
|
||||
never an absolute path (quirks: machine-specific paths).
|
||||
|
||||
The PROJECT rows carry the oracle kwargs in the harness-kwargs slot, and
|
||||
the rig echoes the adapter-resolved bridge scalars (waiting_state /
|
||||
live_correlation) inside the expected wrapper — the Rust battery rebuilds
|
||||
its kernel calls from the same rows (the SPEC-grammar law).
|
||||
|
||||
Run (from the repo root):
|
||||
PYTHONHASHSEED=0 PYTHONPATH=scripts/cproj_freeze_seams:src \
|
||||
python3 scripts/gen_construction_projection_truth.py
|
||||
|
||||
Verify (byte-identical, same rig):
|
||||
PYTHONHASHSEED=0 PYTHONPATH=scripts/cproj_freeze_seams:src \
|
||||
python3 scripts/freeze_truth.py --verify \
|
||||
src/rust/construction_projection/src/frozen_truth.verify.json \
|
||||
--module rune.construction_projection
|
||||
|
||||
Case map (every gap branch — freezable shapes; the oracle's CRASH shapes
|
||||
(null entities, non-dict projection, non-str slicing, non-str blocker
|
||||
evidence) are crash-parity rows in scripts/check_construction_projection_
|
||||
parity.py, un-freezable through the shared harness):
|
||||
* cpj_freeze_contract_entity — match / no-match (absent key) /
|
||||
no-match (different contract) / empty entities / missing entities key /
|
||||
empty contract_id (matches id "contract:") / first-of-duplicates.
|
||||
* cpj_freeze_validate — valid full projection (empty list) / missing one
|
||||
field / missing several (FIELDS order) / schema_version 2 / 'X' /
|
||||
None / True (bool-subclass equality — VALID, True == 1) / 1.0
|
||||
(numeric equality — valid) / [1] / {'a': 1} / all-missing (24
|
||||
missing-field errors in order).
|
||||
* cpj_freeze_project — empty scaffold (defaults) / full entity
|
||||
(the W6-era canary regression shape) / blocker dict (class default +
|
||||
>120-char evidence truncation) / blocker string / evidence truncation +
|
||||
empty skipped / approval.required truthy non-bool ('yes') / state ==
|
||||
REQUIRED / phase awaiting_approval / falsy 0 / canaries (null rides /
|
||||
missing defaults) / mode+depth falsy collapse / operation-wait found
|
||||
(correlation passthrough + empty-correlation wait_id fallback) /
|
||||
no-record / OSError / RuntimeError / no lookup (empty operation_id).
|
||||
* cpj_freeze_write_mini_doc — full entity / objective fallback chain
|
||||
(need → ext.need.objective → placeholder) / surface list / single string
|
||||
/ decision_trace fallback / ids fallback / no surface / quality dict
|
||||
(sort_keys rendering) / empty + missing quality / task_ref present /
|
||||
absent / evidence rows / none recorded / empty-surface-list falsiness.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "scripts"))
|
||||
|
||||
from freeze_truth import freeze, write, write_envelope # noqa: E402
|
||||
|
||||
NOW = "2026-10-14T12:00:00.000000"
|
||||
|
||||
LONG = "e" * 200 # >120 chars — the [:120] codepoint-truncation quirk
|
||||
|
||||
|
||||
def doc_of(*entities):
|
||||
return {"entities": list(entities)}
|
||||
|
||||
|
||||
def entity(cid, **kw):
|
||||
e = {"id": f"contract:{cid}"}
|
||||
e.update(kw)
|
||||
return e
|
||||
|
||||
|
||||
FULL_ENTITY = {
|
||||
"id": "contract:B-1",
|
||||
"phase": "verifying",
|
||||
"identities": {
|
||||
"construction_mode": "BUILD_NEW",
|
||||
"depth": "standard",
|
||||
"next_action": "verify",
|
||||
},
|
||||
"ext": {
|
||||
"readiness_canary": "ready",
|
||||
"liveness_canary": "live",
|
||||
"exit_canary": "exit",
|
||||
"decision_trace": {"approval": {"required": True}},
|
||||
},
|
||||
"checkpoints": [{"evidence": "ev-1"}, {"evidence": ""}],
|
||||
}
|
||||
|
||||
PROJECTION_KWARGS = dict(stage="st1", builder_state="building")
|
||||
FULL_KWARGS = dict(
|
||||
originating_goal_id="goal-9",
|
||||
operation_id="",
|
||||
correlation_id="corr-1",
|
||||
stage="st1",
|
||||
builder_state="building",
|
||||
verification_state="verified",
|
||||
checkpoint="cp-1",
|
||||
failure_class="none",
|
||||
completion_state="done",
|
||||
)
|
||||
|
||||
# ── _contract_entity ────────────────────────────────────────────────────────
|
||||
contract_entity_cases = [
|
||||
("cpj_freeze_contract_entity", (doc_of(entity("B-1", phase="verifying")), "B-1"), {}),
|
||||
("cpj_freeze_contract_entity", (doc_of(entity("A-1"), entity("B-2")), "B-9"), {}),
|
||||
("cpj_freeze_contract_entity", (doc_of(entity("B-1", phase="first"), entity("B-1", phase="dup")), "B-1"), {}),
|
||||
("cpj_freeze_contract_entity", ({"entities": []}, "B-1"), {}),
|
||||
("cpj_freeze_contract_entity", ({}, "B-1"), {}),
|
||||
("cpj_freeze_contract_entity", ({}, ""), {}),
|
||||
("cpj_freeze_contract_entity", (doc_of(entity("", phase="empty")), ""), {}),
|
||||
]
|
||||
|
||||
# ── validate ────────────────────────────────────────────────────────────────
|
||||
VALID = {f: f"{f}-v" for f in (
|
||||
"schema_version", "project_id", "originating_goal_id", "construction_id",
|
||||
"contract_id", "mode", "depth", "phase", "stage", "operation_id",
|
||||
"waiting_state", "correlation_id", "builder_state", "blockers",
|
||||
"approval_required", "readiness_canary", "liveness_canary", "exit_canary",
|
||||
"verification_state", "checkpoint", "evidence_refs", "failure_class",
|
||||
"completion_state", "candidate_next_action",
|
||||
)}
|
||||
VALID["schema_version"] = 1
|
||||
VALID["blockers"] = []
|
||||
VALID["evidence_refs"] = []
|
||||
VALID["approval_required"] = False
|
||||
|
||||
missing_one = dict(VALID)
|
||||
del missing_one["depth"]
|
||||
missing_several = dict(VALID)
|
||||
for gone in ("stage", "liveness_canary", "checkpoint"): # FIELDS order
|
||||
del missing_several[gone]
|
||||
all_missing = {"schema_version": 1}
|
||||
|
||||
validate_cases = []
|
||||
for _name, proj in (
|
||||
("valid_full", dict(VALID)),
|
||||
("missing_one", missing_one),
|
||||
("missing_several", missing_several),
|
||||
("all_missing", all_missing),
|
||||
("schema_2", dict(VALID, schema_version=2)),
|
||||
("schema_str", dict(VALID, schema_version="X")),
|
||||
("schema_none", dict(VALID, schema_version=None)),
|
||||
("schema_true", dict(VALID, schema_version=True)), # bool subclass → valid
|
||||
("schema_1_0", dict(VALID, schema_version=1.0)), # numeric equality → valid
|
||||
("schema_list", dict(VALID, schema_version=[1])),
|
||||
("schema_dict", dict(VALID, schema_version={"a": 1})),
|
||||
):
|
||||
validate_cases.append(("cpj_freeze_validate", (proj,), {}))
|
||||
|
||||
# ── project ─────────────────────────────────────────────────────────────────
|
||||
project_cases = [
|
||||
# (args: scaffold_spec, contract_id, bridge_spec-or-None, kwargs: oracle kwargs)
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-1", None, {}),
|
||||
({"load": {}}, "B-1", None, {}), # no project_id attr → getattr "" default
|
||||
({"load": doc_of(FULL_ENTITY), "project_id": "testproj"}, "B-1", None,
|
||||
dict(PROJECTION_KWARGS)),
|
||||
({"load": doc_of(FULL_ENTITY), "project_id": "testproj"}, "B-1", None,
|
||||
dict(FULL_KWARGS)),
|
||||
# blocker dict: class default + >120 evidence truncation.
|
||||
({"load": doc_of(entity(
|
||||
"B-2", identities={"blocker": {"evidence": LONG}}))}, "B-2", None, {}),
|
||||
({"load": doc_of(entity(
|
||||
"B-3", identities={"blocker": {"class": "SCOPE", "evidence": "short"}}))},
|
||||
"B-3", None, {}),
|
||||
# blocker string variant + the empty-string falsy skip.
|
||||
({"load": doc_of(entity(
|
||||
"B-4", identities={"blocker": "hard blocker string"}))}, "B-4", None, {}),
|
||||
({"load": doc_of(entity(
|
||||
"B-5", identities={"blocker": ""}))}, "B-5", None, {}),
|
||||
# evidence truncation + empty rows skipped.
|
||||
({"load": doc_of(entity(
|
||||
"B-6",
|
||||
checkpoints=[{"evidence": LONG}, {"evidence": ""}, {"no": "e"}]))},
|
||||
"B-6", None, {}),
|
||||
# approval_required legs: truthy non-bool / state REQUIRED / phase.
|
||||
({"load": doc_of(entity(
|
||||
"B-7", ext={"decision_trace": {"approval": {"required": "yes"}}}))},
|
||||
"B-7", None, {}),
|
||||
({"load": doc_of(entity(
|
||||
"B-8", ext={"decision_trace": {
|
||||
"approval": {"required": False, "state": "REQUIRED"}}}))},
|
||||
"B-8", None, {}),
|
||||
({"load": doc_of(entity("B-9", phase="awaiting_approval"))}, "B-9", None, {}),
|
||||
# falsy 0 approval + missing trace entirely.
|
||||
({"load": doc_of(entity(
|
||||
"B-10", ext={"decision_trace": {"approval": {"required": 0}}}))},
|
||||
"B-10", None, {}),
|
||||
# canaries: null RIDES (dict.get returns None — the default applies only
|
||||
# when MISSING) + the others present.
|
||||
({"load": doc_of(entity("B-11", ext={
|
||||
"readiness_canary": None, "liveness_canary": "live"}))}, "B-11", None, {}),
|
||||
# mode/depth falsy collapse ("") + null next_action.
|
||||
({"load": doc_of(entity(
|
||||
"B-12", identities={"construction_mode": "", "depth": 0,
|
||||
"next_action": None}))},
|
||||
"B-12", None, {}),
|
||||
# ── bridge rows ──
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-13",
|
||||
{"__mode__": "found", "record": {"status": "WAITING_EXTERNAL", "wait_id": "w-9"}},
|
||||
dict(PROJECTION_KWARGS, operation_id="op-x")),
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-14",
|
||||
{"__mode__": "found", "record": {"status": "READY_TO_RESUME",
|
||||
"wait_id": "w-10",
|
||||
"ext": {"operation_id": "op-y"}}},
|
||||
dict(PROJECTION_KWARGS, operation_id="op-y", correlation_id="corr-2")),
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-15",
|
||||
None, dict(PROJECTION_KWARGS, operation_id="op-z")),
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-16",
|
||||
{"__mode__": "oserr"}, dict(PROJECTION_KWARGS, operation_id="op-e")),
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-17",
|
||||
{"__mode__": "runtimeerr"}, dict(PROJECTION_KWARGS, operation_id="op-r")),
|
||||
({"load": doc_of(), "project_id": "testproj"}, "B-18",
|
||||
{"__mode__": "found", "record": {"status": "S", "wait_id": "w-1"}},
|
||||
dict(PROJECTION_KWARGS, operation_id="")),
|
||||
]
|
||||
|
||||
|
||||
def project_row(spec, cid, bridge, kwargs):
|
||||
return ("cpj_freeze_project", (spec, cid, bridge), kwargs)
|
||||
|
||||
|
||||
# ── write_mini_doc ──────────────────────────────────────────────────────────
|
||||
WIDE_ENTITY = {
|
||||
"id": "contract:M-1",
|
||||
"phase": "closed",
|
||||
"identities": {
|
||||
"objective": "build the mini-doc path",
|
||||
"construction_mode": "BUILD_NEW",
|
||||
"depth": "standard",
|
||||
"task_ref": "T16.9",
|
||||
},
|
||||
"ext": {
|
||||
"affected_paths": ["src/x.py", "tests/x.py"],
|
||||
"contract_quality": {"correctly_predicted": 3, "genuinely_unexpected_new": 0,
|
||||
"scope_check": "green", "zebra_last": True},
|
||||
"readiness_canary": "ready",
|
||||
},
|
||||
"checkpoints": [{"evidence": "evidence-a"}, {"evidence": "evidence-b"}],
|
||||
}
|
||||
|
||||
mini_doc_specs = [
|
||||
# full entity: objective + list surface + quality + task_ref + evidence.
|
||||
({"load": doc_of(WIDE_ENTITY), "project_id": "testproj"}, "M-1"),
|
||||
# objective fallback chain: identities.need.
|
||||
({"load": doc_of(entity("M-2", identities={"need": "the need text"})),
|
||||
"project_id": "p"}, "M-2"),
|
||||
# objective fallback chain: ext.need.objective.
|
||||
({"load": doc_of(entity("M-3", ext={"need": {"objective": "deep objective"}})),
|
||||
"project_id": "p"}, "M-3"),
|
||||
# no objective at all → placeholder line.
|
||||
({"load": doc_of(entity("M-4", identities={})), "project_id": "p"}, "M-4"),
|
||||
# surface: single string wrapped.
|
||||
({"load": doc_of(entity("M-5", ext={"affected_paths": "src/only.py"})),
|
||||
"project_id": "p"}, "M-5"),
|
||||
# surface: decision_trace.affected_surfaces.affected_paths fallback.
|
||||
({"load": doc_of(entity("M-6", ext={"decision_trace": {
|
||||
"affected_surfaces": {"affected_paths": ["src/c.py"]}}})),
|
||||
"project_id": "p"}, "M-6"),
|
||||
# surface: ids.affected_surface fallback.
|
||||
({"load": doc_of(entity("M-7", identities={"affected_surface": "src/d.py"})),
|
||||
"project_id": "p"}, "M-7"),
|
||||
# no surface anywhere → "(no affected surface recorded)".
|
||||
({"load": doc_of(entity("M-8", identities={}, ext={})), "project_id": "p"}, "M-8"),
|
||||
# quality empty dict → 'not recorded'.
|
||||
({"load": doc_of(entity("M-9", ext={"contract_quality": {}})),
|
||||
"project_id": "p"}, "M-9"),
|
||||
# quality missing → 'not recorded'.
|
||||
({"load": doc_of(entity("M-10", ext={})), "project_id": "p"}, "M-10"),
|
||||
# no task_ref → trailing period only.
|
||||
({"load": doc_of(entity("M-11", identities={"objective": "o"})),
|
||||
"project_id": "p"}, "M-11"),
|
||||
# no checkpoints → "(none recorded)" evidence section.
|
||||
({"load": doc_of(entity("M-12", identities={"objective": "o"})),
|
||||
"project_id": "p"}, "M-12"),
|
||||
# empty surface LIST truthiness edge ([] → falsy → placeholder).
|
||||
({"load": doc_of(entity("M-13", ext={"affected_paths": []})),
|
||||
"project_id": "p"}, "M-13"),
|
||||
]
|
||||
|
||||
mini_doc_cases = [
|
||||
("cpj_freeze_write_mini_doc", (spec, cid), {})
|
||||
for spec, cid in mini_doc_specs
|
||||
]
|
||||
|
||||
cases = (
|
||||
contract_entity_cases
|
||||
+ validate_cases
|
||||
+ [project_row(*r) for r in project_cases]
|
||||
+ mini_doc_cases
|
||||
)
|
||||
|
||||
truth = freeze("rune.construction_projection", cases,
|
||||
oracle_module="rune.construction_projection")
|
||||
OUT = REPO / "src" / "rust" / "construction_projection" / "src"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
write_envelope(NOW, truth, OUT / "frozen_truth.json")
|
||||
write(truth, OUT / "frozen_truth.verify.json")
|
||||
print(f"wrote {len(truth)} cases -> {OUT / 'frozen_truth.json'} (+ .verify.json)")
|
||||
|
||||
doc = json.loads(json.dumps(truth))
|
||||
print("case mix:", {k: sum(1 for c in doc if c["fn"] == k)
|
||||
for k in sorted({c["fn"] for c in doc})})
|
||||
@@ -15,6 +15,7 @@ ignored by readers (forward-tolerant); removed fields bump the major.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -50,13 +51,48 @@ FIELDS = (
|
||||
)
|
||||
|
||||
|
||||
def _core():
|
||||
"""The Rust projection core, or None when pinned to the oracle / not
|
||||
built. RUNE_CONSTRUCTION_PROJECTION_CORE=python pins the oracle; the
|
||||
kernel is a drop-in and every caller falls back to the oracle path on
|
||||
any kernel error (construction_pattern._core shape)."""
|
||||
if os.environ.get("RUNE_CONSTRUCTION_PROJECTION_CORE", "auto") == "python":
|
||||
return None
|
||||
try:
|
||||
from rune import cproj_loader
|
||||
|
||||
return cproj_loader if cproj_loader.available() else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _contract_entity(doc: dict, contract_id: str) -> dict | None:
|
||||
core = _core()
|
||||
if core is not None:
|
||||
try:
|
||||
found = core.contract_entity(doc, contract_id)
|
||||
if found is not None:
|
||||
return found
|
||||
return None
|
||||
except Exception:
|
||||
pass # kernel failure → Python floor below
|
||||
for e in doc.get("entities", []):
|
||||
if e.get("id") == f"contract:{contract_id}":
|
||||
return e
|
||||
return None
|
||||
|
||||
|
||||
def _py_emit_mini_doc(text: str, out_dir, contract_id: str) -> "Path":
|
||||
"""Adapter-only file emission of the mini-doc markdown (no file IO in
|
||||
kernels — this is the persistence boundary)."""
|
||||
out = out_dir if out_dir is not None else (Path(__file__).resolve().parents[2] / "store" / "scaffold" / "docs")
|
||||
out = Path(out)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
path = out / f"{contract_id}.md"
|
||||
path.write_text(text, encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def project(
|
||||
scaffold,
|
||||
contract_id: str,
|
||||
@@ -74,38 +110,49 @@ def project(
|
||||
"""Build the stable public projection for one contract. `scaffold` is
|
||||
a Rune Scaffold instance (the canonical store); all live fields are
|
||||
derived from it. Optional live fields (waiting_state etc.) are filled
|
||||
from the event bridge when operation_id is supplied."""
|
||||
from the event bridge when operation_id is supplied.
|
||||
|
||||
Dichotomy gap close: delegates the deterministic dict assembly to the
|
||||
Rust kernel when built (RUNE_CONSTRUCTION_PROJECTION_CORE=python pins
|
||||
the Python floor). The operation-wait lookup stays in the adapter —
|
||||
the kernel never sees the bridge."""
|
||||
doc = scaffold.load()
|
||||
c = _contract_entity(doc, contract_id) or {}
|
||||
# Kernel delegation: the projection dict assembly is deterministic
|
||||
# (frozen_projection.json parity); the operation-wait lookup stays here.
|
||||
try:
|
||||
from rune import cp_loader as _cp
|
||||
if _cp.available():
|
||||
waiting_state = ""
|
||||
live_correlation = correlation_id
|
||||
if operation_id:
|
||||
try:
|
||||
from rune import event_wait as ew
|
||||
rec = ew.operation_wait(operation_id)
|
||||
if rec is not None:
|
||||
waiting_state = rec.get("status", "")
|
||||
live_correlation = correlation_id or rec.get("wait_id", "")
|
||||
except (OSError, RuntimeError):
|
||||
waiting_state = "BRIDGE_UNREACHABLE"
|
||||
return _cp.project(c, contract_id=contract_id,
|
||||
project_id=getattr(scaffold, "project_id", ""),
|
||||
originating_goal_id=originating_goal_id,
|
||||
operation_id=operation_id,
|
||||
waiting_state=waiting_state,
|
||||
live_correlation=live_correlation,
|
||||
stage=stage, builder_state=builder_state,
|
||||
verification_state=verification_state,
|
||||
checkpoint=checkpoint,
|
||||
failure_class=failure_class,
|
||||
completion_state=completion_state)
|
||||
except Exception: # noqa: BLE001 — kernel failure → Python floor below
|
||||
pass
|
||||
if _core() is not None:
|
||||
# Bridge lookup FIRST (adapter-owned); the kernel receives the
|
||||
# resolved waiting_state/live_correlation scalars.
|
||||
waiting_state = ""
|
||||
live_correlation = correlation_id
|
||||
if operation_id:
|
||||
try:
|
||||
from rune import event_wait as ew
|
||||
|
||||
rec = ew.operation_wait(operation_id)
|
||||
if rec is not None:
|
||||
waiting_state = rec.get("status", "")
|
||||
live_correlation = correlation_id or rec.get("wait_id", "")
|
||||
except OSError: # bridge data root unreadable → degrade, never lie
|
||||
waiting_state = "BRIDGE_UNREACHABLE"
|
||||
except RuntimeError: # waitstore not resolvable in this context
|
||||
waiting_state = "BRIDGE_UNREACHABLE"
|
||||
try:
|
||||
from rune import cproj_loader as _cp
|
||||
|
||||
if _cp.available():
|
||||
return _cp.project(
|
||||
c, contract_id=contract_id,
|
||||
project_id=getattr(scaffold, "project_id", ""),
|
||||
originating_goal_id=originating_goal_id,
|
||||
operation_id=operation_id,
|
||||
waiting_state=waiting_state,
|
||||
live_correlation=live_correlation,
|
||||
stage=stage, builder_state=builder_state,
|
||||
verification_state=verification_state,
|
||||
checkpoint=checkpoint,
|
||||
failure_class=failure_class,
|
||||
completion_state=completion_state)
|
||||
except Exception: # noqa: BLE001 — kernel failure → Python floor below
|
||||
pass
|
||||
ids = c.get("identities", {})
|
||||
ext = c.get("ext", {})
|
||||
trace = ext.get("decision_trace", {})
|
||||
@@ -174,7 +221,16 @@ def project(
|
||||
|
||||
def validate(projection: dict) -> list[str]:
|
||||
"""Check a projection against the schema. Returns a list of errors
|
||||
(empty = valid). Consumers use this before trusting a projection."""
|
||||
(empty = valid). Consumers use this before trusting a projection.
|
||||
|
||||
Dichotomy gap close: the error-string building delegates to the Rust
|
||||
kernel when built; the floor below is the parity authority."""
|
||||
core = _core()
|
||||
if core is not None:
|
||||
try:
|
||||
return core.validate(projection)
|
||||
except Exception:
|
||||
pass # kernel failure → Python floor below
|
||||
errors = []
|
||||
if projection.get("schema_version") != SCHEMA_VERSION:
|
||||
errors.append(f"schema_version: expected {SCHEMA_VERSION}, got {projection.get('schema_version')!r}")
|
||||
@@ -195,13 +251,25 @@ def write_mini_doc(scaffold, contract_id: str, out_dir=None) -> "Path":
|
||||
Best-effort by contract: a writer failure raises OSError-family
|
||||
errors the CALLER chooses to degrade on (the closed transition
|
||||
catches and continues — a broken doc never fails a real closure).
|
||||
"""
|
||||
|
||||
Dichotomy gap close: the markdown BUILD delegates to the Rust kernel
|
||||
when built; the file WRITE always stays here (no file IO in kernels).
|
||||
The floor below is the parity authority for the bytes."""
|
||||
doc = scaffold.load()
|
||||
c = _contract_entity(doc, contract_id) or {}
|
||||
ids = c.get("identities", {})
|
||||
ext = c.get("ext", {})
|
||||
projection = project(scaffold, contract_id)
|
||||
|
||||
core = _core()
|
||||
if core is not None:
|
||||
try:
|
||||
text = core.write_mini_doc(c, contract_id, projection)
|
||||
except Exception:
|
||||
text = None # kernel failure → Python floor below
|
||||
if text is not None:
|
||||
return _py_emit_mini_doc(text, out_dir, contract_id)
|
||||
|
||||
objective = ids.get("objective") or ids.get("need") or c.get("ext", {}).get("need", {}).get("objective", "")
|
||||
surface = (
|
||||
ext.get("affected_paths")
|
||||
@@ -246,9 +314,4 @@ def write_mini_doc(scaffold, contract_id: str, out_dir=None) -> "Path":
|
||||
lines += [f"- {e}" for e in evidence] if evidence else ["(none recorded)"]
|
||||
lines.append("")
|
||||
|
||||
out = out_dir if out_dir is not None else (Path(__file__).resolve().parents[2] / "store" / "scaffold" / "docs")
|
||||
out = Path(out)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
path = out / f"{contract_id}.md"
|
||||
path.write_text("\n".join(lines), encoding="utf-8")
|
||||
return path
|
||||
return _py_emit_mini_doc("\n".join(lines), out_dir, contract_id)
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust construction_projection core (dichotomy wave
|
||||
gap close, #2479 tranche). Mirrors cm_loader.py's contract:
|
||||
|
||||
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
|
||||
* cp_project({contract_entity, contract_id, project_id,
|
||||
originating_goal_id, operation_id, waiting_state,
|
||||
live_correlation, stage, builder_state, verification_state,
|
||||
checkpoint, failure_class, completion_state})
|
||||
-> the projection dict (24 §16I fields); the operation-wait lookup
|
||||
stays in the adapter — the kernel never sees the bridge
|
||||
* cp_contract_entity({doc, contract_id}) -> the matched entity or null
|
||||
* cp_validate({projection}) -> list of error strings (empty = valid)
|
||||
* cp_write_mini_doc({contract_entity, contract_id, projection})
|
||||
-> the markdown TEXT (the adapter owns every byte of file IO)
|
||||
|
||||
Err envelopes mark the input shapes where the Python oracle crashes; the
|
||||
facade falls back to the floor and reproduces the same exception exactly
|
||||
(crash-parity law). The kernel is pure: no file IO, no wall clock.
|
||||
|
||||
Override the .so with CPROJ_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" / "construction_projection"
|
||||
_LIB_PATH = Path(os.environ.get("CPROJ_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconstruction_projection.so"))
|
||||
|
||||
_lib = None
|
||||
|
||||
|
||||
class ConstructionProjectionUnavailable(FileNotFoundError):
|
||||
"""The Rust projection 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 ConstructionProjectionUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in ("cp_project", "cp_contract_entity", "cp_validate", "cp_write_mini_doc"):
|
||||
getattr(lib, sym).argtypes = [ctypes.c_void_p]
|
||||
getattr(lib, sym).restype = ctypes.c_void_p
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call_ffi(fn_name: str, payload: dict):
|
||||
lib = _load()
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, fn_name)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{fn_name} returned null")
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
free = getattr(lib, "cp_free_string", None)
|
||||
if free is not None:
|
||||
free.argtypes = [ctypes.c_void_p]
|
||||
free.restype = None
|
||||
free(ptr)
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
raise RuntimeError(doc.get("err", f"{fn_name} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def project(
|
||||
contract_entity: dict,
|
||||
contract_id: str = "",
|
||||
*,
|
||||
project_id: str = "",
|
||||
originating_goal_id: str = "",
|
||||
operation_id: str = "",
|
||||
waiting_state: str = "",
|
||||
live_correlation: str = "",
|
||||
stage: str = "",
|
||||
builder_state: str = "",
|
||||
verification_state: str = "",
|
||||
checkpoint: str = "",
|
||||
failure_class: str = "",
|
||||
completion_state: str = "",
|
||||
) -> dict:
|
||||
"""The 24-field §16I projection for one contract entity."""
|
||||
return _call_ffi("cp_project", {
|
||||
"contract_entity": dict(contract_entity),
|
||||
"contract_id": contract_id,
|
||||
"project_id": project_id,
|
||||
"originating_goal_id": originating_goal_id,
|
||||
"operation_id": operation_id,
|
||||
"waiting_state": waiting_state,
|
||||
"live_correlation": live_correlation,
|
||||
"stage": stage,
|
||||
"builder_state": builder_state,
|
||||
"verification_state": verification_state,
|
||||
"checkpoint": checkpoint,
|
||||
"failure_class": failure_class,
|
||||
"completion_state": completion_state,
|
||||
})
|
||||
|
||||
|
||||
def contract_entity(doc: dict, contract_id: str):
|
||||
"""The contract entity with id `contract:<contract_id>`, or None."""
|
||||
return _call_ffi("cp_contract_entity", {"doc": doc, "contract_id": contract_id})
|
||||
|
||||
|
||||
def validate(projection: dict) -> list:
|
||||
"""Schema check over the wire — the error strings come back exact."""
|
||||
return _call_ffi("cp_validate", {"projection": projection})
|
||||
|
||||
|
||||
def write_mini_doc(contract_entity: dict, contract_id: str, projection: dict) -> str:
|
||||
"""The markdown text of the #2465 mini-doc; the adapter writes it."""
|
||||
return _call_ffi("cp_write_mini_doc", {
|
||||
"contract_entity": contract_entity,
|
||||
"contract_id": contract_id,
|
||||
"projection": projection,
|
||||
})
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""canary_construction_projection.py — parity battery for construction_projection."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CRATE_DIR = Path(__file__).resolve().parent
|
||||
PASS, FAIL = 0, 0
|
||||
|
||||
|
||||
def test(name, cond, detail=""):
|
||||
global PASS, FAIL
|
||||
if cond:
|
||||
PASS += 1
|
||||
else:
|
||||
FAIL += 1
|
||||
print(f" ✗ {name} — {detail}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
r = subprocess.run(["cargo", "build", "--release"], cwd=CRATE_DIR,
|
||||
capture_output=True, text=True)
|
||||
test("cargo build --release exits 0", r.returncode == 0, r.stderr[-300:])
|
||||
if r.returncode != 0:
|
||||
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
|
||||
return 1
|
||||
sys.path.insert(0, str(CRATE_DIR.parents[2] / "src"))
|
||||
sys.path.insert(0, str(CRATE_DIR))
|
||||
from rune import cp_loader # noqa: E402
|
||||
frozen = json.loads((CRATE_DIR / "frozen_projection.json").read_text())
|
||||
got = cp_loader.project(frozen["contract_entity"],
|
||||
contract_id=frozen.get("contract_id", ""),
|
||||
project_id=frozen["want"]["project_id"],
|
||||
stage=frozen["want"]["stage"],
|
||||
builder_state=frozen["want"]["builder_state"])
|
||||
want = frozen["want"]
|
||||
for k in want:
|
||||
test(f"field {k}", got.get(k) == want[k],
|
||||
f"got={json.dumps(got.get(k))[:90]} want={json.dumps(want[k])[:90]}")
|
||||
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
|
||||
return 1 if FAIL else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"_doc": "FROZEN ORACLE (construction_projection exit).",
|
||||
"contract_entity": {
|
||||
"id": "contract:B-1",
|
||||
"phase": "verifying",
|
||||
"identities": {
|
||||
"construction_mode": "BUILD_NEW",
|
||||
"depth": "standard",
|
||||
"next_action": "verify"
|
||||
},
|
||||
"ext": {
|
||||
"readiness_canary": "ready",
|
||||
"liveness_canary": "live",
|
||||
"exit_canary": "exit",
|
||||
"decision_trace": {
|
||||
"approval": {
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkpoints": [
|
||||
{
|
||||
"evidence": "ev-1"
|
||||
},
|
||||
{
|
||||
"evidence": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"contract_id": "B-1",
|
||||
"want": {
|
||||
"schema_version": 1,
|
||||
"project_id": "testproj",
|
||||
"originating_goal_id": "",
|
||||
"construction_id": "B-1",
|
||||
"contract_id": "B-1",
|
||||
"mode": "BUILD_NEW",
|
||||
"depth": "standard",
|
||||
"phase": "verifying",
|
||||
"stage": "st1",
|
||||
"operation_id": "",
|
||||
"waiting_state": "",
|
||||
"correlation_id": "",
|
||||
"builder_state": "building",
|
||||
"blockers": [],
|
||||
"approval_required": true,
|
||||
"readiness_canary": "ready",
|
||||
"liveness_canary": "live",
|
||||
"exit_canary": "exit",
|
||||
"verification_state": "",
|
||||
"checkpoint": "",
|
||||
"evidence_refs": [
|
||||
"ev-1"
|
||||
],
|
||||
"failure_class": "",
|
||||
"completion_state": "",
|
||||
"candidate_next_action": "verify"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,255 @@
|
||||
//! construction_projection gap battery — parity vs the frozen Python-oracle
|
||||
//! truth (dichotomy wave #2479). Two files, both embedded VERBATIM via
|
||||
//! include_str! — never hand-transcribed:
|
||||
//! * frozen_truth.json — the battery envelope (frozen_now + cases) written
|
||||
//! by scripts/gen_construction_projection_truth.py through the shared
|
||||
//! harness (scripts/freeze_truth.py) with the scripts/cproj_freeze_seams
|
||||
//! rig;
|
||||
//! * frozen_truth.verify.json — the bare harness array, verified
|
||||
//! byte-identical against the live oracle by
|
||||
//! `PYTHONHASHSEED=0 PYTHONPATH=scripts/cproj_freeze_seams python3
|
||||
//! scripts/freeze_truth.py --verify ... --module
|
||||
//! rune.construction_projection` (byte-identical x3 in-process).
|
||||
//!
|
||||
//! The envelope_cases_match_verify_file test pins the two together, so the
|
||||
//! replayed corpus cannot drift from the verified truth.
|
||||
//!
|
||||
//! Replay mapping (the adapter owns the bridge; its RESOLVED scalars ride
|
||||
//! the frozen rows as kernel inputs — the SPEC-grammar law):
|
||||
//! * cpj_freeze_contract_entity — crate::_contract_entity(doc, id).
|
||||
//! * cpj_freeze_project — entity lookup inside the crate, then
|
||||
//! project_contract with the kwargs scalars + the resolved bridge row.
|
||||
//! * cpj_freeze_validate — crate::validate.
|
||||
//! * cpj_freeze_write_mini_doc — write_mini_doc(entity, id, projection)
|
||||
//! vs the frozen markdown BYTES + file name.
|
||||
|
||||
use crate::{_contract_entity, project_contract, validate, write_mini_doc};
|
||||
use serde_json::Value;
|
||||
|
||||
const ENV: &str = include_str!("frozen_truth.json");
|
||||
const VERIFY: &str = include_str!("frozen_truth.verify.json");
|
||||
|
||||
/// Numeric-egalitarian equality (Int(1) == F64(1.0), the pyjson parse law).
|
||||
fn num_eq(a: &Value, b: &Value) -> bool {
|
||||
match (a, b) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
x.as_f64().unwrap_or(f64::NAN) == y.as_f64().unwrap_or(f64::NAN)
|
||||
}
|
||||
(Value::Bool(x), Value::Bool(y)) => x == y,
|
||||
(Value::String(x), Value::String(y)) => x == y,
|
||||
(Value::Null, Value::Null) => true,
|
||||
(Value::Array(x), Value::Array(y)) => {
|
||||
x.len() == y.len() && x.iter().zip(y).all(|(i, j)| num_eq(i, j))
|
||||
}
|
||||
(Value::Object(x), Value::Object(y)) => {
|
||||
x.len() == y.len()
|
||||
&& x.iter()
|
||||
.zip(y)
|
||||
.all(|((ka, va), (kb, vb))| ka == kb && num_eq(va, vb))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str<'a>(v: &'a Value, what: &str) -> &'a str {
|
||||
v.as_str()
|
||||
.unwrap_or_else(|| panic!("{} not a str: {}", what, v))
|
||||
}
|
||||
|
||||
fn arg(row: &Value, i: usize) -> &Value {
|
||||
&row["args"][i]
|
||||
}
|
||||
|
||||
fn kw<'a>(row: &'a Value, key: &str) -> &'a Value {
|
||||
row["kwargs"].get(key).unwrap_or(&Value::Null)
|
||||
}
|
||||
|
||||
fn replay_row(row: &Value) -> Result<(), String> {
|
||||
match as_str(&row["fn"], "fn") {
|
||||
"cpj_freeze_contract_entity" => {
|
||||
let doc = arg(row, 0);
|
||||
let cid = as_str(arg(row, 1), "contract_id");
|
||||
let got = _contract_entity(doc, cid)
|
||||
.map_err(|e| format!("kernel errored on a frozen dict row: {}", e))?;
|
||||
let want = &row["expected"];
|
||||
if !num_eq(&got, want) {
|
||||
return Err(format!("contract_entity got {} want {}", got, want));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
"cpj_freeze_validate" => {
|
||||
let p = arg(row, 0);
|
||||
let got =
|
||||
validate(p).map_err(|e| format!("kernel errored on a frozen dict row: {}", e))?;
|
||||
let want: Vec<&str> = row["expected"]
|
||||
.as_array()
|
||||
.expect("errors list")
|
||||
.iter()
|
||||
.map(|s| s.as_str().expect("error string"))
|
||||
.collect();
|
||||
if got.iter().map(String::as_str).collect::<Vec<_>>() != want {
|
||||
return Err(format!("validate got {:?} want {:?}", got, want));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
"cpj_freeze_project" => {
|
||||
let spec = arg(row, 0);
|
||||
let cid = as_str(arg(row, 1), "contract_id");
|
||||
let expected = &row["expected"];
|
||||
// entity lookup inside the kernel (the facade delegates it too)
|
||||
let doc = &spec["load"];
|
||||
let entity =
|
||||
_contract_entity(doc, cid).map_err(|e| format!("lookup errored: {}", e))?;
|
||||
let project_id = spec
|
||||
.get("project_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let s = |k: &str| -> String {
|
||||
kw(row, k)
|
||||
.as_str()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let want = &expected["projection"];
|
||||
let got = project_contract(
|
||||
&entity,
|
||||
cid,
|
||||
project_id,
|
||||
&s("originating_goal_id"),
|
||||
&s("operation_id"),
|
||||
as_str(&expected["waiting_state"], "waiting_state"),
|
||||
as_str(&expected["live_correlation"], "live_correlation"),
|
||||
&s("stage"),
|
||||
&s("builder_state"),
|
||||
&s("verification_state"),
|
||||
&s("checkpoint"),
|
||||
&s("failure_class"),
|
||||
&s("completion_state"),
|
||||
)
|
||||
.map_err(|e| format!("project errored on a frozen dict row: {}", e))?;
|
||||
if !num_eq(&got, want) {
|
||||
return Err(format!(
|
||||
"project got {} want {}",
|
||||
serde_json::to_string(&got).unwrap_or_default(),
|
||||
serde_json::to_string(want).unwrap_or_default()
|
||||
));
|
||||
}
|
||||
// the resolved bridge scalars ride the output unchanged
|
||||
if got["waiting_state"].as_str().unwrap_or("")
|
||||
!= as_str(&expected["waiting_state"], "waiting_state")
|
||||
{
|
||||
return Err("waiting_state did not ride through".to_string());
|
||||
}
|
||||
if got["correlation_id"].as_str().unwrap_or("")
|
||||
!= as_str(&expected["live_correlation"], "live_correlation")
|
||||
{
|
||||
return Err("correlation did not ride through".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
"cpj_freeze_write_mini_doc" => {
|
||||
let spec = arg(row, 0);
|
||||
let cid = as_str(arg(row, 1), "contract_id");
|
||||
let expected = &row["expected"];
|
||||
let doc = &spec["load"];
|
||||
let entity =
|
||||
_contract_entity(doc, cid).map_err(|e| format!("lookup errored: {}", e))?;
|
||||
let projection = &expected["projection"];
|
||||
let got = write_mini_doc(&entity, cid, projection)
|
||||
.map_err(|e| format!("mini-doc errored on a frozen dict row: {}", e))?;
|
||||
if got != as_str(&expected["content"], "content") {
|
||||
return Err(format!(
|
||||
"mini-doc bytes diverge\ngot {}\nwant {}",
|
||||
got,
|
||||
as_str(&expected["content"], "content")
|
||||
));
|
||||
}
|
||||
if format!("{}.md", cid) != as_str(&expected["file"], "file") {
|
||||
return Err("file name diverges".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
other => Err(format!("unknown truth fn {}", other)),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn envelope_cases_match_verify_file() {
|
||||
let env: Value = serde_json::from_str(ENV).expect("envelope parse");
|
||||
let verify: Value = serde_json::from_str(VERIFY).expect("verify parse");
|
||||
assert_eq!(env["cases"], verify, "envelope cases != verified array");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_truth_replays_byte_identical() {
|
||||
let env: Value = serde_json::from_str(ENV).expect("envelope parse");
|
||||
let cases = env["cases"].as_array().expect("cases array").clone();
|
||||
assert!(cases.len() >= 50, "truth corpus shrank: {}", cases.len());
|
||||
let mut failures = Vec::new();
|
||||
for row in &cases {
|
||||
if let Err(e) = replay_row(row) {
|
||||
failures.push(format!("fn={}: {}", row["fn"], e));
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"frozen-truth replay drift ({} of {} rows):\n{}",
|
||||
failures.len(),
|
||||
cases.len(),
|
||||
failures.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_repr_shapes() {
|
||||
// str single-quote, bool/int split, True/False/None scalars.
|
||||
assert_eq!(crate::py_repr(&serde_json::json!("X")), "'X'");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!("it's")), "'it\\'s'");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!(2)), "2");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!(true)), "True");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!(false)), "False");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!(null)), "None");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!(1.0)), "1.0");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!([1, "a"])), "[1, 'a']");
|
||||
assert_eq!(crate::py_repr(&serde_json::json!({"a": 1})), "{'a': 1}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_str_shapes() {
|
||||
// str() (not repr): messages render dict/list via repr — single-quoted —
|
||||
// and scalars plain.
|
||||
assert_eq!(crate::py_str(&serde_json::json!(3)), "3");
|
||||
assert_eq!(crate::py_str(&serde_json::json!(3.0)), "3.0");
|
||||
assert_eq!(crate::py_str(&serde_json::json!(null)), "None");
|
||||
assert_eq!(crate::py_str(&serde_json::json!([1, "a"])), "[1, 'a']");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_json_dumps_sorted_shapes() {
|
||||
// json.dumps(quality, sort_keys=True) — ", "/": " separators, sorted
|
||||
// keys, lowercase booleans/nulls, ensure_ascii.
|
||||
assert_eq!(
|
||||
crate::py_json_dumps_sorted(&serde_json::json!({
|
||||
"genuinely_unexpected_new": 0,
|
||||
"zebra": true,
|
||||
"scope_check": null,
|
||||
"alpha": "x",
|
||||
})),
|
||||
"{\"alpha\": \"x\", \"genuinely_unexpected_new\": 0, \"scope_check\": null, \"zebra\": true}"
|
||||
);
|
||||
assert_eq!(crate::py_json_dumps_sorted(&serde_json::json!(2.0)), "2.0");
|
||||
assert_eq!(crate::py_json_dumps_sorted(&serde_json::json!(0.9)), "0.9");
|
||||
assert_eq!(
|
||||
crate::py_json_dumps_sorted(&serde_json::json!([1, 2])),
|
||||
"[1, 2]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_truncate_is_codepoint_safe() {
|
||||
// str[:120] slices codepoints, never bytes (str[:n] quirk).
|
||||
let s = "λ".repeat(130);
|
||||
assert_eq!(crate::py_truncate(&s, 120).chars().count(), 120);
|
||||
let short = "ok";
|
||||
assert_eq!(crate::py_truncate(short, 120), "ok");
|
||||
}
|
||||
@@ -1,7 +1,490 @@
|
||||
//! construction_projection — versioned public projection of construction state (W6).
|
||||
//! construction_projection — versioned public projection of construction
|
||||
//! state (W6 facade burn + dichotomy-wave gap close).
|
||||
//!
|
||||
//! Name-preserving 1:1 ports of the 4 audit-gap Python functions of
|
||||
//! construction_projection.py (T16 §16I). The kernel is pure (no file IO,
|
||||
//! no wall clock — the adapter owns the scaffold store and the operation-wait
|
||||
//! bridge); every cross-boundary call is the doc-envelope FFI
|
||||
//! (read_arg → {"ok", "value"|"err"}).
|
||||
//!
|
||||
//! Python-shape fidelity laws encoded below (dichotomy-oracle-quirks):
|
||||
//! * dict.get(key, default) returns the VALUE even when it is null —
|
||||
//! the default applies only when the key is MISSING.
|
||||
//! * truthiness is Python truthiness ("" / 0 / [] / {} / null → false).
|
||||
//! * str[:120] slices CHARACTERS (codepoints), never bytes.
|
||||
//! * json.dumps(x, sort_keys=True) renders ", " item / ": " key
|
||||
//! separators with true/false/null lowercase — NOT Rust's compact form.
|
||||
//! * repr() (for validate error text) is single-quoted for strings,
|
||||
//! None/True/False for null/bools.
|
||||
//! * Err() marks input shapes where the oracle CRASHES — the facade
|
||||
//! falls back to the Python floor and reproduces the crash exactly
|
||||
//! (crash-parity law).
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
pub const SCHEMA_VERSION: i32 = 1;
|
||||
|
||||
/// The mandate's required projection fields (§16I), in schema order —
|
||||
/// identical to the Python module's FIELDS tuple.
|
||||
pub const FIELDS: [&str; 24] = [
|
||||
"schema_version",
|
||||
"project_id",
|
||||
"originating_goal_id",
|
||||
"construction_id",
|
||||
"contract_id",
|
||||
"mode",
|
||||
"depth",
|
||||
"phase",
|
||||
"stage",
|
||||
"operation_id",
|
||||
"waiting_state",
|
||||
"correlation_id",
|
||||
"builder_state",
|
||||
"blockers",
|
||||
"approval_required",
|
||||
"readiness_canary",
|
||||
"liveness_canary",
|
||||
"exit_canary",
|
||||
"verification_state",
|
||||
"checkpoint",
|
||||
"evidence_refs",
|
||||
"failure_class",
|
||||
"completion_state",
|
||||
"candidate_next_action",
|
||||
];
|
||||
|
||||
fn empty_str() -> Value {
|
||||
Value::String(String::new())
|
||||
}
|
||||
|
||||
fn empty_arr() -> Value {
|
||||
Value::Array(Vec::new())
|
||||
}
|
||||
|
||||
// ── Python shape helpers ────────────────────────────────────────────────────
|
||||
|
||||
/// Python `dict.get(key, default)`: default ONLY when the key is missing —
|
||||
/// a null value comes back as Null (never the default).
|
||||
fn get_or<'a>(v: &'a Value, key: &str, default: &'a Value) -> &'a Value {
|
||||
match v {
|
||||
Value::Object(m) => m.get(key).unwrap_or(default),
|
||||
_ => default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Python truthiness over a JSON value.
|
||||
pub fn py_truthy(v: &Value) -> bool {
|
||||
match v {
|
||||
Value::Null => false,
|
||||
Value::Bool(b) => *b,
|
||||
Value::Number(n) => n.as_f64().is_some_and(|f| f != 0.0),
|
||||
Value::String(s) => !s.is_empty(),
|
||||
Value::Array(a) => !a.is_empty(),
|
||||
Value::Object(o) => !o.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Python `str(x)` for scalar JSON shapes (message text, not repr).
|
||||
pub fn py_str(v: &Value) -> String {
|
||||
match v {
|
||||
Value::String(s) => s.clone(),
|
||||
Value::Null => "None".to_string(),
|
||||
Value::Bool(b) => (if *b { "True" } else { "False" }).to_string(),
|
||||
Value::Number(n) => n.to_string(),
|
||||
Value::Array(_) | Value::Object(_) => py_repr(v),
|
||||
}
|
||||
}
|
||||
|
||||
/// Python `repr(x)` for the frozen corpus shapes (str/int/float/bool/
|
||||
/// None plus nested list/dict; non-empty dicts render single-key corpora).
|
||||
pub fn py_repr(v: &Value) -> String {
|
||||
match v {
|
||||
Value::String(s) => py_repr_str(s),
|
||||
Value::Null => "None".to_string(),
|
||||
Value::Bool(b) => (if *b { "True" } else { "False" }).to_string(),
|
||||
Value::Number(n) => py_repr_num(n),
|
||||
Value::Array(a) => {
|
||||
let items: Vec<String> = a.iter().map(py_repr).collect();
|
||||
format!("[{}]", items.join(", "))
|
||||
}
|
||||
Value::Object(o) => {
|
||||
let items: Vec<String> = o
|
||||
.iter()
|
||||
.map(|(k, val)| format!("{}: {}", py_repr_str(k), py_repr(val)))
|
||||
.collect();
|
||||
format!("{{{}}}", items.join(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn py_repr_str(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len() + 2);
|
||||
out.push('\'');
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\'' => out.push_str("\\'"),
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if (c as u32) < 0x20 => out.push_str(&format!("\\x{:02x}", c as u32)),
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push('\'');
|
||||
out
|
||||
}
|
||||
|
||||
fn py_repr_num(n: &serde_json::Number) -> String {
|
||||
if let Some(i) = n.as_i64() {
|
||||
return i.to_string();
|
||||
}
|
||||
let f = n.as_f64().unwrap_or(0.0);
|
||||
// Python renders integral floats with ".0" (2.0, not 2).
|
||||
if f == f.trunc() && f.abs() < 1e16 {
|
||||
return format!("{:.1}", f);
|
||||
}
|
||||
format!("{}", f)
|
||||
}
|
||||
|
||||
/// Python `s[:n]` — codepoint slicing (str[:n] quirk), only ever applied
|
||||
/// to a string value.
|
||||
pub fn py_truncate(s: &str, n: usize) -> String {
|
||||
if s.chars().count() <= n {
|
||||
s.to_string()
|
||||
} else {
|
||||
s.chars().take(n).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Python `json.dumps(x, sort_keys=True)` — ", " item / ": " key
|
||||
/// separators, true/false/null lowercase, ensure_ascii escapes, keys
|
||||
/// sorted. Bounded to the quality-dict corpus shapes.
|
||||
pub fn py_json_dumps_sorted(v: &Value) -> String {
|
||||
match v {
|
||||
Value::Null => "null".to_string(),
|
||||
Value::Bool(b) => (if *b { "true" } else { "false" }).to_string(),
|
||||
Value::Number(n) => py_dumps_num(n),
|
||||
Value::String(s) => py_json_str(s),
|
||||
Value::Array(a) => {
|
||||
let items: Vec<String> = a.iter().map(py_json_dumps_sorted).collect();
|
||||
format!("[{}]", items.join(", "))
|
||||
}
|
||||
Value::Object(o) => {
|
||||
let mut keys: Vec<&String> = o.keys().collect();
|
||||
keys.sort();
|
||||
let items: Vec<String> = keys
|
||||
.iter()
|
||||
.map(|k| format!("{}: {}", py_json_str(k), py_json_dumps_sorted(&o[*k])))
|
||||
.collect();
|
||||
format!("{{{}}}", items.join(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn py_dumps_num(n: &serde_json::Number) -> String {
|
||||
if let Some(i) = n.as_i64() {
|
||||
return i.to_string();
|
||||
}
|
||||
let f = n.as_f64().unwrap_or(0.0);
|
||||
if f == f.trunc() && f.abs() < 1e16 {
|
||||
return format!("{:.1}", f);
|
||||
}
|
||||
format!("{}", f)
|
||||
}
|
||||
|
||||
fn py_json_str(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len() + 2);
|
||||
out.push('"');
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'"' => out.push_str("\\\""),
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
|
||||
c if (c as u32) > 0x7e => out.push_str(&format!("\\u{:04x}", c as u32)),
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push('"');
|
||||
out
|
||||
}
|
||||
|
||||
// ── gap-close kernels (name-preserving) ─────────────────────────────────────
|
||||
|
||||
/// `_contract_entity(doc, contract_id)` — the contract entity with
|
||||
/// id `contract:<contract_id>`, or null. Err = non-dict `doc` (the oracle
|
||||
/// crashes with AttributeError there; the facade's floor reproduces it).
|
||||
pub fn _contract_entity(doc: &Value, contract_id: &str) -> Result<Value, String> {
|
||||
if !doc.is_object() {
|
||||
return Err(format!(
|
||||
"doc not a dict (oracle AttributeError site): {}",
|
||||
py_repr(doc)
|
||||
));
|
||||
}
|
||||
let ent_id = format!("contract:{}", contract_id);
|
||||
match doc.get("entities") {
|
||||
Some(Value::Array(ents)) => {
|
||||
for e in ents {
|
||||
if e.get("id").and_then(|i| i.as_str()) == Some(ent_id.as_str()) {
|
||||
return Ok(e.clone());
|
||||
}
|
||||
}
|
||||
Ok(Value::Null)
|
||||
}
|
||||
None => Ok(Value::Null),
|
||||
Some(other) => Err(format!(
|
||||
"entities not iterable (oracle TypeError site): {}",
|
||||
py_repr(other)
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// `validate(projection)` — schema check; returns the error list in the
|
||||
/// oracle's exact order and text (empty = valid). Err = non-dict input
|
||||
/// (oracle AttributeError site).
|
||||
pub fn validate(projection: &Value) -> Result<Vec<String>, String> {
|
||||
let obj = match projection {
|
||||
Value::Object(m) => m,
|
||||
other => {
|
||||
return Err(format!(
|
||||
"projection not a dict (oracle AttributeError site): {}",
|
||||
py_repr(other)
|
||||
))
|
||||
}
|
||||
};
|
||||
let schema = obj.get("schema_version").unwrap_or(&Value::Null);
|
||||
let mut errors = Vec::new();
|
||||
// Python `!= SCHEMA_VERSION`: numerically-equal ints/floats AND
|
||||
// bools-as-ints (True == 1 — the bool-subclass quirk) are all VALID.
|
||||
let matches = match schema {
|
||||
Value::Bool(b) => *b,
|
||||
Value::Number(n) => {
|
||||
n.as_i64().is_some_and(|i| i == i64::from(SCHEMA_VERSION))
|
||||
|| n.as_f64().is_some_and(|f| f == f64::from(SCHEMA_VERSION))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if !matches {
|
||||
errors.push(format!(
|
||||
"schema_version: expected {}, got {}",
|
||||
SCHEMA_VERSION,
|
||||
py_repr(schema)
|
||||
));
|
||||
}
|
||||
for f in FIELDS.iter() {
|
||||
if !obj.contains_key(*f) {
|
||||
errors.push(format!("missing field: {}", f));
|
||||
}
|
||||
}
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Surface {
|
||||
Rows(Vec<Value>),
|
||||
}
|
||||
|
||||
/// `ext.get("affected_paths") or ext.get("decision_trace", {})
|
||||
/// .get("affected_surfaces", {}).get("affected_paths")
|
||||
/// or ids.get("affected_surface") or []` — first TRUTHY wins; a truthy
|
||||
/// string wraps into a single-element list; a truthy non-iterable is the
|
||||
/// oracle's TypeError site.
|
||||
fn surface_or_chain(_c: &Value, ids: &Value, ext: &Value) -> Result<Surface, String> {
|
||||
let via = |v: Option<&Value>| -> Option<Value> { v.filter(|x| py_truthy(x)).cloned() };
|
||||
let dt = get_or(ext, "decision_trace", &Value::Null);
|
||||
let aff_surf = get_or(dt, "affected_surfaces", &Value::Null);
|
||||
let chosen = via(ext.as_object().and_then(|m| m.get("affected_paths")))
|
||||
.or_else(|| via(aff_surf.as_object().and_then(|m| m.get("affected_paths"))))
|
||||
.or_else(|| via(ids.as_object().and_then(|m| m.get("affected_surface"))))
|
||||
.unwrap_or_else(empty_arr);
|
||||
match &chosen {
|
||||
Value::String(s) => Ok(Surface::Rows(vec![Value::String(s.clone())])),
|
||||
Value::Array(rows) => Ok(Surface::Rows(rows.clone())),
|
||||
other => Err(format!(
|
||||
"surface not iterable (oracle TypeError site): {}",
|
||||
py_repr(other)
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// `ids.get("objective") or ids.get("need")
|
||||
/// or c.get("ext", {}).get("need", {}).get("objective", "")` — the or-chain:
|
||||
/// first truthy wins; when all are falsy the LAST operand's value survives
|
||||
/// ("" when objective is missing, its falsy value when present).
|
||||
fn objective_or_chain(ids: &Value, ext: &Value) -> Value {
|
||||
for v in [ids.get("objective"), ids.get("need")]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if py_truthy(v) {
|
||||
return v.clone();
|
||||
}
|
||||
}
|
||||
let need = get_or(ext, "need", &Value::Null);
|
||||
match need.as_object().and_then(|m| m.get("objective")) {
|
||||
Some(v) => v.clone(),
|
||||
None => empty_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `write_mini_doc(contract_entity, contract_id, projection)` — the pure
|
||||
/// markdown build of the #2465 per-contract mini-doc. The ADAPTER owns
|
||||
/// the write (out_dir/mkdir/write_text stay in the facade — no file IO in
|
||||
/// kernels). Err = an input shape the oracle would crash on (the facade
|
||||
/// falls back to the floor and reproduces the crash exactly).
|
||||
pub fn write_mini_doc(
|
||||
contract_entity: &Value,
|
||||
contract_id: &str,
|
||||
projection: &Value,
|
||||
) -> Result<String, String> {
|
||||
if !contract_entity.is_object() {
|
||||
return Err(format!(
|
||||
"contract entity not a dict (oracle AttributeError site): {}",
|
||||
py_repr(contract_entity)
|
||||
));
|
||||
}
|
||||
if !projection.is_object() {
|
||||
return Err(format!(
|
||||
"projection not a dict (oracle AttributeError site): {}",
|
||||
py_repr(projection)
|
||||
));
|
||||
}
|
||||
let ids = get_or(contract_entity, "identities", &Value::Null);
|
||||
let ext = get_or(contract_entity, "ext", &Value::Null);
|
||||
|
||||
let objective = objective_or_chain(ids, ext);
|
||||
let surface = surface_or_chain(contract_entity, ids, ext)?;
|
||||
// projection.get("evidence_refs", []) — missing → []; a STRING
|
||||
// iterates CHARACTERS (str[:n] family quirk); a null rides (None →
|
||||
// len(None) TypeError site); any other non-iterable is the same site.
|
||||
let evidence = match projection.get("evidence_refs") {
|
||||
Some(Value::Array(a)) => Ok(Value::Array(a.clone())),
|
||||
Some(Value::String(s)) => Ok(Value::String(s.clone())),
|
||||
Some(other) => Err(format!(
|
||||
"evidence_refs not len-able (oracle TypeError site): {}",
|
||||
py_repr(other)
|
||||
)),
|
||||
None => Ok(empty_arr()),
|
||||
}?;
|
||||
let task_ref = match get_or(ids, "task_ref", &Value::Null) {
|
||||
Value::Null if !ids.as_object().is_some_and(|m| m.contains_key("task_ref")) => {
|
||||
String::new()
|
||||
}
|
||||
other => py_str(other),
|
||||
};
|
||||
let quality = match ext.as_object().and_then(|m| m.get("contract_quality")) {
|
||||
Some(v) => v.clone(),
|
||||
None => empty_arr(),
|
||||
};
|
||||
|
||||
let phase = match projection.get("phase") {
|
||||
Some(v) => py_str(v),
|
||||
None => py_str(get_or(contract_entity, "phase", &empty_str())),
|
||||
};
|
||||
let mode = or_empty_str(projection.get("mode"));
|
||||
let depth = or_empty_str(projection.get("depth"));
|
||||
|
||||
let mut lines: Vec<String> = Vec::new();
|
||||
lines.push(format!("# Contract {}", contract_id));
|
||||
lines.push(String::new());
|
||||
lines.push(format!(
|
||||
"**Phase:** {} · **Mode:** {} · **Depth:** {}",
|
||||
phase,
|
||||
if mode.is_empty() {
|
||||
"n/a".to_string()
|
||||
} else {
|
||||
mode
|
||||
},
|
||||
if depth.is_empty() {
|
||||
"n/a".to_string()
|
||||
} else {
|
||||
depth
|
||||
},
|
||||
));
|
||||
lines.push(String::new());
|
||||
lines.push("## What was built".to_string());
|
||||
lines.push(String::new());
|
||||
if !py_truthy(&objective) {
|
||||
lines.push("(objective not recorded on the contract entity)".to_string());
|
||||
} else {
|
||||
lines.push(py_str(&objective));
|
||||
}
|
||||
lines.push(String::new());
|
||||
lines.push("## What it connects to".to_string());
|
||||
lines.push(String::new());
|
||||
match surface {
|
||||
Surface::Rows(rows) if !rows.is_empty() => {
|
||||
for s in &rows {
|
||||
lines.push(format!("- `{}`", py_str(s)));
|
||||
}
|
||||
}
|
||||
_ => lines.push("(no affected surface recorded)".to_string()),
|
||||
}
|
||||
lines.push(String::new());
|
||||
lines.push("## How to update those connections".to_string());
|
||||
lines.push(String::new());
|
||||
lines.push(
|
||||
"- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface."
|
||||
.to_string(),
|
||||
);
|
||||
lines.push(format!(
|
||||
"- Evidence trail: {} checkpoint(s) recorded on the contract entity{}",
|
||||
match &evidence {
|
||||
Value::Array(a) => a.len(),
|
||||
Value::String(s) => s.chars().count(),
|
||||
_ => 0,
|
||||
},
|
||||
if !task_ref.is_empty() {
|
||||
format!("; task ref `{}`.", task_ref)
|
||||
} else {
|
||||
".".to_string()
|
||||
}
|
||||
));
|
||||
lines.push(format!(
|
||||
"- Quality at close: {}.",
|
||||
if py_truthy(&quality) {
|
||||
py_json_dumps_sorted(&quality)
|
||||
} else {
|
||||
"not recorded".to_string()
|
||||
}
|
||||
));
|
||||
lines.push(String::new());
|
||||
lines.push("## Evidence".to_string());
|
||||
lines.push(String::new());
|
||||
match &evidence {
|
||||
Value::Array(rows) if !rows.is_empty() => {
|
||||
for e in rows {
|
||||
lines.push(format!("- {}", py_str(e)));
|
||||
}
|
||||
}
|
||||
Value::String(s) if !s.is_empty() => {
|
||||
for c in s.chars() {
|
||||
lines.push(format!("- {}", c));
|
||||
}
|
||||
}
|
||||
_ => lines.push("(none recorded)".to_string()),
|
||||
}
|
||||
lines.push(String::new());
|
||||
Ok(lines.join("\n"))
|
||||
}
|
||||
|
||||
/// Python `x or "n/a"`-chain leg: truthy keeps raw text, falsy → "".
|
||||
fn or_empty_str(v: Option<&Value>) -> String {
|
||||
match v {
|
||||
Some(x) if py_truthy(x) => py_str(x),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `project_contract(...)` — the projection dict assembly (bridge lookup
|
||||
/// stays in the adapter). Same 24 fields, same order, Python shape laws.
|
||||
/// Err = input shapes where the oracle crashes (str-slicing a non-str).
|
||||
#[allow(clippy::too_many_arguments)] // oracle signature: mirrors the facade arg-for-arg
|
||||
pub fn project_contract(
|
||||
contract_entity: &serde_json::Value,
|
||||
contract_entity: &Value,
|
||||
contract_id_ref: &str,
|
||||
project_id: &str,
|
||||
originating_goal_id: &str,
|
||||
@@ -14,52 +497,88 @@ pub fn project_contract(
|
||||
checkpoint: &str,
|
||||
failure_class: &str,
|
||||
completion_state: &str,
|
||||
) -> serde_json::Value {
|
||||
let ids = contract_entity.get("identities").cloned().unwrap_or(serde_json::json!({}));
|
||||
let ext = contract_entity.get("ext").cloned().unwrap_or(serde_json::json!({}));
|
||||
let trace = ext.get("decision_trace").cloned().unwrap_or(serde_json::json!({}));
|
||||
) -> Result<Value, String> {
|
||||
let ids = get_or(contract_entity, "identities", &Value::Null);
|
||||
let ext = get_or(contract_entity, "ext", &Value::Null);
|
||||
let trace = get_or(ext, "decision_trace", &Value::Null);
|
||||
|
||||
let mut evidence_refs: Vec<String> = Vec::new();
|
||||
if let Some(cps) = contract_entity.get("checkpoints").and_then(|c| c.as_array()) {
|
||||
let mut evidence_refs: Vec<Value> = Vec::new();
|
||||
if let Some(cps) = contract_entity
|
||||
.get("checkpoints")
|
||||
.and_then(|c| c.as_array())
|
||||
{
|
||||
for cp in cps {
|
||||
if let Some(ev) = cp.get("evidence").and_then(|e| e.as_str()) {
|
||||
if !ev.is_empty() {
|
||||
evidence_refs.push(ev.chars().take(120).collect());
|
||||
match cp.get("evidence") {
|
||||
Some(Value::String(s)) if !s.is_empty() => {
|
||||
evidence_refs.push(Value::String(py_truncate(s, 120)));
|
||||
}
|
||||
Some(Value::String(_)) => {}
|
||||
Some(Value::Null) => {} // None is falsy
|
||||
Some(other) => {
|
||||
return Err(format!(
|
||||
"evidence not a string (oracle TypeError site): {}",
|
||||
py_repr(other)
|
||||
))
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let approval = trace.get("approval").cloned().unwrap_or(serde_json::json!({}));
|
||||
let approval_required = approval.get("required").and_then(|r| r.as_bool()).unwrap_or(false)
|
||||
let approval = get_or(trace, "approval", &Value::Null);
|
||||
let approval_required = py_truthy(get_or(approval, "required", &Value::Null))
|
||||
|| approval.get("state").and_then(|s| s.as_str()) == Some("REQUIRED")
|
||||
|| contract_entity.get("phase").and_then(|p| p.as_str()) == Some("awaiting_approval");
|
||||
|
||||
let mut blockers: Vec<String> = Vec::new();
|
||||
if let Some(blk) = ids.get("blocker") {
|
||||
match blk {
|
||||
serde_json::Value::Object(m) => {
|
||||
let class = m.get("class").and_then(|c| c.as_str()).unwrap_or("BLOCKED");
|
||||
let evidence = m.get("evidence").and_then(|e| e.as_str()).unwrap_or("");
|
||||
blockers.push(format!("{}: {}", class, evidence.chars().take(120).collect::<String>()));
|
||||
}
|
||||
serde_json::Value::String(s) if !s.is_empty() => blockers.push(s.clone()),
|
||||
_ => {}
|
||||
let mut blockers: Vec<Value> = Vec::new();
|
||||
match ids.get("blocker") {
|
||||
Some(Value::Object(m)) => {
|
||||
let class = match m.get("class") {
|
||||
Some(v) => py_str(v),
|
||||
None => "BLOCKED".to_string(),
|
||||
};
|
||||
let ev = match m.get("evidence") {
|
||||
Some(Value::String(s)) => py_truncate(s, 120),
|
||||
Some(Value::Null) => String::new(),
|
||||
Some(other) => {
|
||||
return Err(format!(
|
||||
"blocker evidence not a string (oracle TypeError site): {}",
|
||||
py_repr(other)
|
||||
))
|
||||
}
|
||||
None => String::new(),
|
||||
};
|
||||
blockers.push(Value::String(format!("{}: {}", class, ev)));
|
||||
}
|
||||
Some(Value::String(s)) if !s.is_empty() => blockers.push(Value::String(s.clone())),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let get_ids = |k: &str| ids.get(k).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let get_ext = |k: &str| ext.get(k).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
// Python `ids.get(k, "") or ""` — falsy (incl. null) → "", truthy
|
||||
// keeps the raw scalar rendered via str().
|
||||
let or_empty_at = |v: Option<&Value>| -> Value {
|
||||
match v {
|
||||
Some(x) if py_truthy(x) => x.clone(),
|
||||
_ => empty_str(),
|
||||
}
|
||||
};
|
||||
// Python `x.get(k, "")` — the default applies only when MISSING.
|
||||
let raw_or_empty = |container: &&Value, key: &str| -> Value {
|
||||
match container.as_object().and_then(|m| m.get(key)) {
|
||||
Some(x) => x.clone(),
|
||||
None => empty_str(),
|
||||
}
|
||||
};
|
||||
|
||||
serde_json::json!({
|
||||
Ok(serde_json::json!({
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"project_id": project_id,
|
||||
"originating_goal_id": originating_goal_id,
|
||||
"construction_id": contract_id_ref,
|
||||
"contract_id": contract_id_ref,
|
||||
"mode": get_ids("construction_mode"),
|
||||
"depth": get_ids("depth"),
|
||||
"phase": contract_entity.get("phase").and_then(|p| p.as_str()).unwrap_or(""),
|
||||
"mode": or_empty_at(ids.as_object().and_then(|m| m.get("construction_mode"))),
|
||||
"depth": or_empty_at(ids.as_object().and_then(|m| m.get("depth"))),
|
||||
"phase": raw_or_empty(&contract_entity, "phase"),
|
||||
"stage": stage,
|
||||
"operation_id": operation_id,
|
||||
"waiting_state": waiting_state,
|
||||
@@ -67,69 +586,34 @@ pub fn project_contract(
|
||||
"builder_state": builder_state,
|
||||
"blockers": blockers,
|
||||
"approval_required": approval_required,
|
||||
"readiness_canary": get_ext("readiness_canary"),
|
||||
"liveness_canary": get_ext("liveness_canary"),
|
||||
"exit_canary": get_ext("exit_canary"),
|
||||
"readiness_canary": raw_or_empty(&ext, "readiness_canary"),
|
||||
"liveness_canary": raw_or_empty(&ext, "liveness_canary"),
|
||||
"exit_canary": raw_or_empty(&ext, "exit_canary"),
|
||||
"verification_state": verification_state,
|
||||
"checkpoint": checkpoint,
|
||||
"evidence_refs": evidence_refs,
|
||||
"failure_class": failure_class,
|
||||
"completion_state": completion_state,
|
||||
"candidate_next_action": get_ids("next_action"),
|
||||
})
|
||||
"candidate_next_action": raw_or_empty(&ids, "next_action"),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn project(construction_state: &serde_json::Value) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"phase": construction_state.get("phase"),
|
||||
"mode": construction_state.get("mode"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── FFI surface (facade burn-down, 2026-10-10) ───────────────────────────────
|
||||
// ── FFI surface (doc-envelope law; adapter owns store + bridge) ─────────────
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
fn read_arg(ptr: *const c_char) -> Result<String, String> {
|
||||
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
|
||||
if ptr.is_null() {
|
||||
return Err("null argument pointer".to_string());
|
||||
}
|
||||
unsafe { CStr::from_ptr(ptr) }
|
||||
let text = unsafe { CStr::from_ptr(ptr) }
|
||||
.to_str()
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|e| format!("argument is not valid UTF-8: {}", e))
|
||||
.map_err(|e| format!("argument is not valid UTF-8: {}", e))?;
|
||||
serde_json::from_str(text).map_err(|e| format!("argument JSON parse failed: {}", e))
|
||||
}
|
||||
|
||||
/// cp_project({contract_entity, project_id, originating_goal_id, operation_id,
|
||||
/// waiting_state, live_correlation, stage, builder_state,
|
||||
/// verification_state, checkpoint, failure_class, completion_state})
|
||||
/// -> projection dict. The operation-wait lookup stays in the adapter.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cp_project(arg: *const c_char) -> *mut c_char {
|
||||
let result = read_arg(arg)
|
||||
.and_then(|t| serde_json::from_str::<serde_json::Value>(&t).map_err(|e| format!("argument JSON parse failed: {}", e)))
|
||||
.and_then(|a| {
|
||||
let ce = a.get("contract_entity").cloned().ok_or("arg.contract_entity required")?;
|
||||
Ok(project_contract(
|
||||
&ce,
|
||||
a.get("contract_id").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("project_id").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("originating_goal_id").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("operation_id").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("waiting_state").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("live_correlation").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("stage").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("builder_state").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("verification_state").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("checkpoint").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("failure_class").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("completion_state").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
))
|
||||
});
|
||||
let body = match result {
|
||||
fn envelope(r: Result<Value, String>) -> *mut c_char {
|
||||
let body = match r {
|
||||
Ok(v) => serde_json::json!({"ok": true, "value": v}),
|
||||
Err(e) => serde_json::json!({"ok": false, "err": e}),
|
||||
};
|
||||
@@ -138,7 +622,82 @@ pub extern "C" fn cp_project(arg: *const c_char) -> *mut c_char {
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
/// cp_project({contract_entity, project_id, originating_goal_id, operation_id,
|
||||
/// waiting_state, live_correlation, stage, builder_state,
|
||||
/// verification_state, checkpoint, failure_class, completion_state})
|
||||
/// -> projection dict. The operation-wait lookup stays in the adapter.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cp_project(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).and_then(|a| {
|
||||
let ce = a
|
||||
.get("contract_entity")
|
||||
.cloned()
|
||||
.ok_or("arg.contract_entity required")?;
|
||||
let s = |k: &str| -> String { a.get(k).and_then(|v| v.as_str()).unwrap_or("").to_string() };
|
||||
project_contract(
|
||||
&ce,
|
||||
&s("contract_id"),
|
||||
&s("project_id"),
|
||||
&s("originating_goal_id"),
|
||||
&s("operation_id"),
|
||||
&s("waiting_state"),
|
||||
&s("live_correlation"),
|
||||
&s("stage"),
|
||||
&s("builder_state"),
|
||||
&s("verification_state"),
|
||||
&s("checkpoint"),
|
||||
&s("failure_class"),
|
||||
&s("completion_state"),
|
||||
)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cp_contract_entity({doc, contract_id}) -> the matched entity or null.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cp_contract_entity(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).and_then(|a| {
|
||||
let doc = a.get("doc").cloned().ok_or("arg.doc required")?;
|
||||
let cid = a.get("contract_id").and_then(|v| v.as_str()).unwrap_or("");
|
||||
_contract_entity(&doc, cid)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cp_validate({projection}) -> list of error strings (empty = valid).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cp_validate(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).and_then(|a| {
|
||||
let p = a
|
||||
.get("projection")
|
||||
.cloned()
|
||||
.ok_or("arg.projection required")?;
|
||||
let errs = validate(&p)?;
|
||||
Ok(Value::Array(errs.into_iter().map(Value::String).collect()))
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cp_write_mini_doc({contract_entity, contract_id, projection}) -> the
|
||||
/// markdown text; the adapter writes it (no file IO in kernels).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cp_write_mini_doc(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).and_then(|a| {
|
||||
let ce = a
|
||||
.get("contract_entity")
|
||||
.cloned()
|
||||
.ok_or("arg.contract_entity required")?;
|
||||
let p = a
|
||||
.get("projection")
|
||||
.cloned()
|
||||
.ok_or("arg.projection required")?;
|
||||
let cid = a.get("contract_id").and_then(|v| v.as_str()).unwrap_or("");
|
||||
write_mini_doc(&ce, cid, &p).map(Value::String)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)] // extern "C" ABI: the loader requires this signature
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cp_free_string(ptr: *mut c_char) {
|
||||
if !ptr.is_null() {
|
||||
@@ -148,8 +707,12 @@ pub extern "C" fn cp_free_string(ptr: *mut c_char) {
|
||||
}
|
||||
}
|
||||
|
||||
#[path = "gap_battery.rs"]
|
||||
#[cfg(test)]
|
||||
mod branch_battery {
|
||||
mod gap_battery;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -158,19 +721,40 @@ mod branch_battery {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_round_trip() {
|
||||
let state =
|
||||
serde_json::json!({"phase": "closed", "mode": "BUILD_NEW", "contract_id": "B-1"});
|
||||
let p = project(&state);
|
||||
fn projection_regression_frozen_shape() {
|
||||
// the historical W6-era canary contract (data inlined; the canary
|
||||
// script was superseded by the gap battery) — the kernel must keep
|
||||
// reproducing its want dict.
|
||||
let doc = serde_json::json!({
|
||||
"entities": [{
|
||||
"id": "contract:B-1",
|
||||
"phase": "verifying",
|
||||
"identities": {
|
||||
"construction_mode": "BUILD_NEW",
|
||||
"depth": "standard",
|
||||
"next_action": "verify"
|
||||
},
|
||||
"ext": {
|
||||
"readiness_canary": "ready",
|
||||
"liveness_canary": "live",
|
||||
"exit_canary": "exit",
|
||||
"decision_trace": {"approval": {"required": true}}
|
||||
},
|
||||
"checkpoints": [{"evidence": "ev-1"}, {"evidence": ""}]
|
||||
}]
|
||||
});
|
||||
let c = _contract_entity(&doc, "B-1").unwrap();
|
||||
assert!(!c.is_null());
|
||||
let p = project_contract(
|
||||
&c, "B-1", "testproj", "", "", "", "", "st1", "building", "", "", "", "",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(p["schema_version"], 1);
|
||||
assert_eq!(p["phase"], "closed");
|
||||
assert_eq!(p["mode"], "BUILD_NEW");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_missing_fields_are_null() {
|
||||
let p = project(&serde_json::json!({}));
|
||||
assert!(p["phase"].is_null());
|
||||
assert!(p["mode"].is_null());
|
||||
assert_eq!(p["phase"], "verifying");
|
||||
assert_eq!(p["approval_required"], true);
|
||||
assert_eq!(p["evidence_refs"], serde_json::json!(["ev-1"]));
|
||||
assert_eq!(p["candidate_next_action"], "verify");
|
||||
assert_eq!(validate(&p).unwrap(), Vec::<String>::new());
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,17 @@ class TestContractMiniDoc(unittest.TestCase):
|
||||
)
|
||||
self.assertTrue(r.get("success"), r.get("errors"))
|
||||
cid = r["ids"]["contract_id"]
|
||||
for phase, ref in (("approved", "t"), ("building", "t"), ("verifying", "t"), ("closed", "final evidence")):
|
||||
for phase, ref in (("approved", "t"), ("building", "t"), ("verifying", "t")):
|
||||
env = self.iface.transition(cid, phase, evidence_ref=ref)
|
||||
self.assertTrue(env.get("success"), (phase, env.get("errors")))
|
||||
# 16c5f22a quality-gate law: raw transition to 'closed' refuses
|
||||
# without contract_quality evidence — the fixture closes via
|
||||
# close() (predicted-vs-observed), which also emits the mini-doc.
|
||||
env = self.iface.close(cid, observed_evidence=[{
|
||||
"kind": "fixture_evidence",
|
||||
"detail": f"minidoc fixture {suffix}: closed via close()",
|
||||
}])
|
||||
self.assertTrue(env.get("success"), ("close", env.get("errors")))
|
||||
return cid
|
||||
|
||||
def test_close_emits_mini_doc_with_sections(self):
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# construction_projection RUST gap-close — verification summary (dichotomy wave #2479, worktree rune-wave-da)
|
||||
|
||||
Module: `src/rune/construction_projection.py` (254 lines @ HEAD, 4 fns) → `src/rust/construction_projection/`.
|
||||
Audit: **1/4 PARTIAL → 4/4 FULL** (regen verified in-checkout, doc reverted — coordinator territory; verification/cpj_audit_after.log).
|
||||
|
||||
## Ported (name-preserving 1:1; the audit named 3 gaps)
|
||||
- `_contract_entity` — contract-entity lookup (`contract:<id>`) incl. crash-parity Err on non-dict doc / non-iterable entities.
|
||||
- `validate` — schema check; exact error order/text; Python `!=` numeric-egalitarian equality (True == 1, 1.0 == 1 valid); repr() rendering of the got-value.
|
||||
- `write_mini_doc` — pure markdown BUILD (the #2465 mini-doc); the file WRITE stays adapter-side (`_py_emit_mini_doc`); json.dumps(sort_keys=True) ", "/": " separators; str() of objective; evidence/surface rows.
|
||||
- `project` — the 24-field §16I projection dict assembly (the pre-existing W6-era facade path was DEAD at HEAD: it delegated to `cp_loader.project`, which never existed in the construction_pattern loader — AttributeError → silent floor, always). Rebuilt as `project_contract` kernel + adapter-owned operation-wait bridge lookup (resolved waiting_state/live_correlation ride as kernel scalars — SPEC-grammar law).
|
||||
|
||||
## Python-shape laws encoded (docs/dichotomy-oracle-quirks.json)
|
||||
- `dict.get(k, default)`: default ONLY on missing key — null VALUE survives (canaries ride as null).
|
||||
- Python truthiness everywhere (`ids.get(k, "") or ""`); `x or "n/a"` chains; first-truthy-or chains with LAST-operand survival.
|
||||
- `str[:120]` codepoint slicing (py_truncate; Λ/é fuzz corpus pins it).
|
||||
- Python `str()`/`repr()` split (single-quote repr for validate error text; `None`/`True`/`False`).
|
||||
- json.dumps sort_keys separators + ensure_ascii escapes (contract_quality rendering).
|
||||
- crash-parity: kernel Err envelopes mark the oracle's TypeError/AttributeError sites; the facade falls to the floor and reproduces the identical exception (7 crash shapes pinned).
|
||||
|
||||
## Verification ladder (evidence files, all cross-checked via Read)
|
||||
| Gate | Evidence | Result |
|
||||
|---|---|---|
|
||||
| cargo build --release | verification/cpj_build.log (+.exit) | BUILD:0 |
|
||||
| cargo test (battery) | verification/cpj_battery.log (+.exit) | 8/8 green — frozen_truth.json + frozen_truth.verify.json embedded VERBATIM (include_str!), envelope≠verify pinned, 52-case replay |
|
||||
| cargo fmt --check | verification/cpj_fmt.log (+.exit) | FMT:0 |
|
||||
| cargo clippy --all-targets -- -D warnings | verification/cpj_clippy.log (+.exit) | CLIPPY:0 |
|
||||
| freeze_truth --verify (byte-identical law) | verification/cpj_verify_1/2/3.log (11:16Z, cancelled-run era) + cpj_verify_final.log (post-fix, fresh) | OK: 52 cases byte-identical ×4 total |
|
||||
| live 3-way parity | verification/cpj_parity.log (+.exit) | PARITY OK: 100% — 843 comparisons, rust == oracle == frozen (52 frozen rows + 240 fuzz project + 40 mini-doc + 7 crash); re-run AFTER the final lib.rs fmt/clippy fix |
|
||||
| Python both pins — module suite | verification/cpj_pytest_live.log, cpj_pytest_python.log | tests/test_contract_minidoc.py 3/3 both pins (delegation live + RUNE_CONSTRUCTION_PROJECTION_CORE=python) |
|
||||
| Python both pins — consumer seam | verification/cpj_pytest_seam_live.log, cpj_pytest_seam_python.log | tests/test_substrate_seam.py 12/12 both pins |
|
||||
| audit regen | verification/cpj_audit_after.log | 1/4 → 4/4 RUST (doc reverted before commit) |
|
||||
|
||||
Rig: `scripts/cproj_freeze_seams/sitecustomize.py` (PYTHONPATH-imported by generator AND --verify; RUNE_CONSTRUCTION_PROJECTION_CORE=python pinned; JSON fake scaffold; pinned fake event_wait bridge: none/missing/found/OSError/RuntimeError; hermetic fixed-path scratch `/tmp/cpj_gap_freeze`; no absolute paths, no wall clock in truth).
|
||||
Generator: `scripts/gen_construction_projection_truth.py` — 52 cases (7 contract_entity / 21 project / 11 validate / 13 write_mini_doc) through the SHARED harness `scripts/freeze_truth.py`.
|
||||
|
||||
## Fix-first with proof (adjacent test, 16c5f22a law)
|
||||
`tests/test_contract_minidoc.py::_closed_contract` raw-transitioned to `closed` — refused since 16c5f22a ("transition to 'closed' requires contract_quality evidence"). Verified PRE-EXISTING at pristine HEAD (both tests fail identically with the port stashed; fixture walks recorded in the log era 11:47–12:00Z). Fixed the fixture to close via `iface.close(observed_evidence=[...])` — which also re-exercised the mini-doc emission end-to-end through the Rust kernel on the REAL close path (the load-bearing coverage the stale fixture had lost). 3/3 both pins after the fix.
|
||||
|
||||
## Documented divergences (not bugs)
|
||||
1. **HEAD's kernel path was dead code** (`cp_loader.project` never existed — construction_pattern's loader has construct/revise/propose/evaluate/derive/combine only). Replaced by `cproj_loader` (construction_projection crate, cp_* FFI); `cp_loader` itself is untouched (different module's loader, still in use by construction_pattern).
|
||||
2. **`canary_construction_projection.py` + `frozen_projection.json`** (crate dir, W6 facade-burn era): the canary calls `cp_loader.project` — non-existent — so it has been broken since cp_loader landed; invoked by nothing (CI/tests/scripts grep: zero hits). Superseded by the gap battery + 3-way parity. Deleted with this close.
|
||||
3. **Bridge stays adapter-side by design**: the kernel never sees the event-wait bridge or the store — the resolved waiting_state/live_correlation scalars ride the frozen rows (SPEC-grammar law); BRIDGE_UNREACHABLE degradation is frozen (OSError + RuntimeError rows).
|
||||
4. **Store-side test residue** (7 minidoc-test fixture entities, docs/X.md, observation rows from pytest walks) stays untracked/discarded — only the close-walk's own observation rows ride the store commit (drift_compression precedent).
|
||||
|
||||
## Environment notes
|
||||
- Both-pin pytest + the close walk need the capability_scoring kernel built (CS_RUST_LIB law; the Python oracle is retired) — built in-worktree (`cargo build --release`), as was rune_spine for the spine test.
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
Reference in new issue
Block a user