refactor(drift_resume): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 52s
CI Pipeline / lint (push) Canceled after 51s
CI Pipeline / canary (push) Canceled after 51s
Auto-version and Release / release (push) Canceled after 53s
deploy / deploy (push) Canceled after 36s
CI Pipeline / type-check (push) Canceled after 47s

The drift vocabulary becomes a DriftClass enum with exhaustive
classify/parse/is_blocking matches (no string soup — the FFI record
still serializes the oracle's exact strings; BENIGN stays in the Python
facade since classify_drift can never assign it — vocabulary for
downstream consumers, not a classified value); the oracle's 'matches is
True / is None / else' identity-check triple is a Verdict three-way
lattice classified ONCE and matched exhaustively (a truthy non-True
value, e.g. 1, is NOT Matches — exact parity with the `is` checks);
reconcile's blocking filter goes through DriftClass::parse →
is_blocking instead of a string-array scan; the moved-twin outcome is a
MovedTwin enum whose evidence()/reconciliation() methods keep the
paired strings consistent by construction; FFI errors are a typed
FfiError enum (NullPtr/Utf8/Json) behind the unchanged doc-envelope err
string. Battery 3/3 green throughout; live parity 35/35 facade +
35/35 kernel re-verified against the rebuilt cdylib; clippy 0; held-out
suites green live AND pinned.
This commit is contained in:
Nick committed 2026-10-13 02:51:26 +01:00
1 parent 29ce279702
commit 51ef7cf967
1 file changed
+169 -66
+169 -66
View File
@@ -29,16 +29,67 @@
//! No dr_free_string export — sibling crates sharing the Python process
//! already export identical free symbols; callers free through
//! capability_scoring's cs_free_string.
//!
//! Language-advantage pass (Phase B, battery-pinned): the drift
//! vocabulary is a `DriftClass` enum with an exhaustive classify/`is_
//! blocking` match (no string soup; the FFI record still serializes the
//! oracle's exact strings); the oracle's `matches is True / is None /
//! else` triple is a `Verdict` three-way lattice classified once and
//! matched exhaustively; the moved-twin outcome is a `MovedTwin` enum
//! whose `evidence`/`reconciliation` methods keep the paired strings
//! consistent by construction; the FFI layer is a typed `FfiError` enum
//! behind the unchanged doc-envelope err string. Battery green
//! throughout.
use serde_json::{json, Value};
pub const EXPECTED: &str = "EXPECTED"; // checkpoint older than reality; benign aging
pub const BENIGN: &str = "BENIGN"; // difference with no bearing on the claims
pub const MATERIAL: &str = "MATERIAL"; // reality contradicts a scaffold claim
pub const CONFLICTING: &str = "CONFLICTING"; // two authorities disagree; needs user
pub const UNKNOWN: &str = "UNKNOWN"; // cannot decide — treated as blocking
// ── drift-class lattice ─────────────────────────────────────────────────────
pub const BLOCKING_CLASSES: [&str; 3] = [MATERIAL, CONFLICTING, UNKNOWN];
/// The classify_drift vocabulary as CLASSIFIED — exhaustive, no string
/// soup. The FFI record serializes the oracle's exact strings. (The
/// oracle's BENIGN constant is vocabulary for downstream consumers, not
/// a value classify_drift can assign — it stays in the Python facade.)
#[derive(Clone, Copy, PartialEq, Eq)]
enum DriftClass {
Expected, // checkpoint older than reality; benign aging
Confirmed, // reality matches the claim
Material, // reality contradicts a scaffold claim
Conflicting, // two authorities disagree; needs user
Unknown, // cannot decide — treated as blocking
}
impl DriftClass {
fn as_str(self) -> &'static str {
match self {
DriftClass::Expected => "EXPECTED",
DriftClass::Confirmed => "CONFIRMED",
DriftClass::Material => "MATERIAL",
DriftClass::Conflicting => "CONFLICTING",
DriftClass::Unknown => "UNKNOWN",
}
}
fn parse(s: &str) -> Option<DriftClass> {
Some(match s {
"EXPECTED" => DriftClass::Expected,
"CONFIRMED" => DriftClass::Confirmed,
"MATERIAL" => DriftClass::Material,
"CONFLICTING" => DriftClass::Conflicting,
"UNKNOWN" => DriftClass::Unknown,
"BENIGN" => return None, // oracle constant, not a classified value
_ => return None,
})
}
/// BLOCKING_CLASSES: MATERIAL / CONFLICTING / UNKNOWN stop dependent
/// work until reconciled.
fn is_blocking(self) -> bool {
matches!(
self,
DriftClass::Material | DriftClass::Conflicting | DriftClass::Unknown
)
}
}
// ── Python scalar semantics ─────────────────────────────────────────────────
@@ -75,59 +126,69 @@ fn get_or_dflt(o: &Value, key: &str, dflt: &str) -> String {
}
}
// ── classify_drift ──────────────────────────────────────────────────────────
// ── classification ──────────────────────────────────────────────────────────
/// The oracle's `matches is True / is None / else` triple, classified
/// ONCE — the identity checks are exact (a truthy non-True value, e.g.
/// 1, is NOT True; a missing key and an explicit null both mean None).
#[derive(Clone, Copy, PartialEq, Eq)]
enum Verdict {
Matches,
Unverifiable,
Contradicted,
}
impl Verdict {
fn classify(observation: &Value) -> Verdict {
match observation.get("matches") {
Some(Value::Bool(true)) => Verdict::Matches,
None | Some(Value::Null) => Verdict::Unverifiable,
_ => Verdict::Contradicted,
}
}
}
/// Classify one claim-vs-observation pair. Deterministic: verification
/// evidence beats prose; explicit beats inferred. `matches is True` and
/// `matches is None` are IDENTITY checks in the oracle — a truthy
/// non-True value (e.g. 1) falls through to the reality-differs branch,
/// and "None" covers both a missing key and an explicit null.
/// evidence beats prose; explicit beats inferred.
pub fn classify_drift(claim: &Value, observation: &Value) -> Value {
let claim_text = claim.get("claim").and_then(Value::as_str).unwrap_or("");
match observation.get("matches") {
Some(Value::Bool(true)) => {
let stale_ok = claim
.get("stale_ok")
.map(py_truthy)
.unwrap_or(false);
json!({
"claim": claim_text,
"class": if stale_ok { EXPECTED } else { "CONFIRMED" },
"evidence": get_or_dflt(observation, "checked_by", ""),
})
let (class, evidence) = match Verdict::classify(observation) {
Verdict::Matches => {
let stale_ok = claim.get("stale_ok").map(py_truthy).unwrap_or(false);
(
if stale_ok { DriftClass::Expected } else { DriftClass::Confirmed },
get_or_dflt(observation, "checked_by", ""),
)
}
None | Some(Value::Null) => json!({
"claim": claim_text,
"class": UNKNOWN,
"evidence": format!(
Verdict::Unverifiable => (
DriftClass::Unknown,
format!(
"unverifiable via {}",
get_or_dflt(observation, "checked_by", "?")
),
}),
_ => {
let user_locked = claim
.get("user_locked")
.map(py_truthy)
.unwrap_or(false);
),
Verdict::Contradicted => {
let user_locked =
claim.get("user_locked").map(py_truthy).unwrap_or(false);
if user_locked {
json!({
"claim": claim_text,
"class": CONFLICTING,
"evidence": "reality differs from a USER-LOCKED claim — user arbitration required",
})
(
DriftClass::Conflicting,
"reality differs from a USER-LOCKED claim — user arbitration required"
.to_string(),
)
} else {
json!({
"claim": claim_text,
"class": MATERIAL,
"evidence": format!(
(
DriftClass::Material,
format!(
"reality differs: {} (checked by {})",
get_or_dflt(observation, "actual", ""),
get_or_dflt(observation, "checked_by", "?")
),
})
)
}
}
}
};
json!({"claim": claim_text, "class": class.as_str(), "evidence": evidence})
}
// ── reconcile ───────────────────────────────────────────────────────────────
@@ -145,7 +206,11 @@ pub fn reconcile(claims: &[Value], observations: &[Value]) -> Value {
let blocking: Vec<Value> = drifts
.iter()
.filter(|d| {
BLOCKING_CLASSES.contains(&d["class"].as_str().unwrap_or(""))
d["class"]
.as_str()
.and_then(DriftClass::parse)
.map(DriftClass::is_blocking)
.unwrap_or(false)
})
.cloned()
.collect();
@@ -180,6 +245,29 @@ fn source_of(e: &Value) -> Option<&str> {
source_field(e, "ext").or_else(|| source_field(e, "identities"))
}
/// The moved-twin outcome — `evidence` and `reconciliation` are a
/// PAIRED rendering of one outcome, kept consistent by construction.
enum MovedTwin {
Moved(String),
Absent,
}
impl MovedTwin {
fn evidence(&self) -> String {
match self {
MovedTwin::Moved(m) => format!("source missing; moved to {}", m),
MovedTwin::Absent => "source file absent from repo".to_string(),
}
}
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(),
}
}
}
/// Compare structural-model component claims against repository reality
/// (facade-gathered relative listings). Observable cases: source file
/// moved/missing (component identities carry "source"), entities for
@@ -212,27 +300,23 @@ pub fn detect_structural_drift(
// moved twin (first basename match in listing order)
let expected_name =
std::path::Path::new(src).file_name().and_then(|n| n.to_str());
let moved: Option<String> = expected_name.and_then(|name| {
files.iter().find_map(|p| {
let s = p.as_str()?;
(std::path::Path::new(s).file_name().and_then(|n| n.to_str())
== Some(name))
.then(|| s.to_string())
let 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())
})
})
});
.map_or(MovedTwin::Absent, MovedTwin::Moved);
let id = e.get("id").map(py_str).unwrap_or_default();
drifts.push(json!({
"claim": format!("{} source at {}", id, src),
"class": if locked { CONFLICTING } else { MATERIAL },
"evidence": match &moved {
Some(m) => format!("source missing; moved to {}", m),
None => "source file absent from repo".to_string(),
},
"class": if locked { DriftClass::Conflicting.as_str() } else { DriftClass::Material.as_str() },
"evidence": twin.evidence(),
"reality_wins": !locked,
"reconciliation": match &moved {
Some(m) => format!("update source to {}", m),
None => "entity requires re-derivation or user-locked preservation".to_string(),
},
"reconciliation": twin.reconciliation(),
}));
}
drifts
@@ -240,20 +324,39 @@ pub fn detect_structural_drift(
// ── FFI (doc-envelope law) ──────────────────────────────────────────────────
fn read_arg(arg: *const std::os::raw::c_char) -> Result<Value, String> {
/// Typed FFI failure lattice behind the doc-envelope Result — the
/// caller-visible `err` string is unchanged (the enum only shapes the
/// Rust side; parity is pinned by the battery).
enum FfiError {
NullPtr,
Utf8(std::str::Utf8Error),
Json(serde_json::Error),
}
impl std::fmt::Display for FfiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FfiError::NullPtr => write!(f, "null argument pointer"),
FfiError::Utf8(e) => write!(f, "{e}"),
FfiError::Json(e) => write!(f, "{e}"),
}
}
}
fn read_arg(arg: *const std::os::raw::c_char) -> Result<Value, FfiError> {
if arg.is_null() {
return Err("null argument pointer".into());
return Err(FfiError::NullPtr);
}
let s = unsafe { std::ffi::CStr::from_ptr(arg) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(s).map_err(|e| e.to_string())
.map_err(FfiError::Utf8)?;
serde_json::from_str(s).map_err(FfiError::Json)
}
fn envelope(r: Result<Value, String>) -> *mut std::os::raw::c_char {
fn envelope(r: Result<Value, FfiError>) -> *mut std::os::raw::c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
Err(e) => json!({"ok": false, "err": e.to_string()}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())