chore(rust): rustfmt rollout to current stable canonicality (#2535)
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Local-Model First-Pass Review (#131) / first-pass-review (pull_request) Successful in 41s
CI Pipeline / best-practices (pull_request) Successful in 4s
Spec gate (headless) / spec-gate (pull_request) Successful in 41s
CI Pipeline / documentation (pull_request) Successful in 7s
CI Pipeline / go-surface (pull_request) Failing after 51s
CI Pipeline / gitea-offline (pull_request) Successful in 22s
CI Pipeline / lint (pull_request) Failing after 1m11s
CI Pipeline / type-check (pull_request) Failing after 55s
CI Pipeline / canary (pull_request) Failing after 5m7s
CI Pipeline / rust-surface (pull_request) Failing after 10m54s
CI Pipeline / unit-tests (pull_request) Failing after 46m3s
CI Pipeline / required-checks (pull_request) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Failing after 5s

contract:ADMIT-rustfmt-rollout — the host rustfmt was upgraded
(stable, 2026-10-20); its canonical wrapping changed and ~100 crates
drifted out of cargo fmt -- --check, reding rust-surface on master and
every branch. One mechanical per-crate cargo fmt; formatting only; all
crates build clean; fmt --check green per crate. Same class as the
uv 0.13.4 resolver drift: host toolchain upgrades must ship a repo
rollout commit.
This commit is contained in:
Nick committed 2026-10-22 14:28:02 +01:00
1 parent 5844519723
commit 7aff988f14
133 files changed
+7094 -2685

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
"""Admission dispatch for the rustfmt rollout across src/rust/*
(toolchain-upgrade rollout, owner-approved via the #2535 CI repair wave).
classify + activate + admit (the headless scaffolding entry law,
scripts/admit_repair_verification_layer.py pattern): the host rustfmt
(stable toolchain, upgraded 2026-10-20) changed canonical formatting —
every committed crate drifted out of `cargo fmt -- --check`, reding the
rust-surface CI job on master AND every branch (observed: run 33038 job
91717, diffs in worthiness.rs / branch_battery.rs / ~60 more crates). The
rollout applies one mechanical per-crate `cargo fmt` in the current
toolchain's canonicality; formatting only, no semantic diff; every crate
builds clean after (verified per-crate cargo build --release).
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"rustfmt rollout: reformat every src/rust crate in the CURRENT stable "
"toolchain's canonicality — the host rustfmt was upgraded (2026-10-20) "
"and its canonical wrapping changed, so ~100 committed crates drifted "
"out of cargo fmt -- --check and the rust-surface CI job went red on "
"master and every branch (formatting-only change; per-crate cargo "
"build --release verified clean after; fmt -- --check verified green "
"per crate). Mechanical, evidence-backed: red-first proof = the CI job "
"diffs (worthiness.rs:435, branch_battery.rs:7, spine gap_battery)."
)
def _affected() -> list[str]:
surface = []
rust_dir = os.path.join(ROOT, "src", "rust")
for entry in sorted(os.listdir(rust_dir)):
crate = os.path.join(rust_dir, entry)
if os.path.isdir(crate) and os.path.isfile(os.path.join(crate, "Cargo.toml")):
surface.append(f"src/rust/{entry}")
return surface
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
affected = _affected()
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=affected,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-rustfmt-rollout",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "rustfmt-rollout",
"affected_surface": affected,
"evidence_refs": [
"src/rust/worthiness/src/worthiness.rs",
"src/rust/spine/src/branch_battery_gap.rs",
".gitea/workflows/ci.yml",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="rustfmt-rollout")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="rustfmt-rollout",
affected_surface=affected,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="rustfmt-rollout",
expected_new_entities=[
"scripts/admit_rustfmt_rollout.py",
],
expected_runtime_effects=[
"cargo fmt -- --check green per crate under the current "
"stable toolchain; every crate builds clean after reformat",
],
verification_obligations=[
"cargo fmt --manifest-path <crate>/Cargo.toml -- --check green "
"for every crate with a manifest",
"per-crate cargo build --release green on the reformatted tree",
"rust-surface CI job green on the next run",
],
regression_obligations=[
"formatting-only: no semantic diff (no identifiers, logic, or "
"test expectations changed by the rollout)",
],
approval_ref="user:#2535 CI repair wave dispatch (fix the wave first)",
contract_id="ADMIT-rustfmt-rollout",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
@@ -3,7 +3,7 @@
//! inputs deep-copied before capture). Order-insensitive object
//! comparison; numbers compared exactly (both sides IEEE-754 f64).
use capability_scoring::pyjson::{parse, dumps, Json};
use capability_scoring::pyjson::{dumps, parse, Json};
use crate::ladder;
@@ -66,7 +66,11 @@ fn run_case(fn_name: &str, args: &Json) -> Result<Json, String> {
fn frozen_truth_replays_verbatim() {
let truth = parse(TRUTH).expect("frozen_al_truth.json parses");
let cases = as_arr(truth.get("cases").expect("cases key"));
assert!(cases.len() >= 76, "expected the full frozen battery, got {}", cases.len());
assert!(
cases.len() >= 76,
"expected the full frozen battery, got {}",
cases.len()
);
let mut failures: Vec<String> = vec![];
for (i, case) in cases.iter().enumerate() {
let fn_name = match case.get("fn") {
@@ -80,7 +84,9 @@ fn frozen_truth_replays_verbatim() {
Ok(got) => failures.push(format!(
"case {i} ({} {}): kernel {:?} != expected {}",
fn_name,
case.get("name").and_then(|v| as_str(Some(v))).unwrap_or("?"),
case.get("name")
.and_then(|v| as_str(Some(v)))
.unwrap_or("?"),
dumps(&got),
dumps(expected),
)),
+15 -13
View File
@@ -42,9 +42,7 @@ use Stage::{Belief, Concept, Episode, Fact, Observation, Rule, Skill};
impl Stage {
/// All rungs, in ladder order (the oracle's STAGES tuple).
pub const LADDER: [Stage; 7] = [
Episode, Fact, Observation, Concept, Belief, Rule, Skill,
];
pub const LADDER: [Stage; 7] = [Episode, Fact, Observation, Concept, Belief, Rule, Skill];
pub fn name(self) -> &'static str {
match self {
@@ -138,7 +136,11 @@ struct Verdict {
impl Verdict {
fn refuse(reason: impl Into<String>) -> Verdict {
Verdict { promote: false, to: None, reason: reason.into() }
Verdict {
promote: false,
to: None,
reason: reason.into(),
}
}
fn grant(to: Stage) -> Verdict {
@@ -150,8 +152,7 @@ impl Verdict {
}
fn to_pairs(&self) -> Vec<(String, Json)> {
let mut pairs: Vec<(String, Json)> =
vec![("promote".into(), Json::Bool(self.promote))];
let mut pairs: Vec<(String, Json)> = vec![("promote".into(), Json::Bool(self.promote))];
if let Some(to) = self.to {
pairs.push(("to".into(), Json::Str(to.name().into())));
}
@@ -187,7 +188,10 @@ impl Gate {
/// The stage after this one (None at the top). A non-rung stage
/// mirrors the oracle's STAGES.index ValueError → None.
pub fn next_stage(stage: Option<&str>) -> Option<&'static str> {
stage.and_then(Stage::parse).and_then(Stage::next).map(Stage::name)
stage
.and_then(Stage::parse)
.and_then(Stage::next)
.map(Stage::name)
}
/// Can this item promote? Needs: evidence threshold met, provenance
@@ -230,8 +234,7 @@ fn verdict(item: &Json) -> Verdict {
}) {
return Verdict::refuse(reason);
}
if stage.needs_verified_outcome()
&& !item.get("verified_outcome").map(truthy).unwrap_or(false)
if stage.needs_verified_outcome() && !item.get("verified_outcome").map(truthy).unwrap_or(false)
{
return Verdict::refuse("belief+ requires a verified outcome");
}
@@ -267,10 +270,9 @@ fn with_item(mut pairs: Vec<(String, Json)>, item: Json) -> Json {
pub fn promote(item: &Json) -> Json {
let v = verdict(item);
let out_item = if v.promote {
let to = v
.to
.map(|s| Json::Str(s.name().into()))
.unwrap_or(Json::Null);
let to =
v.to.map(|s| Json::Str(s.name().into()))
.unwrap_or(Json::Null);
set_key(&set_key(item, "stage", to), "sightings", Json::Num(0.0))
} else {
item.clone()
+4 -5
View File
@@ -29,16 +29,15 @@ 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())?;
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),
]),
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)),
@@ -3,9 +3,7 @@
//! capture_messages / to_prompt / estimate_tokens / create_capsule across
//! every branch. Embedded VERBATIM — never transcribed.
use crate::{
capture_messages, create_capsule, estimate_tokens, test_capsule, AttentionCapsule,
};
use crate::{capture_messages, create_capsule, estimate_tokens, test_capsule, AttentionCapsule};
use serde_json::Value;
const TRUTH: &str = include_str!("frozen_ac_truth.json");
+39 -12
View File
@@ -61,14 +61,24 @@ pub enum BlockWire {
/// attributes in Python — getattr defaults — and is skipped by
/// `to_messages`).
enum Block<'a> {
Obj { name: &'a str, content: &'a Value, payload: Option<&'a Value> },
Dict { payload: &'a Value },
Obj {
name: &'a str,
content: &'a Value,
payload: Option<&'a Value>,
},
Dict {
payload: &'a Value,
},
}
impl AttentionCapsule {
fn blocks(&self) -> impl Iterator<Item = Block<'_>> {
self.blocks.iter().map(|b| match b {
BlockWire::Obj { name, content, dict } => Block::Obj {
BlockWire::Obj {
name,
content,
dict,
} => Block::Obj {
name: name.as_str(),
content,
payload: dict.as_ref(),
@@ -140,7 +150,12 @@ impl PromptSection {
PromptSection::Memories => {
if !c.relevant_memories.is_empty() {
parts.push("Relevant memories:".into());
parts.extend(c.relevant_memories.iter().take(5).map(|mem| format!(" - {mem}")));
parts.extend(
c.relevant_memories
.iter()
.take(5)
.map(|mem| format!(" - {mem}")),
);
}
}
PromptSection::Commitments => {
@@ -294,8 +309,8 @@ fn relevant_lines<'a>(
}
let line_low = line.to_lowercase();
let line_words: HashSet<&str> = line_low.split_whitespace().collect();
(task_words.intersection(&line_words).count() >= 2)
.then(|| take(line, 100)) // truncate long lines
(task_words.intersection(&line_words).count() >= 2).then(|| take(line, 100))
// truncate long lines
})
.take(5) // max 5 memories
}
@@ -349,8 +364,11 @@ enum TrimStage {
Objective,
}
const TRIM_ORDER: &[TrimStage] =
&[TrimStage::Memories, TrimStage::Constraints, TrimStage::Objective];
const TRIM_ORDER: &[TrimStage] = &[
TrimStage::Memories,
TrimStage::Constraints,
TrimStage::Objective,
];
impl AttentionCapsule {
fn retrim(&mut self, max_tokens: i64) {
@@ -439,7 +457,9 @@ fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
@@ -460,14 +480,18 @@ fn parse_capsule(v: &Value) -> Result<AttentionCapsule, String> {
/// ac_to_dict(capsule) -> capsule dict
#[no_mangle]
pub extern "C" fn ac_to_dict(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| parse_capsule(&a)).map(|c| c.to_dict());
let r = read_arg(arg)
.and_then(|a| parse_capsule(&a))
.map(|c| c.to_dict());
envelope(r)
}
/// ac_to_prompt(capsule) -> prompt string
#[no_mangle]
pub extern "C" fn ac_to_prompt(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| parse_capsule(&a)).map(|c| Value::String(c.to_prompt()));
let r = read_arg(arg)
.and_then(|a| parse_capsule(&a))
.map(|c| Value::String(c.to_prompt()));
envelope(r)
}
@@ -476,7 +500,10 @@ pub extern "C" fn ac_to_prompt(arg: *const c_char) -> *mut c_char {
pub extern "C" fn ac_to_messages(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let capsule = parse_capsule(a.get("capsule").unwrap_or(&Value::Null))?;
let preamble = a.get("system_preamble").and_then(|v| v.as_str()).unwrap_or("");
let preamble = a
.get("system_preamble")
.and_then(|v| v.as_str())
.unwrap_or("");
Ok(capsule.to_messages(preamble))
});
envelope(r)
+187 -52
View File
@@ -9,11 +9,12 @@
//! bh_pure_truth.json (freeze_truth envelope) replays through the same
//! public fns — the in-crate twin of `freeze_truth.py --verify`.
use super::{behavior_stats, connect, list_items, plugin,
_bh_core, _correction_matches_python, _habit_matches_python,
_reinforce_python, _reinforce_rust,
correction_matches, correction_reinforce, correction_visible, habit_matches,
habit_reinforce, habit_visible, parse_scope_tags, CorrectionRow, HabitRow};
use super::{
_bh_core, _correction_matches_python, _habit_matches_python, _reinforce_python,
_reinforce_rust, behavior_stats, connect, correction_matches, correction_reinforce,
correction_visible, habit_matches, habit_reinforce, habit_visible, list_items,
parse_scope_tags, plugin, CorrectionRow, HabitRow,
};
use serde_json::Value;
const TRUTH: &str = include_str!("bh_truth.json");
@@ -68,7 +69,11 @@ fn test_add_shapes() {
assert_eq!(row["evidence_count"].as_i64(), Some(1));
assert_eq!(
row["scope_tags"].as_str(),
Some(if c["name"] == "scoped" { r#"{"k": "v"}"# } else { "{}" }),
Some(if c["name"] == "scoped" {
r#"{"k": "v"}"#
} else {
"{}"
}),
"{}: scope_tags stored as JSON string",
c["name"]
);
@@ -85,7 +90,11 @@ fn test_add_shapes() {
);
assert_eq!(
row["context_tags"].as_str(),
Some(if c["name"] == "full" { r#"["a"]"# } else { "[]" }),
Some(if c["name"] == "full" {
r#"["a"]"#
} else {
"[]"
}),
"{}: context_tags stored as JSON string",
c["name"]
);
@@ -116,21 +125,40 @@ fn test_match_matrix() {
// every frozen habit survivor text corresponds to a trigger that
// matches the context; corrections need BOTH pattern and domain.
if case["name"].as_str() == Some("pattern-domain-hit") {
assert!(correction_matches("deploy", "shell", case["context"].as_str().unwrap()));
assert!(correction_matches(
"deploy",
"shell",
case["context"].as_str().unwrap()
));
assert!(habit_matches("deploy", case["context"].as_str().unwrap()));
}
if case["name"].as_str() == Some("domain-only-miss") {
// "deploy the editor": editor-domain correction (no pattern)
// survives, shell-domain one dies on the domain filter.
assert!(correction_matches("", "editor", case["context"].as_str().unwrap()));
assert!(!correction_matches("", "shell", case["context"].as_str().unwrap()));
assert!(correction_matches(
"",
"editor",
case["context"].as_str().unwrap()
));
assert!(!correction_matches(
"",
"shell",
case["context"].as_str().unwrap()
));
}
if case["name"].as_str() == Some("low-conf-filtered") {
assert!(!habit_visible("candidate", 0.2), "confidence gate");
}
if case["name"].as_str() == Some("bad-regex-skipped") {
assert!(!correction_matches("([unclosed", "", case["context"].as_str().unwrap()));
assert!(!habit_matches("([broken", case["context"].as_str().unwrap()));
assert!(!correction_matches(
"([unclosed",
"",
case["context"].as_str().unwrap()
));
assert!(!habit_matches(
"([broken",
case["context"].as_str().unwrap()
));
}
}
}
@@ -235,14 +263,18 @@ fn test_reinforce_correction_matrix() {
}
}
};
let pre_count = row["evidence_count"].as_i64().unwrap()
- i64::from(outcome == "success") * n;
let pre_count =
row["evidence_count"].as_i64().unwrap() - i64::from(outcome == "success") * n;
// Replay and compare every step + final row.
let mut count = pre_count;
for i in 0..n {
let (nc, ncnt, ns) = correction_reinforce(conf, count, outcome);
let want = &case["results"][i as usize];
assert_eq!(want["state"].as_str(), Some(ns.as_str()), "{name}: step {i} state");
assert_eq!(
want["state"].as_str(),
Some(ns.as_str()),
"{name}: step {i} state"
);
let wconf = want["confidence"].as_f64().unwrap();
assert!(
(wconf - nc).abs() < 1e-12,
@@ -281,18 +313,29 @@ fn test_visibility_gates_and_list_counts() {
assert_eq!(c["n_corr"].as_i64(), Some(1), "LIMIT 1 applies");
}
"type-correction" => {
assert!(c["keys"].as_array().unwrap().iter().all(|k| k.as_str() == Some("corrections")));
assert!(c["keys"]
.as_array()
.unwrap()
.iter()
.all(|k| k.as_str() == Some("corrections")));
assert!(c["n_hab"].as_i64().unwrap() == 0 || c.get("n_hab").is_none());
}
"type-habit" => {
assert!(c["keys"].as_array().unwrap().iter().all(|k| k.as_str() == Some("habits")));
assert!(c["keys"]
.as_array()
.unwrap()
.iter()
.all(|k| k.as_str() == Some("habits")));
}
_ => {}
}
}
// Gates (unit-level, oracle-probed formulas):
assert!(correction_visible("active", 0.31));
assert!(!correction_visible("active", 0.3), "confidence > 0.3 strict");
assert!(
!correction_visible("active", 0.3),
"confidence > 0.3 strict"
);
assert!(!correction_visible("archived", 0.9));
assert!(habit_visible("candidate", 0.31));
assert!(habit_visible("active", 0.31));
@@ -305,19 +348,38 @@ fn test_visibility_gates_and_list_counts() {
#[test]
fn test_match_predicates() {
assert!(correction_matches("deploy", "shell", "deploy to shell now"));
assert!(!correction_matches("deploy", "editor", "deploy to shell now"),
"domain miss filters");
assert!(!correction_matches("editor", "shell", "deploy to shell now"),
"pattern miss filters");
assert!(!correction_matches("([unclosed", "", "anything"),
"invalid regex skips");
assert!(correction_matches("", "", "anything"), "empty filters never block");
assert!(correction_matches("", "SHELL", "deploy to shell now"),
"domain case-insensitive");
assert!(habit_matches("deploy", "DEPLOY the thing"), "trigger IGNORECASE");
assert!(!habit_matches("test (unit|e2e)", "run the unit tests"),
"unescaped parens match nothing (frozen)");
assert!(!habit_matches("([broken", "anything"), "invalid trigger skips");
assert!(
!correction_matches("deploy", "editor", "deploy to shell now"),
"domain miss filters"
);
assert!(
!correction_matches("editor", "shell", "deploy to shell now"),
"pattern miss filters"
);
assert!(
!correction_matches("([unclosed", "", "anything"),
"invalid regex skips"
);
assert!(
correction_matches("", "", "anything"),
"empty filters never block"
);
assert!(
correction_matches("", "SHELL", "deploy to shell now"),
"domain case-insensitive"
);
assert!(
habit_matches("deploy", "DEPLOY the thing"),
"trigger IGNORECASE"
);
assert!(
!habit_matches("test (unit|e2e)", "run the unit tests"),
"unescaped parens match nothing (frozen)"
);
assert!(
!habit_matches("([broken", "anything"),
"invalid trigger skips"
);
}
/// scope_tags parse fallback: valid JSON parses, corrupt input → {}.
@@ -359,10 +421,17 @@ fn test_habit_update_dict_shape() {
#[test]
fn test_correction_update_dict_shape() {
// failure-archives: confidence 0.5 → 0.2, evidence_count UNCHANGED.
let upd = corr_upd(&serde_json::json!({"confidence": 0.5, "evidence_count": 1}), "failure");
let upd = corr_upd(
&serde_json::json!({"confidence": 0.5, "evidence_count": 1}),
"failure",
);
assert_eq!(upd["state"].as_str(), Some("archived"));
assert_eq!(upd["confidence"].as_f64(), Some(0.2));
assert_eq!(upd["evidence_count"].as_f64(), Some(1.0), "failure keeps count");
assert_eq!(
upd["evidence_count"].as_f64(),
Some(1.0),
"failure keeps count"
);
}
// ── gap-close surfaces ───────────────────────────────────────────────────
@@ -382,9 +451,19 @@ fn test_connect_schema() {
let frozen_schema = frozen["schema"].as_array().unwrap();
assert_eq!(spec.schema.len(), frozen_schema.len(), "schema stmt count");
for (stmt, want) in spec.schema.iter().zip(frozen_schema) {
assert_eq!(stmt.kind, want["kind"].as_str().unwrap(), "{} kind", stmt.name);
assert_eq!(
stmt.kind,
want["kind"].as_str().unwrap(),
"{} kind",
stmt.name
);
assert_eq!(stmt.name, want["name"].as_str().unwrap(), "stmt name");
assert_eq!(stmt.sql, want["sql"].as_str().unwrap(), "{} DDL verbatim", stmt.name);
assert_eq!(
stmt.sql,
want["sql"].as_str().unwrap(),
"{} DDL verbatim",
stmt.name
);
}
let _guard = ENV_LOCK.lock().unwrap();
let key = "RUNE_MEMORY_DIR";
@@ -430,7 +509,10 @@ fn test_python_predicates_matrix() {
let p = &t["cases"]["predicates"];
let n_corr = p["corrections"].as_array().unwrap().len();
let n_habit = p["habits"].as_array().unwrap().len();
assert!(n_corr >= 40 && n_habit >= 30, "matrix is full ({n_corr}+{n_habit})");
assert!(
n_corr >= 40 && n_habit >= 30,
"matrix is full ({n_corr}+{n_habit})"
);
for c in p["corrections"].as_array().unwrap() {
let row = corr_row_full(&c["row"]);
let ctx = c["context"].as_str().unwrap();
@@ -439,7 +521,8 @@ fn test_python_predicates_matrix() {
_correction_matches_python(&row, ctx, ctx_low),
c["matched"].as_bool().unwrap(),
"correction {} vs {:?}",
row.id, ctx
row.id,
ctx
);
}
for h in p["habits"].as_array().unwrap() {
@@ -449,7 +532,8 @@ fn test_python_predicates_matrix() {
_habit_matches_python(&row, ctx),
h["matched"].as_bool().unwrap(),
"habit {} vs {:?}",
row.id, ctx
row.id,
ctx
);
}
}
@@ -468,25 +552,57 @@ fn test_reinforce_plan_replay() {
let item_type = case["item_type"].as_str().unwrap();
let n = case["n"].as_i64().unwrap();
if case["pre_row"].is_null() {
let id = if name == "habit-not-found" { 99991 } else { 99992 };
let id = if name == "habit-not-found" {
99991
} else {
99992
};
let plan = _reinforce_rust(id, outcome, item_type, None, "ts");
assert_eq!(plan.result, case["results"][0], "{name}: not-found envelope");
assert!(plan.writeback.is_null() && plan.activation.is_none(), "{name}");
assert_eq!(
plan.result, case["results"][0],
"{name}: not-found envelope"
);
assert!(
plan.writeback.is_null() && plan.activation.is_none(),
"{name}"
);
continue;
}
let ts = case["pre_row"]["created_at"].as_str().unwrap();
let mut cur = case["pre_row"].clone();
for i in 0..n as usize {
let plan = _reinforce_rust(case["pre_row"]["id"].as_i64().unwrap(), outcome, item_type, Some(&cur), ts);
let twin = _reinforce_python(case["pre_row"]["id"].as_i64().unwrap(), outcome, item_type, Some(&cur), ts);
let plan = _reinforce_rust(
case["pre_row"]["id"].as_i64().unwrap(),
outcome,
item_type,
Some(&cur),
ts,
);
let twin = _reinforce_python(
case["pre_row"]["id"].as_i64().unwrap(),
outcome,
item_type,
Some(&cur),
ts,
);
assert_eq!(plan.result, twin.result, "{name}: step {i} halves coincide");
assert_eq!(plan.writeback, twin.writeback, "{name}: step {i} writebacks coincide");
assert_eq!(plan.activation, twin.activation, "{name}: step {i} activation rows coincide");
assert_eq!(
plan.writeback, twin.writeback,
"{name}: step {i} writebacks coincide"
);
assert_eq!(
plan.activation, twin.activation,
"{name}: step {i} activation rows coincide"
);
assert_eq!(plan.result, case["results"][i], "{name}: step {i} result");
if item_type == "habit" {
let act = plan.activation.as_ref().unwrap();
assert_eq!(act["activated_at"].as_str(), Some(ts), "{name}: ts pinned");
assert_eq!(act["context"].as_str(), Some(""), "{name}: logged context ''");
assert_eq!(
act["context"].as_str(),
Some(""),
"{name}: logged context ''"
);
}
// thread the write-back (the oracle's UPDATE)
for (k, v) in plan.writeback.as_object().unwrap() {
@@ -520,10 +636,17 @@ fn test_list_items_replay() {
// tie law: the two 0.85 corrections appear in rowid order behind the
// single 1.0 one.
let all = list_items(&corrections, &habits, "all", 20);
let ids: Vec<i64> = all["corrections"].as_array().unwrap()
.iter().map(|c| c["id"].as_i64().unwrap()).collect();
let ids: Vec<i64> = all["corrections"]
.as_array()
.unwrap()
.iter()
.map(|c| c["id"].as_i64().unwrap())
.collect();
let pos = |id: i64| ids.iter().position(|x| *x == id).unwrap();
assert!(pos(11) < pos(1) && pos(1) < pos(2), "confidence order then rowid ties: {ids:?}");
assert!(
pos(11) < pos(1) && pos(1) < pos(2),
"confidence order then rowid ties: {ids:?}"
);
}
/// behavior_stats over the frozen row collections, both captured stats
@@ -538,7 +661,10 @@ fn test_behavior_stats_replay() {
let activations = sf["activations"].as_i64().unwrap();
let got = behavior_stats(&corrections, &habits, activations);
assert_eq!(got, sf["stats"], "stats_full counts");
assert_eq!(got, t["cases"]["stats"][0]["expected"], "matrix-time counts");
assert_eq!(
got, t["cases"]["stats"][0]["expected"],
"matrix-time counts"
);
}
/// bh_pure_truth.json (freeze_truth envelope): the pure-fn corpus the
@@ -557,7 +683,11 @@ fn test_pure_truth_corpus() {
"_correction_matches_python" => {
let row = corr_row_full(&args[0]);
assert_eq!(
_correction_matches_python(&row, args[1].as_str().unwrap(), args[2].as_str().unwrap()),
_correction_matches_python(
&row,
args[1].as_str().unwrap(),
args[2].as_str().unwrap()
),
c["expected"].as_bool().unwrap()
);
}
@@ -571,7 +701,12 @@ fn test_pure_truth_corpus() {
"reinforce" => {
let item_type = c["kwargs"]["item_type"].as_str().unwrap();
let plan = _reinforce_rust(
args[0].as_i64().unwrap(), args[1].as_str().unwrap(), item_type, None, "ts");
args[0].as_i64().unwrap(),
args[1].as_str().unwrap(),
item_type,
None,
"ts",
);
assert_eq!(plan.result, c["expected"]);
}
"_bh_core" => {
+72 -27
View File
@@ -349,7 +349,10 @@ pub struct SchemaStmt {
}
const SCHEMA: [SchemaStmt; 7] = [
SchemaStmt { kind: "table", name: "corrections", sql: r#"CREATE TABLE corrections (
SchemaStmt {
kind: "table",
name: "corrections",
sql: r#"CREATE TABLE corrections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
original_behavior TEXT,
corrected_behavior TEXT NOT NULL,
@@ -362,8 +365,12 @@ const SCHEMA: [SchemaStmt; 7] = [
created_at TEXT NOT NULL,
last_reinforced TEXT,
supersedes_id INTEGER
)"# },
SchemaStmt { kind: "table", name: "habits", sql: r#"CREATE TABLE habits (
)"#,
},
SchemaStmt {
kind: "table",
name: "habits",
sql: r#"CREATE TABLE habits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT DEFAULT '',
trigger_pattern TEXT NOT NULL,
@@ -378,19 +385,40 @@ const SCHEMA: [SchemaStmt; 7] = [
state TEXT DEFAULT 'candidate',
created_at TEXT NOT NULL,
last_activated TEXT
)"# },
SchemaStmt { kind: "table", name: "habit_activations", sql: r#"CREATE TABLE habit_activations (
)"#,
},
SchemaStmt {
kind: "table",
name: "habit_activations",
sql: r#"CREATE TABLE habit_activations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
habit_id INTEGER REFERENCES habits(id),
context TEXT NOT NULL,
outcome TEXT DEFAULT 'neutral',
feedback TEXT DEFAULT '',
activated_at TEXT NOT NULL
)"# },
SchemaStmt { kind: "index", name: "idx_corrections_state", sql: r#"CREATE INDEX idx_corrections_state ON corrections(state)"# },
SchemaStmt { kind: "index", name: "idx_corrections_domain", sql: r#"CREATE INDEX idx_corrections_domain ON corrections(scope_domain)"# },
SchemaStmt { kind: "index", name: "idx_habits_state", sql: r#"CREATE INDEX idx_habits_state ON habits(state)"# },
SchemaStmt { kind: "index", name: "idx_habits_trigger", sql: r#"CREATE INDEX idx_habits_trigger ON habits(trigger_pattern)"# },
)"#,
},
SchemaStmt {
kind: "index",
name: "idx_corrections_state",
sql: r#"CREATE INDEX idx_corrections_state ON corrections(state)"#,
},
SchemaStmt {
kind: "index",
name: "idx_corrections_domain",
sql: r#"CREATE INDEX idx_corrections_domain ON corrections(scope_domain)"#,
},
SchemaStmt {
kind: "index",
name: "idx_habits_state",
sql: r#"CREATE INDEX idx_habits_state ON habits(state)"#,
},
SchemaStmt {
kind: "index",
name: "idx_habits_trigger",
sql: r#"CREATE INDEX idx_habits_trigger ON habits(trigger_pattern)"#,
},
];
/// connect()'s deterministic content as data: where the database lives,
@@ -407,7 +435,11 @@ pub struct ConnectSpec {
/// `connect`): the decision surface — path resolution, WAL journal
/// mode, the full schema — returned as data for the facade to apply.
pub fn connect() -> ConnectSpec {
ConnectSpec { path: db_path(), journal_mode: "wal", schema: SCHEMA }
ConnectSpec {
path: db_path(),
journal_mode: "wal",
schema: SCHEMA,
}
}
// ── the Python-pin predicates (the oracle's inline fallbacks) ────────────
@@ -419,8 +451,7 @@ pub fn connect() -> ConnectSpec {
/// caller; passing anything else is a caller bug).
pub fn _correction_matches_python(row: &CorrectionRow, context: &str, context_low: &str) -> bool {
scope_pattern_matches(&row.scope_pattern, context)
&& (row.scope_domain.is_empty()
|| context_low.contains(&row.scope_domain.to_lowercase()))
&& (row.scope_domain.is_empty() || context_low.contains(&row.scope_domain.to_lowercase()))
}
/// The inline per-row habit predicate (name-preserving port of
@@ -630,7 +661,11 @@ pub fn list_items(
/// Return behavioral pattern statistics (name-preserving port of
/// `behavior_stats`): the five counters over the row collections; the
/// activation count is passed in (its rows aren't decision input).
pub fn behavior_stats(corrections: &[CorrectionRow], habits: &[HabitRow], activations: i64) -> serde_json::Value {
pub fn behavior_stats(
corrections: &[CorrectionRow],
habits: &[HabitRow],
activations: i64,
) -> serde_json::Value {
serde_json::json!({
"corrections_active":
corrections.iter().filter(|c| c.state == "active").count() as i64,
@@ -711,7 +746,9 @@ fn str_field(row: &serde_json::Value, key: &str) -> String {
/// bh_habit_reinforce({row, outcome}) -> the update dict.
#[no_mangle]
pub extern "C" fn bh_habit_reinforce(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
pub extern "C" fn bh_habit_reinforce(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).and_then(|a| {
let row_v = a.get("row").cloned().unwrap_or(serde_json::json!({}));
let outcome = str_field(&a, "outcome");
@@ -800,7 +837,9 @@ pub extern "C" fn bh_core_probe() -> *mut std::os::raw::c_char {
/// bh_python_predicates({kind, row, context, context_low?}) -> bool.
/// kind: "correction" (context_low defaults to context.lower()) | "habit".
#[no_mangle]
pub extern "C" fn bh_python_predicates(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
pub extern "C" fn bh_python_predicates(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).and_then(|a| {
let kind = str_field(&a, "kind");
let row_v = a.get("row").cloned().unwrap_or(serde_json::json!({}));
@@ -814,7 +853,11 @@ pub extern "C" fn bh_python_predicates(arg: *const std::os::raw::c_char) -> *mut
_ => context.to_lowercase(),
};
let row: CorrectionRow = serde_json::from_value(row_v).map_err(|e| e.to_string())?;
Ok(serde_json::json!(_correction_matches_python(&row, &context, &context_low)))
Ok(serde_json::json!(_correction_matches_python(
&row,
&context,
&context_low
)))
}
}))
}
@@ -844,13 +887,14 @@ pub extern "C" fn bh_reinforce_plan(arg: *const std::os::raw::c_char) -> *mut st
pub extern "C" fn bh_list_items(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).and_then(|a| {
let corrections: Vec<CorrectionRow> = serde_json::from_value(
a.get("corrections").cloned().unwrap_or(serde_json::json!([])),
)
.map_err(|e| e.to_string())?;
let habits: Vec<HabitRow> = serde_json::from_value(
a.get("habits").cloned().unwrap_or(serde_json::json!([])),
a.get("corrections")
.cloned()
.unwrap_or(serde_json::json!([])),
)
.map_err(|e| e.to_string())?;
let habits: Vec<HabitRow> =
serde_json::from_value(a.get("habits").cloned().unwrap_or(serde_json::json!([])))
.map_err(|e| e.to_string())?;
let item_type = str_field(&a, "item_type");
let limit = num(&a, "limit", 20.0) as i64;
Ok(list_items(&corrections, &habits, &item_type, limit))
@@ -862,13 +906,14 @@ pub extern "C" fn bh_list_items(arg: *const std::os::raw::c_char) -> *mut std::o
pub extern "C" fn bh_behavior_stats(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).and_then(|a| {
let corrections: Vec<CorrectionRow> = serde_json::from_value(
a.get("corrections").cloned().unwrap_or(serde_json::json!([])),
)
.map_err(|e| e.to_string())?;
let habits: Vec<HabitRow> = serde_json::from_value(
a.get("habits").cloned().unwrap_or(serde_json::json!([])),
a.get("corrections")
.cloned()
.unwrap_or(serde_json::json!([])),
)
.map_err(|e| e.to_string())?;
let habits: Vec<HabitRow> =
serde_json::from_value(a.get("habits").cloned().unwrap_or(serde_json::json!([])))
.map_err(|e| e.to_string())?;
let activations = num(&a, "activations", 0.0) as i64;
Ok(behavior_stats(&corrections, &habits, activations))
}))
+2 -2
View File
@@ -6,8 +6,8 @@ use std::path::PathBuf;
fn main() {
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
let default_path = PathBuf::from(&manifest)
.join("../../../verification/build-id/boundary.json");
let default_path =
PathBuf::from(&manifest).join("../../../verification/build-id/boundary.json");
let path = std::env::var("RUNE_BUILD_ID_FILE")
.map(PathBuf::from)
.unwrap_or(default_path);
+73 -52
View File
@@ -37,10 +37,10 @@ pub const BUILD_ID: &str = match option_env!("RUNE_BUILD_ID") {
None => "unset",
};
/// Schema: schemas/boundary/v1/error.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Error {
/// Schema: schemas/boundary/v1/error.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Error {
pub v: u32,
pub r#type: String,
pub msg_id: String,
@@ -50,12 +50,12 @@ pub const BUILD_ID: &str = match option_env!("RUNE_BUILD_ID") {
pub detail: String,
pub task_id: String,
pub recoverable: bool,
}
}
/// Schema: schemas/boundary/v1/log_line.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogLine {
/// Schema: schemas/boundary/v1/log_line.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogLine {
pub v: u32,
pub r#type: String,
pub msg_id: String,
@@ -64,46 +64,46 @@ pub const BUILD_ID: &str = match option_env!("RUNE_BUILD_ID") {
pub stream: String,
pub seq: u64,
pub text: String,
}
}
/// Schema: schemas/boundary/v1/ping.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Ping {
/// Schema: schemas/boundary/v1/ping.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Ping {
pub v: u32,
pub r#type: String,
pub msg_id: String,
pub ts: String,
pub nonce: String,
}
}
/// Schema: schemas/boundary/v1/pong.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Pong {
/// Schema: schemas/boundary/v1/pong.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Pong {
pub v: u32,
pub r#type: String,
pub msg_id: String,
pub ts: String,
pub nonce: String,
}
}
/// Schema: schemas/boundary/v1/shutdown.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Shutdown {
/// Schema: schemas/boundary/v1/shutdown.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Shutdown {
pub v: u32,
pub r#type: String,
pub msg_id: String,
pub ts: String,
pub reason: String,
pub graceful: bool,
}
}
/// Schema: schemas/boundary/v1/status_snapshot.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StatusSnapshot {
/// Schema: schemas/boundary/v1/status_snapshot.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StatusSnapshot {
pub v: u32,
pub r#type: String,
pub msg_id: String,
@@ -114,12 +114,12 @@ pub const BUILD_ID: &str = match option_env!("RUNE_BUILD_ID") {
pub tasks_pending: u64,
pub tasks_running: u64,
pub tasks_completed: u64,
}
}
/// Schema: schemas/boundary/v1/task_result.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskResult {
/// Schema: schemas/boundary/v1/task_result.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskResult {
pub v: u32,
pub r#type: String,
pub msg_id: String,
@@ -129,12 +129,12 @@ pub const BUILD_ID: &str = match option_env!("RUNE_BUILD_ID") {
pub summary: String,
pub evidence_refs: Vec<String>,
pub duration_ms: u64,
}
}
/// Schema: schemas/boundary/v1/task_start.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskStart {
/// Schema: schemas/boundary/v1/task_start.schema.json (v1)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskStart {
pub v: u32,
pub r#type: String,
pub msg_id: String,
@@ -143,7 +143,7 @@ pub const BUILD_ID: &str = match option_env!("RUNE_BUILD_ID") {
pub objective: String,
pub lane: String,
pub tools_allowed: Vec<String>,
}
}
#[derive(Debug)]
pub enum BoundaryError {
@@ -153,16 +153,21 @@ pub enum BoundaryError {
}
impl From<serde_json::Error> for BoundaryError {
fn from(e: serde_json::Error) -> Self { BoundaryError::Parse(e) }
fn from(e: serde_json::Error) -> Self {
BoundaryError::Parse(e)
}
}
impl std::fmt::Display for BoundaryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BoundaryError::Parse(e) => write!(f, "boundary: parse error: {}", e),
BoundaryError::UnknownMessageType(t) => write!(f, "boundary: unknown message type {:?}", t),
BoundaryError::VersionMismatch { expected, got } => write!(
f, "boundary: wire version {} != {}", got, expected),
BoundaryError::UnknownMessageType(t) => {
write!(f, "boundary: unknown message type {:?}", t)
}
BoundaryError::VersionMismatch { expected, got } => {
write!(f, "boundary: wire version {} != {}", got, expected)
}
}
}
}
@@ -186,17 +191,32 @@ pub fn roundtrip_canonical(line: &str) -> Result<String, BoundaryError> {
.to_string();
let v = value.get("v").and_then(|x| x.as_u64()).unwrap_or(0);
if v != u64::from(BOUNDARY_WIRE_VERSION) {
return Err(BoundaryError::VersionMismatch { expected: BOUNDARY_WIRE_VERSION, got: v });
return Err(BoundaryError::VersionMismatch {
expected: BOUNDARY_WIRE_VERSION,
got: v,
});
}
match t.as_str() {
"error" => canonical(&serde_json::from_value::<Error>(value).map_err(BoundaryError::Parse)?),
"log_line" => canonical(&serde_json::from_value::<LogLine>(value).map_err(BoundaryError::Parse)?),
"error" => {
canonical(&serde_json::from_value::<Error>(value).map_err(BoundaryError::Parse)?)
}
"log_line" => {
canonical(&serde_json::from_value::<LogLine>(value).map_err(BoundaryError::Parse)?)
}
"ping" => canonical(&serde_json::from_value::<Ping>(value).map_err(BoundaryError::Parse)?),
"pong" => canonical(&serde_json::from_value::<Pong>(value).map_err(BoundaryError::Parse)?),
"shutdown" => canonical(&serde_json::from_value::<Shutdown>(value).map_err(BoundaryError::Parse)?),
"status_snapshot" => canonical(&serde_json::from_value::<StatusSnapshot>(value).map_err(BoundaryError::Parse)?),
"task_result" => canonical(&serde_json::from_value::<TaskResult>(value).map_err(BoundaryError::Parse)?),
"task_start" => canonical(&serde_json::from_value::<TaskStart>(value).map_err(BoundaryError::Parse)?),
"shutdown" => {
canonical(&serde_json::from_value::<Shutdown>(value).map_err(BoundaryError::Parse)?)
}
"status_snapshot" => canonical(
&serde_json::from_value::<StatusSnapshot>(value).map_err(BoundaryError::Parse)?,
),
"task_result" => {
canonical(&serde_json::from_value::<TaskResult>(value).map_err(BoundaryError::Parse)?)
}
"task_start" => {
canonical(&serde_json::from_value::<TaskStart>(value).map_err(BoundaryError::Parse)?)
}
other => Err(BoundaryError::UnknownMessageType(other.to_string())),
}
}
@@ -220,7 +240,8 @@ mod tests {
fn wrong_version_is_rejected() {
assert!(roundtrip_canonical(
r#"{"v":2,"type":"ping","msg_id":"m","ts":"2026-01-01T00:00:00Z","nonce":"n"}"#,
).is_err());
)
.is_err());
}
#[test]
@@ -5,10 +5,11 @@
use std::path::PathBuf;
fn corpus_dir() -> PathBuf {
std::env::var("BOUNDARY_CORPUS_DIR").map(PathBuf::from).unwrap_or_else(|_| {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../../schemas/boundary/corpus")
})
std::env::var("BOUNDARY_CORPUS_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../schemas/boundary/corpus")
})
}
#[test]
@@ -25,8 +26,8 @@ fn corpus_roundtrips_canonically() {
for name in &names {
let text = std::fs::read_to_string(dir.join(name)).expect("corpus file");
let line = text.trim_end();
let out = boundary_types::roundtrip_canonical(line)
.unwrap_or_else(|e| panic!("{}: {}", name, e));
let out =
boundary_types::roundtrip_canonical(line).unwrap_or_else(|e| panic!("{}: {}", name, e));
assert_eq!(out, line, "canonical round-trip drift in {}", name);
}
}
@@ -40,9 +41,9 @@ fn corpus_covers_every_schema() {
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| n.ends_with(".json"))
.filter_map(|n| {
std::fs::read_to_string(dir.join(&n)).ok().and_then(|t| {
serde_json::from_str::<serde_json::Value>(&t).ok()
})
std::fs::read_to_string(dir.join(&n))
.ok()
.and_then(|t| serde_json::from_str::<serde_json::Value>(&t).ok())
})
.filter_map(|v| v.get("type").and_then(|t| t.as_str().map(String::from)))
.collect();
+50 -34
View File
@@ -21,8 +21,14 @@ pub const MAX_SELECTED: usize = 10;
/// Task-family keyword table (rune_operator _TASK_FAMILY_KEYWORDS, verbatim
/// order — first match wins).
const TASK_FAMILY_KEYWORDS: &[(&str, &[&str])] = &[
("code", &["fix", "code", "debug", "test", "refactor", "implement"]),
("research", &["design", "architecture", "research", "analyze"]),
(
"code",
&["fix", "code", "debug", "test", "refactor", "implement"],
),
(
"research",
&["design", "architecture", "research", "analyze"],
),
("chat", &["hello", "hi", "thanks", "status", "summarize"]),
];
@@ -40,7 +46,6 @@ fn as_str(v: Option<&Json>) -> &str {
}
}
/// rune_operator.infer_task_family port (first-match keyword scan).
pub fn infer_task_family(description: &str) -> &str {
let d = description.to_lowercase();
@@ -119,16 +124,16 @@ fn scored_tools(cards: &[Json], schemas: &Json, description: &str) -> Vec<(f64,
scored.push((score, name.to_string()));
}
}
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal).then(a.1.cmp(&b.1)));
scored.sort_by(|a, b| {
b.0.partial_cmp(&a.0)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.1.cmp(&b.1))
});
scored
}
/// select_for_job port. Returns {tools, names, reason, task_family}.
pub fn select_for_job(
tools: &[Json],
description: &str,
need_tools: bool,
) -> Json {
pub fn select_for_job(tools: &[Json], description: &str, need_tools: bool) -> Json {
let schemas = Json::Obj(
tools
.iter()
@@ -170,21 +175,32 @@ pub fn select_for_job(
// Explicit shell request promotes run_command to the front.
let d_lower = description.to_lowercase();
let is_shell = ["bash", "shell", "terminal"]
.iter()
.any(|w| {
// \b word boundary approximation: check with non-alnum neighbors
if let Some(pos) = d_lower.find(w) {
let before = d_lower[..pos].chars().last().map(|c| c.is_ascii_alphanumeric()).unwrap_or(false);
let after = d_lower[pos + w.len()..].chars().next().map(|c| c.is_ascii_alphanumeric()).unwrap_or(false);
!before && !after
} else {
false
}
});
let is_shell = ["bash", "shell", "terminal"].iter().any(|w| {
// \b word boundary approximation: check with non-alnum neighbors
if let Some(pos) = d_lower.find(w) {
let before = d_lower[..pos]
.chars()
.last()
.map(|c| c.is_ascii_alphanumeric())
.unwrap_or(false);
let after = d_lower[pos + w.len()..]
.chars()
.next()
.map(|c| c.is_ascii_alphanumeric())
.unwrap_or(false);
!before && !after
} else {
false
}
});
if is_shell && obj_get(&schemas, "run_command").is_some() {
let mut reordered = vec!["run_command".to_string()];
reordered.extend(matches.iter().filter(|(_, n)| n != "run_command").map(|(_, n)| n.clone()));
reordered.extend(
matches
.iter()
.filter(|(_, n)| n != "run_command")
.map(|(_, n)| n.clone()),
);
matches = reordered.into_iter().map(|n| (1.0, n)).collect();
}
@@ -201,7 +217,11 @@ pub fn select_for_job(
format!(
"floor + {} match(es): {}",
matches.len(),
matches.iter().map(|(_, n)| n.as_str()).collect::<Vec<_>>().join(", ")
matches
.iter()
.map(|(_, n)| n.as_str())
.collect::<Vec<_>>()
.join(", ")
)
} else {
"floor (no matches)".to_string()
@@ -224,12 +244,7 @@ pub fn select_for_job(
}
/// find_capability port. Returns [{name, description, risk, parameters?}].
pub fn find_capability(
tools: &[Json],
cards: &[Json],
query: &str,
top_k: usize,
) -> Json {
pub fn find_capability(tools: &[Json], cards: &[Json], query: &str, top_k: usize) -> Json {
let schemas = Json::Obj(
tools
.iter()
@@ -246,10 +261,7 @@ pub fn find_capability(
let mut scored: Vec<(f64, String, &Json)> = vec![];
for card in cards {
let name = as_str(obj_get(card, "name"));
if name.is_empty()
|| FLOOR_TOOLS.contains(&name)
|| name == ESCAPE_HATCH_TOOL
{
if name.is_empty() || FLOOR_TOOLS.contains(&name) || name == ESCAPE_HATCH_TOOL {
continue;
}
let score = score_card(card, query);
@@ -257,7 +269,11 @@ pub fn find_capability(
scored.push((score, name.to_string(), card));
}
}
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal).then(a.1.cmp(&b.1)));
scored.sort_by(|a, b| {
b.0.partial_cmp(&a.0)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.1.cmp(&b.1))
});
let mut out: Vec<Json> = vec![];
for (_, name, card) in scored.iter().take(top_k) {
+247 -41
View File
@@ -28,38 +28,245 @@ use std::fmt::Write as _;
/// SEMANTIC_EXAMPLES — the oracle dict, insertion order preserved.
pub const SEMANTIC_EXAMPLES: &[(&str, &[&str])] = &[
("memory_recall", &["What do I remember about X?", "Have we discussed this before?", "Find previous decisions about Y.", "What do I know about Z?"]),
("memory_write", &["Remember this fact.", "Store this for later.", "Save this decision."]),
("memory_search", &["Search my memory for X.", "Look up anything about Y."]),
("claim_verification", &["Is this claim supported?", "Verify this statement.", "Check if this is accurate."]),
("warm_routing", &["What context is relevant?", "Prime my memory for this topic."]),
("persona_compile", &["Who am I?", "What are my values?", "Load my identity."]),
("drift_detection", &["Am I drifting from my identity?", "Check for persona drift."]),
("growth_extraction", &["What should I learn from this?", "Extract behavioral deltas."]),
("harness_validation", &["Is my harness compliant?", "Validate my runtime."]),
("epub", &["Read this ebook.", "Extract text from this epub."]),
("tasks", &["What are my current tasks?", "Persist this task.", "Show my task list."]),
("politics", &["What do I know about this political topic?", "Absorb this political understanding."]),
("abstraction", &["Extract facts from this text.", "What observations can you make?", "Synthesize knowledge from this.", "What patterns do you see?"]),
("temporal", &["What was true then vs now?", "When did this change?", "Is this still current?", "Show me the history of this."]),
("corrections", &["I corrected you on this before.", "Don't do that again.", "Remember this correction."]),
("habits", &["You should do this automatically.", "Make this a habit.", "Remember this pattern."]),
("cache", &["Cache this result.", "Is this cached?", "Clear the cache."]),
("events", &["What events have happened?", "Show me the event log.", "What changed recently?"]),
("benchmark", &["Run the benchmark.", "Test the system.", "Check for regressions."]),
("identity", &["Sign this file.", "Verify this signature.", "Show my agent ID."]),
("agent_profiles", &["Switch to blank agent.", "Create a test profile.", "Restore Rune profile."]),
("voice_sync", &["Sync the voice.", "Update the system prompt.", "Is the prompt in sync?"]),
("prompt_sync", &["Sync the system prompt.", "Derive prompt from store.", "Is the prompt current?"]),
("context_pressure", &["How full is the context?", "Am I running out of context?", "Check context pressure."]),
("model_routing", &["Which model should I use?", "Route this task.", "What's the best model for this?"]),
("witness", &["What did you learn from this?", "Reflect on what happened.", "What signals did you detect?"]),
("task_gate", &["I've told you this before.", "This should be automatic.", "Make this a standard process.", "Remember this instruction."]),
("self_improve", &["What needs improvement?", "Analyze the system state.", "What's not working?", "Suggest improvements."]),
("task_persist", &["Save this task for later.", "What tasks are pending?", "Show my task list.", "Update task status."]),
("deep_research", &["Research this topic thoroughly.", "Help me investigate this.", "Find information about this.", "Look into this subject."]),
("research_lens", &["Is this claim supported by evidence?", "Check this against the baloney detector.", "Verify this research."]),
("epistemic_verify", &["Is this actually true?", "Verify this claim.", "Check the evidence."]),
(
"memory_recall",
&[
"What do I remember about X?",
"Have we discussed this before?",
"Find previous decisions about Y.",
"What do I know about Z?",
],
),
(
"memory_write",
&[
"Remember this fact.",
"Store this for later.",
"Save this decision.",
],
),
(
"memory_search",
&["Search my memory for X.", "Look up anything about Y."],
),
(
"claim_verification",
&[
"Is this claim supported?",
"Verify this statement.",
"Check if this is accurate.",
],
),
(
"warm_routing",
&[
"What context is relevant?",
"Prime my memory for this topic.",
],
),
(
"persona_compile",
&["Who am I?", "What are my values?", "Load my identity."],
),
(
"drift_detection",
&[
"Am I drifting from my identity?",
"Check for persona drift.",
],
),
(
"growth_extraction",
&[
"What should I learn from this?",
"Extract behavioral deltas.",
],
),
(
"harness_validation",
&["Is my harness compliant?", "Validate my runtime."],
),
(
"epub",
&["Read this ebook.", "Extract text from this epub."],
),
(
"tasks",
&[
"What are my current tasks?",
"Persist this task.",
"Show my task list.",
],
),
(
"politics",
&[
"What do I know about this political topic?",
"Absorb this political understanding.",
],
),
(
"abstraction",
&[
"Extract facts from this text.",
"What observations can you make?",
"Synthesize knowledge from this.",
"What patterns do you see?",
],
),
(
"temporal",
&[
"What was true then vs now?",
"When did this change?",
"Is this still current?",
"Show me the history of this.",
],
),
(
"corrections",
&[
"I corrected you on this before.",
"Don't do that again.",
"Remember this correction.",
],
),
(
"habits",
&[
"You should do this automatically.",
"Make this a habit.",
"Remember this pattern.",
],
),
(
"cache",
&["Cache this result.", "Is this cached?", "Clear the cache."],
),
(
"events",
&[
"What events have happened?",
"Show me the event log.",
"What changed recently?",
],
),
(
"benchmark",
&[
"Run the benchmark.",
"Test the system.",
"Check for regressions.",
],
),
(
"identity",
&[
"Sign this file.",
"Verify this signature.",
"Show my agent ID.",
],
),
(
"agent_profiles",
&[
"Switch to blank agent.",
"Create a test profile.",
"Restore Rune profile.",
],
),
(
"voice_sync",
&[
"Sync the voice.",
"Update the system prompt.",
"Is the prompt in sync?",
],
),
(
"prompt_sync",
&[
"Sync the system prompt.",
"Derive prompt from store.",
"Is the prompt current?",
],
),
(
"context_pressure",
&[
"How full is the context?",
"Am I running out of context?",
"Check context pressure.",
],
),
(
"model_routing",
&[
"Which model should I use?",
"Route this task.",
"What's the best model for this?",
],
),
(
"witness",
&[
"What did you learn from this?",
"Reflect on what happened.",
"What signals did you detect?",
],
),
(
"task_gate",
&[
"I've told you this before.",
"This should be automatic.",
"Make this a standard process.",
"Remember this instruction.",
],
),
(
"self_improve",
&[
"What needs improvement?",
"Analyze the system state.",
"What's not working?",
"Suggest improvements.",
],
),
(
"task_persist",
&[
"Save this task for later.",
"What tasks are pending?",
"Show my task list.",
"Update task status.",
],
),
(
"deep_research",
&[
"Research this topic thoroughly.",
"Help me investigate this.",
"Find information about this.",
"Look into this subject.",
],
),
(
"research_lens",
&[
"Is this claim supported by evidence?",
"Check this against the baloney detector.",
"Verify this research.",
],
),
(
"epistemic_verify",
&[
"Is this actually true?",
"Verify this claim.",
"Check the evidence.",
],
),
];
/// RISK_MAP — the oracle dict, insertion order preserved.
@@ -106,9 +313,8 @@ pub const RISK_MAP: &[(&str, &str)] = &[
/// generate_card, as whole-`\w`-token equals (each alternative is a
/// complete word token, so token-level equality is exact).
pub const CARD_VERBS: &[&str] = &[
"recall", "search", "verify", "check", "write", "store", "detect", "audit",
"extract", "route", "compile", "validate", "assess", "build", "list", "show",
"find",
"recall", "search", "verify", "check", "write", "store", "detect", "audit", "extract", "route",
"compile", "validate", "assess", "build", "list", "show", "find",
];
// ── the core probe (facade `_cs_core` mirror, _ss_core law) ─────────────────
@@ -412,7 +618,10 @@ fn cap_iter(caps: &Json) -> Result<Vec<String>, String> {
fn hooks_keys(hooks: &Json) -> Result<(bool, Vec<String>), String> {
match hooks {
Json::Obj(pairs) => Ok((!pairs.is_empty(), pairs.iter().map(|(k, _)| k.clone()).collect())),
Json::Obj(pairs) => Ok((
!pairs.is_empty(),
pairs.iter().map(|(k, _)| k.clone()).collect(),
)),
// non-dict hooks: bool() is legal but the oracle crashes on
// hooks.keys() in the card build — the TypeError arm.
_ => Err("'hooks' object has no attribute 'keys'".into()),
@@ -679,10 +888,7 @@ pub fn match_capabilities(cards: &[Json], query: &str, top_k: usize) -> Json {
scored.push((score, card.clone()));
}
}
scored.sort_by(|a, b| {
b.0.partial_cmp(&a.0)
.unwrap_or(std::cmp::Ordering::Equal)
});
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
Json::Arr(scored.into_iter().take(top_k).map(|(_, c)| c).collect())
}
@@ -806,4 +1012,4 @@ fn py_type_name(v: &Json) -> &'static str {
Json::Arr(_) => "list",
Json::Obj(_) => "dict",
}
}
}
+37 -29
View File
@@ -141,11 +141,8 @@ fn gap_replays_frozen_truth_verbatim() {
.and_then(|c| case_field(c, "expected"))
.cloned()
.expect("load_registry case present");
std::fs::write(
&home.registry_path,
pyjson::dump_indent2(&registry),
)
.expect("write fixture registry");
std::fs::write(&home.registry_path, pyjson::dump_indent2(&registry))
.expect("write fixture registry");
// pinned staleness law (mirrors the generator's os.utime pins)
pin_mtime(&home.cards_path, 1_000_000_000);
pin_mtime(&home.registry_path, 1_000_000_100);
@@ -181,13 +178,8 @@ fn gap_replays_frozen_truth_verbatim() {
assert_json_eq(&label, &Json::Bool(stale), expected);
}
"load_cards" => {
let out = gap::load_cards(
&home.cards_path,
&home.registry_path,
&tools,
&io,
)
.unwrap_or_else(|e| panic!("{}: load_cards errored: {}", label, e));
let out = gap::load_cards(&home.cards_path, &home.registry_path, &tools, &io)
.unwrap_or_else(|e| panic!("{}: load_cards errored: {}", label, e));
cards = match &out {
Json::Arr(a) => a.clone(),
_ => vec![],
@@ -209,8 +201,11 @@ fn gap_replays_frozen_truth_verbatim() {
assert_json_eq(&label, &out, expected);
}
"generate_card" => {
let out = gap::generate_card(&as_string(args.first()), args.get(1).unwrap_or(&Json::Obj(vec![])))
.unwrap_or_else(|e| panic!("{}: generate_card errored: {}", label, e));
let out = gap::generate_card(
&as_string(args.first()),
args.get(1).unwrap_or(&Json::Obj(vec![])),
)
.unwrap_or_else(|e| panic!("{}: generate_card errored: {}", label, e));
assert_json_eq(&label, &out, expected);
}
"_py_score_card" => {
@@ -261,7 +256,9 @@ impl gap::CbIo for FailIo {
fn mtime(&self, path: &str) -> Option<f64> {
std::fs::metadata(path).ok().and_then(|m| {
m.modified().ok().and_then(|t| {
t.duration_since(std::time::UNIX_EPOCH).ok().map(|d| d.as_secs_f64())
t.duration_since(std::time::UNIX_EPOCH)
.ok()
.map(|d| d.as_secs_f64())
})
})
}
@@ -310,7 +307,7 @@ fn load_registry_missing_and_value_law() {
let home = fixture_home();
let io = gap::StdIo;
let _ = std::fs::remove_file(&home.registry_path); // never created by fixture_home
// missing registry → the empty dict (never an Err)
// missing registry → the empty dict (never an Err)
let empty = gap::load_registry(&home.registry_path, &io).unwrap();
assert_eq!(j(&empty), "{}");
// corrupt registry text surfaces as an envelope err (oracle ValueError)
@@ -322,9 +319,12 @@ fn load_registry_missing_and_value_law() {
fn load_cards_rebuild_and_degrade_arms() {
let home = fixture_home();
let io = gap::StdIo;
let registry: Json = pyjson::parse(r#"{"plugins": {"only": {"manifest": {
let registry: Json = pyjson::parse(
r#"{"plugins": {"only": {"manifest": {
"name": "only", "description": "route this", "capabilities": ["warm_routing"],
"priority": 1}}}}"#).expect("registry fixture");
"priority": 1}}}}"#,
)
.expect("registry fixture");
std::fs::write(&home.registry_path, pyjson::dump_indent2(&registry)).expect("registry");
// missing cards + missing dir → makedirs law creates the dir, rebuild writes
@@ -353,7 +353,8 @@ fn load_cards_rebuild_and_degrade_arms() {
fail_replace: true,
};
std::fs::write(&home.cards_path, "").expect("clobber to empty cards");
let degraded = gap::load_cards(&home.cards_path, &home.registry_path, &[], &fail_io).expect("degrade");
let degraded =
gap::load_cards(&home.cards_path, &home.registry_path, &[], &fail_io).expect("degrade");
assert_eq!(j(&degraded), j(&out));
let on_disk = std::fs::read_to_string(&home.cards_path).expect("cards readable");
assert_eq!(on_disk, "", "write failure leaves no fresh index behind");
@@ -367,9 +368,9 @@ fn load_cards_corrupt_and_non_list_arms() {
std::fs::write(&home.registry_path, pyjson::dump_indent2(&registry)).expect("registry");
for (label, seeds) in [
("corrupt", "{\"name\": true"), // JSONDecodeError → rebuild
("scalar", "42"), // valid JSON, not a list → rebuild
("empty_list", "[]"), // empty list → rebuild
("corrupt", "{\"name\": true"), // JSONDecodeError → rebuild
("scalar", "42"), // valid JSON, not a list → rebuild
("empty_list", "[]"), // empty list → rebuild
] {
std::fs::write(&home.cards_path, seeds).expect("seed");
let out = gap::load_cards(&home.cards_path, &home.registry_path, &[], &io).expect(label);
@@ -377,13 +378,20 @@ fn load_cards_corrupt_and_non_list_arms() {
Json::Arr(a) => a.clone(),
_ => panic!("{}", label),
};
assert!(arr.is_empty(), "{}: plugin-less rebuild has no cards", label);
assert!(
arr.is_empty(),
"{}: plugin-less rebuild has no cards",
label
);
// the rebuild replaced the bad index on disk (atomic replace)
let on_disk = pyjson::parse(
&std::fs::read_to_string(&home.cards_path).expect("rewritten"),
)
.expect("valid JSON index");
assert_eq!(j(&out), j(&on_disk), "{}: rebuilt index == returned cards", label);
let on_disk = pyjson::parse(&std::fs::read_to_string(&home.cards_path).expect("rewritten"))
.expect("valid JSON index");
assert_eq!(
j(&out),
j(&on_disk),
"{}: rebuilt index == returned cards",
label
);
}
}
@@ -517,4 +525,4 @@ fn failio_seam_declared() {
fail_replace: true,
};
assert!(io.read("/nope").is_err());
}
}
@@ -7,10 +7,9 @@
//! facade does: mutators return the updated doc, queries don't mutate.
use crate::{
op_active, op_attach_spec, op_bump_version, op_by_component,
op_classify_version_bump, op_context_packet, op_define, op_get,
op_material_change, op_move, op_new, op_retire, op_revise_spec,
op_save, op_spec_context, op_spec_drift_status, op_supersede,
op_active, op_attach_spec, op_bump_version, op_by_component, op_classify_version_bump,
op_context_packet, op_define, op_get, op_material_change, op_move, op_new, op_retire,
op_revise_spec, op_save, op_spec_context, op_spec_drift_status, op_supersede,
};
use serde_json::{json, Map, Value};
@@ -84,7 +83,8 @@ fn parity_battery() {
let mut contract = envelope.clone();
contract.as_object_mut().unwrap().remove("doc");
assert_eq!(
&contract, exp,
&contract,
exp,
"seq '{name}' op {i} {:?}",
serde_json::to_string(op).unwrap_or_default()
);
@@ -121,7 +121,9 @@ fn save_bytes_pinned() {
serde_json::to_string_pretty(&sorted)
.map(pretty_to_py_indent)
.ok(),
seq["saved_files"]["capability_ledger.json"].as_str().map(str::to_string),
seq["saved_files"]["capability_ledger.json"]
.as_str()
.map(str::to_string),
"seq '{}': save() byte parity",
seq["name"].as_str().unwrap()
);
+174 -102
View File
@@ -40,28 +40,81 @@ const SPEC_SCHEMA_VERSION: i64 = 1;
/// with digit value = codepoint - run start (verified against
/// unicodedata across the whole BMP+ planes).
const ND_RANGES: &[(u32, u32)] = &[
(0x30, 0x39), (0x660, 0x669), (0x6F0, 0x6F9), (0x7C0, 0x7C9),
(0x966, 0x96F), (0x9E6, 0x9EF), (0xA66, 0xA6F), (0xAE6, 0xAEF),
(0xB66, 0xB6F), (0xBE6, 0xBEF), (0xC66, 0xC6F), (0xCE6, 0xCEF),
(0xD66, 0xD6F), (0xDE6, 0xDEF), (0xE50, 0xE59), (0xED0, 0xED9),
(0xF20, 0xF29), (0x1040, 0x1049), (0x1090, 0x1099), (0x17E0, 0x17E9),
(0x1810, 0x1819), (0x1946, 0x194F), (0x19D0, 0x19D9), (0x1A80, 0x1A89),
(0x1A90, 0x1A99), (0x1B50, 0x1B59), (0x1BB0, 0x1BB9), (0x1C40, 0x1C49),
(0x1C50, 0x1C59), (0xA620, 0xA629), (0xA8D0, 0xA8D9), (0xA900, 0xA909),
(0xA9D0, 0xA9D9), (0xA9F0, 0xA9F9), (0xAA50, 0xAA59), (0xABF0, 0xABF9),
(0xFF10, 0xFF19), (0x104A0, 0x104A9), (0x10D30, 0x10D39),
(0x10D40, 0x10D49), (0x11066, 0x1106F), (0x110F0, 0x110F9),
(0x11136, 0x1113F), (0x111D0, 0x111D9), (0x112F0, 0x112F9),
(0x11450, 0x11459), (0x114D0, 0x114D9), (0x11650, 0x11659),
(0x116C0, 0x116C9), (0x116D0, 0x116D9), (0x116DA, 0x116E3),
(0x11730, 0x11739), (0x118E0, 0x118E9), (0x11950, 0x11959),
(0x11BF0, 0x11BF9), (0x11C50, 0x11C59), (0x11D50, 0x11D59),
(0x11DA0, 0x11DA9), (0x11F50, 0x11F59), (0x16130, 0x16139),
(0x16A60, 0x16A69), (0x16AC0, 0x16AC9), (0x16B50, 0x16B59),
(0x16D70, 0x16D79), (0x1CCF0, 0x1CCF9), (0x1D7CE, 0x1D7D7),
(0x1D7D8, 0x1D7E1), (0x1D7E2, 0x1D7EB), (0x1D7EC, 0x1D7F5),
(0x1D7F6, 0x1D7FF), (0x1E140, 0x1E149), (0x1E2F0, 0x1E2F9),
(0x1E4F0, 0x1E4F9), (0x1E5F1, 0x1E5FA), (0x1E950, 0x1E959),
(0x30, 0x39),
(0x660, 0x669),
(0x6F0, 0x6F9),
(0x7C0, 0x7C9),
(0x966, 0x96F),
(0x9E6, 0x9EF),
(0xA66, 0xA6F),
(0xAE6, 0xAEF),
(0xB66, 0xB6F),
(0xBE6, 0xBEF),
(0xC66, 0xC6F),
(0xCE6, 0xCEF),
(0xD66, 0xD6F),
(0xDE6, 0xDEF),
(0xE50, 0xE59),
(0xED0, 0xED9),
(0xF20, 0xF29),
(0x1040, 0x1049),
(0x1090, 0x1099),
(0x17E0, 0x17E9),
(0x1810, 0x1819),
(0x1946, 0x194F),
(0x19D0, 0x19D9),
(0x1A80, 0x1A89),
(0x1A90, 0x1A99),
(0x1B50, 0x1B59),
(0x1BB0, 0x1BB9),
(0x1C40, 0x1C49),
(0x1C50, 0x1C59),
(0xA620, 0xA629),
(0xA8D0, 0xA8D9),
(0xA900, 0xA909),
(0xA9D0, 0xA9D9),
(0xA9F0, 0xA9F9),
(0xAA50, 0xAA59),
(0xABF0, 0xABF9),
(0xFF10, 0xFF19),
(0x104A0, 0x104A9),
(0x10D30, 0x10D39),
(0x10D40, 0x10D49),
(0x11066, 0x1106F),
(0x110F0, 0x110F9),
(0x11136, 0x1113F),
(0x111D0, 0x111D9),
(0x112F0, 0x112F9),
(0x11450, 0x11459),
(0x114D0, 0x114D9),
(0x11650, 0x11659),
(0x116C0, 0x116C9),
(0x116D0, 0x116D9),
(0x116DA, 0x116E3),
(0x11730, 0x11739),
(0x118E0, 0x118E9),
(0x11950, 0x11959),
(0x11BF0, 0x11BF9),
(0x11C50, 0x11C59),
(0x11D50, 0x11D59),
(0x11DA0, 0x11DA9),
(0x11F50, 0x11F59),
(0x16130, 0x16139),
(0x16A60, 0x16A69),
(0x16AC0, 0x16AC9),
(0x16B50, 0x16B59),
(0x16D70, 0x16D79),
(0x1CCF0, 0x1CCF9),
(0x1D7CE, 0x1D7D7),
(0x1D7D8, 0x1D7E1),
(0x1D7E2, 0x1D7EB),
(0x1D7EC, 0x1D7F5),
(0x1D7F6, 0x1D7FF),
(0x1E140, 0x1E149),
(0x1E2F0, 0x1E2F9),
(0x1E4F0, 0x1E4F9),
(0x1E5F1, 0x1E5FA),
(0x1E950, 0x1E959),
(0x1FBF0, 0x1FBF9),
];
@@ -238,9 +291,12 @@ impl Maturity {
}
}
fn classify_raw(contract_changed: bool, behaviour_added: bool,
consumer_break: bool, implementation_only: bool)
-> Result<BumpKind, String> {
fn classify_raw(
contract_changed: bool,
behaviour_added: bool,
consumer_break: bool,
implementation_only: bool,
) -> Result<BumpKind, String> {
if consumer_break || contract_changed {
return Ok(BumpKind::Major);
}
@@ -265,7 +321,11 @@ fn bump_version_str(version: &str, kind: &str) -> Result<String, String> {
}
// int() semantics on every component: leading zeros collapse whether
// or not that component is the one being bumped
let [maj, min, pat] = [norm_component(parts[0]), norm_component(parts[1]), norm_component(parts[2])];
let [maj, min, pat] = [
norm_component(parts[0]),
norm_component(parts[1]),
norm_component(parts[2]),
];
let kind = BumpKind::from_oracle(kind).map_err(|repr| {
// bump_version's own branch ladder raises ValueError("unknown bump
// kind {kind!r}") — different error text from the revise_spec
@@ -294,8 +354,7 @@ fn ledger_get<'a>(doc: &'a Value, capability_id: &str) -> Option<&'a Value> {
doc.get("capabilities").and_then(|c| c.get(&key))
}
fn ledger_take(doc: &mut Value, capability_id: &str)
-> Result<(String, Value), String> {
fn ledger_take(doc: &mut Value, capability_id: &str) -> Result<(String, Value), String> {
let key = resolve_key(doc, capability_id);
match doc.get("capabilities").and_then(|c| c.get(&key)) {
Some(rec) => Ok((key, rec.clone())),
@@ -385,8 +444,7 @@ pub fn op_get(a: &Value) -> OpResult {
pub fn op_material_change(a: &Value) -> OpResult {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let now = arg_str(a, "now", "");
let (key, mut rec) =
ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let (key, mut rec) = ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
// T21.1 §21.1D: deterministic revision counter, not wall-clock ordering
let rev = rec
.get("material_revision")
@@ -423,8 +481,7 @@ pub fn op_material_change(a: &Value) -> OpResult {
pub fn op_move(a: &Value) -> OpResult {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let now = arg_str(a, "now", "");
let (key, mut rec) =
ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let (key, mut rec) = ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let old = rec["source_locations"].clone();
let new_locations = a.get("new_locations").cloned().unwrap_or(json!([]));
rec["source_locations"] = new_locations.clone();
@@ -446,8 +503,7 @@ pub fn op_move(a: &Value) -> OpResult {
pub fn op_supersede(a: &Value) -> OpResult {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let now = arg_str(a, "now", "");
let (key, mut rec) =
ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let (key, mut rec) = ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let by_in = arg_str(a, "by", "");
let by = if by_in.contains(':') {
by_in.to_string()
@@ -474,8 +530,7 @@ pub fn op_supersede(a: &Value) -> OpResult {
pub fn op_retire(a: &Value) -> OpResult {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let now = arg_str(a, "now", "");
let (key, mut rec) =
ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let (key, mut rec) = ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
rec["maturity"] = json!("retired");
rec["updated_at"] = json!(now);
push_history(
@@ -537,9 +592,7 @@ pub fn op_by_component(a: &Value) -> OpResult {
Value::Array(
caps.values()
.filter(|r| is_active(r))
.filter(|r| {
r.get("component").and_then(|v| v.as_str()) == Some(component)
})
.filter(|r| r.get("component").and_then(|v| v.as_str()) == Some(component))
.cloned()
.collect(),
),
@@ -551,27 +604,33 @@ pub fn op_context_packet(a: &Value) -> OpResult {
let doc = &a["doc"];
let cid = arg_str(a, "capability_id", "");
let Some(rec) = ledger_get(doc, cid) else {
return Ok((json!({"error": format!("unknown capability {}", py_repr(cid))}), None));
return Ok((
json!({"error": format!("unknown capability {}", py_repr(cid))}),
None,
));
};
let g = |k: &str| rec.get(k).cloned().unwrap_or(Value::Null);
Ok((json!({
"schema_version": LEDGER_SCHEMA_VERSION,
"capability_id": g("capability_id"),
"name": g("name"),
"purpose": g("purpose"),
"component": g("component"),
"source_locations": g("source_locations"),
"contract": g("contract"),
"state_touched": g("state_touched"),
"dependencies": g("dependencies"),
"consumers": g("consumers"),
"invariants": g("invariants"),
"behaviour_specs": g("behaviour_specs"),
"maturity": g("maturity"),
"constraints": g("constraints"),
"generation": g("generation"),
"limitations": g("limitations"),
}), None))
Ok((
json!({
"schema_version": LEDGER_SCHEMA_VERSION,
"capability_id": g("capability_id"),
"name": g("name"),
"purpose": g("purpose"),
"component": g("component"),
"source_locations": g("source_locations"),
"contract": g("contract"),
"state_touched": g("state_touched"),
"dependencies": g("dependencies"),
"consumers": g("consumers"),
"invariants": g("invariants"),
"behaviour_specs": g("behaviour_specs"),
"maturity": g("maturity"),
"constraints": g("constraints"),
"generation": g("generation"),
"limitations": g("limitations"),
}),
None,
))
}
pub fn op_save(a: &Value) -> OpResult {
@@ -588,14 +647,18 @@ pub fn op_classify_version_bump(a: &Value) -> OpResult {
arg_bool(a, "behaviour_added"),
arg_bool(a, "consumer_break"),
arg_bool(a, "implementation_only"),
)?.as_str()),
)?
.as_str()),
None,
))
}
pub fn op_bump_version(a: &Value) -> OpResult {
Ok((
json!(bump_version_str(arg_str(a, "version", ""), arg_str(a, "kind", ""))?),
json!(bump_version_str(
arg_str(a, "version", ""),
arg_str(a, "kind", "")
)?),
None,
))
}
@@ -603,8 +666,7 @@ pub fn op_bump_version(a: &Value) -> OpResult {
pub fn op_attach_spec(a: &Value) -> OpResult {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let now = arg_str(a, "now", "");
let (key, mut rec) =
ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let (key, mut rec) = ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let baseline_version = arg_str(a, "baseline_version", "0.1.0").to_string();
if !is_semver(&baseline_version) {
return Err(format!(
@@ -642,8 +704,7 @@ pub fn op_attach_spec(a: &Value) -> OpResult {
pub fn op_revise_spec(a: &Value) -> OpResult {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let now = arg_str(a, "now", "");
let (key, mut rec) =
ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let (key, mut rec) = ledger_take(&mut doc, arg_str(a, "capability_id", ""))?;
let cid = arg_str(a, "capability_id", "");
let mut spec = match rec.get("spec") {
Some(s) if !s.is_null() => s.clone(),
@@ -674,24 +735,21 @@ pub fn op_revise_spec(a: &Value) -> OpResult {
let old = spec["version"].as_str().unwrap_or("").to_string();
let new = bump_version_str(&old, kind.as_str())?;
spec["version"] = json!(new);
spec["version_history"]
.as_array_mut()
.unwrap()
.push(json!({
"at": now,
"version": new,
"kind": kind.as_str(),
"reason": py_slice(arg_str(a, "reason", ""), 200),
"changed_sections": arg_arr(a, "changed_sections"),
"affected_consumers": arg_arr(a, "affected_consumers"),
"validation_consequences": arg_arr(a, "validation_consequences"),
// the governed decision COVERS all material revisions up to
// and including the current one — drift closes deterministically
"covers_material_revision": rec
.get("material_revision")
.and_then(|v| v.as_i64())
.unwrap_or(0),
}));
spec["version_history"].as_array_mut().unwrap().push(json!({
"at": now,
"version": new,
"kind": kind.as_str(),
"reason": py_slice(arg_str(a, "reason", ""), 200),
"changed_sections": arg_arr(a, "changed_sections"),
"affected_consumers": arg_arr(a, "affected_consumers"),
"validation_consequences": arg_arr(a, "validation_consequences"),
// the governed decision COVERS all material revisions up to
// and including the current one — drift closes deterministically
"covers_material_revision": rec
.get("material_revision")
.and_then(|v| v.as_i64())
.unwrap_or(0),
}));
rec["spec"] = spec;
rec["updated_at"] = json!(now);
put_rec(&mut doc, &key, rec);
@@ -705,16 +763,22 @@ pub fn op_spec_drift_status(a: &Value) -> OpResult {
let doc = &a["doc"];
let cid = arg_str(a, "capability_id", "");
let Some(rec) = ledger_get(doc, cid) else {
return Ok((json!({
"drift": false, "obligation": Value::Null,
"detail": format!("unknown capability {}", py_repr(cid)),
}), None));
return Ok((
json!({
"drift": false, "obligation": Value::Null,
"detail": format!("unknown capability {}", py_repr(cid)),
}),
None,
));
};
let Some(spec) = rec.get("spec").filter(|s| !s.is_null()) else {
return Ok((json!({
"drift": false, "obligation": Value::Null,
"detail": "no spec attached — nothing to drift from",
}), None));
return Ok((
json!({
"drift": false, "obligation": Value::Null,
"detail": "no spec attached — nothing to drift from",
}),
None,
));
};
let hist = spec.get("version_history").and_then(|v| v.as_array());
let covered = match hist {
@@ -735,18 +799,24 @@ pub fn op_spec_drift_status(a: &Value) -> OpResult {
"spec_version": spec.get("version").cloned().unwrap_or(Value::Null),
});
if current > covered {
Ok((json!({
"drift": true,
"obligation": format!(
"SPEC_VERSION_DRIFT: {cid} has {} uncovered material revision(s) \
(material rev {current} > governed rev {covered}) — \
run revise_spec (PATCH/MINOR/MAJOR) to close",
current - covered
),
"detail": detail,
}), None))
Ok((
json!({
"drift": true,
"obligation": format!(
"SPEC_VERSION_DRIFT: {cid} has {} uncovered material revision(s) \
(material rev {current} > governed rev {covered}) — \
run revise_spec (PATCH/MINOR/MAJOR) to close",
current - covered
),
"detail": detail,
}),
None,
))
} else {
Ok((json!({"drift": false, "obligation": Value::Null, "detail": detail}), None))
Ok((
json!({"drift": false, "obligation": Value::Null, "detail": detail}),
None,
))
}
}
@@ -789,7 +859,9 @@ fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
@@ -45,9 +45,13 @@ fn as_arr(v: &Json) -> Vec<Json> {
fn run_case(fn_name: &str, args: &Json) -> Json {
match fn_name {
"new_topology" => topology::new_topology(),
"cofire" => topology::cofire(&as_obj(&args.get("topology").cloned().unwrap_or(Json::Null)),
&as_arr(&args.get("topics").cloned().unwrap_or(Json::Arr(vec![])))),
"decay" => topology::decay(&as_obj(&args.get("topology").cloned().unwrap_or(Json::Null))),
"cofire" => topology::cofire(
&as_obj(&args.get("topology").cloned().unwrap_or(Json::Null)),
&as_arr(&args.get("topics").cloned().unwrap_or(Json::Arr(vec![]))),
),
"decay" => topology::decay(&as_obj(
&args.get("topology").cloned().unwrap_or(Json::Null),
)),
"clusters" => {
let t = as_obj(&args.get("topology").cloned().unwrap_or(Json::Null));
let threshold = match args.get("threshold") {
@@ -56,8 +60,15 @@ fn run_case(fn_name: &str, args: &Json) -> Json {
};
Json::Arr(topology::clusters(&t, threshold))
}
"suggest" => topology::suggest(&as_obj(&args.get("topology").cloned().unwrap_or(Json::Null)),
&as_arr(&args.get("fact_topics").cloned().unwrap_or(Json::Arr(vec![])))),
"suggest" => topology::suggest(
&as_obj(&args.get("topology").cloned().unwrap_or(Json::Null)),
&as_arr(
&args
.get("fact_topics")
.cloned()
.unwrap_or(Json::Arr(vec![])),
),
),
other => panic!("unknown frozen fn {other}"),
}
}
@@ -66,7 +77,11 @@ fn run_case(fn_name: &str, args: &Json) -> Json {
fn frozen_truth_replays_verbatim() {
let truth = parse(TRUTH).expect("frozen_ct_truth.json parses");
let cases = as_arr(truth.get("cases").expect("cases key"));
assert!(cases.len() >= 36, "expected the full frozen battery, got {}", cases.len());
assert!(
cases.len() >= 36,
"expected the full frozen battery, got {}",
cases.len()
);
let mut failures: Vec<String> = vec![];
for (i, case) in cases.iter().enumerate() {
let fn_name = match case.get("fn") {
+2 -6
View File
@@ -38,10 +38,7 @@ fn read_arg(ptr: *const c_char) -> Result<Json, String> {
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),
]),
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)),
@@ -80,8 +77,7 @@ pub extern "C" fn ct_cofire(arg: *const c_char) -> *mut c_char {
/// ct_decay({"topology": {...}})
#[no_mangle]
pub extern "C" fn ct_decay(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg)
.map(|a| topology::decay(&obj_arg(&a, "topology")));
let r = read_arg(arg).map(|a| topology::decay(&obj_arg(&a, "topology")));
envelope(r)
}
+7 -2
View File
@@ -108,7 +108,9 @@ pub fn decay(topology: &Json) -> Json {
let kept: Vec<(String, Json)> = obj_pairs(topology.get("edges"))
.iter()
.filter_map(|(k, v)| match v {
Json::Num(n) if n * DECAY_PER_TICK >= 0.5 => Some((k.clone(), Json::Num(n * DECAY_PER_TICK))),
Json::Num(n) if n * DECAY_PER_TICK >= 0.5 => {
Some((k.clone(), Json::Num(n * DECAY_PER_TICK)))
}
_ => None,
})
.collect();
@@ -205,7 +207,10 @@ impl Suggestion {
"suggestion".into(),
Json::Arr(names.iter().cloned().map(Json::Str).collect()),
),
("reason".into(), Json::Str(format!("shares {overlap} topic(s)"))),
(
"reason".into(),
Json::Str(format!("shares {overlap} topic(s)")),
),
]),
Suggestion::NewTopic => Json::Obj(vec![
("suggestion".into(), Json::Str("new_topic".into())),
@@ -83,4 +83,4 @@ fn dispatch_finish_parity_battery() {
i.get("env").unwrap_or(&Value::Null),
)
});
}
}
+71 -15
View File
@@ -324,8 +324,7 @@ fn _validate(
.and_then(Value::as_array)
.and_then(|entities| {
entities.iter().find(|e| {
e.get("id").and_then(Value::as_str)
== Some(&format!("contract:{construction_id}"))
e.get("id").and_then(Value::as_str) == Some(&format!("contract:{construction_id}"))
})
});
let contract = match contract {
@@ -621,7 +620,10 @@ pub fn dispatch(
};
format!(
"command:{}{suffix}",
intent.get("command_id").and_then(Value::as_str).unwrap_or("")
intent
.get("command_id")
.and_then(Value::as_str)
.unwrap_or("")
)
}
Op::Resume | Op::AmendScope => String::new(),
@@ -639,7 +641,12 @@ pub fn dispatch(
/// Final dispatch envelope assembly after the facade executed the intent
/// through the Construction Interface. `success` is Python bool() of the
/// interface envelope's success (truthiness, not identity).
pub fn dispatch_finish(command_type: &str, construction_id: &str, intent: &Value, env: &Value) -> Value {
pub fn dispatch_finish(
command_type: &str,
construction_id: &str,
intent: &Value,
env: &Value,
) -> Value {
json!({
"accepted": true,
"executed": true,
@@ -766,7 +773,13 @@ mod tests {
#[test]
fn start_in_draft_accepts_transition() {
let r = command(
"START", "c1", "corr", "cmd-1", &json!({}), NOW, None,
"START",
"c1",
"corr",
"cmd-1",
&json!({}),
NOW,
None,
Some(&draft_doc("c1")),
);
assert_eq!(r["accepted"], true);
@@ -777,7 +790,16 @@ mod tests {
#[test]
fn start_in_building_refused() {
let doc = json!({"entities": [{"id": "contract:c1", "phase": "building"}]});
let r = command("START", "c1", "corr", "cmd-1", &json!({}), NOW, None, Some(&doc));
let r = command(
"START",
"c1",
"corr",
"cmd-1",
&json!({}),
NOW,
None,
Some(&doc),
);
assert_eq!(r["accepted"], false);
assert_eq!(
r["reason"],
@@ -802,9 +824,21 @@ mod tests {
("REQUEST_STATE", CommandType::RequestState, Op::Resume),
("START", CommandType::Start, Op::Transition),
("RESUME", CommandType::Resume, Op::Resume),
("ACKNOWLEDGE_BLOCKER", CommandType::AcknowledgeBlocker, Op::Transition),
("PROVIDE_APPROVAL", CommandType::ProvideApproval, Op::Transition),
("PROVIDE_CONTEXT", CommandType::ProvideContext, Op::AmendScope),
(
"ACKNOWLEDGE_BLOCKER",
CommandType::AcknowledgeBlocker,
Op::Transition,
),
(
"PROVIDE_APPROVAL",
CommandType::ProvideApproval,
Op::Transition,
),
(
"PROVIDE_CONTEXT",
CommandType::ProvideContext,
Op::AmendScope,
),
] {
assert_eq!(CommandType::parse(name), Some(cmd));
assert_eq!(cmd.to_op(), op);
@@ -830,7 +864,13 @@ mod tests {
let prior = json!({"accepted": true, "op": "resume", "command_id": "cmd-1",
"idempotent_replay": false});
let r = command(
"RESUME", "c1", "corr", "cmd-1", &json!({}), NOW, Some(&prior),
"RESUME",
"c1",
"corr",
"cmd-1",
&json!({}),
NOW,
Some(&prior),
Some(&draft_doc("c1")),
);
assert_eq!(r["idempotent_replay"], true);
@@ -841,8 +881,15 @@ mod tests {
fn dispatch_exec_decision_carries_command_provenance() {
let doc = draft_doc("c1");
let prep = dispatch(
"PROVIDE_APPROVAL", "c1", "corr", "cmd-9",
&json!({"approved_by": "user"}), NOW, None, None, Some(&doc),
"PROVIDE_APPROVAL",
"c1",
"corr",
"cmd-9",
&json!({"approved_by": "user"}),
NOW,
None,
None,
Some(&doc),
);
assert_eq!(prep["kind"], "exec");
assert_eq!(prep["evidence_ref"], "command:cmd-9 approved_by:user");
@@ -860,8 +907,17 @@ mod tests {
#[test]
fn serving_unavailable_truncates_to_200_chars() {
let exc = "x".repeat(500);
let prep = dispatch("START", "c1", "corr", "cmd-1", &json!({}), NOW, None,
Some(&exc), None);
let prep = dispatch(
"START",
"c1",
"corr",
"cmd-1",
&json!({}),
NOW,
None,
Some(&exc),
None,
);
let err = prep["envelope"]["error"].as_str().unwrap();
assert_eq!(err.chars().count(), 200);
assert!(err.starts_with("serving unavailable: "));
@@ -869,4 +925,4 @@ mod tests {
}
#[cfg(test)]
mod branch_battery;
mod branch_battery;
@@ -92,7 +92,10 @@ fn normalize_need_battery() {
let need = &case["args"][0];
let out = normalize_need(need);
check(
&format!("normalize {}", serde_json::to_string(&need["need_id"]).unwrap()),
&format!(
"normalize {}",
serde_json::to_string(&need["need_id"]).unwrap()
),
out,
&case["expected"],
);
@@ -114,8 +117,8 @@ fn language_placement_battery() {
.iter()
.map(|v| v.as_str().unwrap())
.collect();
let actual = language_placement(project, &chars)
.unwrap_or_else(|e| serde_json::json!({"error": e}));
let actual =
language_placement(project, &chars).unwrap_or_else(|e| serde_json::json!({"error": e}));
check(
&format!("placement {} {:?}", project, chars),
actual,
@@ -167,7 +170,11 @@ fn ss_entity_battery() {
.filter(|c| c["fn"] == "ss_entity")
{
let actual = ss_entity_public(&case["args"][0], str_arg(case, 1)).unwrap_or(Value::Null);
check(&format!("entity {:?}", str_arg(case, 1)), actual, &case["expected"]);
check(
&format!("entity {:?}", str_arg(case, 1)),
actual,
&case["expected"],
);
}
}
@@ -186,11 +193,7 @@ fn implemented_contracts_battery() {
.map(Value::String)
.collect(),
);
check(
&format!("contracts {eid:?}"),
actual,
&case["expected"],
);
check(&format!("contracts {eid:?}"), actual, &case["expected"]);
}
}
@@ -297,7 +300,12 @@ fn needs_user_approval_battery() {
#[test]
fn project_exists_battery() {
// The oracle returns a tuple; frozen as [exists, evidence].
for (i, case) in truth_doc()["pe_cases"].as_array().unwrap().iter().enumerate() {
for (i, case) in truth_doc()["pe_cases"]
.as_array()
.unwrap()
.iter()
.enumerate()
{
let project = case["args"][0].as_str();
let (exists, evidence) = _project_exists_evidence(project, &case["args"][1]);
let actual = serde_json::json!([exists, evidence]);
@@ -350,10 +358,7 @@ fn make_decision_battery() {
.enumerate()
{
let k = &case["kwargs"];
let extra: Option<Vec<Value>> = k
.get("extra_patterns")
.and_then(|x| x.as_array())
.cloned();
let extra: Option<Vec<Value>> = k.get("extra_patterns").and_then(|x| x.as_array()).cloned();
let actual = make_decision(
&case["args"][0],
&k["authorities"],
@@ -376,7 +381,11 @@ fn need_view_helpers_battery() {
// the full projection round-trips), exactly like the oracle's
// `dict(need)`.
for case in cases.iter().filter(|c| c["fn"] == "_need_dict") {
check("_need_dict", _need_dict(&case["args"][0]), &case["expected"]);
check(
"_need_dict",
_need_dict(&case["args"][0]),
&case["expected"],
);
}
// _cdec — frozen under the harness pin: the oracle returned None.
for case in cases.iter().filter(|c| c["fn"] == "_cdec") {
+63 -28
View File
@@ -11,9 +11,9 @@ use serde_json::{json, Value};
use crate::{
_project_exists_evidence, _py_language_placement, check_completeness, classify_surface,
implemented_contracts, language_placement, layout_obligations_for, make_decision,
merge_resolutions, need_new, need_to_dict, normalize_need, obligations_from_trace,
record_proposed_requirement, resolve_affected_surface, resolve_surface, research_route,
needs_user_approval as approval_gate,
merge_resolutions, need_new, need_to_dict, needs_user_approval as approval_gate,
normalize_need, obligations_from_trace, record_proposed_requirement, research_route,
resolve_affected_surface, resolve_surface,
};
fn read_arg(arg: *const c_char) -> Result<Value, String> {
@@ -56,7 +56,10 @@ pub extern "C" fn cdec_need_new(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cdec_normalize_need(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let need = a.get("need").cloned().ok_or_else(|| "need required".to_string())?;
let need = a
.get("need")
.cloned()
.ok_or_else(|| "need required".to_string())?;
Ok(normalize_need(&need))
});
envelope(r)
@@ -144,7 +147,10 @@ pub extern "C" fn cdec_resolve_surface(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cdec_ss_entity(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let doc = a.get("doc").cloned().ok_or_else(|| "doc required".to_string())?;
let doc = a
.get("doc")
.cloned()
.ok_or_else(|| "doc required".to_string())?;
let eid = a
.get("eid")
.and_then(|s| s.as_str())
@@ -158,7 +164,10 @@ pub extern "C" fn cdec_ss_entity(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cdec_implemented_contracts(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let doc = a.get("doc").cloned().ok_or_else(|| "doc required".to_string())?;
let doc = a
.get("doc")
.cloned()
.ok_or_else(|| "doc required".to_string())?;
let entity_id = a
.get("entity_id")
.and_then(|s| s.as_str())
@@ -172,7 +181,10 @@ pub extern "C" fn cdec_implemented_contracts(arg: *const c_char) -> *mut c_char
#[no_mangle]
pub extern "C" fn cdec_resolve_affected_surface(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let need = a.get("need").cloned().ok_or_else(|| "need required".to_string())?;
let need = a
.get("need")
.cloned()
.ok_or_else(|| "need required".to_string())?;
let authorities = a.get("authorities").cloned().unwrap_or(json!({}));
Ok(resolve_affected_surface(&need, &authorities))
});
@@ -183,14 +195,25 @@ pub extern "C" fn cdec_resolve_affected_surface(arg: *const c_char) -> *mut c_ch
#[no_mangle]
pub extern "C" fn cdec_check_completeness(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let req = a.get("req").cloned().ok_or_else(|| "req required".to_string())?;
let need = a.get("need").cloned().ok_or_else(|| "need required".to_string())?;
let req = a
.get("req")
.cloned()
.ok_or_else(|| "req required".to_string())?;
let need = a
.get("need")
.cloned()
.ok_or_else(|| "need required".to_string())?;
let authorities = a.get("authorities").cloned().unwrap_or(json!({}));
let surface_result = a
.get("surface_result")
.cloned()
.ok_or_else(|| "surface_result required".to_string())?;
Ok(check_completeness(&req, &need, &authorities, &surface_result))
Ok(check_completeness(
&req,
&need,
&authorities,
&surface_result,
))
});
envelope(r)
}
@@ -199,10 +222,7 @@ pub extern "C" fn cdec_check_completeness(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cdec_record_proposed_requirement(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let contract_id = a
.get("contract_id")
.and_then(|s| s.as_str())
.unwrap_or("");
let contract_id = a.get("contract_id").and_then(|s| s.as_str()).unwrap_or("");
let provider = a.get("provider").and_then(|s| s.as_str()).unwrap_or("");
let model = a.get("model").and_then(|s| s.as_str()).unwrap_or("");
let evidence: Vec<Value> = a
@@ -223,7 +243,10 @@ pub extern "C" fn cdec_record_proposed_requirement(arg: *const c_char) -> *mut c
#[no_mangle]
pub extern "C" fn cdec_research_route(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let req = a.get("req").cloned().ok_or_else(|| "req required".to_string())?;
let req = a
.get("req")
.cloned()
.ok_or_else(|| "req required".to_string())?;
let decision = a
.get("decision")
.cloned()
@@ -251,10 +274,7 @@ pub extern "C" fn cdec_merge_resolutions(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cdec_needs_user_approval(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let resolution = a
.get("resolution")
.and_then(|s| s.as_str())
.unwrap_or("");
let resolution = a.get("resolution").and_then(|s| s.as_str()).unwrap_or("");
let toolchain_decision = a
.get("toolchain_decision")
.cloned()
@@ -264,8 +284,16 @@ pub extern "C" fn cdec_needs_user_approval(arg: *const c_char) -> *mut c_char {
.and_then(|x| x.as_array())
.cloned()
.unwrap_or_default();
let need = a.get("need").cloned().ok_or_else(|| "need required".to_string())?;
Ok(approval_gate(resolution, &toolchain_decision, &layout_obligations, &need))
let need = a
.get("need")
.cloned()
.ok_or_else(|| "need required".to_string())?;
Ok(approval_gate(
resolution,
&toolchain_decision,
&layout_obligations,
&need,
))
});
envelope(r)
}
@@ -292,14 +320,15 @@ pub extern "C" fn cdec_project_exists_evidence(arg: *const c_char) -> *mut c_cha
#[no_mangle]
pub extern "C" fn cdec_make_decision(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let need = a.get("need").cloned().ok_or_else(|| "need required".to_string())?;
let need = a
.get("need")
.cloned()
.ok_or_else(|| "need required".to_string())?;
let authorities = a.get("authorities").cloned().unwrap_or(json!({}));
let project_toolchain = a.get("project_toolchain");
let prior_iterations = a.get("prior_iterations");
let extra_patterns: Option<Vec<Value>> = a
.get("extra_patterns")
.and_then(|x| x.as_array())
.cloned();
let extra_patterns: Option<Vec<Value>> =
a.get("extra_patterns").and_then(|x| x.as_array()).cloned();
make_decision(
&need,
&authorities,
@@ -315,7 +344,10 @@ pub extern "C" fn cdec_make_decision(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cdec_layout_obligations_for(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let doc = a.get("doc").cloned().ok_or_else(|| "doc required".to_string())?;
let doc = a
.get("doc")
.cloned()
.ok_or_else(|| "doc required".to_string())?;
let affected: Vec<Value> = a
.get("affected")
.and_then(|x| x.as_array())
@@ -330,7 +362,10 @@ pub extern "C" fn cdec_layout_obligations_for(arg: *const c_char) -> *mut c_char
#[no_mangle]
pub extern "C" fn cdec_obligations_from_trace(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let trace = a.get("trace").cloned().ok_or_else(|| "trace required".to_string())?;
let trace = a
.get("trace")
.cloned()
.ok_or_else(|| "trace required".to_string())?;
let contracts: Vec<String> = a
.get("contracts")
.and_then(|x| x.as_array())
+69 -29
View File
@@ -150,8 +150,7 @@ pub fn need_new(d: &Value) -> Result<Value, String> {
let objective = obj.get("objective").and_then(|x| x.as_str()).unwrap_or("");
if objective.is_empty() {
return Err(
"objective (required effect) required — do not normalize a need we cannot state"
.into(),
"objective (required effect) required — do not normalize a need we cannot state".into(),
);
}
Ok(need_to_dict(d))
@@ -432,8 +431,8 @@ const STRUCTURAL_PREFIXES: [&str; 7] = [
];
const WORKFLOW_EXTS: [&str; 4] = [".yml", ".yaml", ".json", ".toml"];
const SRC_EXTS: [&str; 20] = [
".py", ".js", ".ts", ".jsx", ".tsx", ".rs", ".md", ".go", ".java", ".c", ".cpp", ".h",
".css", ".html", ".yaml", ".yml", ".mjs", ".cjs", ".mts", ".cts",
".py", ".js", ".ts", ".jsx", ".tsx", ".rs", ".md", ".go", ".java", ".c", ".cpp", ".h", ".css",
".html", ".yaml", ".yml", ".mjs", ".cjs", ".mts", ".cts",
];
impl SurfaceKind {
@@ -471,7 +470,8 @@ pub fn classify_surface(surface: &str) -> &'static str {
/// record HOW the mapping was established (identity | path_attribute |
/// layout_contract).
pub fn resolve_surface(surface: &str, kind: &str, authorities: &Value) -> Value {
let mut out = json!({"surface": surface, "kind": kind, "resolved": [], "unresolved_reason": ""});
let mut out =
json!({"surface": surface, "kind": kind, "resolved": [], "unresolved_reason": ""});
let binding = empty_map();
let auth_map = authorities.as_object().unwrap_or(&binding);
match SurfaceKind::from_str_kind(kind) {
@@ -607,7 +607,10 @@ pub fn resolve_affected_surface(need: &Value, authorities: &Value) -> Value {
for surface in &affected_surface {
let kind = SurfaceKind::classify(surface);
let res = resolve_surface(surface, kind.as_str(), authorities);
result["resolutions"].as_array_mut().unwrap().push(res.clone());
result["resolutions"]
.as_array_mut()
.unwrap()
.push(res.clone());
match kind {
SurfaceKind::SourcePath => result["affected_paths"]
.as_array_mut()
@@ -1013,10 +1016,7 @@ pub fn needs_user_approval(
let mut covered = !approval_context.is_empty() && reasons.is_empty();
if !reasons.is_empty()
&& !approval_context.is_empty()
&& constraints
.get("approval_covers")
.and_then(|x| x.as_str())
== Some("all")
&& constraints.get("approval_covers").and_then(|x| x.as_str()) == Some("all")
{
covered = true;
reasons = reasons
@@ -1131,7 +1131,13 @@ fn consumers_of(doc: &Value, eid: &str) -> Vec<String> {
/// (open-world kinds stay discoverable). Names are NOT fit.
fn cf_discover_candidates(doc: &Value, environment: &Value) -> Vec<Value> {
const BASE_KINDS: [&str; 7] = [
"capability", "module", "component", "service", "cli", "tool", "workflow",
"capability",
"module",
"component",
"service",
"cli",
"tool",
"workflow",
];
let mut candidates: Vec<Value> = vec![];
let Some(ents) = doc.get("entities").and_then(|e| e.as_array()) else {
@@ -1292,7 +1298,10 @@ fn cf_decide(
let mut best = exact[0];
for f in &exact[1..] {
if f.get("maturity_rank").and_then(|x| x.as_i64()).unwrap_or(0)
> best.get("maturity_rank").and_then(|x| x.as_i64()).unwrap_or(0)
> best
.get("maturity_rank")
.and_then(|x| x.as_i64())
.unwrap_or(0)
{
best = f;
}
@@ -1409,7 +1418,11 @@ fn cf_decide(
json!({"incompatible": incompatible.iter().map(|f| f["candidate"].clone()).collect::<Vec<_>>()}),
);
}
result("INSUFFICIENT_EVIDENCE", "no usable fit evidence".into(), json!({}))
result(
"INSUFFICIENT_EVIDENCE",
"no usable fit evidence".into(),
json!({}),
)
}
// ── layout obligations (§19-20) ─────────────────────────────────────────────
@@ -1433,7 +1446,10 @@ pub fn layout_obligations_for(doc: &Value, affected: &[Value]) -> Vec<Value> {
continue;
}
let ident = e.get("identities").cloned().unwrap_or(json!({}));
let comp = ident.get("component").and_then(|x| x.as_str()).unwrap_or("");
let comp = ident
.get("component")
.and_then(|x| x.as_str())
.unwrap_or("");
let region = ident.get("region").and_then(|x| x.as_str()).unwrap_or("");
let impl_path = ident
.get("implementation")
@@ -1532,7 +1548,9 @@ pub fn obligations_from_trace(trace: &Value, req_contracts: &[String]) -> Value
obligations["verification"]
.as_array_mut()
.unwrap()
.push(json!("regression proof: original capability's consumers still work"));
.push(json!(
"regression proof: original capability's consumers still work"
));
}
if resolution == "COMPOSE" {
let components = merged.get("components").cloned().unwrap_or(json!(selected));
@@ -1554,7 +1572,9 @@ pub fn obligations_from_trace(trace: &Value, req_contracts: &[String]) -> Value
obligations["verification"]
.as_array_mut()
.unwrap()
.push(json!("external acquisition requires research/approval step before install"));
.push(json!(
"external acquisition requires research/approval step before install"
));
}
if resolution == "BUILD_NEW" {
obligations["verification"]
@@ -1634,7 +1654,10 @@ pub fn make_decision(
None,
);
for ev in &existence_evidence {
mode_decision["evidence"].as_array_mut().unwrap().push(json!(ev));
mode_decision["evidence"]
.as_array_mut()
.unwrap()
.push(json!(ev));
}
trace["construction_mode"] = mode_decision;
trace["project_existence"] = json!({
@@ -1698,14 +1721,23 @@ pub fn make_decision(
}
if CompletenessVerdict::from_verdict(completeness["verdict"].as_str().unwrap_or(""))
== Some(CompletenessVerdict::MaterialGap)
&& ["SATISFIED", "REUSE", "COMPOSE", "ADAPT_EXISTING", "CONFIGURE"]
.contains(&merged["resolution"].as_str().unwrap_or(""))
&& [
"SATISFIED",
"REUSE",
"COMPOSE",
"ADAPT_EXISTING",
"CONFIGURE",
]
.contains(&merged["resolution"].as_str().unwrap_or(""))
{
// PREMATURE-FIT GUARD (Tranche 5 §6-7): the fit engine was correct
// GIVEN its requirement — but the requirement is materially
// incomplete (affected surface carries unrepresented obligations).
// A concrete verdict on an incomplete requirement is not trustworthy.
let gap_count = completeness["gaps"].as_array().map(|g| g.len()).unwrap_or(0);
let gap_count = completeness["gaps"]
.as_array()
.map(|g| g.len())
.unwrap_or(0);
let would_have_been = merged["resolution"].clone();
merged = json!({
"resolution": "INSUFFICIENT_EVIDENCE",
@@ -1777,7 +1809,10 @@ pub fn make_decision(
// Project evidence > pattern assumption (§3).
let mut pattern_candidates: Vec<Value> = construction_patterns_builtin::registry::patterns()
.iter()
.map(|p| serde_json::from_str::<Value>(&capability_scoring::pyjson::dumps(p)).unwrap_or(Value::Null))
.map(|p| {
serde_json::from_str::<Value>(&capability_scoring::pyjson::dumps(p))
.unwrap_or(Value::Null)
})
.collect();
if let Some(extra) = extra_patterns {
pattern_candidates.extend(extra.iter().cloned());
@@ -1792,9 +1827,15 @@ pub fn make_decision(
let mut patterns_considered: Vec<Value> = vec![];
let mut derivations: Vec<Value> = vec![];
for pattern in &pattern_candidates {
let Some(p_obj) = pattern.as_object() else { continue };
let app =
construction_pattern::evaluate_applicability(p_obj, &need_map, &surface_map, &auth_map2);
let Some(p_obj) = pattern.as_object() else {
continue;
};
let app = construction_pattern::evaluate_applicability(
p_obj,
&need_map,
&surface_map,
&auth_map2,
);
patterns_considered.push(app.clone());
// APPLIES and PARTIAL both reach obligation derivation: applicability
// gates whether the pattern is CONSULTED; the per-obligation `when`
@@ -1842,7 +1883,9 @@ pub fn make_decision(
obligations["verification"]
.as_array_mut()
.unwrap()
.push(json!(format!("[pattern:{pattern}@r{revision}] {obligation}")));
.push(json!(format!(
"[pattern:{pattern}@r{revision}] {obligation}"
)));
}
trace["obligations"] = obligations;
Ok(trace)
@@ -1862,10 +1905,7 @@ pub struct CdecCore;
/// None; every other value resolves the core. The oracle's load-failure
/// branch can't fire here — in-process is always loaded.
pub fn _cdec() -> Option<CdecCore> {
if std::env::var("RUNE_CONSTRUCTION_DECISION_CORE")
.unwrap_or_default()
== "python"
{
if std::env::var("RUNE_CONSTRUCTION_DECISION_CORE").unwrap_or_default() == "python" {
None
} else {
Some(CdecCore)
+4 -5
View File
@@ -236,7 +236,9 @@ pub fn select_depth(
ceremony.insert(0, "evidence probe".into());
}
if affected_surface_len > 5 {
ev.push(format!("wide affected surface: {affected_surface_len} entries"));
ev.push(format!(
"wide affected surface: {affected_surface_len} entries"
));
level = Level::Full;
reason = "wide surface — full depth warranted".into();
}
@@ -268,10 +270,7 @@ fn read_arg(ptr: *const c_char) -> Result<Json, String> {
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),
]),
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)),
@@ -114,12 +114,27 @@ fn audit_missing_fns_are_real_ports() {
})];
let (cont, after) = kernel::programme_continuation(&progs, "S1", "2026-10-13T09:41:23Z");
assert_eq!(cont["stage_marked_complete"], serde_json::json!("S1"));
assert_eq!(cont["canary"]["verdict"], serde_json::json!("PROGRAMME_OPEN"));
assert_eq!(cont["canary"]["next_action"], serde_json::json!(["start", "S2"]));
assert_eq!(cont["canary"]["reason"], serde_json::json!("1/2 complete; 1 eligible — continue immediately"));
assert_eq!(
cont["canary"]["verdict"],
serde_json::json!("PROGRAMME_OPEN")
);
assert_eq!(
cont["canary"]["next_action"],
serde_json::json!(["start", "S2"])
);
assert_eq!(
cont["canary"]["reason"],
serde_json::json!("1/2 complete; 1 eligible — continue immediately")
);
let after = after.as_array().unwrap();
assert_eq!(after[0]["stages"][0]["state"], serde_json::json!("COMPLETE"));
assert_eq!(
after[0]["stages"][0]["state"],
serde_json::json!("COMPLETE")
);
// save drops the in-memory result — the persisted shape is 5-key stages
assert!(after[0]["stages"][0].get("result").is_none());
assert_eq!(after[0]["saved_at"], serde_json::json!("2026-10-13T09:41:23Z"));
assert_eq!(
after[0]["saved_at"],
serde_json::json!("2026-10-13T09:41:23Z")
);
}
+304 -122
View File
@@ -67,13 +67,10 @@ fn find_contract<'a>(doc: &'a Value, contract_id: &str) -> Option<&'a Value> {
/// callers re-find rather than cache).
fn contract_pos(doc: &Value, contract_id: &str) -> Option<usize> {
let want = format!("contract:{}", contract_id);
doc.get("entities")?
.as_array()?
.iter()
.position(|e| {
e.get("kind").and_then(|k| k.as_str()) == Some("construction_contract")
&& e.get("id").and_then(|i| i.as_str()) == Some(want.as_str())
})
doc.get("entities")?.as_array()?.iter().position(|e| {
e.get("kind").and_then(|k| k.as_str()) == Some("construction_contract")
&& e.get("id").and_then(|i| i.as_str()) == Some(want.as_str())
})
}
/// The single-error failure envelope every contract-scoped operation
@@ -128,14 +125,16 @@ pub fn record_session(
.expect("doc is an object")
.entry("observations")
.or_insert(json!([]));
obs.as_array_mut().expect("observations is a list").push(json!({
"kind": "builder_session",
"builder": builder,
"mode": mode,
"operation": operation,
"detail": projection::py_truncate(detail, 200),
"observed_at": now,
}));
obs.as_array_mut()
.expect("observations is a list")
.push(json!({
"kind": "builder_session",
"builder": builder,
"mode": mode,
"operation": operation,
"detail": projection::py_truncate(detail, 200),
"observed_at": now,
}));
doc
}
@@ -175,10 +174,7 @@ pub fn inspect_decision(doc: &Value, contract_id: &str, builder: &str) -> Value
let obl = t.get("obligations").cloned().unwrap_or(json!({}));
let mut structural = serde_json::Map::new();
for k in ["regression", "verification", "composition", "acquisition"] {
structural.insert(
k.to_string(),
obl.get(k).cloned().unwrap_or(json!([])),
);
structural.insert(k.to_string(), obl.get(k).cloned().unwrap_or(json!([])));
}
let state = json!({
"requirement": t.get("requirement").cloned().unwrap_or(json!({})),
@@ -276,13 +272,23 @@ pub fn report_observation(
}
let Some(items_in) = observations.as_array() else {
return (
fail_envelope("report_observation", contract_id, "NEED_INCOMPLETE", "observations list required"),
fail_envelope(
"report_observation",
contract_id,
"NEED_INCOMPLETE",
"observations list required",
),
vec![],
);
};
if items_in.is_empty() {
return (
fail_envelope("report_observation", contract_id, "NEED_INCOMPLETE", "observations list required"),
fail_envelope(
"report_observation",
contract_id,
"NEED_INCOMPLETE",
"observations list required",
),
vec![],
);
}
@@ -291,14 +297,16 @@ pub fn report_observation(
let entity = o.get("entity").and_then(|e| e.as_str()).unwrap_or("");
if !o.is_object() || entity.is_empty() {
return (
fail_envelope("report_observation", contract_id, "NEED_INCOMPLETE", "each observation needs an entity"),
fail_envelope(
"report_observation",
contract_id,
"NEED_INCOMPLETE",
"each observation needs an entity",
),
vec![],
);
}
let detail = o
.get("detail")
.map(projection::py_str)
.unwrap_or_default();
let detail = o.get("detail").map(projection::py_str).unwrap_or_default();
items.push(json!({
"entity": entity,
"source": o.get("source").and_then(|s| s.as_str())
@@ -355,7 +363,8 @@ pub fn amend_scope(
let mut doc = doc.clone();
let Some(pos) = contract_pos(&doc, contract_id) else {
return (unknown_contract("amend_scope", contract_id), doc);
}; let mut scope: Vec<String> = doc["entities"][pos]["identities"]["scope"]
};
let mut scope: Vec<String> = doc["entities"][pos]["identities"]["scope"]
.as_array()
.map(|a| {
a.iter()
@@ -423,9 +432,7 @@ pub fn construction_context(
.and_then(|e| e.as_array())
.map(|ents| {
ents.iter()
.filter(|e| {
e.get("kind").and_then(|k| k.as_str()) == Some("construction_contract")
})
.filter(|e| e.get("kind").and_then(|k| k.as_str()) == Some("construction_contract"))
.collect()
})
.unwrap_or_default();
@@ -436,9 +443,9 @@ pub fn construction_context(
.iter()
.filter(|e| {
e.get("status").and_then(|s| s.as_str()) == Some("open")
&& !terminal_phases.iter().any(|p| {
Some(p.as_str()) == e.get("phase").and_then(|x| x.as_str())
})
&& !terminal_phases
.iter()
.any(|p| Some(p.as_str()) == e.get("phase").and_then(|x| x.as_str()))
})
.collect();
open.sort_by_key(|e| {
@@ -484,7 +491,10 @@ pub fn construction_context(
.map(|(_, rest)| rest)
.unwrap_or("")),
);
ctx.insert("need".into(), json!(projection::py_truncate(&need_obj, 200)));
ctx.insert(
"need".into(),
json!(projection::py_truncate(&need_obj, 200)),
);
ctx.insert(
"resolution".into(),
t.get("resolution").cloned().unwrap_or(json!("")),
@@ -496,8 +506,14 @@ pub fn construction_context(
.cloned()
.unwrap_or(Value::Null),
);
ctx.insert("phase".into(), target.get("phase").cloned().unwrap_or(Value::Null));
ctx.insert("status".into(), target.get("status").cloned().unwrap_or(Value::Null));
ctx.insert(
"phase".into(),
target.get("phase").cloned().unwrap_or(Value::Null),
);
ctx.insert(
"status".into(),
target.get("status").cloned().unwrap_or(Value::Null),
);
ctx.insert(
"scope".into(),
target
@@ -518,7 +534,10 @@ pub fn construction_context(
.cloned()
.unwrap_or(json!([])),
);
ctx.insert("approval".into(), t.get("approval").cloned().unwrap_or(json!({})));
ctx.insert(
"approval".into(),
t.get("approval").cloned().unwrap_or(json!({})),
);
ctx.insert(
"completeness".into(),
t.get("completeness")
@@ -744,17 +763,18 @@ pub fn discover_active_contract(candidates: &Value, root_name: &str) -> Value {
if e.get("kind").and_then(|k| k.as_str()) != Some("construction_contract") {
continue;
}
let proj = project
.map(str::to_string)
.unwrap_or_else(|| {
e.get("identities")
.and_then(|i| i.get("project"))
.and_then(|p| p.as_str())
.unwrap_or(root_name)
.to_string()
});
let proj = project.map(str::to_string).unwrap_or_else(|| {
e.get("identities")
.and_then(|i| i.get("project"))
.and_then(|p| p.as_str())
.unwrap_or(root_name)
.to_string()
});
let key = (
e.get("observed_at").and_then(|o| o.as_str()).unwrap_or("").to_string(),
e.get("observed_at")
.and_then(|o| o.as_str())
.unwrap_or("")
.to_string(),
proj.clone(),
e.get("id")
.and_then(|i| i.as_str())
@@ -763,7 +783,11 @@ pub fn discover_active_contract(candidates: &Value, root_name: &str) -> Value {
.map(|(_, rest)| rest.to_string())
.unwrap_or_default(),
);
if best.as_ref().map(|b| key > (b.0.clone(), b.1.clone(), b.2.clone())).unwrap_or(true) {
if best
.as_ref()
.map(|b| key > (b.0.clone(), b.1.clone(), b.2.clone()))
.unwrap_or(true)
{
best = Some(key);
best_project = proj;
}
@@ -854,10 +878,7 @@ pub fn submit_need(
)
.unwrap_or(json!({}));
let mut ids = serde_json::Map::new();
ids.insert(
"need_id".to_string(),
need["need_id"].clone(),
);
ids.insert("need_id".to_string(), need["need_id"].clone());
let resolution = trace
.get("resolution")
.and_then(|r| r.as_str())
@@ -947,7 +968,14 @@ pub fn submit_need(
need["need_id"].as_str().unwrap_or(""),
resolution
);
let doc = record_session(&doc, "submit_need", &detail, builder, "external_project", now);
let doc = record_session(
&doc,
"submit_need",
&detail,
builder,
"external_project",
now,
);
let doc = normalize_sorted(doc);
let env = projection::envelope(
"submit_need",
@@ -965,7 +993,13 @@ pub fn submit_need(
/// `generate_contract(doc, trace, contract_id, now, builder)` — the
/// construction_decision.generate_contract + scaffold.new_contract twin
/// (mutates the carried doc).
pub fn generate_contract(doc: &mut Value, trace: &Value, contract_id: &str, now: &str, builder: &str) {
pub fn generate_contract(
doc: &mut Value,
trace: &Value,
contract_id: &str,
now: &str,
builder: &str,
) {
let merged = trace.get("_merged").cloned().unwrap_or(json!({}));
let req = trace.get("requirement").cloned().unwrap_or(json!({}));
let surfaces = trace.get("affected_surfaces").cloned().unwrap_or(json!({}));
@@ -1197,7 +1231,10 @@ pub fn generate_contract(doc: &mut Value, trace: &Value, contract_id: &str, now:
}
fn resolution_of(trace: &Value) -> &str {
trace.get("resolution").and_then(|r| r.as_str()).unwrap_or("")
trace
.get("resolution")
.and_then(|r| r.as_str())
.unwrap_or("")
}
fn sorted_strs(v: Option<&Value>) -> Vec<String> {
@@ -1268,9 +1305,11 @@ pub fn check_scope(doc: &Value, contract_id: &str, changed_files: &[String]) ->
let mut in_scope = Vec::new();
let mut out_scope = Vec::new();
for f in changed_files {
let ok = scope
.iter()
.any(|s| f == s || f.starts_with(&format!("{}/", s.trim_end_matches('/'))) || f.contains(s.as_str()));
let ok = scope.iter().any(|s| {
f == s
|| f.starts_with(&format!("{}/", s.trim_end_matches('/')))
|| f.contains(s.as_str())
});
if ok {
in_scope.push(f.clone());
} else {
@@ -1335,7 +1374,10 @@ pub fn verify(
}
if let Some(obs) = layout_observations {
let gate = layout_contract::check_layout(
layout_contracts.as_array().map(|a| a.as_slice()).unwrap_or(&[]),
layout_contracts
.as_array()
.map(|a| a.as_slice())
.unwrap_or(&[]),
obs,
);
state.insert(
@@ -1481,7 +1523,12 @@ pub fn transition_doc(
let current = c.get("phase").and_then(|p| p.as_str()).unwrap_or("draft");
if !PHASES.contains(&phase) {
return (
fail_envelope("transition", contract_id, "UNKNOWN_CONTRACT", &format!("unknown phase: {}", phase)),
fail_envelope(
"transition",
contract_id,
"UNKNOWN_CONTRACT",
&format!("unknown phase: {}", phase),
),
doc.clone(),
);
}
@@ -1506,7 +1553,11 @@ pub fn transition_doc(
"illegal transition: {} → {} (legal: [{}])",
current,
phase,
legal.iter().map(|s| format!("'{}'", s)).collect::<Vec<_>>().join(", ")
legal
.iter()
.map(|s| format!("'{}'", s))
.collect::<Vec<_>>()
.join(", ")
),
),
doc.clone(),
@@ -1617,7 +1668,12 @@ fn normalize_sorted_ref(v: &Value) -> Value {
/// `record_observed_impact(doc, contract_id, items, now)` — the
/// structural_store.record_observed_impact twin (mutates the carried doc).
pub fn record_observed_impact(doc: &mut Value, contract_id: &str, items: &[Value], now: &str) -> Value {
pub fn record_observed_impact(
doc: &mut Value,
contract_id: &str,
items: &[Value],
now: &str,
) -> Value {
let mut implicated: Vec<String> = Vec::new();
for item in items {
let eid = item.get("entity").and_then(|e| e.as_str()).unwrap_or("");
@@ -1693,7 +1749,11 @@ pub fn close_quality(inputs: &Value) -> Value {
let expected_new_missing = set_minus(&expected_new, &obs_set);
let matched_runtime: Vec<String> = obs_set
.iter()
.filter(|o| expected_runtime.iter().any(|eff| eff.starts_with(o.as_str())))
.filter(|o| {
expected_runtime
.iter()
.any(|eff| eff.starts_with(o.as_str()))
})
.cloned()
.collect();
let matched_runtime = sorted_set(matched_runtime);
@@ -1909,7 +1969,8 @@ pub fn close(
) -> (Value, Value, Value, Value) {
let mut doc = doc.clone();
let scope_vec: Vec<String> = scope_files.map(|s| s.to_vec()).unwrap_or_default();
let (material, ledger_after) = auto_material_change(&doc, &scope_vec, ledger_doc, builder, project, now);
let (material, ledger_after) =
auto_material_change(&doc, &scope_vec, ledger_doc, builder, project, now);
let (continuation, programmes_after) = programme_continuation(programmes, contract_id, now);
match close_contract(
&mut doc,
@@ -2004,7 +2065,10 @@ pub fn auto_material_change(
result.insert("changed".to_string(), json!(changed));
result.insert("unmodeled".to_string(), json!(unmodeled));
if changed.is_empty() && scope_files.is_empty() {
result.insert("note".to_string(), json!("no capability ledger for this project"));
result.insert(
"note".to_string(),
json!("no capability ledger for this project"),
);
}
(Value::Object(result), doc)
}
@@ -2042,9 +2106,8 @@ pub fn continuation_canary(prog: &Value) -> Value {
.get("depends_on")
.and_then(|d| d.as_array())
.map(|deps| {
deps.iter().all(|d| {
d.as_str().map(|id| done.contains(&id)).unwrap_or(false)
})
deps.iter()
.all(|d| d.as_str().map(|id| done.contains(&id)).unwrap_or(false))
})
.unwrap_or(true)
})
@@ -2052,7 +2115,10 @@ pub fn continuation_canary(prog: &Value) -> Value {
eligible.sort_by_key(|s| {
(
s.get("priority").and_then(|p| p.as_i64()).unwrap_or(0),
s.get("stage_id").and_then(|x| x.as_str()).unwrap_or("").to_string(),
s.get("stage_id")
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string(),
)
});
if let Some(nxt) = eligible.first() {
@@ -2069,7 +2135,10 @@ pub fn continuation_canary(prog: &Value) -> Value {
.filter(|s| s.get("state").and_then(|x| x.as_str()) == Some("WAITING_EVENT"))
.collect();
if !waiting.is_empty() {
let sid = waiting[0].get("stage_id").and_then(|x| x.as_str()).unwrap_or("");
let sid = waiting[0]
.get("stage_id")
.and_then(|x| x.as_str())
.unwrap_or("");
return json!({
"verdict": "PROGRAMME_OPEN_WAITING",
"next_action": ["await_event", sid],
@@ -2115,14 +2184,20 @@ pub fn continuation_canary(prog: &Value) -> Value {
});
}
let terminal = ["COMPLETE", "BLOCKED", "APPROVAL_REQUIRED", "FAILED"];
if stages
.iter()
.all(|s| s.get("state").and_then(|x| x.as_str()).map(|st| terminal.contains(&st)).unwrap_or(false))
{
if stages.iter().all(|s| {
s.get("state")
.and_then(|x| x.as_str())
.map(|st| terminal.contains(&st))
.unwrap_or(false)
}) {
let failed: Vec<String> = stages
.iter()
.filter(|s| s.get("state").and_then(|x| x.as_str()) == Some("FAILED"))
.filter_map(|s| s.get("stage_id").and_then(|x| x.as_str()).map(str::to_string))
.filter_map(|s| {
s.get("stage_id")
.and_then(|x| x.as_str())
.map(str::to_string)
})
.collect();
return json!({
"verdict": "PROGRAMME_COMPLETE_WITH_FAILURES",
@@ -2143,15 +2218,17 @@ pub fn continuation_canary(prog: &Value) -> Value {
/// the persisted-after shape is the Programme.save projection (5-key
/// stages, fallbacks, saved_at — the in-memory result never persists).
/// Returns (continuation|null, programmes_after|null).
pub fn programme_continuation(programmes: &[Value], contract_id: &str, now: &str) -> (Value, Value) {
pub fn programme_continuation(
programmes: &[Value],
contract_id: &str,
now: &str,
) -> (Value, Value) {
if programmes.is_empty() {
return (Value::Null, Value::Null);
}
let mut loaded: Vec<Value> = programmes.iter().map(load_programme).collect();
for prog in loaded.iter_mut() {
let stages = prog
.get_mut("stages")
.and_then(|s| s.as_array_mut());
let stages = prog.get_mut("stages").and_then(|s| s.as_array_mut());
let Some(stages) = stages else { continue };
let hit = stages.iter_mut().find(|s| {
s.get("stage_id").and_then(|x| x.as_str()) == Some(contract_id)
@@ -2250,7 +2327,8 @@ fn save_programme(p: &Value, now: &str) -> Value {
/// `classify(objective, builder)` — the envelope assembly over the
/// canonical construction_mode classifier (its port surface).
pub fn classify_objective(objective: &str, builder: &str) -> Value {
let d = construction_mode::classify_mode(objective, None, None, None, None, Some(builder), None);
let d =
construction_mode::classify_mode(objective, None, None, None, None, Some(builder), None);
let mut state = d.clone();
state
.as_object_mut()
@@ -2302,7 +2380,11 @@ pub fn main_dispatch(argv: &[String]) -> Value {
/// battery and the ci_replay FFI: {fn, args, kwargs} → expected.
pub fn dispatch(case: &Value) -> Value {
let name = case.get("fn").and_then(|f| f.as_str()).unwrap_or("");
let args = case.get("args").and_then(|a| a.as_array()).cloned().unwrap_or_default();
let args = case
.get("args")
.and_then(|a| a.as_array())
.cloned()
.unwrap_or_default();
let kwargs = case.get("kwargs").cloned().unwrap_or(json!({}));
let get = |i: usize| args.get(i).cloned().unwrap_or(Value::Null);
let kw = |k: &str| kwargs.get(k).cloned().unwrap_or(Value::Null);
@@ -2332,14 +2414,13 @@ pub fn dispatch(case: &Value) -> Value {
Value::Array(projection::guard_specs_from_obligations(&get(0)))
}
"t_layout" => projection::t_layout(&get(0)),
"_obligations_projection" => {
Value::Array(projection::obligations_projection(&get(0)))
}
"_obligation_scope_files" => {
Value::Array(projection::obligation_scope_files(&get(0)))
}
"_obligations_projection" => Value::Array(projection::obligations_projection(&get(0))),
"_obligation_scope_files" => Value::Array(projection::obligation_scope_files(&get(0))),
"_authorities" => {
let mode = kw("mode").as_str().unwrap_or("external_project").to_string();
let mode = kw("mode")
.as_str()
.unwrap_or("external_project")
.to_string();
authorities(&get(0), &get(1), &mode)
}
"_record_session" => record_session(
@@ -2350,10 +2431,7 @@ pub fn dispatch(case: &Value) -> Value {
"external_project",
"2026-10-13T09:41:23Z",
),
"classify" => classify(
&get(0),
kw("builder").as_str().unwrap_or("factory-gap-ci"),
),
"classify" => classify(&get(0), kw("builder").as_str().unwrap_or("factory-gap-ci")),
"construction_context" => construction_context(
&get(1),
get(2).as_str(),
@@ -2372,28 +2450,60 @@ pub fn dispatch(case: &Value) -> Value {
let objective = get(2).as_str().unwrap_or("").to_string();
let origin = kw("origin").as_str().unwrap_or("builder").to_string();
let constraints = kw("constraints");
let constraints = if constraints.is_null() { json!({}) } else { constraints };
let constraints = if constraints.is_null() {
json!({})
} else {
constraints
};
let affected = kw("affected_surface");
let affected = if affected.is_null() { json!([]) } else { affected };
let affected = if affected.is_null() {
json!([])
} else {
affected
};
let evidence = kw("evidence_refs");
let evidence = if evidence.is_null() { json!([]) } else { evidence };
let evidence = if evidence.is_null() {
json!([])
} else {
evidence
};
let approval = kw("approval_context").as_str().unwrap_or("").to_string();
let task_ref = kw("task_ref").as_str().unwrap_or("").to_string();
let generate = kw("generate").as_bool().unwrap_or(true);
let cid = kw("contract_id");
let cid = cid.as_str();
let (env, _) = submit_need(
doc, &objective, &origin, &constraints, &affected, &evidence,
&approval, &task_ref, generate, cid,
"factory-gap-ci", "gap-ci-proj", "2026-10-13T09:41:23Z", "000001",
doc,
&objective,
&origin,
&constraints,
&affected,
&evidence,
&approval,
&task_ref,
generate,
cid,
"factory-gap-ci",
"gap-ci-proj",
"2026-10-13T09:41:23Z",
"000001",
);
env
}
"inspect_decision" => inspect_decision(&get(1), get(2).as_str().unwrap_or(""), "factory-gap-ci"),
"inspect_decision" => {
inspect_decision(&get(1), get(2).as_str().unwrap_or(""), "factory-gap-ci")
}
"get_scope" => get_scope(&get(1), get(2).as_str().unwrap_or(""), "factory-gap-ci"),
"get_obligations" => get_obligations(&get(1), get(2).as_str().unwrap_or(""), "factory-gap-ci"),
"get_obligations" => {
get_obligations(&get(1), get(2).as_str().unwrap_or(""), "factory-gap-ci")
}
"report_observation" => {
let (env, _) = report_observation(&get(1), get(2).as_str().unwrap_or(""), &get(3), "factory-gap-ci");
let (env, _) = report_observation(
&get(1),
get(2).as_str().unwrap_or(""),
&get(3),
"factory-gap-ci",
);
env
}
"verify" => {
@@ -2447,22 +2557,42 @@ pub fn dispatch(case: &Value) -> Value {
let mini_doc = format!("/tmp/ci_gap_freeze_home/cases/{}/docs/{}.md", case, cid);
let evidence: Vec<Value> = get(3).as_array().cloned().unwrap_or_default();
let (env, _, _, _) = close(
&get(1), &cid, &evidence, scope.as_deref(), &mini_doc,
None, &[], "factory-gap-ci", "external_project", "gap-ci-proj",
&get(1),
&cid,
&evidence,
scope.as_deref(),
&mini_doc,
None,
&[],
"factory-gap-ci",
"external_project",
"gap-ci-proj",
"2026-10-13T09:41:23Z",
);
env
}
"_programme_continuation" => {
let programmes: Vec<Value> = kw("programmes").as_array().cloned().unwrap_or_default();
let (cont, after) = programme_continuation(&programmes, get(2).as_str().unwrap_or(""), "2026-10-13T09:41:23Z");
let (cont, after) = programme_continuation(
&programmes,
get(2).as_str().unwrap_or(""),
"2026-10-13T09:41:23Z",
);
json!({"continuation": cont, "programmes_after": after})
}
"_auto_material_change" => {
let scope = strargs(&get(2));
let caps = if kw("capabilities").is_null() { None } else { Some(kw("capabilities")) };
let caps = if kw("capabilities").is_null() {
None
} else {
Some(kw("capabilities"))
};
let (result, after) = auto_material_change(
&get(1), &scope, caps.as_ref(), "factory-gap-ci", "gap-ci-proj",
&get(1),
&scope,
caps.as_ref(),
"factory-gap-ci",
"gap-ci-proj",
"2026-10-13T09:41:23Z",
);
json!({"result": result, "ledger_after": after})
@@ -2480,14 +2610,25 @@ pub fn dispatch(case: &Value) -> Value {
.collect();
discover_active_contract(&Value::Array(rows), "store")
}
"resume" => resume(&get(1), get(2).as_str().unwrap_or(""), "factory-gap-ci", "external_project"),
"resume" => resume(
&get(1),
get(2).as_str().unwrap_or(""),
"factory-gap-ci",
"external_project",
),
"amend_scope" => {
let paths = strargs(&get(3));
let reason = kw("reason").as_str().unwrap_or("").to_string();
let authority = kw("authority").as_str().unwrap_or("user").to_string();
let (env, after) = amend_scope(
&get(1), get(2).as_str().unwrap_or(""), &paths, &reason,
&authority, "factory-gap-ci", "external_project", "2026-10-13T09:41:23Z",
&get(1),
get(2).as_str().unwrap_or(""),
&paths,
&reason,
&authority,
"factory-gap-ci",
"external_project",
"2026-10-13T09:41:23Z",
);
let cid = get(2).as_str().unwrap_or("").to_string();
json!({"envelope": env, "contract_after": contract_of(&after, &cid).cloned().unwrap_or(Value::Null)})
@@ -2504,7 +2645,11 @@ pub fn dispatch(case: &Value) -> Value {
main_exit_code(&argv)
}
"ladder_walk" => {
let obj = if args.len() > 1 { get(1).as_str().map(str::to_string) } else { None };
let obj = if args.len() > 1 {
get(1).as_str().map(str::to_string)
} else {
None
};
ladder_walk_replay(&get(0), obj.as_deref())
}
_ => json!({"error": format!("unknown frozen fn {}", name)}),
@@ -2610,8 +2755,15 @@ fn ladder_walk_replay(case: &Value, objective: Option<&str>) -> Value {
out.push(json!({"step": "verify", "envelope": verify(&doc, &cid, None, &json!([]), None, "factory-gap-ci")}));
for ph in ["approved", "building", "verifying"] {
let (env, after) = transition_doc(
&doc, &cid, ph, "", None, &json!({}),
"factory-gap-ci", "external_project", "2026-10-13T09:41:23Z",
&doc,
&cid,
ph,
"",
None,
&json!({}),
"factory-gap-ci",
"external_project",
"2026-10-13T09:41:23Z",
);
out.push(json!({"step": format!("transition:{}", ph), "envelope": env.clone()}));
if env.get("success").and_then(|s| s.as_bool()) != Some(true) {
@@ -2620,18 +2772,27 @@ fn ladder_walk_replay(case: &Value, objective: Option<&str>) -> Value {
doc = after;
}
let case_name = case.as_str().unwrap_or("walk");
let mini_doc = format!("/tmp/ci_gap_freeze_home/cases/{}/docs/{}.md", case_name, cid);
let mini_doc = format!(
"/tmp/ci_gap_freeze_home/cases/{}/docs/{}.md",
case_name, cid
);
let (env, _, _, _) = close(
&doc, &cid,
&doc,
&cid,
&[json!({"kind": "port_evidence", "detail": "gap closed with proof"})],
None, &mini_doc, None, &[],
"factory-gap-ci", "external_project", "gap-ci-proj", "2026-10-13T09:41:23Z",
None,
&mini_doc,
None,
&[],
"factory-gap-ci",
"external_project",
"gap-ci-proj",
"2026-10-13T09:41:23Z",
);
out.push(json!({"step": "close", "envelope": env}));
Value::Array(out)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -2674,8 +2835,8 @@ mod tests {
assert_eq!(e["errors"][0]["error"], json!("NEED_INCOMPLETE"));
let (e, _) = report_observation(&doc, "C-1", &json!([{"detail": "x"}]), "b");
assert_eq!(e["errors"][0]["error"], json!("NEED_INCOMPLETE"));
let (e, items) = report_observation(
&doc, "C-1", &json!([{"entity": "e", "detail": "d"}]), "b");
let (e, items) =
report_observation(&doc, "C-1", &json!([{"entity": "e", "detail": "d"}]), "b");
assert_eq!(e["success"], json!(true));
assert_eq!(items.len(), 1);
assert_eq!(items[0]["source"], json!("builder:b"));
@@ -2684,13 +2845,34 @@ mod tests {
#[test]
fn amend_requires_user() {
let doc = contract_doc("C-1");
let (e, _) = amend_scope(&doc, "C-1", &["x".to_string()], "r", "builder", "b", "m", "n");
let (e, _) = amend_scope(
&doc,
"C-1",
&["x".to_string()],
"r",
"builder",
"b",
"m",
"n",
);
assert_eq!(e["success"], json!(false));
assert_eq!(e["errors"][0]["error"], json!("APPROVAL_REQUIRED"));
let (e, after) = amend_scope(&doc, "C-1", &["src/b.py".to_string()], "user asked", "user", "b", "m", "n");
let (e, after) = amend_scope(
&doc,
"C-1",
&["src/b.py".to_string()],
"user asked",
"user",
"b",
"m",
"n",
);
assert_eq!(e["success"], json!(true));
assert!(e["state"]["scope"].as_array().unwrap().len() == 2);
assert_eq!(after["entities"][0]["ext"]["scope_amendments"][0]["authorized_by"], json!("user"));
assert_eq!(
after["entities"][0]["ext"]["scope_amendments"][0]["authorized_by"],
json!("user")
);
}
#[test]
+1 -2
View File
@@ -306,8 +306,7 @@ pub extern "C" fn ci_replay(arg: *const u8, arg_len: usize) -> *mut u8 {
unsafe {
let slice = std::slice::from_raw_parts(arg, arg_len);
let text = std::str::from_utf8(slice).unwrap_or("{}");
let case: serde_json::Value =
serde_json::from_str(text).unwrap_or(serde_json::json!({}));
let case: serde_json::Value = serde_json::from_str(text).unwrap_or(serde_json::json!({}));
let result = kernel::dispatch(&case);
to_cstring(result.to_string())
}
@@ -219,15 +219,29 @@ pub fn envelope(
env.insert("operation".into(), json!(operation));
env.insert("success".into(), json!(success));
env.insert("ids".into(), if ids.is_null() { json!({}) } else { ids });
env.insert("state".into(), if state.is_null() { json!({}) } else { state });
env.insert(
"state".into(),
if state.is_null() { json!({}) } else { state },
);
env.insert(
"evidence".into(),
if evidence.is_null() { json!([]) } else { evidence },
if evidence.is_null() {
json!([])
} else {
evidence
},
);
env.insert(
"errors".into(),
if errors.is_null() { json!([]) } else { errors },
);
env.insert("errors".into(), if errors.is_null() { json!([]) } else { errors });
env.insert(
"warnings".into(),
if warnings.is_null() { json!([]) } else { warnings },
if warnings.is_null() {
json!([])
} else {
warnings
},
);
if let Value::Object(extra_map) = extra {
for (k, v) in extra_map {
@@ -95,7 +95,8 @@ fn classify_battery() {
}
let got = run_classify(case);
assert_eq!(
got, case["expected"],
got,
case["expected"],
"case {:?} (kwargs {}): classify diverged from frozen oracle truth",
case["args"].get(0).and_then(|v| v.as_str()),
case["kwargs"]
@@ -131,7 +132,10 @@ fn to_dict_battery() {
fn from_dict_battery() {
// Auxiliary from_dict_cases column: known-key projection + defaults,
// unknown keys dropped, explicit nulls honored verbatim.
for entry in env_doc()["from_dict_cases"].as_array().expect("from_dict_cases") {
for entry in env_doc()["from_dict_cases"]
.as_array()
.expect("from_dict_cases")
{
let got = ConstructionMode::from_dict(&entry["args"])
.expect("from_dict must construct")
.to_dict();
@@ -148,25 +152,29 @@ fn error_shapes_battery() {
// Crash parity: the oracle's TypeError/AttributeError messages are
// the contract (str(e) strips the type name) — frozen in the envelope.
let shapes = &env_doc()["error_shapes"];
let err = ConstructionMode::from_dict(&json!({"evidence": []}))
.expect_err("missing mode must error");
let err =
ConstructionMode::from_dict(&json!({"evidence": []})).expect_err("missing mode must error");
assert_eq!(
err,
shapes["from_dict_missing_mode"].as_str().expect("frozen message"),
shapes["from_dict_missing_mode"]
.as_str()
.expect("frozen message"),
"from_dict missing-mode message"
);
let err = ConstructionMode::from_dict(&json!(123))
.expect_err("non-dict must error");
let err = ConstructionMode::from_dict(&json!(123)).expect_err("non-dict must error");
assert_eq!(
err,
shapes["from_dict_non_dict"].as_str().expect("frozen message"),
shapes["from_dict_non_dict"]
.as_str()
.expect("frozen message"),
"from_dict non-dict message"
);
let err = to_dict_of(&json!({"mode": "X"}))
.expect_err("missing attribute must error");
let err = to_dict_of(&json!({"mode": "X"})).expect_err("missing attribute must error");
assert_eq!(
err,
shapes["to_dict_missing_attribute"].as_str().expect("frozen message"),
shapes["to_dict_missing_attribute"]
.as_str()
.expect("frozen message"),
"to_dict missing-attribute message"
);
}
+235 -66
View File
@@ -44,40 +44,123 @@ impl Mode {
// multi-hit evidence slices ([..:3]) and their formatted strings are
// order-sensitive; the arrays must reproduce the oracle's order exactly.
const STANDALONE_WORDS: &[&str] = &[
"separate app", "own repo", "scaffold a new", "greenfield", "new tool",
"new project", "standalone", "new service", "new web", "new application",
"separate service", "new repository", "from scratch", "new app",
"separate app",
"own repo",
"scaffold a new",
"greenfield",
"new tool",
"new project",
"standalone",
"new service",
"new web",
"new application",
"separate service",
"new repository",
"from scratch",
"new app",
];
const KNOWN_FILES: &[&str] = &[
"readme", "makefile", "dockerfile", "changelog", "license", "gemfile",
"procfile", "jenkinsfile", "justfile",
"readme",
"makefile",
"dockerfile",
"changelog",
"license",
"gemfile",
"procfile",
"jenkinsfile",
"justfile",
];
const LOCAL_WORDS: &[&str] = &[
"one-liner", "tiny fix", "typo", "small fix", "comment fix",
"quick fix", "patch", "small correction", "cosmetic", "minor",
"one-liner",
"tiny fix",
"typo",
"small fix",
"comment fix",
"quick fix",
"patch",
"small correction",
"cosmetic",
"minor",
];
const SCOPING_WORDS: &[&str] = &[
"the project", "this app", "current", "the module", "the service",
"this module", "that module", "this project", "the app", "existing",
"the project",
"this app",
"current",
"the module",
"the service",
"this module",
"that module",
"this project",
"the app",
"existing",
"the codebase",
];
const CONSTRUCTION_WORDS: &[&str] = &[
"build", "create", "make", "implement", "add", "write", "develop",
"construct", "fix", "repair", "refactor", "migrate", "change", "extend",
"replace", "feature", "export", "module", "service", "app", "tool",
"component", "schema", "api",
"build",
"create",
"make",
"implement",
"add",
"write",
"develop",
"construct",
"fix",
"repair",
"refactor",
"migrate",
"change",
"extend",
"replace",
"feature",
"export",
"module",
"service",
"app",
"tool",
"component",
"schema",
"api",
];
const NOT_CONSTRUCTION_HINTS: &[&str] = &[
"explain", "teach me", "confidence", "describe", "list", "why does",
"what is", "how does", "improvisation", "summarize",
"explain",
"teach me",
"confidence",
"describe",
"list",
"why does",
"what is",
"how does",
"improvisation",
"summarize",
];
const CREATION_VERBS: &[&str] = &["build", "create", "make", "write", "develop"];
const ARTIFACT_NOUNS: &[&str] = &[
"program", "cli", "tool", "app", "script", "service", "library",
"package", "module", "dashboard", "website", "bot", "generator",
"tracker", "game", "application", "command",
"program",
"cli",
"tool",
"app",
"script",
"service",
"library",
"package",
"module",
"dashboard",
"website",
"bot",
"generator",
"tracker",
"game",
"application",
"command",
];
const NON_CONSTRUCTIBLE: &[&str] = &[
"list",
"confidence",
"explanation",
"summary",
"chord",
"note",
];
const NON_CONSTRUCTIBLE: &[&str] = &["list", "confidence", "explanation", "summary", "chord", "note"];
/// `_tokens` (construction_mode.py:190-191) — codepoint set split.
fn tokens(text: &str) -> Vec<String> {
@@ -180,15 +263,14 @@ impl ConstructionMode {
))
}
};
let mode = match obj.get("mode") {
Some(v) => v.clone(),
None => {
return Err(
let mode =
match obj.get("mode") {
Some(v) => v.clone(),
None => return Err(
"ConstructionMode.__init__() missing 1 required positional argument: 'mode'"
.to_string(),
)
}
};
),
};
Ok(ConstructionMode {
mode,
evidence: obj.get("evidence").cloned().unwrap_or(json!([])),
@@ -200,7 +282,10 @@ impl ConstructionMode {
.cloned()
.unwrap_or(json!(false)),
probe_questions: obj.get("probe_questions").cloned().unwrap_or(json!([])),
bootstrap_required: obj.get("bootstrap_required").cloned().unwrap_or(json!(false)),
bootstrap_required: obj
.get("bootstrap_required")
.cloned()
.unwrap_or(json!(false)),
r#override: obj.get("override").cloned().unwrap_or(Value::Null),
proposed_by_model: obj.get("proposed_by_model").cloned().unwrap_or(Value::Null),
})
@@ -250,7 +335,9 @@ pub fn to_dict_of(d: &Value) -> Result<Value, String> {
fn has_verb(obj: &str) -> bool {
let t = tokens(obj);
CONSTRUCTION_WORDS.iter().any(|w| t.iter().any(|tok| tok == *w))
CONSTRUCTION_WORDS
.iter()
.any(|w| t.iter().any(|tok| tok == *w))
}
/// classify_surface lexical fallback (construction_decision.classify_surface):
@@ -262,7 +349,10 @@ fn classify_surface_lexical(w: &str) -> Option<&'static str> {
}
if w.contains('.') || w.contains('/') {
// dotted/path: source-ish extensions and known config names
let src_exts = ["py", "rs", "go", "js", "ts", "c", "cpp", "h", "md", "yml", "yaml", "json", "toml", "sh"];
let src_exts = [
"py", "rs", "go", "js", "ts", "c", "cpp", "h", "md", "yml", "yaml", "json", "toml",
"sh",
];
if let Some(ext) = w.rsplit('.').next() {
if src_exts.contains(&ext) {
return Some("source_path");
@@ -302,7 +392,10 @@ pub fn classify_mode(
"override": null, "proposed_by_model": model_proposal,
});
}
ev.push(format!("explicit override by {}", override_authority.unwrap_or("operator")));
ev.push(format!(
"explicit override by {}",
override_authority.unwrap_or("operator")
));
// construction_mode.py:249-259: the override branch passes ONLY
// mode/evidence/target_project/confidence/bootstrap_required/override —
// project_authority and proposed_by_model keep their dataclass defaults.
@@ -330,13 +423,19 @@ pub fn classify_mode(
if !notc.is_empty() {
let obj_tokens = tokens(obj);
let standalone_evidence = !_contains_any(obj, STANDALONE_WORDS).is_empty();
let non_constructible_hit = obj_tokens.iter().any(|t| NON_CONSTRUCTIBLE.contains(&t.as_str()));
let non_constructible_hit = obj_tokens
.iter()
.any(|t| NON_CONSTRUCTIBLE.contains(&t.as_str()));
if !has_v || (non_constructible_hit && !standalone_evidence) {
ev.push(format!("non-construction intent markers: {}", py_repr_list(&notc[..notc.len().min(3)])));
ev.push(
if has_v { "construction-verb object is not a buildable artifact".to_string() }
else { "no construction verb present".to_string() }
);
ev.push(format!(
"non-construction intent markers: {}",
py_repr_list(&notc[..notc.len().min(3)])
));
ev.push(if has_v {
"construction-verb object is not a buildable artifact".to_string()
} else {
"no construction verb present".to_string()
});
// construction_mode.py:276: only mode/evidence/confidence are
// passed — target_project/project_authority/proposed_by_model
// stay at their dataclass defaults.
@@ -368,14 +467,27 @@ pub fn classify_mode(
let scoped = _contains_any(obj, SCOPING_WORDS);
let mut file_scoped = false;
for w in obj_lower.split(|c: char| {
!(c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '.' || c == '/' || c == '-')
!(c.is_ascii_lowercase()
|| c.is_ascii_digit()
|| c == '_'
|| c == '.'
|| c == '/'
|| c == '-')
}) {
if w.len() < 3 { continue; }
if w.len() < 3 {
continue;
}
if KNOWN_FILES.contains(&w) || w.contains('.') || w.contains('/') {
match classify_surface_lexical(w) {
Some("source_path") | Some("workflow_config") => { file_scoped = true; break; }
Some("source_path") | Some("workflow_config") => {
file_scoped = true;
break;
}
_ => {
if KNOWN_FILES.contains(&w) { file_scoped = true; break; }
if KNOWN_FILES.contains(&w) {
file_scoped = true;
break;
}
}
}
}
@@ -385,8 +497,12 @@ pub fn classify_mode(
let article_re = regex_lite_article(obj_lower.as_str());
let mut article_standalone = false;
if article_re {
let creation_verb = tokens(obj).iter().any(|t| CREATION_VERBS.contains(&t.as_str()));
let artifact_noun = tokens(obj).iter().any(|t| ARTIFACT_NOUNS.contains(&t.as_str()));
let creation_verb = tokens(obj)
.iter()
.any(|t| CREATION_VERBS.contains(&t.as_str()));
let artifact_noun = tokens(obj)
.iter()
.any(|t| ARTIFACT_NOUNS.contains(&t.as_str()));
if (creation_verb || artifact_noun)
&& local.is_empty()
&& scoped.is_empty()
@@ -401,52 +517,82 @@ pub fn classify_mode(
let (mut mode, mut conf): (Mode, f64);
if !standalone.is_empty() || article_standalone {
ev.push(format!("standalone/new-lifecycle language: {}", py_repr_list(&standalone[..standalone.len().min(3)])));
ev.push(format!(
"standalone/new-lifecycle language: {}",
py_repr_list(&standalone[..standalone.len().min(3)])
));
if project_exists == Some(true) {
ev.push("BUT target project already exists (structural evidence contradicts standalone language)".to_string());
mode = Mode::Unknown; conf = 0.3; clarification = true;
probes.push("should this extend the existing project or start a genuinely separate one?".to_string());
mode = Mode::Unknown;
conf = 0.3;
clarification = true;
probes.push(
"should this extend the existing project or start a genuinely separate one?"
.to_string(),
);
} else if project_exists.is_none() {
ev.push("no project-existence evidence for the target".to_string());
mode = Mode::Unknown; conf = 0.4; clarification = true;
mode = Mode::Unknown;
conf = 0.4;
clarification = true;
probes.push("does a project for this already exist?".to_string());
} else {
mode = Mode::ProjectBuild; conf = 0.9;
mode = Mode::ProjectBuild;
conf = 0.9;
}
} else if !local.is_empty()
&& (!scoped.is_empty() || file_scoped || target_project.is_some() || project_exists.is_some())
&& (!scoped.is_empty()
|| file_scoped
|| target_project.is_some()
|| project_exists.is_some())
{
ev.push(format!("smallness markers: {}", py_repr_list(&local[..local.len().min(3)])));
ev.push(format!(
"smallness markers: {}",
py_repr_list(&local[..local.len().min(3)])
));
if !scoped.is_empty() {
ev.push(format!("scoping words bind to known target: {}", py_repr_list(&scoped[..scoped.len().min(3)])));
ev.push(format!(
"scoping words bind to known target: {}",
py_repr_list(&scoped[..scoped.len().min(3)])
));
}
if file_scoped {
ev.push("file mention binds scoped target".to_string());
}
mode = Mode::LocalChange; conf = 0.8;
} else if (!scoped.is_empty() || target_project.is_some() || project_exists == Some(true)) && has_v {
mode = Mode::LocalChange;
conf = 0.8;
} else if (!scoped.is_empty() || target_project.is_some() || project_exists == Some(true))
&& has_v
{
ev.push("change scoped to an existing project/target".to_string());
if let Some(tp) = target_project {
ev.push(format!("target project: {}", tp));
}
if project_exists == Some(false) {
ev.push("BUT structural evidence proves the target project does not exist — refusing to manufacture existing-project semantics".to_string());
mode = Mode::Unknown; conf = 0.3; clarification = true;
mode = Mode::Unknown;
conf = 0.3;
clarification = true;
probes.push(format!(
"project '{}' has no structural record — is it new (project build) or does it live somewhere not bound?",
target_project.unwrap_or("the target")
));
} else {
mode = Mode::ExistingProjectChange; conf = 0.8;
mode = Mode::ExistingProjectChange;
conf = 0.8;
}
} else if has_v {
ev.push("construction verb present but no target-scope evidence and no standalone/new-lifecycle language".to_string());
mode = Mode::Unknown; conf = 0.35; clarification = true;
mode = Mode::Unknown;
conf = 0.35;
clarification = true;
probes.push("is this a change to an existing project or a new standalone one?".to_string());
probes.push("which project/module is the target?".to_string());
} else {
ev.push("insufficient lexical evidence either way".to_string());
mode = Mode::Unknown; conf = 0.2; clarification = true;
mode = Mode::Unknown;
conf = 0.2;
clarification = true;
probes.push("is this a construction request?".to_string());
}
@@ -456,9 +602,14 @@ pub fn classify_mode(
Mode::LocalChange | Mode::ExistingProjectChange | Mode::Unknown
) {
let surface_refs: Vec<&str> = surface.iter().map(|s| s.as_str()).collect();
ev.push(format!("affected surface known: {}", py_repr_list(&surface_refs[..surface_refs.len().min(2)])));
ev.push(format!(
"affected surface known: {}",
py_repr_list(&surface_refs[..surface_refs.len().min(2)])
));
if mode == Mode::Unknown {
mode = Mode::ExistingProjectChange; conf = 0.6; clarification = false;
mode = Mode::ExistingProjectChange;
conf = 0.6;
clarification = false;
}
}
}
@@ -535,13 +686,25 @@ fn regex_lite_article(text: &str) -> bool {
while let Some(pos) = text[search_from..].find(article) {
let abs = search_from + pos;
let before_ok = abs == 0
|| !text[..abs].chars().last().map(|c| c.is_ascii_alphanumeric()).unwrap_or(false);
|| !text[..abs]
.chars()
.last()
.map(|c| c.is_ascii_alphanumeric())
.unwrap_or(false);
let after_idx = abs + article.len();
if before_ok && text[after_idx..].chars().next().map(|c| c.is_ascii_lowercase()).unwrap_or(false) {
if before_ok
&& text[after_idx..]
.chars()
.next()
.map(|c| c.is_ascii_lowercase())
.unwrap_or(false)
{
return true;
}
search_from = abs + 1;
if search_from >= text.len() { break; }
if search_from >= text.len() {
break;
}
}
}
false
@@ -558,7 +721,9 @@ fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
@@ -584,10 +749,14 @@ pub extern "C" fn cm_classify(arg: *const c_char) -> *mut c_char {
Some(Value::Bool(b)) => Some(*b),
_ => None,
};
let affected_surface = a
.get("affected_surface")
.and_then(|v| v.as_array())
.map(|arr: &Vec<Value>| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect::<Vec<String>>());
let affected_surface =
a.get("affected_surface")
.and_then(|v| v.as_array())
.map(|arr: &Vec<Value>| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect::<Vec<String>>()
});
let explicit_override = a.get("explicit_override").and_then(|v| v.as_str());
let override_authority = a.get("override_authority").and_then(|v| v.as_str());
let model_proposal = a.get("model_proposal");
@@ -14,7 +14,7 @@ mod battery {
combine_pattern_obligations, construct_pattern, derive_pattern_obligations,
evaluate_applicability, record_pattern_proposal, revise_pattern,
};
use serde_json::{Value, json};
use serde_json::{json, Value};
fn truth() -> Value {
serde_json::from_str(include_str!("frozen_cp_truth.json")).expect("frozen truth parses")
@@ -41,9 +41,18 @@ mod battery {
fn run_revise(c: &Value) -> Result<Value, String> {
let i = &c["input"];
let evidence: Vec<Value> = i["evidence"].as_array().cloned().unwrap_or_default();
let changes = if i["changes"].is_null() { None } else { Some(obj(&i["changes"])) };
revise_pattern(&obj(&i["pattern"]), i["reason"].as_str().unwrap_or(""), &evidence, changes.as_ref())
.map(Value::Object)
let changes = if i["changes"].is_null() {
None
} else {
Some(obj(&i["changes"]))
};
revise_pattern(
&obj(&i["pattern"]),
i["reason"].as_str().unwrap_or(""),
&evidence,
changes.as_ref(),
)
.map(Value::Object)
}
fn run_propose(c: &Value) -> Result<Value, String> {
@@ -61,8 +70,10 @@ mod battery {
}
return Ok(Value::Array(out));
}
let evidence: Vec<&str> =
i["evidence"].as_array().map(|a| a.iter().filter_map(|e| e.as_str()).collect()).unwrap_or_default();
let evidence: Vec<&str> = i["evidence"]
.as_array()
.map(|a| a.iter().filter_map(|e| e.as_str()).collect())
.unwrap_or_default();
record_pattern_proposal(
i["pattern_id"].as_str().unwrap_or(""),
i["provider"].as_str().unwrap_or(""),
@@ -74,18 +85,38 @@ mod battery {
fn run_evaluate(c: &Value) -> Value {
let i = &c["input"];
evaluate_applicability(&obj(&i["pattern"]), &obj(&i["need"]), &obj(&i["surface_result"]), &obj(&i["authorities"]))
evaluate_applicability(
&obj(&i["pattern"]),
&obj(&i["need"]),
&obj(&i["surface_result"]),
&obj(&i["authorities"]),
)
}
fn run_derive(c: &Value) -> Value {
let i = &c["input"];
let exc: Vec<Value> =
if i["project_exceptions"].is_null() { vec![] } else { i["project_exceptions"].as_array().cloned().unwrap_or_default() };
derive_pattern_obligations(&obj(&i["pattern"]), &obj(&i["need"]), &obj(&i["surface_result"]), &obj(&i["authorities"]), &exc)
let exc: Vec<Value> = if i["project_exceptions"].is_null() {
vec![]
} else {
i["project_exceptions"]
.as_array()
.cloned()
.unwrap_or_default()
};
derive_pattern_obligations(
&obj(&i["pattern"]),
&obj(&i["need"]),
&obj(&i["surface_result"]),
&obj(&i["authorities"]),
&exc,
)
}
fn run_combine(c: &Value) -> Value {
let derivs: Vec<Value> = c["input"]["derivations"].as_array().cloned().unwrap_or_default();
let derivs: Vec<Value> = c["input"]["derivations"]
.as_array()
.cloned()
.unwrap_or_default();
combine_pattern_obligations(&derivs)
}
@@ -113,16 +144,28 @@ mod battery {
fn run_surface_text(c: &Value) -> Result<Value, String> {
let i = &c["input"];
let need = i["need"].as_object().expect("need object").clone();
let surface = i["surface_result"].as_object().expect("surface object").clone();
let surface = i["surface_result"]
.as_object()
.expect("surface object")
.clone();
Ok(_surface_text(&need, &surface))
}
fn run_when_hit(c: &Value) -> Result<Value, String> {
let i = &c["input"];
let obligation = i["obligation"].as_object().expect("obligation object").clone();
let obligation = i["obligation"]
.as_object()
.expect("obligation object")
.clone();
let need = i["need"].as_object().expect("need object").clone();
let surface = i["surface_result"].as_object().expect("surface object").clone();
let authorities = i["authorities"].as_object().expect("authorities object").clone();
let surface = i["surface_result"]
.as_object()
.expect("surface object")
.clone();
let authorities = i["authorities"]
.as_object()
.expect("authorities object")
.clone();
let (verdict, reason) = _obligation_when_hit(&obligation, &need, &surface, &authorities);
Ok(json!([verdict.as_str(), reason]))
}
@@ -145,7 +188,11 @@ mod battery {
let cs = cases("construct");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_construct(c), &c["expected"]);
check(
c["name"].as_str().unwrap(),
run_construct(c),
&c["expected"],
);
}
}
@@ -202,7 +249,11 @@ mod battery {
let cs = cases("path_match");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_path_match(c), &c["expected"]);
check(
c["name"].as_str().unwrap(),
run_path_match(c),
&c["expected"],
);
}
}
@@ -211,7 +262,11 @@ mod battery {
let cs = cases("entity_match");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_entity_match(c), &c["expected"]);
check(
c["name"].as_str().unwrap(),
run_entity_match(c),
&c["expected"],
);
}
}
@@ -220,7 +275,11 @@ mod battery {
let cs = cases("need_dict");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_need_dict(c), &c["expected"]);
check(
c["name"].as_str().unwrap(),
run_need_dict(c),
&c["expected"],
);
}
}
@@ -229,7 +288,11 @@ mod battery {
let cs = cases("surface_text");
assert!(!cs.is_empty());
for c in &cs {
check(c["name"].as_str().unwrap(), run_surface_text(c), &c["expected"]);
check(
c["name"].as_str().unwrap(),
run_surface_text(c),
&c["expected"],
);
}
}
@@ -246,11 +309,26 @@ mod battery {
#[test]
fn path_token_discipline() {
assert!(!_path_matches("ui", "src/rune/rune_tui.py"), "'ui' is not a token in rune_tui.py");
assert!(!_path_matches("ui", "src/rune/builtin.py"), "the 'ui'-in-'builtin' FP class");
assert!(_path_matches("spine", "src/rune/spine.py"), "long signal: containment");
assert!(_path_matches("tui", "src/rune/rune_tui.py"), "'tui' IS a token");
assert!(!_path_matches("cfg", "config/settings.py"), "'cfg' not a token of config");
assert!(
!_path_matches("ui", "src/rune/rune_tui.py"),
"'ui' is not a token in rune_tui.py"
);
assert!(
!_path_matches("ui", "src/rune/builtin.py"),
"the 'ui'-in-'builtin' FP class"
);
assert!(
_path_matches("spine", "src/rune/spine.py"),
"long signal: containment"
);
assert!(
_path_matches("tui", "src/rune/rune_tui.py"),
"'tui' IS a token"
);
assert!(
!_path_matches("cfg", "config/settings.py"),
"'cfg' not a token of config"
);
}
#[test]
@@ -259,13 +337,19 @@ mod battery {
assert!(_entity_matches("module:", "module:spine"), "prefix match");
assert!(!_entity_matches("ui:", "region:left"), "prefix mismatch");
assert!(_entity_matches("spine", "module:spine"), "long containment");
assert!(!_entity_matches("ui", "ui:chat"), "oracle truth: token discipline");
assert!(
!_entity_matches("ui", "ui:chat"),
"oracle truth: token discipline"
);
}
#[test]
fn long_signal_containment() {
assert!(_path_matches("structural", "src/rune/structural_store.py"));
assert!(_entity_matches("construction", "module:construction_pattern"));
assert!(_entity_matches(
"construction",
"module:construction_pattern"
));
assert!(!_path_matches("structural", "src/rune/other.py"));
}
}
+337 -83
View File
@@ -166,7 +166,10 @@ fn py_repr(v: &Value) -> String {
format!("[{}]", inner.join(", "))
}
Value::Object(map) => {
let inner: Vec<String> = map.iter().map(|(k, v)| format!("{}: {}", py_quote(k), py_repr(v))).collect();
let inner: Vec<String> = map
.iter()
.map(|(k, v)| format!("{}: {}", py_quote(k), py_repr(v)))
.collect();
format!("{{{}}}", inner.join(", "))
}
}
@@ -224,7 +227,9 @@ pub fn _path_matches(signal: &str, path: &str) -> bool {
if s.chars().count() >= 4 {
return path.to_lowercase().contains(&s);
}
split_tokens(&path.to_lowercase(), false).iter().any(|t| *t == s)
split_tokens(&path.to_lowercase(), false)
.iter()
.any(|t| *t == s)
}
/// Same discipline for entity ids. Prefix signals ending in ':' (e.g.
@@ -239,7 +244,9 @@ pub fn _entity_matches(signal: &str, entity_id: &str) -> bool {
if s.chars().count() >= 4 {
return entity_id.to_lowercase().contains(&s);
}
split_tokens(&entity_id.to_lowercase(), true).iter().any(|t| *t == s)
split_tokens(&entity_id.to_lowercase(), true)
.iter()
.any(|t| *t == s)
}
/// The FFI need envelope (Python `_need_dict`): a need is exactly its three
@@ -247,14 +254,25 @@ pub fn _entity_matches(signal: &str, entity_id: &str) -> bool {
/// else a caller attached is dropped at the kernel boundary.
pub fn _need_dict(need: &Map<String, Value>) -> Map<String, Value> {
let mut out = Map::with_capacity(3);
out.insert("objective".into(), need.get("objective").cloned().unwrap_or(Value::Null));
out.insert(
"objective".into(),
need.get("objective").cloned().unwrap_or(Value::Null),
);
out.insert(
"affected_surface".into(),
need.get("affected_surface").and_then(|v| v.as_array()).cloned().map(Value::Array).unwrap_or(Value::Null),
need.get("affected_surface")
.and_then(|v| v.as_array())
.cloned()
.map(Value::Array)
.unwrap_or(Value::Null),
);
out.insert(
"constraints".into(),
need.get("constraints").and_then(|v| v.as_object()).cloned().map(Value::Object).unwrap_or(Value::Null),
need.get("constraints")
.and_then(|v| v.as_object())
.cloned()
.map(Value::Object)
.unwrap_or(Value::Null),
);
out
}
@@ -276,7 +294,11 @@ pub fn _surface_text(need: &Map<String, Value>, surface: &Map<String, Value>) ->
] {
tokens.extend(surface.get(k).map(str_list).unwrap_or_default());
}
tokens.extend(need.get("affected_surface").map(str_list).unwrap_or_default());
tokens.extend(
need.get("affected_surface")
.map(str_list)
.unwrap_or_default(),
);
let lowered = need
.get("objective")
.and_then(|v| v.as_str())
@@ -284,7 +306,12 @@ pub fn _surface_text(need: &Map<String, Value>, surface: &Map<String, Value>) ->
.replace(['/', '-'], " ")
.to_lowercase();
tokens.extend(lowered.split_whitespace());
Value::Array(tokens.into_iter().map(|t| Value::String(t.to_string())).collect())
Value::Array(
tokens
.into_iter()
.map(|t| Value::String(t.to_string()))
.collect(),
)
}
// ── small JSON accessors (borrowed; owned only where the output needs it) ──
@@ -307,7 +334,10 @@ fn relationships<'a>(doc: &'a Value, from_id: &str) -> Vec<&'a Map<String, Value
.map(|rels| {
rels.iter()
.filter_map(|r| r.as_object())
.filter(|r| from_id.is_empty() || r.get("from").and_then(|f| f.as_str()).unwrap_or("") == from_id)
.filter(|r| {
from_id.is_empty()
|| r.get("from").and_then(|f| f.as_str()).unwrap_or("") == from_id
})
.collect()
})
.unwrap_or_default()
@@ -320,7 +350,10 @@ fn relationships<'a>(doc: &'a Value, from_id: &str) -> Vec<&'a Map<String, Value
/// passthrough). The input and output share one shape — `to_dict` output
/// round-trips through here unchanged.
pub fn construct_pattern(input: &Map<String, Value>) -> Result<Map<String, Value>, String> {
let pattern_id = input.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("");
let pattern_id = input
.get("pattern_id")
.and_then(|v| v.as_str())
.unwrap_or("");
if pattern_id.is_empty() {
return Err("pattern_id required".to_string());
}
@@ -332,7 +365,11 @@ pub fn construct_pattern(input: &Map<String, Value>) -> Result<Map<String, Value
for o in obs {
for key in ["id", "obligation", "when"] {
if o.get(key).is_none() {
return Err(format!("obligation missing {}: {}", py_quote(key), py_repr(o)));
return Err(format!(
"obligation missing {}: {}",
py_quote(key),
py_repr(o)
));
}
}
}
@@ -350,13 +387,24 @@ pub fn construct_pattern(input: &Map<String, Value>) -> Result<Map<String, Value
);
out.insert(
"signals".into(),
input.get("signals").cloned().unwrap_or_else(|| Value::Object(Map::new())),
input
.get("signals")
.cloned()
.unwrap_or_else(|| Value::Object(Map::new())),
);
out.insert(
"obligations".into(),
input.get("obligations").cloned().unwrap_or_else(|| Value::Array(vec![])),
input
.get("obligations")
.cloned()
.unwrap_or_else(|| Value::Array(vec![])),
);
for key in ["provenance", "exclusions", "toolchain_notes", "runtime_effects"] {
for key in [
"provenance",
"exclusions",
"toolchain_notes",
"runtime_effects",
] {
let mut vals = input.get(key).map(str_list_owned).unwrap_or_default();
vals.sort_unstable();
out.insert(key.into(), json!(vals));
@@ -377,8 +425,15 @@ pub fn revise_pattern(
evidence: &[Value],
changes: Option<&Map<String, Value>>,
) -> Result<Map<String, Value>, String> {
let pattern_id = pattern.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let revision = pattern.get("revision").and_then(|v| v.as_i64()).unwrap_or(1);
let pattern_id = pattern
.get("pattern_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let revision = pattern
.get("revision")
.and_then(|v| v.as_i64())
.unwrap_or(1);
let mut merged = pattern.clone();
merged.remove("pattern_id");
if let Some(ch) = changes {
@@ -387,9 +442,14 @@ pub fn revise_pattern(
}
}
merged.insert("revision".into(), json!(revision + 1));
let mut ev: Vec<String> = evidence.iter().filter_map(|e| e.as_str().map(String::from)).collect();
let mut ev: Vec<String> = evidence
.iter()
.filter_map(|e| e.as_str().map(String::from))
.collect();
ev.sort_unstable();
let mut changed: Vec<String> = changes.map(|ch| ch.keys().cloned().collect()).unwrap_or_default();
let mut changed: Vec<String> = changes
.map(|ch| ch.keys().cloned().collect())
.unwrap_or_default();
changed.sort_unstable();
let entry = json!({
"prior_revision": revision,
@@ -446,13 +506,21 @@ fn surface_kind_present(kind: &str, surface: &Map<String, Value>) -> bool {
"layout_region" => "affected_regions",
_ => "",
};
if surface.get(mapped).and_then(|v| v.as_array()).map(|a| !a.is_empty()).unwrap_or(false) {
if surface
.get(mapped)
.and_then(|v| v.as_array())
.map(|a| !a.is_empty())
.unwrap_or(false)
{
return true;
}
surface
.get("resolutions")
.and_then(|v| v.as_array())
.map(|rs| rs.iter().any(|r| r.get("kind").and_then(|k| k.as_str()).unwrap_or("") == kind))
.map(|rs| {
rs.iter()
.any(|r| r.get("kind").and_then(|k| k.as_str()).unwrap_or("") == kind)
})
.unwrap_or(false)
}
@@ -497,7 +565,10 @@ pub fn evaluate_applicability(
surface: &Map<String, Value>,
authorities: &Map<String, Value>,
) -> Value {
let pattern_id = pattern.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("");
let pattern_id = pattern
.get("pattern_id")
.and_then(|v| v.as_str())
.unwrap_or("");
let objective = need.get("objective").and_then(|v| v.as_str()).unwrap_or("");
let exclusions: Vec<&str> = pattern.get("exclusions").map(str_list).unwrap_or_default();
let need_exclusions: Vec<&str> = need
@@ -508,7 +579,11 @@ pub fn evaluate_applicability(
// §3/§18: PROJECT evidence overrides the pattern.
let objective_low = objective.to_lowercase();
for ex in exclusions.iter().copied().chain(need_exclusions.iter().copied()) {
for ex in exclusions
.iter()
.copied()
.chain(need_exclusions.iter().copied())
{
if objective_low.contains(&ex.to_lowercase()) || need_exclusions.contains(&ex) {
return json!({
"pattern": pattern_id,
@@ -540,24 +615,61 @@ pub fn evaluate_applicability(
.map(str_list)
.unwrap_or_default()
.into_iter()
.chain(surface.get("workflow_config_surfaces").map(str_list).unwrap_or_default())
.chain(
surface
.get("workflow_config_surfaces")
.map(str_list)
.unwrap_or_default(),
)
.collect();
let entities: Vec<&str> = surface.get("affected_existing_entities").map(str_list).unwrap_or_default();
let entities: Vec<&str> = surface
.get("affected_existing_entities")
.map(str_list)
.unwrap_or_default();
let mut tally = SignalTally { groups_declared, groups_hit, evidence };
let mut tally = SignalTally {
groups_declared,
groups_hit,
evidence,
};
let surface_kinds = signals.get("surface_kinds").map(str_list).unwrap_or_default();
_check(&mut tally, "surface_kinds", &surface_kinds, &|c| surface_kind_present(c, surface));
let path_signals = signals.get("path_contains").map(str_list).unwrap_or_default();
_check(&mut tally, "path_contains", &path_signals, &|c| paths.iter().any(|p| _path_matches(c, p)));
let entity_signals = signals.get("entities_match").map(str_list).unwrap_or_default();
_check(&mut tally, "entities_match", &entity_signals, &|c| entities.iter().any(|e| _entity_matches(c, e)));
let objective_words: Vec<String> =
objective.replace('/', " ").split_whitespace().map(|w| w.to_lowercase()).collect();
let objective_signals_owned: Vec<String> =
signals.get("objective_any").map(str_list).unwrap_or_default().into_iter().map(str::to_lowercase).collect();
let surface_kinds = signals
.get("surface_kinds")
.map(str_list)
.unwrap_or_default();
_check(&mut tally, "surface_kinds", &surface_kinds, &|c| {
surface_kind_present(c, surface)
});
let path_signals = signals
.get("path_contains")
.map(str_list)
.unwrap_or_default();
_check(&mut tally, "path_contains", &path_signals, &|c| {
paths.iter().any(|p| _path_matches(c, p))
});
let entity_signals = signals
.get("entities_match")
.map(str_list)
.unwrap_or_default();
_check(&mut tally, "entities_match", &entity_signals, &|c| {
entities.iter().any(|e| _entity_matches(c, e))
});
let objective_words: Vec<String> = objective
.replace('/', " ")
.split_whitespace()
.map(|w| w.to_lowercase())
.collect();
let objective_signals_owned: Vec<String> = signals
.get("objective_any")
.map(str_list)
.unwrap_or_default()
.into_iter()
.map(str::to_lowercase)
.collect();
let objective_signals: Vec<&str> = objective_signals_owned.iter().map(String::as_str).collect();
_check(&mut tally, "objective_any", &objective_signals, &|c| objective_words.iter().any(|w| w == c));
_check(&mut tally, "objective_any", &objective_signals, &|c| {
objective_words.iter().any(|w| w == c)
});
if let Some(contracts) = signals.get("contracts_match").and_then(|v| v.as_array()) {
if !contracts.is_empty() {
@@ -567,18 +679,27 @@ pub fn evaluate_applicability(
.values()
.flat_map(|doc| entities.iter().map(move |&e| (doc, e)))
.flat_map(|(doc, e)| relationships(doc, e).into_iter().map(move |r| (e, r)))
.filter(|(_, r)| REL_TYPES.contains(&r.get("type").and_then(|t| t.as_str()).unwrap_or("")))
.filter(|(_, r)| {
REL_TYPES.contains(&r.get("type").and_then(|t| t.as_str()).unwrap_or(""))
})
.map(|(e, r)| (e, r.get("to").and_then(|t| t.as_str()).unwrap_or("")))
.filter(|(_, to)| wanted.contains(to))
.map(|(e, to)| format!("{e}->{to}"))
.collect();
if !hits.is_empty() {
tally.record_hits("contracts_match", hits.iter().take(5).map(|s| s.as_str()).collect());
tally.record_hits(
"contracts_match",
hits.iter().take(5).map(|s| s.as_str()).collect(),
);
}
}
}
let SignalTally { groups_declared, groups_hit, evidence } = tally;
let SignalTally {
groups_declared,
groups_hit,
evidence,
} = tally;
if groups_declared == 0 {
return json!({
@@ -597,7 +718,11 @@ pub fn evaluate_applicability(
};
let mut hit_sorted = groups_hit.clone();
hit_sorted.sort_unstable();
let detail = if hit_sorted.is_empty() { "none".to_string() } else { py_str_list(&hit_sorted) };
let detail = if hit_sorted.is_empty() {
"none".to_string()
} else {
py_str_list(&hit_sorted)
};
json!({
"pattern": pattern_id,
"verdict": verdict.as_str(),
@@ -620,15 +745,30 @@ pub fn _obligation_when_hit(
.map(str_list)
.unwrap_or_default()
.into_iter()
.chain(surface.get("workflow_config_surfaces").map(str_list).unwrap_or_default())
.chain(
surface
.get("workflow_config_surfaces")
.map(str_list)
.unwrap_or_default(),
)
.collect();
let entities: Vec<&str> = surface
.get("affected_existing_entities")
.map(str_list)
.unwrap_or_default()
.into_iter()
.chain(surface.get("affected_ui_surfaces").map(str_list).unwrap_or_default())
.chain(surface.get("affected_regions").map(str_list).unwrap_or_default())
.chain(
surface
.get("affected_ui_surfaces")
.map(str_list)
.unwrap_or_default(),
)
.chain(
surface
.get("affected_regions")
.map(str_list)
.unwrap_or_default(),
)
.collect();
match when_shape(obligation) {
@@ -646,9 +786,18 @@ pub fn _obligation_when_hit(
.flat_map(|sub| paths.iter().filter(|p| _path_matches(sub, p)).copied())
.collect();
if !hits.is_empty() {
return (ObligationVerdict::Applicable, format!("affected path(s): {}", py_str_list(&hits[..3.min(hits.len())])));
return (
ObligationVerdict::Applicable,
format!(
"affected path(s): {}",
py_str_list(&hits[..3.min(hits.len())])
),
);
}
(ObligationVerdict::NotApplicable, format!("no affected path matches {}", py_repr(subs)))
(
ObligationVerdict::NotApplicable,
format!("no affected path matches {}", py_repr(subs)),
)
}
WhenShape::EntitiesMatch(subs) => {
let hits: Vec<&str> = str_list(subs)
@@ -658,10 +807,16 @@ pub fn _obligation_when_hit(
if !hits.is_empty() {
return (
ObligationVerdict::Applicable,
format!("affected entity/entities: {}", py_str_list(&hits[..3.min(hits.len())])),
format!(
"affected entity/entities: {}",
py_str_list(&hits[..3.min(hits.len())])
),
);
}
(ObligationVerdict::NotApplicable, format!("no affected entity matches {}", py_repr(subs)))
(
ObligationVerdict::NotApplicable,
format!("no affected entity matches {}", py_repr(subs)),
)
}
WhenShape::ContractsTouched(wanted) => {
let mut touched: Vec<String> = need
@@ -680,17 +835,28 @@ pub fn _obligation_when_hit(
}
}
}
let hits: Vec<&str> =
str_list(wanted).into_iter().filter(|c| touched.iter().any(|t| t == c)).collect();
let hits: Vec<&str> = str_list(wanted)
.into_iter()
.filter(|c| touched.iter().any(|t| t == c))
.collect();
if !hits.is_empty() {
return (ObligationVerdict::Applicable, format!("contract(s) touched: {}", py_str_list(&hits)));
return (
ObligationVerdict::Applicable,
format!("contract(s) touched: {}", py_str_list(&hits)),
);
}
(ObligationVerdict::NotApplicable, format!("touched contracts do not include {}", py_repr(wanted)))
(
ObligationVerdict::NotApplicable,
format!("touched contracts do not include {}", py_repr(wanted)),
)
}
WhenShape::Unrecognized(keys) => {
let mut keys = keys;
keys.sort_unstable();
(ObligationVerdict::Unknown, format!("unrecognized when-condition shape: {}", py_str_list(&keys)))
(
ObligationVerdict::Unknown,
format!("unrecognized when-condition shape: {}", py_str_list(&keys)),
)
}
}
}
@@ -703,9 +869,16 @@ pub fn derive_pattern_obligations(
authorities: &Map<String, Value>,
project_exceptions: &[Value],
) -> Value {
let pattern_id = pattern.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let pattern_id = pattern
.get("pattern_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let revision = pattern.get("revision").cloned().unwrap_or(json!(1));
let provenance = pattern.get("provenance").cloned().unwrap_or_else(|| Value::Array(vec![]));
let provenance = pattern
.get("provenance")
.cloned()
.unwrap_or_else(|| Value::Array(vec![]));
let mut accepted = Vec::new();
let mut rejected = Vec::new();
let mut unresolved = Vec::new();
@@ -715,7 +888,10 @@ pub fn derive_pattern_obligations(
let o_obj: &Map<String, Value> = o.as_object().unwrap_or(&empty);
let mut entry = Map::new();
entry.insert("id".into(), o.get("id").cloned().unwrap_or(Value::Null));
entry.insert("obligation".into(), o.get("obligation").cloned().unwrap_or(Value::Null));
entry.insert(
"obligation".into(),
o.get("obligation").cloned().unwrap_or(Value::Null),
);
entry.insert("pattern".into(), Value::String(pattern_id.clone()));
entry.insert("revision".into(), revision.clone());
entry.insert("provenance".into(), Value::String("PATTERN".to_string()));
@@ -728,26 +904,37 @@ pub fn derive_pattern_obligations(
entry.insert("verdict".into(), Value::String("EXCEPTED".to_string()));
entry.insert(
"reason".into(),
exc.get("reason").cloned().unwrap_or_else(|| Value::String(String::new())),
exc.get("reason")
.cloned()
.unwrap_or_else(|| Value::String(String::new())),
);
// Oracle field names on the exception are `evidence` /
// `authority`; they land on the entry as exception_*.
entry.insert(
"exception_evidence".into(),
exc.get("evidence").cloned().unwrap_or_else(|| Value::String(String::new())),
exc.get("evidence")
.cloned()
.unwrap_or_else(|| Value::String(String::new())),
);
entry.insert(
"exception_authority".into(),
exc.get("authority").cloned().unwrap_or_else(|| Value::String("user".to_string())),
exc.get("authority")
.cloned()
.unwrap_or_else(|| Value::String("user".to_string())),
);
rejected.push(Value::Object(entry));
continue;
}
let (verdict, reason) = _obligation_when_hit(o_obj, need, surface, authorities);
entry.insert("verdict".into(), Value::String(verdict.as_str().to_string()));
entry.insert(
"verdict".into(),
Value::String(verdict.as_str().to_string()),
);
entry.insert("reason".into(), Value::String(reason));
match verdict {
ObligationVerdict::Required | ObligationVerdict::Applicable => accepted.push(Value::Object(entry)),
ObligationVerdict::Required | ObligationVerdict::Applicable => {
accepted.push(Value::Object(entry))
}
ObligationVerdict::NotApplicable => rejected.push(Value::Object(entry)),
ObligationVerdict::Unknown => unresolved.push(Value::Object(entry)),
}
@@ -772,10 +959,20 @@ pub fn combine_pattern_obligations(derivations: &[Value]) -> Value {
for d in derivations {
if let Some(acc) = d.get("accepted").and_then(|v| v.as_array()) {
for entry in acc {
let oid = entry.get("id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let oid = entry
.get("id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if let Some(prev) = merged.get_mut(&oid) {
let prev_text = prev.get("obligation").and_then(|v| v.as_str()).unwrap_or("");
let new_text = entry.get("obligation").and_then(|v| v.as_str()).unwrap_or("");
let prev_text = prev
.get("obligation")
.and_then(|v| v.as_str())
.unwrap_or("");
let new_text = entry
.get("obligation")
.and_then(|v| v.as_str())
.unwrap_or("");
if prev_text != new_text {
conflicts.push(json!({
"obligation_id": oid,
@@ -807,7 +1004,10 @@ pub fn combine_pattern_obligations(derivations: &[Value]) -> Value {
}
}
order.sort_unstable();
let obligations: Vec<Value> = order.into_iter().filter_map(|k| merged.remove(&k)).collect();
let obligations: Vec<Value> = order
.into_iter()
.filter_map(|k| merged.remove(&k))
.collect();
json!({"obligations": obligations, "conflicts": conflicts})
}
@@ -820,7 +1020,9 @@ fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
@@ -835,13 +1037,17 @@ fn envelope(r: Result<Value, String>) -> *mut c_char {
}
fn obj_arg(a: &Value) -> Result<Map<String, Value>, String> {
a.as_object().cloned().ok_or_else(|| "argument must be a JSON object".to_string())
a.as_object()
.cloned()
.ok_or_else(|| "argument must be a JSON object".to_string())
}
/// cp_construct({pattern dict}) → normalized pattern dict.
#[no_mangle]
pub extern "C" fn cp_construct(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).and_then(|a| construct_pattern(&a).map(Value::Object));
let r = read_arg(arg)
.and_then(|a| obj_arg(&a))
.and_then(|a| construct_pattern(&a).map(Value::Object));
envelope(r)
}
@@ -849,10 +1055,17 @@ pub extern "C" fn cp_construct(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cp_revise(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).and_then(|a| {
let pattern = a.get("pattern").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let pattern = a
.get("pattern")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let reason = a.get("reason").and_then(|v| v.as_str()).unwrap_or("");
let evidence: Vec<Value> =
a.get("evidence").and_then(|v| v.as_array()).cloned().unwrap_or_default();
let evidence: Vec<Value> = a
.get("evidence")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let changes = a.get("changes").and_then(|v| v.as_object()).cloned();
revise_pattern(&pattern, reason, &evidence, changes.as_ref()).map(Value::Object)
});
@@ -864,7 +1077,10 @@ pub extern "C" fn cp_revise(arg: *const c_char) -> *mut c_char {
pub extern "C" fn cp_propose(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).and_then(|a| {
let evidence = a.get("evidence").map(str_list).unwrap_or_default();
let confidence = a.get("confidence").and_then(|v| v.as_f64()).unwrap_or(f64::NAN);
let confidence = a
.get("confidence")
.and_then(|v| v.as_f64())
.unwrap_or(f64::NAN);
record_pattern_proposal(
a.get("pattern_id").and_then(|v| v.as_str()).unwrap_or(""),
a.get("provider").and_then(|v| v.as_str()).unwrap_or(""),
@@ -880,11 +1096,27 @@ pub extern "C" fn cp_propose(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cp_evaluate(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).map(|a| {
let pattern = a.get("pattern").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let raw_need = a.get("need").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let pattern = a
.get("pattern")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let raw_need = a
.get("need")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let need = _need_dict(&raw_need);
let surface = a.get("surface_result").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let authorities = a.get("authorities").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let surface = a
.get("surface_result")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let authorities = a
.get("authorities")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
evaluate_applicability(&pattern, &need, &surface, &authorities)
});
envelope(r)
@@ -895,13 +1127,32 @@ pub extern "C" fn cp_evaluate(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cp_derive(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).map(|a| {
let pattern = a.get("pattern").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let raw_need = a.get("need").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let pattern = a
.get("pattern")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let raw_need = a
.get("need")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let need = _need_dict(&raw_need);
let surface = a.get("surface_result").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let authorities = a.get("authorities").and_then(|v| v.as_object()).cloned().unwrap_or_default();
let exceptions: Vec<Value> =
a.get("project_exceptions").and_then(|v| v.as_array()).cloned().unwrap_or_default();
let surface = a
.get("surface_result")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let authorities = a
.get("authorities")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let exceptions: Vec<Value> = a
.get("project_exceptions")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
derive_pattern_obligations(&pattern, &need, &surface, &authorities, &exceptions)
});
envelope(r)
@@ -911,8 +1162,11 @@ pub extern "C" fn cp_derive(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn cp_combine(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| obj_arg(&a)).map(|a| {
let derivs: Vec<Value> =
a.get("derivations").and_then(|v| v.as_array()).cloned().unwrap_or_default();
let derivs: Vec<Value> = a
.get("derivations")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
combine_pattern_obligations(&derivs)
});
envelope(r)
+7 -4
View File
@@ -281,9 +281,7 @@ pub fn _resolve_and_project(
InvokeOutcome::ImportCrash(e) => (Phase::Crash, e, false, None),
InvokeOutcome::NotFound => (Phase::NotFound, String::new(), false, None),
InvokeOutcome::CallCrash { error, callable }
| InvokeOutcome::ProjectCrash { error, callable } => {
(Phase::Crash, error, callable, None)
}
| InvokeOutcome::ProjectCrash { error, callable } => (Phase::Crash, error, callable, None),
InvokeOutcome::Found {
callable,
projection,
@@ -932,7 +930,12 @@ mod tests {
/// unrecognized phase to Found — the oracle's catch-all arm.
#[test]
fn test_phase_roundtrip_and_catchall() {
for p in [Phase::ImportError, Phase::Crash, Phase::NotFound, Phase::Found] {
for p in [
Phase::ImportError,
Phase::Crash,
Phase::NotFound,
Phase::Found,
] {
assert_eq!(Phase::parse(p.as_str()), p);
assert_eq!(p.to_string(), p.as_str());
}
+42 -23
View File
@@ -26,10 +26,7 @@ fn cases(group: &str) -> Vec<Value> {
}
fn entries_of(i: &Value) -> Map<String, Value> {
i["entries"]
.as_object()
.cloned()
.unwrap_or_default()
i["entries"].as_object().cloned().unwrap_or_default()
}
fn replay(group: &str, f: impl Fn(&Value) -> Value) {
@@ -47,16 +44,18 @@ fn replay(group: &str, f: impl Fn(&Value) -> Value) {
#[test]
fn compute_priority_parity_battery() {
replay("compute_priority", |i| {
json!({"priority": compute_priority(&i["entry"])})
});
replay(
"compute_priority",
|i| json!({"priority": compute_priority(&i["entry"])}),
);
}
#[test]
fn qualifies_for_cache_parity_battery() {
replay("qualifies_for_cache", |i| {
json!({"qualifies": qualifies_for_cache(&i["entry"])})
});
replay(
"qualifies_for_cache",
|i| json!({"qualifies": qualifies_for_cache(&i["entry"])}),
);
}
#[test]
@@ -64,8 +63,12 @@ fn add_parity_battery() {
replay("add", |i| {
let entries = entries_of(i);
let now = i["now"].as_str().unwrap();
let (entries, admitted, evicted) =
add(&entries, i["entry_id"].as_i64().unwrap_or(-1), &i["entry"], now);
let (entries, admitted, evicted) = add(
&entries,
i["entry_id"].as_i64().unwrap_or(-1),
&i["entry"],
now,
);
json!({"entries": entries, "admitted": admitted, "evicted": evicted})
});
}
@@ -74,24 +77,29 @@ fn add_parity_battery() {
fn access_parity_battery() {
replay("access", |i| {
let entries = entries_of(i);
let (entries, changed) =
access(&entries, i["entry_id"].as_i64().unwrap_or(-1), i["now"].as_str().unwrap());
let (entries, changed) = access(
&entries,
i["entry_id"].as_i64().unwrap_or(-1),
i["now"].as_str().unwrap(),
);
json!({"entries": entries, "changed": changed})
});
}
#[test]
fn contains_parity_battery() {
replay("contains", |i| {
json!({"contains": contains(&entries_of(i), i["entry_id"].as_i64().unwrap_or(-1))})
});
replay(
"contains",
|i| json!({"contains": contains(&entries_of(i), i["entry_id"].as_i64().unwrap_or(-1))}),
);
}
#[test]
fn get_priority_parity_battery() {
replay("get_priority", |i| {
json!({"priority": get_priority(&entries_of(i), i["entry_id"].as_i64().unwrap_or(-1))})
});
replay(
"get_priority",
|i| json!({"priority": get_priority(&entries_of(i), i["entry_id"].as_i64().unwrap_or(-1))}),
);
}
#[test]
@@ -118,16 +126,27 @@ fn main_cli_mirror_battery() {
let i = &case["input"];
let argv: Vec<String> = i["argv"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default();
let mut entries = entries_of(i);
let sync_rows = i["sync_rows"].as_object().cloned();
let add_row = i["add_row"].as_array().map(|a| Value::Array(a.clone()));
let now = i["now"].as_str().unwrap_or("");
let (stdout, stderr, code) = crate::main(&argv, &mut entries, sync_rows.as_ref(), add_row.as_ref(), now);
let (stdout, stderr, code) = crate::main(
&argv,
&mut entries,
sync_rows.as_ref(),
add_row.as_ref(),
now,
);
let got = json!({"stdout": stdout, "stderr": stderr, "exit": code});
assert_eq!(
&got, &case["expected"],
&got,
&case["expected"],
"[main] case '{}' diverged",
case["name"].as_str().unwrap()
);
+34 -21
View File
@@ -215,7 +215,10 @@ fn evict(entries: &mut Map<String, Value>) -> Vec<i64> {
for k in &evicted {
entries.remove(k);
}
evicted.iter().filter_map(|k| k.parse::<i64>().ok()).collect()
evicted
.iter()
.filter_map(|k| k.parse::<i64>().ok())
.collect()
}
/// `add` — admit a proven-or-important entry, stamp it with `now`, and
@@ -257,7 +260,11 @@ pub fn add(
/// `access` — record a cache access (reinforces the entry). Missing id is
/// a silent no-op (`changed: false`, oracle saves nothing).
pub fn access(entries: &Map<String, Value>, entry_id: i64, now: &str) -> (Map<String, Value>, bool) {
pub fn access(
entries: &Map<String, Value>,
entry_id: i64,
now: &str,
) -> (Map<String, Value>, bool) {
let mut out = entries.clone();
{
let key = entry_id.to_string();
@@ -295,7 +302,10 @@ pub fn get_priority(entries: &Map<String, Value>, entry_id: i64) -> f64 {
/// `row[1] or 0.5`, `row[2] or "washable"`. Each row coerces ONCE —
/// the priority is computed straight off the borrowed fields, never via
/// an intermediate JSON object re-parse.
pub fn update_entries(entries: &Map<String, Value>, rows: &Map<String, Value>) -> Map<String, Value> {
pub fn update_entries(
entries: &Map<String, Value>,
rows: &Map<String, Value>,
) -> Map<String, Value> {
let mut out = entries.clone();
let keys: Vec<String> = out.keys().cloned().collect();
for key in keys {
@@ -342,12 +352,10 @@ pub fn get_stats(entries: &Map<String, Value>) -> Value {
(0.0f64, f64::NEG_INFINITY, f64::INFINITY, 0usize, 0usize),
|(sum, max, min, rock, est), e| {
let p = e.get("priority").and_then(Value::as_f64).unwrap_or(0.0);
let rock = rock
+ usize::from(e.get("rock_status").and_then(Value::as_str) == Some("rock"));
let rock =
rock + usize::from(e.get("rock_status").and_then(Value::as_str) == Some("rock"));
let est = est
+ usize::from(
e.get("rock_status").and_then(Value::as_str) == Some("established"),
);
+ usize::from(e.get("rock_status").and_then(Value::as_str) == Some("established"));
(sum + p, max.max(p), min.min(p), rock, est)
},
);
@@ -467,7 +475,11 @@ pub fn main(
});
let (out, _admitted, _evicted) = add(entries, entry_id, &entry, now);
*entries = out;
(format!("Added entry {entry_id} to cache\n"), String::new(), 0)
(
format!("Added entry {entry_id} to cache\n"),
String::new(),
0,
)
}
None => (format!("Entry {entry_id} not found\n"), String::new(), 0),
}
@@ -536,17 +548,20 @@ fn entry_id_of(a: &Value) -> i64 {
/// dcc_compute_priority({entry}) → {priority}
#[no_mangle]
pub extern "C" fn dcc_compute_priority(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
json!({"priority": compute_priority(a.get("entry").unwrap_or(&Value::Null))})
}))
envelope(
read_arg(arg)
.map(|a| json!({"priority": compute_priority(a.get("entry").unwrap_or(&Value::Null))})),
)
}
/// dcc_qualifies_for_cache({entry}) → {qualifies}
#[no_mangle]
pub extern "C" fn dcc_qualifies_for_cache(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
json!({"qualifies": qualifies_for_cache(a.get("entry").unwrap_or(&Value::Null))})
}))
envelope(
read_arg(arg).map(
|a| json!({"qualifies": qualifies_for_cache(a.get("entry").unwrap_or(&Value::Null))}),
),
)
}
/// dcc_add({entries, entry_id, entry, now}) → {entries, admitted, evicted}
@@ -575,17 +590,15 @@ pub extern "C" fn dcc_access(arg: *const c_char) -> *mut c_char {
/// dcc_contains({entries, entry_id}) → {contains}
#[no_mangle]
pub extern "C" fn dcc_contains(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
json!({"contains": contains(&entries_of(&a), entry_id_of(&a))})
}))
envelope(read_arg(arg).map(|a| json!({"contains": contains(&entries_of(&a), entry_id_of(&a))})))
}
/// dcc_get_priority({entries, entry_id}) → {priority}
#[no_mangle]
pub extern "C" fn dcc_get_priority(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
json!({"priority": get_priority(&entries_of(&a), entry_id_of(&a))})
}))
envelope(
read_arg(arg).map(|a| json!({"priority": get_priority(&entries_of(&a), entry_id_of(&a))})),
)
}
/// dcc_update_entries({entries, rows}) → {entries}
+96 -25
View File
@@ -7,8 +7,10 @@
//! are replayed byte-for-byte, the IO-seam groups ride along as provenance
//! and are re-verified live by the generator's RUNE_DL_CORE=auto parity pass).
use crate::{_auto_fix_decision, anchor_record, check_alerts, diff_blocks,
fix_decision, ledger_path_for, sha, snapshot_entry};
use crate::{
_auto_fix_decision, anchor_record, check_alerts, diff_blocks, fix_decision, ledger_path_for,
sha, snapshot_entry,
};
const TRUTH: &str = r###"{
"frozen": "drift_ledger sha/diff/ledger_path/load_ledger/save_ledger/read_block/journal/surface_alert/snapshot/anchor/check/auto_fix/fix/restore_topic/handle_hook/main",
@@ -2059,50 +2061,102 @@ fn truth() -> serde_json::Value {
#[test]
fn sha_matches_oracle() {
for (idx, case) in truth()["sha"].as_array().expect("sha array").iter().enumerate() {
for (idx, case) in truth()["sha"]
.as_array()
.expect("sha array")
.iter()
.enumerate()
{
let got = sha(case["input"]["text"].as_str().unwrap_or(""));
assert_eq!(got, case["expected"].as_str().unwrap_or(""), "sha case {}", idx);
assert_eq!(
got,
case["expected"].as_str().unwrap_or(""),
"sha case {}",
idx
);
}
}
#[test]
fn diff_matches_oracle() {
for (idx, case) in truth()["diff"].as_array().expect("diff array").iter().enumerate() {
let got = diff_blocks(case["input"]["ref"].as_str().unwrap_or(""),
case["input"]["cur"].as_str().unwrap_or(""));
assert_eq!(got, case["expected"].as_str().unwrap_or(""), "diff case {}", idx);
for (idx, case) in truth()["diff"]
.as_array()
.expect("diff array")
.iter()
.enumerate()
{
let got = diff_blocks(
case["input"]["ref"].as_str().unwrap_or(""),
case["input"]["cur"].as_str().unwrap_or(""),
);
assert_eq!(
got,
case["expected"].as_str().unwrap_or(""),
"diff case {}",
idx
);
}
}
#[test]
fn ledger_path_matches_oracle() {
for (idx, case) in truth()["ledger_path"].as_array().expect("ledger_path array").iter().enumerate() {
for (idx, case) in truth()["ledger_path"]
.as_array()
.expect("ledger_path array")
.iter()
.enumerate()
{
let got = ledger_path_for(case["input"]["index_file"].as_str().unwrap_or(""));
assert_eq!(got, case["expected"].as_str().unwrap_or(""), "ledger_path case {}", idx);
assert_eq!(
got,
case["expected"].as_str().unwrap_or(""),
"ledger_path case {}",
idx
);
}
}
#[test]
fn snapshot_entry_matches_oracle() {
for (idx, case) in truth()["k_snapshot"].as_array().expect("k_snapshot array").iter().enumerate() {
let got = snapshot_entry(&case["input"]["blocks"], case["input"]["when"].as_str().unwrap_or(""));
for (idx, case) in truth()["k_snapshot"]
.as_array()
.expect("k_snapshot array")
.iter()
.enumerate()
{
let got = snapshot_entry(
&case["input"]["blocks"],
case["input"]["when"].as_str().unwrap_or(""),
);
assert_eq!(got, case["expected"], "k_snapshot case {}", idx);
}
}
#[test]
fn anchor_record_matches_oracle() {
for (idx, case) in truth()["k_anchor"].as_array().expect("k_anchor array").iter().enumerate() {
let got = anchor_record(case["input"]["content"].as_str().unwrap_or(""),
case["input"]["reason"].as_str().unwrap_or(""),
case["input"]["when"].as_str().unwrap_or(""));
for (idx, case) in truth()["k_anchor"]
.as_array()
.expect("k_anchor array")
.iter()
.enumerate()
{
let got = anchor_record(
case["input"]["content"].as_str().unwrap_or(""),
case["input"]["reason"].as_str().unwrap_or(""),
case["input"]["when"].as_str().unwrap_or(""),
);
assert_eq!(got, case["expected"], "k_anchor case {}", idx);
}
}
#[test]
fn check_alerts_matches_oracle() {
for (idx, case) in truth()["k_alerts"].as_array().expect("k_alerts array").iter().enumerate() {
for (idx, case) in truth()["k_alerts"]
.as_array()
.expect("k_alerts array")
.iter()
.enumerate()
{
let got = check_alerts(&case["input"]["ledger"], &case["input"]["blocks"]);
assert_eq!(got, case["expected"], "k_alerts case {}", idx);
}
@@ -2110,20 +2164,37 @@ fn check_alerts_matches_oracle() {
#[test]
fn auto_fix_decision_matches_oracle() {
for (idx, case) in truth()["k_auto_fix"].as_array().expect("k_auto_fix array").iter().enumerate() {
let got = _auto_fix_decision(case["input"]["label"].as_str().unwrap_or(""),
matches!(case["input"]["anchored"], serde_json::Value::Bool(true)),
matches!(case["input"]["has_last_good"], serde_json::Value::Bool(true)));
for (idx, case) in truth()["k_auto_fix"]
.as_array()
.expect("k_auto_fix array")
.iter()
.enumerate()
{
let got = _auto_fix_decision(
case["input"]["label"].as_str().unwrap_or(""),
matches!(case["input"]["anchored"], serde_json::Value::Bool(true)),
matches!(
case["input"]["has_last_good"],
serde_json::Value::Bool(true)
),
);
assert_eq!(got, case["expected"], "k_auto_fix case {}", idx);
}
}
#[test]
fn fix_decision_matches_oracle() {
for (idx, case) in truth()["k_fix"].as_array().expect("k_fix array").iter().enumerate() {
let got = fix_decision(&case["input"]["ledger"],
case["input"]["label"].as_str().unwrap_or(""),
case["input"].get("cur_sha").and_then(|v| v.as_str()));
for (idx, case) in truth()["k_fix"]
.as_array()
.expect("k_fix array")
.iter()
.enumerate()
{
let got = fix_decision(
&case["input"]["ledger"],
case["input"]["label"].as_str().unwrap_or(""),
case["input"].get("cur_sha").and_then(|v| v.as_str()),
);
assert_eq!(got, case["expected"], "k_fix case {}", idx);
}
}
+30 -15
View File
@@ -96,13 +96,21 @@ pub fn diff_blocks(reference: &str, current: &str) -> String {
if !added.is_empty() {
parts.push(format!(
"ADDED: {}",
added.iter().map(|l| l.chars().take(60).collect::<String>()).collect::<Vec<_>>().join(" | ")
added
.iter()
.map(|l| l.chars().take(60).collect::<String>())
.collect::<Vec<_>>()
.join(" | ")
));
}
if !removed.is_empty() {
parts.push(format!(
"REMOVED: {}",
removed.iter().map(|l| l.chars().take(60).collect::<String>()).collect::<Vec<_>>().join(" | ")
removed
.iter()
.map(|l| l.chars().take(60).collect::<String>())
.collect::<Vec<_>>()
.join(" | ")
));
}
if parts.is_empty() {
@@ -128,7 +136,11 @@ pub fn check_block(
};
let pct = (delta.abs() as f64) / (base.max(1) as f64);
let is_constitution = matches!(BlockLabel::of_name(label), Some(BlockLabel::Constitution));
let limit = if is_constitution { CONSTITUTION_LIMIT } else { DRIFT_LIMIT };
let limit = if is_constitution {
CONSTITUTION_LIMIT
} else {
DRIFT_LIMIT
};
let content_changed = is_constitution
&& anchor_size.is_some()
&& base_sha.is_some()
@@ -185,7 +197,10 @@ pub fn ledger_path_for(index_file: &str) -> String {
pub fn snapshot_entry(blocks: &Value, when: &str) -> Value {
let mut b = serde_json::Map::new();
for label in BlockLabel::ALL {
let raw = blocks.get(label.as_str()).and_then(|v| v.as_str()).unwrap_or("");
let raw = blocks
.get(label.as_str())
.and_then(|v| v.as_str())
.unwrap_or("");
b.insert(
label.as_str().to_string(),
json!({
@@ -227,7 +242,10 @@ pub fn check_alerts(ledger: &Value, blocks: &Value) -> Value {
let alerts: Vec<Value> = BlockLabel::ALL
.iter()
.filter_map(|label| {
let cur = blocks.get(label.as_str()).and_then(|v| v.as_str()).unwrap_or("");
let cur = blocks
.get(label.as_str())
.and_then(|v| v.as_str())
.unwrap_or("");
let cur_size = cur.chars().count() as i64;
let anchor_meta = anchors.get(label.as_str());
let (base, base_sha, delta, ref_content) = match anchor_meta {
@@ -305,7 +323,9 @@ fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
@@ -329,18 +349,14 @@ fn s(v: Option<&Value>) -> &str {
/// dl_sha({"text"}) -> hex
#[no_mangle]
pub extern "C" fn dl_sha(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
Value::String(sha(s(a.get("text"))))
});
let r = read_arg(arg).map(|a| Value::String(sha(s(a.get("text")))));
envelope(r)
}
/// dl_diff({"ref", "cur"}) -> summary string
#[no_mangle]
pub extern "C" fn dl_diff(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
Value::String(diff_blocks(s(a.get("ref")), s(a.get("cur"))))
});
let r = read_arg(arg).map(|a| Value::String(diff_blocks(s(a.get("ref")), s(a.get("cur")))));
envelope(r)
}
@@ -391,9 +407,8 @@ pub extern "C" fn dl_snapshot_entry(arg: *const c_char) -> *mut c_char {
/// dl_anchor_record({"content", "reason", "when"}) -> anchor record
#[no_mangle]
pub extern "C" fn dl_anchor_record(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
anchor_record(s(a.get("content")), s(a.get("reason")), s(a.get("when")))
});
let r = read_arg(arg)
.map(|a| anchor_record(s(a.get("content")), s(a.get("reason")), s(a.get("when"))));
envelope(r)
}
@@ -8,8 +8,7 @@
//! pinned (poisoned-truth law). Embedded VERBATIM — never transcribed.
use crate::{
add_alert, acknowledge_alert, check_alerts, get_unacknowledged_alerts,
run_retrospective,
acknowledge_alert, add_alert, check_alerts, get_unacknowledged_alerts, run_retrospective,
};
use serde_json::{json, Value};
@@ -17,10 +16,7 @@ const TRUTH: &str = include_str!("frozen_do_truth.json");
fn cases(group: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][group]
.as_array()
.unwrap()
.clone()
truth["cases"][group].as_array().unwrap().clone()
}
fn replay(group: &str, f: impl Fn(&Value) -> Value) {
@@ -83,9 +79,10 @@ fn run_retrospective_parity_battery() {
#[test]
fn get_unacknowledged_alerts_parity_battery() {
replay("get_unacknowledged_alerts", |i| {
json!({"unacked": get_unacknowledged_alerts(i["alerts"].as_array().unwrap())})
});
replay(
"get_unacknowledged_alerts",
|i| json!({"unacked": get_unacknowledged_alerts(i["alerts"].as_array().unwrap())}),
);
}
#[test]
@@ -147,10 +144,16 @@ fn main_parity_battery() {
for case in cases("main") {
let i = &case["input"];
let st = DoMainState {
alerts_text: i.get("alerts_text").and_then(Value::as_str).map(str::to_string),
alerts_text: i
.get("alerts_text")
.and_then(Value::as_str)
.map(str::to_string),
velocity: i["velocity"].clone(),
direction: i["direction"].clone(),
last_washed: i.get("last_washed").and_then(Value::as_str).map(str::to_string),
last_washed: i
.get("last_washed")
.and_then(Value::as_str)
.map(str::to_string),
counts: i["counts"].clone(),
now: i["now"].as_str().unwrap_or("").to_string(),
now_local: i["now_local"].as_str().unwrap_or("").to_string(),
@@ -167,9 +170,21 @@ fn main_parity_battery() {
let got = main(&argv, &st);
let want = &case["expected"];
let name = case["name"].as_str().unwrap();
assert_eq!(got.exit as i64, want["exit"].as_i64().unwrap(), "[main] {name} exit");
assert_eq!(got.stdout, want["stdout"].as_str().unwrap(), "[main] {name} stdout");
assert_eq!(got.stderr, want["stderr"].as_str().unwrap(), "[main] {name} stderr");
assert_eq!(
got.exit as i64,
want["exit"].as_i64().unwrap(),
"[main] {name} exit"
);
assert_eq!(
got.stdout,
want["stdout"].as_str().unwrap(),
"[main] {name} stdout"
);
assert_eq!(
got.stderr,
want["stderr"].as_str().unwrap(),
"[main] {name} stderr"
);
if let Some(want_text) = want.get("alerts_file").and_then(Value::as_str) {
let rendered = crate::_save_alerts(&got.alerts_after);
assert_eq!(rendered, want_text, "[main] {name} alerts file bytes");
@@ -182,7 +197,11 @@ fn main_parity_battery() {
r.get("filename"),
"[main] {name} retro filename"
);
assert_eq!(g.get("report"), r.get("report"), "[main] {name} retro report");
assert_eq!(
g.get("report"),
r.get("report"),
"[main] {name} retro report"
);
} else {
assert!(got.retro.is_none(), "[main] {name} unexpected retro");
}
+72 -27
View File
@@ -221,7 +221,11 @@ pub fn check_alerts(
let mut raised: Vec<NewAlert> = Vec::new();
// Velocity
if velocity.get("alert").and_then(Value::as_bool).unwrap_or(false) {
if velocity
.get("alert")
.and_then(Value::as_bool)
.unwrap_or(false)
{
raised.push(NewAlert {
kind: AlertKind::HighVelocity,
message: format!(
@@ -356,9 +360,7 @@ fn parse_iso(s: &str) -> Option<IsoInstant> {
if bytes.len() < 19 {
return None;
}
let num = |from: usize, to: usize| -> Option<i64> {
s.get(from..to)?.parse::<i64>().ok()
};
let num = |from: usize, to: usize| -> Option<i64> { s.get(from..to)?.parse::<i64>().ok() };
let (y, mo, d) = (num(0, 4)?, num(5, 7)?, num(8, 10)?);
let sep = bytes[10];
if sep != b'T' && sep != b't' && sep != b' ' {
@@ -368,7 +370,10 @@ fn parse_iso(s: &str) -> Option<IsoInstant> {
let mut rest = &s[19..];
let mut micros = 0i64;
if let Some(stripped) = rest.strip_prefix('.') {
let digits: String = stripped.chars().take_while(|c| c.is_ascii_digit()).collect();
let digits: String = stripped
.chars()
.take_while(|c| c.is_ascii_digit())
.collect();
if digits.is_empty() {
return None;
}
@@ -391,7 +396,10 @@ fn parse_iso(s: &str) -> Option<IsoInstant> {
let body = &rest[1..];
let body = body.trim_end_matches(|c: char| c.is_whitespace());
let (oh, om) = if body.len() == 5 && body.as_bytes()[2] == b':' {
(body[0..2].parse::<i64>().ok()?, body[3..5].parse::<i64>().ok()?)
(
body[0..2].parse::<i64>().ok()?,
body[3..5].parse::<i64>().ok()?,
)
} else if body.len() == 2 {
(body[0..2].parse::<i64>().ok()?, 0)
} else {
@@ -401,7 +409,10 @@ fn parse_iso(s: &str) -> Option<IsoInstant> {
}
};
let day_secs = days_from_civil(y, mo, d) * 86400 + h * 3600 + mi * 60 + sec - offset_secs;
Some(IsoInstant { micros: day_secs * 1_000_000 + micros, aware })
Some(IsoInstant {
micros: day_secs * 1_000_000 + micros,
aware,
})
}
/// Howard Hinnant's days_from_civil — days since 1970-01-01.
@@ -570,9 +581,7 @@ pub extern "C" fn dob_run_retrospective(arg: *const c_char) -> *mut c_char {
/// dob_get_unacknowledged_alerts({alerts}) → {unacked}
#[no_mangle]
pub extern "C" fn dob_get_unacknowledged_alerts(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
json!({"unacked": get_unacknowledged_alerts(alerts_of(&a))})
}))
envelope(read_arg(arg).map(|a| json!({"unacked": get_unacknowledged_alerts(alerts_of(&a))})))
}
/// dob_acknowledge_alert({alerts, index}) → {alerts, changed}
@@ -790,7 +799,13 @@ pub fn main(argv: &[String], st: &DoMainState) -> DoMainOutcome {
retro: None,
};
};
let valid = ["check", "retrospective", "alerts", "acknowledge", "dashboard"];
let valid = [
"check",
"retrospective",
"alerts",
"acknowledge",
"dashboard",
];
if !valid.contains(&cmd.as_str()) {
return top_usage_err(&format!(
"argument cmd: invalid choice: '{cmd}' (choose from 'check', \
@@ -819,9 +834,7 @@ fn subparser_err(sub: &str, usage: &str, detail: &str) -> DoMainOutcome {
DoMainOutcome {
exit: 2,
stdout: String::new(),
stderr: format!(
"{usage}\ndrift_observability.py {sub}: error: {detail}\n"
),
stderr: format!("{usage}\ndrift_observability.py {sub}: error: {detail}\n"),
alerts_after: Vec::new(),
retro: None,
}
@@ -849,7 +862,13 @@ fn main_check(alerts: &[Value], st: &DoMainState) -> DoMainOutcome {
));
}
}
DoMainOutcome { exit: 0, stdout: out, stderr: String::new(), alerts_after: all, retro: None }
DoMainOutcome {
exit: 0,
stdout: out,
stderr: String::new(),
alerts_after: all,
retro: None,
}
}
fn main_retrospective(days: i64, st: &DoMainState) -> DoMainOutcome {
@@ -902,14 +921,16 @@ fn main_alerts(alerts: &[Value]) -> DoMainOutcome {
));
}
}
DoMainOutcome { exit: 0, stdout: out, stderr: String::new(), alerts_after: alerts.to_vec(), retro: None }
DoMainOutcome {
exit: 0,
stdout: out,
stderr: String::new(),
alerts_after: alerts.to_vec(),
retro: None,
}
}
fn main_acknowledge(
index_arg: Option<&str>,
alerts: &[Value],
_st: &DoMainState,
) -> DoMainOutcome {
fn main_acknowledge(index_arg: Option<&str>, alerts: &[Value], _st: &DoMainState) -> DoMainOutcome {
let Some(raw) = index_arg else {
return subparser_err(
"acknowledge",
@@ -927,7 +948,13 @@ fn main_acknowledge(
};
let (all, _) = acknowledge_alert(alerts, index);
let out = format!("Alert {index} acknowledged.\n");
DoMainOutcome { exit: 0, stdout: out, stderr: String::new(), alerts_after: all, retro: None }
DoMainOutcome {
exit: 0,
stdout: out,
stderr: String::new(),
alerts_after: all,
retro: None,
}
}
fn main_dashboard(alerts: &[Value], st: &DoMainState) -> DoMainOutcome {
@@ -945,12 +972,24 @@ fn main_dashboard(alerts: &[Value], st: &DoMainState) -> DoMainOutcome {
out.push_str(&format!("Total entries: {total}\n"));
out.push_str(&format!("Validated (proven, trusted): {established}\n"));
out.push_str(&format!("Locked (immutable, identity anchors): {rock}\n"));
out.push_str(&format!("Provisional (still being tested): {}\n", total - established - rock));
out.push_str(&format!(
"Provisional (still being tested): {}\n",
total - established - rock
));
out.push_str(&format!("Trust ratio: {:.2}%\n", ratio * 100.0));
out.push_str(&format!("Evolution direction: {}\n", py_scalar_str(&d("direction"))));
out.push_str(&format!(
"Evolution direction: {}\n",
py_scalar_str(&d("direction"))
));
out.push_str("\n--- State Evolution ---\n");
out.push_str(&format!("Changes/day: {}\n", py_scalar_str(&v("changes_per_day"))));
out.push_str(&format!("Promotions/day: {}\n", py_scalar_str(&v("promotions_per_day"))));
out.push_str(&format!(
"Changes/day: {}\n",
py_scalar_str(&v("changes_per_day"))
));
out.push_str(&format!(
"Promotions/day: {}\n",
py_scalar_str(&v("promotions_per_day"))
));
out.push_str(&format!("Alert: {}\n", py_scalar_str(&v("alert"))));
out.push_str("\n--- Alerts ---\n");
out.push_str(&format!("Unacknowledged: {}\n", unacked.len()));
@@ -962,7 +1001,13 @@ fn main_dashboard(alerts: &[Value], st: &DoMainState) -> DoMainOutcome {
a.get("message").and_then(Value::as_str).unwrap_or(""),
));
}
DoMainOutcome { exit: 0, stdout: out, stderr: String::new(), alerts_after: alerts.to_vec(), retro: None }
DoMainOutcome {
exit: 0,
stdout: out,
stderr: String::new(),
alerts_after: alerts.to_vec(),
retro: None,
}
}
// ── Python-JSON writer (task_persist/task_gate law) ───────────────────────
+5 -5
View File
@@ -37,17 +37,17 @@ fn replay(group: &str, f: impl Fn(&Value) -> Value) {
#[test]
fn classify_drift_parity_battery() {
replay("classify_drift", |i| {
json!({"record": classify_drift(&i["claim"], &i["observation"])})
});
replay(
"classify_drift",
|i| json!({"record": classify_drift(&i["claim"], &i["observation"])}),
);
}
#[test]
fn reconcile_parity_battery() {
replay("reconcile", |i| {
let claims = i["claims"].as_array().cloned().unwrap_or_default();
let observations =
i["observations"].as_array().cloned().unwrap_or_default();
let observations = i["observations"].as_array().cloned().unwrap_or_default();
reconcile(&claims, &observations)
});
}
+19 -25
View File
@@ -156,7 +156,11 @@ pub fn classify_drift(claim: &Value, observation: &Value) -> Value {
Verdict::Matches => {
let stale_ok = claim.get("stale_ok").map(py_truthy).unwrap_or(false);
(
if stale_ok { DriftClass::Expected } else { DriftClass::Confirmed },
if stale_ok {
DriftClass::Expected
} else {
DriftClass::Confirmed
},
get_or_dflt(observation, "checked_by", ""),
)
}
@@ -168,8 +172,7 @@ pub fn classify_drift(claim: &Value, observation: &Value) -> Value {
),
),
Verdict::Contradicted => {
let user_locked =
claim.get("user_locked").map(py_truthy).unwrap_or(false);
let user_locked = claim.get("user_locked").map(py_truthy).unwrap_or(false);
if user_locked {
(
DriftClass::Conflicting,
@@ -263,7 +266,9 @@ impl MovedTwin {
fn reconciliation(&self) -> String {
match self {
MovedTwin::Moved(m) => format!("update source to {}", m),
MovedTwin::Absent => "entity requires re-derivation or user-locked preservation".to_string(),
MovedTwin::Absent => {
"entity requires re-derivation or user-locked preservation".to_string()
}
}
}
}
@@ -274,14 +279,8 @@ impl MovedTwin {
/// files that no longer exist. REALITY WINS unless the claim is
/// user-locked (identities.user_locked is True — the oracle's `is True`
/// identity check, so a truthy non-True value is NOT locked).
pub fn detect_structural_drift(
entities: &[Value],
files: &[Value],
dirs: &[Value],
) -> Vec<Value> {
let in_set = |set: &[Value], src: &str| {
set.iter().any(|p| p.as_str() == Some(src))
};
pub fn detect_structural_drift(entities: &[Value], files: &[Value], dirs: &[Value]) -> Vec<Value> {
let in_set = |set: &[Value], src: &str| set.iter().any(|p| p.as_str() == Some(src));
let mut drifts = Vec::new();
for e in entities {
if e.get("kind").and_then(Value::as_str) != Some("component") {
@@ -290,23 +289,22 @@ pub fn detect_structural_drift(
let Some(src) = source_of(e) else {
continue;
};
let locked = obj_or_empty(e, "identities").get("user_locked")
== Some(&Value::Bool(true));
let locked = obj_or_empty(e, "identities").get("user_locked") == Some(&Value::Bool(true));
let exists = in_set(files, src) || in_set(dirs, src);
if exists {
continue;
}
// reality: file gone/moved — search the facade listing for a
// moved twin (first basename match in listing order)
let expected_name =
std::path::Path::new(src).file_name().and_then(|n| n.to_str());
let expected_name = std::path::Path::new(src)
.file_name()
.and_then(|n| n.to_str());
let twin: MovedTwin = expected_name
.and_then(|name| {
files.iter().find_map(|p| {
let s = p.as_str()?;
(std::path::Path::new(s).file_name().and_then(|n| n.to_str())
== Some(name))
.then(|| s.to_string())
(std::path::Path::new(s).file_name().and_then(|n| n.to_str()) == Some(name))
.then(|| s.to_string())
})
})
.map_or(MovedTwin::Absent, MovedTwin::Moved);
@@ -365,9 +363,7 @@ fn envelope(r: Result<Value, FfiError>) -> *mut std::os::raw::c_char {
/// dr_classify_drift({claim, observation}) → {record}
#[no_mangle]
pub extern "C" fn dr_classify_drift(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
pub extern "C" fn dr_classify_drift(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
json!({
"record": classify_drift(
@@ -380,9 +376,7 @@ pub extern "C" fn dr_classify_drift(
/// dr_reconcile({claims, observations}) → {resume, drifts, blocking}
#[no_mangle]
pub extern "C" fn dr_reconcile(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
pub extern "C" fn dr_reconcile(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
let claims = a.get("claims").and_then(Value::as_array);
let observations = a.get("observations").and_then(Value::as_array);
@@ -1,6 +1,6 @@
//! branch_battery — replays frozen_ec_truth.json VERBATIM.
use super::{pyjson, pack, size, unpack, Json};
use super::{pack, pyjson, size, unpack, Json};
const TRUTH: &str = include_str!("../frozen_ec_truth.json");
+1 -4
View File
@@ -115,10 +115,7 @@ pub fn unpack(capsule: &Json) -> Json {
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),
]),
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)),
@@ -23,10 +23,7 @@ fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
let mut cur = v;
for k in path {
cur = match cur {
Json::Obj(pairs) => pairs
.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, x)| x)?,
Json::Obj(pairs) => pairs.iter().find(|(k2, _)| k2 == k).map(|(_, x)| x)?,
_ => return None,
};
}
@@ -126,9 +123,7 @@ fn replay_trace_task() {
let lines = at(&c, &["args", "lines"]);
let outcomes = match lines {
None | Some(Json::Null) => None,
Some(Json::Arr(items)) => {
Some(read_outcome_events(items).expect("oracle-safe corpus"))
}
Some(Json::Arr(items)) => Some(read_outcome_events(items).expect("oracle-safe corpus")),
_ => None,
};
let task_record = match at(&c, &["args", "task_record"]) {
+62 -39
View File
@@ -74,8 +74,7 @@ pub fn read_outcome_events(lines: &[Json]) -> Result<Json, (String, String)> {
// find-then-scan for duplicate task_ids — one pass over a journal of
// N lines is O(N) instead of O(N²), with insertion order preserved
// (the dict-assignment law needs first-seen ordering, not sorted).
let mut index: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
let mut index: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
for line in lines {
let Json::Str(raw) = line else { continue }; // TypeError branch
let rec = match pyjson::parse(raw) {
@@ -155,10 +154,19 @@ pub fn build_receipt(task: &Json, payload: Option<&Json>) -> Json {
("description", field(Some(task), "description", s(""))),
("status", status),
("model", model),
("memories_used", field(Some(task), "memories_used", Json::Arr(Vec::new()))),
("rules_used", field(Some(task), "rules_used", Json::Arr(Vec::new()))),
(
"memories_used",
field(Some(task), "memories_used", Json::Arr(Vec::new())),
),
(
"rules_used",
field(Some(task), "rules_used", Json::Arr(Vec::new())),
),
("capability", field(Some(task), "capability", s(""))),
("actions", field(Some(task), "actions", Json::Arr(Vec::new()))),
(
"actions",
field(Some(task), "actions", Json::Arr(Vec::new())),
),
("outcome", outcome),
("completed_at", completed),
])
@@ -206,11 +214,9 @@ fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
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(),
Err(_) => CString::new(r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#)
.unwrap()
.into_raw(),
}
}
@@ -239,51 +245,68 @@ 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));
let out: Result<Json, (String, String)> = read_arg(arg).and_then(|raw| $body(&raw));
envelope(out)
}
};
}
ffi!(et_read_outcome_events, |raw: &str| -> Result<Json, (String, String)> {
ffi!(et_read_outcome_events, |raw: &str| -> Result<
Json,
(String, String),
> {
let doc = parse_doc(raw)?;
match doc.get("lines") {
Some(Json::Arr(items)) => read_outcome_events(items),
_ => Err(("lines must be a list of journal lines".into(), "TypeError".into())),
_ => Err((
"lines must be a list of journal lines".into(),
"TypeError".into(),
)),
}
});
ffi!(et_build_receipt, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let task = want_obj(&doc, "task")?
.ok_or(("task must be an object".into(), "TypeError".into()))?;
if !matches!(task, Json::Obj(_)) {
return Err(("task must be an object".into(), "TypeError".into()));
ffi!(
et_build_receipt,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let task =
want_obj(&doc, "task")?.ok_or(("task must be an object".into(), "TypeError".into()))?;
if !matches!(task, Json::Obj(_)) {
return Err(("task must be an object".into(), "TypeError".into()));
}
Ok(build_receipt(task, want_obj(&doc, "outcome_payload")?))
}
Ok(build_receipt(task, want_obj(&doc, "outcome_payload")?))
});
);
ffi!(et_trace_task, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let task_id = match doc.get("task_id") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("task_id must be a string".into(), "TypeError".into())),
};
let task_record = want_obj(&doc, "task_record")?;
if let Some(rec) = task_record {
if !matches!(rec, Json::Obj(_)) {
return Err(("task_record must be an object or null".into(), "TypeError".into()));
ffi!(
et_trace_task,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let task_id = match doc.get("task_id") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("task_id must be a string".into(), "TypeError".into())),
};
let task_record = want_obj(&doc, "task_record")?;
if let Some(rec) = task_record {
if !matches!(rec, Json::Obj(_)) {
return Err((
"task_record must be an object or null".into(),
"TypeError".into(),
));
}
}
}
let outcomes = want_obj(&doc, "outcomes")?;
if let Some(o) = outcomes {
if !matches!(o, Json::Obj(_)) {
return Err(("outcomes must be an object or null".into(), "TypeError".into()));
let outcomes = want_obj(&doc, "outcomes")?;
if let Some(o) = outcomes {
if !matches!(o, Json::Obj(_)) {
return Err((
"outcomes must be an object or null".into(),
"TypeError".into(),
));
}
}
Ok(trace_task(&task_id, task_record, outcomes))
}
Ok(trace_task(&task_id, task_record, outcomes))
});
);
#[cfg(test)]
mod branch_battery;
+27 -7
View File
@@ -23,11 +23,18 @@ fn s<'a>(v: &'a Value, key: &str) -> &'a str {
#[test]
fn battery_replays_frozen_truth_verbatim() {
let rows = rows();
assert!(rows.len() >= 300, "truth must be substantial, got {}", rows.len());
assert!(
rows.len() >= 300,
"truth must be substantial, got {}",
rows.len()
);
for (i, row) in rows.iter().enumerate() {
let fname = s(row, "fn");
let expected = s(row, "expected");
let args = row.get("args").and_then(|a| a.as_array()).expect("args array");
let args = row
.get("args")
.and_then(|a| a.as_array())
.expect("args array");
let outcome = match fname {
"affirm_rule" => affirm_rule(args[0].as_str().expect("fact arg")).into_owned(),
"grade_fact" => {
@@ -40,9 +47,13 @@ fn battery_replays_frozen_truth_verbatim() {
other => panic!("unknown fn in truth row {}: {}", i, other),
};
assert_eq!(
outcome, expected,
outcome,
expected,
"row {} diverged: fn={} args={:?} user_name={:?}",
i, fname, args, row.get("user_name")
i,
fname,
args,
row.get("user_name")
);
}
}
@@ -66,14 +77,20 @@ fn truth_pin_name_token_branch() {
.zip(named.iter())
.filter(|(a, b)| a["args"] == b["args"] && s(a, "expected") != s(b, "expected"))
.count();
assert_eq!(divergent, 2, "name-token branch must flip exactly the 2 name rows");
assert_eq!(
divergent, 2,
"name-token branch must flip exactly the 2 name rows"
);
}
#[test]
fn affirm_dict_order_is_first_match_wins() {
// dict insertion order: "never hedge" precedes "don't stall",
// "do not overclaim" precedes "don't stall".
assert_eq!(affirm_rule("never hedge and don't stall"), "I answer directly, without hedging");
assert_eq!(
affirm_rule("never hedge and don't stall"),
"I answer directly, without hedging"
);
assert_eq!(
affirm_rule("don't stall and do not overclaim"),
"I name evidence as evidence and confirmed causes as confirmed"
@@ -131,5 +148,8 @@ fn affirm_pass_through_is_zero_copy() {
std::borrow::Cow::Owned(s) => panic!("pass-through allocated: {s}"),
}
// A known prohibition rewrites -> owned.
assert!(matches!(affirm_rule("never hedge"), std::borrow::Cow::Owned(_)));
assert!(matches!(
affirm_rule("never hedge"),
std::borrow::Cow::Owned(_)
));
}
+124 -27
View File
@@ -29,47 +29,123 @@
/// Imperative / rule signals: a sentence instructing HOW to operate.
pub const RULE_WEAK: &[&str] = &[
"should", "prefer", "instead of", "lead with", "first", "before ", "avoid",
"favour", "keep ", "stop ", "start ", "use ",
"should",
"prefer",
"instead of",
"lead with",
"first",
"before ",
"avoid",
"favour",
"keep ",
"stop ",
"start ",
"use ",
];
pub const RULE_STRONG: &[&str] = &[
"always", "never ", "must", "do not", "don't", "i always", "i must",
"i should",
"always", "never ", "must", "do not", "don't", "i always", "i must", "i should",
];
/// Positive-affirmation hint words — behavioural, not factual.
pub const AFFIRM_SIGNALS: &[&str] = &[
"i protect", "i am", "i reveal", "i factor", "i dissolve", "i respect",
"i teach", "i make", "i write", "i use the tiers", "i check", "i stop",
"i protect",
"i am",
"i reveal",
"i factor",
"i dissolve",
"i respect",
"i teach",
"i make",
"i write",
"i use the tiers",
"i check",
"i stop",
];
/// Subject signals: who/what the fact is about (portable — no hardcoded
/// identity; the user's name arrives via the caller).
pub const USER_FACT_SIGNALS: &[&str] = &[
"name", "email", "role", "lives in", "located in", "daw", "os:", "shell",
"has a dog", "has a cat", "prefers", "works in", "works at", "teaches",
"studies", "interested in", "financial", "income", "dietary", "location",
"my home", "my machine",
"name",
"email",
"role",
"lives in",
"located in",
"daw",
"os:",
"shell",
"has a dog",
"has a cat",
"prefers",
"works in",
"works at",
"teaches",
"studies",
"interested in",
"financial",
"income",
"dietary",
"location",
"my home",
"my machine",
];
/// Decisive personal-attribute signals: alone, they mark a fact as about
/// the user — no second signal needed.
pub const USER_ATTR_DECISIVE: &[&str] = &[
"prefers", "lives in", "located in", "teaches", "studies", "works at",
"works in", "interested in", "dietary", "income", "financial",
"prefers",
"lives in",
"located in",
"teaches",
"studies",
"works at",
"works in",
"interested in",
"dietary",
"income",
"financial",
];
pub const PROJECT_SIGNALS: &[&str] = &[
"project", "current work", "task", "todo", "goal", "milestone",
"deadline", "in progress", "blocked", "decision", "spec", "wip",
"building ", "developing ", "course", "channel", "migration", "feature",
"fixing", "working on", "active projects", "current state",
"project",
"current work",
"task",
"todo",
"goal",
"milestone",
"deadline",
"in progress",
"blocked",
"decision",
"spec",
"wip",
"building ",
"developing ",
"course",
"channel",
"migration",
"feature",
"fixing",
"working on",
"active projects",
"current state",
];
/// Directives that grow the constitution.
pub const CONSTITUTION_SIGNALS: &[&str] = &[
"add this to the constitution", "constitution", "always do",
"i will always", "must always", "inviolable",
"add this to the constitution",
"constitution",
"always do",
"i will always",
"must always",
"inviolable",
];
/// Blocks the grader would never route straight to a hot block.
pub const DOC_SIGNALS: &[&str] = &[
"book", "chapter", "document", "transcript", "pdf", "epub", "verbatim",
"diary entry", "palace drawer", "gutenberg",
"book",
"chapter",
"document",
"transcript",
"pdf",
"epub",
"verbatim",
"diary entry",
"palace drawer",
"gutenberg",
];
/// Curated prohibition -> true positive affirmation, in the oracle's
@@ -97,8 +173,14 @@ pub const AFFIRM_DICT: &[(&str, &str)] = &[
),
("never hedge", "I answer directly, without hedging"),
("don't hedge", "I answer directly, without hedging"),
("do not overresearch", "I act on the quick answer when one will do"),
("don't overresearch", "I act on the quick answer when one will do"),
(
"do not overresearch",
"I act on the quick answer when one will do",
),
(
"don't overresearch",
"I act on the quick answer when one will do",
),
(
"do not overclaim",
"I name evidence as evidence and confirmed causes as confirmed",
@@ -210,7 +292,8 @@ fn contains_space_bounded(low: &str, name: &str) -> bool {
while let Some(pos) = low[from..].find(name) {
let i = from + pos;
let after = i + name.len();
let bounded = (i == 0 || bytes[i - 1] == b' ') && (after == low.len() || bytes[after] == b' ');
let bounded =
(i == 0 || bytes[i - 1] == b' ') && (after == low.len() || bytes[after] == b' ');
if bounded {
return true;
}
@@ -226,7 +309,11 @@ fn contains_space_bounded(low: &str, name: &str) -> bool {
/// the pass-through path (Cow borrows `fact` after trim).
pub fn affirm_rule(fact: &str) -> std::borrow::Cow<'_, str> {
let f = fact.trim().trim_start_matches(['-', '*', ' ']).trim();
let low = f.to_lowercase().split_whitespace().collect::<Vec<&str>>().join(" ");
let low = f
.to_lowercase()
.split_whitespace()
.collect::<Vec<&str>>()
.join(" ");
// The oracle's second needle (low.strip(".").replace(" ", " ")) is
// key-independent and can only differ from `low` when edge dots or
// double spaces exist — normalize ONCE under that guard, lazily,
@@ -371,7 +458,10 @@ fn str_field(v: &serde_json::Value, key: &str) -> String {
/// gr_affirm_rule({fact}) -> the (possibly rewritten) rule string.
#[no_mangle]
pub extern "C" fn gr_affirm_rule(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| serde_json::Value::String(affirm_rule(&str_field(&a, "fact")).into_owned())))
envelope(
read_arg(arg)
.map(|a| serde_json::Value::String(affirm_rule(&str_field(&a, "fact")).into_owned())),
)
}
/// gr_grade_fact({fact, target, user_name}) -> the tier name.
@@ -383,14 +473,21 @@ pub extern "C" fn gr_grade_fact(arg: *const std::os::raw::c_char) -> *mut std::o
_ => None,
};
let user_name = str_field(&a, "user_name");
serde_json::Value::String(grade_fact(&str_field(&a, "fact"), target, &user_name).as_str().to_string())
serde_json::Value::String(
grade_fact(&str_field(&a, "fact"), target, &user_name)
.as_str()
.to_string(),
)
}))
}
/// gr_tier_of({grade}) -> the journal label.
#[no_mangle]
pub extern "C" fn gr_tier_of(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| serde_json::Value::String(tier_of(&str_field(&a, "grade")).to_string())))
envelope(
read_arg(arg)
.map(|a| serde_json::Value::String(tier_of(&str_field(&a, "grade")).to_string())),
)
}
#[cfg(test)]
+56 -11
View File
@@ -124,11 +124,19 @@ impl Store {
}
}
fn ename(&self, id: i64) -> &str {
self.entities.iter().find(|e| e.id == id).map(|e| e.name.as_str()).unwrap_or("")
self.entities
.iter()
.find(|e| e.id == id)
.map(|e| e.name.as_str())
.unwrap_or("")
}
fn etype(&self, id: i64) -> &str {
self.entities.iter().find(|e| e.id == id).map(|e| e.etype.as_str()).unwrap_or("")
self.entities
.iter()
.find(|e| e.id == id)
.map(|e| e.etype.as_str())
.unwrap_or("")
}
#[allow(clippy::too_many_arguments)]
@@ -214,14 +222,21 @@ impl Store {
if term.is_empty() {
return None;
}
if let Some(e) = self.entities.iter().find(|e| e.name.to_lowercase() == term.to_lowercase()) {
if let Some(e) = self
.entities
.iter()
.find(|e| e.name.to_lowercase() == term.to_lowercase())
{
return Some(e.id);
}
let lower = term.to_lowercase();
if let Some((eid, _)) = self.aliases.iter().find(|(_, a)| a.to_lowercase() == lower) {
return Some(*eid);
}
self.entities.iter().find(|e| e.name.contains(term)).map(|e| e.id)
self.entities
.iter()
.find(|e| e.name.contains(term))
.map(|e| e.id)
}
fn query(&self, term: &str, hops: i64) -> Value {
@@ -237,7 +252,11 @@ impl Store {
if depth > hops {
break;
}
for e in self.edges.iter().filter(|e| e.subj == cur && e.status == "active") {
for e in self
.edges
.iter()
.filter(|e| e.subj == cur && e.status == "active")
{
facts.push(query_fact(
self.ename(e.subj),
&e.pred,
@@ -256,7 +275,11 @@ impl Store {
}
}
}
for e in self.edges.iter().filter(|e| e.obj == eid && e.status == "active") {
for e in self
.edges
.iter()
.filter(|e| e.obj == eid && e.status == "active")
{
facts.push(query_fact(
self.ename(e.subj),
&e.pred,
@@ -331,7 +354,10 @@ fn gap_truth_replays_through_kernel() {
}
"gm_resolve" => {
let name = s(args.first().unwrap_or(&Value::Null));
let nt = args.get(1).map(|v| s(&v.clone())).unwrap_or_else(|| "concept".to_string());
let nt = args
.get(1)
.map(|v| s(&v.clone()))
.unwrap_or_else(|| "concept".to_string());
match store.resolve(&name, &nt) {
None => Value::Null,
Some(id) => json!({"id": id, "name": store.ename(id), "type": store.etype(id)}),
@@ -379,7 +405,13 @@ fn gap_truth_replays_through_kernel() {
mut o => {
if let Some(facts) = o.get("facts").cloned() {
o["facts"] = Value::Array(
facts.as_array().cloned().unwrap_or_default().into_iter().map(norm_fact).collect(),
facts
.as_array()
.cloned()
.unwrap_or_default()
.into_iter()
.map(norm_fact)
.collect(),
);
}
o
@@ -394,7 +426,13 @@ fn gap_truth_replays_through_kernel() {
if let Some(o) = want.as_object_mut() {
if let Some(facts) = o.get("facts").cloned() {
want["facts"] = Value::Array(
facts.as_array().cloned().unwrap_or_default().into_iter().map(norm_fact).collect(),
facts
.as_array()
.cloned()
.unwrap_or_default()
.into_iter()
.map(norm_fact)
.collect(),
);
}
}
@@ -402,7 +440,11 @@ fn gap_truth_replays_through_kernel() {
// now_iso carries the frozen clock's +00:00 suffix; the kernel
// asserts the truncation SHAPE, not the envelope's clock.
if fn_name == "now_iso" {
assert_eq!(got, json!("2026-10-13T12:00:00"), "now_iso truncation shape");
assert_eq!(
got,
json!("2026-10-13T12:00:00"),
"now_iso truncation shape"
);
n += 1;
continue;
}
@@ -429,7 +471,10 @@ fn gap_truth_replays_through_kernel() {
#[test]
fn kernel_unit_pins() {
// now_iso truncation shapes.
assert_eq!(now_iso("2026-10-13T12:00:00.123456+00:00"), "2026-10-13T12:00:00+00:00");
assert_eq!(
now_iso("2026-10-13T12:00:00.123456+00:00"),
"2026-10-13T12:00:00+00:00"
);
assert_eq!(now_iso("2026-10-13T12:00:00"), "2026-10-13T12:00:00");
// gating boundaries.
assert_eq!(strength_to_conf(Some(&json!(2.9999))), None);
+18 -9
View File
@@ -306,19 +306,23 @@ pub extern "C" fn gm_now_iso(arg: *const c_char) -> *mut c_char {
/// gm_strength_to_conf({strength}) -> {conf, ok}.
#[no_mangle]
pub extern "C" fn gm_strength_to_conf(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| match strength_to_conf(a.get("strength")) {
Some(c) => json!({"conf": c, "ok": true}),
None => json!({"conf": 0.0, "ok": false}),
}))
envelope(
read_arg(arg).map(|a| match strength_to_conf(a.get("strength")) {
Some(c) => json!({"conf": c, "ok": true}),
None => json!({"conf": 0.0, "ok": false}),
}),
)
}
/// gm_normalize_pred({pred}) -> {pred, ok}.
#[no_mangle]
pub extern "C" fn gm_normalize_pred(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| match normalize_pred(&py_str(a.get("pred"))) {
Some(p) => json!({"pred": p, "ok": true}),
None => json!({"pred": "", "ok": false}),
}))
envelope(
read_arg(arg).map(|a| match normalize_pred(&py_str(a.get("pred"))) {
Some(p) => json!({"pred": p, "ok": true}),
None => json!({"pred": "", "ok": false}),
}),
)
}
/// gm_node_type({node_type}) -> effective type.
@@ -373,7 +377,12 @@ pub extern "C" fn gm_stats(arg: *const c_char) -> *mut c_char {
let by_pred: Vec<(String, i64)> = match a.get("by_pred") {
Some(Value::Array(rows)) => rows
.iter()
.map(|r| (py_str(r.get("pred")), r.get("n").and_then(|v| v.as_i64()).unwrap_or(0)))
.map(|r| {
(
py_str(r.get("pred")),
r.get("n").and_then(|v| v.as_i64()).unwrap_or(0),
)
})
.collect(),
_ => Vec::new(),
};
+4 -1
View File
@@ -200,7 +200,10 @@ mod phaseb_units {
fn contradiction_wins_from_every_state() {
for s in ["candidate", "supported", "trial", "promoted", "zzz"] {
let d = json!({"state": s});
let out = advance(&d, &json!({"contradictions": 1, "sightings": 9, "trial_outcomes": 9}));
let out = advance(
&d,
&json!({"contradictions": 1, "sightings": 9, "trial_outcomes": 9}),
);
assert_eq!(out["state"], "rejected", "state {s}");
}
}
+20 -7
View File
@@ -87,7 +87,8 @@ pub fn build_preview(
let env_runtime_implications: Vec<serde_json::Value> = {
let target = closure.get("target").and_then(|t| t.as_str()).unwrap_or("");
model.get(target)
model
.get(target)
.and_then(|e| e.get("deployment_surfaces"))
.and_then(|d| d.as_array())
.cloned()
@@ -131,7 +132,6 @@ pub fn validate_preview(preview: &serde_json::Value) -> Vec<String> {
errs
}
// ── FFI surface (facade burn-down, 2026-10-10) ───────────────────────────────
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
@@ -151,17 +151,30 @@ fn read_arg(ptr: *const c_char) -> Result<String, String> {
#[no_mangle]
pub extern "C" fn ip_preview(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(|t| {
serde_json::from_str::<serde_json::Value>(&t)
.map_err(|e| format!("argument JSON parse failed: {}", e))
})
.and_then(|a| {
let model: std::collections::HashMap<String, HashMap<String, serde_json::Value>> =
serde_json::from_value(a.get("model").cloned().unwrap_or(serde_json::json!({})))
.map_err(|e| format!("arg.model parse: {}", e))?;
let closure = a.get("closure").cloned().ok_or("arg.closure required")?;
let basis = a.get("basis").cloned();
let scope = a.get("contract_scope").and_then(|c| c.as_array()).map(|arr| {
arr.iter().filter_map(|v| v.as_str().map(String::from)).collect::<Vec<String>>()
});
Ok(build_preview(&model, &closure, basis.as_ref(), scope.as_deref()))
let scope = a
.get("contract_scope")
.and_then(|c| c.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect::<Vec<String>>()
});
Ok(build_preview(
&model,
&closure,
basis.as_ref(),
scope.as_deref(),
))
});
let body = match result {
Ok(v) => serde_json::json!({"ok": true, "value": v}),
+5 -3
View File
@@ -9,8 +9,8 @@
//! so key order is insensitive but every value must match exactly.
use crate::{
_now, context_packet, descriptive_impact, invalidate_behaviours,
post_change_obligations, predictive_impact, record_delta_line,
_now, context_packet, descriptive_impact, invalidate_behaviours, post_change_obligations,
predictive_impact, record_delta_line,
};
use evidence_provenance::pyjson::{self, Json};
@@ -110,7 +110,9 @@ fn replay_context_packet() {
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),
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"])),
);
+102 -85
View File
@@ -149,9 +149,7 @@ 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
RelType::of(r) == Some(RelType::BelongsTo) && s(r, "to") == eid && s(r, "from") != eid
})
.map(|r| s(r, "from"))
.collect()
@@ -184,10 +182,7 @@ fn owning_domain(doc: &Json, eid: &str) -> Json {
}
let parents: Vec<String> = relationships(doc)
.into_iter()
.filter(|r| {
RelType::of(r) == Some(RelType::BelongsTo)
&& s(r, "from") == cur
})
.filter(|r| RelType::of(r) == Some(RelType::BelongsTo) && s(r, "from") == cur)
.map(|r| s(r, "to"))
.collect();
match parents.into_iter().next() {
@@ -202,11 +197,7 @@ fn owning_domain(doc: &Json, eid: &str) -> Json {
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)
})
.filter(|r| RelType::of(r) == Some(RelType::DependsOn) && s(r, "from") == eid && !stale(r))
.map(|r| s(r, "to"))
.collect();
v.sort();
@@ -312,10 +303,7 @@ fn impact_closure(doc: &Json, eid: &str) -> Json {
"observation_scope",
obj(vec![
("direct_dependencies", arr(direct_deps)),
(
"interfaces_provided",
arr(interfaces_for(doc, eid)),
),
("interfaces_provided", arr(interfaces_for(doc, eid))),
("interfaces_consumed", arr(consumes_of(doc, eid))),
]),
),
@@ -334,9 +322,7 @@ fn impact_closure(doc: &Json, eid: &str) -> Json {
(
"invariants",
arr(vec![
Json::Str(format!(
"belongs_to hierarchy of {eid} remains stable"
)),
Json::Str(format!("belongs_to hierarchy of {eid} remains stable")),
Json::Str("canonical-state ownership unchanged".into()),
]),
),
@@ -387,10 +373,21 @@ fn ledger_context_packet(ledger: &Json, capability_id: &str) -> Json {
}
};
let fields = [
"capability_id", "name", "purpose", "component",
"source_locations", "contract", "state_touched", "dependencies",
"consumers", "invariants", "behaviour_specs", "maturity",
"constraints", "generation", "limitations",
"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))];
@@ -443,23 +440,15 @@ pub fn context_packet(
})
.count();
let mut files: Vec<String> = Vec::new();
for eid in std::iter::once(target.to_string())
.chain(children(model, target))
{
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"))
{
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"))
{
if let Some(Json::Str(x)) = e.get("identities").and_then(|x| x.get("source")) {
src = x.clone();
}
}
@@ -479,8 +468,7 @@ pub fn context_packet(
}
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 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) {
@@ -493,7 +481,10 @@ pub fn context_packet(
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 uncertainty = closure
.get("uncertainty")
.cloned()
.unwrap_or(Json::Arr(vec![]));
let connected = obj(vec![
("dependencies", arr(dependencies(model, target))),
(
@@ -510,7 +501,10 @@ pub fn context_packet(
"verification",
arr(edges_from(model, target, RelType::VerifiedBy)),
),
("deployment", arr(edges_from(model, target, RelType::DeployedBy))),
(
"deployment",
arr(edges_from(model, target, RelType::DeployedBy)),
),
("uncertainty", uncertainty),
]);
let target_kind = match entity.get("kind") {
@@ -557,16 +551,13 @@ pub fn descriptive_impact(
_ => HashSet::new(),
};
let observed: HashSet<String> = observed_changed.iter().cloned().collect();
let mut predicted_sorted: Vec<String> =
predicted_scope.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();
let mut unexpected: Vec<String> = observed.difference(&predicted_scope).cloned().collect();
unexpected.sort();
let mut unaffected: Vec<String> =
predicted_scope.difference(&observed).cloned().collect();
let mut unaffected: Vec<String> = predicted_scope.difference(&observed).cloned().collect();
unaffected.sort();
obj(vec![
("target", Json::Str(target.to_string())),
@@ -635,8 +626,11 @@ pub fn post_change_obligations(
};
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)>| {
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())),
@@ -745,10 +739,7 @@ pub fn post_change_obligations(
pub(crate) fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
let body = match result {
Ok(value) => obj(vec![
("ok", Json::Bool(true)),
("value", value),
]),
Ok(value) => obj(vec![("ok", Json::Bool(true)), ("value", value)]),
Err((err, ty)) => obj(vec![
("ok", Json::Bool(false)),
("err", Json::Str(err)),
@@ -805,32 +796,43 @@ fn str_list(v: Option<&Json>) -> Vec<String> {
}
}
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_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)> {
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)> {
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");
@@ -840,31 +842,46 @@ ffi!(itr_descriptive_impact, |raw: &str| -> Result<Json, (String, String)> {
_ => String::new(),
};
let now = s(&doc, "now");
Ok(descriptive_impact(&model, &target, &observed, &generation, &now))
Ok(descriptive_impact(
&model,
&target,
&observed,
&generation,
&now,
))
});
ffi!(itr_record_delta_line, |raw: &str| -> Result<Json, (String, String)> {
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_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)> {
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");
+28 -5
View File
@@ -264,17 +264,35 @@ fn contracts_fixture() -> Vec<serde_json::Value> {
fn check_matches_oracle() {
let truth: serde_json::Value = serde_json::from_str(TRUTH).expect("truth parses");
let contracts = contracts_fixture();
for (idx, case) in truth["check"].as_array().expect("check array").iter().enumerate() {
for (idx, case) in truth["check"]
.as_array()
.expect("check array")
.iter()
.enumerate()
{
let got = check_layout(&contracts, &case["observed"]);
assert_eq!(got["verdict"], case["out"]["verdict"], "check case {} verdict", idx);
assert_eq!(got["checks"], case["out"]["checks"], "check case {} checks", idx);
assert_eq!(
got["verdict"], case["out"]["verdict"],
"check case {} verdict",
idx
);
assert_eq!(
got["checks"], case["out"]["checks"],
"check case {} checks",
idx
);
}
}
#[test]
fn drift_matches_oracle() {
let truth: serde_json::Value = serde_json::from_str(TRUTH).expect("truth parses");
for (idx, case) in truth["drift"].as_array().expect("drift array").iter().enumerate() {
for (idx, case) in truth["drift"]
.as_array()
.expect("drift array")
.iter()
.enumerate()
{
// rebuild contracts with the recorded post-status (the DRIFTING case needs it)
let contracts = contracts_fixture();
if let Some(arr) = case.get("post_status").and_then(|v| v.as_array()) {
@@ -285,6 +303,11 @@ fn drift_matches_oracle() {
let got_checks = check_layout(&contracts, &case["observed"]);
let checks = got_checks["checks"].as_array().cloned().unwrap_or_default();
let got = drift_decisions(&contracts, &checks);
assert_eq!(got["drifted_locked"], serde_json::json!(case["out"]["drifted_locked"]), "drift case {} staled", idx);
assert_eq!(
got["drifted_locked"],
serde_json::json!(case["out"]["drifted_locked"]),
"drift case {} staled",
idx
);
}
}
+78 -13
View File
@@ -106,8 +106,17 @@ fn region(rid: &str, evidence: &[&str]) -> Value {
fn branch_evidence_union_is_sorted_dedup() {
let mut doc = doc_with(vec![region("sidebar", &["b", "a"])]);
let e = kernels::define_region(
&mut doc, "sidebar", "", None, "", "", "", &Default::default(),
"scaffold", &[json!("a"), json!("c")], kernels::FROZEN_NOW,
&mut doc,
"sidebar",
"",
None,
"",
"",
"",
&Default::default(),
"scaffold",
&[json!("a"), json!("c")],
kernels::FROZEN_NOW,
)
.expect("re-define ok");
assert_eq!(
@@ -123,11 +132,24 @@ fn branch_maturity_high_water_never_downgrades() {
mature["maturity"] = json!("WIRED");
let mut doc = doc_with(vec![mature]);
let e = kernels::define_region(
&mut doc, "sidebar", "", None, "", "", "", &Default::default(),
"scaffold", &[], kernels::FROZEN_NOW,
&mut doc,
"sidebar",
"",
None,
"",
"",
"",
&Default::default(),
"scaffold",
&[],
kernels::FROZEN_NOW,
)
.expect("re-define ok");
assert_eq!(e["maturity"], json!("WIRED"), "DECLARED never downgrades WIRED");
assert_eq!(
e["maturity"],
json!("WIRED"),
"DECLARED never downgrades WIRED"
);
}
#[test]
@@ -138,8 +160,20 @@ fn branch_region_resolution_follows_aliases() {
r["aliases"] = json!(["region:sidebar-old"]);
let mut doc = doc_with(vec![r]);
let e = kernels::bind_component(
&mut doc, "version-info", "sidebar-old", false, "scaffold", None, "",
"", "", "", "", "", &Default::default(), kernels::FROZEN_NOW,
&mut doc,
"version-info",
"sidebar-old",
false,
"scaffold",
None,
"",
"",
"",
"",
"",
"",
&Default::default(),
kernels::FROZEN_NOW,
)
.expect("alias-following resolution binds");
assert_eq!(e["identities"]["region"], json!("sidebar-old"));
@@ -163,7 +197,14 @@ fn branch_reason_truncation_is_200_codepoints() {
]);
let reason = "é".repeat(250); // multibyte — truncation must count codepoints
let e = kernels::amend_layout_contract(
&mut doc, "x", "", Some(9), false, "builder", &reason, "",
&mut doc,
"x",
"",
Some(9),
false,
"builder",
&reason,
"",
kernels::FROZEN_NOW,
)
.expect("amend ok");
@@ -196,11 +237,27 @@ fn branch_rebind_resets_amendments_ledger() {
}),
]);
let e = kernels::bind_component(
&mut doc, "x", "sidebar", false, "scaffold", None, "", "", "", "",
"", "", &Default::default(), kernels::FROZEN_NOW,
&mut doc,
"x",
"sidebar",
false,
"scaffold",
None,
"",
"",
"",
"",
"",
"",
&Default::default(),
kernels::FROZEN_NOW,
)
.expect("re-bind ok");
assert_eq!(e["ext"]["amendments"], json!([]), "re-bind clears the ledger");
assert_eq!(
e["ext"]["amendments"],
json!([]),
"re-bind clears the ledger"
);
}
#[test]
@@ -223,14 +280,22 @@ fn branch_amend_unlock_rejection_leaves_prior_mutations() {
}),
]);
let err = kernels::amend_layout_contract(
&mut doc, "x", "other", None, true, "builder", "r", "",
&mut doc,
"x",
"other",
None,
true,
"builder",
"r",
"",
kernels::FROZEN_NOW,
)
.expect_err("unlock without user must fail");
assert_eq!(err, "unlock requires user authorization");
let stored = &doc["entities"][2];
assert_eq!(
stored["identities"]["region"], json!("other"),
stored["identities"]["region"],
json!("other"),
"region mutation precedes the unlock rejection (oracle order)"
);
}
+22 -20
View File
@@ -19,7 +19,12 @@ use serde_json::{json, Map, Value};
pub const FROZEN_NOW: &str = "2026-10-14T00:00:00Z";
const MATURITY_ORDER: [&str; 6] = [
"DECLARED", "PRESENT", "WIRED", "REACHABLE", "EXERCISED", "LIVE_PROVEN",
"DECLARED",
"PRESENT",
"WIRED",
"REACHABLE",
"EXERCISED",
"LIVE_PROVEN",
];
fn maturity_rank(state: &str) -> i64 {
@@ -96,10 +101,7 @@ fn add_entity(
.get_mut("entities")
.and_then(|v| v.as_array_mut())
.ok_or_else(|| "KeyError: 'entities'".to_string())?;
if let Some(idx) = ents
.iter()
.position(|e| as_str(e.get("id")) == id)
{
if let Some(idx) = ents.iter().position(|e| as_str(e.get("id")) == id) {
let e = &mut ents[idx];
let current = as_str(e.get("maturity"));
if !maturity.is_empty() && maturity_rank(maturity) > maturity_rank(current) {
@@ -219,9 +221,7 @@ pub fn bind_component(
format!("ui:{component}")
};
if entity_index(doc, &format!("region:{region_id}")).is_none() {
return Err(format!(
"unknown region: {region_id} (define_region first)"
));
return Err(format!("unknown region: {region_id} (define_region first)"));
}
if lock && provenance != "user" {
return Err("lock=True requires user provenance — a scaffold/builder/model can never self-issue a lock".into());
@@ -235,7 +235,10 @@ pub fn bind_component(
identities.insert("sizing".into(), json!(sizing));
identities.insert("visibility".into(), json!(visibility));
identities.insert("implementation".into(), json!(implementation));
identities.insert("framework_attrs".into(), Value::Object(framework_attrs.clone()));
identities.insert(
"framework_attrs".into(),
Value::Object(framework_attrs.clone()),
);
if let Some(o) = order {
identities.insert("order".into(), json!(o));
}
@@ -310,9 +313,7 @@ pub fn amend_layout_contract(
}
}
}
if !new_region.is_empty()
&& entity_index(doc, &format!("region:{new_region}")).is_none()
{
if !new_region.is_empty() && entity_index(doc, &format!("region:{new_region}")).is_none() {
return Err(format!("unknown region: {new_region}"));
}
let mut changes: Map<String, Value> = Map::new();
@@ -403,12 +404,7 @@ pub fn dispatch(case: &Value) -> Value {
.to_string()
};
let korder = |k: &str| kwargs.get(k).and_then(|v| v.as_i64());
let kbool = |k: &str| {
kwargs
.get(k)
.and_then(|v| v.as_bool())
.unwrap_or(false)
};
let kbool = |k: &str| kwargs.get(k).and_then(|v| v.as_bool()).unwrap_or(false);
let kmap = |k: &str| {
kwargs
.get(k)
@@ -435,8 +431,14 @@ pub fn dispatch(case: &Value) -> Value {
.filter(|(k, _)| {
!matches!(
k.as_str(),
"lock" | "provenance" | "order" | "parent_of" | "anchor"
| "sizing" | "visibility" | "evidence_ref"
"lock"
| "provenance"
| "order"
| "parent_of"
| "anchor"
| "sizing"
| "visibility"
| "evidence_ref"
| "implementation"
)
})
+54 -22
View File
@@ -49,8 +49,14 @@ pub fn check_layout(contracts: &[Value], observed: &Value) -> Value {
Some(m) => m,
None => continue,
};
let cid = ids.get("component").map(|v| s(Some(v)).to_string()).unwrap_or_default();
let required_region = ids.get("region").map(|v| s(Some(v)).to_string()).unwrap_or_default();
let cid = ids
.get("component")
.map(|v| s(Some(v)).to_string())
.unwrap_or_default();
let required_region = ids
.get("region")
.map(|v| s(Some(v)).to_string())
.unwrap_or_default();
let locked = bool_of(ids.get("lock"));
let required_order = ids.get("order").and_then(|v| v.as_i64());
@@ -73,7 +79,10 @@ pub fn check_layout(contracts: &[Value], observed: &Value) -> Value {
let mut problems: Vec<String> = vec![];
let got_region = s(o.get("region"));
if got_region != required_region {
problems.push(format!("region '{}' != required '{}'", got_region, required_region));
problems.push(format!(
"region '{}' != required '{}'",
got_region, required_region
));
}
if let Some(req_order) = required_order {
if let Some(got_order) = o.get("order").and_then(|v| v.as_i64()) {
@@ -115,7 +124,9 @@ pub fn drift_decisions(contracts: &[Value], checks: &[Value]) -> Value {
let comp = s(d.get("component")).to_string();
let is_locked = bool_of(d.get("locked"));
let has_entity = contracts.iter().any(|e| {
idents(e).map(|m| m.get("component").map(|v| s(Some(v))) == Some(&comp)).unwrap_or(false)
idents(e)
.map(|m| m.get("component").map(|v| s(Some(v))) == Some(&comp))
.unwrap_or(false)
});
if !has_entity {
continue;
@@ -129,7 +140,10 @@ pub fn drift_decisions(contracts: &[Value], checks: &[Value]) -> Value {
if s(e.get("kind")) != "layout_contract" || s(e.get("status")) != "DRIFTING" {
continue;
}
let comp = idents(e).and_then(|m| m.get("component")).map(|v| s(Some(v)).to_string()).unwrap_or_default();
let comp = idents(e)
.and_then(|m| m.get("component"))
.map(|v| s(Some(v)).to_string())
.unwrap_or_default();
let mine = checks.iter().find(|c| s(c.get("component")) == comp);
if let Some(m) = mine {
if s(m.get("verdict")) == "PASS" {
@@ -144,7 +158,9 @@ fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
@@ -170,7 +186,10 @@ fn arr(v: Option<&Value>) -> Vec<Value> {
pub extern "C" fn lc_check(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let contracts = arr(a.get("contracts"));
let observed = a.get("observed").cloned().unwrap_or(Value::Object(Default::default()));
let observed = a
.get("observed")
.cloned()
.unwrap_or(Value::Object(Default::default()));
check_layout(&contracts, &observed)
});
envelope(r)
@@ -192,10 +211,7 @@ pub extern "C" fn lc_drift(arg: *const c_char) -> *mut c_char {
// {"doc": updated_doc, "entity"|"observations": ...}.
fn a_str(a: &Value, k: &str, d: &str) -> String {
a.get(k)
.and_then(|v| v.as_str())
.unwrap_or(d)
.to_string()
a.get(k).and_then(|v| v.as_str()).unwrap_or(d).to_string()
}
fn a_order(a: &Value, k: &str) -> Option<i64> {
@@ -225,7 +241,11 @@ fn a_refs(a: &Value, k: &str) -> Vec<Value> {
pub extern "C" fn lc_define_region(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().ok_or("arg.doc required")?;
let region_id = a.get("region_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let region_id = a
.get("region_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let now = a_str(&a, "now", kernels::FROZEN_NOW);
let e = kernels::define_region(
&mut doc,
@@ -250,8 +270,16 @@ pub extern "C" fn lc_define_region(arg: *const c_char) -> *mut c_char {
pub extern "C" fn lc_bind_component(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().ok_or("arg.doc required")?;
let component = a.get("component").and_then(|v| v.as_str()).unwrap_or("").to_string();
let region_id = a.get("region_id").and_then(|v| v.as_str()).unwrap_or("").to_string();
let component = a
.get("component")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let region_id = a
.get("region_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let now = a_str(&a, "now", kernels::FROZEN_NOW);
let e = kernels::bind_component(
&mut doc,
@@ -279,7 +307,11 @@ pub extern "C" fn lc_bind_component(arg: *const c_char) -> *mut c_char {
pub extern "C" fn lc_amend_layout_contract(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().ok_or("arg.doc required")?;
let component = a.get("component").and_then(|v| v.as_str()).unwrap_or("").to_string();
let component = a
.get("component")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let now = a_str(&a, "now", kernels::FROZEN_NOW);
let e = kernels::amend_layout_contract(
&mut doc,
@@ -302,14 +334,14 @@ pub extern "C" fn lc_amend_layout_contract(arg: *const c_char) -> *mut c_char {
pub extern "C" fn lc_record_observation(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().ok_or("arg.doc required")?;
let adapter = a.get("adapter").and_then(|v| v.as_str()).unwrap_or("").to_string();
let adapter = a
.get("adapter")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let now = a_str(&a, "now", kernels::FROZEN_NOW);
let out = kernels::record_layout_observation(
&mut doc,
&adapter,
&a_map(&a, "observed"),
&now,
)?;
let out =
kernels::record_layout_observation(&mut doc, &adapter, &a_map(&a, "observed"), &now)?;
Ok(json!({"doc": doc, "observations": out}))
});
envelope(r)
@@ -45,9 +45,10 @@ fn replay(group: &str, f: impl Fn(&Value) -> Value) {
#[test]
fn content_words_parity_battery() {
replay("content_words", |i| {
json!({"words": content_words(i["text"].as_str())})
});
replay(
"content_words",
|i| json!({"words": content_words(i["text"].as_str())}),
);
}
#[test]
@@ -56,7 +57,11 @@ fn distinctive_terms_parity_battery() {
let fact = i["fact"].as_str().unwrap_or("");
let corpus: Vec<String> = i["corpus_texts"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default();
let terms: Vec<String> = distinctive_terms(fact, &corpus).into_iter().collect();
json!({"terms": terms})
@@ -69,13 +74,18 @@ fn shared_distinctive_parity_battery() {
let fact = i["fact"].as_str().unwrap_or("");
let strs = |k: &str| -> Vec<String> {
i[k].as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default()
};
let existing = strs("existing_values");
let corpus = strs("corpus_texts");
let shared: Vec<String> =
shared_distinctive(fact, &existing, &corpus).into_iter().collect();
let shared: Vec<String> = shared_distinctive(fact, &existing, &corpus)
.into_iter()
.collect();
json!({"shared": shared})
});
}
@@ -85,7 +95,11 @@ fn coherence_parity_battery() {
replay("coherence", |i| {
let strs = |k: &str| -> Vec<String> {
i[k].as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default()
};
let verdict = coherence(
@@ -102,7 +116,11 @@ fn grounded_parity_battery() {
replay("grounded", |i| {
let sources: Vec<String> = i["source_texts"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default();
let min = i.get("min_distinct").and_then(Value::as_f64).unwrap_or(1.0);
json!({"grounded": grounded(i["fact"].as_str().unwrap_or(""), &sources, min)})
@@ -111,9 +129,10 @@ fn grounded_parity_battery() {
#[test]
fn stem_parity_battery() {
replay("stem", |i| {
json!({"stem": stem(i["word"].as_str().unwrap_or(""))})
});
replay(
"stem",
|i| json!({"stem": stem(i["word"].as_str().unwrap_or(""))}),
);
}
#[test]
@@ -121,11 +140,19 @@ fn promote_eligible_parity_battery() {
replay("promote_eligible", |i| {
let existing: Vec<String> = i["existing"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default();
let corpus: Vec<String> = i["corpus_texts"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or("").to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or("").to_string())
.collect()
})
.unwrap_or_default();
let strength = i.get("evidence_strength").cloned().unwrap_or(json!(0));
let min = i.get("min_strength").cloned().unwrap_or(json!(3));
+68 -35
View File
@@ -82,30 +82,64 @@ use std::sync::OnceLock;
// SAME stopword set as memory_store._STOPWORDS (single source of truth
// would be ideal; keep in sync — see memory_store.py).
pub const STOP: &[&str] = &[
"the", "and", "for", "with", "that", "this", "from", "into", "when",
"then", "were", "have", "been", "will", "was", "are", "but", "not",
"you", "your", "also", "its", "his", "her", "him", "over", "under",
"them", "they", "there", "about", "after", "what", "which", "who",
"how", "why", "where", "while", "just", "more", "each", "than", "then",
"very", "such", "some", "only", "prefers", "needs", "wants", "likes",
"the", "and", "for", "with", "that", "this", "from", "into", "when", "then", "were", "have",
"been", "will", "was", "are", "but", "not", "you", "your", "also", "its", "his", "her", "him",
"over", "under", "them", "they", "there", "about", "after", "what", "which", "who", "how",
"why", "where", "while", "just", "more", "each", "than", "then", "very", "such", "some",
"only", "prefers", "needs", "wants", "likes",
];
// Hard stop for evolution: these generic framing words never carry a
// relation ("work", "always", "way") — a persona built on them would be a
// coincidence.
pub const FRAMING: &[&str] = &[
"work", "always", "way", "thing", "value", "identity", "persona",
"someone", "something", "never", "always", "before", "after", "then",
"when", "while", "must", "should", "will", "would", "could", "can",
"have", "has", "had", "been", "being", "used", "use", "uses", "make",
"makes", "made", "keep", "keeps", "take", "takes", "took", "get",
"gets", "got", "going",
"work",
"always",
"way",
"thing",
"value",
"identity",
"persona",
"someone",
"something",
"never",
"always",
"before",
"after",
"then",
"when",
"while",
"must",
"should",
"will",
"would",
"could",
"can",
"have",
"has",
"had",
"been",
"being",
"used",
"use",
"uses",
"make",
"makes",
"made",
"keep",
"keeps",
"take",
"takes",
"took",
"get",
"gets",
"got",
"going",
];
pub const NEGATION: &[&str] = &[
"not", "never", "don't", "doesn't", "cannot", "can't", "no", "nor",
"neither", "without", "avoid", "reject", "against", "refuses",
"refuse", "opposed",
"not", "never", "don't", "doesn't", "cannot", "can't", "no", "nor", "neither", "without",
"avoid", "reject", "against", "refuses", "refuse", "opposed",
];
// same rare-word threshold as recall/associations
@@ -218,16 +252,14 @@ pub fn stem(w: &str) -> String {
/// The df table for the corpus: stem → number of documents containing it
/// (stem-aware: migrate/migration count as one term).
fn doc_frequencies(corpus_texts: &[String]) -> HashMap<String, u64> {
corpus_texts
.iter()
.fold(HashMap::new(), |mut acc, t| {
let low = t.to_lowercase();
let stems: HashSet<String> = scan_words(&low).map(stem).collect();
for s in stems {
*acc.entry(s).or_insert(0) += 1;
}
acc
})
corpus_texts.iter().fold(HashMap::new(), |mut acc, t| {
let low = t.to_lowercase();
let stems: HashSet<String> = scan_words(&low).map(stem).collect();
for s in stems {
*acc.entry(s).or_insert(0) += 1;
}
acc
})
}
/// `distinctive_terms` — the terms in `fact` that are RARE across the
@@ -367,7 +399,11 @@ pub fn grounded(fact: &str, source_texts: &[impl AsRef<str>], min_distinct: f64)
if srcs.is_empty() {
return false;
}
let joined = srcs.iter().map(|s| s.to_lowercase()).collect::<Vec<_>>().join(" ");
let joined = srcs
.iter()
.map(|s| s.to_lowercase())
.collect::<Vec<_>>()
.join(" ");
let stems: HashSet<String> = scan_words(&joined).map(stem).collect();
let low = fact.to_lowercase();
let fact_terms: BTreeSet<&str> = content_word_refs(&low).collect();
@@ -447,11 +483,8 @@ pub fn promote_eligible(
let shared = shared_distinctive_of(proposal, &existing, &corpus);
if !shared.is_empty() {
let top: Vec<String> = shared.iter().take(3).map(|s| format!("'{}'", s)).collect();
return Promotion::Eligible(format!(
"shares distinctive term(s): [{}]",
top.join(", ")
))
.into_pair();
return Promotion::Eligible(format!("shares distinctive term(s): [{}]", top.join(", ")))
.into_pair();
}
// No distinctive shared term: a NOVEL axis is eligible only if it is
// GROUNDED in the absorbed corpus via a distinctive term (a real
@@ -555,8 +588,9 @@ pub extern "C" fn le_shared_distinctive(arg: *const c_char) -> *mut c_char {
let fact = as_text(a.get("fact")).unwrap_or("");
let existing = str_list(&a, "existing_values");
let corpus = str_list(&a, "corpus_texts");
let shared: Vec<String> =
shared_distinctive(fact, &existing, &corpus).into_iter().collect();
let shared: Vec<String> = shared_distinctive(fact, &existing, &corpus)
.into_iter()
.collect();
json!({"shared": shared})
}))
}
@@ -602,8 +636,7 @@ pub extern "C" fn le_promote_eligible(arg: *const c_char) -> *mut c_char {
let corpus = str_list(&a, "corpus_texts");
let strength = a.get("evidence_strength").cloned().unwrap_or(json!(0));
let min = a.get("min_strength").cloned().unwrap_or(json!(3));
let (eligible, reason) =
promote_eligible(proposal, &existing, &strength, &min, &corpus);
let (eligible, reason) = promote_eligible(proposal, &existing, &strength, &min, &corpus);
json!({"eligible": eligible, "reason": reason})
}))
}
@@ -23,10 +23,7 @@ fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
let mut cur = v;
for k in path {
cur = match cur {
Json::Obj(pairs) => pairs
.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, x)| x)?,
Json::Obj(pairs) => pairs.iter().find(|(k2, _)| k2 == k).map(|(_, x)| x)?,
_ => return None,
};
}
+78 -35
View File
@@ -49,14 +49,54 @@ pub struct Trigger {
/// The eight trigger classes, TABLE ORDER (the fire_due listing law).
pub const TRIGGERS: [Trigger; 8] = [
Trigger { name: "unresolved_commitments", condition_key: "has_open_commitments", rate_limit_s: 3600.0, stop_key: "commitments_resolved" },
Trigger { name: "approaching_deadlines", condition_key: "deadline_within_24h", rate_limit_s: 1800.0, stop_key: "deadline_passed_or_met" },
Trigger { name: "repeated_failure", condition_key: "same_failure_3x", rate_limit_s: 900.0, stop_key: "failure_pattern_broken" },
Trigger { name: "contradictions", condition_key: "contradiction_detected", rate_limit_s: 1800.0, stop_key: "contradiction_resolved" },
Trigger { name: "curiosity_candidates", condition_key: "novel_material_present", rate_limit_s: 7200.0, stop_key: "material_absorbed" },
Trigger { name: "prediction_mismatches", condition_key: "prediction_wrong", rate_limit_s: 1800.0, stop_key: "model_updated" },
Trigger { name: "task_blockers", condition_key: "task_blocked", rate_limit_s: 600.0, stop_key: "task_unblocked" },
Trigger { name: "owner_corrections", condition_key: "correction_received", rate_limit_s: 300.0, stop_key: "correction_absorbed" },
Trigger {
name: "unresolved_commitments",
condition_key: "has_open_commitments",
rate_limit_s: 3600.0,
stop_key: "commitments_resolved",
},
Trigger {
name: "approaching_deadlines",
condition_key: "deadline_within_24h",
rate_limit_s: 1800.0,
stop_key: "deadline_passed_or_met",
},
Trigger {
name: "repeated_failure",
condition_key: "same_failure_3x",
rate_limit_s: 900.0,
stop_key: "failure_pattern_broken",
},
Trigger {
name: "contradictions",
condition_key: "contradiction_detected",
rate_limit_s: 1800.0,
stop_key: "contradiction_resolved",
},
Trigger {
name: "curiosity_candidates",
condition_key: "novel_material_present",
rate_limit_s: 7200.0,
stop_key: "material_absorbed",
},
Trigger {
name: "prediction_mismatches",
condition_key: "prediction_wrong",
rate_limit_s: 1800.0,
stop_key: "model_updated",
},
Trigger {
name: "task_blockers",
condition_key: "task_blocked",
rate_limit_s: 600.0,
stop_key: "task_unblocked",
},
Trigger {
name: "owner_corrections",
condition_key: "correction_received",
rate_limit_s: 300.0,
stop_key: "correction_absorbed",
},
];
fn find(trigger: &str) -> Option<&'static Trigger> {
@@ -186,11 +226,9 @@ fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
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(),
Err(_) => CString::new(r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#)
.unwrap()
.into_raw(),
}
}
@@ -228,23 +266,25 @@ 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));
let out: Result<Json, (String, String)> = read_arg(arg).and_then(|raw| $body(&raw));
envelope(out)
}
};
}
ffi!(me_should_fire, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let trigger = match doc.get("trigger") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("trigger must be a string".into(), "TypeError".into())),
};
let state = state_of(&doc)?.clone();
let now = now_of(&doc)?;
Ok(should_fire(&trigger, &state, now))
});
ffi!(
me_should_fire,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let trigger = match doc.get("trigger") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("trigger must be a string".into(), "TypeError".into())),
};
let state = state_of(&doc)?.clone();
let now = now_of(&doc)?;
Ok(should_fire(&trigger, &state, now))
}
);
ffi!(me_fire_due, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
@@ -253,16 +293,19 @@ ffi!(me_fire_due, |raw: &str| -> Result<Json, (String, String)> {
Ok(fire_due(&state, now))
});
ffi!(me_mark_fired, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let state = state_of(&doc)?.clone();
let trigger = match doc.get("trigger") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("trigger must be a string".into(), "TypeError".into())),
};
let now = now_of(&doc)?;
Ok(mark_fired(&state, &trigger, now))
});
ffi!(
me_mark_fired,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let state = state_of(&doc)?.clone();
let trigger = match doc.get("trigger") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("trigger must be a string".into(), "TypeError".into())),
};
let now = now_of(&doc)?;
Ok(mark_fired(&state, &trigger, now))
}
);
#[cfg(test)]
mod branch_battery;
+3 -1
View File
@@ -83,7 +83,9 @@ fn main() {
eprintln!("ms_ipc: ms_{op} returned null");
std::process::exit(1);
}
let s = unsafe { CStr::from_ptr(ret) }.to_string_lossy().into_owned();
let s = unsafe { CStr::from_ptr(ret) }
.to_string_lossy()
.into_owned();
memory_scoring::ms_free_string(ret);
// Wrap the exact FFI envelope string as a JSON string: the Go side
@@ -5,8 +5,8 @@
//! the per-source decision math at its input boundary.
use crate::{
abstraction_decision, add_source, consolidated_decision, graphs_decision,
neurons_decision, persona_wanted, select_core, store_project,
abstraction_decision, add_source, consolidated_decision, graphs_decision, neurons_decision,
persona_wanted, select_core, store_project,
};
use serde_json::Value;
@@ -1802,8 +1802,7 @@ mod gap_battery {
#[test]
fn add_source_rows_match_frozen_oracle() {
let rows: Vec<Value> =
serde_json::from_str(GAP_TRUTH).expect("gap truth parses");
let rows: Vec<Value> = serde_json::from_str(GAP_TRUTH).expect("gap truth parses");
let mut replayed = 0;
for row in &rows {
if row["fn"].as_str() != Some("ms_add_source") {
@@ -1830,8 +1829,7 @@ mod gap_battery {
// contract row exists and says exactly that, so a facade that
// ignored the pin would fail the live-parity gate, not silently
// pass.
let rows: Vec<Value> =
serde_json::from_str(GAP_TRUTH).expect("gap truth parses");
let rows: Vec<Value> = serde_json::from_str(GAP_TRUTH).expect("gap truth parses");
let row = rows
.iter()
.find(|r| r["fn"].as_str() == Some("_mse_core"))
+19 -3
View File
@@ -213,7 +213,11 @@ pub fn neurons_decision(neurons: &[Value], query: &str, budget: i64, min_score:
let text = py_str(neuron.get("text"));
let score = lex_overlap_score(&text, query);
let slug = py_str(neuron.get("slug"));
let slug = if slug.is_empty() { "warm".to_string() } else { slug };
let slug = if slug.is_empty() {
"warm".to_string()
} else {
slug
};
Some((format!("[{slug}] {text}"), score))
});
Value::Array(floored_rows(
@@ -242,7 +246,13 @@ pub fn graphs_decision(
}
(text, score)
});
Value::Array(floored_rows(pairs, "graph", "graph_memory.query", budget, min_score))
Value::Array(floored_rows(
pairs,
"graph",
"graph_memory.query",
budget,
min_score,
))
}
/// _abstraction_recall's post-IO decision math: LIKE matches are only
@@ -253,7 +263,13 @@ pub fn abstraction_decision(whats: &[Value], query: &str, budget: i64, min_score
let score = lex_overlap_score(&text, query);
(text, score)
});
Value::Array(floored_rows(pairs, "abstraction", "abstraction facts", budget, min_score))
Value::Array(floored_rows(
pairs,
"abstraction",
"abstraction facts",
budget,
min_score,
))
}
/// _consolidated_recall's post-IO decision math: knowledge competes under the
+1 -5
View File
@@ -28,11 +28,7 @@ fn frozen_truth_replays_verbatim() {
let want_keys: Vec<&String> = want.keys().collect();
assert_eq!(got_keys, want_keys, "case {name}: key order");
for (k, v) in want {
assert_eq!(
got[k].as_i64(),
v.as_i64(),
"case {name}: share {k}"
);
assert_eq!(got[k].as_i64(), v.as_i64(), "case {name}: share {k}");
}
}
"type_for_request" => {
+15 -6
View File
@@ -59,7 +59,10 @@ pub fn allocate_budgets(total_tokens: i64) -> Map<String, Value> {
used += share;
}
// remainder absorbs rounding
out.insert(TYPES[TYPES.len() - 1].to_string(), json!(total_tokens - used));
out.insert(
TYPES[TYPES.len() - 1].to_string(),
json!(total_tokens - used),
);
out
}
@@ -71,7 +74,16 @@ const ROUTE_TABLE: [(&[&str], &str); 5] = [
EPISODIC,
),
(
&["how to", "how do", "skill", "procedure", "steps", "deploy", "build", "run"],
&[
"how to",
"how do",
"skill",
"procedure",
"steps",
"deploy",
"build",
"run",
],
PROCEDURAL,
),
(&["who am i", "identity", "values", "persona"], SELF),
@@ -159,10 +171,7 @@ mod phaseb_units {
#[test]
fn shares_sum_to_one() {
let total: f64 = budget_shares()
.values()
.filter_map(Value::as_f64)
.sum();
let total: f64 = budget_shares().values().filter_map(Value::as_f64).sum();
assert!((total - 1.0).abs() < 1e-9);
}
+34 -13
View File
@@ -47,12 +47,7 @@ fn check_err(case_name: &str, want: &Value, got: &crate::MgErr) -> Result<(), St
Ok(())
}
fn expect_field(
case_name: &str,
field: &str,
got: &Value,
want: &Value,
) -> Result<(), String> {
fn expect_field(case_name: &str, field: &str, got: &Value, want: &Value) -> Result<(), String> {
if got != want {
return Err(format!(
"[{case_name}] {field} mismatch:\n got: {got}\n want: {want}"
@@ -159,26 +154,43 @@ fn replay(group: &str, c: &Value) -> Result<(), String> {
}
"lineage" => {
let g = graph_from_wire(input).map_err(|e| format!("[{case_name}] {e:?}"))?;
let got = Value::Array(g.lineage(input["node_id"].as_str()).into_iter().cloned().collect());
let got = Value::Array(
g.lineage(input["node_id"].as_str())
.into_iter()
.cloned()
.collect(),
);
expect_field(&case_name, "result", &got, &expected["result"])
}
"history" => {
let g = graph_from_wire(input).map_err(|e| format!("[{case_name}] {e:?}"))?;
let got = Value::Array(
g.history(input["node_id"].as_str().unwrap_or_default()).into_iter().cloned().collect(),
g.history(input["node_id"].as_str().unwrap_or_default())
.into_iter()
.cloned()
.collect(),
);
expect_field(&case_name, "result", &got, &expected["result"])
}
"migrate_flat" => {
let text = input.get("text").and_then(Value::as_str);
let transcript: Vec<Value> = input["transcript"].as_array().cloned().unwrap_or_default();
let transcript: Vec<Value> =
input["transcript"].as_array().cloned().unwrap_or_default();
let ids: Vec<String> = input["ids"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or_default().to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or_default().to_string())
.collect()
})
.unwrap_or_default();
let tss: Vec<String> = input["tss"]
.as_array()
.map(|a| a.iter().map(|v| v.as_str().unwrap_or_default().to_string()).collect())
.map(|a| {
a.iter()
.map(|v| v.as_str().unwrap_or_default().to_string())
.collect()
})
.unwrap_or_default();
match MessageGraph::migrate_flat(text, &transcript, &ids, &tss) {
Err(e) => check_err(&case_name, c, &e),
@@ -284,8 +296,17 @@ fn battery_probe() {
#[test]
fn battery_total_is_68() {
let total: usize = [
"load", "save", "add", "tool_call", "tool_result", "revise", "branch",
"lineage", "history", "migrate_flat", "cue",
"load",
"save",
"add",
"tool_call",
"tool_result",
"revise",
"branch",
"lineage",
"history",
"migrate_flat",
"cue",
]
.iter()
.map(|g| run_group(g))
+39 -34
View File
@@ -155,10 +155,16 @@ pub struct MgErr {
impl MgErr {
fn value(msg: impl Into<String>) -> Self {
MgErr { kind: ErrKind::ValueError, message: msg.into() }
MgErr {
kind: ErrKind::ValueError,
message: msg.into(),
}
}
fn key(msg: impl Into<String>) -> Self {
MgErr { kind: ErrKind::KeyError, message: msg.into() }
MgErr {
kind: ErrKind::KeyError,
message: msg.into(),
}
}
}
@@ -192,10 +198,7 @@ impl MessageGraph {
let Some(obj) = doc.as_object() else {
return Err(MgErr {
kind: ErrKind::AttributeError,
message: format!(
"'{}' object has no attribute 'get'",
py_type(&doc)
),
message: format!("'{}' object has no attribute 'get'", py_type(&doc)),
});
};
if !schema_is_one(obj.get("schema").unwrap_or(&Value::Null)) {
@@ -268,8 +271,8 @@ impl MessageGraph {
) -> Result<Value, MgErr> {
let kind =
Kind::parse(kind).ok_or_else(|| MgErr::value(format!("unknown kind '{kind}'")))?;
let edge = EdgeType::parse(edge)
.ok_or_else(|| MgErr::value(format!("unknown edge '{edge}'")))?;
let edge =
EdgeType::parse(edge).ok_or_else(|| MgErr::value(format!("unknown edge '{edge}'")))?;
let parent_value = match parent {
Some(p) => json!(p),
None => match self.order.last() {
@@ -355,7 +358,9 @@ impl MessageGraph {
Some(old) => match old.get("role") {
Some(r) => (
r.clone(),
old.get("parent").and_then(Value::as_str).map(str::to_string),
old.get("parent")
.and_then(Value::as_str)
.map(str::to_string),
),
None => return Err(MgErr::key("'role'")),
},
@@ -380,12 +385,7 @@ impl MessageGraph {
}
/// Start a new conversation branch off any message.
pub fn branch(
&mut self,
node_id: &str,
head_id: &str,
ts: &str,
) -> Result<Value, MgErr> {
pub fn branch(&mut self, node_id: &str, head_id: &str, ts: &str) -> Result<Value, MgErr> {
if !self.nodes.contains_key(node_id) {
return Err(MgErr::key(format!("no such message '{node_id}'")));
}
@@ -451,9 +451,7 @@ impl MessageGraph {
.map(|edges| {
edges
.iter()
.filter(|e| {
e.get("type").and_then(Value::as_str) == Some("supersedes")
})
.filter(|e| e.get("type").and_then(Value::as_str) == Some("supersedes"))
.filter_map(|e| e.get("to").and_then(Value::as_str))
.collect()
})
@@ -586,14 +584,14 @@ fn py_type(v: &Value) -> &'static str {
/// The oracle's `doc.get("schema") != SCHEMA` gate: Python equality —
/// 1.0 == 1 and True == 1 both pass.
fn schema_is_one(v: &Value) -> bool {
v.as_i64() == Some(SCHEMA)
|| v.as_f64() == Some(SCHEMA as f64)
|| v.as_bool() == Some(true)
v.as_i64() == Some(SCHEMA) || v.as_f64() == Some(SCHEMA as f64) || v.as_bool() == Some(true)
}
/// Graph state from the wire: {"nodes": {id: node}, "order": [ids]}.
fn graph_from_wire(arg: &Value) -> Result<MessageGraph, MgErr> {
let g = arg.get("graph").ok_or_else(|| arg_err("arg.graph required"))?;
let g = arg
.get("graph")
.ok_or_else(|| arg_err("arg.graph required"))?;
let nodes = g
.get("nodes")
.and_then(Value::as_object)
@@ -659,19 +657,23 @@ fn envelope(r: Result<Value, MgErr>) -> *mut c_char {
};
match CString::new(serde_json::to_string(&v).unwrap_or_default()) {
Ok(s) => s.into_raw(),
Err(_) => CString::new("{\"ok\":false,\"err\":\"envelope nul\"}").unwrap().into_raw(),
Err(_) => CString::new("{\"ok\":false,\"err\":\"envelope nul\"}")
.unwrap()
.into_raw(),
}
}
fn arg_err(e: impl Into<String>) -> MgErr {
MgErr { kind: ErrKind::ValueError, message: e.into() }
MgErr {
kind: ErrKind::ValueError,
message: e.into(),
}
}
#[no_mangle]
pub extern "C" fn mg_load(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
MessageGraph::load(opt_str(&a, "text")).map(|g| g.to_wire())
});
let r =
read_arg(arg).and_then(|a| MessageGraph::load(opt_str(&a, "text")).map(|g| g.to_wire()));
envelope(r)
}
@@ -765,7 +767,10 @@ pub extern "C" fn mg_lineage(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let g = graph_from_wire(&a)?;
Ok(Value::Array(
g.lineage(opt_str(&a, "node_id")).into_iter().cloned().collect(),
g.lineage(opt_str(&a, "node_id"))
.into_iter()
.cloned()
.collect(),
))
});
envelope(r)
@@ -776,7 +781,10 @@ pub extern "C" fn mg_history(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let g = graph_from_wire(&a)?;
Ok(Value::Array(
g.history(require_str(&a, "node_id")?).into_iter().cloned().collect(),
g.history(require_str(&a, "node_id")?)
.into_iter()
.cloned()
.collect(),
))
});
envelope(r)
@@ -812,11 +820,8 @@ pub extern "C" fn mg_migrate_flat(arg: *const c_char) -> *mut c_char {
#[no_mangle]
pub extern "C" fn mg_cue(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
Value::String(MessageGraph::cue(
a.get("node").unwrap_or(&Value::Null),
))
});
let r = read_arg(arg)
.map(|a| Value::String(MessageGraph::cue(a.get("node").unwrap_or(&Value::Null))));
envelope(r)
}
@@ -51,7 +51,8 @@ fn replays_frozen_oracle_truth_verbatim() {
let got = step(path_str, op);
let want = scenario["obs"][i]["result"].clone();
assert_eq!(
got, want,
got,
want,
"{name} op#{i} ({}) diverged from the frozen oracle",
op["op"].as_str().unwrap_or("?")
);
@@ -70,5 +71,8 @@ fn unknown_op_is_an_err_envelope_not_a_panic() {
let out = unsafe { std::ffi::CStr::from_ptr(ptr) }.to_str().unwrap();
let env: Value = serde_json::from_str(out).unwrap();
assert_eq!(env["ok"], Value::Bool(false));
assert!(env["err"].as_str().unwrap().contains("arg.op must be one of"));
assert!(env["err"]
.as_str()
.unwrap()
.contains("arg.op must be one of"));
}
+12 -4
View File
@@ -217,9 +217,12 @@ impl Where<'_> {
/// OutcomeLedger._query_stats — identical aggregate SQL to the oracle.
pub fn query_stats(db_path: &str, wc: Where) -> Result<Value, String> {
let conn = Conn::open(db_path)?;
let sql = format!("{STATS_SQL_HEAD}
let sql = format!(
"{STATS_SQL_HEAD}
{}
", wc.sql());
",
wc.sql()
);
let mut stmt = conn.prepare(&sql)?;
if let Some(p) = wc.param() {
stmt.bind_text(1, p)?;
@@ -339,7 +342,9 @@ impl<'a> Args<'a> {
}
fn record(&self) -> Result<RecordView<'a>, String> {
let v = self.record.ok_or_else(|| "arg.record required".to_string())?;
let v = self
.record
.ok_or_else(|| "arg.record required".to_string())?;
RecordView::from_value(v).ok_or_else(|| "arg.record invalid".to_string())
}
}
@@ -381,7 +386,10 @@ pub extern "C" fn ol_run(arg: *const std::os::raw::c_char) -> *mut std::os::raw:
.get("op")
.and_then(Value::as_str)
.and_then(Op::parse)
.ok_or_else(|| "arg.op must be one of init_db|record|get_stats|get_model_stats|to_dict".to_string())?;
.ok_or_else(|| {
"arg.op must be one of init_db|record|get_stats|get_model_stats|to_dict"
.to_string()
})?;
let args = Args::from_value(&doc).ok_or_else(|| "arg.db_path required".to_string())?;
dispatch(op, &args)
});
+16 -3
View File
@@ -65,7 +65,12 @@ impl Conn {
let cpath = CString::new(path).map_err(|e| e.to_string())?;
let mut db: *mut c_void = std::ptr::null_mut();
let rc = unsafe {
sqlite3_open_v2(cpath.as_ptr(), &mut db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, std::ptr::null())
sqlite3_open_v2(
cpath.as_ptr(),
&mut db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
std::ptr::null(),
)
};
if rc != SQLITE_OK {
return Err(format!("sqlite3_open_v2 failed (rc={rc}) for {path}"));
@@ -129,7 +134,13 @@ impl Stmt {
pub fn bind_text(&mut self, idx: usize, val: &str) -> Result<(), String> {
let cval = CString::new(val).map_err(|e| e.to_string())?;
let rc = unsafe {
sqlite3_bind_text(self.0, idx as c_int, cval.as_ptr(), val.len() as c_int, SQLITE_TRANSIENT)
sqlite3_bind_text(
self.0,
idx as c_int,
cval.as_ptr(),
val.len() as c_int,
SQLITE_TRANSIENT,
)
};
if rc != SQLITE_OK {
Err(format!("sqlite3_bind_text failed (rc={rc})"))
@@ -198,7 +209,9 @@ impl Stmt {
if ptr.is_null() {
String::new()
} else {
CStr::from_ptr(ptr.cast::<c_char>()).to_string_lossy().into_owned()
CStr::from_ptr(ptr.cast::<c_char>())
.to_string_lossy()
.into_owned()
}
}
}
+2 -1
View File
@@ -25,7 +25,8 @@ fn frozen_truth_replays_verbatim() {
let expect = &c["expect"];
match c["fn"].as_str().expect("fn tag") {
"apply_outcome" => {
let got = crate::apply_outcome(f(args, "utility"), args["outcome"].as_str().unwrap());
let got =
crate::apply_outcome(f(args, "utility"), args["outcome"].as_str().unwrap());
assert_eq!(Some(got), expect.as_f64(), "case {name}");
}
"apply_stale" => {
+4 -1
View File
@@ -92,7 +92,10 @@ pub fn record_verified_outcome(
store.insert(key.clone(), json!({"utility": 0.0, "confidence": 0.5}));
store.get_mut(&key).unwrap()
};
let util = current.get("utility").and_then(Value::as_f64).unwrap_or(0.0);
let util = current
.get("utility")
.and_then(Value::as_f64)
.unwrap_or(0.0);
let new_util = apply_outcome(util, outcome);
current["utility"] = json!(new_util);
let confidence = store[&key]
@@ -23,10 +23,7 @@ fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
let mut cur = v;
for k in path {
cur = match cur {
Json::Obj(pairs) => pairs
.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, x)| x)?,
Json::Obj(pairs) => pairs.iter().find(|(k2, _)| k2 == k).map(|(_, x)| x)?,
_ => return None,
};
}
+32 -25
View File
@@ -88,9 +88,10 @@ pub fn classify_utterance(text: Option<&str>) -> Json {
UtteranceKind::Amendment => {
obj(vec![("kind", s("amendment")), ("priority", Json::Num(2.0))])
}
UtteranceKind::Discussion => {
obj(vec![("kind", s("discussion")), ("priority", Json::Num(1.0))])
}
UtteranceKind::Discussion => obj(vec![
("kind", s("discussion")),
("priority", Json::Num(1.0)),
]),
}
}
@@ -191,11 +192,9 @@ fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
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(),
Err(_) => CString::new(r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#)
.unwrap()
.into_raw(),
}
}
@@ -218,7 +217,10 @@ fn opt_text(doc: &Json, key: &str) -> Result<Option<String>, (String, String)> {
None => Err((format!("{key} is required"), "KeyError".into())),
Some(Json::Null) => Ok(None),
Some(Json::Str(t)) => Ok(Some(t.clone())),
Some(_) => Err((format!("{key} must be a string or null"), "TypeError".into())),
Some(_) => Err((
format!("{key} must be a string or null"),
"TypeError".into(),
)),
}
}
@@ -226,32 +228,37 @@ 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));
let out: Result<Json, (String, String)> = read_arg(arg).and_then(|raw| $body(&raw));
envelope(out)
}
};
}
ffi!(pa_classify_utterance, |raw: &str| -> Result<Json, (String, String)> {
ffi!(pa_classify_utterance, |raw: &str| -> Result<
Json,
(String, String),
> {
let doc = parse_doc(raw)?;
let text = opt_text(&doc, "text")?;
Ok(classify_utterance(text.as_deref()))
});
ffi!(pa_apply_to_task, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let pane = match doc.get("pane") {
Some(Json::Str(p)) => p.clone(),
_ => return Err(("pane must be a string".into(), "TypeError".into())),
};
let task = match doc.get("task") {
Some(t @ Json::Obj(_)) => t.clone(),
_ => return Err(("task must be an object".into(), "TypeError".into())),
};
let utterance = opt_text(&doc, "utterance")?;
Ok(apply_to_task(&pane, &task, utterance.as_deref()))
});
ffi!(
pa_apply_to_task,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let pane = match doc.get("pane") {
Some(Json::Str(p)) => p.clone(),
_ => return Err(("pane must be a string".into(), "TypeError".into())),
};
let task = match doc.get("task") {
Some(t @ Json::Obj(_)) => t.clone(),
_ => return Err(("task must be an object".into(), "TypeError".into())),
};
let utterance = opt_text(&doc, "utterance")?;
Ok(apply_to_task(&pane, &task, utterance.as_deref()))
}
);
#[cfg(test)]
mod branch_battery;
+29 -20
View File
@@ -61,9 +61,7 @@ fn replay_result(group: &str, f: impl Fn(&Value) -> Result<Value, crate::Registr
#[test]
fn default_doc_parity_battery() {
replay("default_doc", |i| {
json!({"doc": default_doc(s(i, "now"))})
});
replay("default_doc", |i| json!({"doc": default_doc(s(i, "now"))}));
}
#[test]
@@ -88,24 +86,31 @@ fn register_parity_battery() {
#[test]
fn get_parity_battery() {
replay("get", |i| {
json!({"entry": get(&doc_of(i), s(i, "project_id")).unwrap()})
});
replay(
"get",
|i| json!({"entry": get(&doc_of(i), s(i, "project_id")).unwrap()}),
);
}
#[test]
fn all_projects_parity_battery() {
replay("all_projects", |i| {
json!({"projects": all_projects(&doc_of(i)).unwrap()})
});
replay(
"all_projects",
|i| json!({"projects": all_projects(&doc_of(i)).unwrap()}),
);
}
#[test]
fn set_health_parity_battery() {
replay_result("set_health", |i| {
set_health(&doc_of(i), s(i, "project_id"), s(i, "health"), s(i, "note"),
s(i, "now"))
.map(|doc| json!({"doc": doc}))
set_health(
&doc_of(i),
s(i, "project_id"),
s(i, "health"),
s(i, "note"),
s(i, "now"),
)
.map(|doc| json!({"doc": doc}))
});
}
@@ -120,25 +125,29 @@ fn set_outstanding_parity_battery() {
#[test]
fn mark_generation_parity_battery() {
replay_result("mark_generation", |i| {
mark_generation(&doc_of(i), s(i, "project_id"), s(i, "current"),
s(i, "last_known_good"))
.map(|doc| json!({"doc": doc}))
mark_generation(
&doc_of(i),
s(i, "project_id"),
s(i, "current"),
s(i, "last_known_good"),
)
.map(|doc| json!({"doc": doc}))
});
}
#[test]
fn resolve_cwd_parity_battery() {
replay_result("resolve_cwd", |i| {
resolve_cwd(&doc_of(i), s(i, "project_id"))
.map(|cwd| json!({"cwd": cwd}))
resolve_cwd(&doc_of(i), s(i, "project_id")).map(|cwd| json!({"cwd": cwd}))
});
}
#[test]
fn needs_migration_parity_battery() {
replay("needs_migration", |i| {
json!({"ids": needs_migration(&doc_of(i), i["proposed"].as_i64().unwrap()).unwrap()})
});
replay(
"needs_migration",
|i| json!({"ids": needs_migration(&doc_of(i), i["proposed"].as_i64().unwrap()).unwrap()}),
);
}
#[test]
+24 -15
View File
@@ -302,8 +302,7 @@ pub fn set_health(
.and_then(Value::as_object_mut)
.ok_or_else(|| RegistryError::Key("scaffold_state".to_string()))?;
state.insert("health".to_string(), json!(lattice.as_str()));
state
.insert("last_material_update".to_string(), json!(now));
state.insert("last_material_update".to_string(), json!(now));
if !note.is_empty() {
let notes = state
.entry("notes".to_string())
@@ -379,10 +378,7 @@ impl CwdResolution {
/// unresolved/refuse. Registry stores repo_identity as "git:<path>" or a
/// bare path. Scaffold storage is NEVER used as a repo-cwd proxy. Raises
/// for unknown projects (refuse mutation rather than guessing).
pub fn resolve_cwd(
doc: &Map<String, Value>,
project_id: &str,
) -> Result<String, RegistryError> {
pub fn resolve_cwd(doc: &Map<String, Value>, project_id: &str) -> Result<String, RegistryError> {
let entry = projects_of(doc)?
.get(project_id)
.ok_or_else(|| unknown_project(project_id))?;
@@ -414,7 +410,10 @@ pub fn resolve_cwd(
/// `needs_migration` — projects whose structural schema is older than a
/// proposed version (feeds the 18I consent sweep — never silent
/// upgrades). Entries default to schema version 1.
pub fn needs_migration(doc: &Map<String, Value>, proposed: i64) -> Result<Vec<String>, RegistryError> {
pub fn needs_migration(
doc: &Map<String, Value>,
proposed: i64,
) -> Result<Vec<String>, RegistryError> {
let mut ids: Vec<String> = projects_of(doc)?
.iter()
.filter(|(_, p)| SchemaVersion::of(p).older_than(proposed))
@@ -494,8 +493,7 @@ pub extern "C" fn pr_register(arg: *const c_char) -> *mut c_char {
s("repo_identity"),
s("forge_repo"),
s("default_branch"),
a.get("structural_schema_version")
.unwrap_or(&Value::Null),
a.get("structural_schema_version").unwrap_or(&Value::Null),
s("runtime_generation"),
s("last_known_good"),
s("now"),
@@ -531,9 +529,15 @@ pub extern "C" fn pr_all_projects(arg: *const c_char) -> *mut c_char {
pub extern "C" fn pr_set_health(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let s = |k: &str| a.get(k).and_then(Value::as_str).unwrap_or("");
set_health(&doc_of(&a), s("project_id"), s("health"), s("note"), s("now"))
.map(|doc| json!({"doc": doc}))
.map_err(FfiError::Registry)
set_health(
&doc_of(&a),
s("project_id"),
s("health"),
s("note"),
s("now"),
)
.map(|doc| json!({"doc": doc}))
.map_err(FfiError::Registry)
}))
}
@@ -554,9 +558,14 @@ pub extern "C" fn pr_set_outstanding(arg: *const c_char) -> *mut c_char {
pub extern "C" fn pr_mark_generation(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let s = |k: &str| a.get(k).and_then(Value::as_str).unwrap_or("");
mark_generation(&doc_of(&a), s("project_id"), s("current"), s("last_known_good"))
.map(|doc| json!({"doc": doc}))
.map_err(FfiError::Registry)
mark_generation(
&doc_of(&a),
s("project_id"),
s("current"),
s("last_known_good"),
)
.map(|doc| json!({"doc": doc}))
.map_err(FfiError::Registry)
}))
}
+5 -2
View File
@@ -3,7 +3,7 @@
//! store fixtures). Every case must reproduce byte-identically through the
//! Rust kernel.
use super::{_fetch_corpus, pyjson, expand, Json};
use super::{_fetch_corpus, expand, pyjson, Json};
const TRUTH: &str = include_str!("../frozen_ru_truth.json");
@@ -47,7 +47,10 @@ fn frozen_truth_replays_verbatim() {
_ => None,
};
let got = Json::Arr(expand(query, corpus).into_iter().map(Json::Str).collect());
assert_eq!(got, expected, "case {i} ({query:?}) diverged from the oracle");
assert_eq!(
got, expected,
"case {i} ({query:?}) diverged from the oracle"
);
}
}
+82 -28
View File
@@ -39,32 +39,86 @@ pub const MAX_COOCCUR_WORDS: usize = 40;
/// The expansion map, in oracle dict-literal order (iteration order is
/// observable: terms are added map-order first).
const EXPANSIONS: &[(&str, &[&str])] = &[
("housing", &["housing", "rent", "rent burden", "eviction", "affordable housing", "homelessness", "landlord", "mortgage"]),
("homeless", &["homelessness", "homeless", "housing", "shelter", "rent"]),
("memory", &["memory store", "recall", "retrieval", "embedding", "sqlite-vec"]),
("recall", &["recall", "retrieval", "memory store", "abstention", "floor"]),
("router", &["routing", "adaptive router", "route authority", "RouteDecision"]),
("route", &["routing", "adaptive router", "route authority", "escalation"]),
("research", &["research pipeline", "epistemic stage", "OUTWARD", "SYNTHESIS"]),
(
"housing",
&[
"housing",
"rent",
"rent burden",
"eviction",
"affordable housing",
"homelessness",
"landlord",
"mortgage",
],
),
(
"homeless",
&["homelessness", "homeless", "housing", "shelter", "rent"],
),
(
"memory",
&[
"memory store",
"recall",
"retrieval",
"embedding",
"sqlite-vec",
],
),
(
"recall",
&["recall", "retrieval", "memory store", "abstention", "floor"],
),
(
"router",
&[
"routing",
"adaptive router",
"route authority",
"RouteDecision",
],
),
(
"route",
&[
"routing",
"adaptive router",
"route authority",
"escalation",
],
),
(
"research",
&[
"research pipeline",
"epistemic stage",
"OUTWARD",
"SYNTHESIS",
],
),
("test", &["unit test", "pytest", "regression", "suite"]),
("ci", &["CI", "gitea", "pipeline", "required checks"]),
("model", &["model routing", "provider", "ollama", "local model"]),
("migration", &["schema migration", "PLAN", "old-schema", "compat shim"]),
(
"model",
&["model routing", "provider", "ollama", "local model"],
),
(
"migration",
&["schema migration", "PLAN", "old-schema", "compat shim"],
),
("gitea", &["gitea", "issue", "pull request", "CI"]),
];
/// The oracle's stopword set (insertion is a set — membership only).
const STOPWORDS: &[&str] = &[
"the", "a", "an", "and", "or", "of", "to", "in", "on", "for", "with",
"is", "are", "was", "were", "be", "been", "it", "its", "this", "that",
"as", "at", "by", "from", "has", "have", "had", "not", "no", "but",
"if", "then", "than", "so", "such", "can", "could", "should", "would",
"will", "shall", "may", "might", "must", "do", "does", "did", "done",
"you", "your", "i", "me", "my", "we", "our", "they", "their", "them",
"he", "she", "his", "her", "what", "which", "who", "whom", "when",
"where", "why", "how", "all", "any", "both", "each", "few", "more",
"most", "other", "some", "only", "own", "same", "too", "very", "just",
"also",
"the", "a", "an", "and", "or", "of", "to", "in", "on", "for", "with", "is", "are", "was",
"were", "be", "been", "it", "its", "this", "that", "as", "at", "by", "from", "has", "have",
"had", "not", "no", "but", "if", "then", "than", "so", "such", "can", "could", "should",
"would", "will", "shall", "may", "might", "must", "do", "does", "did", "done", "you", "your",
"i", "me", "my", "we", "our", "they", "their", "them", "he", "she", "his", "her", "what",
"which", "who", "whom", "when", "where", "why", "how", "all", "any", "both", "each", "few",
"more", "most", "other", "some", "only", "own", "same", "too", "very", "just", "also",
];
fn is_stopword(w: &str) -> bool {
@@ -93,7 +147,13 @@ fn findall_runs(s: &str, min_len: usize) -> Vec<String> {
/// ASCII fold (sqlite LIKE default folds ASCII only).
fn ascii_lower(s: &str) -> String {
s.chars()
.map(|c| if c.is_ascii_uppercase() { (c as u8 + 32) as char } else { c })
.map(|c| {
if c.is_ascii_uppercase() {
(c as u8 + 32) as char
} else {
c
}
})
.collect()
}
@@ -218,10 +278,7 @@ pub fn expand(query: &str, corpus: Option<&[Json]>) -> Vec<String> {
}
}
let originals: Vec<String> = tokens
.into_iter()
.filter(|t| !seen.contains(t))
.collect();
let originals: Vec<String> = tokens.into_iter().filter(|t| !seen.contains(t)).collect();
originals.into_iter().chain(added).collect()
}
@@ -239,10 +296,7 @@ fn read_arg(ptr: *const c_char) -> Result<Json, String> {
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),
]),
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)),
@@ -1,6 +1,6 @@
//! branch_battery — replays frozen_rb_truth.json VERBATIM.
use super::{pyjson, new_budget, spend, stop, Json};
use super::{new_budget, pyjson, spend, stop, Json};
const TRUTH: &str = include_str!("../frozen_rb_truth.json");
+1 -4
View File
@@ -135,10 +135,7 @@ pub fn stop(budget: &Json, reason: &str) -> Json {
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),
]),
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)),
+1 -4
View File
@@ -24,10 +24,7 @@ fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
let mut cur = v;
for k in path {
cur = match cur {
Json::Obj(pairs) => pairs
.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, x)| x)?,
Json::Obj(pairs) => pairs.iter().find(|(k2, _)| k2 == k).map(|(_, x)| x)?,
_ => return None,
};
}
+37 -34
View File
@@ -41,7 +41,10 @@ pub enum StatsShape {
}
/// profile_function / profile_model_call stats law (shared body).
pub fn profile_stats(times: &[f64], iterations: usize, shape: StatsShape
pub fn profile_stats(
times: &[f64],
iterations: usize,
shape: StatsShape,
) -> Result<Json, (String, String)> {
if times.is_empty() {
// the oracle raises min() on the empty list before any stats
@@ -95,11 +98,9 @@ fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
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(),
Err(_) => CString::new(r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#)
.unwrap()
.into_raw(),
}
}
@@ -121,39 +122,41 @@ 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));
let out: Result<Json, (String, String)> = read_arg(arg).and_then(|raw| $body(&raw));
envelope(out)
}
};
}
ffi!(rp_profile_stats, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let times: Vec<f64> = match doc.get("times") {
Some(Json::Arr(items)) => items
.iter()
.filter_map(|x| match x {
// elapsed-ms values are Python floats — json.dumps emits
// them with a decimal point, which pyjson parses as
// Json::Float; the int form (Num) rides for int inputs
Json::Num(n) | Json::Float(n) => Some(*n),
_ => None,
})
.collect(),
_ => Vec::new(),
};
let iterations = match doc.get("iterations") {
Some(Json::Num(n)) => *n as usize,
_ => times.len(),
};
let shape = if matches!(doc.get("model_call"), Some(Json::Bool(true))) {
StatsShape::ModelCall
} else {
StatsShape::Function
};
profile_stats(&times, iterations, shape)
});
ffi!(
rp_profile_stats,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let times: Vec<f64> = match doc.get("times") {
Some(Json::Arr(items)) => items
.iter()
.filter_map(|x| match x {
// elapsed-ms values are Python floats — json.dumps emits
// them with a decimal point, which pyjson parses as
// Json::Float; the int form (Num) rides for int inputs
Json::Num(n) | Json::Float(n) => Some(*n),
_ => None,
})
.collect(),
_ => Vec::new(),
};
let iterations = match doc.get("iterations") {
Some(Json::Num(n)) => *n as usize,
_ => times.len(),
};
let shape = if matches!(doc.get("model_call"), Some(Json::Bool(true))) {
StatsShape::ModelCall
} else {
StatsShape::Function
};
profile_stats(&times, iterations, shape)
}
);
#[cfg(test)]
mod branch_battery;
+8 -2
View File
@@ -32,13 +32,19 @@ fn get_str<'a>(v: &'a Json, key: &str) -> Option<&'a str> {
fn main() {
let mut input = String::new();
if std::io::stdin().read_to_string(&mut input).is_err() {
emit(Err(("failed to read stdin".to_string(), "OSError".to_string())));
emit(Err((
"failed to read stdin".to_string(),
"OSError".to_string(),
)));
return;
}
let parsed = match pyjson::parse(&input) {
Ok(p) => p,
Err(e) => {
emit(Err((format!("stdin JSON parse failed: {e}"), "ValueError".to_string())));
emit(Err((
format!("stdin JSON parse failed: {e}"),
"ValueError".to_string(),
)));
return;
}
};
@@ -60,10 +60,7 @@ fn replay(case: &Json) {
.unwrap_or("");
assert_eq!(err, msg, "case {}/{} error text", fname, name);
}
(other, _) => panic!(
"case {}/{}: unexpected outcome: {:?}",
fname, name, other
),
(other, _) => panic!("case {}/{}: unexpected outcome: {:?}", fname, name, other),
}
}
@@ -237,4 +234,4 @@ mod truth_battery {
let case = parse(TRUTH_CASES[25]);
replay(&case);
}
}
}
+3 -11
View File
@@ -75,9 +75,7 @@ fn push_indent(out: &mut String, depth: usize) {
// ── sf_sorted({"v": ...}) -> envelope(normalized) ───────────────────────────
pub fn sorted_fn(arg: &Json) -> Result<Json, String> {
let v = arg
.get("v")
.ok_or_else(|| "arg.v required".to_string())?;
let v = arg.get("v").ok_or_else(|| "arg.v required".to_string())?;
Ok(store::sorted_deep(v))
}
@@ -151,16 +149,10 @@ pub fn declared_surfaces(arg: &Json) -> Result<Json, String> {
]));
}
for surface in str_list(u.get("go_surfaces")) {
go_surfaces.push(Json::Arr(vec![
Json::Str(surface),
Json::Str(name.clone()),
]));
go_surfaces.push(Json::Arr(vec![Json::Str(surface), Json::Str(name.clone())]));
}
for cfg in str_list(u.get("configs")) {
configs.push(Json::Arr(vec![
Json::Str(cfg),
Json::Str(name.clone()),
]));
configs.push(Json::Arr(vec![Json::Str(cfg), Json::Str(name.clone())]));
}
}
}
+4 -2
View File
@@ -46,10 +46,12 @@ fn split_name(id: &str) -> &str {
/// carries the oracle's EXACT ValueError text (parity battery pins it).
pub fn ingest_umbrellas(doc: &mut Json, manifest: &Json, now: &str) -> Result<i64, String> {
if obj_get(manifest, GENERATED_VIEW_MARKER).is_some() {
return Err("the manifest is a Spine-generated compatibility view; refusing to \
return Err(
"the manifest is a Spine-generated compatibility view; refusing to \
ingest it (Phase A reads the manual authority exactly once). \
Structural membership now lives in the Spine store."
.to_string());
.to_string(),
);
}
let known = [
"description",
+2 -3
View File
@@ -3,7 +3,7 @@
//! deep-copied). Every case must reproduce byte-identically through the
//! Rust kernel.
use super::{pyjson, compile_self_model, estimate_tokens, why_question, Json};
use super::{compile_self_model, estimate_tokens, pyjson, why_question, Json};
const TRUTH: &str = include_str!("../frozen_sm_truth.json");
@@ -57,8 +57,7 @@ fn frozen_truth_replays_verbatim() {
};
let (gs, es) = (pyjson::dumps(&got), pyjson::dumps(&expected));
assert_eq!(
gs,
es,
gs, es,
"case {i} ({fname}) diverged:\n got: {gs}\n want: {es}"
);
}
+1 -4
View File
@@ -149,10 +149,7 @@ fn contains_ci(text: &str, topic: &str) -> bool {
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),
]),
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)),
+29 -26
View File
@@ -18,8 +18,7 @@
//! an error (envelope law — the loader re-raises).
use crate::{
compare_baseline, monitor, propose_change, promote_or_discard, rollback, run_gates,
FfiError,
compare_baseline, monitor, promote_or_discard, propose_change, rollback, run_gates, FfiError,
};
use serde_json::{json, Value};
use std::fs;
@@ -47,10 +46,7 @@ fn cases(group: &str) -> &[Value] {
// ── fixture + setup interpreter (same recipe as the generator) ───────────
fn battery_root(group: &str) -> PathBuf {
let root = std::env::temp_dir().join(format!(
"smod-battery-{}-{group}",
std::process::id()
));
let root = std::env::temp_dir().join(format!("smod-battery-{}-{group}", std::process::id()));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).expect("battery root");
root
@@ -104,8 +100,17 @@ fn run_setup(repo: &Path, steps: &[Value]) -> Option<PathBuf> {
match op {
"worktree" => {
let path = repo.parent().unwrap().join(format!("wt-{arg}"));
let (ok, _, err) =
git(repo, &["worktree", "add", &path.to_string_lossy(), "-b", arg, "master"]);
let (ok, _, err) = git(
repo,
&[
"worktree",
"add",
&path.to_string_lossy(),
"-b",
arg,
"master",
],
);
assert!(ok, "worktree add failed: {err}");
wt = Some(path);
}
@@ -186,11 +191,9 @@ fn call_kernel(group: &str, input: &Value) -> Result<Value, FfiError> {
&input["candidate_result"],
&input["baseline_result"],
)),
"promote_or_discard" => promote_or_discard(
s("repo"),
&input["proposal"],
&input["verdict"],
),
"promote_or_discard" => {
promote_or_discard(s("repo"), &input["proposal"], &input["verdict"])
}
"rollback" => rollback(s("repo"), s("rollback_ref")),
"monitor" => Ok(monitor(&input["proposal"])),
other => panic!("unknown kernel group: {other}"),
@@ -203,8 +206,11 @@ fn replay_case(group: &str, case: &Value) {
let case_root = root.join(name);
fs::create_dir_all(&case_root).expect("case root");
let repo = make_fixture(&case_root);
let wt = run_setup(&repo, case["setup"].as_array().map(Vec::as_slice).unwrap_or(&[]))
.unwrap_or_else(|| case_root.join("wt-input"));
let wt = run_setup(
&repo,
case["setup"].as_array().map(Vec::as_slice).unwrap_or(&[]),
)
.unwrap_or_else(|| case_root.join("wt-input"));
let stub = if case["stub"].as_bool().unwrap_or(false) {
make_stub_dir(&case_root)
} else {
@@ -232,12 +238,14 @@ fn replay_case(group: &str, case: &Value) {
return;
}
let got = got.unwrap_or_else(|e| {
panic!("[{group}] {name}: kernel raised {e}, expected {:?}", expected)
panic!(
"[{group}] {name}: kernel raised {e}, expected {:?}",
expected
)
});
let got = normalize(&got, &repo, &wt, &stub);
assert_eq!(
&got,
expected,
&got, expected,
"[{group}] {name} diverged (frozen truth, kernel-level)"
);
}
@@ -253,9 +261,8 @@ impl PathGuard {
std::env::set_var(
"PATH",
std::env::join_paths(
std::iter::once(stub.to_path_buf()).chain(
std::env::split_paths(old.as_deref().unwrap_or_default()),
),
std::iter::once(stub.to_path_buf())
.chain(std::env::split_paths(old.as_deref().unwrap_or_default())),
)
.expect("joined PATH"),
);
@@ -421,9 +428,5 @@ fn phaseb_chars_tail_is_char_boundary_safe() {
assert!(tail.ends_with("αβγδε"));
let short = "short";
assert_eq!(chars_tail(short, 2000), short);
assert_eq!(
chars_tail("", 2000),
"",
"empty gate output stays empty"
);
assert_eq!(chars_tail("", 2000), "", "empty gate output stays empty");
}
+19 -33
View File
@@ -121,18 +121,9 @@ impl BaselineVerdict {
/// as None by the oracle — only a MISSING key raises KeyError).
#[derive(Debug)]
enum ProposalOutcome {
Promoted {
branch: String,
rollback_ref: Value,
},
MergeFailed {
error: String,
rollback_ref: Value,
},
Discarded {
branch: String,
rollback_ref: Value,
},
Promoted { branch: String, rollback_ref: Value },
MergeFailed { error: String, rollback_ref: Value },
Discarded { branch: String, rollback_ref: Value },
}
impl ProposalOutcome {
@@ -176,7 +167,10 @@ pub enum FfiError {
/// pipe read / wait failed (the oracle's decode errors propagate too).
Io(std::io::Error),
/// subprocess.run(timeout=…) — TimeoutExpired propagates in the oracle.
Timeout { cmd: String, secs: u64 },
Timeout {
cmd: String,
secs: u64,
},
/// dict[missing-key] — the oracle's KeyError.
KeyError(String),
/// a non-string reached the argv seam — the oracle's TypeError.
@@ -192,7 +186,11 @@ impl fmt::Display for FfiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FfiError::Spawn(e) => {
write!(f, "[Errno {errno}] {e}", errno = e.raw_os_error().unwrap_or(0))
write!(
f,
"[Errno {errno}] {e}",
errno = e.raw_os_error().unwrap_or(0)
)
}
FfiError::Io(e) => write!(f, "{e}"),
FfiError::Timeout { cmd, secs } => {
@@ -264,9 +262,7 @@ fn _run(cmd: &[&str], cwd: &str, timeout_secs: u64) -> Result<(i32, String, Stri
Ok((status.code().unwrap_or(-1), uni(out), uni(err)))
}
fn join_reader(
handle: thread::JoinHandle<Result<String, FfiError>>,
) -> Result<String, FfiError> {
fn join_reader(handle: thread::JoinHandle<Result<String, FfiError>>) -> Result<String, FfiError> {
match handle.join() {
Ok(r) => r,
Err(_) => Err(FfiError::Io(std::io::Error::other(
@@ -317,10 +313,7 @@ fn mkdtemp_selfmod() -> Result<String, FfiError> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(
&path,
std::fs::Permissions::from_mode(0o700),
);
let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700));
}
return Ok(path.to_string_lossy().into_owned());
}
@@ -359,12 +352,7 @@ fn random_byte() -> u8 {
/// `propose_change` — create the isolated worktree + branch. Returns the
/// proposal record (with rollback_ref = the base HEAD — the recovery path).
pub fn propose_change(
repo: &str,
branch: &str,
base: &str,
now: &str,
) -> Result<Value, FfiError> {
pub fn propose_change(repo: &str, branch: &str, base: &str, now: &str) -> Result<Value, FfiError> {
let (rc, out, _err) = _run(&["git", "rev-parse", base], repo, RUN_TIMEOUT_SECS)?;
if rc != 0 {
return Ok(json!({"proposed": false, "error": format!("unknown base: {base}")}));
@@ -611,12 +599,10 @@ pub extern "C" fn smod_promote_or_discard(arg: *const c_char) -> *mut c_char {
/// smod_rollback({repo, rollback_ref}) → result dict
#[no_mangle]
pub extern "C" fn smod_rollback(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
rollback(
required_str(&a, "repo")?,
required_str(&a, "rollback_ref")?,
)
}))
envelope(
read_arg(arg)
.and_then(|a| rollback(required_str(&a, "repo")?, required_str(&a, "rollback_ref")?)),
)
}
/// smod_monitor({proposal}) → watch record
+107 -29
View File
@@ -21,17 +21,23 @@ extern "C" fn mock_ops_cb(op_ptr: *const c_char) -> *const c_char {
let op = pyjson::parse(op_json).unwrap();
let next = OPS.with(|q| {
let mut q = q.borrow_mut();
assert!(!q.is_empty(), "kernel issued an op the oracle never did: {}", op_json);
assert!(
!q.is_empty(),
"kernel issued an op the oracle never did: {}",
op_json
);
q.remove(0)
});
assert_eq!(
op.get("op"), next.get("op"),
op.get("op"),
next.get("op"),
"op order divergence: kernel issued {}, truth expects {:?}",
op_json,
next.get("op")
);
assert_eq!(
op.get("args"), next.get("args"),
op.get("args"),
next.get("args"),
"op args divergence on op {:?}: kernel {}, truth {:?}",
next.get("op"),
op_json,
@@ -85,22 +91,48 @@ fn setup_case(case: &Json, root: &std::path::Path) {
fn run_kernel(case: &Json) -> Json {
let inp = case.get("input").unwrap();
let index = inp.get("index").cloned().unwrap_or(Json::Obj(Vec::new()));
let fn_name = case.get("fn").and_then(|f| match f {
Json::Str(s) => Some(s.as_str()),
_ => None,
}).unwrap();
let fn_name = case
.get("fn")
.and_then(|f| match f {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap();
match fn_name {
"_load_index" => load_index(
inp.get("index_path").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
).unwrap(),
inp.get("index_path")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
)
.unwrap(),
"__init__" => {
let idx = init(
inp.get("shard_dir").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("index_path").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
).unwrap();
inp.get("shard_dir")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
inp.get("index_path")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
)
.unwrap();
let dir_exists = std::path::Path::new(
inp.get("shard_dir").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
).is_dir();
inp.get("shard_dir")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
)
.is_dir();
Json::Obj(vec![
("index".into(), idx),
("dir_exists".into(), Json::Bool(dir_exists)),
@@ -108,30 +140,67 @@ fn run_kernel(case: &Json) -> Json {
}
"create_shard" => {
let (path, idx) = create_shard(
inp.get("shard_dir").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("index_path").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("topic").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("shard_dir")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
inp.get("index_path")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
inp.get("topic")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
&index,
mock_ops_cb,
).unwrap();
)
.unwrap();
Json::Obj(vec![
("shard_path".into(), Json::Str(path)),
("index".into(), idx),
])
}
"route_query" => Json::Str(route_query(
inp.get("query").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("query")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
&index,
)),
"merge_results" => merge_results(inp.get("results_list").unwrap()),
"get_shard" => {
let (path, idx) = get_shard(
inp.get("shard_dir").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("index_path").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("topic").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
inp.get("shard_dir")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
inp.get("index_path")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
inp.get("topic")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
&index,
mock_ops_cb,
).unwrap();
)
.unwrap();
Json::Obj(vec![
("shard_path".into(), Json::Str(path)),
("index".into(), idx),
@@ -149,15 +218,19 @@ fn assert_case(case: &Json, result: &Json) {
Some(Json::Str(s)) => s.as_str(),
_ => "?",
};
let fn_name = case.get("fn").and_then(|f| match f {
Json::Str(s) => Some(s.as_str()),
_ => None,
}).unwrap();
let fn_name = case
.get("fn")
.and_then(|f| match f {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap();
let expected = case.get("expected").unwrap();
match fn_name {
"create_shard" | "get_shard" => {
assert_eq!(
result.get("shard_path"), Some(expected),
result.get("shard_path"),
Some(expected),
"case {}: shard_path mismatch",
id
);
@@ -203,7 +276,12 @@ fn battery_replays_frozen_truth_verbatim() {
assert!(
remaining.is_empty(),
"case {}: kernel issued fewer ops than the oracle ({} pending: {:?})",
case.get("id").and_then(|v| match v { Json::Str(s) => Some(s.as_str()), _ => None }).unwrap(),
case.get("id")
.and_then(|v| match v {
Json::Str(s) => Some(s.as_str()),
_ => None,
})
.unwrap(),
remaining.len(),
remaining
);
+17 -11
View File
@@ -97,10 +97,7 @@ fn read_arg(ptr: *const c_char) -> Result<Json, SmError> {
fn envelope(r: Result<Json, SmError>) -> *mut c_char {
let v = match r {
Ok(x) => Json::Obj(vec![
("ok".into(), Json::Bool(true)),
("value".into(), x),
]),
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.to_string())),
@@ -206,9 +203,12 @@ pub fn create_shard(
if std::path::Path::new(&shard_path).exists() {
return Ok((shard_path, index.clone()));
}
try_op(cb, &OpKind::EnsureSchema {
path: shard_path.clone(),
})
try_op(
cb,
&OpKind::EnsureSchema {
path: shard_path.clone(),
},
)
.ok_or(SmError::OpRejected(OP_ENSURE_SCHEMA))?;
let mut idx = index.clone();
index_set(&mut idx, topic, &shard_path);
@@ -322,9 +322,12 @@ pub fn get_shard_stats(index: &Json, cb: OpsCb) -> Result<Json, SmError> {
Json::Str(s) => s.as_str(),
_ => "",
};
let count = try_op(cb, &OpKind::CountEntries {
path: path.to_string(),
})
let count = try_op(
cb,
&OpKind::CountEntries {
path: path.to_string(),
},
)
.and_then(|v| match v {
Json::Num(n) => Some(n as i64),
_ => None,
@@ -418,7 +421,10 @@ pub extern "C" fn sm_route_query(doc_ptr: *const c_char) -> *mut c_char {
pub extern "C" fn sm_merge_results(doc_ptr: *const c_char) -> *mut c_char {
let run = || -> Result<Json, SmError> {
let doc = read_arg(doc_ptr)?;
let results_list = doc.get("results_list").cloned().unwrap_or(Json::Arr(Vec::new()));
let results_list = doc
.get("results_list")
.cloned()
.unwrap_or(Json::Arr(Vec::new()));
Ok(merge_results(&results_list))
};
envelope(run())
@@ -23,10 +23,7 @@ fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
let mut cur = v;
for k in path {
cur = match cur {
Json::Obj(pairs) => pairs
.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, x)| x)?,
Json::Obj(pairs) => pairs.iter().find(|(k2, _)| k2 == k).map(|(_, x)| x)?,
_ => return None,
};
}
@@ -127,11 +124,7 @@ fn replay_default_step_check() {
}
let got = default_step_check(r.get("step").expect("step"));
let want = crate::obj_ok_detail(&r);
assert_case(
&jstr(at(&c, &["name"])),
&want,
&got,
);
assert_case(&jstr(at(&c, &["name"])), &want, &got);
}
}
}
+51 -38
View File
@@ -105,10 +105,14 @@ pub fn default_step_check(step: &Json) -> Json {
/// must replay cleanly; vacuous-True when there are no results.
pub fn sandbox_gate(skill: &Json, results: &[Json]) -> Json {
let passed = results.iter().all(|r| truthy(r.get("ok")));
Json::Obj(with_key(clone_pairs(skill), "sandbox", obj(vec![
("passed", Json::Bool(passed)),
("results", Json::Arr(results.to_vec())),
])))
Json::Obj(with_key(
clone_pairs(skill),
"sandbox",
obj(vec![
("passed", Json::Bool(passed)),
("results", Json::Arr(results.to_vec())),
]),
))
}
/// Phase B (language advantage): the promote gate is a typed verdict
@@ -177,11 +181,9 @@ fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
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(),
Err(_) => CString::new(r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#)
.unwrap()
.into_raw(),
}
}
@@ -203,8 +205,7 @@ 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));
let out: Result<Json, (String, String)> = read_arg(arg).and_then(|raw| $body(&raw));
envelope(out)
}
};
@@ -217,26 +218,35 @@ fn steps_of(doc: &Json, key: &str) -> Result<Vec<Json>, (String, String)> {
}
}
ffi!(sk_sequence_id, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
Ok(s(&sequence_id(&steps_of(&doc, "steps")?)))
});
ffi!(
sk_sequence_id,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
Ok(s(&sequence_id(&steps_of(&doc, "steps")?)))
}
);
ffi!(sk_candidate_skill, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let steps = steps_of(&doc, "steps")?;
let family = match doc.get("family") {
Some(Json::Str(f)) => f.clone(),
_ => return Err(("family must be a string".into(), "TypeError".into())),
};
let evidence = match doc.get("evidence") {
None | Some(Json::Null) => None,
Some(e) => Some(e),
};
Ok(candidate_skill(&steps, &family, evidence))
});
ffi!(
sk_candidate_skill,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let steps = steps_of(&doc, "steps")?;
let family = match doc.get("family") {
Some(Json::Str(f)) => f.clone(),
_ => return Err(("family must be a string".into(), "TypeError".into())),
};
let evidence = match doc.get("evidence") {
None | Some(Json::Null) => None,
Some(e) => Some(e),
};
Ok(candidate_skill(&steps, &family, evidence))
}
);
ffi!(sk_default_step_check, |raw: &str| -> Result<Json, (String, String)> {
ffi!(sk_default_step_check, |raw: &str| -> Result<
Json,
(String, String),
> {
let doc = parse_doc(raw)?;
match doc.get("step") {
Some(step) => Ok(default_step_check(step)),
@@ -244,15 +254,18 @@ ffi!(sk_default_step_check, |raw: &str| -> Result<Json, (String, String)> {
}
});
ffi!(sk_sandbox_gate, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let skill = match doc.get("skill") {
Some(sk @ Json::Obj(_)) => sk.clone(),
_ => return Err(("skill must be an object".into(), "TypeError".into())),
};
let results = steps_of(&doc, "results")?;
Ok(sandbox_gate(&skill, &results))
});
ffi!(
sk_sandbox_gate,
|raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let skill = match doc.get("skill") {
Some(sk @ Json::Obj(_)) => sk.clone(),
_ => return Err(("skill must be an object".into(), "TypeError".into())),
};
let results = steps_of(&doc, "results")?;
Ok(sandbox_gate(&skill, &results))
}
);
ffi!(sk_promote, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
+32 -46
View File
@@ -58,11 +58,7 @@ fn materialize(files: &Json, root: &Path) {
}
fn sandbox_dir(idx: usize) -> PathBuf {
let d = std::env::temp_dir().join(format!(
"spine-gap-battery-{}-{}",
std::process::id(),
idx
));
let d = std::env::temp_dir().join(format!("spine-gap-battery-{}-{}", std::process::id(), idx));
let _ = fs::remove_dir_all(&d);
fs::create_dir_all(&d).expect("sandbox mkdir");
d
@@ -109,12 +105,7 @@ mod truth_battery {
let got = replay_harness(case)
.unwrap_or_else(|e| panic!("{} dispatch failed: {}", fname, e));
let expect = expect_field(case, "expected");
assert_eq!(
canon(&got),
canon(expect),
"case {} parity (canon)",
fname
);
assert_eq!(canon(&got), canon(expect), "case {} parity (canon)", fname);
n += 1;
}
}
@@ -166,23 +157,26 @@ mod truth_battery {
None => Json::Null,
})
}
"_py_project_meta_surfaces" => gap::_py_project_meta_surfaces(
&args[0],
&args[1],
&stamp,
)
.map(|out| {
// the frozen expected carries the oracle's return
// (the count int); post_doc carries the mutated doc
Json::Obj(vec![
("result".into(), out.get("count").cloned().unwrap_or(Json::Null)),
("doc".into(), out.get("doc").cloned().unwrap_or(Json::Null)),
])
}),
"_py_project_meta_surfaces" => {
gap::_py_project_meta_surfaces(&args[0], &args[1], &stamp).map(|out| {
// the frozen expected carries the oracle's return
// (the count int); post_doc carries the mutated doc
Json::Obj(vec![
(
"result".into(),
out.get("count").cloned().unwrap_or(Json::Null),
),
("doc".into(), out.get("doc").cloned().unwrap_or(Json::Null)),
])
})
}
"_py_ingest_umbrellas_data" => {
gap::_py_ingest_umbrellas_data(&args[0], &args[1], &stamp).map(|out| {
Json::Obj(vec![
("result".into(), out.get("count").cloned().unwrap_or(Json::Null)),
(
"result".into(),
out.get("count").cloned().unwrap_or(Json::Null),
),
("doc".into(), out.get("doc").cloned().unwrap_or(Json::Null)),
])
})
@@ -200,19 +194,14 @@ mod truth_battery {
// the Python-side gathers the generator patched in
// are case fixtures (the registry/graph seam):
let registry = vec!["canary_alpha".to_string(), "canary_beta".to_string()];
let graph = parse(
r#"{"alpha": ["json", "rune"], "beta": ["sys"]}"#,
);
let graph = parse(r#"{"alpha": ["json", "rune"], "beta": ["sys"]}"#);
// the oracle's _test_backed_modules strips the
// test_ prefix (p.stem[5:])
let mut tested: Vec<String> = file_stems_under(
&root.join("tests"),
"test_",
".py",
)
.into_iter()
.map(|stem| stem["test_".len()..].to_string())
.collect();
let mut tested: Vec<String> =
file_stems_under(&root.join("tests"), "test_", ".py")
.into_iter()
.map(|stem| stem["test_".len()..].to_string())
.collect();
tested.sort();
let src_stems = readdir_stems(&root.join("src/rune"), ".py");
gap::_gather_drift_inputs(
@@ -224,10 +213,10 @@ mod truth_battery {
&Json::Null,
)
}
"_py_drift_from_inputs" => gap::_py_drift_from_inputs(&args[0], &args[1], false),
"_py_export_umbrellas" => {
gap::_py_export_umbrellas(&args[0], &stamp, false)
"_py_drift_from_inputs" => {
gap::_py_drift_from_inputs(&args[0], &args[1], false)
}
"_py_export_umbrellas" => gap::_py_export_umbrellas(&args[0], &stamp, false),
"_py_query" => gap::_py_query(&args[0], store::as_str(&args[1]), false),
"_fresh_doc" => gap::_fresh_doc(&root, None),
other => panic!("unknown fn in clocked truth: {}", other),
@@ -270,9 +259,7 @@ mod truth_battery {
}
}
(Err(err), Some(err_doc)) => {
let msg = store::as_str(
err_doc.get("message").expect("error.message"),
);
let msg = store::as_str(err_doc.get("message").expect("error.message"));
assert_eq!(
err,
strip_loader_prefix(msg),
@@ -281,10 +268,9 @@ mod truth_battery {
name
);
}
(other, _) => panic!(
"case {}/{}: unexpected outcome: {:?}",
fname, name, other
),
(other, _) => {
panic!("case {}/{}: unexpected outcome: {:?}", fname, name, other)
}
}
let _ = fs::remove_dir_all(&root);
n += 1;
+33 -20
View File
@@ -51,7 +51,8 @@ use rune_spine::facade;
use rune_spine::pyjson::{self, Json};
use rune_spine::store;
pub const KERNEL_NOT_BUILT: &str = "rune_spine kernel not built — run the rune_spine build; the Python oracle was retired";
pub const KERNEL_NOT_BUILT: &str =
"rune_spine kernel not built — run the rune_spine build; the Python oracle was retired";
fn s(text: &str) -> Json {
Json::Str(text.to_string())
@@ -300,17 +301,18 @@ pub fn _module_imports(module: &str, src_text: Option<&str>) -> Result<Vec<Strin
i += 1;
}
let parsed: Result<(), String> = if i < chars.len() && is_ident_start(chars[i]) {
parse_dotted(&chars, i).and_then(|(module_path, next)| {
expect_keyword(&chars, next, "import").map(|_| module_path)
})
.map(|module_path| {
// `from x.y import ...` → node.module = "x.y" → "x";
// `from .x import ...` → node.module = "x" → "x"
let top = module_path.split('.').next().unwrap_or("").to_string();
if !names.contains(&top) {
names.push(top);
}
})
parse_dotted(&chars, i)
.and_then(|(module_path, next)| {
expect_keyword(&chars, next, "import").map(|_| module_path)
})
.map(|module_path| {
// `from x.y import ...` → node.module = "x.y" → "x";
// `from .x import ...` → node.module = "x" → "x"
let top = module_path.split('.').next().unwrap_or("").to_string();
if !names.contains(&top) {
names.push(top);
}
})
} else {
// `from . import x` → node.module = None → skipped by the
// oracle; still validate the import keyword + names
@@ -337,7 +339,10 @@ fn read_manifest(root: &Path) -> Option<Json> {
fn dir_entry_names(dir: &Path) -> Vec<String> {
let mut names: Vec<String> = match fs::read_dir(dir) {
Ok(rd) => rd.filter_map(|e| e.ok()).map(|e| e.file_name().to_string_lossy().into_owned()).collect(),
Ok(rd) => rd
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect(),
Err(_) => vec![],
};
names.sort();
@@ -345,9 +350,7 @@ fn dir_entry_names(dir: &Path) -> Vec<String> {
}
fn glob_match(name: &str, prefix: &str, suffix: &str) -> bool {
name.len() >= prefix.len() + suffix.len()
&& name.starts_with(prefix)
&& name.ends_with(suffix)
name.len() >= prefix.len() + suffix.len() && name.starts_with(prefix) && name.ends_with(suffix)
}
/// The spine._gather_meta_inputs core. `workflows_dir` defaults to
@@ -460,7 +463,7 @@ pub fn _gather_meta_inputs(root: &Path, workflows_dir: Option<&Path>) -> Result<
pub fn _deployed_source_files(root: &Path) -> Vec<String> {
let mut out: Vec<String> = vec![];
let bases = [
(root.join("src").join("rune"), "*.py" , false),
(root.join("src").join("rune"), "*.py", false),
(root.join("src").join("rust"), "*/canary_*.py", true),
];
for (base, pattern, nested) in bases {
@@ -508,7 +511,10 @@ fn readdir_names(dir: &Path) -> Vec<String> {
}
fn rel_posix(root: &Path, p: &Path) -> String {
p.strip_prefix(root).unwrap_or(p).to_string_lossy().replace('\\', "/")
p.strip_prefix(root)
.unwrap_or(p)
.to_string_lossy()
.replace('\\', "/")
}
// ── _gather_drift_inputs — assembly + pyproject version extraction ────────
@@ -529,7 +535,10 @@ pub fn _gather_drift_inputs(
let mut sorted_graph: Vec<(String, Json)> = vec![];
if let Some(Json::Obj(pairs)) = Some(graph) {
for (m, deps) in pairs {
let mut list: Vec<String> = store::as_arr(deps).iter().map(|x| store::as_str(x).to_string()).collect();
let mut list: Vec<String> = store::as_arr(deps)
.iter()
.map(|x| store::as_str(x).to_string())
.collect();
list.sort();
sorted_graph.push((m.clone(), str_arr(&list)));
}
@@ -682,7 +691,11 @@ pub fn _py_build_from_inputs(inputs: &Json, core_available: bool) -> Result<Json
rune_spine::project::project(inputs)
}
pub fn _py_drift_from_inputs(doc: &Json, inputs: &Json, core_available: bool) -> Result<Json, String> {
pub fn _py_drift_from_inputs(
doc: &Json,
inputs: &Json,
core_available: bool,
) -> Result<Json, String> {
if !core_available {
return Err(KERNEL_NOT_BUILT.to_string());
}
+12 -13
View File
@@ -63,10 +63,13 @@ fn root_arg(v: &Json, key: &str) -> Result<std::path::PathBuf, String> {
#[no_mangle]
pub extern "C" fn sp_module_imports(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let module = a.get("module").and_then(|x| match x {
Json::Str(s) => Some(s.clone()),
_ => None,
}).ok_or("arg.module required")?;
let module = a
.get("module")
.and_then(|x| match x {
Json::Str(s) => Some(s.clone()),
_ => None,
})
.ok_or("arg.module required")?;
let src_text = match a.get("src_text") {
Some(Json::Str(t)) => Some(t.clone()),
_ => None,
@@ -97,7 +100,10 @@ pub extern "C" fn sp_deployed_source_files(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let root = root_arg(&a, "root")?;
Ok(Json::Arr(
gap::_deployed_source_files(&root).into_iter().map(Json::Str).collect(),
gap::_deployed_source_files(&root)
.into_iter()
.map(Json::Str)
.collect(),
))
});
envelope(r)
@@ -147,14 +153,7 @@ pub extern "C" fn sp_gather_drift_inputs(arg: *const c_char) -> *mut c_char {
})
.ok_or("arg.src_stems required")?;
let deployed_obs = a.get("deployed_obs").cloned().unwrap_or(Json::Null);
gap::_gather_drift_inputs(
&root,
&registry,
&graph,
&tested,
&src_stems,
&deployed_obs,
)
gap::_gather_drift_inputs(&root, &registry, &graph, &tested, &src_stems, &deployed_obs)
});
envelope(r)
}
@@ -130,7 +130,8 @@ fn frozen_truth_replays_verbatim() {
Some(Json::Str(s)) => s.as_str().to_string(),
_ => format!("case-{i}"),
};
let expected = spine::mask_observed_at(&case.get("expected").cloned().unwrap_or(Json::Null));
let expected =
spine::mask_observed_at(&case.get("expected").cloned().unwrap_or(Json::Null));
match run_case(&fn_name, case) {
Ok(actual) => {
if !jeq(&expected, &actual) {
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff. Show more