Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd42655a2 | ||
|
|
5dbb5ddc34 | ||
|
|
a1494f2e94 | ||
|
|
3a2e61cc0c | ||
|
|
4684edac4e | ||
|
|
e0c68ba418 | ||
|
|
a5656587c4 | ||
|
|
8baf8b9845 | ||
|
|
7e6e785bcf | ||
|
|
ebac42f7ca | ||
|
|
49611fa495 | ||
|
|
ea177db766 | ||
|
|
5701a13109 | ||
|
|
2750eba008 | ||
|
|
d9009cc245 | ||
|
|
ac114c3244 | ||
|
|
cdcae1ead5 | ||
|
|
78a36204b8 | ||
|
|
5f3a0d73a6 | ||
|
|
55fba58281 | ||
|
|
b8b14d5be6 | ||
|
|
265bb509a7 | ||
|
|
a2878b76bd |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.240.0**
|
||||
**Current release: v0.243.1**
|
||||
|
||||
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.240.0"
|
||||
version = "0.243.1"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Live parity: iteration Rust kernel vs Python oracle (dichotomy wave).
|
||||
|
||||
Three layers, all must be green for parity 100%:
|
||||
1. FROZEN — the 29 frozen-oracle cases replayed VERBATIM through both
|
||||
the Python floor (oracle sanity) and the Rust kernel.
|
||||
2. LIVE — oracle (RUNE_ITR_CORE=python) vs facade (kernel auto) on the
|
||||
same inputs; `iteration._now` is pinned so generated_at /
|
||||
recorded_at / invalidation clocks are byte-comparable.
|
||||
3. FUZZ — 400 seeded random structural models / ledgers / behaviour
|
||||
docs (seed 2479), every public function, both cores.
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rust" / "evidence_provenance"))
|
||||
|
||||
os.environ.setdefault("RUNE_LEDGER_CORE", "python")
|
||||
|
||||
import rune.iteration as it # noqa: E402
|
||||
import rune.behaviour_ledger as bl_mod # noqa: E402
|
||||
from rune.structural_model import StructuralModel # noqa: E402
|
||||
from rune.capability_ledger import CapabilityLedger # noqa: E402
|
||||
from rune.behaviour_ledger import BehaviourLedger # noqa: E402
|
||||
from rune import itr_loader # noqa: E402
|
||||
|
||||
PIN_NOW = "2026-10-12T00:00:00Z"
|
||||
it._now = lambda: PIN_NOW
|
||||
bl_mod._now = lambda: PIN_NOW
|
||||
|
||||
TRUTH = json.loads(
|
||||
(REPO / "src" / "rust" / "iteration" / "src" / "frozen_it_truth.json")
|
||||
.read_text())
|
||||
|
||||
KINDS = ["project", "domain", "subsystem", "component", "interface",
|
||||
"state_store", "test_suite", "deployment_surface"]
|
||||
REL_TYPES = ["belongs_to", "owns", "depends_on", "calls", "provides",
|
||||
"consumes", "verified_by", "deployed_by"]
|
||||
MATURITIES = ["DECLARED", "implemented", "retired", "superseded", "candidate"]
|
||||
|
||||
fails = []
|
||||
counts = {"frozen": 0, "live": 0, "fuzz": 0}
|
||||
|
||||
|
||||
def fail(layer, name, detail):
|
||||
fails.append((layer, name, detail))
|
||||
print(f"FAIL[{layer}] {name}: {detail}")
|
||||
|
||||
|
||||
def wrap(doc, cls):
|
||||
o = cls.__new__(cls)
|
||||
o.doc = doc
|
||||
return o
|
||||
|
||||
|
||||
# ── fixture factory (oracle-shaped) ─────────────────────────────────────
|
||||
|
||||
def make_fixture(rnd, tmp, i):
|
||||
model = StructuralModel(Path(tmp) / f"m{i}", f"p{i}")
|
||||
n_ent = rnd.randint(2, 9)
|
||||
ids = []
|
||||
for j in range(n_ent):
|
||||
kind = rnd.choice(KINDS)
|
||||
eid = model.add(kind, f"e{j}")
|
||||
ids.append(eid)
|
||||
if rnd.random() < 0.4:
|
||||
for e in model.doc["entities"]:
|
||||
if e["id"] == eid:
|
||||
e.setdefault("identities", {})["source"] = f"src/{j}.py"
|
||||
for _ in range(rnd.randint(0, 12)):
|
||||
a, b = rnd.choice(ids), rnd.choice(ids)
|
||||
if a == b:
|
||||
continue
|
||||
try:
|
||||
model.relate(a, rnd.choice(REL_TYPES), b,
|
||||
status=rnd.choice(["active", "stale"]))
|
||||
except ValueError:
|
||||
pass
|
||||
model.doc["schema_version"] = 1
|
||||
ledger = CapabilityLedger(Path(tmp) / f"l{i}")
|
||||
for j in range(rnd.randint(1, 4)):
|
||||
ledger.define(
|
||||
capability_id=f"cap{j}", name=f"c{j}", purpose="p",
|
||||
owning_project=f"p{i}",
|
||||
component=rnd.choice(ids),
|
||||
source_locations=[f"src/{rnd.randint(0, n_ent)}.py"])
|
||||
bh = BehaviourLedger(Path(tmp) / f"b{i}")
|
||||
for j in range(rnd.randint(1, 3)):
|
||||
bh.define(behaviour_id=f"b{j}", owning_project=f"p{i}",
|
||||
intended_behaviour="x",
|
||||
owner_component=rnd.choice(ids),
|
||||
dependencies=[rnd.choice(ids)])
|
||||
if rnd.random() < 0.6:
|
||||
bh.record_real_world(f"b{j}", passed=True, detail="ok")
|
||||
return model, ledger, bh
|
||||
|
||||
|
||||
def run_all(model, ledger, bh, target):
|
||||
"""Call every public fn through whatever core is currently pinned."""
|
||||
out = {}
|
||||
out["context_packet"] = it.context_packet(
|
||||
model, ledger, target, intent="i", basis_summary={"d": 1})
|
||||
out["predictive_impact"] = it.predictive_impact(model, target)
|
||||
out["descriptive_impact"] = it.descriptive_impact(
|
||||
model, target, observed_changed=[target, "surprise:x"],
|
||||
generation="gf")
|
||||
out["post_change_obligations"] = it.post_change_obligations(
|
||||
model, target, behaviours=bh)
|
||||
out["post_change_obligations_nobh"] = it.post_change_obligations(
|
||||
model, target)
|
||||
out["invalidate"] = None
|
||||
return out
|
||||
|
||||
|
||||
# ── 1. frozen replay ────────────────────────────────────────────────────
|
||||
|
||||
def frozen():
|
||||
for group, cases in TRUTH.items():
|
||||
if group in ("module", "now"):
|
||||
continue
|
||||
for c in cases:
|
||||
counts["frozen"] += 1
|
||||
args = copy.deepcopy(c["args"])
|
||||
fn = c["fn"]
|
||||
now = args.get("now", PIN_NOW)
|
||||
if fn == "context_packet":
|
||||
m = wrap(args["model"], StructuralModel)
|
||||
l = wrap(args.get("ledger") or {"schema_version": 1, "capabilities": {}}, CapabilityLedger)
|
||||
want = c["expect"]
|
||||
got_o = it.context_packet(
|
||||
m, l, args["target"], intent=args.get("intent", ""),
|
||||
basis_summary=args.get("basis_summary"),
|
||||
max_local_files=args.get("max_local_files", 12))
|
||||
got_k = itr_loader.context_packet(
|
||||
args["model"], args["ledger"], args["target"],
|
||||
intent=args.get("intent", ""),
|
||||
basis_summary=args.get("basis_summary"),
|
||||
max_local_files=args.get("max_local_files", 12),
|
||||
now=now)
|
||||
elif fn == "predictive_impact":
|
||||
m = wrap(args["model"], StructuralModel)
|
||||
want = c["expect"]
|
||||
got_o = it.predictive_impact(m, args["target"])
|
||||
got_k = itr_loader.predictive_impact(args["model"],
|
||||
args["target"])
|
||||
elif fn == "descriptive_impact":
|
||||
m = wrap(args["model"], StructuralModel)
|
||||
want = c["expect"]
|
||||
got_o = it.descriptive_impact(
|
||||
m, args["target"],
|
||||
observed_changed=args["observed_changed"],
|
||||
generation=args.get("generation", ""))
|
||||
got_k = itr_loader.descriptive_impact(
|
||||
args["model"], args["target"],
|
||||
observed_changed=args["observed_changed"],
|
||||
generation=args.get("generation", ""), now=now)
|
||||
elif fn == "record_impact_delta":
|
||||
want = c["expect"]
|
||||
got_o = json.dumps(args["delta"], sort_keys=True)
|
||||
got_k = itr_loader.record_delta_line(args["delta"])
|
||||
elif fn == "invalidate_behaviours":
|
||||
want = c["expect"]
|
||||
b = wrap(copy.deepcopy(args["behaviours"]), BehaviourLedger)
|
||||
recs_o = it.invalidate_behaviours(
|
||||
wrap({"relationships": []}, StructuralModel), b,
|
||||
changed_entities=set(args["changed"]),
|
||||
generation=args["generation"])
|
||||
got_o = {"doc": b.doc, "recs": recs_o}
|
||||
doc_k, recs_k = itr_loader.invalidate(
|
||||
args["behaviours"], args["changed"],
|
||||
args["generation"], now)
|
||||
got_k = {"doc": doc_k, "recs": recs_k}
|
||||
elif fn == "post_change_obligations":
|
||||
m = wrap(args["model"], StructuralModel)
|
||||
want = c["expect"]
|
||||
bh = (wrap(args["behaviours"], BehaviourLedger)
|
||||
if "behaviours" in args else None)
|
||||
got_o = it.post_change_obligations(
|
||||
m, args["target"],
|
||||
changed_entities=set(args["changed"])
|
||||
if "changed" in args else None,
|
||||
behaviours=bh)
|
||||
got_k = itr_loader.post_change_obligations(
|
||||
args["model"], args["target"],
|
||||
changed=args.get("changed"),
|
||||
behaviours_doc=args.get("behaviours"))
|
||||
else: # pragma: no cover
|
||||
continue
|
||||
for tag, got in (("oracle", got_o), ("kernel", got_k)):
|
||||
if json.dumps(got, sort_keys=True) != json.dumps(
|
||||
want, sort_keys=True):
|
||||
fail("frozen", f"{fn}/{c['name']}/{tag}",
|
||||
f"want={json.dumps(want, sort_keys=True)[:300]} "
|
||||
f"got={json.dumps(got, sort_keys=True)[:300]}")
|
||||
|
||||
|
||||
# ── 2+3. live + fuzz ────────────────────────────────────────────────────
|
||||
|
||||
def compare_pair(name, o, k):
|
||||
counts["live"] += 1
|
||||
jo, jk = json.dumps(o, sort_keys=True), json.dumps(k, sort_keys=True)
|
||||
if jo != jk:
|
||||
fail("live", name, f"oracle={jo[:300]} kernel={jk[:300]}")
|
||||
|
||||
|
||||
def live_and_fuzz():
|
||||
rnd = random.Random(2479)
|
||||
with tempfile.TemporaryDirectory(prefix="it_parity_") as tmp:
|
||||
# live on the frozen fixture inputs
|
||||
for group, cases in TRUTH.items():
|
||||
if group in ("module", "now"):
|
||||
continue
|
||||
for c in cases:
|
||||
args = c["args"]
|
||||
if "model" not in args:
|
||||
continue
|
||||
m = wrap(args["model"], StructuralModel)
|
||||
l = wrap(args.get("ledger") or {"schema_version": 1, "capabilities": {}}, CapabilityLedger)
|
||||
bh = (wrap(args["behaviours"], BehaviourLedger)
|
||||
if "behaviours" in args else None)
|
||||
target = args["target"]
|
||||
os.environ["RUNE_ITR_CORE"] = "python"
|
||||
o = run_all(m, l, bh or
|
||||
wrap({"behaviours": {}}, BehaviourLedger),
|
||||
target)
|
||||
os.environ["RUNE_ITR_CORE"] = "auto"
|
||||
k = run_all(m, l, bh or
|
||||
wrap({"behaviours": {}}, BehaviourLedger),
|
||||
target)
|
||||
for key in o:
|
||||
compare_pair(f"{c['name']}/{key}", o[key], k[key])
|
||||
# fuzz
|
||||
for i in range(400):
|
||||
model, ledger, bh = make_fixture(rnd, tmp, i)
|
||||
target = rnd.choice(
|
||||
[e["id"] for e in model.doc["entities"]])
|
||||
os.environ["RUNE_ITR_CORE"] = "python"
|
||||
o = run_all(model, ledger, bh, target)
|
||||
os.environ["RUNE_ITR_CORE"] = "auto"
|
||||
k = run_all(model, ledger, bh, target)
|
||||
for key in o:
|
||||
counts["fuzz"] += 1
|
||||
if json.dumps(o[key], sort_keys=True) != json.dumps(
|
||||
k[key], sort_keys=True):
|
||||
fail("fuzz", f"fuzz{i}/{key}",
|
||||
f"oracle={json.dumps(o[key], sort_keys=True)[:300]} "
|
||||
f"kernel={json.dumps(k[key], sort_keys=True)[:300]}")
|
||||
# record_impact_delta line parity
|
||||
delta = o["descriptive_impact"]
|
||||
counts["fuzz"] += 1
|
||||
if json.dumps(delta, sort_keys=True) != \
|
||||
itr_loader.record_delta_line(delta):
|
||||
fail("fuzz", f"fuzz{i}/delta_line", "line mismatch")
|
||||
# invalidate parity (deep-copied docs both sides)
|
||||
changed = set(rnd.sample(
|
||||
[e["id"] for e in model.doc["entities"]],
|
||||
k=min(2, len(model.doc["entities"]))))
|
||||
bo = BehaviourLedger.__new__(BehaviourLedger)
|
||||
bo.doc = copy.deepcopy(bh.doc)
|
||||
os.environ["RUNE_ITR_CORE"] = "python"
|
||||
recs_o = it.invalidate_behaviours(model, bo,
|
||||
changed_entities=changed,
|
||||
generation="gz")
|
||||
os.environ["RUNE_ITR_CORE"] = "auto"
|
||||
bk = BehaviourLedger.__new__(BehaviourLedger)
|
||||
bk.doc = copy.deepcopy(bh.doc)
|
||||
recs_k = it.invalidate_behaviours(model, bk,
|
||||
changed_entities=changed,
|
||||
generation="gz")
|
||||
counts["fuzz"] += 1
|
||||
if json.dumps(bo.doc, sort_keys=True) != json.dumps(
|
||||
bk.doc, sort_keys=True) or \
|
||||
json.dumps(recs_o, sort_keys=True) != \
|
||||
json.dumps(recs_k, sort_keys=True):
|
||||
fail("fuzz", f"fuzz{i}/invalidate", "doc/recs mismatch")
|
||||
|
||||
|
||||
def main():
|
||||
if not itr_loader.available():
|
||||
print("kernel not built — build src/rust/iteration first")
|
||||
return 1
|
||||
os.environ["RUNE_ITR_CORE"] = "python"
|
||||
frozen()
|
||||
live_and_fuzz()
|
||||
os.environ["RUNE_ITR_CORE"] = "auto"
|
||||
total = counts["frozen"] + counts["live"] + counts["fuzz"]
|
||||
print(f"parity {total - len(fails)}/{total} "
|
||||
f"(frozen {counts['frozen']}, live {counts['live']}, "
|
||||
f"fuzz {counts['fuzz']})")
|
||||
return 1 if fails else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Close the #2479 dichotomy-wave construction contract for <module>.
|
||||
verifying+closed back-to-back IN ONE PROCESS (shared-store clobber hazard).
|
||||
|
||||
Usage: close_contract_batch_a.py <module> <contract_id> <loader_py> <parity_sha> <phaseb_sha> <evidence>
|
||||
|
||||
The shared scaffold store is clobber-hazard territory: if the contract
|
||||
entity was clobbered by a concurrent batch, submit_need(contract_id=...)
|
||||
re-walks the SAME contract id on the fresh store (concept_topology law).
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
module, cid, loader_py, parity_sha, phaseb_sha = sys.argv[1:6]
|
||||
evidence = sys.argv[6]
|
||||
|
||||
iface = ConstructionInterface(builder="factory-batch-a")
|
||||
|
||||
r = iface.submit_need(
|
||||
f"Port src/rune/{module}.py to FULL Rust (dichotomy factory, issue "
|
||||
f"#2479 tranche): doc-envelope FFI, ctypes loader, facade delegation "
|
||||
f"pin, frozen-truth battery, live parity 100%, Phase B "
|
||||
f"language-advantage pass.",
|
||||
origin="builder",
|
||||
contract_id=cid,
|
||||
)
|
||||
print("submit_need:", json.dumps(r)[:300])
|
||||
if not r.get("success"):
|
||||
print("NEED FAILED — aborting")
|
||||
sys.exit(1)
|
||||
|
||||
t = iface.transition(cid, "verifying")
|
||||
print("transition->verifying:", json.dumps(t)[:300])
|
||||
if not t.get("success"):
|
||||
sys.exit(1)
|
||||
|
||||
c = iface.close(cid, observed_evidence=[{
|
||||
"kind": "port_evidence",
|
||||
"detail": evidence,
|
||||
}], scope_files=[
|
||||
f"src/rune/{module}.py",
|
||||
f"src/rune/{loader_py}",
|
||||
f"src/rust/{module}/src/lib.rs",
|
||||
f"src/rust/{module}/src/branch_battery.rs",
|
||||
f"scripts/gen_{module}_truth.py",
|
||||
])
|
||||
print("close:", json.dumps(c)[:400])
|
||||
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Close the #2479 dichotomy-wave construction contract for connection_pool
|
||||
with SHA evidence."""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
cid = sys.argv[1]
|
||||
iface = ConstructionInterface(builder="factory-batch-b")
|
||||
|
||||
c = iface.close(cid, observed_evidence=[{
|
||||
"kind": "port_evidence",
|
||||
"detail": "connection_pool dichotomy completion — parity 19887b9b "
|
||||
"feat(connection_pool): port to FULL Rust — dichotomy wave; "
|
||||
"Phase B (Action enum + zero-copy borrowed pooling-order "
|
||||
"scan) pushed gitea/master. Battery 2 tests replay the "
|
||||
"16-case frozen oracle truth VERBATIM (new/reuse/stale/"
|
||||
"evict/commit/status/close_all against a real sqlite-backed "
|
||||
"pool); live parity 60/60 random-op seeds identical (RUNE_"
|
||||
"CPOOL_CORE auto vs =python) + 16/16 frozen; pytest 2/2; "
|
||||
"clippy -D warnings clean. Classification flipped GO→RUST "
|
||||
"in store/rebase/classification.json (programme verdict; "
|
||||
"sqlite handles stay in the facade). Real bug the sweep "
|
||||
"caught pre-landing: facade stale path overwrote the dict "
|
||||
"entry in place instead of del-then-reinsert — re-created "
|
||||
"connections rejoined at the wrong pooling order.",
|
||||
}], scope_files=[
|
||||
"src/rune/connection_pool.py",
|
||||
"src/rune/cpool_loader.py",
|
||||
"src/rust/connection_pool/src/lib.rs",
|
||||
"src/rust/connection_pool/src/branch_battery.rs",
|
||||
"src/rust/connection_pool/frozen_cpool_truth.json",
|
||||
"scripts/gen_connection_pool_truth.py",
|
||||
"tests/test_connection_pool.py",
|
||||
"store/rebase/classification.json",
|
||||
"scripts/walk_contract_connection_pool.py",
|
||||
"scripts/close_contract_connection_pool.py",
|
||||
])
|
||||
print("close:", json.dumps(c)[:300])
|
||||
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Close the #2479 dichotomy-wave construction contract for
|
||||
construction_depth with SHA evidence."""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
cid = sys.argv[1]
|
||||
iface = ConstructionInterface(builder="factory-batch-b")
|
||||
|
||||
c = iface.close(cid, observed_evidence=[{
|
||||
"kind": "port_evidence",
|
||||
"detail": "construction_depth dichotomy completion — parity 265bb509 "
|
||||
"feat(construction_depth): port to FULL Rust — dichotomy wave; "
|
||||
"Phase B b8b14d5b refactor(construction_depth): Phase B "
|
||||
"language-advantage pass — dichotomy wave (both pushed "
|
||||
"gitea/master). Battery 2 tests replay the 40-case frozen "
|
||||
"oracle truth VERBATIM (select_depth + to_dict, every "
|
||||
"branch); live parity 500/500 fuzz (seed 2479) + 40/40 frozen "
|
||||
"(RUNE_CD_CORE auto vs =python); canary 81/81; clippy -D "
|
||||
"warnings clean; Python tests green (25 passed on "
|
||||
"depth-touching suites). Divergence fix: the facade-burn batch "
|
||||
"had flipped 2 held-out pytest assertions to kernel behavior "
|
||||
"(wide-surface escalation, PROJECT_BUILD ceremony) — restored "
|
||||
"to oracle truth, kernel realigned to the live oracle.",
|
||||
}], scope_files=[
|
||||
"src/rune/construction_depth.py",
|
||||
"src/rune/cd_loader.py",
|
||||
"src/rust/construction_depth/src/lib.rs",
|
||||
"src/rust/construction_depth/src/branch_battery.rs",
|
||||
"src/rust/construction_depth/frozen_cd_truth.json",
|
||||
"src/rust/construction_depth/canary_construction_depth.py",
|
||||
"scripts/gen_construction_depth_truth.py",
|
||||
"scripts/walk_contract_construction_depth.py",
|
||||
"scripts/close_contract_construction_depth.py",
|
||||
])
|
||||
print("close:", json.dumps(c)[:400])
|
||||
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_connection_pool_truth.py — freeze the connection_pool parity oracle
|
||||
(dichotomy wave #2479 tranche).
|
||||
|
||||
Generator law: RUNE_CPOOL_CORE=python pinned (the Rust core must never see
|
||||
the freeze), inputs deep-copied, every public kernel branch exercised
|
||||
(new / reuse / stale-removal / capacity-eviction / commit / status /
|
||||
close_all) against a REAL sqlite-backed pool in a temp dir. Output:
|
||||
src/rust/connection_pool/frozen_cpool_truth.json — replayed VERBATIM by the
|
||||
crate's cfg(test) battery.
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
os.environ["RUNE_CPOOL_CORE"] = "python"
|
||||
|
||||
from rune.connection_pool import ConnectionPool # noqa: E402
|
||||
|
||||
MAX = 2
|
||||
cases: list = []
|
||||
|
||||
|
||||
def pool_state(p: ConnectionPool) -> dict:
|
||||
"""The kernel-shaped pool state, as the oracle holds it right now."""
|
||||
st = p.get_status()
|
||||
return {"max_connections": st["max_connections"],
|
||||
"databases": copy.deepcopy(st["databases"])}
|
||||
|
||||
|
||||
def record_begin_get(p: ConnectionPool, db_path: str, valid: bool) -> None:
|
||||
"""Run one oracle get_connection op and freeze it: a cpool_begin_get
|
||||
case (and, when the oracle created a connection, the matching
|
||||
cpool_commit case)."""
|
||||
before = pool_state(p)
|
||||
pooled_before = dict(p.connections)
|
||||
conn = p.get_connection(db_path)
|
||||
after_full = pool_state(p)
|
||||
reuse = db_path in pooled_before and conn is pooled_before[db_path]
|
||||
if reuse:
|
||||
after_begin = copy.deepcopy(before)
|
||||
else:
|
||||
after_begin = {"max_connections": after_full["max_connections"],
|
||||
"databases": [d for d in after_full["databases"]
|
||||
if d != db_path]}
|
||||
evicted = (set(before["databases"]) - set(after_full["databases"])
|
||||
- {db_path})
|
||||
cases.append({
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {"pool": copy.deepcopy(before), "db_path": db_path,
|
||||
"valid": valid},
|
||||
"expected": {"pool": after_begin, "reuse": reuse,
|
||||
"evict": sorted(evicted)[0] if evicted else None},
|
||||
})
|
||||
if not reuse:
|
||||
cases.append({
|
||||
"fn": "cpool_commit",
|
||||
"args": {"pool": copy.deepcopy(after_begin), "db_path": db_path},
|
||||
"expected": {"pool": copy.deepcopy(after_full)},
|
||||
})
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
p = ConnectionPool(max_connections=MAX)
|
||||
cases.append({
|
||||
"fn": "cpool_new",
|
||||
"args": {"max_connections": MAX},
|
||||
"expected": copy.deepcopy(pool_state(p)),
|
||||
})
|
||||
|
||||
dbs = [os.path.join(td, f"{n}.db") for n in "ABCD"]
|
||||
|
||||
record_begin_get(p, dbs[0], True) # create A
|
||||
record_begin_get(p, dbs[0], True) # reuse A
|
||||
record_begin_get(p, dbs[1], True) # create B
|
||||
record_begin_get(p, dbs[2], True) # create C — evicts A
|
||||
# force B stale: close the underlying connection, then re-get
|
||||
p.connections[dbs[1]].close()
|
||||
record_begin_get(p, dbs[1], False) # stale B — no evict
|
||||
record_begin_get(p, dbs[3], True) # create D — evicts C
|
||||
|
||||
before_status = pool_state(p)
|
||||
cases.append({
|
||||
"fn": "cpool_status",
|
||||
"args": {"pool": copy.deepcopy(before_status)},
|
||||
"expected": copy.deepcopy(p.get_status()),
|
||||
})
|
||||
|
||||
before_close = pool_state(p)
|
||||
p.close_all()
|
||||
cases.append({
|
||||
"fn": "cpool_close_all",
|
||||
"args": {"pool": copy.deepcopy(before_close)},
|
||||
"expected": copy.deepcopy(pool_state(p)),
|
||||
})
|
||||
|
||||
record_begin_get(p, dbs[0], True) # fresh pool: create A
|
||||
|
||||
doc = {
|
||||
"module": "connection_pool",
|
||||
"pin": "RUNE_CPOOL_CORE=python",
|
||||
"frozen_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"cases": cases,
|
||||
}
|
||||
out = ROOT / "src" / "rust" / "connection_pool" / "frozen_cpool_truth.json"
|
||||
out.write_text(json.dumps(doc, indent=1) + "\n")
|
||||
print(f"frozen {len(cases)} cases -> {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_construction_depth_truth.py — freeze the construction_depth parity
|
||||
oracle (dichotomy wave #2479 tranche).
|
||||
|
||||
Generator law: RUNE_CD_CORE=python pinned (the Rust core must never see the
|
||||
freeze), inputs deep-copied, every public function/branch exercised. Output:
|
||||
src/rust/construction_depth/frozen_cd_truth.json — replayed VERBATIM by the
|
||||
crate's cfg(test) battery.
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
os.environ["RUNE_CD_CORE"] = "python"
|
||||
|
||||
from rune.construction_depth import Depth, select_depth # noqa: E402
|
||||
|
||||
WIDE = [f"surface-{i}" for i in range(6)]
|
||||
|
||||
CASES = [
|
||||
# branch: NOT_CONSTRUCTION → NONE, everything bypassed
|
||||
dict(mode="NOT_CONSTRUCTION"),
|
||||
# branch: UNKNOWN → UNRESOLVED, probe/clarify
|
||||
dict(mode="UNKNOWN"),
|
||||
# branch: LOCAL_CHANGE → LIGHT, minimal ceremony
|
||||
dict(mode="LOCAL_CHANGE", objective="fix typo"),
|
||||
# branch: LOCAL_CHANGE + user-locked layout
|
||||
dict(mode="LOCAL_CHANGE", objective="tweak layout", user_locked_layout=True),
|
||||
# branch: PROJECT_BUILD → FULL lifecycle
|
||||
dict(mode="PROJECT_BUILD", objective="new project"),
|
||||
# branch: EXISTING_PROJECT_CHANGE base (default path)
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", objective="add feature"),
|
||||
# modifier: migration
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", has_migration=True),
|
||||
# modifier: breaking
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", breaking=True),
|
||||
# modifier: user-locked layout
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", user_locked_layout=True),
|
||||
# modifier: external service
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", external_service=True),
|
||||
# modifier: uncertainty high (inserts evidence probe at ceremony[0])
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", uncertainty="high"),
|
||||
# modifier: uncertainty partial (no-op branch — no probe)
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", uncertainty="partial"),
|
||||
# modifier: wide surface (>5 entries) → FULL
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", affected_surface=list(WIDE)),
|
||||
# modifier: breaking + migration → FULL
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", breaking=True, has_migration=True),
|
||||
# every modifier at once (wide surface wins over breaking+migration reason)
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", has_migration=True, breaking=True,
|
||||
user_locked_layout=True, external_service=True,
|
||||
uncertainty="high", affected_surface=list(WIDE)),
|
||||
# non-canonical mode falls through to the EXISTING_PROJECT_CHANGE path
|
||||
dict(mode="RESEARCHED_CHANGE", objective="investigate options"),
|
||||
dict(mode="WEIRD_MODE"),
|
||||
# empty affected_surface list (falsy — no wide-surface branch)
|
||||
dict(mode="EXISTING_PROJECT_CHANGE", affected_surface=[]),
|
||||
# exactly 5 entries — boundary (not > 5, stays STANDARD)
|
||||
dict(mode="EXISTING_PROJECT_CHANGE",
|
||||
affected_surface=[f"s{i}" for i in range(5)]),
|
||||
# objective passthrough (ignored by the kernel — recorded for truth)
|
||||
dict(mode="LOCAL_CHANGE", objective="rename variable", uncertainty=""),
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cases = []
|
||||
for kwargs in CASES:
|
||||
args = copy.deepcopy(kwargs)
|
||||
d = select_depth(**copy.deepcopy(kwargs))
|
||||
expected = copy.deepcopy(d.to_dict())
|
||||
cases.append({"fn": "select_depth", "args": args, "expected": expected})
|
||||
# to_dict is public surface — freeze its output too (same payload)
|
||||
cases.append({"fn": "to_dict",
|
||||
"args": {"select": copy.deepcopy(kwargs)},
|
||||
"expected": copy.deepcopy(d.to_dict())})
|
||||
|
||||
doc = {
|
||||
"module": "construction_depth",
|
||||
"pin": "RUNE_CD_CORE=python",
|
||||
"frozen_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"cases": cases,
|
||||
}
|
||||
out = ROOT / "src" / "rust" / "construction_depth" / "frozen_cd_truth.json"
|
||||
out.write_text(json.dumps(doc, indent=1) + "\n")
|
||||
print(f"frozen {len(cases)} cases -> {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze the oracle truth for the GO port of src/rune/forge.py (dichotomy
|
||||
wave #2479, GO). Every public function, every branch, against the LIVE
|
||||
oracle. IO seams (subprocess, urllib, the gitea_auth credential ladder) are
|
||||
monkeypatched deterministically and every IO call is captured (argv, URL,
|
||||
headers) so the GO battery replays VERBATIM.
|
||||
|
||||
machine-dependent values are normalized:
|
||||
* subprocess argv[0] (sys.executable) is dropped — the GO port's python
|
||||
binary is a seam default ("python3"), so rows pin argv[1:] only
|
||||
* the credential ladder error message pins $HOME as <HOME>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
|
||||
from rune import forge # noqa: E402
|
||||
import gitea_auth # noqa: E402
|
||||
|
||||
rows: list[dict] = []
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, body: bytes):
|
||||
self._body = body
|
||||
|
||||
def read(self) -> bytes:
|
||||
return self._body
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc):
|
||||
return False
|
||||
|
||||
|
||||
class CapturedRequest:
|
||||
def __init__(self, req: urllib.request.Request):
|
||||
self.method = req.get_method()
|
||||
self.url = req.full_url
|
||||
self.headers = {k.lower(): v for k, v in req.header_items()}
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def fake_http(handler):
|
||||
"""handler(captured: CapturedRequest) → body bytes | raises OSError."""
|
||||
calls = []
|
||||
|
||||
def urlopen(req, timeout=None):
|
||||
cap = CapturedRequest(req)
|
||||
calls.append({"method": cap.method, "url": cap.url,
|
||||
"headers": cap.headers})
|
||||
out = handler(cap)
|
||||
if isinstance(out, OSError):
|
||||
raise out
|
||||
return FakeResponse(out)
|
||||
|
||||
orig = urllib.request.urlopen
|
||||
urllib.request.urlopen = urlopen
|
||||
try:
|
||||
yield calls
|
||||
finally:
|
||||
urllib.request.urlopen = orig
|
||||
|
||||
|
||||
class FakeProc:
|
||||
def __init__(self, rc, stdout="", stderr=""):
|
||||
self.returncode = rc
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def fake_subprocess(calls):
|
||||
def run(argv, capture_output=False, text=False, **kw):
|
||||
calls.append({"argv": list(argv)})
|
||||
return FakeProc(*PENDING_RC.get("args", (0, "", "")))
|
||||
orig = subprocess.run
|
||||
subprocess.run = run
|
||||
try:
|
||||
yield calls
|
||||
finally:
|
||||
subprocess.run = orig
|
||||
|
||||
|
||||
PENDING_RC = {"args": (0, "", "")}
|
||||
|
||||
|
||||
def record(name, op, setup, want):
|
||||
rows.append({"name": name, "op": op, "setup": setup, "want": want})
|
||||
|
||||
|
||||
def clean_env(monkeypatch_env: dict):
|
||||
"""Run fn with a controlled GITEA_* environment."""
|
||||
saved = {}
|
||||
keys = ["GITEA_SERVER", "GITEA_REPO", "GITEA_TOKEN", "GITEA_TOKEN_FILE"]
|
||||
for k in keys:
|
||||
saved[k] = os.environ.pop(k, None)
|
||||
os.environ.update({k: v for k, v in monkeypatch_env.items()})
|
||||
return saved
|
||||
|
||||
|
||||
def restore_env(saved):
|
||||
for k, v in saved.items():
|
||||
if v is None:
|
||||
os.environ.pop(k, None)
|
||||
else:
|
||||
os.environ[k] = v
|
||||
|
||||
|
||||
# ── default_forge ───────────────────────────────────────────────────────────
|
||||
f = forge.default_forge()
|
||||
record("default_forge:working-adapter", "default_forge", {},
|
||||
{"server": f._server, "repo": f._repo})
|
||||
|
||||
# ── _env ────────────────────────────────────────────────────────────────────
|
||||
for name, server, repo, env in [
|
||||
("env:inherits-and-defaults", None, None, {}),
|
||||
("env:server-override", "http://local:3000", None, {}),
|
||||
("env:repo-explicit", None, "other/repo", {}),
|
||||
("env:env-server-kept-when-no-attr", None, None,
|
||||
{"GITEA_SERVER": "http://env-server"}),
|
||||
("env:env-server-overridden-by-attr", "http://attr", None,
|
||||
{"GITEA_SERVER": "http://env-server"}),
|
||||
("env:env-repo-not-clobbered", None, None, {"GITEA_REPO": "env/repo"}),
|
||||
("env:inherits-sample-var", None, None, {"RUNE_SAMPLE": "1"}),
|
||||
]:
|
||||
eff_env = dict(env)
|
||||
eff_env.setdefault("RUNE_SAMPLE", "")
|
||||
saved = clean_env(eff_env)
|
||||
os.environ["RUNE_SAMPLE"] = eff_env["RUNE_SAMPLE"]
|
||||
gf = forge.GiteaForge(server=server, repo=repo or "conductor/rune")
|
||||
built = gf._env()
|
||||
want = {k: built.get(k) for k in
|
||||
("GITEA_SERVER", "GITEA_REPO", "RUNE_SAMPLE")}
|
||||
restore_env(saved)
|
||||
record(f"_env:{name}", "env", {"server": server, "repo": repo,
|
||||
"environ": eff_env}, want)
|
||||
|
||||
# ── push ────────────────────────────────────────────────────────────────────
|
||||
for name, rc, stderr, branch in [
|
||||
("push:ok", 0, "", "feature-x"),
|
||||
("push:fail-short-stderr", 1, "rejected non-fast-forward", "main"),
|
||||
("push:fail-long-stderr-truncates-200", 128, "e" * 300, "dev"),
|
||||
("push:fail-empty-stderr", 2, "", "release"),
|
||||
]:
|
||||
gf = forge.GiteaForge()
|
||||
calls = []
|
||||
PENDING_RC["args"] = (rc, "", stderr)
|
||||
with fake_subprocess(calls):
|
||||
try:
|
||||
out = gf.push("ignored/repo", branch)
|
||||
err = None
|
||||
except forge.ForgeUnavailable as exc:
|
||||
out, err = None, str(exc)
|
||||
PENDING_RC["args"] = (0, "", "")
|
||||
record(f"push:{name}", "push",
|
||||
{"repo": "ignored/repo", "branch": branch, "rc": rc,
|
||||
"stderr": stderr},
|
||||
{"result": out, "error": err, "calls": calls})
|
||||
|
||||
# ── gitea_auth credential ladder (the REAL scripts module) ─────────────────
|
||||
tmp = Path(tempfile.mkdtemp(prefix="forge-truth-"))
|
||||
FIXTURES = ROOT / "src/go/forge/fixtures"
|
||||
FIXTURES.mkdir(parents=True, exist_ok=True)
|
||||
good_file = FIXTURES / "token0600"
|
||||
good_file.write_text("filetoken123\n")
|
||||
os.chmod(good_file, 0o600)
|
||||
bad_file = FIXTURES / "token0644"
|
||||
bad_file.write_text("weaktoken\n")
|
||||
os.chmod(bad_file, 0o644)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def fake_helper():
|
||||
orig = gitea_auth._token_from_credential_helper
|
||||
gitea_auth._token_from_credential_helper = lambda: ""
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
gitea_auth._token_from_credential_helper = orig
|
||||
|
||||
|
||||
# rung 1: GITEA_TOKEN (stripped)
|
||||
saved = clean_env({})
|
||||
os.environ["GITEA_TOKEN"] = " envtoken \n"
|
||||
record("gitea_auth:env-token-rung", "gitea_auth",
|
||||
{"environ": {"GITEA_TOKEN": " envtoken \n"}},
|
||||
{"has_token": gitea_auth.has_token(),
|
||||
"get_token": gitea_auth.get_token()})
|
||||
os.environ.pop("GITEA_TOKEN")
|
||||
# rung 2: token file (0600)
|
||||
os.environ["GITEA_TOKEN_FILE"] = str(good_file)
|
||||
record("gitea_auth:token-file-0600", "gitea_auth",
|
||||
{"environ": {"GITEA_TOKEN_FILE": "<fixture token0600>"}},
|
||||
{"has_token": gitea_auth.has_token(),
|
||||
"get_token": gitea_auth.get_token()})
|
||||
# rung 2 rejected: token file (0644 → PermissionError)
|
||||
os.environ["GITEA_TOKEN_FILE"] = str(bad_file)
|
||||
try:
|
||||
gitea_auth.get_token()
|
||||
got = {"has_token": gitea_auth.has_token(), "get_token": None,
|
||||
"error": None}
|
||||
except PermissionError as exc:
|
||||
got = {"has_token": gitea_auth.has_token(), "get_token": None,
|
||||
"error": str(exc).replace(str(FIXTURES) + "/", "<FIXTURES>/")}
|
||||
record("gitea_auth:token-file-0644-rejected", "gitea_auth",
|
||||
{"environ": {"GITEA_TOKEN_FILE": "<fixture token0644>"}}, got)
|
||||
# rung 3 empty + nothing anywhere → GiteaCredentialError. The default token
|
||||
# file and the credential helper are pinned so the row is machine-independent.
|
||||
os.environ.pop("GITEA_TOKEN_FILE")
|
||||
pinned_file = tmp / "missing-token"
|
||||
orig_default = gitea_auth.DEFAULT_TOKEN_FILE
|
||||
gitea_auth.DEFAULT_TOKEN_FILE = pinned_file
|
||||
with fake_helper():
|
||||
try:
|
||||
gitea_auth.get_token()
|
||||
no_cred = {"has_token": True, "get_token": "unexpected",
|
||||
"error": None}
|
||||
except gitea_auth.GiteaCredentialError as exc:
|
||||
no_cred = {"has_token": gitea_auth.has_token(), "get_token": None,
|
||||
"error": str(exc).replace(str(tmp) + "/", "<TMP>/")}
|
||||
gitea_auth.DEFAULT_TOKEN_FILE = orig_default
|
||||
record("gitea_auth:no-credential", "gitea_auth",
|
||||
{"default_token_file": "<TMP>/missing-token"}, no_cred)
|
||||
restore_env(saved)
|
||||
|
||||
|
||||
# ── commit_status ───────────────────────────────────────────────────────────
|
||||
def auth_stub(has=True, token="t0k3n"):
|
||||
m = types.SimpleNamespace()
|
||||
m.has_token = lambda: has
|
||||
m.get_token = lambda: token
|
||||
return m
|
||||
|
||||
|
||||
for name, server, env_server, repo, sha, has_tok, body, http_err in [
|
||||
("commit_status:no-token", None, None, "conductor/rune", "abc", False,
|
||||
None, None),
|
||||
("commit_status:success", None, None, "conductor/rune", "abc", True,
|
||||
b'{"state": "success"}', None),
|
||||
("commit_status:state-missing-unknown", None, None, "o/r", "def", True,
|
||||
b'{"statuses": []}', None),
|
||||
("commit_status:server-attr-wins", "http://attr:3000", "http://env:3000",
|
||||
"o/r", "ghi", True, b'{"state": "pending"}', None),
|
||||
("commit_status:server-from-env", None, "http://env:3000", "o/r", "ghi",
|
||||
True, b'{"state": "failure"}', None),
|
||||
("commit_status:http-error", None, None, "o/r", "jkl", True, None,
|
||||
OSError("HTTP Error 404: Not Found")),
|
||||
("commit_status:network-error", None, None, "o/r", "jkl", True, None,
|
||||
OSError("conn refused")),
|
||||
]:
|
||||
gf = forge.GiteaForge(server=server)
|
||||
saved = clean_env({"GITEA_SERVER": env_server} if env_server else {})
|
||||
forge.GiteaForge._gitea_auth = staticmethod(
|
||||
lambda: auth_stub(has=has_tok)) # noqa: B010
|
||||
calls = []
|
||||
try:
|
||||
with fake_http(lambda cap: http_err if http_err else body) as captured:
|
||||
try:
|
||||
state = gf.commit_status(repo, sha)
|
||||
err = None
|
||||
except forge.ForgeUnavailable as exc:
|
||||
state, err = None, str(exc)
|
||||
req = captured[0] if captured else None
|
||||
finally:
|
||||
restore_env(saved)
|
||||
del forge.GiteaForge._gitea_auth
|
||||
record(f"commit_status:{name}", "commit_status",
|
||||
{"server": server, "env_server": env_server, "repo": repo,
|
||||
"sha": sha, "has_token": has_tok,
|
||||
"body": body.decode() if body else None, "http_err":
|
||||
str(http_err) if http_err else None},
|
||||
{"state": state, "error": err, "request": req})
|
||||
|
||||
# ── ci_runs_for_sha ─────────────────────────────────────────────────────────
|
||||
runs_payload = {"workflow_runs": [
|
||||
{"id": 1, "head_sha": "abc"},
|
||||
{"id": 2, "head_sha": None},
|
||||
{"id": 3, "head_sha": "other"},
|
||||
{"id": 4},
|
||||
{"id": 5, "head_sha": "abc"},
|
||||
]}
|
||||
for name, server, repo, sha, payload, err in [
|
||||
("ci_runs:no-token", None, "o/r", "s", None, None),
|
||||
("ci_runs:filter-by-head-sha", None, "o/r", "abc", runs_payload,
|
||||
None),
|
||||
("ci_runs:none-matching", None, "o/r", "zzz", runs_payload, None),
|
||||
("ci_runs:absent-key", None, "o/r", "s", {"total_count": 0}, None),
|
||||
("ci_runs:empty-sha-matches-absent", None, "o/r", "", runs_payload,
|
||||
None),
|
||||
("ci_runs:unreachable", None, "o/r", "s", None,
|
||||
OSError("runs down")),
|
||||
]:
|
||||
gf = forge.GiteaForge(server=server)
|
||||
saved = clean_env({})
|
||||
forge.GiteaForge._gitea_auth = staticmethod(
|
||||
lambda: auth_stub(has=payload is not None or "no-token" not in name))
|
||||
calls = []
|
||||
try:
|
||||
with fake_http(lambda cap: err if err else json.dumps(payload).encode()) as captured:
|
||||
try:
|
||||
got = gf.ci_runs_for_sha(repo, sha)
|
||||
e = None
|
||||
except forge.ForgeUnavailable as exc:
|
||||
got, e = None, str(exc)
|
||||
req = captured[0] if captured else None
|
||||
finally:
|
||||
restore_env(saved)
|
||||
del forge.GiteaForge._gitea_auth
|
||||
record(f"ci_runs:{name}", "ci_runs",
|
||||
{"server": server, "repo": repo, "sha": sha, "payload": payload,
|
||||
"err": str(err) if err else None,
|
||||
"has_token": payload is not None or "no-token" not in name},
|
||||
{"runs": got, "error": e, "request": req})
|
||||
|
||||
# ── create_or_patch_release ────────────────────────────────────────────────
|
||||
for name, has_tok, script_present, rc, stderr, stdout, tag in [
|
||||
("release:no-token", False, True, 0, "", "", "v9.9.9"),
|
||||
("release:published", True, True, 0, "", "", "v1.2.3"),
|
||||
("release:script-fails-stderr", True, True, 3, "tag exists already",
|
||||
"", "v0.0.1"),
|
||||
("release:script-fails-falls-back-stdout", True, True, 4, "",
|
||||
"o" * 250, "v0.0.2"),
|
||||
("release:script-missing", True, False, 0, "", "", "v0.0.3"),
|
||||
]:
|
||||
gf = forge.GiteaForge()
|
||||
saved = clean_env({})
|
||||
forge.GiteaForge._gitea_auth = staticmethod(lambda: auth_stub(has=has_tok))
|
||||
calls = []
|
||||
cwd = ROOT if script_present else str(tmp)
|
||||
old_cwd = os.getcwd()
|
||||
os.chdir(cwd)
|
||||
PENDING_RC["args"] = (rc, stdout, stderr)
|
||||
try:
|
||||
with fake_subprocess(calls):
|
||||
try:
|
||||
got = gf.create_or_patch_release("o/r", tag, "T", "B")
|
||||
e = None
|
||||
except forge.ForgeUnavailable as exc:
|
||||
got, e = None, str(exc)
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
del forge.GiteaForge._gitea_auth
|
||||
PENDING_RC["args"] = (0, "", "")
|
||||
restore_env(saved)
|
||||
argv = calls[0]["argv"][1:] if calls else None
|
||||
record(f"release:{name}", "release",
|
||||
{"has_token": has_tok, "script_present": script_present, "rc": rc,
|
||||
"stderr": stderr, "stdout": stdout, "tag": tag,
|
||||
"cwd": "<root>" if script_present else "<tmp>"},
|
||||
{"result": got, "error": e, "argv": argv,
|
||||
"python_bin": calls[0]["argv"][0] if calls else None})
|
||||
|
||||
doc = {
|
||||
"frozen_ts": "2026-10-12T00:00:00+00:00",
|
||||
"generator": "scripts/gen_forge_truth.py — live oracle src/rune/forge.py",
|
||||
"rows": rows,
|
||||
}
|
||||
out = ROOT / "src/go/forge/truth.json"
|
||||
out.write_text(json.dumps(doc, indent=1, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8")
|
||||
print(f"frozen {len(rows)} rows → {out}")
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze the dichotomy-wave truth corpus for src/rune/iteration.py.
|
||||
|
||||
LAW (poisoned-truth): RUNE_IT_CORE=python is pinned at the top — truth is
|
||||
generated by the PYTHON ORACLE ONLY, never by the kernel under test.
|
||||
RUNE_LEDGER_CORE=python pins the capability ledger to its Python floor.
|
||||
The evidence_provenance cdylib stays LIVE: it is part of the oracle stack
|
||||
(behaviour_ledger has no Python floor) and is already parity-verified.
|
||||
|
||||
Every public function is exercised across ALL branches. Timestamps:
|
||||
iteration._now / behaviour_ledger._now are monkeypatched to a FIXED value
|
||||
so the frozen corpus is byte-stable; the Rust kernel receives `now` as an
|
||||
argument (the same determinism seam the facade uses live).
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["RUNE_IT_CORE"] = "python" # poisoned-truth law
|
||||
os.environ["RUNE_LEDGER_CORE"] = "python" # capability_ledger floor
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
sys.path.insert(0, str(REPO / "src" / "rust" / "evidence_provenance"))
|
||||
|
||||
import rune.iteration as it # noqa: E402
|
||||
import rune.behaviour_ledger as bl_mod # noqa: E402
|
||||
from rune.structural_model import StructuralModel # noqa: E402
|
||||
from rune.capability_ledger import CapabilityLedger # noqa: E402
|
||||
from rune.behaviour_ledger import BehaviourLedger # noqa: E402
|
||||
|
||||
FIXED_NOW = "2026-10-12T00:00:00Z"
|
||||
it._now = lambda: FIXED_NOW
|
||||
bl_mod._now = lambda: FIXED_NOW
|
||||
|
||||
OUT = REPO / "src" / "rust" / "iteration" / "src" / "frozen_it_truth.json"
|
||||
|
||||
cases = []
|
||||
|
||||
|
||||
def case(fn, name, args, expect):
|
||||
cases.append({"fn": fn, "name": name,
|
||||
"args": args, "expect": expect})
|
||||
|
||||
|
||||
def build_fixture(tmp):
|
||||
model = StructuralModel(Path(tmp) / "model", "demo")
|
||||
model.add("subsystem", "core")
|
||||
model.add("component", "alpha", parent="subsystem:core",
|
||||
ext={"source": "src/alpha.py"})
|
||||
model.add("component", "beta", parent="subsystem:core")
|
||||
for e in model.doc["entities"]:
|
||||
if e["id"] == "component:beta":
|
||||
e["identities"]["source"] = "src/beta.py"
|
||||
model.add("interface", "api")
|
||||
model.add("interface", "legacy")
|
||||
model.add("state_store", "db")
|
||||
model.add("test_suite", "t1")
|
||||
model.add("deployment_surface", "prod")
|
||||
model.add("component", "gamma", parent="subsystem:core")
|
||||
model.add("component", "delta", parent="subsystem:core")
|
||||
model.relate("component:alpha", "provides", "interface:api")
|
||||
model.relate("component:gamma", "consumes", "interface:api")
|
||||
model.relate("component:delta", "depends_on", "component:alpha",
|
||||
status="stale")
|
||||
model.relate("component:alpha", "consumes", "interface:legacy")
|
||||
model.relate("component:alpha", "owns", "state_store:db")
|
||||
model.relate("component:alpha", "verified_by", "test_suite:t1")
|
||||
model.relate("component:alpha", "deployed_by", "deployment_surface:prod")
|
||||
model.relate("component:gamma", "provides", "interface:legacy")
|
||||
model.relate("component:gamma", "owns", "state_store:db")
|
||||
model.save()
|
||||
|
||||
ledger = CapabilityLedger(Path(tmp) / "ledger")
|
||||
ledger.define(
|
||||
capability_id="alpha-cap", name="alpha", purpose="alpha purpose",
|
||||
owning_project="demo", component="component:alpha",
|
||||
source_locations=["src/alpha.py", "src/alpha_util.py"],
|
||||
contract="alpha contract", state_touched="state_store:db",
|
||||
dependencies=["interface:legacy"], consumers=["component:gamma"],
|
||||
invariants=["inv-1"], behaviour_specs=["b-alpha"],
|
||||
constraints="no network", generation="g1")
|
||||
ledger.define(
|
||||
capability_id="beta-cap", name="beta", purpose="beta purpose",
|
||||
owning_project="demo", component="component:beta",
|
||||
source_locations=["src/beta.py"])
|
||||
ledger.doc["capabilities"]["capability:beta-cap"]["maturity"] = "retired"
|
||||
ledger.define(
|
||||
capability_id="gamma-cap", name="gamma", purpose="gamma purpose",
|
||||
owning_project="demo", component="component:gamma",
|
||||
source_locations=["src/gamma.py", "src/gamma_more.py"])
|
||||
|
||||
behaviours = BehaviourLedger(Path(tmp) / "behaviours")
|
||||
behaviours.define(
|
||||
behaviour_id="b-alpha", owning_project="demo",
|
||||
intended_behaviour="alpha behaves", owner_component="component:alpha",
|
||||
dependencies=["component:beta"])
|
||||
behaviours.define(
|
||||
behaviour_id="b-gamma", owning_project="demo",
|
||||
intended_behaviour="gamma behaves", owner_component="component:gamma")
|
||||
behaviours.record_real_world("b-alpha", passed=True, detail="ok")
|
||||
return model, ledger, behaviours
|
||||
|
||||
|
||||
def main():
|
||||
tmp = tempfile.mkdtemp(prefix="it_truth_")
|
||||
try:
|
||||
model, ledger, behaviours = build_fixture(tmp)
|
||||
model_doc = model.doc
|
||||
ledger_doc = ledger.doc
|
||||
bh_doc = behaviours.doc
|
||||
|
||||
# ── context_packet ────────────────────────────────────────────
|
||||
r = it.context_packet(model, ledger, "component:alpha")
|
||||
case("context_packet", "alpha_full",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "component:alpha", "now": FIXED_NOW}, r)
|
||||
r = it.context_packet(model, ledger, "nope:missing")
|
||||
case("context_packet", "unknown_target",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "nope:missing", "now": FIXED_NOW}, r)
|
||||
r = it.context_packet(model, ledger, "component:gamma",
|
||||
intent="ship gamma", basis_summary={"depth": 2})
|
||||
case("context_packet", "gamma_intent_basis",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "component:gamma", "intent": "ship gamma",
|
||||
"basis_summary": {"depth": 2}, "now": FIXED_NOW}, r)
|
||||
r = it.context_packet(model, ledger, "component:alpha",
|
||||
max_local_files=1)
|
||||
case("context_packet", "alpha_truncated",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "component:alpha", "max_local_files": 1,
|
||||
"now": FIXED_NOW}, r)
|
||||
r = it.context_packet(model, ledger, "component:beta")
|
||||
case("context_packet", "beta_identities_source",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "component:beta", "now": FIXED_NOW}, r)
|
||||
r = it.context_packet(model, ledger, "interface:api")
|
||||
case("context_packet", "interface_target",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "interface:api", "now": FIXED_NOW}, r)
|
||||
r = it.context_packet(model, ledger, "project:demo")
|
||||
case("context_packet", "root_project",
|
||||
{"model": model_doc, "ledger": ledger_doc,
|
||||
"target": "project:demo", "now": FIXED_NOW}, r)
|
||||
empty = CapabilityLedger.__new__(CapabilityLedger)
|
||||
empty.doc = {"schema_version": 1, "capabilities": {}}
|
||||
r = it.context_packet(model, empty, "component:alpha")
|
||||
case("context_packet", "empty_ledger",
|
||||
{"model": model_doc, "ledger": empty.doc,
|
||||
"target": "component:alpha", "now": FIXED_NOW}, r)
|
||||
|
||||
# ── predictive_impact ─────────────────────────────────────────
|
||||
for name, target in [("alpha", "component:alpha"),
|
||||
("unknown", "nope:missing"),
|
||||
("leaf", "component:beta")]:
|
||||
r = it.predictive_impact(model, target)
|
||||
case("predictive_impact", name,
|
||||
{"model": model_doc, "target": target}, r)
|
||||
|
||||
# ── descriptive_impact ────────────────────────────────────────
|
||||
for name, kwargs in [
|
||||
("full_overlap", {"observed_changed": ["component:alpha",
|
||||
"component:gamma"]}),
|
||||
("unexpected", {"observed_changed": ["component:alpha",
|
||||
"surprise:one"]}),
|
||||
("none_observed", {"observed_changed": []}),
|
||||
]:
|
||||
r = it.descriptive_impact(model, "component:alpha", **kwargs)
|
||||
case("descriptive_impact", name,
|
||||
{"model": model_doc, "target": "component:alpha",
|
||||
"observed_changed": kwargs["observed_changed"],
|
||||
"now": FIXED_NOW}, r)
|
||||
r = it.descriptive_impact(model, "component:alpha",
|
||||
observed_changed=["component:gamma"],
|
||||
generation="g7")
|
||||
case("descriptive_impact", "generation_tag",
|
||||
{"model": model_doc, "target": "component:alpha",
|
||||
"observed_changed": ["component:gamma"],
|
||||
"generation": "g7", "now": FIXED_NOW}, r)
|
||||
r = it.descriptive_impact(model, "nope:missing",
|
||||
observed_changed=["x"])
|
||||
case("descriptive_impact", "unknown_target",
|
||||
{"model": model_doc, "target": "nope:missing",
|
||||
"observed_changed": ["x"], "now": FIXED_NOW}, r)
|
||||
|
||||
# ── record_impact_delta (exact jsonl line — file IO facade) ───
|
||||
deltas = [
|
||||
{"target": "component:alpha", "generation": "g1",
|
||||
"predicted": ["component:alpha"], "observed": [],
|
||||
"unexpected": [], "unaffected_as_predicted":
|
||||
["component:alpha"], "recorded_at": FIXED_NOW},
|
||||
{"b": 1, "a": [1.5, 2], "c": None, "s": "é\nx"},
|
||||
{"nested": {"z": True, "y": 0.1}},
|
||||
]
|
||||
for i, delta in enumerate(deltas):
|
||||
d = Path(tmp) / f"deltas{i}"
|
||||
d.mkdir()
|
||||
it.record_impact_delta(d, delta)
|
||||
line = (d / "impact_deltas.jsonl").read_text()
|
||||
assert line.endswith("\n") and line.count("\n") == 1
|
||||
case("record_impact_delta", f"line{i}", {"delta": delta},
|
||||
line[:-1])
|
||||
|
||||
# ── invalidate_behaviours ─────────────────────────────────────
|
||||
for name, changed in [
|
||||
("hit_deps_and_owner", {"component:alpha", "component:beta"}),
|
||||
("owner_only", {"component:gamma"}),
|
||||
("no_intersection", {"component:delta"}),
|
||||
]:
|
||||
b = BehaviourLedger.__new__(BehaviourLedger)
|
||||
b.doc = copy.deepcopy(bh_doc)
|
||||
recs = it.invalidate_behaviours(
|
||||
model, b, changed_entities=changed, generation="g9")
|
||||
case("invalidate_behaviours", name,
|
||||
{"behaviours": bh_doc, "changed": sorted(changed),
|
||||
"generation": "g9", "now": FIXED_NOW},
|
||||
{"doc": b.doc, "recs": recs})
|
||||
|
||||
# ── post_change_obligations ───────────────────────────────────
|
||||
for name, target, changed in [
|
||||
("alpha_default", "component:alpha", None),
|
||||
("alpha_two_changed", "component:alpha",
|
||||
["component:alpha", "component:gamma"]),
|
||||
("beta_leaf", "component:beta", None),
|
||||
("gamma_consumes_iface", "component:gamma", None),
|
||||
("delta_stale_dep", "component:delta", None),
|
||||
("unknown_in_changed", "component:beta",
|
||||
["component:beta", "ghost:x"]),
|
||||
]:
|
||||
if changed is None:
|
||||
r = it.post_change_obligations(model, target)
|
||||
args = {"model": model_doc, "target": target}
|
||||
else:
|
||||
r = it.post_change_obligations(
|
||||
model, target, changed_entities=set(changed))
|
||||
args = {"model": model_doc, "target": target,
|
||||
"changed": changed}
|
||||
case("post_change_obligations", name, args, r)
|
||||
r = it.post_change_obligations(model, "component:alpha",
|
||||
behaviours=behaviours)
|
||||
case("post_change_obligations", "alpha_with_behaviours",
|
||||
{"model": model_doc, "target": "component:alpha",
|
||||
"behaviours": bh_doc}, r)
|
||||
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
groups = {}
|
||||
for c in cases:
|
||||
groups.setdefault(f"{c['fn']}_cases", []).append(c)
|
||||
OUT.write_text(json.dumps(
|
||||
{"module": "iteration", "now": FIXED_NOW, **groups},
|
||||
indent=1, sort_keys=True) + "\n")
|
||||
print(f"frozen {len(cases)} cases -> {OUT}")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze the oracle truth for the GO port of src/rune/verification_layer.py
|
||||
(dichotomy wave #2479, GO). Every public function, every branch, against the
|
||||
LIVE oracle (which delegates to the Rust evidence_provenance core — parity
|
||||
already proven 33/33). Rows pin the full VerificationResult field set AND the
|
||||
byte-exact json.dumps(to_dict()) rendering. handle_hook + PLUGIN pinned too.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune import verification_layer as vl # noqa: E402
|
||||
|
||||
FIXTURE = ROOT / "src/go/verification_layer/.fixtures/exists.txt"
|
||||
|
||||
rows: list[dict] = []
|
||||
|
||||
|
||||
def row(name, fn, verifier_type, result):
|
||||
got = getattr(vl, fn)(dict(result), verifier_type) if fn == "verify" \
|
||||
else getattr(vl, fn)(dict(result))
|
||||
rows.append({
|
||||
"name": name,
|
||||
"fn": fn,
|
||||
"verifier_type": verifier_type,
|
||||
"result": result,
|
||||
"want": got.to_dict(),
|
||||
"want_json": json.dumps(got.to_dict()),
|
||||
})
|
||||
|
||||
|
||||
def hook_row(name, event, data=None):
|
||||
got = vl.handle_hook(event) if data is None else vl.handle_hook(event, data)
|
||||
rows.append({
|
||||
"name": name,
|
||||
"fn": "handle_hook",
|
||||
"verifier_type": "",
|
||||
"result": {"event": event, "data": data},
|
||||
"want": got,
|
||||
"want_json": json.dumps(got, sort_keys=True),
|
||||
})
|
||||
|
||||
|
||||
# ── verify_exit_code: every branch ──────────────────────────────────────────
|
||||
row("exit_code:absent-defaults-0", "verify_exit_code", "exit_code", {})
|
||||
row("exit_code:zero-pass", "verify_exit_code", "exit_code", {"exit_code": 0})
|
||||
row("exit_code:one-fail", "verify_exit_code", "exit_code", {"exit_code": 1})
|
||||
row("exit_code:negative-fail", "verify_exit_code", "exit_code", {"exit_code": -3})
|
||||
row("exit_code:float-zero-pass", "verify_exit_code", "exit_code", {"exit_code": 0.0})
|
||||
row("exit_code:float-frac-fail", "verify_exit_code", "exit_code", {"exit_code": 0.5})
|
||||
row("exit_code:bool-true-fails", "verify_exit_code", "exit_code", {"exit_code": True})
|
||||
row("exit_code:bool-false-passes", "verify_exit_code", "exit_code", {"exit_code": False})
|
||||
row("exit_code:str-zero-not-equal", "verify_exit_code", "exit_code", {"exit_code": "0"})
|
||||
row("exit_code:null-not-equal", "verify_exit_code", "exit_code", {"exit_code": None})
|
||||
row("exit_code:big-int-evidence", "verify_exit_code", "exit_code", {"exit_code": 91247})
|
||||
|
||||
# ── verify_unit_test ────────────────────────────────────────────────────────
|
||||
row("unit_test:all-defaults-total-zero-fails", "verify_unit_test", "unit_test", {})
|
||||
row("unit_test:pass", "verify_unit_test", "unit_test",
|
||||
{"tests_passed": 10, "tests_failed": 0, "tests_total": 10})
|
||||
row("unit_test:two-fail", "verify_unit_test", "unit_test",
|
||||
{"tests_passed": 3, "tests_failed": 2, "tests_total": 5})
|
||||
row("unit_test:failed-zero-total-positive", "verify_unit_test", "unit_test",
|
||||
{"tests_failed": 0, "tests_total": 7})
|
||||
row("unit_test:bool-failed-false-total-true", "verify_unit_test", "unit_test",
|
||||
{"tests_failed": False, "tests_total": True})
|
||||
row("unit_test:failed-true-counts-1", "verify_unit_test", "unit_test",
|
||||
{"tests_failed": True, "tests_total": 1})
|
||||
row("unit_test:str-total-ignored", "verify_unit_test", "unit_test",
|
||||
{"tests_total": "5", "tests_failed": 0})
|
||||
|
||||
# ── verify_schema ───────────────────────────────────────────────────────────
|
||||
row("schema:defaults-pass", "verify_schema", "schema", {})
|
||||
row("schema:valid-true-no-errors", "verify_schema", "schema", {"schema_valid": True})
|
||||
row("schema:invalid-fail", "verify_schema", "schema", {"schema_valid": False})
|
||||
row("schema:errors-fail", "verify_schema", "schema",
|
||||
{"schema_valid": True, "schema_errors": ["bad type at $.a", "missing $.b"]})
|
||||
row("schema:empty-errors-pass", "verify_schema", "schema",
|
||||
{"schema_valid": True, "schema_errors": []})
|
||||
row("schema:errors-nonlist-ignored", "verify_schema", "schema",
|
||||
{"schema_valid": True, "schema_errors": "nope"})
|
||||
row("schema:errors-with-nested-values", "verify_schema", "schema",
|
||||
{"schema_valid": False, "schema_errors": [3, {"path": "$.x", "msg": "type"}, True]})
|
||||
row("schema:valid-null-defaults-true", "verify_schema", "schema",
|
||||
{"schema_valid": None})
|
||||
row("schema:valid-zero-falsy", "verify_schema", "schema", {"schema_valid": 0})
|
||||
row("schema:valid-truthy-str", "verify_schema", "schema", {"schema_valid": "yes"})
|
||||
row("schema:unicode-error-escaping", "verify_schema", "schema",
|
||||
{"schema_valid": False, "schema_errors": ["ungültig bei „$.pfad“"]})
|
||||
|
||||
# ── verify_file_exists (fixture file is committed next to the port) ────────
|
||||
row("file_exists:exists-pass", "verify_file_exists", "file_exists",
|
||||
{"file_path": str(FIXTURE)})
|
||||
row("file_exists:missing-fail", "verify_file_exists", "file_exists",
|
||||
{"file_path": str(ROOT / "src/go/verification_layer/.fixtures/definitely_missing.txt")})
|
||||
row("file_exists:empty-path-fail", "verify_file_exists", "file_exists",
|
||||
{"file_path": ""})
|
||||
row("file_exists:dir-counts", "verify_file_exists", "file_exists",
|
||||
{"file_path": str(ROOT / "src/go/verification_layer/.fixtures")})
|
||||
row("file_exists:absent-key-fail", "verify_file_exists", "file_exists", {})
|
||||
|
||||
# ── verify_file_changed ─────────────────────────────────────────────────────
|
||||
row("file_changed:empty-expected-pass", "verify_file_changed", "file_changed",
|
||||
{"file_path": "a.txt", "expected_hash": "", "actual_hash": "deadbeef"})
|
||||
row("file_changed:match-pass", "verify_file_changed", "file_changed",
|
||||
{"file_path": "a.txt", "expected_hash": "cafe", "actual_hash": "cafe"})
|
||||
row("file_changed:mismatch-fail", "verify_file_changed", "file_changed",
|
||||
{"file_path": "a.txt", "expected_hash": "cafe", "actual_hash": "beef"})
|
||||
row("file_changed:no-keys-pass", "verify_file_changed", "file_changed", {})
|
||||
|
||||
# ── verify_tool_receipt ─────────────────────────────────────────────────────
|
||||
row("tool_receipt:default-pass", "verify_tool_receipt", "tool_receipt", {})
|
||||
row("tool_receipt:success-true", "verify_tool_receipt", "tool_receipt",
|
||||
{"tool_success": True, "tool_name": "grep"})
|
||||
row("tool_receipt:fail-with-error", "verify_tool_receipt", "tool_receipt",
|
||||
{"tool_success": False, "tool_error": "boom: exit 2"})
|
||||
row("tool_receipt:fail-empty-error", "verify_tool_receipt", "tool_receipt",
|
||||
{"tool_success": False})
|
||||
row("tool_receipt:zero-falsy", "verify_tool_receipt", "tool_receipt",
|
||||
{"tool_success": 0, "tool_error": "falsy rc"})
|
||||
row("tool_receipt:error-str-truthy", "verify_tool_receipt", "tool_receipt",
|
||||
{"tool_success": "ok"})
|
||||
|
||||
# ── verify_constraint_check ─────────────────────────────────────────────────
|
||||
row("constraint:default-pass", "verify_constraint_check", "constraint_check", {})
|
||||
row("constraint:met-true-pass", "verify_constraint_check", "constraint_check",
|
||||
{"constraints_met": True})
|
||||
row("constraint:unmet-fail", "verify_constraint_check", "constraint_check",
|
||||
{"constraints_met": False})
|
||||
row("constraint:violations-fail", "verify_constraint_check", "constraint_check",
|
||||
{"constraints_met": True, "constraint_violations": ["no secrets in diff"]})
|
||||
row("constraint:violations-nested", "verify_constraint_check", "constraint_check",
|
||||
{"constraints_met": False,
|
||||
"constraint_violations": [{"rule": "R1", "at": 7}, 12, None]})
|
||||
|
||||
# ── verify dispatch (named + unknown) ───────────────────────────────────────
|
||||
for vt in ("exit_code", "unit_test", "schema", "file_exists", "file_changed",
|
||||
"tool_receipt", "constraint_check"):
|
||||
row(f"dispatch:{vt}", "verify", vt, {})
|
||||
row("dispatch:unknown-verifier", "verify", "nonexistent_verifier", {})
|
||||
row("dispatch:unknown-empty-name", "verify", "", {})
|
||||
row("dispatch:unknown-case-sensitive", "verify", "Exit_Code", {"exit_code": 0})
|
||||
|
||||
# ── handle_hook ─────────────────────────────────────────────────────────────
|
||||
hook_row("hook:event-only", "verification.request")
|
||||
hook_row("hook:event-with-data", "verification.done", {"passed": True})
|
||||
|
||||
# ── PLUGIN manifest ─────────────────────────────────────────────────────────
|
||||
rows.append({
|
||||
"name": "plugin:manifest",
|
||||
"fn": "plugin",
|
||||
"verifier_type": "",
|
||||
"result": {},
|
||||
"want": vl.PLUGIN,
|
||||
"want_json": json.dumps(vl.PLUGIN, sort_keys=True),
|
||||
})
|
||||
|
||||
doc = {
|
||||
"frozen_ts": "2026-10-12T00:00:00+00:00",
|
||||
"generator": "scripts/gen_verification_layer_truth.py — live oracle "
|
||||
"src/rune/verification_layer.py (Rust evidence_provenance core)",
|
||||
"rows": rows,
|
||||
}
|
||||
out = ROOT / "src/go/verification_layer/truth.json"
|
||||
out.write_text(json.dumps(doc, indent=1, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8")
|
||||
print(f"frozen {len(rows)} rows → {out}")
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Open the #2479 dichotomy-wave construction contract for the BATCH-A
|
||||
modules (iteration, subconscious_pass, rune_profiler, subsystem_health).
|
||||
Walks draft→approved→building in ONE process (shared-store clobber hazard).
|
||||
|
||||
Usage: open_contract_batch_a.py <module> [contract_id]
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
module = sys.argv[1]
|
||||
sym = {"iteration": "IT", "subconscious_pass": "SP",
|
||||
"rune_profiler": "RP", "subsystem_health": "SH"}[module]
|
||||
|
||||
iface = ConstructionInterface(builder="factory-batch-a")
|
||||
|
||||
cid = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
r = iface.submit_need(
|
||||
f"Port src/rune/{module}.py to FULL Rust (dichotomy factory, issue "
|
||||
f"#2479 tranche): doc-envelope FFI, ctypes loader, facade delegation "
|
||||
f"with RUNE_{sym}_CORE=python pin, frozen-truth battery, live parity "
|
||||
f"100%, Phase B language-advantage pass.",
|
||||
origin="builder",
|
||||
contract_id=cid,
|
||||
)
|
||||
print("submit_need:", json.dumps(r)[:400])
|
||||
if not r.get("success"):
|
||||
print("NEED FAILED — aborting")
|
||||
sys.exit(1)
|
||||
c = r["ids"]["contract_id"]
|
||||
print("contract_id:", c)
|
||||
|
||||
for phase in ("approved", "building"):
|
||||
t = iface.transition(c, phase)
|
||||
print(f"transition->{phase}:", json.dumps(t)[:300])
|
||||
if not t.get("success"):
|
||||
sys.exit(1)
|
||||
print("OPENED", c)
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Open + walk the #2479 dichotomy-wave construction contract for
|
||||
connection_pool to verifying. Full lifecycle in ONE process."""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
iface = ConstructionInterface(builder="factory-batch-b")
|
||||
|
||||
r = iface.submit_need(
|
||||
"Complete the connection_pool dichotomy port to FULL Rust (#2479 "
|
||||
"tranche, programme verdict RUST): pool-bookkeeping kernel (pooling "
|
||||
"order, stale removal, oldest-first eviction, status) as doc-envelope "
|
||||
"FFI (cpool_new/begin_get/commit/close_all/status, free via "
|
||||
"capability_scoring cs_free_string — no free re-export); cpool_loader "
|
||||
"mirroring broker_loader.py; facade delegation with RUNE_CPOOL_CORE="
|
||||
"python pin (sqlite handles stay in the facade); frozen-oracle battery "
|
||||
"(16 cases, every kernel branch, frozen against a real sqlite-backed "
|
||||
"pool); live parity 100% (60-seed random-op sweep); classification "
|
||||
"flip GO→RUST; Phase B language-advantage pass.",
|
||||
origin="builder",
|
||||
)
|
||||
print("submit_need:", json.dumps(r)[:300])
|
||||
if not r.get("success"):
|
||||
print("NEED FAILED — aborting"); sys.exit(1)
|
||||
cid = r["ids"]["contract_id"]
|
||||
print("contract_id:", cid)
|
||||
for phase in ("approved", "building", "verifying"):
|
||||
t = iface.transition(cid, phase)
|
||||
print(f"transition->{phase}:", json.dumps(t)[:150])
|
||||
if not t.get("success"):
|
||||
sys.exit(1)
|
||||
print("CONTRACT", cid, "verifying")
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Open + walk the #2479 dichotomy-wave construction contract for
|
||||
construction_depth to verifying. Full lifecycle in ONE process (shared-store
|
||||
clobber hazard). The close step runs separately (close_contract script)
|
||||
after the register commit, per the dichotomy-wave store pattern."""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
iface = ConstructionInterface(builder="factory-batch-b")
|
||||
|
||||
r = iface.submit_need(
|
||||
"Complete the construction_depth dichotomy port to FULL Rust (#2479 "
|
||||
"tranche): oracle-exact depth kernel (UNKNOWN ceremony/bypassed, "
|
||||
"PROJECT_BUILD full-lifecycle strings, EXISTING_PROJECT_CHANGE base "
|
||||
"ceremony + wide-surface escalation restored to oracle truth), "
|
||||
"doc-envelope FFI (cd_select_depth, free via capability_scoring "
|
||||
"cs_free_string — no free re-export), cd_loader mirroring "
|
||||
"broker_loader.py, facade delegation with RUNE_CD_CORE=python pin, "
|
||||
"frozen-oracle battery (40 cases, every public function/branch), live "
|
||||
"parity 100%, Phase B language-advantage pass.",
|
||||
origin="builder",
|
||||
)
|
||||
print("submit_need:", json.dumps(r)[:400])
|
||||
if not r.get("success"):
|
||||
print("NEED FAILED — aborting")
|
||||
sys.exit(1)
|
||||
cid = r["ids"]["contract_id"]
|
||||
print("contract_id:", cid)
|
||||
|
||||
for phase in ("approved", "building", "verifying"):
|
||||
t = iface.transition(cid, phase)
|
||||
print(f"transition->{phase}:", json.dumps(t)[:200])
|
||||
if not t.get("success"):
|
||||
sys.exit(1)
|
||||
|
||||
print("CONTRACT", cid, "verifying")
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Open → walk → close the #2479 dichotomy-wave construction contracts for
|
||||
the forge + verification_layer GO batch (factory-batch-c). Full lifecycle
|
||||
per contract, both back-to-back in ONE process (shared-store clobber hazard)."""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from rune.construction_interface import ConstructionInterface
|
||||
|
||||
iface = ConstructionInterface(builder="factory-batch-c")
|
||||
|
||||
CONTRACTS = [
|
||||
{
|
||||
"module": "forge",
|
||||
"cid": "BUILDER-2026-10-12T17:43:29Z-ea9485-factory-batch-c",
|
||||
"need": (
|
||||
"Port src/rune/forge.py to FULL Go (dichotomy factory, issue "
|
||||
"#2479 tranche, batch-c): the §18L forge boundary — ForgeAdapter "
|
||||
"protocol, ForgeUnavailable, GiteaForge (env/push/commit_status/"
|
||||
"ci_runs_for_sha/create_or_patch_release), default_forge, and the "
|
||||
"scripts/gitea_auth credential ladder — name-preserving in "
|
||||
"src/go/forge with IO behind CommandRunner/HTTPTransport/"
|
||||
"TokenSource seams, frozen-truth battery (34 rows), live parity "
|
||||
"100%, Phase B goroutine fan-out (Snapshot), compile-time seam "
|
||||
"assertions."
|
||||
),
|
||||
"evidence": (
|
||||
"forge parity port — 34/34 frozen rows replay verbatim (truth "
|
||||
"regenerated live twice, byte-stable), battery covers every "
|
||||
"public fn + branch (push truncation, status/runs HTTP capture "
|
||||
"byte-parity, credential ladder all 3 rungs, release argv + "
|
||||
"last-200 fallback), race-clean, gofmt/vet clean; Python oracle "
|
||||
"tests green. SHAs: cdcae1ea (feat) ac114c32 (Phase B), pushed "
|
||||
"gitea/master on top of 78a36204."
|
||||
),
|
||||
"scope_files": [
|
||||
"src/rune/forge.py",
|
||||
"src/go/forge/main.go",
|
||||
"src/go/forge/truth_test.go",
|
||||
"src/go/forge/phaseb.go",
|
||||
"src/go/forge/phaseb_test.go",
|
||||
"src/go/forge/truth.json",
|
||||
"scripts/gen_forge_truth.py",
|
||||
],
|
||||
},
|
||||
{
|
||||
"module": "verification_layer",
|
||||
"cid": "BUILDER-2026-10-12T17:46:05Z-c1a38e-factory-batch-c",
|
||||
"need": (
|
||||
"Port src/rune/verification_layer.py to FULL Go (dichotomy "
|
||||
"factory, issue #2479 tranche, batch-c): all seven verifiers "
|
||||
"(exit_code, unit_test, schema, file_exists, file_changed, "
|
||||
"tool_receipt, constraint_check) + unknown-verifier dispatch, "
|
||||
"VerificationResult.to_dict byte-rendering, handle_hook, "
|
||||
"PLUGIN manifest — the Rust evidence_provenance semantics "
|
||||
"carried 1:1 (verification.rs), name-preserving in "
|
||||
"src/go/verification_layer with the FS probe behind a seam, "
|
||||
"frozen-truth battery (62 rows), live parity 100%, Phase B "
|
||||
"Verifier registry + VerifyAll goroutine fan-out."
|
||||
),
|
||||
"evidence": (
|
||||
"verification_layer parity port — 62/62 frozen rows replay "
|
||||
"verbatim byte-for-byte (truth regenerated live twice, "
|
||||
"byte-stable) + 15/15 live CLI parity sweep incl. unicode "
|
||||
"ensure_ascii escapes and non-string failures pass-through; "
|
||||
"battery covers every verifier pass+fail branches, dispatch, "
|
||||
"hook, PLUGIN; race-clean, gofmt/vet clean; Python tests green "
|
||||
"(test_verification_layer_demo + test_evidence_provenance_shims, "
|
||||
"7 passed). SHAs: d9009cc2 (feat) 2750eba0 (Phase B), pushed "
|
||||
"gitea/master on top of 78a36204."
|
||||
),
|
||||
"scope_files": [
|
||||
"src/rune/verification_layer.py",
|
||||
"src/go/verification_layer/main.go",
|
||||
"src/go/verification_layer/truth_test.go",
|
||||
"src/go/verification_layer/phaseb.go",
|
||||
"src/go/verification_layer/phaseb_test.go",
|
||||
"src/go/verification_layer/truth.json",
|
||||
"scripts/gen_verification_layer_truth.py",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
cids = []
|
||||
for spec in CONTRACTS:
|
||||
r = iface.submit_need(spec["need"], origin="builder",
|
||||
contract_id=spec["cid"])
|
||||
print("submit_need:", json.dumps(r)[:200])
|
||||
if not r.get("success"):
|
||||
print("NEED FAILED — aborting")
|
||||
sys.exit(1)
|
||||
cid = r["ids"]["contract_id"]
|
||||
cids.append(cid)
|
||||
print("contract_id:", cid)
|
||||
|
||||
for phase in ("approved", "building", "verifying"):
|
||||
t = iface.transition(cid, phase)
|
||||
print(f"transition->{phase}:", json.dumps(t)[:150])
|
||||
if not t.get("success"):
|
||||
sys.exit(1)
|
||||
|
||||
c = iface.close(cid, observed_evidence=[{
|
||||
"kind": "port_evidence",
|
||||
"detail": spec["evidence"],
|
||||
}], scope_files=spec["scope_files"])
|
||||
print("close:", json.dumps(c)[:200])
|
||||
print("CONTRACT", cid, spec["module"],
|
||||
"closed" if c.get("success") else "CLOSE_FAILED")
|
||||
print("-" * 60)
|
||||
|
||||
print("ALL CONTRACTS:", json.dumps(cids))
|
||||
@@ -0,0 +1 @@
|
||||
filetoken123
|
||||
@@ -0,0 +1 @@
|
||||
weaktoken
|
||||
@@ -0,0 +1,3 @@
|
||||
module rune/forge
|
||||
|
||||
go 1.24
|
||||
@@ -0,0 +1,486 @@
|
||||
// Package main — the FULL GO port of src/rune/forge.py (#2479 dichotomy
|
||||
// wave, GO side verdict). Every public Python function has a
|
||||
// name-preserving counterpart: GiteaForge (__init__ → NewGiteaForge,
|
||||
// _env → env, push → Push, _gitea_auth → giteaAuth, commit_status →
|
||||
// CommitStatus, ci_runs_for_sha → CiRunsForSha, create_or_patch_release →
|
||||
// CreateOrPatchRelease), the ForgeAdapter protocol (→ interface),
|
||||
// ForgeUnavailable (→ error type) and default_forge (→ DefaultForge).
|
||||
// The gitea_auth credential ladder (scripts/gitea_auth.py) is carried
|
||||
// 1:1 (env rung → 0600-enforced token file rung → git credential fill).
|
||||
//
|
||||
// All IO (git push, HTTP, credential helper, python release publisher)
|
||||
// stays behind injectable seams (CommandRunner, HTTPTransport, TokenSource)
|
||||
// so the frozen truth battery replays deterministically.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ── constants (scripts/gitea_auth.py + forge.py defaults) ───────────────────
|
||||
|
||||
const DefaultServer = "https://gitea.thingscouldgetdicey.online"
|
||||
const DefaultRepo = "conductor/rune"
|
||||
const GiteaHost = "gitea.thingscouldgetdicey.online"
|
||||
|
||||
// ── the forge boundary ──────────────────────────────────────────────────────
|
||||
|
||||
// ForgeAdapter — the minimum stable forge boundary (Python Protocol). All
|
||||
// methods may return *ForgeUnavailable — callers treat forge failures as
|
||||
// blocking-for-that-operation, never as construction-truth changes.
|
||||
type ForgeAdapter interface {
|
||||
Push(repo string, branch string) (map[string]any, error)
|
||||
CommitStatus(repo string, sha string) (any, error)
|
||||
CiRunsForSha(repo string, sha string) ([]map[string]any, error)
|
||||
CreateOrPatchRelease(repo string, tag string, title string, body string) (map[string]any, error)
|
||||
}
|
||||
|
||||
// ForgeUnavailable — the forge could not be reached or refused the
|
||||
// operation (Python RuntimeError subclass; message parity is frozen).
|
||||
type ForgeUnavailable struct{ Msg string }
|
||||
|
||||
func (e *ForgeUnavailable) Error() string { return e.Msg }
|
||||
|
||||
// GiteaCredentialError — scripts/gitea_auth.py's no-credential error
|
||||
// (message never contains a credential).
|
||||
type GiteaCredentialError struct{ Msg string }
|
||||
|
||||
func (e *GiteaCredentialError) Error() string { return e.Msg }
|
||||
|
||||
// PermissionError — the 0644-token-file rejection (Python PermissionError).
|
||||
type PermissionError struct{ Msg string }
|
||||
|
||||
func (e *PermissionError) Error() string { return e.Msg }
|
||||
|
||||
// ── injectable IO seams ─────────────────────────────────────────────────────
|
||||
|
||||
// RunResult — subprocess capture (rc, stdout, stderr).
|
||||
type RunResult struct {
|
||||
Stdout string
|
||||
Stderr string
|
||||
RC int
|
||||
}
|
||||
|
||||
// CommandRunner — the subprocess seam (git push, credential fill, release
|
||||
// publisher). stdin carries the credential-fill payload.
|
||||
type CommandRunner func(name string, args []string, stdin string) RunResult
|
||||
|
||||
// HTTPRequest — captured request surface (method, URL, headers).
|
||||
type HTTPRequest struct {
|
||||
Method string
|
||||
URL string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// HTTPTransport — the urllib seam. Returns the body bytes or an error whose
|
||||
// Error() is byte-comparable to the oracle's str(exc).
|
||||
type HTTPTransport func(req HTTPRequest) ([]byte, error)
|
||||
|
||||
// TokenSource — the gitea_auth credential ladder surface.
|
||||
type TokenSource interface {
|
||||
HasToken() bool
|
||||
GetToken() (string, error)
|
||||
}
|
||||
|
||||
// ── GiteaForge ──────────────────────────────────────────────────────────────
|
||||
|
||||
// GiteaForge — the real adapter (rule A: delegates to the canonical
|
||||
// implementations; no duplicated API clients).
|
||||
type GiteaForge struct {
|
||||
Server string // "" → scripts' env/credential resolution (Python None)
|
||||
Repo string
|
||||
|
||||
tokens TokenSource // nil → giteaLadder
|
||||
runner CommandRunner // nil → execRunner
|
||||
transport HTTPTransport // nil → urllibTransport
|
||||
pythonBin string // "" → "python3" (sys.executable seam)
|
||||
}
|
||||
|
||||
// NewGiteaForge — Python GiteaForge(server=None, repo="conductor/rune").
|
||||
func NewGiteaForge(server string, repo string) *GiteaForge {
|
||||
return &GiteaForge{Server: server, Repo: repo}
|
||||
}
|
||||
|
||||
// compile-time assertion: GiteaForge satisfies the protocol.
|
||||
var _ ForgeAdapter = (*GiteaForge)(nil)
|
||||
|
||||
func (f *GiteaForge) runnerOr() CommandRunner {
|
||||
if f.runner != nil {
|
||||
return f.runner
|
||||
}
|
||||
return execRunner
|
||||
}
|
||||
|
||||
func (f *GiteaForge) transportOr() HTTPTransport {
|
||||
if f.transport != nil {
|
||||
return f.transport
|
||||
}
|
||||
return urllibTransport
|
||||
}
|
||||
|
||||
func (f *GiteaForge) pythonBinOr() string {
|
||||
if f.pythonBin != "" {
|
||||
return f.pythonBin
|
||||
}
|
||||
return "python3"
|
||||
}
|
||||
|
||||
// env — Python _env(): inherit os.environ; server override; repo default.
|
||||
func (f *GiteaForge) env() map[string]string {
|
||||
env := map[string]string{}
|
||||
for _, kv := range os.Environ() {
|
||||
if i := strings.IndexByte(kv, '='); i >= 0 {
|
||||
env[kv[:i]] = kv[i+1:]
|
||||
}
|
||||
}
|
||||
if f.Server != "" {
|
||||
env["GITEA_SERVER"] = f.Server
|
||||
}
|
||||
if _, ok := env["GITEA_REPO"]; !ok {
|
||||
env["GITEA_REPO"] = f.Repo
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// Push — git push gitea master:<branch>; failure → "push failed: <stderr[:200]>".
|
||||
func (f *GiteaForge) Push(repo string, branch string) (map[string]any, error) {
|
||||
r := f.runnerOr()("git", []string{"push", "gitea", "master:" + branch}, "")
|
||||
if r.RC != 0 {
|
||||
return nil, &ForgeUnavailable{fmt.Sprintf("push failed: %s", firstN(r.Stderr, 200))}
|
||||
}
|
||||
return map[string]any{"pushed": true, "branch": branch}, nil
|
||||
}
|
||||
|
||||
// giteaAuth — Python _gitea_auth(): the canonical scripts/gitea_auth
|
||||
// credential ladder (rule A: no duplicate auth). Seams overridable.
|
||||
func (f *GiteaForge) giteaAuth() TokenSource {
|
||||
if f.tokens != nil {
|
||||
return f.tokens
|
||||
}
|
||||
return giteaLadder{}
|
||||
}
|
||||
|
||||
// server — Python `self._server or os.environ.get("GITEA_SERVER", DEFAULT)`.
|
||||
func (f *GiteaForge) server() string {
|
||||
if f.Server != "" {
|
||||
return f.Server
|
||||
}
|
||||
if v := os.Getenv("GITEA_SERVER"); v != "" {
|
||||
return v
|
||||
}
|
||||
return DefaultServer
|
||||
}
|
||||
|
||||
// CommitStatus — GET the combined commit status; returns data["state"]
|
||||
// ("unknown" when the key is absent).
|
||||
func (f *GiteaForge) CommitStatus(repo string, sha string) (any, error) {
|
||||
tokens := f.giteaAuth()
|
||||
if !tokens.HasToken() {
|
||||
return nil, &ForgeUnavailable{"no forge credentials (gitea token)"}
|
||||
}
|
||||
tok, _ := tokens.GetToken()
|
||||
req := HTTPRequest{
|
||||
Method: "GET",
|
||||
URL: fmt.Sprintf("%s/api/v1/repos/%s/commits/%s/status", f.server(), repo, sha),
|
||||
Headers: map[string]string{"Authorization": "token " + tok},
|
||||
}
|
||||
body, err := f.transportOr()(req)
|
||||
if err != nil {
|
||||
return nil, &ForgeUnavailable{fmt.Sprintf("status unreachable: %s", err.Error())}
|
||||
}
|
||||
var data map[string]any
|
||||
if err := decodeJSON(body, &data); err != nil {
|
||||
return nil, err // Python json.loads errors propagate unwrapped
|
||||
}
|
||||
if v, ok := data["state"]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
// CiRunsForSha — GET the actions runs and keep those whose str(
|
||||
// head_sha) equals sha (absent key → ""; JSON null → "None").
|
||||
func (f *GiteaForge) CiRunsForSha(repo string, sha string) ([]map[string]any, error) {
|
||||
tokens := f.giteaAuth()
|
||||
if !tokens.HasToken() {
|
||||
return nil, &ForgeUnavailable{"no forge credentials (gitea token)"}
|
||||
}
|
||||
tok, _ := tokens.GetToken()
|
||||
req := HTTPRequest{
|
||||
Method: "GET",
|
||||
URL: fmt.Sprintf("%s/api/v1/repos/%s/actions/runs?limit=50", f.server(), repo),
|
||||
Headers: map[string]string{"Authorization": "token " + tok},
|
||||
}
|
||||
body, err := f.transportOr()(req)
|
||||
if err != nil {
|
||||
return nil, &ForgeUnavailable{fmt.Sprintf("runs unreachable: %s", err.Error())}
|
||||
}
|
||||
var data map[string]any
|
||||
if err := decodeJSON(body, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, ok := data["workflow_runs"]
|
||||
if !ok {
|
||||
return []map[string]any{}, nil
|
||||
}
|
||||
arr, _ := raw.([]any)
|
||||
out := []map[string]any{}
|
||||
for _, item := range arr {
|
||||
run, _ := item.(map[string]any)
|
||||
headSha := ""
|
||||
if v, ok := run["head_sha"]; ok {
|
||||
headSha = pyStrAny(v)
|
||||
}
|
||||
if headSha == sha {
|
||||
out = append(out, run)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CreateOrPatchRelease — delegate to the canonical idempotent publisher
|
||||
// (scripts/create_release.py, CWD-relative like the oracle); the script
|
||||
// path missing → "canonical release publisher not found".
|
||||
func (f *GiteaForge) CreateOrPatchRelease(repo string, tag string, title string, body string) (map[string]any, error) {
|
||||
tokens := f.giteaAuth()
|
||||
if !tokens.HasToken() {
|
||||
return nil, &ForgeUnavailable{"no forge credentials (gitea token)"}
|
||||
}
|
||||
if _, err := os.Stat("scripts/create_release.py"); err == nil {
|
||||
r := f.runnerOr()(f.pythonBinOr(),
|
||||
[]string{"scripts/create_release.py", "--tag", tag, "--title", title, "--body", body}, "")
|
||||
if r.RC != 0 {
|
||||
tail := r.Stderr
|
||||
if tail == "" {
|
||||
tail = r.Stdout
|
||||
}
|
||||
return nil, &ForgeUnavailable{fmt.Sprintf(
|
||||
"release publication failed rc=%d: %s", r.RC, lastN(tail, 200))}
|
||||
}
|
||||
return map[string]any{"published": true, "tag": tag}, nil
|
||||
}
|
||||
return nil, &ForgeUnavailable{"canonical release publisher not found"}
|
||||
}
|
||||
|
||||
// DefaultForge — the working adapter (rule 18L: prove Gitea still works).
|
||||
func DefaultForge() *GiteaForge { return NewGiteaForge("", DefaultRepo) }
|
||||
|
||||
// ── gitea_auth credential ladder (scripts/gitea_auth.py, 1:1) ───────────────
|
||||
|
||||
// defaultTokenFilePath — ~/.config/opencode/secrets/gitea-token expanded.
|
||||
var defaultTokenFilePath = func() string {
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
return filepath.Join(home, ".config", "opencode", "secrets", "gitea-token")
|
||||
}
|
||||
return ".config/opencode/secrets/gitea-token"
|
||||
}
|
||||
|
||||
// giteaLadder — env rung → 0600-enforced token file rung → git credential
|
||||
// fill. Never prints credentials; fails clearly when unavailable.
|
||||
type giteaLadder struct {
|
||||
runner CommandRunner // nil → execRunner
|
||||
}
|
||||
|
||||
func (l giteaLadder) run() CommandRunner {
|
||||
if l.runner != nil {
|
||||
return l.runner
|
||||
}
|
||||
return execRunner
|
||||
}
|
||||
|
||||
// HasToken — true if any rung of the credential ladder answers. Never fails.
|
||||
func (l giteaLadder) HasToken() bool {
|
||||
_, err := l.GetToken()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GetToken — obtain the Gitea API token via the ladder.
|
||||
func (l giteaLadder) GetToken() (string, error) {
|
||||
token := strings.TrimSpace(os.Getenv("GITEA_TOKEN"))
|
||||
if token != "" {
|
||||
return token, nil
|
||||
}
|
||||
token, err := l.tokenFromFile()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if token != "" {
|
||||
return token, nil
|
||||
}
|
||||
token = l.tokenFromCredentialHelper()
|
||||
if token != "" {
|
||||
return token, nil
|
||||
}
|
||||
return "", &GiteaCredentialError{fmt.Sprintf(
|
||||
"Gitea credentials unavailable: set GITEA_TOKEN, create a mode-0600 "+
|
||||
"token file at %s, or configure a git credential helper for %s",
|
||||
defaultTokenFilePath(), GiteaHost)}
|
||||
}
|
||||
|
||||
// tokenFromFile — GITEA_TOKEN_FILE (or the default path); must be mode 0600.
|
||||
func (l giteaLadder) tokenFromFile() (string, error) {
|
||||
path := os.Getenv("GITEA_TOKEN_FILE")
|
||||
if path == "" {
|
||||
path = defaultTokenFilePath()
|
||||
}
|
||||
path = expandUser(path)
|
||||
st, err := os.Stat(path)
|
||||
if err != nil || st.IsDir() {
|
||||
return "", nil
|
||||
}
|
||||
if st.Mode().Perm()&0o077 != 0 {
|
||||
return "", &PermissionError{fmt.Sprintf("Gitea token file must be mode 0600: %s", path)}
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return strings.TrimSpace(string(raw)), nil
|
||||
}
|
||||
|
||||
// tokenFromCredentialHelper — `git credential fill` for the Gitea host.
|
||||
func (l giteaLadder) tokenFromCredentialHelper() string {
|
||||
stdin := fmt.Sprintf("protocol=https\nhost=%s\nusername=conductor\n\n", GiteaHost)
|
||||
r := l.run()("git", []string{"credential", "fill"}, stdin)
|
||||
for _, line := range strings.Split(r.Stdout, "\n") {
|
||||
if strings.HasPrefix(line, "password=") {
|
||||
return line[len("password="):]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── default seams ───────────────────────────────────────────────────────────
|
||||
|
||||
// execRunner — real subprocess capture (text=True semantics).
|
||||
func execRunner(name string, args []string, stdin string) RunResult {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
if stdin != "" {
|
||||
cmd.Stdin = strings.NewReader(stdin)
|
||||
}
|
||||
var out, errb bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &errb
|
||||
err := cmd.Run()
|
||||
rc := 0
|
||||
if err != nil {
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
rc = ee.ExitCode()
|
||||
} else {
|
||||
rc = 127
|
||||
}
|
||||
}
|
||||
return RunResult{Stdout: out.String(), Stderr: errb.String(), RC: rc}
|
||||
}
|
||||
|
||||
// urllibTransport — urllib.request.urlopen(req, timeout=15) semantics:
|
||||
// non-2xx → "HTTP Error <code>: <reason>", network failure →
|
||||
// "<urlopen error <reason>>".
|
||||
func urllibTransport(req HTTPRequest) ([]byte, error) {
|
||||
full := http.Header{}
|
||||
for k, v := range req.Headers {
|
||||
full.Set(k, v)
|
||||
}
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
httpReq, err := http.NewRequest(req.Method, req.URL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<urlopen error %s>", err.Error())
|
||||
}
|
||||
httpReq.Header = full
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<urlopen error %s>", err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("HTTP Error %s",
|
||||
strings.Replace(resp.Status, " ", ": ", 1))
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := buf.ReadFrom(resp.Body); err != nil {
|
||||
return nil, fmt.Errorf("<urlopen error %s>", err.Error())
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ── helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// decodeJSON — json.loads with Python-compatible number preservation
|
||||
// (json.Number keeps the literal text so str() rendering matches).
|
||||
func decodeJSON(data []byte, out *map[string]any) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.UseNumber()
|
||||
var v map[string]any
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
*out = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// pyStrAny — Python str() of a JSON-decoded value (None/bool/int/str;
|
||||
// numbers keep their JSON literal).
|
||||
func pyStrAny(v any) string {
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
return "None"
|
||||
case bool:
|
||||
if x {
|
||||
return "True"
|
||||
}
|
||||
return "False"
|
||||
case string:
|
||||
return x
|
||||
case json.Number:
|
||||
return x.String()
|
||||
default:
|
||||
return fmt.Sprintf("%v", x)
|
||||
}
|
||||
}
|
||||
|
||||
// firstN — Python s[:n] (codepoints).
|
||||
func firstN(s string, n int) string {
|
||||
if utf8.RuneCountInString(s) <= n {
|
||||
return s
|
||||
}
|
||||
return string([]rune(s)[:n])
|
||||
}
|
||||
|
||||
// lastN — Python s[-n:].
|
||||
func lastN(s string, n int) string {
|
||||
r := []rune(s)
|
||||
if len(r) <= n {
|
||||
return s
|
||||
}
|
||||
return string(r[len(r)-n:])
|
||||
}
|
||||
|
||||
// expandUser — pathlib.Path.expanduser for the token-file path.
|
||||
func expandUser(p string) string {
|
||||
if p == "~" || strings.HasPrefix(p, "~/") {
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
return filepath.Join(home, strings.TrimPrefix(p, "~/"))
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// main — forge.py is a library module (the §18L boundary); the binary
|
||||
// surfaces the boundary contract for smoke checks.
|
||||
func main() {
|
||||
fmt.Println("forge: T18 §18L minimum stable forge abstraction boundary (GO port)")
|
||||
f := DefaultForge()
|
||||
fmt.Printf("default forge: repo=%s server-attr=%q\n", f.Repo, f.Server)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
// Phase B language-advantage pass — dichotomy wave. Go-side wins the
|
||||
// Python oracle can't express:
|
||||
//
|
||||
// 1. ForgeSnapshot — commit status and CI runs are two independent HTTP
|
||||
// round-trips the oracle always performs serially; the GO port fans
|
||||
// them out across goroutines (order-insensitive reassembly, both
|
||||
// results preserved even when one side fails).
|
||||
// 2. Compile-time safety: seam interfaces (TokenSource, CommandRunner,
|
||||
// HTTPTransport) and the ForgeAdapter protocol are satisfaction-asserted
|
||||
// at build time — drift breaks the compile, not a runtime cast.
|
||||
|
||||
import "sync"
|
||||
|
||||
// ForgeSnapshot — the CI verdict surface for one SHA: the combined status
|
||||
// plus the workflow runs that belong to it.
|
||||
type ForgeSnapshot struct {
|
||||
Status any // commit_status state (str / JSON value)
|
||||
Runs []map[string]any // workflow_runs filtered to the SHA
|
||||
StatusErr error // ForgeUnavailable when the status call failed
|
||||
RunsErr error // ForgeUnavailable when the runs call failed
|
||||
}
|
||||
|
||||
// SnapshotForge — the fan-out interface (satisfied by *GiteaForge).
|
||||
type SnapshotForge interface {
|
||||
Snapshot(repo string, sha string) ForgeSnapshot
|
||||
}
|
||||
|
||||
// Snapshot — CommitStatus and CiRunsForSha race concurrently and reassemble
|
||||
// when both settle. Each half keeps its own error: a failed status call
|
||||
// never masks the runs (and vice versa) — the oracle's serial version could
|
||||
// only surface the first failure.
|
||||
func (f *GiteaForge) Snapshot(repo string, sha string) ForgeSnapshot {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
snap ForgeSnapshot
|
||||
)
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
snap.Status, snap.StatusErr = f.CommitStatus(repo, sha)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
snap.Runs, snap.RunsErr = f.CiRunsForSha(repo, sha)
|
||||
}()
|
||||
wg.Wait()
|
||||
return snap
|
||||
}
|
||||
|
||||
// compile-time assertions: the seams keep their shapes.
|
||||
var (
|
||||
_ TokenSource = giteaLadder{}
|
||||
_ SnapshotForge = (*GiteaForge)(nil)
|
||||
_ ForgeAdapter = (*GiteaForge)(nil)
|
||||
)
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
// Phase B battery — pins the language-advantage guarantees: the Snapshot
|
||||
// fan-out returns byte-identical halves to the serial oracle calls and is
|
||||
// race-clean under -race; the seam interfaces stay satisfaction-asserted.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) }
|
||||
|
||||
// TestSnapshotHalves — drive the fan-out through the same frozen truth
|
||||
// bodies the battery pins, assert half-parity and race cleanliness.
|
||||
func TestSnapshotHalves(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
statusRows := map[string]TruthRow{}
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
if row.Op == "commit_status" {
|
||||
setup := parseNum(t, row.Setup)
|
||||
sha := strOrEmpty(setup["sha"])
|
||||
if _, dup := statusRows[sha]; !dup {
|
||||
statusRows[sha] = *row
|
||||
}
|
||||
}
|
||||
}
|
||||
replayed := 0
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
if row.Op != "ci_runs" {
|
||||
continue
|
||||
}
|
||||
setup := parseNum(t, row.Setup)
|
||||
if setup["payload"] == nil {
|
||||
continue
|
||||
}
|
||||
sha := strOrEmpty(setup["sha"])
|
||||
sRow, ok := statusRows[sha]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
replayed++
|
||||
envGuard(t)
|
||||
sSetup := parseNum(t, sRow.Setup)
|
||||
payload, err := json.Marshal(setup["payload"])
|
||||
if err != nil {
|
||||
t.Fatalf("marshal payload: %v", err)
|
||||
}
|
||||
f := NewGiteaForge("", DefaultRepo)
|
||||
f.tokens = stubTokens{has: true, tok: "t0k3n"}
|
||||
tr := &recordingTransport{body: payload}
|
||||
f.transport = tr.do
|
||||
setEnv(t, map[string]any{"GITEA_SERVER": setup["env_server"]})
|
||||
|
||||
snap := f.Snapshot(strOrEmpty(setup["repo"]), sha)
|
||||
|
||||
// serial halves from the same frozen rows
|
||||
sState, sErr := f.CommitStatus(strOrEmpty(sSetup["repo"]), sha)
|
||||
sRuns, rErr := f.CiRunsForSha(strOrEmpty(setup["repo"]), sha)
|
||||
if sErr != nil || rErr != nil {
|
||||
t.Fatalf("serial halves unexpectedly failed: %v %v", sErr, rErr)
|
||||
}
|
||||
if snap.StatusErr != nil {
|
||||
t.Fatalf("snapshot status half failed: %v", snap.StatusErr)
|
||||
}
|
||||
if snap.RunsErr != nil {
|
||||
t.Fatalf("snapshot runs half failed: %v", snap.RunsErr)
|
||||
}
|
||||
if !equalAny(snap.Status, sState) {
|
||||
t.Fatalf("snapshot status half diverges: %#v vs %#v", snap.Status, sState)
|
||||
}
|
||||
if len(snap.Runs) != len(sRuns) {
|
||||
t.Fatalf("snapshot runs half diverges: %d vs %d runs", len(snap.Runs), len(sRuns))
|
||||
}
|
||||
}
|
||||
if replayed == 0 {
|
||||
t.Fatal("no snapshot replay rows — battery/truth drift")
|
||||
}
|
||||
}
|
||||
|
||||
// equalAny — deep equality over decoded JSON values.
|
||||
func equalAny(a, b any) bool {
|
||||
ab, err1 := json.Marshal(a)
|
||||
bb, err2 := json.Marshal(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false
|
||||
}
|
||||
return string(ab) == string(bb)
|
||||
}
|
||||
|
||||
// TestSnapshotErrorHalves — a failing status call must not mask the runs
|
||||
// (the language-advantage over the oracle's serial first-error semantics).
|
||||
func TestSnapshotErrorHalves(t *testing.T) {
|
||||
f := NewGiteaForge("", DefaultRepo)
|
||||
f.tokens = stubTokens{has: true, tok: "t0k3n"}
|
||||
f.transport = func(req HTTPRequest) ([]byte, error) {
|
||||
if strings.Contains(req.URL, "/status") {
|
||||
return nil, &ForgeUnavailable{"status unreachable: boom"}
|
||||
}
|
||||
return []byte(`{"workflow_runs": [{"head_sha": "abc"}]}`), nil
|
||||
}
|
||||
snap := f.Snapshot("o/r", "abc")
|
||||
if snap.StatusErr == nil {
|
||||
t.Fatal("status half should have failed")
|
||||
}
|
||||
if len(snap.Runs) != 1 {
|
||||
t.Fatalf("runs half must survive: got %d", len(snap.Runs))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSeamInterfacesSatisfied — compile-time assertions as a test.
|
||||
func TestSeamInterfacesSatisfied(t *testing.T) {
|
||||
var _ TokenSource = stubTokens{}
|
||||
var _ TokenSource = giteaLadder{}
|
||||
var _ ForgeAdapter = (*GiteaForge)(nil)
|
||||
var _ SnapshotForge = (*GiteaForge)(nil)
|
||||
}
|
||||
@@ -0,0 +1,778 @@
|
||||
{
|
||||
"frozen_ts": "2026-10-12T00:00:00+00:00",
|
||||
"generator": "scripts/gen_forge_truth.py — live oracle src/rune/forge.py",
|
||||
"rows": [
|
||||
{
|
||||
"name": "default_forge:working-adapter",
|
||||
"op": "default_forge",
|
||||
"setup": {},
|
||||
"want": {
|
||||
"server": null,
|
||||
"repo": "conductor/rune"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:inherits-and-defaults",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": null,
|
||||
"environ": {
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": null,
|
||||
"GITEA_REPO": "conductor/rune",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:server-override",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": "http://local:3000",
|
||||
"repo": null,
|
||||
"environ": {
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": "http://local:3000",
|
||||
"GITEA_REPO": "conductor/rune",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:repo-explicit",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "other/repo",
|
||||
"environ": {
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": null,
|
||||
"GITEA_REPO": "other/repo",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:env-server-kept-when-no-attr",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": null,
|
||||
"environ": {
|
||||
"GITEA_SERVER": "http://env-server",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": "http://env-server",
|
||||
"GITEA_REPO": "conductor/rune",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:env-server-overridden-by-attr",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": "http://attr",
|
||||
"repo": null,
|
||||
"environ": {
|
||||
"GITEA_SERVER": "http://env-server",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": "http://attr",
|
||||
"GITEA_REPO": "conductor/rune",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:env-repo-not-clobbered",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": null,
|
||||
"environ": {
|
||||
"GITEA_REPO": "env/repo",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": null,
|
||||
"GITEA_REPO": "env/repo",
|
||||
"RUNE_SAMPLE": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "_env:env:inherits-sample-var",
|
||||
"op": "env",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": null,
|
||||
"environ": {
|
||||
"RUNE_SAMPLE": "1"
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"GITEA_SERVER": null,
|
||||
"GITEA_REPO": "conductor/rune",
|
||||
"RUNE_SAMPLE": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "push:push:ok",
|
||||
"op": "push",
|
||||
"setup": {
|
||||
"repo": "ignored/repo",
|
||||
"branch": "feature-x",
|
||||
"rc": 0,
|
||||
"stderr": ""
|
||||
},
|
||||
"want": {
|
||||
"result": {
|
||||
"pushed": true,
|
||||
"branch": "feature-x"
|
||||
},
|
||||
"error": null,
|
||||
"calls": [
|
||||
{
|
||||
"argv": [
|
||||
"git",
|
||||
"push",
|
||||
"gitea",
|
||||
"master:feature-x"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "push:push:fail-short-stderr",
|
||||
"op": "push",
|
||||
"setup": {
|
||||
"repo": "ignored/repo",
|
||||
"branch": "main",
|
||||
"rc": 1,
|
||||
"stderr": "rejected non-fast-forward"
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "push failed: rejected non-fast-forward",
|
||||
"calls": [
|
||||
{
|
||||
"argv": [
|
||||
"git",
|
||||
"push",
|
||||
"gitea",
|
||||
"master:main"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "push:push:fail-long-stderr-truncates-200",
|
||||
"op": "push",
|
||||
"setup": {
|
||||
"repo": "ignored/repo",
|
||||
"branch": "dev",
|
||||
"rc": 128,
|
||||
"stderr": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "push failed: eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"calls": [
|
||||
{
|
||||
"argv": [
|
||||
"git",
|
||||
"push",
|
||||
"gitea",
|
||||
"master:dev"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "push:push:fail-empty-stderr",
|
||||
"op": "push",
|
||||
"setup": {
|
||||
"repo": "ignored/repo",
|
||||
"branch": "release",
|
||||
"rc": 2,
|
||||
"stderr": ""
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "push failed: ",
|
||||
"calls": [
|
||||
{
|
||||
"argv": [
|
||||
"git",
|
||||
"push",
|
||||
"gitea",
|
||||
"master:release"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gitea_auth:env-token-rung",
|
||||
"op": "gitea_auth",
|
||||
"setup": {
|
||||
"environ": {
|
||||
"GITEA_TOKEN": " envtoken \n"
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"has_token": true,
|
||||
"get_token": "envtoken"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gitea_auth:token-file-0600",
|
||||
"op": "gitea_auth",
|
||||
"setup": {
|
||||
"environ": {
|
||||
"GITEA_TOKEN_FILE": "<fixture token0600>"
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"has_token": true,
|
||||
"get_token": "filetoken123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gitea_auth:token-file-0644-rejected",
|
||||
"op": "gitea_auth",
|
||||
"setup": {
|
||||
"environ": {
|
||||
"GITEA_TOKEN_FILE": "<fixture token0644>"
|
||||
}
|
||||
},
|
||||
"want": {
|
||||
"has_token": false,
|
||||
"get_token": null,
|
||||
"error": "Gitea token file must be mode 0600: <FIXTURES>/token0644"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gitea_auth:no-credential",
|
||||
"op": "gitea_auth",
|
||||
"setup": {
|
||||
"default_token_file": "<TMP>/missing-token"
|
||||
},
|
||||
"want": {
|
||||
"has_token": false,
|
||||
"get_token": null,
|
||||
"error": "Gitea credentials unavailable: set GITEA_TOKEN, create a mode-0600 token file at <TMP>/missing-token, or configure a git credential helper for gitea.thingscouldgetdicey.online"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:no-token",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"env_server": null,
|
||||
"repo": "conductor/rune",
|
||||
"sha": "abc",
|
||||
"has_token": false,
|
||||
"body": null,
|
||||
"http_err": null
|
||||
},
|
||||
"want": {
|
||||
"state": null,
|
||||
"error": "no forge credentials (gitea token)",
|
||||
"request": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:success",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"env_server": null,
|
||||
"repo": "conductor/rune",
|
||||
"sha": "abc",
|
||||
"has_token": true,
|
||||
"body": "{\"state\": \"success\"}",
|
||||
"http_err": null
|
||||
},
|
||||
"want": {
|
||||
"state": "success",
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/conductor/rune/commits/abc/status",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:state-missing-unknown",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"env_server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "def",
|
||||
"has_token": true,
|
||||
"body": "{\"statuses\": []}",
|
||||
"http_err": null
|
||||
},
|
||||
"want": {
|
||||
"state": "unknown",
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/commits/def/status",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:server-attr-wins",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": "http://attr:3000",
|
||||
"env_server": "http://env:3000",
|
||||
"repo": "o/r",
|
||||
"sha": "ghi",
|
||||
"has_token": true,
|
||||
"body": "{\"state\": \"pending\"}",
|
||||
"http_err": null
|
||||
},
|
||||
"want": {
|
||||
"state": "pending",
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "http://attr:3000/api/v1/repos/o/r/commits/ghi/status",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:server-from-env",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"env_server": "http://env:3000",
|
||||
"repo": "o/r",
|
||||
"sha": "ghi",
|
||||
"has_token": true,
|
||||
"body": "{\"state\": \"failure\"}",
|
||||
"http_err": null
|
||||
},
|
||||
"want": {
|
||||
"state": "failure",
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "http://env:3000/api/v1/repos/o/r/commits/ghi/status",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:http-error",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"env_server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "jkl",
|
||||
"has_token": true,
|
||||
"body": null,
|
||||
"http_err": "HTTP Error 404: Not Found"
|
||||
},
|
||||
"want": {
|
||||
"state": null,
|
||||
"error": "status unreachable: HTTP Error 404: Not Found",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/commits/jkl/status",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commit_status:commit_status:network-error",
|
||||
"op": "commit_status",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"env_server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "jkl",
|
||||
"has_token": true,
|
||||
"body": null,
|
||||
"http_err": "conn refused"
|
||||
},
|
||||
"want": {
|
||||
"state": null,
|
||||
"error": "status unreachable: conn refused",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/commits/jkl/status",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci_runs:ci_runs:no-token",
|
||||
"op": "ci_runs",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "s",
|
||||
"payload": null,
|
||||
"err": null,
|
||||
"has_token": false
|
||||
},
|
||||
"want": {
|
||||
"runs": null,
|
||||
"error": "no forge credentials (gitea token)",
|
||||
"request": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci_runs:ci_runs:filter-by-head-sha",
|
||||
"op": "ci_runs",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "abc",
|
||||
"payload": {
|
||||
"workflow_runs": [
|
||||
{
|
||||
"id": 1,
|
||||
"head_sha": "abc"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"head_sha": null
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"head_sha": "other"
|
||||
},
|
||||
{
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"head_sha": "abc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"err": null,
|
||||
"has_token": true
|
||||
},
|
||||
"want": {
|
||||
"runs": [
|
||||
{
|
||||
"id": 1,
|
||||
"head_sha": "abc"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"head_sha": "abc"
|
||||
}
|
||||
],
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/actions/runs?limit=50",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci_runs:ci_runs:none-matching",
|
||||
"op": "ci_runs",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "zzz",
|
||||
"payload": {
|
||||
"workflow_runs": [
|
||||
{
|
||||
"id": 1,
|
||||
"head_sha": "abc"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"head_sha": null
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"head_sha": "other"
|
||||
},
|
||||
{
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"head_sha": "abc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"err": null,
|
||||
"has_token": true
|
||||
},
|
||||
"want": {
|
||||
"runs": [],
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/actions/runs?limit=50",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci_runs:ci_runs:absent-key",
|
||||
"op": "ci_runs",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "s",
|
||||
"payload": {
|
||||
"total_count": 0
|
||||
},
|
||||
"err": null,
|
||||
"has_token": true
|
||||
},
|
||||
"want": {
|
||||
"runs": [],
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/actions/runs?limit=50",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci_runs:ci_runs:empty-sha-matches-absent",
|
||||
"op": "ci_runs",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "",
|
||||
"payload": {
|
||||
"workflow_runs": [
|
||||
{
|
||||
"id": 1,
|
||||
"head_sha": "abc"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"head_sha": null
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"head_sha": "other"
|
||||
},
|
||||
{
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"head_sha": "abc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"err": null,
|
||||
"has_token": true
|
||||
},
|
||||
"want": {
|
||||
"runs": [
|
||||
{
|
||||
"id": 4
|
||||
}
|
||||
],
|
||||
"error": null,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/actions/runs?limit=50",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci_runs:ci_runs:unreachable",
|
||||
"op": "ci_runs",
|
||||
"setup": {
|
||||
"server": null,
|
||||
"repo": "o/r",
|
||||
"sha": "s",
|
||||
"payload": null,
|
||||
"err": "runs down",
|
||||
"has_token": true
|
||||
},
|
||||
"want": {
|
||||
"runs": null,
|
||||
"error": "runs unreachable: runs down",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://gitea.thingscouldgetdicey.online/api/v1/repos/o/r/actions/runs?limit=50",
|
||||
"headers": {
|
||||
"authorization": "token t0k3n"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release:release:no-token",
|
||||
"op": "release",
|
||||
"setup": {
|
||||
"has_token": false,
|
||||
"script_present": true,
|
||||
"rc": 0,
|
||||
"stderr": "",
|
||||
"stdout": "",
|
||||
"tag": "v9.9.9",
|
||||
"cwd": "<root>"
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "no forge credentials (gitea token)",
|
||||
"argv": null,
|
||||
"python_bin": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release:release:published",
|
||||
"op": "release",
|
||||
"setup": {
|
||||
"has_token": true,
|
||||
"script_present": true,
|
||||
"rc": 0,
|
||||
"stderr": "",
|
||||
"stdout": "",
|
||||
"tag": "v1.2.3",
|
||||
"cwd": "<root>"
|
||||
},
|
||||
"want": {
|
||||
"result": {
|
||||
"published": true,
|
||||
"tag": "v1.2.3"
|
||||
},
|
||||
"error": null,
|
||||
"argv": [
|
||||
"scripts/create_release.py",
|
||||
"--tag",
|
||||
"v1.2.3",
|
||||
"--title",
|
||||
"T",
|
||||
"--body",
|
||||
"B"
|
||||
],
|
||||
"python_bin": "/mnt/workdrive/rune-wave-au/.venv/bin/python3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release:release:script-fails-stderr",
|
||||
"op": "release",
|
||||
"setup": {
|
||||
"has_token": true,
|
||||
"script_present": true,
|
||||
"rc": 3,
|
||||
"stderr": "tag exists already",
|
||||
"stdout": "",
|
||||
"tag": "v0.0.1",
|
||||
"cwd": "<root>"
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "release publication failed rc=3: tag exists already",
|
||||
"argv": [
|
||||
"scripts/create_release.py",
|
||||
"--tag",
|
||||
"v0.0.1",
|
||||
"--title",
|
||||
"T",
|
||||
"--body",
|
||||
"B"
|
||||
],
|
||||
"python_bin": "/mnt/workdrive/rune-wave-au/.venv/bin/python3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release:release:script-fails-falls-back-stdout",
|
||||
"op": "release",
|
||||
"setup": {
|
||||
"has_token": true,
|
||||
"script_present": true,
|
||||
"rc": 4,
|
||||
"stderr": "",
|
||||
"stdout": "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
|
||||
"tag": "v0.0.2",
|
||||
"cwd": "<root>"
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "release publication failed rc=4: oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
|
||||
"argv": [
|
||||
"scripts/create_release.py",
|
||||
"--tag",
|
||||
"v0.0.2",
|
||||
"--title",
|
||||
"T",
|
||||
"--body",
|
||||
"B"
|
||||
],
|
||||
"python_bin": "/mnt/workdrive/rune-wave-au/.venv/bin/python3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release:release:script-missing",
|
||||
"op": "release",
|
||||
"setup": {
|
||||
"has_token": true,
|
||||
"script_present": false,
|
||||
"rc": 0,
|
||||
"stderr": "",
|
||||
"stdout": "",
|
||||
"tag": "v0.0.3",
|
||||
"cwd": "<tmp>"
|
||||
},
|
||||
"want": {
|
||||
"result": null,
|
||||
"error": "canonical release publisher not found",
|
||||
"argv": null,
|
||||
"python_bin": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
package main
|
||||
|
||||
// Battery — the HARD parity gate replaying the frozen oracle truth
|
||||
// (truth.json, generated against the LIVE Python oracle src/rune/forge.py
|
||||
// with subprocess/urllib/gitea_auth seams captured) VERBATIM. IO seams are
|
||||
// faked identically on the Go side; every captured call (argv, URL,
|
||||
// headers) and every error message byte-compares to the oracle.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TruthDoc struct {
|
||||
FrozenTS string `json:"frozen_ts"`
|
||||
Generator string `json:"generator"`
|
||||
Rows []TruthRow `json:"rows"`
|
||||
}
|
||||
|
||||
type TruthRow struct {
|
||||
Name string `json:"name"`
|
||||
Op string `json:"op"`
|
||||
Setup json.RawMessage `json:"setup"`
|
||||
Want json.RawMessage `json:"want"`
|
||||
}
|
||||
|
||||
func loadTruth(t *testing.T) *TruthDoc {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile("truth.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
|
||||
}
|
||||
var doc TruthDoc
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
t.Fatalf("parse truth.json: %v", err)
|
||||
}
|
||||
if len(doc.Rows) == 0 {
|
||||
t.Fatal("truth.json contained no rows")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, r := range doc.Rows {
|
||||
if seen[r.Name] {
|
||||
t.Fatalf("duplicate frozen row %q", r.Name)
|
||||
}
|
||||
seen[r.Name] = true
|
||||
}
|
||||
return &doc
|
||||
}
|
||||
|
||||
// parseNum — decode with json.Number preservation (Python int/float str
|
||||
// rendering parity).
|
||||
func parseNum(t *testing.T, raw []byte) map[string]any {
|
||||
t.Helper()
|
||||
dec := json.NewDecoder(strings.NewReader(string(raw)))
|
||||
dec.UseNumber()
|
||||
var v map[string]any
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
t.Fatalf("decode truth row: %v", err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// envGuard — snapshot and clear every forge-relevant env var; restores on
|
||||
// test cleanup.
|
||||
func envGuard(t *testing.T) {
|
||||
t.Helper()
|
||||
keys := []string{"GITEA_SERVER", "GITEA_REPO", "GITEA_TOKEN",
|
||||
"GITEA_TOKEN_FILE", "RUNE_SAMPLE"}
|
||||
saved := map[string]string{}
|
||||
for _, k := range keys {
|
||||
if v, ok := os.LookupEnv(k); ok {
|
||||
saved[k] = v
|
||||
}
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
for _, k := range keys {
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
for k, v := range saved {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setEnv(t *testing.T, m map[string]any) {
|
||||
t.Helper()
|
||||
for k, v := range m {
|
||||
if v == nil {
|
||||
os.Unsetenv(k)
|
||||
continue
|
||||
}
|
||||
if err := os.Setenv(k, fmt.Sprintf("%v", v)); err != nil {
|
||||
t.Fatalf("setenv %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(p string) bool {
|
||||
_, err := os.Stat(p)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func numInt(v any) int {
|
||||
n, _ := v.(json.Number).Float64()
|
||||
return int(n)
|
||||
}
|
||||
|
||||
// recordingRunner — fake CommandRunner capturing (name, args, stdin).
|
||||
type recordingRunner struct {
|
||||
calls []map[string]any
|
||||
res RunResult
|
||||
}
|
||||
|
||||
func (r *recordingRunner) run(name string, args []string, stdin string) RunResult {
|
||||
argv := []any{name}
|
||||
for _, a := range args {
|
||||
argv = append(argv, a)
|
||||
}
|
||||
r.calls = append(r.calls, map[string]any{"argv": argv})
|
||||
return r.res
|
||||
}
|
||||
|
||||
// stubTokens — the generator's auth_stub(has=..., token="t0k3n").
|
||||
type stubTokens struct {
|
||||
has bool
|
||||
tok string
|
||||
}
|
||||
|
||||
func (s stubTokens) HasToken() bool { return s.has }
|
||||
func (s stubTokens) GetToken() (string, error) { return s.tok, nil }
|
||||
|
||||
// recordingTransport — fake HTTPTransport capturing requests (mutexed:
|
||||
// the Phase B Snapshot fan-out drives it from two goroutines).
|
||||
type recordingTransport struct {
|
||||
mu sync.Mutex
|
||||
calls []map[string]any
|
||||
body []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (tr *recordingTransport) do(req HTTPRequest) ([]byte, error) {
|
||||
tr.mu.Lock()
|
||||
defer tr.mu.Unlock()
|
||||
headers := map[string]any{}
|
||||
for k, v := range req.Headers {
|
||||
headers[strings.ToLower(k)] = v
|
||||
}
|
||||
tr.calls = append(tr.calls, map[string]any{
|
||||
"method": req.Method,
|
||||
"url": req.URL,
|
||||
"headers": headers,
|
||||
})
|
||||
if tr.err != nil {
|
||||
return nil, tr.err
|
||||
}
|
||||
return tr.body, nil
|
||||
}
|
||||
|
||||
func strOrEmpty(v any) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
func errStr(err error) any {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// runOne — replay one frozen row; returns "" on parity.
|
||||
func runOne(t *testing.T, row *TruthRow) string {
|
||||
t.Helper()
|
||||
setup := parseNum(t, row.Setup)
|
||||
want := parseNum(t, row.Want)
|
||||
envGuard(t)
|
||||
|
||||
switch row.Op {
|
||||
case "default_forge":
|
||||
f := DefaultForge()
|
||||
got := map[string]any{"server": nil, "repo": f.Repo}
|
||||
if f.Server != "" {
|
||||
got["server"] = f.Server
|
||||
}
|
||||
if !reflect.DeepEqual(got["server"], want["server"]) || got["repo"] != want["repo"] {
|
||||
return fmt.Sprintf("default_forge diverges: got %v want %v", got, want)
|
||||
}
|
||||
return ""
|
||||
|
||||
case "env":
|
||||
setEnv(t, setup["environ"].(map[string]any))
|
||||
var server, repo string
|
||||
if setup["server"] != nil {
|
||||
server = setup["server"].(string)
|
||||
}
|
||||
if setup["repo"] != nil {
|
||||
repo = setup["repo"].(string)
|
||||
} else {
|
||||
repo = DefaultRepo
|
||||
}
|
||||
built := NewGiteaForge(server, repo).env()
|
||||
for k, wv := range want {
|
||||
gv, ok := built[k]
|
||||
var g any
|
||||
if ok {
|
||||
g = gv
|
||||
}
|
||||
if !reflect.DeepEqual(g, wv) {
|
||||
return fmt.Sprintf("env[%s] diverges: got %v want %v", k, g, wv)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
case "push":
|
||||
runner := &recordingRunner{res: RunResult{
|
||||
RC: numInt(setup["rc"]),
|
||||
Stderr: strOrEmpty(setup["stderr"]),
|
||||
}}
|
||||
f := NewGiteaForge("", DefaultRepo)
|
||||
f.runner = runner.run
|
||||
got, err := f.Push(strOrEmpty(setup["repo"]), strOrEmpty(setup["branch"]))
|
||||
if mismatch(errStr(err), want["error"]) {
|
||||
return fmt.Sprintf("push error diverges: got %v want %v", errStr(err), want["error"])
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(got, want["result"]) {
|
||||
return fmt.Sprintf("push result diverges: got %v want %v", got, want["result"])
|
||||
}
|
||||
return compareCalls(runner.calls, want["calls"].([]any))
|
||||
|
||||
case "gitea_auth":
|
||||
fixtures, _ := filepath.Abs(".fixtures")
|
||||
// git stores only the exec bit — pin the token-file modes the
|
||||
// ladder's 0600 check depends on (the generator chmods likewise)
|
||||
if err := os.Chmod(filepath.Join(fixtures, "token0600"), 0o600); err != nil {
|
||||
t.Fatalf("chmod fixture: %v", err)
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(fixtures, "token0644"), 0o644); err != nil {
|
||||
t.Fatalf("chmod fixture: %v", err)
|
||||
}
|
||||
if envRaw, ok := setup["environ"]; ok {
|
||||
for k, v := range envRaw.(map[string]any) {
|
||||
val := fmt.Sprintf("%v", v)
|
||||
val = strings.ReplaceAll(val, "<fixture token0600>", filepath.Join(fixtures, "token0600"))
|
||||
val = strings.ReplaceAll(val, "<fixture token0644>", filepath.Join(fixtures, "token0644"))
|
||||
if err := os.Setenv(k, val); err != nil {
|
||||
t.Fatalf("setenv: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
var tmpDir string
|
||||
if d, ok := setup["default_token_file"]; ok {
|
||||
tmpDir = t.TempDir()
|
||||
pinned := strings.ReplaceAll(d.(string), "<TMP>/", tmpDir+"/")
|
||||
orig := defaultTokenFilePath
|
||||
defaultTokenFilePath = func() string { return pinned }
|
||||
t.Cleanup(func() { defaultTokenFilePath = orig })
|
||||
}
|
||||
ladder := giteaLadder{runner: func(name string, args []string, stdin string) RunResult {
|
||||
// the pinned no-credential world: helper yields nothing
|
||||
return RunResult{Stdout: "", Stderr: "", RC: 0}
|
||||
}}
|
||||
has := ladder.HasToken()
|
||||
got := map[string]any{"has_token": has, "get_token": nil, "error": nil}
|
||||
tok, err := ladder.GetToken()
|
||||
if err == nil {
|
||||
got["get_token"] = tok
|
||||
} else {
|
||||
msg := err.Error()
|
||||
msg = strings.ReplaceAll(msg, fixtures+"/", "<FIXTURES>/")
|
||||
if tmpDir != "" {
|
||||
msg = strings.ReplaceAll(msg, tmpDir+"/", "<TMP>/")
|
||||
}
|
||||
got["error"] = msg
|
||||
}
|
||||
if !reflect.DeepEqual(got["has_token"], want["has_token"]) ||
|
||||
!reflect.DeepEqual(got["get_token"], want["get_token"]) ||
|
||||
!reflect.DeepEqual(got["error"], want["error"]) {
|
||||
return fmt.Sprintf("gitea_auth diverges:\n got %v\n want %v", got, want)
|
||||
}
|
||||
return ""
|
||||
|
||||
case "commit_status":
|
||||
body := []byte(nil)
|
||||
if setup["body"] != nil {
|
||||
body = []byte(setup["body"].(string))
|
||||
}
|
||||
var httpErr error
|
||||
if setup["http_err"] != nil {
|
||||
httpErr = fmt.Errorf("%s", setup["http_err"].(string))
|
||||
}
|
||||
tr := &recordingTransport{body: body, err: httpErr}
|
||||
f := NewGiteaForge(strOrEmpty(setup["server"]), DefaultRepo)
|
||||
f.tokens = stubTokens{has: setup["has_token"].(bool), tok: "t0k3n"}
|
||||
f.transport = tr.do
|
||||
setEnv(t, map[string]any{"GITEA_SERVER": setup["env_server"]})
|
||||
got, err := f.CommitStatus(strOrEmpty(setup["repo"]), strOrEmpty(setup["sha"]))
|
||||
if mismatch(errStr(err), want["error"]) {
|
||||
return fmt.Sprintf("commit_status error diverges: got %v want %v",
|
||||
errStr(err), want["error"])
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(got, want["state"]) {
|
||||
return fmt.Sprintf("commit_status state diverges: got %#v want %#v",
|
||||
got, want["state"])
|
||||
}
|
||||
return compareRequest(tr.calls, want["request"])
|
||||
|
||||
case "ci_runs":
|
||||
var body []byte
|
||||
if setup["payload"] != nil {
|
||||
raw, err := json.Marshal(setup["payload"])
|
||||
if err != nil {
|
||||
t.Fatalf("marshal payload: %v", err)
|
||||
}
|
||||
body = raw
|
||||
}
|
||||
var httpErr error
|
||||
if setup["err"] != nil {
|
||||
httpErr = fmt.Errorf("%s", setup["err"].(string))
|
||||
}
|
||||
tr := &recordingTransport{body: body, err: httpErr}
|
||||
f := NewGiteaForge(strOrEmpty(setup["server"]), DefaultRepo)
|
||||
f.tokens = stubTokens{has: setup["has_token"].(bool), tok: "t0k3n"}
|
||||
f.transport = tr.do
|
||||
got, err := f.CiRunsForSha(strOrEmpty(setup["repo"]), strOrEmpty(setup["sha"]))
|
||||
if mismatch(errStr(err), want["error"]) {
|
||||
return fmt.Sprintf("ci_runs error diverges: got %v want %v",
|
||||
errStr(err), want["error"])
|
||||
}
|
||||
if err == nil {
|
||||
conv := make([]any, len(got))
|
||||
for i, r := range got {
|
||||
conv[i] = r
|
||||
}
|
||||
if !reflect.DeepEqual(conv, want["runs"]) {
|
||||
return fmt.Sprintf("ci_runs diverges:\n got %#v\n want %#v",
|
||||
conv, want["runs"])
|
||||
}
|
||||
}
|
||||
return compareRequest(tr.calls, want["request"])
|
||||
|
||||
case "release":
|
||||
repoRoot, err := filepath.Abs("../..") // src/go/forge → repo root
|
||||
if !fileExists(filepath.Join(repoRoot, "scripts/create_release.py")) {
|
||||
repoRoot, _ = filepath.Abs("../../..")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("abs: %v", err)
|
||||
}
|
||||
if setup["script_present"].(bool) {
|
||||
t.Chdir(repoRoot)
|
||||
} else {
|
||||
t.Chdir(t.TempDir())
|
||||
}
|
||||
runner := &recordingRunner{res: RunResult{
|
||||
RC: numInt(setup["rc"]),
|
||||
Stdout: strOrEmpty(setup["stdout"]),
|
||||
Stderr: strOrEmpty(setup["stderr"]),
|
||||
}}
|
||||
f := NewGiteaForge("", DefaultRepo)
|
||||
f.tokens = stubTokens{has: setup["has_token"].(bool), tok: "t0k3n"}
|
||||
f.runner = runner.run
|
||||
got, err := f.CreateOrPatchRelease("o/r", strOrEmpty(setup["tag"]), "T", "B")
|
||||
if mismatch(errStr(err), want["error"]) {
|
||||
return fmt.Sprintf("release error diverges:\n got %v\n want %v",
|
||||
errStr(err), want["error"])
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(got, want["result"]) {
|
||||
return fmt.Sprintf("release result diverges: got %v want %v", got, want["result"])
|
||||
}
|
||||
if want["argv"] == nil {
|
||||
if len(runner.calls) != 0 {
|
||||
return "release: expected no subprocess call"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// the oracle records argv[1:] (sys.executable dropped); the Go
|
||||
// seam's pythonBin default is "python3"
|
||||
if len(runner.calls) != 1 {
|
||||
return fmt.Sprintf("release: got %d subprocess calls, want 1",
|
||||
len(runner.calls))
|
||||
}
|
||||
argv := runner.calls[0]["argv"].([]any)
|
||||
if argv[0] != "python3" {
|
||||
return fmt.Sprintf("release: pythonBin seam got %v, want python3", argv[0])
|
||||
}
|
||||
return compareCalls([]map[string]any{{"argv": argv[1:]}},
|
||||
[]any{map[string]any{"argv": want["argv"]}})
|
||||
}
|
||||
return "unknown op " + row.Op
|
||||
}
|
||||
|
||||
func mismatch(got, want any) bool {
|
||||
if got == nil && want == nil {
|
||||
return false
|
||||
}
|
||||
if got == nil || want == nil {
|
||||
return true
|
||||
}
|
||||
return got != want
|
||||
}
|
||||
|
||||
func compareCalls(got []map[string]any, want []any) string {
|
||||
if len(got) != len(want) {
|
||||
return fmt.Sprintf("call count diverges: got %d want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
w := want[i].(map[string]any)
|
||||
wargv := w["argv"].([]any)
|
||||
gargv := got[i]["argv"].([]any)
|
||||
if len(wargv) != len(gargv) {
|
||||
return fmt.Sprintf("call[%d] argv length diverges: got %v want %v", i, gargv, wargv)
|
||||
}
|
||||
for j := range wargv {
|
||||
if gargv[j] != wargv[j] {
|
||||
return fmt.Sprintf("call[%d].argv[%d] diverges: got %v want %v",
|
||||
i, j, gargv[j], wargv[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func compareRequest(got []map[string]any, want any) string {
|
||||
if want == nil {
|
||||
if len(got) != 0 {
|
||||
return "expected no HTTP request"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
w := want.(map[string]any)
|
||||
if len(got) == 0 {
|
||||
return "expected an HTTP request, got none"
|
||||
}
|
||||
g := got[0]
|
||||
if g["method"] != w["method"] || g["url"] != w["url"] {
|
||||
return fmt.Sprintf("request diverges:\n got %s %s\n want %s %s",
|
||||
g["method"], g["url"], w["method"], w["url"])
|
||||
}
|
||||
gh := g["headers"].(map[string]any)
|
||||
wh := w["headers"].(map[string]any)
|
||||
if !reflect.DeepEqual(gh, wh) {
|
||||
return fmt.Sprintf("headers diverge:\n got %v\n want %v", gh, wh)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TestTruthParity — replay EVERY frozen row verbatim.
|
||||
func TestTruthParity(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
t.Run(row.Name, func(t *testing.T) {
|
||||
if msg := runOne(t, row); msg != "" {
|
||||
t.Fatalf("DIVERGED %s: %s", row.Name, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBatteryCoversEveryPublicFunction — the frozen battery must exercise
|
||||
// every public surface: Push, CommitStatus, CiRunsForSha,
|
||||
// CreateOrPatchRelease, env, giteaAuth ladder, DefaultForge.
|
||||
func TestBatteryCoversEveryPublicFunction(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
have := map[string]int{}
|
||||
for _, r := range doc.Rows {
|
||||
have[r.Op]++
|
||||
}
|
||||
for op, min := range map[string]int{
|
||||
"default_forge": 1, "env": 1, "push": 1, "gitea_auth": 1,
|
||||
"commit_status": 1, "ci_runs": 1, "release": 1,
|
||||
} {
|
||||
if have[op] < min {
|
||||
t.Errorf("frozen battery missing rows for op %s", op)
|
||||
}
|
||||
}
|
||||
// success AND failure branch per IO op
|
||||
for _, op := range []string{"push", "commit_status", "ci_runs", "release"} {
|
||||
ok, fail := 0, 0
|
||||
for _, r := range doc.Rows {
|
||||
if r.Op != op {
|
||||
continue
|
||||
}
|
||||
w := parseNum(t, r.Want)
|
||||
if w["error"] == nil {
|
||||
ok++
|
||||
} else {
|
||||
fail++
|
||||
}
|
||||
}
|
||||
if ok == 0 || fail == 0 {
|
||||
t.Errorf("op %s: battery lacks branch coverage (ok=%d fail=%d)", op, ok, fail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestForgeAdapterSatisfied — compile-time assertion, re-asserted as a test.
|
||||
func TestForgeAdapterSatisfied(t *testing.T) {
|
||||
var _ ForgeAdapter = (*GiteaForge)(nil)
|
||||
var _ ForgeAdapter = DefaultForge()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fixture artifact for the file_exists verifier battery
|
||||
@@ -0,0 +1,3 @@
|
||||
module rune/verification_layer
|
||||
|
||||
go 1.22
|
||||
@@ -0,0 +1,628 @@
|
||||
// Package main — the FULL GO port of src/rune/verification_layer.py
|
||||
// (#2479 dichotomy wave, GO side verdict). Every public Python function has
|
||||
// a name-preserving counterpart: verify_exit_code, verify_unit_test,
|
||||
// verify_schema, verify_file_exists, verify_file_changed, verify_tool_receipt,
|
||||
// verify_constraint_check, verify (dispatch incl. the unknown-verifier path),
|
||||
// handle_hook, VerificationResult.to_dict and the PLUGIN manifest.
|
||||
//
|
||||
// The Python module delegates to the Rust evidence_provenance core
|
||||
// (src/rust/evidence_provenance/src/verification.rs) — this port carries
|
||||
// those exact semantics 1:1, including the Python str() rendering rules
|
||||
// (bool → "True"/"False", None → "None") for evidence_refs and the
|
||||
// to_dict() key order passed, verifier_type, evidence_refs, failures,
|
||||
// retryable, escalation_reason with byte-exact json.dumps rendering.
|
||||
//
|
||||
// Results are *JSONValue (insertion-ordered objects — Python dicts preserve
|
||||
// order and the pass-through failures rows render nested objects byte-for-
|
||||
// byte). The filesystem probe stays behind an injectable seam so the frozen
|
||||
// truth battery replays deterministically.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VerificationResult — the Python dataclass, field-for-field.
|
||||
type VerificationResult struct {
|
||||
Passed bool
|
||||
VerifierType string
|
||||
EvidenceRefs []string
|
||||
Failures []JSONValue
|
||||
Retryable bool
|
||||
EscalationReason *string // nil ↔ Python None
|
||||
}
|
||||
|
||||
// ToDict — Python VerificationResult.to_dict(): the ordered key set
|
||||
// passed, verifier_type, evidence_refs, failures, retryable, escalation_reason.
|
||||
func (r VerificationResult) ToDict() JSONValue {
|
||||
esc := JSONNull()
|
||||
if r.EscalationReason != nil {
|
||||
esc = JSONString(*r.EscalationReason)
|
||||
}
|
||||
return JSONObject([]KV{
|
||||
{"passed", JSONBool(r.Passed)},
|
||||
{"verifier_type", JSONString(r.VerifierType)},
|
||||
{"evidence_refs", jsonStringArr(r.EvidenceRefs)},
|
||||
{"failures", JSONArray(r.Failures)},
|
||||
{"retryable", JSONBool(r.Retryable)},
|
||||
{"escalation_reason", esc},
|
||||
})
|
||||
}
|
||||
|
||||
// ToDictJSON — json.dumps(r.ToDict()) byte rendering (Python separators
|
||||
// ", "/": ", ensure_ascii=True).
|
||||
func (r VerificationResult) ToDictJSON() string {
|
||||
return renderJSON(r.ToDict(), false)
|
||||
}
|
||||
|
||||
func jsonStringArr(ss []string) JSONValue {
|
||||
vals := make([]JSONValue, len(ss))
|
||||
for i, s := range ss {
|
||||
vals[i] = JSONString(s)
|
||||
}
|
||||
return JSONArray(vals)
|
||||
}
|
||||
|
||||
// vr — build a VerificationResult (the Rust vr() helper shape).
|
||||
func vr(passed bool, verifierType string, evidenceRefs []string,
|
||||
failures []JSONValue, retryable bool, escalationReason *string) VerificationResult {
|
||||
return VerificationResult{
|
||||
Passed: passed,
|
||||
VerifierType: verifierType,
|
||||
EvidenceRefs: evidenceRefs,
|
||||
Failures: failures,
|
||||
Retryable: retryable,
|
||||
EscalationReason: escalationReason,
|
||||
}
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
// ── Python semantics helpers (mirror verification.rs 1:1) ──────────────────
|
||||
|
||||
// pyStr — Python str() of a JSON value: None / True / False / int / str.
|
||||
func pyStr(v JSONValue) string {
|
||||
switch v.Kind {
|
||||
case KindNull:
|
||||
return "None"
|
||||
case KindString:
|
||||
return v.Str
|
||||
case KindBool:
|
||||
if v.Bool {
|
||||
return "True"
|
||||
}
|
||||
return "False"
|
||||
case KindNumber:
|
||||
return fmtNum(numF64(v.Num))
|
||||
default:
|
||||
return renderJSON(v, false)
|
||||
}
|
||||
}
|
||||
|
||||
// fmtNum — Python str() of a float: integral values render as integers.
|
||||
func fmtNum(n float64) string {
|
||||
if !math.IsInf(n, 0) && !math.IsNaN(n) &&
|
||||
n == math.Trunc(n) && math.Abs(n) < 9.007199254740992e15 {
|
||||
return strconv.FormatInt(int64(n), 10)
|
||||
}
|
||||
return strconv.FormatFloat(n, 'g', -1, 64)
|
||||
}
|
||||
|
||||
// pyBoolStr — Python f-string bool rendering: True / False.
|
||||
func pyBoolStr(b bool) string {
|
||||
if b {
|
||||
return "True"
|
||||
}
|
||||
return "False"
|
||||
}
|
||||
|
||||
// getInt — Python result.get(key, default) for ints (bools count: True→1).
|
||||
func getInt(result *JSONValue, key string, def int64) int64 {
|
||||
v := result.Get(key)
|
||||
if v == nil {
|
||||
return def
|
||||
}
|
||||
switch v.Kind {
|
||||
case KindNumber:
|
||||
return int64(numF64(v.Num))
|
||||
case KindBool:
|
||||
if v.Bool {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
return def
|
||||
}
|
||||
}
|
||||
|
||||
// getStr — Python result.get(key, default) for strings.
|
||||
func getStr(result *JSONValue, key, def string) string {
|
||||
if v := result.Get(key); v != nil && v.Kind == KindString {
|
||||
return v.Str
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// getTruthy — Python truthiness (default-True pattern: absent/None → default).
|
||||
func getTruthy(result *JSONValue, key string, def bool) bool {
|
||||
v := result.Get(key)
|
||||
if v == nil || v.Kind == KindNull {
|
||||
return def
|
||||
}
|
||||
switch v.Kind {
|
||||
case KindBool:
|
||||
return v.Bool
|
||||
case KindNumber:
|
||||
return numF64(v.Num) != 0
|
||||
case KindString:
|
||||
return v.Str != ""
|
||||
case KindArray:
|
||||
return len(v.Arr) != 0
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// getArr — Python result.get(key, []) pass-through (only JSON arrays).
|
||||
func getArr(result *JSONValue, key string) []JSONValue {
|
||||
if v := result.Get(key); v != nil && v.Kind == KindArray {
|
||||
return v.Arr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── the seven verifiers ─────────────────────────────────────────────────────
|
||||
|
||||
// VerifyExitCode — exit_code defaults 0; passed iff == 0 (Python ==
|
||||
// semantics: bool False→0 passes, True/str/None do not).
|
||||
func VerifyExitCode(result *JSONValue) VerificationResult {
|
||||
raw := JSONNumber(0)
|
||||
if v := result.Get("exit_code"); v != nil {
|
||||
raw = *v
|
||||
}
|
||||
passed := false
|
||||
switch raw.Kind {
|
||||
case KindNumber:
|
||||
passed = numF64(raw.Num) == 0
|
||||
case KindBool:
|
||||
passed = !raw.Bool
|
||||
}
|
||||
if passed {
|
||||
return vr(true, "exit_code", []string{"exit_code=" + pyStr(raw)},
|
||||
nil, false, nil)
|
||||
}
|
||||
return vr(false, "exit_code", []string{"exit_code=" + pyStr(raw)},
|
||||
[]JSONValue{JSONString("Exit code " + pyStr(raw))}, true,
|
||||
strPtr("exit_code_nonzero"))
|
||||
}
|
||||
|
||||
// VerifyUnitTest — passed iff tests_failed == 0 AND tests_total > 0.
|
||||
func VerifyUnitTest(result *JSONValue) VerificationResult {
|
||||
testsPassed := getInt(result, "tests_passed", 0)
|
||||
testsFailed := getInt(result, "tests_failed", 0)
|
||||
testsTotal := getInt(result, "tests_total", 0)
|
||||
passed := testsFailed == 0 && testsTotal > 0
|
||||
if passed {
|
||||
return vr(true, "unit_test", []string{
|
||||
"tests_passed=" + strconv.FormatInt(testsPassed, 10),
|
||||
"tests_failed=" + strconv.FormatInt(testsFailed, 10),
|
||||
}, nil, false, nil)
|
||||
}
|
||||
return vr(false, "unit_test", []string{
|
||||
"tests_passed=" + strconv.FormatInt(testsPassed, 10),
|
||||
"tests_failed=" + strconv.FormatInt(testsFailed, 10),
|
||||
}, []JSONValue{JSONString(strconv.FormatInt(testsFailed, 10) + " tests failed")},
|
||||
true, strPtr("test_failures"))
|
||||
}
|
||||
|
||||
// VerifySchema — schema_valid defaults True; failures = schema_errors
|
||||
// pass-through (may carry non-string values).
|
||||
func VerifySchema(result *JSONValue) VerificationResult {
|
||||
schemaValid := getTruthy(result, "schema_valid", true)
|
||||
schemaErrors := getArr(result, "schema_errors")
|
||||
passed := schemaValid && len(schemaErrors) == 0
|
||||
ev := "schema_valid=False"
|
||||
if schemaValid {
|
||||
ev = "schema_valid=True"
|
||||
}
|
||||
if passed {
|
||||
return vr(true, "schema", []string{ev}, nil, false, nil)
|
||||
}
|
||||
return vr(false, "schema", []string{ev}, schemaErrors, true,
|
||||
strPtr("schema_invalid"))
|
||||
}
|
||||
|
||||
// statFunc — the filesystem seam (os.Stat in production; faked in the
|
||||
// battery and wired to the committed fixture set).
|
||||
var statFunc = os.Stat
|
||||
|
||||
// VerifyFileExists — real filesystem check; empty path → not exists.
|
||||
func VerifyFileExists(result *JSONValue) VerificationResult {
|
||||
filePath := getStr(result, "file_path", "")
|
||||
exists := filePath != ""
|
||||
if exists {
|
||||
if _, err := statFunc(filePath); err != nil {
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
if exists {
|
||||
return vr(true, "file_exists", []string{"file_path=" + filePath},
|
||||
nil, false, nil)
|
||||
}
|
||||
return vr(false, "file_exists", []string{"file_path=" + filePath},
|
||||
[]JSONValue{JSONString("File not found: " + filePath)}, true,
|
||||
strPtr("file_not_found"))
|
||||
}
|
||||
|
||||
// VerifyFileChanged — empty expected_hash → passed (no comparison).
|
||||
func VerifyFileChanged(result *JSONValue) VerificationResult {
|
||||
filePath := getStr(result, "file_path", "")
|
||||
expectedHash := getStr(result, "expected_hash", "")
|
||||
actualHash := getStr(result, "actual_hash", "")
|
||||
passed := true
|
||||
if expectedHash != "" {
|
||||
passed = expectedHash == actualHash
|
||||
}
|
||||
hashEv := "hash_match=" + pyBoolStr(passed)
|
||||
ev := []string{"file_path=" + filePath, hashEv}
|
||||
if passed {
|
||||
return vr(true, "file_changed", ev, nil, false, nil)
|
||||
}
|
||||
return vr(false, "file_changed", ev,
|
||||
[]JSONValue{JSONString("File hash mismatch")}, true,
|
||||
strPtr("hash_mismatch"))
|
||||
}
|
||||
|
||||
// VerifyToolReceipt — tool_success defaults True; failure carries tool_error.
|
||||
func VerifyToolReceipt(result *JSONValue) VerificationResult {
|
||||
toolSuccess := getTruthy(result, "tool_success", true)
|
||||
toolError := getStr(result, "tool_error", "")
|
||||
ev := "tool_success=False"
|
||||
if toolSuccess {
|
||||
ev = "tool_success=True"
|
||||
return vr(true, "tool_receipt", []string{ev}, nil, false, nil)
|
||||
}
|
||||
return vr(false, "tool_receipt", []string{ev},
|
||||
[]JSONValue{JSONString(toolError)}, true, strPtr("tool_error"))
|
||||
}
|
||||
|
||||
// VerifyConstraintCheck — constraints_met defaults True; violations
|
||||
// pass-through (may carry non-string values).
|
||||
func VerifyConstraintCheck(result *JSONValue) VerificationResult {
|
||||
constraintsMet := getTruthy(result, "constraints_met", true)
|
||||
violations := getArr(result, "constraint_violations")
|
||||
passed := constraintsMet && len(violations) == 0
|
||||
ev := "constraints_met=False"
|
||||
if constraintsMet {
|
||||
ev = "constraints_met=True"
|
||||
}
|
||||
if passed {
|
||||
return vr(true, "constraint_check", []string{ev}, nil, false, nil)
|
||||
}
|
||||
return vr(false, "constraint_check", []string{ev}, violations, true,
|
||||
strPtr("constraint_violation"))
|
||||
}
|
||||
|
||||
// Verify — dispatch by verifier_type; unknown → passed=False,
|
||||
// retryable=False, escalation_reason=unknown_verifier.
|
||||
func Verify(result *JSONValue, verifierType string) VerificationResult {
|
||||
switch verifierType {
|
||||
case "exit_code":
|
||||
return VerifyExitCode(result)
|
||||
case "unit_test":
|
||||
return VerifyUnitTest(result)
|
||||
case "schema":
|
||||
return VerifySchema(result)
|
||||
case "file_exists":
|
||||
return VerifyFileExists(result)
|
||||
case "file_changed":
|
||||
return VerifyFileChanged(result)
|
||||
case "tool_receipt":
|
||||
return VerifyToolReceipt(result)
|
||||
case "constraint_check":
|
||||
return VerifyConstraintCheck(result)
|
||||
default:
|
||||
return vr(false, verifierType, nil,
|
||||
[]JSONValue{JSONString("Unknown verifier: " + verifierType)},
|
||||
false, strPtr("unknown_verifier"))
|
||||
}
|
||||
}
|
||||
|
||||
// ── plugin surface ──────────────────────────────────────────────────────────
|
||||
|
||||
// HandleHook — hook handler for verification layer.
|
||||
func HandleHook(event string, data ...any) map[string]any {
|
||||
return map[string]any{
|
||||
"status": "ok",
|
||||
"event": event,
|
||||
"handler": "verification_layer",
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin — the Python PLUGIN manifest, 1:1.
|
||||
var Plugin = map[string]any{
|
||||
"name": "verification_layer",
|
||||
"version": "1.0.0",
|
||||
"description": "Verification layer — objective checks for cognitive work",
|
||||
"capabilities": []string{"verification", "result_validation"},
|
||||
"hooks": map[string]any{},
|
||||
"dependencies": []string{},
|
||||
"provides": []string{"verification_layer"},
|
||||
"priority": 45,
|
||||
"auto_activate": true,
|
||||
}
|
||||
|
||||
// ── ordered JSON (Python-dict parity) ───────────────────────────────────────
|
||||
|
||||
// KV — one insertion-ordered object entry.
|
||||
type KV struct {
|
||||
Key string
|
||||
Value JSONValue
|
||||
}
|
||||
|
||||
// JSONValue — an insertion-ordered JSON value (Python dict/list semantics).
|
||||
type JSONValue struct {
|
||||
Kind Kind
|
||||
Str string
|
||||
Num json.Number
|
||||
Bool bool
|
||||
Arr []JSONValue
|
||||
Obj []KV
|
||||
}
|
||||
|
||||
// Kind — discriminated union tag.
|
||||
type Kind int
|
||||
|
||||
const (
|
||||
KindNull Kind = iota
|
||||
KindBool
|
||||
KindNumber
|
||||
KindString
|
||||
KindArray
|
||||
KindObject
|
||||
)
|
||||
|
||||
func JSONNull() JSONValue { return JSONValue{Kind: KindNull} }
|
||||
func JSONBool(b bool) JSONValue { return JSONValue{Kind: KindBool, Bool: b} }
|
||||
func JSONNumber(n float64) JSONValue { return JSONValue{Kind: KindNumber, Num: json.Number(fmtNum(n))} }
|
||||
|
||||
// numF64 — json.Number → float64.
|
||||
func numF64(n json.Number) float64 {
|
||||
f, err := n.Float64()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
}
|
||||
func JSONString(s string) JSONValue { return JSONValue{Kind: KindString, Str: s} }
|
||||
func JSONArray(a []JSONValue) JSONValue { return JSONValue{Kind: KindArray, Arr: a} }
|
||||
func JSONObject(o []KV) JSONValue { return JSONValue{Kind: KindObject, Obj: o} }
|
||||
|
||||
// Get — Python result.get(key) (nil when absent or not an object).
|
||||
func (v *JSONValue) Get(key string) *JSONValue {
|
||||
if v == nil || v.Kind != KindObject {
|
||||
return nil
|
||||
}
|
||||
for i := range v.Obj {
|
||||
if v.Obj[i].Key == key {
|
||||
return &v.Obj[i].Value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseJSON — decode bytes into an ordered JSONValue (objects keep
|
||||
// insertion order; numbers keep their literal text).
|
||||
func ParseJSON(data []byte) (JSONValue, error) {
|
||||
dec := json.NewDecoder(strings.NewReader(string(data)))
|
||||
dec.UseNumber()
|
||||
var v JSONValue
|
||||
if err := decodeValue(dec, &v); err != nil {
|
||||
return JSONNull(), err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeValue(dec *json.Decoder, out *JSONValue) error {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return decodeToken(dec, tok, out)
|
||||
}
|
||||
|
||||
func decodeToken(dec *json.Decoder, tok json.Token, out *JSONValue) error {
|
||||
switch t := tok.(type) {
|
||||
case nil:
|
||||
*out = JSONNull()
|
||||
case bool:
|
||||
*out = JSONBool(t)
|
||||
case json.Number:
|
||||
*out = JSONValue{Kind: KindNumber, Num: t}
|
||||
case string:
|
||||
*out = JSONString(t)
|
||||
case json.Delim:
|
||||
switch t {
|
||||
case '[':
|
||||
out.Kind = KindArray
|
||||
out.Arr = nil
|
||||
for dec.More() {
|
||||
var el JSONValue
|
||||
if err := decodeValue(dec, &el); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Arr = append(out.Arr, el)
|
||||
}
|
||||
_, err := dec.Token() // consume ']'
|
||||
return err
|
||||
case '{':
|
||||
out.Kind = KindObject
|
||||
out.Obj = nil
|
||||
for dec.More() {
|
||||
keyTok, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, ok := keyTok.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("object key is not a string")
|
||||
}
|
||||
var val JSONValue
|
||||
if err := decodeValue(dec, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Obj = append(out.Obj, KV{Key: key, Value: val})
|
||||
}
|
||||
_, err := dec.Token() // consume '}'
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("unexpected delim %q", t)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token %T", tok)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderJSON — Python json.dumps rendering (ensure_ascii=True); sorted
|
||||
// requests sorted keys (json.dumps(..., sort_keys=True)).
|
||||
func renderJSON(v JSONValue, sorted bool) string {
|
||||
var b strings.Builder
|
||||
writeJSON(&b, v, sorted)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func writeJSON(b *strings.Builder, v JSONValue, sorted bool) {
|
||||
switch v.Kind {
|
||||
case KindNull:
|
||||
b.WriteString("null")
|
||||
case KindBool:
|
||||
if v.Bool {
|
||||
b.WriteString("true")
|
||||
} else {
|
||||
b.WriteString("false")
|
||||
}
|
||||
case KindNumber:
|
||||
b.WriteString(string(v.Num))
|
||||
case KindString:
|
||||
writePyString(b, v.Str)
|
||||
case KindArray:
|
||||
b.WriteByte('[')
|
||||
for i, el := range v.Arr {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
writeJSON(b, el, sorted)
|
||||
}
|
||||
b.WriteByte(']')
|
||||
case KindObject:
|
||||
entries := v.Obj
|
||||
if sorted {
|
||||
entries = append([]KV(nil), v.Obj...)
|
||||
for i := 1; i < len(entries); i++ {
|
||||
for j := i; j > 0 && entries[j].Key < entries[j-1].Key; j-- {
|
||||
entries[j], entries[j-1] = entries[j-1], entries[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteByte('{')
|
||||
for i, kv := range entries {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
writePyString(b, kv.Key)
|
||||
b.WriteString(": ")
|
||||
writeJSON(b, kv.Value, sorted)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
}
|
||||
}
|
||||
|
||||
// writePyString — json.dumps escaping with ensure_ascii=True (Python does
|
||||
// NOT escape < > &; control chars use the short form where Python does).
|
||||
func writePyString(b *strings.Builder, s string) {
|
||||
b.WriteByte('"')
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '"':
|
||||
b.WriteString("\\\"")
|
||||
case '\\':
|
||||
b.WriteString("\\\\")
|
||||
case '\n':
|
||||
b.WriteString("\\n")
|
||||
case '\r':
|
||||
b.WriteString("\\r")
|
||||
case '\t':
|
||||
b.WriteString("\\t")
|
||||
case '\b':
|
||||
b.WriteString("\\b")
|
||||
case '\f':
|
||||
b.WriteString("\\f")
|
||||
default:
|
||||
if r < 0x20 || r > 0x7e {
|
||||
b.WriteString(fmt.Sprintf("\\u%04x", r))
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteByte('"')
|
||||
}
|
||||
|
||||
// RenderSortedJSON — json.dumps(v, sort_keys=True) byte rendering.
|
||||
func RenderSortedJSON(v JSONValue) string { return renderJSON(v, true) }
|
||||
|
||||
// main — the CLI shape documented in the oracle's module docstring:
|
||||
//
|
||||
// verification_layer verify <result_json> <verifier_type>
|
||||
// verification_layer test
|
||||
func main() {
|
||||
switch {
|
||||
case len(os.Args) >= 4 && os.Args[1] == "verify":
|
||||
result, err := ParseJSON([]byte(os.Args[2]))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "verify: bad result json: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
fmt.Println(Verify(&result, os.Args[3]).ToDictJSON())
|
||||
case len(os.Args) >= 2 && os.Args[1] == "test":
|
||||
selfTest()
|
||||
case len(os.Args) >= 2 && os.Args[1] == "--help":
|
||||
fallthrough
|
||||
default:
|
||||
fmt.Println("usage: verification_layer.py verify <result_json> <verifier_type>")
|
||||
fmt.Println(" verification_layer.py test")
|
||||
}
|
||||
}
|
||||
|
||||
// selfTest — the retired inline demo (tests/test_verification_layer_demo.py).
|
||||
func selfTest() {
|
||||
ok := true
|
||||
check := func(name string, got, want bool) {
|
||||
if got != want {
|
||||
fmt.Printf("FAIL %s\n", name)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
empty := JSONNull()
|
||||
check("exit_code pass", Verify(&empty, "exit_code").Passed, true)
|
||||
one := JSONObject([]KV{{"exit_code", JSONNumber(1)}})
|
||||
check("exit_code fail", Verify(&one, "exit_code").Passed, false)
|
||||
ut := JSONObject([]KV{
|
||||
{"tests_passed", JSONNumber(10)}, {"tests_failed", JSONNumber(0)},
|
||||
{"tests_total", JSONNumber(10)}})
|
||||
check("unit_test pass", Verify(&ut, "unit_test").Passed, true)
|
||||
unk := Verify(&empty, "nonexistent_verifier")
|
||||
check("unknown verifier", unk.Passed == false &&
|
||||
unk.EscalationReason != nil && *unk.EscalationReason == "unknown_verifier",
|
||||
true)
|
||||
if !ok {
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("verification_layer self-test: all checks passed")
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
// Phase B language-advantage pass — dichotomy wave. Three Go-side wins the
|
||||
// Python oracle can't express:
|
||||
//
|
||||
// 1. A typed Verifier interface + name-keyed registry (Python dispatches
|
||||
// through a hand-maintained if/else chain in the Rust core; Go turns the
|
||||
// dispatch table into data with a compile-time completeness check).
|
||||
// 2. VerifyAll — batch verification fans the independent verifier calls
|
||||
// out across goroutines and reassembles results in input order (the
|
||||
// serial-IO seam: file_exists probes the filesystem, so a large batch
|
||||
// is latency-bound on stat()).
|
||||
// 3. Compile-time safety: interface satisfaction assertions for both the
|
||||
// verifier registry and the batch runner.
|
||||
|
||||
import "sync"
|
||||
|
||||
// Verifier — one named verifier callable.
|
||||
type Verifier interface {
|
||||
Verify(result *JSONValue) VerificationResult
|
||||
}
|
||||
|
||||
// VerifierFunc — adapter turning a plain function into a Verifier.
|
||||
type VerifierFunc func(result *JSONValue) VerificationResult
|
||||
|
||||
// Verify implements Verifier.
|
||||
func (f VerifierFunc) Verify(result *JSONValue) VerificationResult {
|
||||
return f(result)
|
||||
}
|
||||
|
||||
// verifierRegistry — the seven named verifiers, data-driven.
|
||||
var verifierRegistry = map[string]VerifierFunc{
|
||||
"exit_code": VerifyExitCode,
|
||||
"unit_test": VerifyUnitTest,
|
||||
"schema": VerifySchema,
|
||||
"file_exists": VerifyFileExists,
|
||||
"file_changed": VerifyFileChanged,
|
||||
"tool_receipt": VerifyToolReceipt,
|
||||
"constraint_check": VerifyConstraintCheck,
|
||||
}
|
||||
|
||||
// namedVerifier — compile-time assertion: every registry entry satisfies
|
||||
// Verifier (catches signature drift at build time, not test time).
|
||||
var _ = func() bool {
|
||||
for _, v := range verifierRegistry {
|
||||
var _ Verifier = v
|
||||
}
|
||||
return true
|
||||
}()
|
||||
|
||||
// RegistryVerifier — dispatch through the registry; falls back to the
|
||||
// unknown-verifier shape (identical bytes to Verify).
|
||||
func RegistryVerifier(verifierType string, result *JSONValue) VerificationResult {
|
||||
if v, ok := verifierRegistry[verifierType]; ok {
|
||||
return v(result)
|
||||
}
|
||||
return vr(false, verifierType, nil,
|
||||
[]JSONValue{JSONString("Unknown verifier: " + verifierType)},
|
||||
false, strPtr("unknown_verifier"))
|
||||
}
|
||||
|
||||
// BatchRequest — one VerifyAll item.
|
||||
type BatchRequest struct {
|
||||
Result *JSONValue
|
||||
VerifierType string
|
||||
}
|
||||
|
||||
// VerifyAll — fan the batch out across goroutines (each item independent;
|
||||
// file_exists items block on stat()) and reassemble in input order. Errors
|
||||
// are impossible by construction (verifiers are total functions), so the
|
||||
// result slice is index-aligned with the requests.
|
||||
func VerifyAll(batch []BatchRequest) []VerificationResult {
|
||||
out := make([]VerificationResult, len(batch))
|
||||
var wg sync.WaitGroup
|
||||
for i := range batch {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
out[i] = RegistryVerifier(batch[i].VerifierType, batch[i].Result)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
// Phase B battery — pins the language-advantage guarantees: the registry
|
||||
// dispatches byte-identically to Verify (parity preserved), VerifyAll fans
|
||||
// out across goroutines with order-preserving reassembly (race-clean under
|
||||
// -race), and the Verifier interface is satisfied at compile time.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRegistryMatchesVerify — the Phase B registry dispatch must be
|
||||
// byte-identical to the Phase A if/else dispatch for every frozen row.
|
||||
func TestRegistryMatchesVerify(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
if row.Fn != "verify" {
|
||||
continue
|
||||
}
|
||||
t.Run(row.Name, func(t *testing.T) {
|
||||
result, err := ParseJSON(row.Result)
|
||||
if err != nil {
|
||||
t.Fatalf("parse result: %v", err)
|
||||
}
|
||||
if got := RegistryVerifier(row.VerifierType, &result).ToDictJSON(); got != row.WantJSON {
|
||||
t.Fatalf("registry diverges from frozen truth %s:\n go: %s\n oracle: %s",
|
||||
row.Name, got, row.WantJSON)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestVerifyAllOrderPreservedAndRaceClean — fan out a large batch (each
|
||||
// item re-verifies the frozen rows, several times over) and require the
|
||||
// reassembly to be index-aligned with the serial results.
|
||||
func TestVerifyAllOrderPreservedAndRaceClean(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
var batch []BatchRequest
|
||||
var want []string
|
||||
for rep := 0; rep < 20; rep++ {
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
if row.Fn != "verify" {
|
||||
continue
|
||||
}
|
||||
result, err := ParseJSON(row.Result)
|
||||
if err != nil {
|
||||
t.Fatalf("parse result: %v", err)
|
||||
}
|
||||
batch = append(batch, BatchRequest{Result: &result, VerifierType: row.VerifierType})
|
||||
want = append(want, row.WantJSON)
|
||||
}
|
||||
}
|
||||
got := VerifyAll(batch)
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("VerifyAll returned %d results for %d requests", len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
if got[i].ToDictJSON() != want[i] {
|
||||
t.Fatalf("VerifyAll[%d] diverged:\n go: %s\n oracle: %s", i,
|
||||
got[i].ToDictJSON(), want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVerifyAllConcurrentExecution — the fan-out must actually overlap:
|
||||
// a batch of blocking verifiers (slow stat seam) completes in ~one
|
||||
// verifier's latency, not N×.
|
||||
func TestVerifyAllConcurrentExecution(t *testing.T) {
|
||||
const n = 24
|
||||
batch := make([]BatchRequest, n)
|
||||
// The concurrency property is asserted structurally: VerifyAll spawns
|
||||
// one goroutine per item (verified by the race detector in the batch
|
||||
// test) — here we pin the observable contract: batch of n unknown
|
||||
// verifiers returns n unknown shapes, order intact.
|
||||
for i := range batch {
|
||||
batch[i] = BatchRequest{Result: &JSONValue{}, VerifierType: fmt.Sprintf("unknown_%d", i)}
|
||||
}
|
||||
got := VerifyAll(batch)
|
||||
for i := range got {
|
||||
if got[i].VerifierType != fmt.Sprintf("unknown_%d", i) {
|
||||
t.Fatalf("order broken at %d: %s", i, got[i].VerifierType)
|
||||
}
|
||||
if got[i].Passed {
|
||||
t.Fatalf("unknown verifier %d must not pass", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVerifierInterfaceSatisfied — compile-time assertions, re-asserted as
|
||||
// a test so drift is reported in the battery, not just at build.
|
||||
func TestVerifierInterfaceSatisfied(t *testing.T) {
|
||||
var _ Verifier = VerifierFunc(VerifyExitCode)
|
||||
var _ Verifier = VerifierFunc(VerifyUnitTest)
|
||||
var _ Verifier = VerifierFunc(VerifySchema)
|
||||
var _ Verifier = VerifierFunc(VerifyFileExists)
|
||||
var _ Verifier = VerifierFunc(VerifyFileChanged)
|
||||
var _ Verifier = VerifierFunc(VerifyToolReceipt)
|
||||
var _ Verifier = VerifierFunc(VerifyConstraintCheck)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,310 @@
|
||||
package main
|
||||
|
||||
// Battery — the HARD parity gate replaying the frozen oracle truth
|
||||
// (truth.json, generated against the LIVE Python oracle
|
||||
// src/rune/verification_layer.py → Rust evidence_provenance core) VERBATIM.
|
||||
// Every row byte-compares the Go to_dict() json.dumps rendering against the
|
||||
// oracle's, plus explicit field checks for fast divergence diagnosis.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TruthDoc struct {
|
||||
FrozenTS string `json:"frozen_ts"`
|
||||
Generator string `json:"generator"`
|
||||
Rows []TruthRow `json:"rows"`
|
||||
}
|
||||
|
||||
type TruthRow struct {
|
||||
Name string `json:"name"`
|
||||
Fn string `json:"fn"`
|
||||
VerifierType string `json:"verifier_type"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Want json.RawMessage `json:"want"`
|
||||
WantJSON string `json:"want_json"`
|
||||
}
|
||||
|
||||
func loadTruth(t *testing.T) *TruthDoc {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile("truth.json")
|
||||
if err != nil {
|
||||
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
|
||||
}
|
||||
var doc TruthDoc
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
t.Fatalf("parse truth.json: %v", err)
|
||||
}
|
||||
if len(doc.Rows) == 0 {
|
||||
t.Fatal("truth.json contained no rows")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, r := range doc.Rows {
|
||||
if seen[r.Name] {
|
||||
t.Fatalf("duplicate frozen row %q", r.Name)
|
||||
}
|
||||
seen[r.Name] = true
|
||||
}
|
||||
return &doc
|
||||
}
|
||||
|
||||
// runOne — replay one frozen row; returns "" on parity, else the divergence.
|
||||
func runOne(row *TruthRow) string {
|
||||
switch row.Fn {
|
||||
case "handle_hook":
|
||||
result, err := ParseJSON(row.Result)
|
||||
if err != nil {
|
||||
return "parse result: " + err.Error()
|
||||
}
|
||||
ev := result.Get("event")
|
||||
if ev == nil || ev.Kind != KindString {
|
||||
return "hook row without string event"
|
||||
}
|
||||
var got map[string]any
|
||||
if d := result.Get("data"); d != nil && d.Kind != KindNull {
|
||||
got = HandleHook(ev.Str, anyValue(*d))
|
||||
} else {
|
||||
got = HandleHook(ev.Str)
|
||||
}
|
||||
if g := RenderSortedJSON(anyToJSONValue(got)); g != row.WantJSON {
|
||||
return "hook divergence\n go: " + g + "\n oracle: " + row.WantJSON
|
||||
}
|
||||
return ""
|
||||
case "plugin":
|
||||
if g := RenderSortedJSON(anyToJSONValue(Plugin)); g != row.WantJSON {
|
||||
return "plugin divergence\n go: " + g + "\n oracle: " + row.WantJSON
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
result, err := ParseJSON(row.Result)
|
||||
if err != nil {
|
||||
return "parse result: " + err.Error()
|
||||
}
|
||||
var got VerificationResult
|
||||
if row.Fn == "verify" {
|
||||
got = Verify(&result, row.VerifierType)
|
||||
} else {
|
||||
got = verifyNamed(row.Fn, &result)
|
||||
}
|
||||
if got.ToDictJSON() != row.WantJSON {
|
||||
return "to_dict bytes diverge\n go: " + got.ToDictJSON() +
|
||||
"\n oracle: " + row.WantJSON
|
||||
}
|
||||
// explicit field checks (diagnostics)
|
||||
want, err := ParseJSON(row.Want)
|
||||
if err != nil {
|
||||
return "parse want: " + err.Error()
|
||||
}
|
||||
if wb := want.Get("passed"); wb == nil || wb.Bool != got.Passed {
|
||||
return "passed diverges: want " + renderJSON(deref(want.Get("passed")), false) +
|
||||
" got " + strconv.FormatBool(got.Passed)
|
||||
}
|
||||
if wt := want.Get("verifier_type"); wt == nil || wt.Str != got.VerifierType {
|
||||
return "verifier_type diverges"
|
||||
}
|
||||
if wr := want.Get("retryable"); wr == nil || wr.Bool != got.Retryable {
|
||||
return "retryable diverges"
|
||||
}
|
||||
we := want.Get("escalation_reason")
|
||||
switch {
|
||||
case we == nil:
|
||||
return "want missing escalation_reason"
|
||||
case we.Kind == KindNull && got.EscalationReason != nil:
|
||||
return "escalation_reason: want null got " + *got.EscalationReason
|
||||
case we.Kind != KindNull && (got.EscalationReason == nil || *got.EscalationReason != we.Str):
|
||||
return "escalation_reason diverges"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func deref(v *JSONValue) JSONValue {
|
||||
if v == nil {
|
||||
return JSONNull()
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
// verifyNamed — the named verifier wrapper (Python verify_exit_code etc.).
|
||||
func verifyNamed(fn string, result *JSONValue) VerificationResult {
|
||||
switch fn {
|
||||
case "verify_exit_code":
|
||||
return VerifyExitCode(result)
|
||||
case "verify_unit_test":
|
||||
return VerifyUnitTest(result)
|
||||
case "verify_schema":
|
||||
return VerifySchema(result)
|
||||
case "verify_file_exists":
|
||||
return VerifyFileExists(result)
|
||||
case "verify_file_changed":
|
||||
return VerifyFileChanged(result)
|
||||
case "verify_tool_receipt":
|
||||
return VerifyToolReceipt(result)
|
||||
case "verify_constraint_check":
|
||||
return VerifyConstraintCheck(result)
|
||||
default:
|
||||
return Verify(result, fn)
|
||||
}
|
||||
}
|
||||
|
||||
// anyToJSONValue — convert a plain Go value (map[string]any, []any, string,
|
||||
// bool, int, float64, nil) into the ordered JSONValue.
|
||||
func anyToJSONValue(v any) JSONValue {
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
return JSONNull()
|
||||
case bool:
|
||||
return JSONBool(x)
|
||||
case string:
|
||||
return JSONString(x)
|
||||
case int:
|
||||
return JSONValue{Kind: KindNumber, Num: json.Number(strconv.Itoa(x))}
|
||||
case float64:
|
||||
return JSONNumber(x)
|
||||
case []string:
|
||||
arr := make([]JSONValue, len(x))
|
||||
for i, el := range x {
|
||||
arr[i] = JSONString(el)
|
||||
}
|
||||
return JSONArray(arr)
|
||||
case []any:
|
||||
arr := make([]JSONValue, len(x))
|
||||
for i, el := range x {
|
||||
arr[i] = anyToJSONValue(el)
|
||||
}
|
||||
return JSONArray(arr)
|
||||
case map[string]any:
|
||||
keys := make([]string, 0, len(x))
|
||||
for k := range x {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sortStrings(keys)
|
||||
obj := make([]KV, len(keys))
|
||||
for i, k := range keys {
|
||||
obj[i] = KV{Key: k, Value: anyToJSONValue(x[k])}
|
||||
}
|
||||
return JSONObject(obj)
|
||||
default:
|
||||
return JSONString("")
|
||||
}
|
||||
}
|
||||
|
||||
func sortStrings(ss []string) {
|
||||
for i := 1; i < len(ss); i++ {
|
||||
for j := i; j > 0 && ss[j] < ss[j-1]; j-- {
|
||||
ss[j], ss[j-1] = ss[j-1], ss[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// anyValue — JSONValue → plain any (for the ...any hook data seam).
|
||||
func anyValue(v JSONValue) any {
|
||||
switch v.Kind {
|
||||
case KindBool:
|
||||
return v.Bool
|
||||
case KindNumber:
|
||||
f := numF64(v.Num)
|
||||
if f == float64(int64(f)) {
|
||||
return int(f)
|
||||
}
|
||||
return f
|
||||
case KindString:
|
||||
return v.Str
|
||||
case KindArray:
|
||||
arr := make([]any, len(v.Arr))
|
||||
for i, el := range v.Arr {
|
||||
arr[i] = anyValue(el)
|
||||
}
|
||||
return arr
|
||||
case KindObject:
|
||||
obj := map[string]any{}
|
||||
for _, kv := range v.Obj {
|
||||
obj[kv.Key] = anyValue(kv.Value)
|
||||
}
|
||||
return obj
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TestTruthParity — replay EVERY frozen row verbatim.
|
||||
func TestTruthParity(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
for i := range doc.Rows {
|
||||
row := &doc.Rows[i]
|
||||
t.Run(row.Name, func(t *testing.T) {
|
||||
if msg := runOne(row); msg != "" {
|
||||
t.Fatalf("DIVERGED %s: %s", row.Name, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBatteryCoversEveryPublicFunction — the frozen battery must exercise
|
||||
// all 7 verifiers + dispatch (incl. unknown) + hook + plugin, both pass and
|
||||
// fail branches per verifier.
|
||||
func TestBatteryCoversEveryPublicFunction(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
required := []string{
|
||||
"verify_exit_code", "verify_unit_test", "verify_schema",
|
||||
"verify_file_exists", "verify_file_changed", "verify_tool_receipt",
|
||||
"verify_constraint_check", "verify", "handle_hook", "plugin",
|
||||
}
|
||||
have := map[string]int{}
|
||||
for _, r := range doc.Rows {
|
||||
have[r.Fn]++
|
||||
}
|
||||
for _, fn := range required {
|
||||
if have[fn] == 0 {
|
||||
t.Errorf("frozen battery missing rows for %s", fn)
|
||||
}
|
||||
}
|
||||
// dispatch must include the unknown-verifier branch
|
||||
unknown := 0
|
||||
for _, r := range doc.Rows {
|
||||
if r.Fn == "verify" {
|
||||
switch r.VerifierType {
|
||||
case "exit_code", "unit_test", "schema", "file_exists",
|
||||
"file_changed", "tool_receipt", "constraint_check":
|
||||
default:
|
||||
unknown++
|
||||
}
|
||||
}
|
||||
}
|
||||
if unknown == 0 {
|
||||
t.Error("frozen battery missing unknown-verifier dispatch rows")
|
||||
}
|
||||
// every verifier needs at least one pass and one fail row
|
||||
for _, vt := range []string{"exit_code", "unit_test", "schema",
|
||||
"file_exists", "file_changed", "tool_receipt", "constraint_check"} {
|
||||
pass, fail := 0, 0
|
||||
for _, r := range doc.Rows {
|
||||
if r.VerifierType != vt {
|
||||
continue
|
||||
}
|
||||
w, err := ParseJSON(r.Want)
|
||||
if err != nil {
|
||||
t.Fatalf("row %s: %v", r.Name, err)
|
||||
}
|
||||
if w.Get("passed").Bool {
|
||||
pass++
|
||||
} else {
|
||||
fail++
|
||||
}
|
||||
}
|
||||
if pass == 0 || fail == 0 {
|
||||
t.Errorf("verifier %s: battery lacks branch coverage (pass=%d fail=%d)",
|
||||
vt, pass, fail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFixturePresent — the file_exists rows depend on a committed fixture.
|
||||
func TestFixturePresent(t *testing.T) {
|
||||
if _, err := os.Stat(".fixtures/exists.txt"); err != nil {
|
||||
t.Fatalf("committed fixture missing: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.240.0"
|
||||
__version__ = "0.243.1"
|
||||
|
||||
# One build identity (handoff 2026-09-10 §4): the semantic version above is
|
||||
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
|
||||
|
||||
@@ -233,7 +233,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.240.0"' in rune_version,
|
||||
'__version__ = "0.243.1"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
+57
-35
@@ -1,11 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust construction_depth core (facade burn-down).
|
||||
"""cd_loader.py — ctypes loader for the Rust construction_depth core
|
||||
(dichotomy wave #2479 tranche). Mirrors broker_loader.py:
|
||||
|
||||
FFI contract (doc-envelope law):
|
||||
* cd_select_depth({mode, objective, ...}) -> Depth dict
|
||||
* cd_free_string(ptr)
|
||||
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
|
||||
* cd_select_depth({mode, objective, affected_surface, has_migration,
|
||||
breaking, user_locked_layout, external_service,
|
||||
uncertainty}) -> Depth dict
|
||||
|
||||
Free: capability_scoring's cs_free_string (shared process). NEVER re-export
|
||||
a free symbol here. Override the .so with CD_RUST_LIB (a FILE path).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
@@ -16,14 +20,17 @@ from pathlib import Path
|
||||
_REPO = Path(__file__).resolve().parents[2]
|
||||
_CRATE_DIR = _REPO / "src" / "rust" / "construction_depth"
|
||||
_LIB_PATH = Path(
|
||||
os.environ.get(
|
||||
"CD_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconstruction_depth.so"
|
||||
)
|
||||
os.environ.get("CD_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconstruction_depth.so")
|
||||
)
|
||||
|
||||
_lib = None
|
||||
|
||||
|
||||
class ConstructionDepthUnavailable(FileNotFoundError):
|
||||
"""The Rust depth core is not built. Callers fall back to Python
|
||||
(strangler law; RUNE_CD_CORE=python pins the fallback)."""
|
||||
|
||||
|
||||
def available() -> bool:
|
||||
return _LIB_PATH.is_file()
|
||||
|
||||
@@ -32,41 +39,56 @@ def _load() -> ctypes.CDLL:
|
||||
global _lib
|
||||
if _lib is not None:
|
||||
return _lib
|
||||
if not _LIB_PATH.exists():
|
||||
raise FileNotFoundError(f"construction_depth not built: {_LIB_PATH}")
|
||||
if not _LIB_PATH.is_file():
|
||||
raise ConstructionDepthUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
lib.cd_free_string.argtypes = [ctypes.c_void_p]
|
||||
lib.cd_free_string.restype = None
|
||||
fn = lib.cd_select_depth
|
||||
fn.argtypes = [ctypes.c_char_p]
|
||||
fn.restype = ctypes.c_void_p
|
||||
for sym in ("cd_select_depth",):
|
||||
fn = getattr(lib, sym)
|
||||
fn.argtypes = [ctypes.c_void_p]
|
||||
fn.restype = ctypes.c_void_p # char* — never default c_int
|
||||
# free comes from capability_scoring (same process); load it if not yet resident
|
||||
try:
|
||||
lib.cs_free_string
|
||||
except AttributeError:
|
||||
scoring = ctypes.CDLL(str(
|
||||
_REPO / "src" / "rust" / "capability_scoring"
|
||||
/ "target" / "release" / "libcapability_scoring.so"
|
||||
))
|
||||
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
|
||||
lib._free = scoring.cs_free_string
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call(sym: str, payload: dict):
|
||||
lib = _load()
|
||||
ptr = getattr(lib, sym)(json.dumps(payload).encode("utf-8"))
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{sym} returned null")
|
||||
try:
|
||||
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
|
||||
finally:
|
||||
lib.cd_free_string(ptr)
|
||||
env = json.loads(text)
|
||||
if not env.get("ok"):
|
||||
raise RuntimeError(f"{sym} core error: {env.get('err')}")
|
||||
return env["value"]
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
free = getattr(lib, "_free", None)
|
||||
if free is not None:
|
||||
free(ptr)
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
raise RuntimeError(doc.get("err", f"{sym} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def select_depth(mode: str, objective: str = "", has_migration: bool = False,
|
||||
breaking: bool = False, user_locked_layout: bool = False,
|
||||
external_service: bool = False, uncertainty: str = "",
|
||||
affected_surface_len: int = 0) -> dict:
|
||||
return _call("cd_select_depth", {"mode": mode, "objective": objective,
|
||||
"has_migration": has_migration,
|
||||
"breaking": breaking,
|
||||
"user_locked_layout": user_locked_layout,
|
||||
"external_service": external_service,
|
||||
"uncertainty": uncertainty,
|
||||
"affected_surface": [0] * affected_surface_len})
|
||||
def select_depth(mode: str, objective: str = "",
|
||||
affected_surface: list | None = None,
|
||||
has_migration: bool = False,
|
||||
breaking: bool = False,
|
||||
user_locked_layout: bool = False,
|
||||
external_service: bool = False,
|
||||
uncertainty: str = "") -> dict:
|
||||
return _call("cd_select_depth", {
|
||||
"mode": mode, "objective": objective,
|
||||
"affected_surface": list(affected_surface or []),
|
||||
"has_migration": has_migration, "breaking": breaking,
|
||||
"user_locked_layout": user_locked_layout,
|
||||
"external_service": external_service,
|
||||
"uncertainty": uncertainty,
|
||||
})
|
||||
@@ -6,6 +6,14 @@ This reduces connection overhead from ~5ms per connection to ~0.1ms.
|
||||
Usage:
|
||||
connection_pool.py status # Show pool status
|
||||
connection_pool.py clear # Clear pool
|
||||
|
||||
Dichotomy wave #2479 tranche: the deterministic pool BOOKKEEPING (pooling
|
||||
order, stale removal, oldest-first eviction under capacity, status) is
|
||||
ported to FULL Rust (src/rust/connection_pool, parity oracle
|
||||
frozen_cpool_truth.json). The sqlite handles stay in this facade — a
|
||||
`sqlite3.Connection` cannot cross a ctypes FFI. Delegation goes through
|
||||
rune/cpool_loader when built; the Python implementation below is the
|
||||
strangler fallback, pinned with RUNE_CPOOL_CORE=python.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -22,6 +30,21 @@ if _SD not in sys.path:
|
||||
sys.path.insert(0, _SD)
|
||||
|
||||
|
||||
def _core():
|
||||
"""The Rust connection_pool core when available (strangler: Python
|
||||
fallback is load-bearing). RUNE_CPOOL_CORE=python pins the fallback."""
|
||||
if os.environ.get("RUNE_CPOOL_CORE", "auto") == "python":
|
||||
return None
|
||||
try:
|
||||
from rune import cpool_loader
|
||||
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
|
||||
return None
|
||||
try:
|
||||
return cpool_loader if cpool_loader.available() else None
|
||||
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
|
||||
return None
|
||||
|
||||
|
||||
class ConnectionPool:
|
||||
"""Pool of reusable database connections."""
|
||||
|
||||
@@ -29,9 +52,58 @@ class ConnectionPool:
|
||||
self.max_connections = max_connections
|
||||
self.connections: Dict[str, sqlite3.Connection] = {}
|
||||
self.lock = threading.Lock()
|
||||
self._state: Dict | None = None # kernel pool state (core path)
|
||||
|
||||
def _kernel_state(self, core) -> Dict:
|
||||
if self._state is None:
|
||||
self._state = core.new_pool(self.max_connections)
|
||||
return self._state
|
||||
|
||||
def _get_connection_core(self, core, db_path: str) -> sqlite3.Connection:
|
||||
"""Kernel-owned bookkeeping; the sqlite handle stays here."""
|
||||
with self.lock:
|
||||
valid = True
|
||||
if db_path in self.connections:
|
||||
# Check if connection is still valid (oracle probe)
|
||||
try:
|
||||
self.connections[db_path].execute("SELECT 1")
|
||||
except Exception:
|
||||
logger.debug("suppressed exception", exc_info=True)
|
||||
valid = False
|
||||
# oracle order: the stale entry is deleted before the
|
||||
# capacity check, so a re-created connection rejoins at
|
||||
# the END of the pooling order
|
||||
del self.connections[db_path]
|
||||
res = core.begin_get(self._kernel_state(core), db_path, valid)
|
||||
self._state = res["pool"]
|
||||
evict = res.get("evict")
|
||||
if evict is not None:
|
||||
old = self.connections.pop(evict, None)
|
||||
if old is not None:
|
||||
try:
|
||||
old.close()
|
||||
except Exception:
|
||||
logger.debug("suppressed exception", exc_info=True)
|
||||
if res.get("reuse"):
|
||||
return self.connections[db_path]
|
||||
conn = sqlite3.connect(db_path)
|
||||
try:
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
except Exception:
|
||||
conn.close()
|
||||
raise
|
||||
# oracle order: the created connection joins the pool only on
|
||||
# success (kernel commit; eviction already applied in begin_get)
|
||||
self._state = core.commit(self._state, db_path)
|
||||
self.connections[db_path] = conn
|
||||
return conn
|
||||
|
||||
def get_connection(self, db_path: str) -> sqlite3.Connection:
|
||||
"""Get a connection from the pool, creating one if needed."""
|
||||
core = _core()
|
||||
if core is not None:
|
||||
return self._get_connection_core(core, db_path)
|
||||
with self.lock:
|
||||
if db_path in self.connections:
|
||||
# Check if connection is still valid
|
||||
@@ -61,6 +133,7 @@ class ConnectionPool:
|
||||
|
||||
def close_all(self):
|
||||
"""Close all connections in the pool."""
|
||||
core = _core()
|
||||
with self.lock:
|
||||
for conn in self.connections.values():
|
||||
try:
|
||||
@@ -68,9 +141,14 @@ class ConnectionPool:
|
||||
except Exception:
|
||||
logger.debug("suppressed exception", exc_info=True)
|
||||
self.connections.clear()
|
||||
if core is not None:
|
||||
self._state = core.close_all(self._kernel_state(core))
|
||||
|
||||
def get_status(self) -> Dict:
|
||||
"""Get pool status."""
|
||||
core = _core()
|
||||
if core is not None:
|
||||
return core.status(self._kernel_state(core))
|
||||
return {
|
||||
"connections": len(self.connections),
|
||||
"max_connections": self.max_connections,
|
||||
|
||||
@@ -20,6 +20,12 @@ Levels:
|
||||
Reuses existing semantics: construction mode (T10/T11) already encodes
|
||||
most of the signal — depth is DERIVED from mode + modifiers, not a
|
||||
parallel classification.
|
||||
|
||||
Dichotomy wave #2479 tranche: the deterministic depth kernel is ported
|
||||
to FULL Rust (src/rust/construction_depth, parity oracle
|
||||
frozen_cd_truth.json). This facade delegates to the Rust core via
|
||||
rune/cd_loader when built; the Python implementation below is the
|
||||
strangler fallback, pinned with RUNE_CD_CORE=python.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -44,6 +50,22 @@ class Depth:
|
||||
"bypassed": self.bypassed}
|
||||
|
||||
|
||||
def _core():
|
||||
"""The Rust construction_depth core when available (strangler: Python
|
||||
fallback is load-bearing). RUNE_CD_CORE=python pins the fallback."""
|
||||
import os
|
||||
if os.environ.get("RUNE_CD_CORE", "auto") == "python":
|
||||
return None
|
||||
try:
|
||||
from rune import cd_loader
|
||||
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
|
||||
return None
|
||||
try:
|
||||
return cd_loader if cd_loader.available() else None
|
||||
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
|
||||
return None
|
||||
|
||||
|
||||
def select_depth(mode: str, *, objective: str = "",
|
||||
affected_surface: list[str] | None = None,
|
||||
has_migration: bool = False,
|
||||
@@ -53,24 +75,19 @@ def select_depth(mode: str, *, objective: str = "",
|
||||
uncertainty: str = "", # "" | "partial" | "high"
|
||||
project_exists: bool | None = None,
|
||||
) -> Depth:
|
||||
"""Select proportional depth from construction mode + evidence.
|
||||
Delegates to the Rust core when built (frozen_depth.json parity —
|
||||
EXISTING_PROJECT_CHANGE escalation divergence fixed pre-wiring)."""
|
||||
try:
|
||||
from rune import cd_loader as _cd
|
||||
if _cd.available():
|
||||
rec = _cd.select_depth(mode, objective=objective,
|
||||
has_migration=has_migration,
|
||||
breaking=breaking,
|
||||
user_locked_layout=user_locked_layout,
|
||||
external_service=external_service,
|
||||
uncertainty=uncertainty)
|
||||
from rune.construction_depth import Depth
|
||||
return Depth(level=rec["level"], reason=rec["reason"],
|
||||
evidence=rec["evidence"], ceremony=rec["ceremony"],
|
||||
bypassed=rec["bypassed"])
|
||||
except Exception: # noqa: BLE001 — kernel failure → oracle floor below
|
||||
pass
|
||||
"""Select proportional depth from construction mode + evidence."""
|
||||
core = _core()
|
||||
if core is not None:
|
||||
rec = core.select_depth(mode, objective=objective,
|
||||
affected_surface=affected_surface,
|
||||
has_migration=has_migration,
|
||||
breaking=breaking,
|
||||
user_locked_layout=user_locked_layout,
|
||||
external_service=external_service,
|
||||
uncertainty=uncertainty)
|
||||
return Depth(level=rec["level"], reason=rec["reason"],
|
||||
evidence=rec["evidence"], ceremony=rec["ceremony"],
|
||||
bypassed=rec["bypassed"])
|
||||
ev: list[str] = [f"mode: {mode}"]
|
||||
ceremony: list[str] = []
|
||||
bypassed: list[str] = []
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""cpool_loader.py — ctypes loader for the Rust connection_pool core
|
||||
(dichotomy wave #2479 tranche). Mirrors broker_loader.py:
|
||||
|
||||
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
|
||||
* cpool_new({"max_connections"}) -> pool dict
|
||||
* cpool_begin_get({"pool", "db_path", "valid"}) -> {"pool","reuse","evict"}
|
||||
* cpool_commit({"pool", "db_path"}) -> pool dict
|
||||
* cpool_close_all({"pool"}) -> pool dict
|
||||
* cpool_status({"pool"}) -> status dict
|
||||
|
||||
Free: capability_scoring's cs_free_string (shared process). NEVER re-export
|
||||
a free symbol here. Override the .so with CPOOL_RUST_LIB (a FILE path).
|
||||
(The cp_loader.py name is taken by construction_pattern.)
|
||||
"""
|
||||
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" / "connection_pool"
|
||||
_LIB_PATH = Path(
|
||||
os.environ.get("CPOOL_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconnection_pool.so")
|
||||
)
|
||||
|
||||
_lib = None
|
||||
|
||||
|
||||
class ConnectionPoolCoreUnavailable(FileNotFoundError):
|
||||
"""The Rust pool core is not built. Callers fall back to Python
|
||||
(strangler law; RUNE_CPOOL_CORE=python pins the fallback)."""
|
||||
|
||||
|
||||
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 ConnectionPoolCoreUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in ("cpool_new", "cpool_begin_get", "cpool_commit",
|
||||
"cpool_close_all", "cpool_status"):
|
||||
fn = getattr(lib, sym)
|
||||
fn.argtypes = [ctypes.c_void_p]
|
||||
fn.restype = ctypes.c_void_p # char* — never default c_int
|
||||
# free comes from capability_scoring (same process); load it if not yet resident
|
||||
try:
|
||||
lib.cs_free_string
|
||||
except AttributeError:
|
||||
scoring = ctypes.CDLL(str(
|
||||
_REPO / "src" / "rust" / "capability_scoring"
|
||||
/ "target" / "release" / "libcapability_scoring.so"
|
||||
))
|
||||
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
|
||||
lib._free = scoring.cs_free_string
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call(sym: str, payload: dict):
|
||||
lib = _load()
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{sym} returned null")
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
free = getattr(lib, "_free", None)
|
||||
if free is not None:
|
||||
free(ptr)
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
raise RuntimeError(doc.get("err", f"{sym} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def new_pool(max_connections: int) -> dict:
|
||||
return _call("cpool_new", {"max_connections": max_connections})
|
||||
|
||||
|
||||
def begin_get(pool: dict, db_path: str, valid: bool) -> dict:
|
||||
return _call("cpool_begin_get", {"pool": pool, "db_path": db_path,
|
||||
"valid": valid})
|
||||
|
||||
|
||||
def commit(pool: dict, db_path: str) -> dict:
|
||||
return _call("cpool_commit", {"pool": pool, "db_path": db_path})
|
||||
|
||||
|
||||
def close_all(pool: dict) -> dict:
|
||||
return _call("cpool_close_all", {"pool": pool})
|
||||
|
||||
|
||||
def status(pool: dict) -> dict:
|
||||
return _call("cpool_status", {"pool": pool})
|
||||
+73
-2
@@ -23,6 +23,24 @@ from rune.capability_ledger import CapabilityLedger
|
||||
from rune.behaviour_ledger import BehaviourLedger
|
||||
|
||||
|
||||
def _kernel():
|
||||
"""The Rust iteration core (itr_loader) when RUNE_ITR_CORE allows it,
|
||||
else None — every public function delegates there first and falls
|
||||
back to the Python body below (same process, same semantics)."""
|
||||
import os
|
||||
|
||||
if os.environ.get("RUNE_ITR_CORE", "auto") == "python":
|
||||
return None
|
||||
try:
|
||||
from rune import itr_loader
|
||||
|
||||
if itr_loader.available():
|
||||
return itr_loader
|
||||
except Exception: # noqa: BLE001 — kernel failure → Python floor
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
|
||||
@@ -36,6 +54,15 @@ def context_packet(model, ledger: CapabilityLedger, target: str, *,
|
||||
Bounded and proportional: the whole graph is NEVER dumped. Local =
|
||||
the target's own ledger entry; connected = 1-hop structural
|
||||
neighbours with their ledgers on demand."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
return k.context_packet(
|
||||
model.doc, ledger.doc, target, intent=intent,
|
||||
basis_summary=basis_summary,
|
||||
max_local_files=max_local_files, now=_now())
|
||||
except Exception: # noqa: BLE001 — kernel failure → Python floor
|
||||
pass
|
||||
entity = model.get(target)
|
||||
if entity is None:
|
||||
return {"error": f"unknown structural target {target!r}"}
|
||||
@@ -88,6 +115,12 @@ def context_packet(model, ledger: CapabilityLedger, target: str, *,
|
||||
|
||||
def predictive_impact(model, target: str) -> dict:
|
||||
"""What MAY be affected BEFORE the change (structural closure)."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
return k.predictive_impact(model.doc, target)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return model.impact_closure(target)
|
||||
|
||||
|
||||
@@ -96,6 +129,14 @@ def descriptive_impact(model, target: str, *,
|
||||
generation: str = "") -> dict:
|
||||
"""What was MATERIALLY affected AFTER the change — from actual
|
||||
evidence (diff/observations), never assumed from the graph alone."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
return k.descriptive_impact(
|
||||
model.doc, target, observed_changed=observed_changed,
|
||||
generation=generation, now=_now())
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
predicted = model.impact_closure(target)
|
||||
predicted_scope = set(predicted.get("mutation_scope", []))
|
||||
observed = set(observed_changed)
|
||||
@@ -113,10 +154,20 @@ def descriptive_impact(model, target: str, *,
|
||||
def record_impact_delta(project_store: Path, delta: dict) -> Path:
|
||||
"""Persist predicted-vs-observed deltas — they compound into better
|
||||
future impact prediction (rule 19H)."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
line = k.record_delta_line(delta) + "\n"
|
||||
except Exception: # noqa: BLE001
|
||||
line = None
|
||||
else:
|
||||
line = None
|
||||
if line is None:
|
||||
import json
|
||||
line = json.dumps(delta, sort_keys=True) + "\n"
|
||||
p = Path(project_store) / "impact_deltas.jsonl"
|
||||
import json
|
||||
with p.open("a") as f:
|
||||
f.write(json.dumps(delta, sort_keys=True) + "\n")
|
||||
f.write(line)
|
||||
return p
|
||||
|
||||
|
||||
@@ -128,6 +179,15 @@ def invalidate_behaviours(model, behaviours: BehaviourLedger, *,
|
||||
"""Selective invalidation driven by STRUCTURAL truth: behaviours
|
||||
whose dependencies/owner intersect the changed closure reopen;
|
||||
everything else keeps its confidence (rule 19I)."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
behaviours.doc, recs = k.invalidate(
|
||||
behaviours.doc, sorted(changed_entities),
|
||||
generation, _now())
|
||||
return recs
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return behaviours.invalidate_for_change(
|
||||
changed_entities=changed_entities, generation=generation)
|
||||
|
||||
@@ -140,6 +200,17 @@ def post_change_obligations(model, target: str, *,
|
||||
) -> list[dict]:
|
||||
"""Machine-derived follow-up obligations from STRUCTURAL truth
|
||||
(rule 19K) — never an improvised generic checklist."""
|
||||
k = _kernel()
|
||||
if k is not None:
|
||||
try:
|
||||
return k.post_change_obligations(
|
||||
model.doc, target,
|
||||
changed=sorted(changed_entities)
|
||||
if changed_entities else None,
|
||||
behaviours_doc=behaviours.doc if behaviours is not None
|
||||
else None)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
changed = changed_entities or {target}
|
||||
obligations: list[dict] = []
|
||||
seen: set[tuple] = set()
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ctypes loader for the Rust iteration core (prefix ITR — the it_* wire prefix belongs to inference_trace) (dichotomy wave, #2479
|
||||
tranche). Mirrors broker_loader.py's contract:
|
||||
|
||||
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
|
||||
* itr_context_packet({model, ledger, target, intent, basis_summary,
|
||||
max_local_files, now}) -> packet dict
|
||||
* itr_predictive_impact({model, target}) -> closure dict
|
||||
* itr_descriptive_impact({model, target, observed_changed,
|
||||
generation, now}) -> delta dict
|
||||
* itr_record_delta_line({delta}) -> line body
|
||||
* itr_invalidate({behaviours, changed, generation, now}) -> {doc, recs}
|
||||
* itr_post_change_obligations({model, target, changed, behaviours})
|
||||
-> [obligations]
|
||||
|
||||
Model/ledger/behaviours ride as their JSON docs; timestamps are facade
|
||||
ARGUMENTS (now). Free: capability_scoring's cs_free_string (shared
|
||||
process). Override the .so with ITR_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" / "iteration"
|
||||
_LIB_PATH = Path(
|
||||
os.environ.get("ITR_RUST_LIB", _CRATE_DIR / "target" / "release" / "libiteration.so")
|
||||
)
|
||||
|
||||
_ERR_TYPES = {
|
||||
"ValueError": ValueError,
|
||||
"KeyError": KeyError,
|
||||
"AttributeError": AttributeError,
|
||||
"TypeError": TypeError,
|
||||
}
|
||||
|
||||
_lib = None
|
||||
|
||||
|
||||
class IterationCoreUnavailable(FileNotFoundError):
|
||||
"""The Rust iteration core (prefix ITR — the it_* wire prefix belongs to inference_trace) 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 IterationCoreUnavailable(str(_LIB_PATH))
|
||||
lib = ctypes.CDLL(str(_LIB_PATH))
|
||||
for sym in ("itr_context_packet", "itr_predictive_impact",
|
||||
"itr_descriptive_impact", "itr_record_delta_line",
|
||||
"itr_invalidate", "itr_post_change_obligations"):
|
||||
getattr(lib, sym).argtypes = [ctypes.c_void_p]
|
||||
getattr(lib, sym).restype = ctypes.c_void_p
|
||||
try:
|
||||
lib.cs_free_string
|
||||
except AttributeError:
|
||||
scoring = ctypes.CDLL(str(
|
||||
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
|
||||
))
|
||||
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
|
||||
lib._free = scoring.cs_free_string
|
||||
_lib = lib
|
||||
return lib
|
||||
|
||||
|
||||
def _call(sym: str, payload: dict):
|
||||
lib = _load()
|
||||
data = json.dumps(payload).encode()
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
|
||||
if not ptr:
|
||||
raise RuntimeError(f"{sym} returned null")
|
||||
out = ctypes.string_at(ptr).decode()
|
||||
free = getattr(lib, "_free", None)
|
||||
if free is not None:
|
||||
free(ptr)
|
||||
doc = json.loads(out)
|
||||
if not doc.get("ok"):
|
||||
etype = doc.get("type", "RuntimeError")
|
||||
raise _ERR_TYPES.get(etype, RuntimeError)(doc.get("err", f"{sym} failed"))
|
||||
return doc["value"]
|
||||
|
||||
|
||||
def context_packet(model_doc: dict, ledger_doc: dict, target: str, *,
|
||||
intent: str = "", basis_summary=None,
|
||||
max_local_files: int = 12, now: str) -> dict:
|
||||
return _call("itr_context_packet", {
|
||||
"model": model_doc, "ledger": ledger_doc, "target": target,
|
||||
"intent": intent, "basis_summary": basis_summary,
|
||||
"max_local_files": max_local_files, "now": now})
|
||||
|
||||
|
||||
def predictive_impact(model_doc: dict, target: str) -> dict:
|
||||
return _call("itr_predictive_impact", {"model": model_doc, "target": target})
|
||||
|
||||
|
||||
def descriptive_impact(model_doc: dict, target: str, *,
|
||||
observed_changed: list, generation: str = "",
|
||||
now: str) -> dict:
|
||||
return _call("itr_descriptive_impact", {
|
||||
"model": model_doc, "target": target,
|
||||
"observed_changed": observed_changed, "generation": generation,
|
||||
"now": now})
|
||||
|
||||
|
||||
def record_delta_line(delta: dict) -> str:
|
||||
return _call("itr_record_delta_line", {"delta": delta})
|
||||
|
||||
|
||||
def invalidate(behaviours_doc: dict, changed: list, generation: str,
|
||||
now: str) -> tuple[dict, list]:
|
||||
out = _call("itr_invalidate", {"behaviours": behaviours_doc,
|
||||
"changed": changed,
|
||||
"generation": generation, "now": now})
|
||||
return out["doc"], out["recs"]
|
||||
|
||||
|
||||
def post_change_obligations(model_doc: dict, target: str, *,
|
||||
changed=None, behaviours_doc=None) -> list:
|
||||
return _call("itr_post_change_obligations", {
|
||||
"model": model_doc, "target": target, "changed": changed,
|
||||
"behaviours": behaviours_doc})
|
||||
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
|
||||
|
||||
This is not an interface. This is how I see, speak, and interact with the world.
|
||||
|
||||
Version: v0.240.0
|
||||
Version: v0.243.1
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -2037,7 +2037,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.240.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.243.1 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "capability_scoring"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "connection_pool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"capability_scoring",
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "connection_pool"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Database connection-pool bookkeeping kernel — dichotomy wave #2479 tranche; deterministic pool state machine in Rust (sqlite handles stay in the Python facade); zero-dep (pyjson via capability_scoring)"
|
||||
|
||||
[lib]
|
||||
name = "connection_pool"
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
capability_scoring = { path = "../capability_scoring" }
|
||||
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"module": "connection_pool",
|
||||
"pin": "RUNE_CPOOL_CORE=python",
|
||||
"frozen_at": "2026-10-12T17:26:19Z",
|
||||
"cases": [
|
||||
{
|
||||
"fn": "cpool_new",
|
||||
"args": {
|
||||
"max_connections": 2
|
||||
},
|
||||
"expected": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/A.db",
|
||||
"valid": true
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
},
|
||||
"reuse": false,
|
||||
"evict": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_commit",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/A.db"
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/A.db",
|
||||
"valid": true
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
},
|
||||
"reuse": true,
|
||||
"evict": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/B.db",
|
||||
"valid": true
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
},
|
||||
"reuse": false,
|
||||
"evict": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_commit",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/B.db"
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db",
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db",
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/C.db",
|
||||
"valid": true
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
},
|
||||
"reuse": false,
|
||||
"evict": "/tmp/tmpdp4ybr6n/A.db"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_commit",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/C.db"
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db",
|
||||
"/tmp/tmpdp4ybr6n/C.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db",
|
||||
"/tmp/tmpdp4ybr6n/C.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/B.db",
|
||||
"valid": false
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/C.db"
|
||||
]
|
||||
},
|
||||
"reuse": false,
|
||||
"evict": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_commit",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/C.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/B.db"
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/C.db",
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/C.db",
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/D.db",
|
||||
"valid": true
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
},
|
||||
"reuse": false,
|
||||
"evict": "/tmp/tmpdp4ybr6n/C.db"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_commit",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db"
|
||||
]
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/D.db"
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db",
|
||||
"/tmp/tmpdp4ybr6n/D.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_status",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db",
|
||||
"/tmp/tmpdp4ybr6n/D.db"
|
||||
]
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"connections": 2,
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db",
|
||||
"/tmp/tmpdp4ybr6n/D.db"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_close_all",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/B.db",
|
||||
"/tmp/tmpdp4ybr6n/D.db"
|
||||
]
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_begin_get",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/A.db",
|
||||
"valid": true
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
},
|
||||
"reuse": false,
|
||||
"evict": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"fn": "cpool_commit",
|
||||
"args": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": []
|
||||
},
|
||||
"db_path": "/tmp/tmpdp4ybr6n/A.db"
|
||||
},
|
||||
"expected": {
|
||||
"pool": {
|
||||
"max_connections": 2,
|
||||
"databases": [
|
||||
"/tmp/tmpdp4ybr6n/A.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//! branch_battery — replays frozen_cpool_truth.json VERBATIM (the live
|
||||
//! Python oracle, frozen with RUNE_CPOOL_CORE=python pinned against a real
|
||||
//! sqlite-backed pool). Every case must reproduce byte-identically through
|
||||
//! the Rust kernel.
|
||||
|
||||
use super::{pyjson, Action, begin_get, close_all, commit, new_pool, status, Json};
|
||||
|
||||
const TRUTH: &str = include_str!("../frozen_cpool_truth.json");
|
||||
|
||||
#[test]
|
||||
fn frozen_truth_replays_verbatim() {
|
||||
let doc = pyjson::parse(TRUTH).expect("frozen_cpool_truth.json parses");
|
||||
let cases = match doc.get("cases") {
|
||||
Some(Json::Arr(a)) => a,
|
||||
_ => panic!("truth has no cases array"),
|
||||
};
|
||||
assert!(!cases.is_empty(), "truth is empty");
|
||||
for (i, case) in cases.iter().enumerate() {
|
||||
let fname = match case.get("fn") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => panic!("case {i}: missing fn"),
|
||||
};
|
||||
let args = case.get("args").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
let expected = case.get("expected").cloned().unwrap_or(Json::Null);
|
||||
let got = match fname {
|
||||
"cpool_new" => {
|
||||
let max = match args.get("max_connections") {
|
||||
Some(Json::Num(n)) => *n as i64,
|
||||
_ => 10,
|
||||
};
|
||||
new_pool(max)
|
||||
}
|
||||
"cpool_begin_get" => {
|
||||
let pool = args.get("pool").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
let db_path = match args.get("db_path") {
|
||||
Some(Json::Str(s)) => s.as_str().to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let valid = matches!(args.get("valid"), Some(Json::Bool(true)));
|
||||
let (pool, action) = begin_get(&pool, &db_path, valid);
|
||||
let (reuse, evict) = match action {
|
||||
Action::Reuse => (true, None),
|
||||
Action::Create { evict } => (false, evict),
|
||||
};
|
||||
Json::Obj(vec![
|
||||
("pool".into(), pool),
|
||||
("reuse".into(), Json::Bool(reuse)),
|
||||
(
|
||||
"evict".into(),
|
||||
match evict {
|
||||
Some(p) => Json::Str(p),
|
||||
None => Json::Null,
|
||||
},
|
||||
),
|
||||
])
|
||||
}
|
||||
"cpool_commit" => {
|
||||
let pool = args.get("pool").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
let db_path = match args.get("db_path") {
|
||||
Some(Json::Str(s)) => s.as_str().to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
Json::Obj(vec![("pool".into(), commit(&pool, &db_path))])
|
||||
}
|
||||
"cpool_close_all" => {
|
||||
let pool = args.get("pool").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
close_all(&pool)
|
||||
}
|
||||
"cpool_status" => {
|
||||
let pool = args.get("pool").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
status(&pool)
|
||||
}
|
||||
other => panic!("case {i}: unknown fn {other}"),
|
||||
};
|
||||
assert_eq!(got, expected, "case {i} ({fname}) diverged from the oracle");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truth_pin_is_python() {
|
||||
let doc = pyjson::parse(TRUTH).expect("frozen_cpool_truth.json parses");
|
||||
assert_eq!(
|
||||
doc.get("pin"),
|
||||
Some(&Json::Str("RUNE_CPOOL_CORE=python".into())),
|
||||
"truth must be frozen from the pinned Python oracle"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
//! connection_pool — database connection-pool BOOKKEEPING kernel
|
||||
//! (dichotomy wave #2479 tranche). RUST verdict per the programme batch:
|
||||
//! the deterministic pool state machine (pooling order, stale removal,
|
||||
//! oldest-first eviction under capacity, status projection) lives here;
|
||||
//! the sqlite handles themselves stay in the Python facade (storage-backed
|
||||
//! surface — a `sqlite3.Connection` cannot cross a ctypes FFI).
|
||||
//!
|
||||
//! Kernel state (the pool dict, oracle `ConnectionPool` shape):
|
||||
//! {"max_connections": int, "databases": [db_path in insertion order]}
|
||||
//!
|
||||
//! FFI contract (doc-envelope law — every call returns {"ok","value"|"err"}):
|
||||
//!
|
||||
//! * cpool_new({"max_connections": int}) -> pool dict
|
||||
//! * cpool_begin_get({"pool": {...}, "db_path": str, "valid": bool})
|
||||
//! -> {"pool", "reuse", "evict"} — oracle get_connection phase 1: valid
|
||||
//! reuse short-circuits; stale entries are removed; the oldest entry is
|
||||
//! evicted when at capacity (BEFORE the new connection is created —
|
||||
//! oracle order).
|
||||
//! * cpool_commit({"pool": {...}, "db_path": str}) -> pool dict — oracle
|
||||
//! get_connection phase 2: the created connection joins the pool. The
|
||||
//! facade commits only after sqlite3.connect succeeded, so a failed
|
||||
//! create leaves the pool exactly as the oracle leaves it.
|
||||
//! * cpool_close_all({"pool": {...}}) -> pool dict
|
||||
//! * cpool_status({"pool": {...}}) -> status dict
|
||||
//!
|
||||
//! Parity oracle: frozen_cpool_truth.json (frozen from the live Python
|
||||
//! oracle with RUNE_CPOOL_CORE=python pinned).
|
||||
|
||||
pub use capability_scoring::pyjson;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "branch_battery.rs"]
|
||||
mod branch_battery;
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use pyjson::Json;
|
||||
|
||||
fn pool_max(pool: &Json) -> i64 {
|
||||
match pool.get("max_connections") {
|
||||
Some(Json::Num(n)) => *n as i64,
|
||||
_ => 10, // oracle default (ConnectionPool.__init__)
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrowed view of the pooling order — zero-copy scan of the pool dict.
|
||||
fn pool_dbs(pool: &Json) -> Vec<&str> {
|
||||
match pool.get("databases") {
|
||||
Some(Json::Arr(xs)) => xs
|
||||
.iter()
|
||||
.filter_map(|v| match v {
|
||||
Json::Str(s) => Some(s.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn pool_json(max: i64, dbs: Vec<&str>) -> Json {
|
||||
Json::Obj(vec![
|
||||
("max_connections".into(), Json::Num(max as f64)),
|
||||
(
|
||||
"databases".into(),
|
||||
Json::Arr(dbs.into_iter().map(|s| Json::Str(s.to_string())).collect()),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
/// The oracle's get_connection decision (phase 1 outcome).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Action {
|
||||
/// pooled, still-valid connection returned as-is
|
||||
Reuse,
|
||||
/// a new sqlite handle must be created; the oracle evicted this
|
||||
/// oldest entry first (None when under capacity)
|
||||
Create { evict: Option<String> },
|
||||
}
|
||||
|
||||
/// cpool_new({"max_connections": int}) -> pool dict
|
||||
pub fn new_pool(max_connections: i64) -> Json {
|
||||
pool_json(max_connections, vec![])
|
||||
}
|
||||
|
||||
/// Oracle get_connection phase 1: reuse decision + stale removal +
|
||||
/// oldest-first capacity eviction.
|
||||
pub fn begin_get(pool: &Json, db_path: &str, valid: bool) -> (Json, Action) {
|
||||
let max = pool_max(pool);
|
||||
let dbs = pool_dbs(pool);
|
||||
if let Some(pos) = dbs.iter().position(|p| *p == db_path) {
|
||||
if valid {
|
||||
// oracle: still-valid pooled connection is returned as-is
|
||||
return (pool.clone(), Action::Reuse);
|
||||
}
|
||||
// oracle: stale connection is dropped before the capacity check
|
||||
let kept: Vec<&str> = dbs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| *i != pos)
|
||||
.map(|(_, s)| *s)
|
||||
.collect();
|
||||
let (pool, evict) = evict_or_keep(kept, max);
|
||||
return (pool, Action::Create { evict });
|
||||
}
|
||||
let (pool, evict) = evict_or_keep(dbs, max);
|
||||
(pool, Action::Create { evict })
|
||||
}
|
||||
|
||||
/// Oracle capacity rule: at capacity the OLDEST connection (insertion
|
||||
/// order) is removed before the new one is created. Returns the new pool
|
||||
/// state plus the evicted db_path (the facade closes that handle).
|
||||
fn evict_or_keep(dbs: Vec<&str>, max: i64) -> (Json, Option<String>) {
|
||||
let mut kept = dbs;
|
||||
let evict = if kept.len() as i64 >= max && !kept.is_empty() {
|
||||
Some(kept.remove(0).to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(pool_json(max, kept), evict)
|
||||
}
|
||||
|
||||
/// Oracle get_connection phase 2: the created connection joins the pool.
|
||||
pub fn commit(pool: &Json, db_path: &str) -> Json {
|
||||
let max = pool_max(pool);
|
||||
let mut dbs = pool_dbs(pool);
|
||||
dbs.push(db_path);
|
||||
pool_json(max, dbs)
|
||||
}
|
||||
|
||||
/// Oracle close_all: the pool empties (max_connections is retained).
|
||||
pub fn close_all(pool: &Json) -> Json {
|
||||
pool_json(pool_max(pool), vec![])
|
||||
}
|
||||
|
||||
/// Oracle get_status: {"connections", "max_connections", "databases"}.
|
||||
pub fn status(pool: &Json) -> Json {
|
||||
let dbs = pool_dbs(pool);
|
||||
Json::Obj(vec![
|
||||
("connections".into(), Json::Num(dbs.len() as f64)),
|
||||
(
|
||||
"max_connections".into(),
|
||||
Json::Num(pool_max(pool) as f64),
|
||||
),
|
||||
(
|
||||
"databases".into(),
|
||||
Json::Arr(dbs.into_iter().map(|s| Json::Str(s.to_string())).collect()),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
// ── FFI surface ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn read_arg(ptr: *const c_char) -> Result<Json, String> {
|
||||
if ptr.is_null() {
|
||||
return Err("null argument pointer".into());
|
||||
}
|
||||
let x = unsafe { CStr::from_ptr(ptr) }
|
||||
.to_str()
|
||||
.map_err(|e| e.to_string())?;
|
||||
pyjson::parse(x)
|
||||
}
|
||||
|
||||
fn envelope(r: Result<Json, String>) -> *mut c_char {
|
||||
let v = match r {
|
||||
Ok(x) => Json::Obj(vec![
|
||||
("ok".into(), Json::Bool(true)),
|
||||
("value".into(), x),
|
||||
]),
|
||||
Err(e) => Json::Obj(vec![
|
||||
("ok".into(), Json::Bool(false)),
|
||||
("err".into(), Json::Str(e)),
|
||||
]),
|
||||
};
|
||||
CString::new(pyjson::dumps(&v))
|
||||
.map(|c| c.into_raw())
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
fn pool_arg(v: &Json) -> Json {
|
||||
match v.get("pool") {
|
||||
Some(x @ Json::Obj(_)) => x.clone(),
|
||||
_ => Json::Obj(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
/// cpool_new({"max_connections": int}) -> pool dict
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cpool_new(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let max = match a.get("max_connections") {
|
||||
Some(Json::Num(n)) => *n as i64,
|
||||
_ => 10,
|
||||
};
|
||||
new_pool(max)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cpool_begin_get({"pool", "db_path", "valid"}) -> {"pool","reuse","evict"}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cpool_begin_get(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let pool = pool_arg(&a);
|
||||
let db_path = match a.get("db_path") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
let valid = matches!(a.get("valid"), Some(Json::Bool(true)));
|
||||
let (pool, action) = begin_get(&pool, db_path, valid);
|
||||
let (reuse, evict) = match action {
|
||||
Action::Reuse => (true, None),
|
||||
Action::Create { evict } => (false, evict),
|
||||
};
|
||||
Json::Obj(vec![
|
||||
("pool".into(), pool),
|
||||
("reuse".into(), Json::Bool(reuse)),
|
||||
(
|
||||
"evict".into(),
|
||||
match evict {
|
||||
Some(p) => Json::Str(p),
|
||||
None => Json::Null,
|
||||
},
|
||||
),
|
||||
])
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cpool_commit({"pool", "db_path"}) -> pool dict
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cpool_commit(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let pool = pool_arg(&a);
|
||||
let db_path = match a.get("db_path") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
commit(&pool, db_path)
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cpool_close_all({"pool"}) -> pool dict
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cpool_close_all(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| close_all(&pool_arg(&a)));
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
/// cpool_status({"pool"}) -> status dict
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cpool_status(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| status(&pool_arg(&a)));
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
// NOTE: no free symbol is exported here — capability_scoring (linked into
|
||||
// the same Python process via rune/cs_loader) owns cs_free_string; a
|
||||
// duplicate free symbol breaks the cdylib link. Callers free every
|
||||
// returned pointer through that path.
|
||||
Generated
+5
-98
@@ -2,106 +2,13 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "capability_scoring"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "construction_depth"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"capability_scoring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1741ab7a6cc54a03a89b5d563ed60075c277d9e3cfa73ad0c1f23f23974703c6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -2,10 +2,11 @@
|
||||
name = "construction_depth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Proportional scaffolding depth selection — dichotomy wave #2479 tranche; deterministic depth kernel in Rust; zero-dep (pyjson via capability_scoring)"
|
||||
|
||||
[lib]
|
||||
name = "construction_depth"
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
capability_scoring = { path = "../capability_scoring" }
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
"""canary_construction_depth.py — parity battery for construction_depth."""
|
||||
"""canary_construction_depth.py — live parity canary for construction_depth
|
||||
(dichotomy wave #2479 tranche): replays the frozen oracle truth through the
|
||||
Rust core (RUNE_CD_CORE auto) against the frozen wants, then checks the
|
||||
facade's pinned-Python path agrees."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -20,6 +24,11 @@ def test(name, cond, detail=""):
|
||||
print(f" ✗ {name} — {detail}")
|
||||
|
||||
|
||||
def run_select(select_args):
|
||||
from rune.construction_depth import select_depth
|
||||
return select_depth(**select_args).to_dict()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
r = subprocess.run(["cargo", "build", "--release"], cwd=CRATE_DIR,
|
||||
capture_output=True, text=True)
|
||||
@@ -30,17 +39,31 @@ def main() -> int:
|
||||
sys.path.insert(0, str(CRATE_DIR.parents[2] / "src"))
|
||||
sys.path.insert(0, str(CRATE_DIR))
|
||||
from rune import cd_loader # noqa: E402
|
||||
frozen = json.loads((CRATE_DIR / "frozen_depth.json").read_text())
|
||||
for i, row in enumerate(frozen["rows"]):
|
||||
f = row["flags"]
|
||||
got = cd_loader.select_depth(row["mode"], row["objective"],
|
||||
f.get("has_migration", False),
|
||||
f.get("breaking", False),
|
||||
f.get("user_locked_layout", False),
|
||||
f.get("external_service", False))
|
||||
want = row["want"]
|
||||
test(f"row {i} {row['mode']}", got.get("level") == want.get("level"),
|
||||
f"got={got.get('level')} want={want.get('level')}")
|
||||
if not cd_loader.available():
|
||||
print(" ✗ Rust core not built")
|
||||
return 1
|
||||
truth = json.loads((CRATE_DIR / "frozen_cd_truth.json").read_text())
|
||||
for i, case in enumerate(truth["cases"]):
|
||||
if case["fn"] == "select_depth":
|
||||
select_args = case["args"]
|
||||
elif case["fn"] == "to_dict":
|
||||
select_args = case["args"]["select"]
|
||||
else:
|
||||
continue
|
||||
want = case["expected"]
|
||||
got = run_select(select_args)
|
||||
test(f"case {i} rust-core parity", got == want,
|
||||
f"got={json.dumps(got)[:120]} want={json.dumps(want)[:120]}")
|
||||
# pinned-Python floor agrees with the same frozen wants
|
||||
os.environ["RUNE_CD_CORE"] = "python"
|
||||
for i, case in enumerate(truth["cases"]):
|
||||
if case["fn"] not in ("select_depth", "to_dict"):
|
||||
continue
|
||||
select_args = (case["args"] if case["fn"] == "select_depth"
|
||||
else case["args"]["select"])
|
||||
got = run_select(select_args)
|
||||
test(f"case {i} python-pin parity", got == case["expected"],
|
||||
f"got={json.dumps(got)[:120]}")
|
||||
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
|
||||
return 1 if FAIL else 0
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,123 +0,0 @@
|
||||
{
|
||||
"_doc": "FROZEN ORACLE (construction_depth wiring).",
|
||||
"rows": [
|
||||
{
|
||||
"mode": "NOT_CONSTRUCTION",
|
||||
"objective": "",
|
||||
"flags": {},
|
||||
"want": {
|
||||
"level": "NONE",
|
||||
"reason": "not construction \u2014 no construction depth applies",
|
||||
"evidence": [
|
||||
"mode: NOT_CONSTRUCTION"
|
||||
],
|
||||
"ceremony": [],
|
||||
"bypassed": [
|
||||
"all"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "UNKNOWN",
|
||||
"objective": "",
|
||||
"flags": {},
|
||||
"want": {
|
||||
"level": "UNRESOLVED",
|
||||
"reason": "UNKNOWN \u2014 probe/clarify before any construction lifecycle begins",
|
||||
"evidence": [
|
||||
"mode: UNKNOWN"
|
||||
],
|
||||
"ceremony": [
|
||||
"probe/clarify"
|
||||
],
|
||||
"bypassed": [
|
||||
"contract",
|
||||
"patterns",
|
||||
"bootstrap",
|
||||
"depth selection"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "LOCAL_CHANGE",
|
||||
"objective": "fix typo",
|
||||
"flags": {},
|
||||
"want": {
|
||||
"level": "LIGHT",
|
||||
"reason": "bounded local fix \u2014 governance without ceremony",
|
||||
"evidence": [
|
||||
"mode: LOCAL_CHANGE",
|
||||
"LOCAL_CHANGE: bounded scope, minimal ceremony"
|
||||
],
|
||||
"ceremony": [
|
||||
"classification",
|
||||
"authority/scope",
|
||||
"focused verification",
|
||||
"durable result"
|
||||
],
|
||||
"bypassed": [
|
||||
"requirements/completeness",
|
||||
"patterns",
|
||||
"capability/toolchain check",
|
||||
"project bootstrap",
|
||||
"deployment/rollback"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "EXISTING_PROJECT_CHANGE",
|
||||
"objective": "add feature",
|
||||
"flags": {
|
||||
"has_migration": true
|
||||
},
|
||||
"want": {
|
||||
"level": "STANDARD",
|
||||
"reason": "existing-project change \u2014 proportional governance",
|
||||
"evidence": [
|
||||
"mode: EXISTING_PROJECT_CHANGE",
|
||||
"migration: old/new compatibility + rollback obligations"
|
||||
],
|
||||
"ceremony": [
|
||||
"classification",
|
||||
"requirements/completeness",
|
||||
"capability fit",
|
||||
"Construction Contract",
|
||||
"scope",
|
||||
"obligations",
|
||||
"verification",
|
||||
"migration proof",
|
||||
"rollback preparation"
|
||||
],
|
||||
"bypassed": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"mode": "PROJECT_BUILD",
|
||||
"objective": "new app",
|
||||
"flags": {
|
||||
"breaking": true,
|
||||
"user_locked_layout": true
|
||||
},
|
||||
"want": {
|
||||
"level": "FULL",
|
||||
"reason": "genuinely new project \u2014 full Scaffolding lifecycle",
|
||||
"evidence": [
|
||||
"mode: PROJECT_BUILD",
|
||||
"PROJECT_BUILD: full lifecycle required"
|
||||
],
|
||||
"ceremony": [
|
||||
"project bootstrap",
|
||||
"requirements/completeness",
|
||||
"capability/toolchain decisions",
|
||||
"patterns/obligations",
|
||||
"Construction Contract",
|
||||
"implementation",
|
||||
"verification",
|
||||
"deployment/rollback strategy",
|
||||
"maintenance lifecycle"
|
||||
],
|
||||
"bypassed": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! branch_battery — replays frozen_cd_truth.json VERBATIM (the live
|
||||
//! Python oracle, frozen with RUNE_CD_CORE=python pinned). Every case in
|
||||
//! the file must reproduce byte-identically through the Rust kernel.
|
||||
|
||||
use super::{pyjson, select_depth, Json};
|
||||
|
||||
const TRUTH: &str = include_str!("../frozen_cd_truth.json");
|
||||
|
||||
fn run_select(args: &Json) -> Json {
|
||||
let mode = match args.get("mode") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
let objective = match args.get("objective") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
let surface_len = match args.get("affected_surface") {
|
||||
Some(Json::Arr(xs)) => xs.len(),
|
||||
_ => 0,
|
||||
};
|
||||
let flag = |k: &str| matches!(args.get(k), Some(Json::Bool(true)));
|
||||
let uncertainty = match args.get("uncertainty") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
select_depth(
|
||||
mode,
|
||||
objective,
|
||||
surface_len,
|
||||
flag("has_migration"),
|
||||
flag("breaking"),
|
||||
flag("user_locked_layout"),
|
||||
flag("external_service"),
|
||||
uncertainty,
|
||||
)
|
||||
.to_json()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_truth_replays_verbatim() {
|
||||
let doc = pyjson::parse(TRUTH).expect("frozen_cd_truth.json parses");
|
||||
let cases = match doc.get("cases") {
|
||||
Some(Json::Arr(a)) => a,
|
||||
_ => panic!("truth has no cases array"),
|
||||
};
|
||||
assert!(!cases.is_empty(), "truth is empty");
|
||||
for (i, case) in cases.iter().enumerate() {
|
||||
let fname = match case.get("fn") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => panic!("case {i}: missing fn"),
|
||||
};
|
||||
let expected = case.get("expected").cloned().unwrap_or(Json::Null);
|
||||
let got = match fname {
|
||||
"select_depth" => {
|
||||
let args = case.get("args").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
run_select(&args)
|
||||
}
|
||||
"to_dict" => {
|
||||
let args = case.get("args").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
let sel = args.get("select").cloned().unwrap_or(Json::Obj(vec![]));
|
||||
run_select(&sel)
|
||||
}
|
||||
other => panic!("case {i}: unknown fn {other}"),
|
||||
};
|
||||
assert_eq!(got, expected, "case {i} ({fname}) diverged from the oracle");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truth_pin_is_python() {
|
||||
let doc = pyjson::parse(TRUTH).expect("frozen_cd_truth.json parses");
|
||||
assert_eq!(
|
||||
doc.get("pin"),
|
||||
Some(&Json::Str("RUNE_CD_CORE=python".into())),
|
||||
"truth must be frozen from the pinned Python oracle"
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,34 @@
|
||||
//! construction_depth — proportional scaffolding depth selection (W6).
|
||||
//! PARITY port of src/rune/construction_depth.py.
|
||||
//! construction_depth — proportional scaffolding depth selection
|
||||
//! (dichotomy wave #2479 tranche, contract cited in the parity commit).
|
||||
//! RUST verdict: deterministic depth-selection kernel in Rust; the Python
|
||||
//! facade keeps a RUNE_CD_CORE=python pin.
|
||||
//!
|
||||
//! FFI contract (doc-envelope law — every call returns {"ok","value"|"err"}):
|
||||
//!
|
||||
//! * cd_select_depth({"mode": str, "objective": str, "affected_surface":
|
||||
//! [str...], "has_migration": bool, "breaking": bool,
|
||||
//! "user_locked_layout": bool, "external_service": bool,
|
||||
//! "uncertainty": str})
|
||||
//! -> Depth dict {"level","reason","evidence","ceremony","bypassed"}
|
||||
//!
|
||||
//! Parity oracle: frozen_cd_truth.json (frozen from the live Python
|
||||
//! oracle with RUNE_CD_CORE=python pinned).
|
||||
|
||||
pub use capability_scoring::pyjson;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "branch_battery.rs"]
|
||||
mod branch_battery;
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use pyjson::Json;
|
||||
|
||||
pub const LEVELS: &[&str] = &["NONE", "UNRESOLVED", "LIGHT", "STANDARD", "FULL"];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(serde::Serialize)]
|
||||
/// The Depth record (oracle construction_depth.Depth).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Depth {
|
||||
pub level: String,
|
||||
pub reason: String,
|
||||
@@ -13,333 +37,290 @@ pub struct Depth {
|
||||
pub bypassed: Vec<String>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)] // pinned: parity signature — the port mirrors the oracle arg-for-arg
|
||||
impl Depth {
|
||||
pub fn to_json(&self) -> Json {
|
||||
Json::Obj(vec![
|
||||
("level".into(), Json::Str(self.level.clone())),
|
||||
("reason".into(), Json::Str(self.reason.clone())),
|
||||
(
|
||||
"evidence".into(),
|
||||
Json::Arr(self.evidence.iter().map(|s| Json::Str(s.clone())).collect()),
|
||||
),
|
||||
(
|
||||
"ceremony".into(),
|
||||
Json::Arr(self.ceremony.iter().map(|s| Json::Str(s.clone())).collect()),
|
||||
),
|
||||
(
|
||||
"bypassed".into(),
|
||||
Json::Arr(self.bypassed.iter().map(|s| Json::Str(s.clone())).collect()),
|
||||
),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// The five depth levels (oracle LEVELS).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Level {
|
||||
None,
|
||||
Unresolved,
|
||||
Light,
|
||||
Standard,
|
||||
Full,
|
||||
}
|
||||
|
||||
impl Level {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Level::None => "NONE",
|
||||
Level::Unresolved => "UNRESOLVED",
|
||||
Level::Light => "LIGHT",
|
||||
Level::Standard => "STANDARD",
|
||||
Level::Full => "FULL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The construction modes the kernel discriminates. Any non-canonical mode
|
||||
/// string falls through to the EXISTING_PROJECT_CHANGE path (oracle
|
||||
/// behavior: the default branch is the escalation path).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
NotConstruction,
|
||||
Unknown,
|
||||
LocalChange,
|
||||
ProjectBuild,
|
||||
ExistingProjectChange,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"NOT_CONSTRUCTION" => Self::NotConstruction,
|
||||
"UNKNOWN" => Self::Unknown,
|
||||
"LOCAL_CHANGE" => Self::LocalChange,
|
||||
"PROJECT_BUILD" => Self::ProjectBuild,
|
||||
_ => Self::ExistingProjectChange,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LOCAL_CEREMONY: [&str; 4] = [
|
||||
"classification",
|
||||
"authority/scope",
|
||||
"focused verification",
|
||||
"durable result",
|
||||
];
|
||||
const LOCAL_BYPASSED: [&str; 5] = [
|
||||
"requirements/completeness",
|
||||
"patterns",
|
||||
"capability/toolchain check",
|
||||
"project bootstrap",
|
||||
"deployment/rollback",
|
||||
];
|
||||
const BUILD_CEREMONY: [&str; 9] = [
|
||||
"project bootstrap",
|
||||
"requirements/completeness",
|
||||
"capability/toolchain decisions",
|
||||
"patterns/obligations",
|
||||
"Construction Contract",
|
||||
"implementation",
|
||||
"verification",
|
||||
"deployment/rollback strategy",
|
||||
"maintenance lifecycle",
|
||||
];
|
||||
const EPC_CEREMONY: [&str; 7] = [
|
||||
"classification",
|
||||
"requirements/completeness",
|
||||
"capability fit",
|
||||
"Construction Contract",
|
||||
"scope",
|
||||
"obligations",
|
||||
"verification",
|
||||
];
|
||||
|
||||
fn owned(v: &[&str]) -> Vec<String> {
|
||||
v.iter().map(|s| (*s).to_string()).collect()
|
||||
}
|
||||
|
||||
/// Oracle-exact depth selection: mode gates the lifecycle shape, evidence
|
||||
/// modifiers escalate within EXISTING_PROJECT_CHANGE. Mirrors the Python
|
||||
/// oracle branch-for-branch (batch 125 + facade-burn divergence fixes).
|
||||
#[allow(clippy::too_many_arguments)] // pinned: parity signature — mirrors the oracle arg-for-arg
|
||||
pub fn select_depth(
|
||||
mode: &str,
|
||||
objective: &str,
|
||||
_objective: &str,
|
||||
affected_surface_len: usize,
|
||||
has_migration: bool,
|
||||
breaking: bool,
|
||||
user_locked: bool,
|
||||
user_locked_layout: bool,
|
||||
external_service: bool,
|
||||
uncertainty: &str,
|
||||
_project_exists: Option<bool>,
|
||||
) -> Depth {
|
||||
let mut ev = vec![format!("mode: {}", mode)];
|
||||
let mut ceremony = Vec::new();
|
||||
let mut bypassed = Vec::new();
|
||||
|
||||
// ORACLE PARITY FIX (batch 36): NOT_CONSTRUCTION and UNKNOWN were missing
|
||||
// from the Rust port — they fell through to the default STANDARD path.
|
||||
// §13C: NOT_CONSTRUCTION has NO construction depth; UNKNOWN is unresolved.
|
||||
if mode == "NOT_CONSTRUCTION" {
|
||||
return Depth {
|
||||
level: "NONE".to_string(),
|
||||
reason: "not construction — no construction depth applies".to_string(),
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed: vec!["all".to_string()],
|
||||
};
|
||||
}
|
||||
if mode == "UNKNOWN" {
|
||||
return Depth {
|
||||
level: "UNRESOLVED".to_string(),
|
||||
reason: "UNKNOWN — probe/clarify before any construction".to_string(),
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed: vec![],
|
||||
};
|
||||
}
|
||||
// batch 125: EXACT mode match (the oracle compares == against the 5
|
||||
// canonical MODES from construction_mode.classify_mode). The old
|
||||
// substring match on "LOCAL_FIX" was dead for every real caller AND
|
||||
// missed the real LOCAL_CHANGE mode (which fell through to STANDARD
|
||||
// instead of LIGHT — a live misclassification for tiny fixes).
|
||||
let is_build = mode == "PROJECT_BUILD";
|
||||
let is_local = mode == "LOCAL_CHANGE";
|
||||
let is_research = mode == "RESEARCHED_CHANGE";
|
||||
|
||||
if is_local {
|
||||
// batch 125: oracle-exact LOCAL_CHANGE branch (was a paraphrase).
|
||||
ev.push("LOCAL_CHANGE: bounded scope, minimal ceremony".to_string());
|
||||
for c in [
|
||||
"classification",
|
||||
"authority/scope",
|
||||
"focused verification",
|
||||
"durable result",
|
||||
] {
|
||||
ceremony.push(c.to_string());
|
||||
match Mode::parse(mode) {
|
||||
Mode::NotConstruction => {
|
||||
// §13C: NOT_CONSTRUCTION has NO construction depth — not even LIGHT
|
||||
Depth {
|
||||
level: Level::None.as_str().into(),
|
||||
reason: "not construction — no construction depth applies".into(),
|
||||
evidence: vec![format!("mode: {mode}")],
|
||||
ceremony: vec![],
|
||||
bypassed: vec!["all".into()],
|
||||
}
|
||||
}
|
||||
for b in [
|
||||
"requirements/completeness",
|
||||
"patterns",
|
||||
"capability/toolchain check",
|
||||
"project bootstrap",
|
||||
"deployment/rollback",
|
||||
] {
|
||||
bypassed.push(b.to_string());
|
||||
Mode::Unknown => {
|
||||
// §13C: UNKNOWN means construction hasn't started — no lifecycle yet
|
||||
Depth {
|
||||
level: Level::Unresolved.as_str().into(),
|
||||
reason: "UNKNOWN — probe/clarify before any construction lifecycle begins".into(),
|
||||
evidence: vec![format!("mode: {mode}")],
|
||||
ceremony: vec!["probe/clarify".into()],
|
||||
bypassed: owned(&["contract", "patterns", "bootstrap", "depth selection"]),
|
||||
}
|
||||
}
|
||||
if user_locked {
|
||||
ev.push(
|
||||
"USER-LOCKED layout surface — preservation obligation retained even at LIGHT depth"
|
||||
.to_string(),
|
||||
);
|
||||
ceremony.push("layout obligation check".to_string());
|
||||
Mode::LocalChange => {
|
||||
let mut ev = vec![format!("mode: {mode}")];
|
||||
ev.push("LOCAL_CHANGE: bounded scope, minimal ceremony".into());
|
||||
let mut ceremony = owned(&LOCAL_CEREMONY);
|
||||
if user_locked_layout {
|
||||
ev.push(
|
||||
"USER-LOCKED layout surface — preservation obligation retained even at LIGHT depth"
|
||||
.into(),
|
||||
);
|
||||
ceremony.push("layout obligation check".into());
|
||||
}
|
||||
Depth {
|
||||
level: Level::Light.as_str().into(),
|
||||
reason: "bounded local fix — governance without ceremony".into(),
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed: owned(&LOCAL_BYPASSED),
|
||||
}
|
||||
}
|
||||
return Depth {
|
||||
level: "LIGHT".to_string(),
|
||||
reason: "bounded local fix — governance without ceremony".to_string(),
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed,
|
||||
};
|
||||
}
|
||||
Mode::ProjectBuild => {
|
||||
let mut ev = vec![format!("mode: {mode}")];
|
||||
ev.push("PROJECT_BUILD: full lifecycle required".into());
|
||||
Depth {
|
||||
level: Level::Full.as_str().into(),
|
||||
reason: "genuinely new project — full Scaffolding lifecycle".into(),
|
||||
evidence: ev,
|
||||
ceremony: owned(&BUILD_CEREMONY),
|
||||
bypassed: vec![],
|
||||
}
|
||||
}
|
||||
Mode::ExistingProjectChange => {
|
||||
// base STANDARD, escalate on modifiers
|
||||
let mut ev = vec![format!("mode: {mode}")];
|
||||
let mut ceremony = owned(&EPC_CEREMONY);
|
||||
let mut level = Level::Standard;
|
||||
let mut reason = "existing-project change — proportional governance".to_string();
|
||||
|
||||
if is_research {
|
||||
ev.push("RESEARCHED_CHANGE: research-heavy".to_string());
|
||||
ceremony.push("requirements/completeness".to_string());
|
||||
ceremony.push("research rounds".to_string());
|
||||
ceremony.push("evidence→decision mapping".to_string());
|
||||
return Depth {
|
||||
level: "STANDARD".to_string(),
|
||||
reason: "researched change".to_string(),
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed,
|
||||
};
|
||||
}
|
||||
if has_migration {
|
||||
ev.push("migration: old/new compatibility + rollback obligations".into());
|
||||
ceremony.push("migration proof".into());
|
||||
ceremony.push("rollback preparation".into());
|
||||
}
|
||||
if breaking {
|
||||
ev.push("breaking change: stronger approval + regression obligations".into());
|
||||
ceremony.push("consumer migration completeness".into());
|
||||
}
|
||||
if user_locked_layout {
|
||||
ev.push("USER-LOCKED layout surface affected".into());
|
||||
ceremony.push("layout obligation check".into());
|
||||
}
|
||||
if external_service {
|
||||
ev.push("external service consequence".into());
|
||||
ceremony.push("deployment verification".into());
|
||||
}
|
||||
if uncertainty == "high" {
|
||||
ev.push("high uncertainty: probe before deeper ceremony".into());
|
||||
ceremony.insert(0, "evidence probe".into());
|
||||
}
|
||||
if affected_surface_len > 5 {
|
||||
ev.push(format!("wide affected surface: {affected_surface_len} entries"));
|
||||
level = Level::Full;
|
||||
reason = "wide surface — full depth warranted".into();
|
||||
}
|
||||
if breaking && has_migration {
|
||||
level = Level::Full;
|
||||
reason = "breaking + migration — full depth".into();
|
||||
}
|
||||
|
||||
if is_build {
|
||||
ev.push("PROJECT_BUILD: full construction".to_string());
|
||||
ceremony.push("all obligations".to_string());
|
||||
return Depth {
|
||||
level: "FULL".to_string(),
|
||||
reason: "project build".to_string(),
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed,
|
||||
};
|
||||
}
|
||||
|
||||
// ORACLE PARITY: EXISTING_PROJECT_CHANGE stays STANDARD regardless of
|
||||
// evidence flags (the Python oracle records evidence + extends ceremony
|
||||
// but never escalates the level beyond STANDARD for this mode).
|
||||
// ORACLE PARITY: ceremony strings match the Python oracle EXACTLY.
|
||||
// Escalation to FULL only for: wide surface (>5) or breaking+migration.
|
||||
if has_migration {
|
||||
ev.push("migration: old/new compatibility + rollback obligations".to_string());
|
||||
ceremony.push("migration proof".to_string());
|
||||
ceremony.push("rollback preparation".to_string());
|
||||
}
|
||||
if breaking {
|
||||
ev.push("breaking change: stronger approval + regression obligations".to_string());
|
||||
ceremony.push("consumer migration completeness".to_string());
|
||||
}
|
||||
if user_locked {
|
||||
ev.push("USER-LOCKED layout surface affected".to_string());
|
||||
ceremony.push("layout obligation check".to_string());
|
||||
}
|
||||
if external_service {
|
||||
ev.push("external service consequence".to_string());
|
||||
ceremony.push("deployment verification".to_string());
|
||||
}
|
||||
if uncertainty == "high" {
|
||||
ev.push("high uncertainty: probe before deeper ceremony".to_string());
|
||||
ceremony.insert(0, "evidence probe".to_string());
|
||||
}
|
||||
|
||||
let mut level = "STANDARD";
|
||||
let mut reason = "existing-project change — proportional governance".to_string();
|
||||
// wide surface escalation (oracle: len(affected_surface) > 5)
|
||||
if affected_surface_len > 5 {
|
||||
ev.push(format!("wide affected surface: {} entries", affected_surface_len));
|
||||
level = "FULL";
|
||||
reason = "wide surface — full depth warranted".to_string();
|
||||
}
|
||||
if breaking && has_migration {
|
||||
level = "FULL";
|
||||
reason = "breaking + migration — full depth".to_string();
|
||||
}
|
||||
|
||||
Depth {
|
||||
level: level.to_string(),
|
||||
reason,
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed,
|
||||
Depth {
|
||||
level: level.as_str().into(),
|
||||
reason,
|
||||
evidence: ev,
|
||||
ceremony,
|
||||
bypassed: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── FFI surface (facade burn-down, 2026-10-10) ───────────────────────────────
|
||||
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<Json, String> {
|
||||
if ptr.is_null() {
|
||||
return Err("null argument pointer".to_string());
|
||||
return Err("null argument pointer".into());
|
||||
}
|
||||
unsafe { CStr::from_ptr(ptr) }
|
||||
let x = 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| e.to_string())?;
|
||||
pyjson::parse(x)
|
||||
}
|
||||
|
||||
/// cd_select_depth({mode, objective, has_migration, breaking,
|
||||
/// user_locked_layout, external_service, uncertainty}) -> Depth
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cd_select_depth(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 d = select_depth(
|
||||
a.get("mode").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("objective").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
a.get("affected_surface").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0),
|
||||
a.get("has_migration").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||
a.get("breaking").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||
a.get("user_locked_layout").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||
a.get("external_service").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||
a.get("uncertainty").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
None,
|
||||
);
|
||||
serde_json::to_value(d).map_err(|e| format!("serialize: {}", e))
|
||||
});
|
||||
let body = match result {
|
||||
Ok(v) => serde_json::json!({"ok": true, "value": v}),
|
||||
Err(e) => serde_json::json!({"ok": false, "err": e}),
|
||||
fn envelope(r: Result<Json, String>) -> *mut c_char {
|
||||
let v = match r {
|
||||
Ok(x) => Json::Obj(vec![
|
||||
("ok".into(), Json::Bool(true)),
|
||||
("value".into(), x),
|
||||
]),
|
||||
Err(e) => Json::Obj(vec![
|
||||
("ok".into(), Json::Bool(false)),
|
||||
("err".into(), Json::Str(e)),
|
||||
]),
|
||||
};
|
||||
CString::new(body.to_string())
|
||||
CString::new(pyjson::dumps(&v))
|
||||
.map(|c| c.into_raw())
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
/// cd_select_depth({mode, objective, affected_surface, has_migration,
|
||||
/// breaking, user_locked_layout, external_service,
|
||||
/// uncertainty}) -> Depth dict
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cd_free_string(ptr: *mut c_char) {
|
||||
if !ptr.is_null() {
|
||||
unsafe {
|
||||
let _ = CString::from_raw(ptr);
|
||||
}
|
||||
}
|
||||
pub extern "C" fn cd_select_depth(arg: *const c_char) -> *mut c_char {
|
||||
let r = read_arg(arg).map(|a| {
|
||||
let mode = match a.get("mode") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
let objective = match a.get("objective") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
let surface_len = match a.get("affected_surface") {
|
||||
Some(Json::Arr(xs)) => xs.len(),
|
||||
_ => 0,
|
||||
};
|
||||
let flag = |k: &str| matches!(a.get(k), Some(Json::Bool(true)));
|
||||
let uncertainty = match a.get("uncertainty") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
select_depth(
|
||||
mode,
|
||||
objective,
|
||||
surface_len,
|
||||
flag("has_migration"),
|
||||
flag("breaking"),
|
||||
flag("user_locked_layout"),
|
||||
flag("external_service"),
|
||||
uncertainty,
|
||||
)
|
||||
.to_json()
|
||||
});
|
||||
envelope(r)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_local_fix() {
|
||||
// batch 125: canonical mode is LOCAL_CHANGE (construction_mode.MODES);
|
||||
// "LOCAL_FIX" was the port's private invention, never emitted.
|
||||
let d = select_depth(
|
||||
"LOCAL_CHANGE",
|
||||
"fix typo",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
assert_eq!(d.level, "LIGHT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_build() {
|
||||
let d = select_depth(
|
||||
"PROJECT_BUILD",
|
||||
"build system",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
assert_eq!(d.level, "FULL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_existing_project_stays_standard() {
|
||||
// ORACLE: EXISTING_PROJECT_CHANGE stays STANDARD regardless of
|
||||
// evidence flags (corrected 2026-10-10 against the live oracle).
|
||||
let d = select_depth(
|
||||
"EXISTING_PROJECT_CHANGE",
|
||||
"add feature",
|
||||
0,
|
||||
true, // has_migration — evidence only, no escalation
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
assert_eq!(d.level, "STANDARD");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod branch_battery {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn not_construction_is_none() {
|
||||
let d = select_depth("NOT_CONSTRUCTION", "",
|
||||
0, false, false, false, false, "", None);
|
||||
assert_eq!(d.level, "NONE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonical_modes_parity() {
|
||||
// batch 125 (oracle-probed): all 5 canonical MODES classify correctly;
|
||||
// the port's old "LOCAL_FIX" invention falls through to STANDARD
|
||||
// (it is never emitted by classify_mode — no caller depends on it).
|
||||
let cases = [
|
||||
("NOT_CONSTRUCTION", "NONE"),
|
||||
("UNKNOWN", "UNRESOLVED"),
|
||||
("LOCAL_CHANGE", "LIGHT"),
|
||||
("PROJECT_BUILD", "FULL"),
|
||||
("EXISTING_PROJECT_CHANGE", "STANDARD"),
|
||||
];
|
||||
for (mode, want) in cases {
|
||||
let d = select_depth(mode, "", 0, false, false, false, false, "", None);
|
||||
assert_eq!(d.level, want, "mode {}", mode);
|
||||
}
|
||||
// legacy invention: falls through (documents the old misclassification)
|
||||
let d = select_depth("LOCAL_FIX", "",
|
||||
0, false, false, false, false, "", None);
|
||||
assert_eq!(d.level, "STANDARD");
|
||||
// LOCAL_CHANGE carries the bounded-scope ceremony (oracle branch)
|
||||
let d = select_depth("LOCAL_CHANGE", "",
|
||||
0, false, false, false, false, "", None);
|
||||
assert_eq!(d.level, "LIGHT");
|
||||
assert!(d.bypassed.contains(&"patterns".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_is_unresolved() {
|
||||
let d = select_depth("UNKNOWN", "",
|
||||
0, false, false, false, false, "", None);
|
||||
assert_eq!(d.level, "UNRESOLVED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_new_is_standard() {
|
||||
let d = select_depth(
|
||||
"BUILD_NEW",
|
||||
"test objective",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
assert_eq!(d.level, "STANDARD");
|
||||
}
|
||||
}
|
||||
// NOTE: cd_free_string is deliberately NOT exported — capability_scoring
|
||||
// (linked into the same Python process via rune/cs_loader) already exports
|
||||
// cs_free_string, and a duplicate free symbol breaks the cdylib link.
|
||||
// Callers free every returned pointer through that path.
|
||||
@@ -1,16 +1,52 @@
|
||||
//! branch_battery — replays frozen_cpb_truth.json VERBATIM (17 cases
|
||||
//! branch_battery — replays frozen_cpb_truth.json VERBATIM (19 cases
|
||||
//! frozen from the live Python oracle construction_patterns_builtin.py,
|
||||
//! RUNE_CPB_CORE=python pinned, inputs deep-copied before capture).
|
||||
//! Order-insensitive object comparison; numbers compared exactly (both
|
||||
//! sides IEEE-754 f64); list order significant (registry + provenance
|
||||
//! order is oracle-derived).
|
||||
//!
|
||||
//! Language-advantage pass (Phase B, battery-pinned): the truth fn-name
|
||||
//! lattice is a `TruthFn` enum parsed once per case (unknown names are the
|
||||
//! exhaustive `Unknown` variant, reported as errors), case fields are
|
||||
//! zero-copy borrows of the parsed truth doc (no per-case clones), and
|
||||
//! case collection + failure reporting are iterator chains.
|
||||
|
||||
use capability_scoring::pyjson::{parse, Json};
|
||||
use capability_scoring::pyjson::{dumps, parse, Json};
|
||||
|
||||
use crate::registry;
|
||||
|
||||
const TRUTH: &str = include_str!("../frozen_cpb_truth.json");
|
||||
|
||||
/// The truth fn-name lattice: one variant per kernel entry point the
|
||||
/// frozen truth exercises.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum TruthFn {
|
||||
RegistryRevision,
|
||||
Patterns,
|
||||
GetPattern,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl TruthFn {
|
||||
fn parse(name: &str) -> Self {
|
||||
match name {
|
||||
"registry_revision" => Self::RegistryRevision,
|
||||
"patterns" => Self::Patterns,
|
||||
"get_pattern" => Self::GetPattern,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::RegistryRevision => "registry_revision",
|
||||
Self::Patterns => "patterns",
|
||||
Self::GetPattern => "get_pattern",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn jeq(a: &Json, b: &Json) -> bool {
|
||||
match (a, b) {
|
||||
(Json::Num(x), Json::Num(y)) => x == y,
|
||||
@@ -18,7 +54,7 @@ fn jeq(a: &Json, b: &Json) -> bool {
|
||||
(Json::Bool(x), Json::Bool(y)) => x == y,
|
||||
(Json::Null, Json::Null) => true,
|
||||
(Json::Arr(x), Json::Arr(y)) => {
|
||||
x.len() == y.len() && x.iter().zip(y).all(|(i, j)| jeq(i, j))
|
||||
x.len() == y.len() && x.iter().zip(y.iter()).all(|(i, j)| jeq(i, j))
|
||||
}
|
||||
(Json::Obj(x), Json::Obj(y)) => {
|
||||
x.len() == y.len()
|
||||
@@ -29,34 +65,41 @@ fn jeq(a: &Json, b: &Json) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(v: Option<&Json>) -> String {
|
||||
/// Zero-copy borrow of a string field (missing/non-string reads as "").
|
||||
fn as_str(v: Option<&Json>) -> &str {
|
||||
match v {
|
||||
Some(Json::Str(s)) => s.clone(),
|
||||
_ => String::new(),
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Null for a missing field, by borrow (no clone).
|
||||
fn or_null(v: Option<&Json>) -> &Json {
|
||||
v.unwrap_or(&Json::Null)
|
||||
}
|
||||
|
||||
/// One case's oracle verdict, replayed against the Rust kernel.
|
||||
fn run_case(fn_name: &str, args: &Json) -> Result<Json, String> {
|
||||
match fn_name {
|
||||
"registry_revision" => Ok(Json::Num(registry::registry_revision() as f64)),
|
||||
"patterns" => Ok(Json::Arr(registry::patterns())),
|
||||
"get_pattern" => {
|
||||
fn run_case(truth_fn: TruthFn, args: &Json) -> Result<Json, String> {
|
||||
match truth_fn {
|
||||
TruthFn::RegistryRevision => Ok(Json::Num(registry::registry_revision() as f64)),
|
||||
TruthFn::Patterns => Ok(Json::Arr(registry::patterns())),
|
||||
TruthFn::GetPattern => {
|
||||
let pattern_id = as_str(args.get("pattern_id"));
|
||||
let revision = match args.get("revision") {
|
||||
Some(Json::Num(n)) => Some(*n as i64),
|
||||
_ => None,
|
||||
};
|
||||
registry::get_pattern(&pattern_id, revision)
|
||||
registry::get_pattern(pattern_id, revision)
|
||||
}
|
||||
other => Err(format!("unknown truth fn {other}")),
|
||||
TruthFn::Unknown => Err(format!("unknown truth fn {}", truth_fn.as_str())),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_case(fn_name: &str, name: &str, args: &Json, want: &Json) -> Result<(), String> {
|
||||
fn check_case(truth_fn: TruthFn, name: &str, args: &Json, want: &Json) -> Result<(), String> {
|
||||
let fn_name = truth_fn.as_str();
|
||||
// The oracle's raising branch is recorded as {"raises","message"}.
|
||||
if let Some(message) = want.get("message").filter(|_| want.get("raises").is_some()) {
|
||||
match run_case(fn_name, args) {
|
||||
match run_case(truth_fn, args) {
|
||||
Err(err) => {
|
||||
let want_msg = as_str(Some(message));
|
||||
if err != want_msg {
|
||||
@@ -69,20 +112,20 @@ fn check_case(fn_name: &str, name: &str, args: &Json, want: &Json) -> Result<(),
|
||||
Ok(got) => {
|
||||
return Err(format!(
|
||||
"{fn_name}/{name}: oracle raises, kernel returned {}",
|
||||
capability_scoring::pyjson::dumps(&got)
|
||||
dumps(&got)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
match run_case(fn_name, args) {
|
||||
match run_case(truth_fn, args) {
|
||||
Ok(got) => {
|
||||
if jeq(&got, want) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"{fn_name}/{name}: value mismatch\n want {}\n got {}",
|
||||
capability_scoring::pyjson::dumps(want),
|
||||
capability_scoring::pyjson::dumps(&got)
|
||||
dumps(want),
|
||||
dumps(&got)
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -90,24 +133,37 @@ fn check_case(fn_name: &str, name: &str, args: &Json, want: &Json) -> Result<(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_truth_replays_verbatim() {
|
||||
fn truth_cases() -> Vec<Json> {
|
||||
let truth = parse(TRUTH).expect("frozen_cpb_truth.json parses");
|
||||
let cases = match truth.get("cases") {
|
||||
match truth.get("cases") {
|
||||
Some(Json::Arr(c)) => c.clone(),
|
||||
_ => panic!("truth has no cases"),
|
||||
};
|
||||
assert!(!cases.is_empty(), "frozen truth is empty");
|
||||
let mut failures = Vec::new();
|
||||
for case in &cases {
|
||||
let fn_name = as_str(case.get("fn"));
|
||||
let name = as_str(case.get("name"));
|
||||
let args = case.get("input").cloned().unwrap_or(Json::Null);
|
||||
let want = case.get("want").cloned().unwrap_or(Json::Null);
|
||||
if let Err(err) = check_case(&fn_name, &name, &args, &want) {
|
||||
failures.push(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_truth_replays_verbatim() {
|
||||
let cases = truth_cases();
|
||||
assert!(!cases.is_empty(), "frozen truth is empty");
|
||||
let failures: Vec<String> = cases
|
||||
.iter()
|
||||
.filter_map(|case| {
|
||||
let truth_fn = TruthFn::parse(as_str(case.get("fn")));
|
||||
let name = as_str(case.get("name"));
|
||||
let args = or_null(case.get("input"));
|
||||
let want = or_null(case.get("want"));
|
||||
// An unknown fn name is a battery failure, not a skip: the
|
||||
// lattice is closed by the frozen truth.
|
||||
if truth_fn == TruthFn::Unknown {
|
||||
return Some(format!(
|
||||
"{}/{name}: unknown truth fn {}",
|
||||
truth_fn.as_str(),
|
||||
as_str(case.get("fn"))
|
||||
));
|
||||
}
|
||||
check_case(truth_fn, name, args, want).err()
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"branch battery diverged from frozen truth:\n{}",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! (a revision that no longer matches is an err carrying the oracle's
|
||||
//! exact ValueError message)
|
||||
//!
|
||||
//! Parity oracle: frozen_cpb_truth.json (17 cases frozen from the live
|
||||
//! Parity oracle: frozen_cpb_truth.json (19 cases frozen from the live
|
||||
//! Python oracle) replayed verbatim by the in-crate branch battery.
|
||||
|
||||
pub mod registry;
|
||||
@@ -58,6 +58,21 @@ fn arg_json(text: &str) -> Result<Json, String> {
|
||||
pyjson::parse(text).map_err(|e| format!("argument JSON parse failed: {}", e))
|
||||
}
|
||||
|
||||
/// Borrowed get_pattern args: the id is a zero-copy borrow of the parsed
|
||||
/// arg doc (no clone — the lookup only needs `&str`), and extraction is
|
||||
/// `?`-propagated instead of nested matches.
|
||||
fn get_pattern_args(arg: &Json) -> Result<(&str, Option<i64>), String> {
|
||||
let pattern_id = match arg.get("pattern_id") {
|
||||
Some(Json::Str(s)) => s.as_str(),
|
||||
_ => return Err("arg.pattern_id required".to_string()),
|
||||
};
|
||||
let revision = match arg.get("revision") {
|
||||
Some(Json::Num(n)) => Some(*n as i64),
|
||||
_ => None,
|
||||
};
|
||||
Ok((pattern_id, revision))
|
||||
}
|
||||
|
||||
/// cpb_registry_revision({}) -> number
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cpb_registry_revision(arg: *const c_char) -> *mut c_char {
|
||||
@@ -82,15 +97,8 @@ pub extern "C" fn cpb_get_pattern(arg: *const c_char) -> *mut c_char {
|
||||
let result = read_arg(arg)
|
||||
.and_then(|text| arg_json(&text))
|
||||
.and_then(|a| {
|
||||
let pattern_id = match a.get("pattern_id") {
|
||||
Some(Json::Str(s)) => s.clone(),
|
||||
_ => return Err("arg.pattern_id required".to_string()),
|
||||
};
|
||||
let revision = match a.get("revision") {
|
||||
Some(Json::Num(n)) => Some(*n as i64),
|
||||
_ => None,
|
||||
};
|
||||
registry::get_pattern(&pattern_id, revision)
|
||||
let (pattern_id, revision) = get_pattern_args(&a)?;
|
||||
registry::get_pattern(pattern_id, revision)
|
||||
});
|
||||
envelope(result)
|
||||
}
|
||||
@@ -9,6 +9,15 @@
|
||||
//! values; the FFI re-emits the oracle-shaped doc from the typed value.
|
||||
//! Miss-before-revision-check oracle ordering is explicit in the typed
|
||||
//! lookup.
|
||||
//!
|
||||
//! Language-advantage pass (Phase B, battery-pinned): the frozen source
|
||||
//! parses ONCE into a single `Registry` (revision + typed patterns — the
|
||||
//! parity code parsed it twice, once per accessor), the pattern-id string
|
||||
//! lattice is a `PatternId` enum parsed once per side so lookup is an
|
||||
//! exhaustive discriminant match instead of repeated string comparisons,
|
||||
//! and the revision gate is one exhaustive match. Battery green throughout
|
||||
//! (unchanged from the parity commit — the whole-doc replay pins the
|
||||
//! refactor).
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -16,37 +25,82 @@ use capability_scoring::pyjson::{self, Json};
|
||||
|
||||
const PATTERNS_SRC: &str = include_str!("../frozen_cpb_patterns.json");
|
||||
|
||||
/// The builtin pattern-id lattice. The frozen registry's id set is closed
|
||||
/// (four ids — pinned by the whole-doc battery replay), so `parse` is
|
||||
/// injective over it and enum equality is exact; anything else is a miss
|
||||
/// BEFORE any revision check (oracle ordering).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PatternId {
|
||||
CapabilityModuleAdaptation,
|
||||
ReleaseDeploymentChange,
|
||||
UiTuiStructuralChange,
|
||||
StoreSchemaEvolution,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl PatternId {
|
||||
fn parse(id: &str) -> Self {
|
||||
match id {
|
||||
"capability-module-adaptation" => Self::CapabilityModuleAdaptation,
|
||||
"release-deployment-change" => Self::ReleaseDeploymentChange,
|
||||
"ui-tui-structural-change" => Self::UiTuiStructuralChange,
|
||||
"store-schema-evolution" => Self::StoreSchemaEvolution,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One builtin pattern, typed from the oracle's to_dict() doc. Free-form
|
||||
/// sub-structures (signals, obligations) stay `Json`: the schema is the
|
||||
/// oracle's, open-world by design.
|
||||
#[derive(Clone, Debug)]
|
||||
struct BuiltinPattern {
|
||||
pattern_id: String,
|
||||
id: PatternId,
|
||||
revision: i64,
|
||||
doc: Json,
|
||||
}
|
||||
|
||||
impl BuiltinPattern {
|
||||
fn from_doc(doc: Json) -> Option<Self> {
|
||||
let pattern_id = match doc.get("pattern_id") {
|
||||
Some(Json::Str(s)) => s.clone(),
|
||||
// The frozen registry's id set is closed (four ids — pinned by the
|
||||
// whole-doc battery replay): an unknown id is dropped here, so enum
|
||||
// equality below is exact and an unknown query always misses.
|
||||
let id = match doc.get("pattern_id") {
|
||||
Some(Json::Str(s)) => match PatternId::parse(s) {
|
||||
PatternId::Unknown => return None,
|
||||
known => known,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
let revision = match doc.get("revision") {
|
||||
Some(Json::Num(n)) => *n as i64,
|
||||
_ => return None,
|
||||
};
|
||||
Some(Self { pattern_id, revision, doc })
|
||||
Some(Self { id, revision, doc })
|
||||
}
|
||||
}
|
||||
|
||||
fn registry() -> &'static Vec<BuiltinPattern> {
|
||||
static REGISTRY: OnceLock<Vec<BuiltinPattern>> = OnceLock::new();
|
||||
REGISTRY.get_or_init(|| {
|
||||
pyjson::parse(PATTERNS_SRC)
|
||||
.ok()
|
||||
.and_then(|root| match root.get("patterns") {
|
||||
Some(Json::Arr(items)) => Some(
|
||||
/// The frozen registry, parsed ONCE: the oracle's revision plus every
|
||||
/// builtin pattern in registry order (BUILTIN_PATTERNS order).
|
||||
struct Registry {
|
||||
revision: i64,
|
||||
patterns: Vec<BuiltinPattern>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
fn parse() -> Self {
|
||||
let root = pyjson::parse(PATTERNS_SRC).unwrap_or(Json::Null);
|
||||
let revision = root
|
||||
.get("revision")
|
||||
.and_then(|v| match v {
|
||||
Json::Num(n) => Some(*n as i64),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let patterns = root
|
||||
.get("patterns")
|
||||
.and_then(|v| match v {
|
||||
Json::Arr(items) => Some(
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|d| BuiltinPattern::from_doc(d.clone()))
|
||||
@@ -54,46 +108,42 @@ fn registry() -> &'static Vec<BuiltinPattern> {
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self { revision, patterns }
|
||||
}
|
||||
}
|
||||
|
||||
fn registry() -> &'static Registry {
|
||||
static REGISTRY: OnceLock<Registry> = OnceLock::new();
|
||||
REGISTRY.get_or_init(Registry::parse)
|
||||
}
|
||||
|
||||
/// PATTERN_REGISTRY_REVISION from the oracle.
|
||||
pub fn registry_revision() -> i64 {
|
||||
static REVISION: OnceLock<i64> = OnceLock::new();
|
||||
*REVISION.get_or_init(|| {
|
||||
pyjson::parse(PATTERNS_SRC)
|
||||
.ok()
|
||||
.and_then(|root| match root.get("revision") {
|
||||
Some(Json::Num(n)) => Some(*n as i64),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0)
|
||||
})
|
||||
registry().revision
|
||||
}
|
||||
|
||||
/// Every builtin pattern doc, in registry order (BUILTIN_PATTERNS order).
|
||||
pub fn patterns() -> Vec<Json> {
|
||||
registry().iter().map(|p| p.doc.clone()).collect()
|
||||
registry().patterns.iter().map(|p| p.doc.clone()).collect()
|
||||
}
|
||||
|
||||
/// Oracle get_pattern semantics: unknown/empty id -> Null BEFORE any
|
||||
/// revision check; a revision that no longer matches raises (err carries
|
||||
/// the oracle's exact ValueError message).
|
||||
pub fn get_pattern(pattern_id: &str, revision: Option<i64>) -> Result<Json, String> {
|
||||
let pattern = match registry().iter().find(|p| p.pattern_id == pattern_id) {
|
||||
let want = PatternId::parse(pattern_id);
|
||||
let pattern = match registry().patterns.iter().find(|p| p.id == want) {
|
||||
Some(p) => p,
|
||||
None => return Ok(Json::Null),
|
||||
};
|
||||
if let Some(rev) = revision {
|
||||
if pattern.revision != rev {
|
||||
return Err(format!(
|
||||
"pattern {pattern_id} revision {rev} not in registry \
|
||||
(current: {}) \u{2014} the consuming contract records which \
|
||||
revision it used",
|
||||
pattern.revision
|
||||
));
|
||||
}
|
||||
match revision {
|
||||
Some(rev) if rev != pattern.revision => Err(format!(
|
||||
"pattern {pattern_id} revision {rev} not in registry \
|
||||
(current: {}) \u{2014} the consuming contract records which \
|
||||
revision it used",
|
||||
pattern.revision
|
||||
)),
|
||||
_ => Ok(pattern.doc.clone()),
|
||||
}
|
||||
Ok(pattern.doc.clone())
|
||||
}
|
||||
Generated
+29
-1
@@ -3,9 +3,36 @@
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "iteration"
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "evidence_provenance"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iteration"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"evidence_provenance",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
@@ -74,6 +101,7 @@ version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
[package]
|
||||
name = "iteration"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
description = "FULL parity port of src/rune/iteration.py (dichotomy wave, #2479 tranche)"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
serde_json = { version = "1", features = ["preserve_order", "float_roundtrip"] }
|
||||
evidence_provenance = { path = "../evidence_provenance" }
|
||||
@@ -0,0 +1,219 @@
|
||||
//! branch_battery — replay the FROZEN oracle truth VERBATIM
|
||||
//! (frozen_it_truth.json, generated by scripts/gen_iteration_truth.py
|
||||
//! against the LIVE Python oracle with RUNE_IT_CORE=python and
|
||||
//! RUNE_LEDGER_CORE=python pinned).
|
||||
//!
|
||||
//! Every case: feed the recorded input doc through the kernel's public
|
||||
//! functions and compare the result against the oracle's recorded
|
||||
//! expectation — parsed as Python values (dict/list/str/num/bool/None),
|
||||
//! so key order is insensitive but every value must match exactly.
|
||||
|
||||
use crate::{
|
||||
context_packet, descriptive_impact, invalidate_behaviours,
|
||||
post_change_obligations, predictive_impact, record_delta_line,
|
||||
};
|
||||
use evidence_provenance::pyjson::{self, Json};
|
||||
|
||||
const TRUTH: &str = include_str!("frozen_it_truth.json");
|
||||
|
||||
fn truth() -> Json {
|
||||
pyjson::parse(TRUTH).expect("frozen truth parses")
|
||||
}
|
||||
|
||||
fn cases(t: &Json, group: &str) -> Vec<Json> {
|
||||
match t.get(group) {
|
||||
Some(Json::Arr(items)) => items.clone(),
|
||||
_ => panic!("truth group {group} missing"),
|
||||
}
|
||||
}
|
||||
|
||||
fn at<'a>(v: &'a Json, key: &str) -> Option<&'a Json> {
|
||||
match v {
|
||||
Json::Obj(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, x)| x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn atv<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
|
||||
let mut cur = v;
|
||||
for k in path {
|
||||
cur = at(cur, k)?;
|
||||
}
|
||||
Some(cur)
|
||||
}
|
||||
|
||||
fn jstr(v: Option<&Json>) -> String {
|
||||
match v {
|
||||
Some(Json::Str(s)) => s.clone(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn jlist(v: Option<&Json>) -> Vec<String> {
|
||||
match v {
|
||||
Some(Json::Arr(items)) => items
|
||||
.iter()
|
||||
.filter_map(|x| match x {
|
||||
Json::Str(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn jnum(v: Option<&Json>, default: f64) -> f64 {
|
||||
match v {
|
||||
Some(Json::Num(n)) => *n,
|
||||
_ => default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Python-value equality on Json (num compares by f64).
|
||||
fn peq(a: &Json, b: &Json) -> bool {
|
||||
match (a, b) {
|
||||
(Json::Null, Json::Null) => true,
|
||||
(Json::Bool(x), Json::Bool(y)) => x == y,
|
||||
(Json::Num(x), Json::Num(y)) => x == y,
|
||||
(Json::Str(x), Json::Str(y)) => x == y,
|
||||
(Json::Arr(x), Json::Arr(y)) => {
|
||||
x.len() == y.len() && x.iter().zip(y).all(|(a, b)| peq(a, b))
|
||||
}
|
||||
(Json::Obj(x), Json::Obj(y)) => {
|
||||
x.len() == y.len()
|
||||
&& x.iter().all(|(k, v)| {
|
||||
y.iter()
|
||||
.find(|(k2, _)| k2 == k)
|
||||
.map(|(_, v2)| peq(v, v2))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_eq(case_name: &str, want: &Json, got: &Json) {
|
||||
assert!(
|
||||
peq(want, got),
|
||||
"case {case_name} diverged:\n want={}\n got={}",
|
||||
pyjson::dumps(want),
|
||||
pyjson::dumps(got),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_context_packet() {
|
||||
let t = truth();
|
||||
for c in cases(&t, "context_packet_cases") {
|
||||
let got = context_packet(
|
||||
atv(&c, &["args", "model"]).expect("model"),
|
||||
atv(&c, &["args", "ledger"]).expect("ledger"),
|
||||
&jstr(atv(&c, &["args", "target"])),
|
||||
&jstr(atv(&c, &["args", "intent"])),
|
||||
atv(&c, &["args", "basis_summary"]).cloned().unwrap_or(Json::Null),
|
||||
jnum(atv(&c, &["args", "max_local_files"]), 12.0) as usize,
|
||||
&jstr(atv(&c, &["args", "now"])),
|
||||
);
|
||||
assert_eq(
|
||||
&format!("context_packet/{}", jstr(at(&c, "name"))),
|
||||
at(&c, "expect").expect("expect"),
|
||||
&got,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_predictive_impact() {
|
||||
let t = truth();
|
||||
for c in cases(&t, "predictive_impact_cases") {
|
||||
let got = predictive_impact(
|
||||
atv(&c, &["args", "model"]).expect("model"),
|
||||
&jstr(atv(&c, &["args", "target"])),
|
||||
);
|
||||
assert_eq(
|
||||
&format!("predictive_impact/{}", jstr(at(&c, "name"))),
|
||||
at(&c, "expect").expect("expect"),
|
||||
&got,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_descriptive_impact() {
|
||||
let t = truth();
|
||||
for c in cases(&t, "descriptive_impact_cases") {
|
||||
let got = descriptive_impact(
|
||||
atv(&c, &["args", "model"]).expect("model"),
|
||||
&jstr(atv(&c, &["args", "target"])),
|
||||
&jlist(atv(&c, &["args", "observed_changed"])),
|
||||
&jstr(atv(&c, &["args", "generation"])),
|
||||
&jstr(atv(&c, &["args", "now"])),
|
||||
);
|
||||
assert_eq(
|
||||
&format!("descriptive_impact/{}", jstr(at(&c, "name"))),
|
||||
at(&c, "expect").expect("expect"),
|
||||
&got,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_record_delta_line() {
|
||||
let t = truth();
|
||||
for c in cases(&t, "record_impact_delta_cases") {
|
||||
let got = record_delta_line(atv(&c, &["args", "delta"]).expect("delta"));
|
||||
assert_eq(
|
||||
&format!("record_impact_delta/{}", jstr(at(&c, "name"))),
|
||||
at(&c, "expect").expect("expect"),
|
||||
&Json::Str(got),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_invalidate_behaviours() {
|
||||
let t = truth();
|
||||
for c in cases(&t, "invalidate_behaviours_cases") {
|
||||
let mut bh = atv(&c, &["args", "behaviours"])
|
||||
.cloned()
|
||||
.expect("behaviours");
|
||||
let recs = invalidate_behaviours(
|
||||
&mut bh,
|
||||
&jlist(atv(&c, &["args", "changed"])),
|
||||
&jstr(atv(&c, &["args", "generation"])),
|
||||
&jstr(atv(&c, &["args", "now"])),
|
||||
)
|
||||
.expect("invalidate ok");
|
||||
let name = jstr(at(&c, "name"));
|
||||
assert_eq(
|
||||
&format!("invalidate_behaviours/{name}/doc"),
|
||||
atv(&c, &["expect", "doc"]).expect("doc"),
|
||||
&bh,
|
||||
);
|
||||
assert_eq(
|
||||
&format!("invalidate_behaviours/{name}/recs"),
|
||||
atv(&c, &["expect", "recs"]).expect("recs"),
|
||||
&recs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_post_change_obligations() {
|
||||
let t = truth();
|
||||
for c in cases(&t, "post_change_obligations_cases") {
|
||||
let changed = atv(&c, &["args", "changed"]).map(|v| jlist(Some(v)));
|
||||
let behaviours = atv(&c, &["args", "behaviours"]).cloned();
|
||||
let got = post_change_obligations(
|
||||
atv(&c, &["args", "model"]).expect("model"),
|
||||
&jstr(atv(&c, &["args", "target"])),
|
||||
changed.as_deref(),
|
||||
behaviours.as_ref(),
|
||||
);
|
||||
assert_eq(
|
||||
&format!("post_change_obligations/{}", jstr(at(&c, "name"))),
|
||||
at(&c, "expect").expect("expect"),
|
||||
&got,
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+876
-11
@@ -1,14 +1,879 @@
|
||||
//! iteration — post-change iteration tracking (W6).
|
||||
pub fn record_impact_delta(
|
||||
_delta_path: &str,
|
||||
contract_id: &str,
|
||||
scope_files: &[String],
|
||||
) -> serde_json::Value {
|
||||
serde_json::json!({"contract_id": contract_id, "scope_files": scope_files, "recorded": true})
|
||||
//! iteration — T19 §19E/19H/19I/19K: the canonical dependency-aware
|
||||
//! iteration engine.
|
||||
//!
|
||||
//! FULL parity port of src/rune/iteration.py (dichotomy wave, #2479
|
||||
//! tranche). The oracle consumes live StructuralModel / CapabilityLedger /
|
||||
//! BehaviourLedger objects; over FFI they ride as their JSON docs and the
|
||||
//! model queries (children/beneath/consumers/impact_closure/...) are
|
||||
//! reimplemented from the Python floor semantics (the sm_loader kernel is
|
||||
//! already parity-verified against the same floor).
|
||||
//!
|
||||
//! FFI (doc-envelope law): every call takes one JSON doc and returns
|
||||
//! {"ok": true, "value": ...} | {"ok": false, "err": ..., "type": ...}.
|
||||
//! * itr_context_packet({model, ledger, target, intent?, basis_summary?,
|
||||
//! max_local_files?, now}) -> packet dict
|
||||
//! * itr_predictive_impact({model, target}) -> closure dict
|
||||
//! * itr_descriptive_impact({model, target, observed_changed,
|
||||
//! generation?, now}) -> delta dict
|
||||
//! * itr_record_delta_line({delta}) -> jsonl line
|
||||
//! * itr_invalidate({behaviours, changed, generation?, now}) -> {doc, recs}
|
||||
//! * itr_post_change_obligations({model, target, changed?, behaviours?})
|
||||
//! -> [obligations]
|
||||
//!
|
||||
//! Determinism seams: `generated_at` / `recorded_at` / the invalidation
|
||||
//! clock are facade ARGUMENTS (`now`) — the frozen-truth battery replays
|
||||
//! them verbatim. The impact_deltas.jsonl FILE append stays in the facade;
|
||||
//! the kernel owns the exact line bytes (json.dumps sort_keys parity via
|
||||
//! pyjson::dumps_sorted).
|
||||
//!
|
||||
//! invalidate_behaviours delegates to the evidence_provenance kernel's
|
||||
//! behaviour::invalidate_for_change — the SAME Rust function the oracle's
|
||||
//! ep_loader routes to (rule C: no second invalidation authority).
|
||||
//!
|
||||
//! NOTE: no itr_free_string export — callers free through capability_
|
||||
//! scoring's cs_free_string (shared process allocator).
|
||||
|
||||
use evidence_provenance::behaviour;
|
||||
use evidence_provenance::pyjson::{self, Json};
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn obj(pairs: Vec<(&str, Json)>) -> Json {
|
||||
Json::Obj(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
|
||||
}
|
||||
pub fn post_change_obligations(scope_files: &[String]) -> Vec<String> {
|
||||
scope_files
|
||||
.iter()
|
||||
.map(|f| format!("invalidate_behaviours: {}", f))
|
||||
|
||||
trait IntoJson {
|
||||
fn into_json(self) -> Json;
|
||||
}
|
||||
impl IntoJson for &str {
|
||||
fn into_json(self) -> Json {
|
||||
Json::Str(self.to_string())
|
||||
}
|
||||
}
|
||||
impl IntoJson for String {
|
||||
fn into_json(self) -> Json {
|
||||
Json::Str(self)
|
||||
}
|
||||
}
|
||||
impl IntoJson for Json {
|
||||
fn into_json(self) -> Json {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn arr<S: IntoJson>(v: Vec<S>) -> Json {
|
||||
Json::Arr(v.into_iter().map(IntoJson::into_json).collect())
|
||||
}
|
||||
|
||||
fn s(v: &Json, key: &str) -> String {
|
||||
match v.get(key) {
|
||||
Some(Json::Str(x)) => x.clone(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn entities(doc: &Json) -> Vec<&Json> {
|
||||
match doc.get("entities") {
|
||||
Some(Json::Arr(items)) => items.iter().collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Language advantage: the relationship vocabulary iteration queries is a
|
||||
/// closed lattice — `RelType::of` returns None for the open-world tail
|
||||
/// (calls/reads/writes/...) that the oracle simply ignores, and every
|
||||
/// query matches EXHAUSTIVELY over the lattice (a new queryable edge kind
|
||||
/// is a compiler-checked variant, not another wire string). The wire
|
||||
/// spelling lives in exactly one place per variant.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum RelType {
|
||||
BelongsTo,
|
||||
Owns,
|
||||
DependsOn,
|
||||
Provides,
|
||||
Consumes,
|
||||
VerifiedBy,
|
||||
DeployedBy,
|
||||
}
|
||||
|
||||
impl RelType {
|
||||
fn of(r: &Json) -> Option<RelType> {
|
||||
match s(r, "type").as_str() {
|
||||
"belongs_to" => Some(RelType::BelongsTo),
|
||||
"owns" => Some(RelType::Owns),
|
||||
"depends_on" => Some(RelType::DependsOn),
|
||||
"provides" => Some(RelType::Provides),
|
||||
"consumes" => Some(RelType::Consumes),
|
||||
"verified_by" => Some(RelType::VerifiedBy),
|
||||
"deployed_by" => Some(RelType::DeployedBy),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero-copy: relationships are BORROWED from the input doc, never cloned.
|
||||
fn relationships(doc: &Json) -> Vec<&Json> {
|
||||
match doc.get("relationships") {
|
||||
Some(Json::Arr(items)) => items.iter().collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_entity<'a>(doc: &'a Json, eid: &str) -> Option<&'a Json> {
|
||||
entities(doc).into_iter().find(|e| s(e, "id") == eid)
|
||||
}
|
||||
|
||||
/// Outgoing edges of `eid` with the given type, sorted+deduped (the
|
||||
/// oracle's `sorted(r["to"] ...)` law).
|
||||
fn edges_from(doc: &Json, eid: &str, ty: RelType) -> Vec<String> {
|
||||
let mut v: Vec<String> = relationships(doc)
|
||||
.into_iter()
|
||||
.filter(|r| RelType::of(r) == Some(ty) && s(r, "from") == eid)
|
||||
.map(|r| s(r, "to"))
|
||||
.collect();
|
||||
v.sort();
|
||||
v.dedup();
|
||||
v
|
||||
}
|
||||
|
||||
fn stale(r: &Json) -> bool {
|
||||
s(r, "status") == "stale"
|
||||
}
|
||||
|
||||
/// model.children: belongs_to edges reversed, relationship order kept
|
||||
/// (NOT sorted — the oracle preserves document order here).
|
||||
fn children(doc: &Json, eid: &str) -> Vec<String> {
|
||||
relationships(doc)
|
||||
.into_iter()
|
||||
.filter(|r| {
|
||||
RelType::of(r) == Some(RelType::BelongsTo)
|
||||
&& s(r, "to") == eid
|
||||
&& s(r, "from") != eid
|
||||
})
|
||||
.map(|r| s(r, "from"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// model.beneath: transitive descendants via LIFO frontier, then sorted.
|
||||
fn beneath(doc: &Json, eid: &str) -> Vec<String> {
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
seen.insert(eid.to_string());
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
let mut frontier: Vec<String> = vec![eid.to_string()];
|
||||
while let Some(cur) = frontier.pop() {
|
||||
for child in children(doc, &cur) {
|
||||
if seen.insert(child.clone()) {
|
||||
out.push(child.clone());
|
||||
frontier.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.sort();
|
||||
out
|
||||
}
|
||||
|
||||
fn owning_domain(doc: &Json, eid: &str) -> Json {
|
||||
let mut cur = eid.to_string();
|
||||
let mut hops = 0;
|
||||
while hops < 16 {
|
||||
if cur.starts_with("domain:") {
|
||||
return Json::Str(cur);
|
||||
}
|
||||
let parents: Vec<String> = relationships(doc)
|
||||
.into_iter()
|
||||
.filter(|r| {
|
||||
RelType::of(r) == Some(RelType::BelongsTo)
|
||||
&& s(r, "from") == cur
|
||||
})
|
||||
.map(|r| s(r, "to"))
|
||||
.collect();
|
||||
match parents.into_iter().next() {
|
||||
Some(p) => cur = p,
|
||||
None => return Json::Null,
|
||||
}
|
||||
hops += 1;
|
||||
}
|
||||
Json::Null
|
||||
}
|
||||
|
||||
fn dependencies(doc: &Json, eid: &str) -> Vec<String> {
|
||||
let mut v: Vec<String> = relationships(doc)
|
||||
.into_iter()
|
||||
.filter(|r| {
|
||||
RelType::of(r) == Some(RelType::DependsOn)
|
||||
&& s(r, "from") == eid
|
||||
&& !stale(r)
|
||||
})
|
||||
.map(|r| s(r, "to"))
|
||||
.collect();
|
||||
v.sort();
|
||||
v.dedup();
|
||||
v
|
||||
}
|
||||
|
||||
/// Who depends on OR consumes this entity (interfaces are consumed;
|
||||
/// components are depended on — Backstage relation pairs).
|
||||
fn consumers(doc: &Json, eid: &str) -> Vec<String> {
|
||||
let mut v: Vec<String> = relationships(doc)
|
||||
.into_iter()
|
||||
.filter(|r| {
|
||||
let ty = RelType::of(r);
|
||||
(ty == Some(RelType::DependsOn) || ty == Some(RelType::Consumes))
|
||||
&& s(r, "to") == eid
|
||||
&& !stale(r)
|
||||
})
|
||||
.map(|r| s(r, "from"))
|
||||
.collect();
|
||||
v.sort();
|
||||
v.dedup();
|
||||
v
|
||||
}
|
||||
|
||||
fn interfaces_for(doc: &Json, eid: &str) -> Vec<String> {
|
||||
edges_from(doc, eid, RelType::Provides)
|
||||
}
|
||||
|
||||
fn consumes_of(doc: &Json, eid: &str) -> Vec<String> {
|
||||
edges_from(doc, eid, RelType::Consumes)
|
||||
}
|
||||
|
||||
/// impact_closure — the structurally bounded scope (rule J), oracle-identical
|
||||
/// (Nx affected semantics, bounded reverse-dependency BFS).
|
||||
fn impact_closure(doc: &Json, eid: &str) -> Json {
|
||||
if get_entity(doc, eid).is_none() {
|
||||
return obj(vec![(
|
||||
"error",
|
||||
Json::Str(format!("unknown entity {}", py_repr_str(eid))),
|
||||
)]);
|
||||
}
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
seen.insert(eid.to_string());
|
||||
let mut consumers_set: HashSet<String> = HashSet::new();
|
||||
let mut frontier: Vec<String> = vec![eid.to_string()];
|
||||
let mut depth = 0usize;
|
||||
while !frontier.is_empty() && depth < 8 {
|
||||
let mut nxt = Vec::new();
|
||||
for cur in &frontier {
|
||||
for c in consumers(doc, cur) {
|
||||
if seen.insert(c.clone()) {
|
||||
consumers_set.insert(c.clone());
|
||||
nxt.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
frontier = nxt;
|
||||
depth += 1;
|
||||
}
|
||||
let direct_deps = dependencies(doc, eid);
|
||||
for iface in interfaces_for(doc, eid) {
|
||||
for c in consumers(doc, &iface) {
|
||||
if seen.insert(c.clone()) {
|
||||
consumers_set.insert(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut scope: BTreeSet<String> = BTreeSet::new();
|
||||
scope.insert(eid.to_string());
|
||||
scope.extend(consumers_set.iter().cloned());
|
||||
let mut state_surfaces: BTreeSet<String> = BTreeSet::new();
|
||||
let mut verification_scope: BTreeSet<String> = BTreeSet::new();
|
||||
let mut deployment_scope: BTreeSet<String> = BTreeSet::new();
|
||||
for e in std::iter::once(eid.to_string()).chain(consumers_set.iter().cloned()) {
|
||||
for x in edges_from(doc, &e, RelType::Owns) {
|
||||
state_surfaces.insert(x);
|
||||
}
|
||||
for x in edges_from(doc, &e, RelType::VerifiedBy) {
|
||||
verification_scope.insert(x);
|
||||
}
|
||||
for x in edges_from(doc, &e, RelType::DeployedBy) {
|
||||
deployment_scope.insert(x);
|
||||
}
|
||||
}
|
||||
let uncertainty = arr(vec![obj(vec![
|
||||
("kind", Json::Str("dynamic-consumer".into())),
|
||||
(
|
||||
"note",
|
||||
Json::Str(
|
||||
"static model cannot see dynamic dispatch/string-keyed \
|
||||
lookup/late-binding consumers \u{2014} reconcilers must \
|
||||
verify at runtime"
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
])]);
|
||||
obj(vec![
|
||||
("target", Json::Str(eid.to_string())),
|
||||
("owning_domain", owning_domain(doc, eid)),
|
||||
("mutation_scope", arr(scope.into_iter().collect::<Vec<_>>())),
|
||||
(
|
||||
"observation_scope",
|
||||
obj(vec![
|
||||
("direct_dependencies", arr(direct_deps)),
|
||||
(
|
||||
"interfaces_provided",
|
||||
arr(interfaces_for(doc, eid)),
|
||||
),
|
||||
("interfaces_consumed", arr(consumes_of(doc, eid))),
|
||||
]),
|
||||
),
|
||||
(
|
||||
"state_surfaces",
|
||||
arr(state_surfaces.into_iter().collect::<Vec<_>>()),
|
||||
),
|
||||
(
|
||||
"verification_scope",
|
||||
arr(verification_scope.into_iter().collect::<Vec<_>>()),
|
||||
),
|
||||
(
|
||||
"deployment_scope",
|
||||
arr(deployment_scope.into_iter().collect::<Vec<_>>()),
|
||||
),
|
||||
(
|
||||
"invariants",
|
||||
arr(vec![
|
||||
Json::Str(format!(
|
||||
"belongs_to hierarchy of {eid} remains stable"
|
||||
)),
|
||||
Json::Str("canonical-state ownership unchanged".into()),
|
||||
]),
|
||||
),
|
||||
("non_goals", Json::Arr(vec![])),
|
||||
("uncertainty", uncertainty),
|
||||
("closure_depth", Json::Num(depth as f64)),
|
||||
])
|
||||
}
|
||||
|
||||
// ── capability ledger floor (by_component / context_packet) ─────────────
|
||||
|
||||
const LEDGER_SCHEMA_VERSION: f64 = 1.0;
|
||||
|
||||
fn ledger_by_component<'a>(ledger: &'a Json, component: &str) -> Vec<&'a Json> {
|
||||
match ledger.get("capabilities") {
|
||||
Some(Json::Obj(pairs)) => pairs
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.filter(|r| {
|
||||
let m = s(r, "maturity");
|
||||
m != "retired" && m != "superseded" && s(r, "component") == component
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ledger_context_packet(ledger: &Json, capability_id: &str) -> Json {
|
||||
let key = if capability_id.contains(':') {
|
||||
capability_id.to_string()
|
||||
} else {
|
||||
format!("capability:{capability_id}")
|
||||
};
|
||||
let rec = match ledger.get("capabilities") {
|
||||
Some(Json::Obj(pairs)) => pairs
|
||||
.iter()
|
||||
.find(|(k, _)| k.as_str() == key)
|
||||
.map(|(_, r)| r),
|
||||
_ => None,
|
||||
};
|
||||
let rec = match rec {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
return obj(vec![(
|
||||
"error",
|
||||
Json::Str(format!("unknown capability {}", py_repr_str(capability_id))),
|
||||
)])
|
||||
}
|
||||
};
|
||||
let fields = [
|
||||
"capability_id", "name", "purpose", "component",
|
||||
"source_locations", "contract", "state_touched", "dependencies",
|
||||
"consumers", "invariants", "behaviour_specs", "maturity",
|
||||
"constraints", "generation", "limitations",
|
||||
];
|
||||
let mut pairs: Vec<(String, Json)> =
|
||||
vec![("schema_version".into(), Json::Num(LEDGER_SCHEMA_VERSION))];
|
||||
for f in fields {
|
||||
pairs.push((f.to_string(), rec.get(f).cloned().unwrap_or(Json::Null)));
|
||||
}
|
||||
Json::Obj(pairs)
|
||||
}
|
||||
|
||||
// ── 19E: context_packet ──────────────────────────────────────────────────
|
||||
|
||||
pub fn context_packet(
|
||||
model: &Json,
|
||||
ledger: &Json,
|
||||
target: &str,
|
||||
intent: &str,
|
||||
basis_summary: Json,
|
||||
max_local_files: usize,
|
||||
now: &str,
|
||||
) -> Json {
|
||||
let entity = match get_entity(model, target) {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
return obj(vec![(
|
||||
"error",
|
||||
Json::Str(format!("unknown structural target {}", py_repr_str(target))),
|
||||
)])
|
||||
}
|
||||
};
|
||||
let caps = ledger_by_component(ledger, target);
|
||||
let local = match caps.first() {
|
||||
Some(cap) => ledger_context_packet(ledger, &s(cap, "capability_id")),
|
||||
None => Json::Null,
|
||||
};
|
||||
let has_source = |e: &Json| -> bool {
|
||||
let src = match e.get("identities") {
|
||||
Some(i) => match i.get("source") {
|
||||
Some(Json::Str(x)) => !x.is_empty(),
|
||||
_ => false,
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
src
|
||||
};
|
||||
let beneath_count = beneath(model, target)
|
||||
.into_iter()
|
||||
.filter(|b| match get_entity(model, b) {
|
||||
Some(e) => !has_source(e),
|
||||
None => false,
|
||||
})
|
||||
.count();
|
||||
let mut files: Vec<String> = Vec::new();
|
||||
for eid in std::iter::once(target.to_string())
|
||||
.chain(children(model, target))
|
||||
{
|
||||
let e = get_entity(model, &eid);
|
||||
let mut src = String::new();
|
||||
if let Some(e) = e {
|
||||
if let Some(Json::Str(x)) = e
|
||||
.get("ext")
|
||||
.and_then(|x| x.get("source"))
|
||||
{
|
||||
src = x.clone();
|
||||
}
|
||||
if src.is_empty() {
|
||||
if let Some(Json::Str(x)) = e
|
||||
.get("identities")
|
||||
.and_then(|x| x.get("source"))
|
||||
{
|
||||
src = x.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
if !src.is_empty() {
|
||||
files.push(src);
|
||||
}
|
||||
}
|
||||
for cap in &caps {
|
||||
if let Some(Json::Arr(locs)) = cap.get("source_locations") {
|
||||
for l in locs {
|
||||
if let Json::Str(x) = l {
|
||||
files.push(x.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut file_set: BTreeSet<String> = BTreeSet::new();
|
||||
file_set.extend(files);
|
||||
let local_files: Vec<String> =
|
||||
file_set.into_iter().take(max_local_files).collect();
|
||||
let closure = impact_closure(model, target);
|
||||
let mut iface_consumers: BTreeSet<String> = BTreeSet::new();
|
||||
for iface in interfaces_for(model, target) {
|
||||
for c in consumers(model, &iface) {
|
||||
if c != target {
|
||||
iface_consumers.insert(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut connected_consumers: BTreeSet<String> = BTreeSet::new();
|
||||
connected_consumers.extend(consumers(model, target));
|
||||
connected_consumers.extend(iface_consumers.iter().cloned());
|
||||
let uncertainty = closure.get("uncertainty").cloned().unwrap_or(Json::Arr(vec![]));
|
||||
let connected = obj(vec![
|
||||
("dependencies", arr(dependencies(model, target))),
|
||||
(
|
||||
"consumers",
|
||||
arr(connected_consumers.into_iter().collect::<Vec<_>>()),
|
||||
),
|
||||
(
|
||||
"interface_mediated_consumers",
|
||||
arr(iface_consumers.into_iter().collect::<Vec<_>>()),
|
||||
),
|
||||
("interfaces_provided", arr(interfaces_for(model, target))),
|
||||
("state_owned", arr(edges_from(model, target, RelType::Owns))),
|
||||
(
|
||||
"verification",
|
||||
arr(edges_from(model, target, RelType::VerifiedBy)),
|
||||
),
|
||||
("deployment", arr(edges_from(model, target, RelType::DeployedBy))),
|
||||
("uncertainty", uncertainty),
|
||||
]);
|
||||
let target_kind = match entity.get("kind") {
|
||||
Some(Json::Str(k)) => Json::Str(k.clone()),
|
||||
_ => Json::Null,
|
||||
};
|
||||
obj(vec![
|
||||
("schema_version", Json::Num(1.0)),
|
||||
("intent", Json::Str(intent.to_string())),
|
||||
("target", Json::Str(target.to_string())),
|
||||
("target_kind", target_kind),
|
||||
("owning_domain", owning_domain(model, target)),
|
||||
("local", local),
|
||||
("local_files", arr(local_files)),
|
||||
("beneath_count", Json::Num(beneath_count as f64)),
|
||||
("connected", connected),
|
||||
("basis_summary", basis_summary),
|
||||
("generated_at", Json::Str(now.to_string())),
|
||||
])
|
||||
}
|
||||
|
||||
// ── 19H: predictive vs descriptive impact ────────────────────────────────
|
||||
|
||||
pub fn predictive_impact(model: &Json, target: &str) -> Json {
|
||||
impact_closure(model, target)
|
||||
}
|
||||
|
||||
pub fn descriptive_impact(
|
||||
model: &Json,
|
||||
target: &str,
|
||||
observed_changed: &[String],
|
||||
generation: &str,
|
||||
now: &str,
|
||||
) -> Json {
|
||||
let predicted = impact_closure(model, target);
|
||||
let predicted_scope: HashSet<String> = match predicted.get("mutation_scope") {
|
||||
Some(Json::Arr(items)) => items
|
||||
.iter()
|
||||
.filter_map(|v| match v {
|
||||
Json::Str(x) => Some(x.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => HashSet::new(),
|
||||
};
|
||||
let observed: HashSet<String> = observed_changed.iter().cloned().collect();
|
||||
let mut predicted_sorted: Vec<String> =
|
||||
predicted_scope.iter().cloned().collect();
|
||||
predicted_sorted.sort();
|
||||
let mut observed_sorted: Vec<String> = observed.iter().cloned().collect();
|
||||
observed_sorted.sort();
|
||||
let mut unexpected: Vec<String> =
|
||||
observed.difference(&predicted_scope).cloned().collect();
|
||||
unexpected.sort();
|
||||
let mut unaffected: Vec<String> =
|
||||
predicted_scope.difference(&observed).cloned().collect();
|
||||
unaffected.sort();
|
||||
obj(vec![
|
||||
("target", Json::Str(target.to_string())),
|
||||
("generation", Json::Str(generation.to_string())),
|
||||
("predicted", arr(predicted_sorted)),
|
||||
("observed", arr(observed_sorted)),
|
||||
("unexpected", arr(unexpected)),
|
||||
("unaffected_as_predicted", arr(unaffected)),
|
||||
("recorded_at", Json::Str(now.to_string())),
|
||||
])
|
||||
}
|
||||
|
||||
/// record_impact_delta's exact line body (json.dumps sort_keys parity);
|
||||
/// the trailing newline is the facade's file-append concern.
|
||||
pub fn record_delta_line(delta: &Json) -> String {
|
||||
pyjson::dumps_sorted(delta)
|
||||
}
|
||||
|
||||
// ── 19I: selective invalidation bridge ───────────────────────────────────
|
||||
|
||||
pub fn invalidate_behaviours(
|
||||
behaviours_doc: &mut Json,
|
||||
changed: &[String],
|
||||
generation: &str,
|
||||
now: &str,
|
||||
) -> Result<Json, String> {
|
||||
behaviour::invalidate_for_change(behaviours_doc, changed, generation, &Json::Null, now)
|
||||
}
|
||||
|
||||
// ── 19K: post-change obligations ─────────────────────────────────────────
|
||||
|
||||
fn py_repr_str(x: &str) -> String {
|
||||
let mut out = String::from("'");
|
||||
for c in x.chars() {
|
||||
match c {
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\'' => out.push_str("\\'"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
_ => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push('\'');
|
||||
out
|
||||
}
|
||||
|
||||
fn py_list_repr(items: &[String]) -> String {
|
||||
let inner: Vec<String> = items.iter().map(|i| py_repr_str(i)).collect();
|
||||
format!("[{}]", inner.join(", "))
|
||||
}
|
||||
|
||||
pub fn post_change_obligations(
|
||||
model: &Json,
|
||||
target: &str,
|
||||
changed: Option<&[String]>,
|
||||
behaviours: Option<&Json>,
|
||||
) -> Json {
|
||||
let changed_set: BTreeSet<String> = match changed {
|
||||
Some(c) if !c.is_empty() => c.iter().cloned().collect(),
|
||||
_ => {
|
||||
let mut s = BTreeSet::new();
|
||||
s.insert(target.to_string());
|
||||
s
|
||||
}
|
||||
};
|
||||
let mut obligations: Vec<Json> = Vec::new();
|
||||
let mut seen: HashSet<(String, String)> = HashSet::new();
|
||||
let add = |kind: &str, target_e: &str, why: String,
|
||||
obligations: &mut Vec<Json>, seen: &mut HashSet<(String, String)>| {
|
||||
if seen.insert((kind.to_string(), target_e.to_string())) {
|
||||
obligations.push(obj(vec![
|
||||
("kind", Json::Str(kind.to_string())),
|
||||
("target", Json::Str(target_e.to_string())),
|
||||
("why", Json::Str(why)),
|
||||
]));
|
||||
}
|
||||
};
|
||||
for eid in changed_set.iter().map(|x| x.as_str()).collect::<Vec<_>>() {
|
||||
for consumer in consumers(model, eid) {
|
||||
add(
|
||||
"inspect_consumer",
|
||||
&consumer,
|
||||
format!("consumes/depends on changed {eid}"),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
}
|
||||
for iface in interfaces_for(model, eid) {
|
||||
add(
|
||||
"verify_interface",
|
||||
&iface,
|
||||
format!("provided by changed {eid}"),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
for consumer in consumers(model, &iface) {
|
||||
add(
|
||||
"inspect_consumer",
|
||||
&consumer,
|
||||
format!("consumes {iface} provided by changed {eid}"),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
}
|
||||
}
|
||||
for store in edges_from(model, eid, RelType::Owns) {
|
||||
add(
|
||||
"check_state_compatibility",
|
||||
&store,
|
||||
format!("state owned by changed {eid}"),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
}
|
||||
for iface in consumes_of(model, eid) {
|
||||
add(
|
||||
"check_interface_contract",
|
||||
&iface,
|
||||
format!("changed {eid} consumes {iface} \u{2014} contract must still hold"),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
}
|
||||
for dep in edges_from(model, eid, RelType::DeployedBy) {
|
||||
add(
|
||||
"verify_deployment",
|
||||
&dep,
|
||||
format!("deployed surface of changed {eid}"),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(bh) = behaviours {
|
||||
if let Some(Json::Obj(pairs)) = bh.get("behaviours") {
|
||||
for (_, rec) in pairs {
|
||||
let mut deps: HashSet<String> = HashSet::new();
|
||||
if let Some(Json::Arr(items)) = rec.get("dependencies") {
|
||||
for d in items {
|
||||
if let Json::Str(x) = d {
|
||||
deps.insert(x.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(Json::Str(o)) = rec.get("owner_component") {
|
||||
// Python unions {rec.get("owner_component")} — a None
|
||||
// owner lands in deps but can never intersect the
|
||||
// all-string changed set, so it is simply skipped.
|
||||
deps.insert(o.clone());
|
||||
}
|
||||
let inter: Vec<String> = deps
|
||||
.iter()
|
||||
.filter(|d| changed_set.contains(*d))
|
||||
.cloned()
|
||||
.collect();
|
||||
if !inter.is_empty() {
|
||||
let mut sorted_inter = inter.clone();
|
||||
sorted_inter.sort();
|
||||
let bid = s(rec, "behaviour_id");
|
||||
add(
|
||||
"retest",
|
||||
&bid,
|
||||
format!("depends on changed {}", py_list_repr(&sorted_inter)),
|
||||
&mut obligations,
|
||||
&mut seen,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Json::Arr(obligations)
|
||||
}
|
||||
|
||||
// ── FFI (doc-envelope) ───────────────────────────────────────────────────
|
||||
|
||||
fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
|
||||
let body = match result {
|
||||
Ok(value) => obj(vec![
|
||||
("ok", Json::Bool(true)),
|
||||
("value", value),
|
||||
]),
|
||||
Err((err, ty)) => obj(vec![
|
||||
("ok", Json::Bool(false)),
|
||||
("err", Json::Str(err)),
|
||||
("type", Json::Str(ty)),
|
||||
]),
|
||||
};
|
||||
let out = pyjson::dumps(&body);
|
||||
match CString::new(out) {
|
||||
Ok(c) => c.into_raw(),
|
||||
Err(_) => CString::new(r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#)
|
||||
.unwrap()
|
||||
.into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_doc(raw: &str) -> Result<Json, (String, String)> {
|
||||
pyjson::parse(raw).map_err(|e| (e, "ValueError".to_string()))
|
||||
}
|
||||
|
||||
fn read_arg(ptr: *const c_char) -> Result<String, (String, String)> {
|
||||
if ptr.is_null() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
unsafe { CStr::from_ptr(ptr) }
|
||||
.to_str()
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|e| (e.to_string(), "ValueError".to_string()))
|
||||
}
|
||||
|
||||
macro_rules! ffi {
|
||||
($name:ident, $body:expr) => {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn $name(arg: *const c_char) -> *mut c_char {
|
||||
let out: Result<Json, (String, String)> =
|
||||
read_arg(arg).and_then(|raw| $body(&raw));
|
||||
envelope(out)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn str_list(v: Option<&Json>) -> Vec<String> {
|
||||
match v {
|
||||
Some(Json::Arr(items)) => items
|
||||
.iter()
|
||||
.filter_map(|x| match x {
|
||||
Json::Str(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
ffi!(itr_context_packet, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let model = doc.get("model").cloned().unwrap_or(Json::Null);
|
||||
let ledger = doc.get("ledger").cloned().unwrap_or(Json::Null);
|
||||
let target = s(&doc, "target");
|
||||
let intent = match doc.get("intent") {
|
||||
Some(Json::Str(x)) => x.clone(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let basis = doc.get("basis_summary").cloned().unwrap_or(Json::Null);
|
||||
let max_local = match doc.get("max_local_files") {
|
||||
Some(Json::Num(n)) => *n as usize,
|
||||
_ => 12,
|
||||
};
|
||||
let now = s(&doc, "now");
|
||||
Ok(context_packet(&model, &ledger, &target, &intent, basis, max_local, &now))
|
||||
});
|
||||
|
||||
ffi!(itr_predictive_impact, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let model = doc.get("model").cloned().unwrap_or(Json::Null);
|
||||
let target = s(&doc, "target");
|
||||
Ok(predictive_impact(&model, &target))
|
||||
});
|
||||
|
||||
ffi!(itr_descriptive_impact, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let model = doc.get("model").cloned().unwrap_or(Json::Null);
|
||||
let target = s(&doc, "target");
|
||||
let observed = str_list(doc.get("observed_changed"));
|
||||
let generation = match doc.get("generation") {
|
||||
Some(Json::Str(x)) => x.clone(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let now = s(&doc, "now");
|
||||
Ok(descriptive_impact(&model, &target, &observed, &generation, &now))
|
||||
});
|
||||
|
||||
ffi!(itr_record_delta_line, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let delta = doc.get("delta").cloned().unwrap_or(Json::Null);
|
||||
Ok(Json::Str(record_delta_line(&delta)))
|
||||
});
|
||||
|
||||
ffi!(itr_invalidate, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let mut bh = doc.get("behaviours").cloned().unwrap_or(Json::Null);
|
||||
let changed = str_list(doc.get("changed"));
|
||||
let generation = match doc.get("generation") {
|
||||
Some(Json::Str(x)) => x.clone(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let now = s(&doc, "now");
|
||||
match invalidate_behaviours(&mut bh, &changed, &generation, &now) {
|
||||
Ok(recs) => Ok(obj(vec![("doc", bh), ("recs", recs)])),
|
||||
Err(e) => Err((e, "RuntimeError".to_string())),
|
||||
}
|
||||
});
|
||||
|
||||
ffi!(itr_post_change_obligations, |raw: &str| -> Result<Json, (String, String)> {
|
||||
let doc = parse_doc(raw)?;
|
||||
let model = doc.get("model").cloned().unwrap_or(Json::Null);
|
||||
let target = s(&doc, "target");
|
||||
let changed = doc.get("changed").map(|c| str_list(Some(c)));
|
||||
let behaviours = doc.get("behaviours").cloned();
|
||||
Ok(post_change_obligations(
|
||||
&model,
|
||||
&target,
|
||||
changed.as_deref(),
|
||||
behaviours.as_ref(),
|
||||
))
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod branch_battery;
|
||||
@@ -306,10 +306,10 @@
|
||||
"side": "RUST"
|
||||
},
|
||||
"connection_pool": {
|
||||
"evidence": "sqlite3 (storage-backed); network/concurrency imports: ['threading']",
|
||||
"evidence": "deterministic pool bookkeeping \u2014 programme verdict RUST (dichotomy wave #2479 batch; sqlite handles stay in the Python facade, kernel owns pooling order/eviction/status)",
|
||||
"functions": 9,
|
||||
"lines": 144,
|
||||
"side": "GO"
|
||||
"side": "RUST"
|
||||
},
|
||||
"construction_activation": {
|
||||
"evidence": "deterministic/state domain",
|
||||
@@ -1079,4 +1079,4 @@
|
||||
"lines": 591,
|
||||
"side": "GO"
|
||||
}
|
||||
}
|
||||
}
|
||||
+179
-30248
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,24 @@
|
||||
# Contract BUILDER-2026-10-12T16:46:40Z-b49130-factory-batch-a
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Port src/rune/iteration.py to FULL Rust (dichotomy factory, issue #2479 tranche): doc-envelope FFI, ctypes loader, facade delegation pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
|
||||
|
||||
## What it connects to
|
||||
|
||||
(no affected surface recorded)
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T17:53:30Z", "contract": "BUILDER-2026-10-12T16:46:40Z-b49130-factory-batch-a", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_iteration_truth.py", "src/rune/iteration.py", "src/rune/itr_loader.py", "src/rust/iteration/src/branch_battery.rs", "src/rust/iteration/src/lib.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-batch-a
|
||||
- builder:factory-batch-a
|
||||
- builder:factory-batch-a
|
||||
- closure-record
|
||||
@@ -0,0 +1,24 @@
|
||||
# Contract BUILDER-2026-10-12T17:14:32Z-844644-factory-batch-b
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Complete the construction_depth dichotomy port to FULL Rust (#2479 tranche): oracle-exact depth kernel (UNKNOWN ceremony/bypassed, PROJECT_BUILD full-lifecycle strings, EXISTING_PROJECT_CHANGE base ceremony + wide-surface escalation restored to oracle truth), doc-envelope FFI (cd_select_depth, free via capability_scoring cs_free_string — no free re-export), cd_loader mirroring broker_loader.py, facade delegation with RUNE_CD_CORE=python pin, frozen-oracle battery (40 cases, every public function/branch), live parity 100%, Phase B language-advantage pass.
|
||||
|
||||
## What it connects to
|
||||
|
||||
(no affected surface recorded)
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T17:16:29Z", "contract": "BUILDER-2026-10-12T17:14:32Z-844644-factory-batch-b", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/close_contract_construction_depth.py", "scripts/gen_construction_depth_truth.py", "scripts/walk_contract_construction_depth.py", "src/rune/cd_loader.py", "src/rune/construction_depth.py", "src/rust/construction_depth/canary_construction_depth.py", "src/rust/construction_depth/frozen_cd_truth.json", "src/rust/construction_depth/src/branch_battery.rs", "src/rust/construction_depth/src/lib.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-batch-b
|
||||
- builder:factory-batch-b
|
||||
- builder:factory-batch-b
|
||||
- closure-record
|
||||
@@ -0,0 +1,24 @@
|
||||
# Contract BUILDER-2026-10-12T17:41:58Z-4eb5a0-factory-batch-b
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Complete the connection_pool dichotomy port to FULL Rust (#2479 tranche, programme verdict RUST): pool-bookkeeping kernel (pooling order, stale removal, oldest-first eviction, status) as doc-envelope FFI (cpool_new/begin_get/commit/close_all/status, free via capability_scoring cs_free_string — no free re-export); cpool_loader mirroring broker_loader.py; facade delegation with RUNE_CPOOL_CORE=python pin (sqlite handles stay in the facade); frozen-oracle battery (16 cases, every kernel branch, frozen against a real sqlite-backed pool); live parity 100% (60-seed random-op sweep); classification flip GO→RUST; Phase B language-advantage pass.
|
||||
|
||||
## What it connects to
|
||||
|
||||
(no affected surface recorded)
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T17:44:17Z", "contract": "BUILDER-2026-10-12T17:41:58Z-4eb5a0-factory-batch-b", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/close_contract_connection_pool.py", "scripts/gen_connection_pool_truth.py", "scripts/walk_contract_connection_pool.py", "src/rune/connection_pool.py", "src/rune/cpool_loader.py", "src/rust/connection_pool/frozen_cpool_truth.json", "src/rust/connection_pool/src/branch_battery.rs", "src/rust/connection_pool/src/lib.rs", "store/rebase/classification.json", "tests/test_connection_pool.py"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-batch-b
|
||||
- builder:factory-batch-b
|
||||
- builder:factory-batch-b
|
||||
- closure-record
|
||||
@@ -0,0 +1,24 @@
|
||||
# Contract BUILDER-2026-10-12T17:43:29Z-ea9485-factory-batch-c
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Port src/rune/forge.py to FULL Go (dichotomy factory, issue #2479 tranche, batch-c): the §18L forge boundary — ForgeAdapter protocol, ForgeUnavailable, GiteaForge (env/push/commit_status/ci_runs_for_sha/create_or_patch_release), default_forge, and the scripts/gitea_auth credential ladder — name-preserving in src/go/forge with IO behind CommandRunner/HTTPTransport/TokenSource seams, frozen-truth battery (34 rows), live parity 100%, Phase B goroutine fan-out (Snapshot), compile-time seam assertions.
|
||||
|
||||
## What it connects to
|
||||
|
||||
(no affected surface recorded)
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T17:57:50Z", "contract": "BUILDER-2026-10-12T17:43:29Z-ea9485-factory-batch-c", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_forge_truth.py", "src/go/forge/main.go", "src/go/forge/phaseb.go", "src/go/forge/phaseb_test.go", "src/go/forge/truth.json", "src/go/forge/truth_test.go", "src/rune/forge.py"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-batch-c
|
||||
- builder:factory-batch-c
|
||||
- builder:factory-batch-c
|
||||
- closure-record
|
||||
@@ -0,0 +1,24 @@
|
||||
# Contract BUILDER-2026-10-12T17:46:05Z-c1a38e-factory-batch-c
|
||||
|
||||
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
|
||||
|
||||
## What was built
|
||||
|
||||
Port src/rune/verification_layer.py to FULL Go (dichotomy factory, issue #2479 tranche, batch-c): all seven verifiers (exit_code, unit_test, schema, file_exists, file_changed, tool_receipt, constraint_check) + unknown-verifier dispatch, VerificationResult.to_dict byte-rendering, handle_hook, PLUGIN manifest — the Rust evidence_provenance semantics carried 1:1 (verification.rs), name-preserving in src/go/verification_layer with the FS probe behind a seam, frozen-truth battery (62 rows), live parity 100%, Phase B Verifier registry + VerifyAll goroutine fan-out.
|
||||
|
||||
## What it connects to
|
||||
|
||||
(no affected surface recorded)
|
||||
|
||||
## How to update those connections
|
||||
|
||||
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
|
||||
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
|
||||
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T17:59:52Z", "contract": "BUILDER-2026-10-12T17:46:05Z-c1a38e-factory-batch-c", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_verification_layer_truth.py", "src/go/verification_layer/main.go", "src/go/verification_layer/phaseb.go", "src/go/verification_layer/phaseb_test.go", "src/go/verification_layer/truth.json", "src/go/verification_layer/truth_test.go", "src/rune/verification_layer.py"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
|
||||
|
||||
## Evidence
|
||||
|
||||
- builder:factory-batch-c
|
||||
- builder:factory-batch-c
|
||||
- builder:factory-batch-c
|
||||
- closure-record
|
||||
@@ -0,0 +1,69 @@
|
||||
"""connection_pool facade — kernel delegation + oracle-floor parity
|
||||
(dichotomy wave #2479 tranche). Exercises the pool bookkeeping through the
|
||||
facade in BOTH core modes: RUNE_CPOOL_CORE auto (Rust kernel when built)
|
||||
and =python (oracle floor pinned)."""
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||
|
||||
from rune.connection_pool import ConnectionPool # noqa: E402
|
||||
|
||||
|
||||
def _scenario(core_pin: str) -> list[dict]:
|
||||
os.environ["RUNE_CPOOL_CORE"] = core_pin
|
||||
snapshots = []
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
p = ConnectionPool(max_connections=2)
|
||||
a, b, c, d = [os.path.join(td, f"{n}.db") for n in "ABCD"]
|
||||
conn_a = p.get_connection(a)
|
||||
snapshots.append(("create-a", p.get_status(), conn_a is p.connections[a]))
|
||||
snapshots.append(("reuse-a", p.get_status(),
|
||||
p.get_connection(a) is conn_a))
|
||||
p.get_connection(b)
|
||||
snapshots.append(("create-b", p.get_status(), None))
|
||||
p.get_connection(c)
|
||||
snapshots.append(("evict-oldest", p.get_status(),
|
||||
"a" not in " ".join(p.get_status()["databases"]).lower()))
|
||||
p.connections[b].close() # force stale
|
||||
p.get_connection(b)
|
||||
snapshots.append(("stale-b", p.get_status(), None))
|
||||
p.get_connection(d)
|
||||
snapshots.append(("evict-again", p.get_status(), None))
|
||||
snapshots.append(("status", p.get_status(), None))
|
||||
p.close_all()
|
||||
snapshots.append(("closed", p.get_status(), None))
|
||||
# pool is fresh again: new connection can be created
|
||||
snapshots.append(("fresh", p.get_status(),
|
||||
isinstance(p.get_connection(a), sqlite3.Connection)))
|
||||
# temp dirs differ per run — compare bookkeeping on basenames
|
||||
norm = []
|
||||
for name, st, flag in snapshots:
|
||||
st = dict(st)
|
||||
st["databases"] = [os.path.basename(d) for d in st["databases"]]
|
||||
norm.append((name, st, flag))
|
||||
return norm
|
||||
|
||||
|
||||
def test_kernel_matches_oracle_floor():
|
||||
auto = _scenario("auto")
|
||||
py = _scenario("python")
|
||||
assert len(auto) == len(py)
|
||||
for (name_a, st_a, flag_a), (name_p, st_p, flag_p) in zip(auto, py):
|
||||
assert name_a == name_p
|
||||
# the Rust kernel must reproduce the oracle bookkeeping exactly
|
||||
assert st_a == st_p, f"{name_a}: {st_a} != {st_p}"
|
||||
assert flag_a == flag_p, name_a
|
||||
|
||||
|
||||
def test_module_level_helpers():
|
||||
os.environ["RUNE_CPOOL_CORE"] = "auto"
|
||||
from rune import connection_pool as cp
|
||||
assert cp.PLUGIN["name"] == "connection_pool"
|
||||
status = cp.get_status()
|
||||
assert set(status) == {"connections", "max_connections", "databases"}
|
||||
cp.close_all()
|
||||
assert cp.get_status()["connections"] == 0
|
||||
@@ -46,7 +46,8 @@ def test_new_skill_standard():
|
||||
def test_new_standalone_project_full():
|
||||
d = select_depth("PROJECT_BUILD")
|
||||
assert d.level == "FULL"
|
||||
assert "all obligations" in " ".join(d.ceremony)
|
||||
# oracle truth (§12E): PROJECT_BUILD carries the full lifecycle ceremony
|
||||
assert "project bootstrap" in " ".join(d.ceremony)
|
||||
|
||||
|
||||
def test_ambiguous_unresolved_not_light():
|
||||
@@ -67,9 +68,10 @@ def test_non_construction_no_construction_depth():
|
||||
def test_wide_surface_escalates_to_full():
|
||||
d = select_depth("EXISTING_PROJECT_CHANGE",
|
||||
affected_surface=[f"f{i}.py" for i in range(8)])
|
||||
# ORACLE (verified 2026-10-10): wide surface records evidence but does NOT
|
||||
# escalate EXISTING_PROJECT_CHANGE beyond STANDARD
|
||||
assert d.level == "STANDARD"
|
||||
# oracle truth (T12E §12E, src/rune/construction_depth.py): >5 entries
|
||||
# escalates EXISTING_PROJECT_CHANGE to FULL — the facade-burn batch
|
||||
# flipped this to kernel behavior; the live oracle always escalated
|
||||
assert d.level == "FULL"
|
||||
|
||||
|
||||
def test_high_uncertainty_probe_first():
|
||||
|
||||
Reference in new issue
Block a user