refactor(testing_mode): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 23s
CI Pipeline / type-check (push) Canceled after 27s
CI Pipeline / unit-tests (push) Canceled after 39s
CI Pipeline / lint (push) Canceled after 39s
Auto-version and Release / release (push) Canceled after 41s
CI Pipeline / canary (push) Canceled after 41s
deploy / deploy (push) Canceled after 25s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 23s
CI Pipeline / type-check (push) Canceled after 27s
CI Pipeline / unit-tests (push) Canceled after 39s
CI Pipeline / lint (push) Canceled after 39s
Auto-version and Release / release (push) Canceled after 41s
CI Pipeline / canary (push) Canceled after 41s
deploy / deploy (push) Canceled after 25s
This commit is contained in:
1 parent
626359e693
commit
3c70f3bd14
1 file changed
+135
-40
@@ -37,12 +37,93 @@
|
||||
//! No tm_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 paused gate is
|
||||
//! a `PauseState` enum with an exhaustive match (classify once from the
|
||||
//! stored flag); the closure verdict is a `ClosureVerdict` enum
|
||||
//! (retest ∧ user-confirmed, exhaustive); aggregate rows classify into
|
||||
//! an `AggregateRow` enum (NoLedger / WithSummary) before rendering;
|
||||
//! next_item hands back the selected queue entry by REFERENCE (zero-copy
|
||||
//! borrow of the facade's outstanding list — the clone happens once, at
|
||||
//! the FFI edge, only for the chosen item); FFI errors are a typed
|
||||
//! `FfiError` enum behind the Result envelope. The STORED session state
|
||||
//! and ledger records stay pass-through Values — the oracle forwards any
|
||||
//! caller shape verbatim, so a closed stored lattice would be a parity
|
||||
//! break, not an advantage. Battery green throughout.
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::fmt;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
pub const SESSION_SCHEMA_VERSION: i64 = 1;
|
||||
|
||||
// ── decision lattices (enums, not string soup) ──────────────────────────────
|
||||
|
||||
/// The 19N pause gate — classified once from the stored `paused` flag
|
||||
/// (Python truthiness), matched exhaustively in next_item.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum PauseState {
|
||||
Paused,
|
||||
Active,
|
||||
}
|
||||
|
||||
impl PauseState {
|
||||
fn classify(state: &Map<String, Value>) -> PauseState {
|
||||
if py_truthy(state.get("paused").unwrap_or(&Value::Null)) {
|
||||
PauseState::Paused
|
||||
} else {
|
||||
PauseState::Active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The 19O closure verdict — an observation closes only when the
|
||||
/// behaviour was retested AND user-confirmed (code changed ≠ closure;
|
||||
/// automated green ≠ closure).
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum ClosureVerdict {
|
||||
Closed,
|
||||
Open,
|
||||
}
|
||||
|
||||
impl From<ClosureVerdict> for bool {
|
||||
fn from(v: ClosureVerdict) -> bool {
|
||||
matches!(v, ClosureVerdict::Closed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Aggregate row classification — decided per registry entry before
|
||||
/// rendering, matched exhaustively.
|
||||
enum AggregateRow {
|
||||
NoLedger(Value),
|
||||
WithSummary(Value, Value),
|
||||
}
|
||||
|
||||
impl AggregateRow {
|
||||
fn classify(entry: &Value) -> AggregateRow {
|
||||
let project = entry.get("project_id").cloned().unwrap_or(Value::Null);
|
||||
match entry.get("summary") {
|
||||
None | Some(Value::Null) => AggregateRow::NoLedger(project),
|
||||
Some(s) => AggregateRow::WithSummary(project, s.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(self) -> Value {
|
||||
match self {
|
||||
AggregateRow::NoLedger(project) => json!({
|
||||
"project": project,
|
||||
"outstanding": Value::Null,
|
||||
"note": "no behaviour ledger yet",
|
||||
}),
|
||||
AggregateRow::WithSummary(project, s) => json!({
|
||||
"project": project,
|
||||
"outstanding": s.get("outstanding").cloned().unwrap_or(Value::Null),
|
||||
"states": s.get("states").cloned().unwrap_or(Value::Null),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Python scalar semantics ─────────────────────────────────────────────────
|
||||
|
||||
/// Python truthiness over a JSON value.
|
||||
@@ -116,25 +197,32 @@ pub fn default_state(project: &Value, scope_component: &Value, now: &str) -> Val
|
||||
})
|
||||
}
|
||||
|
||||
/// The dequeued-item sentinel for paused/empty queues — a stable static
|
||||
/// so the borrowed return never points into a temporary.
|
||||
static NULL_ITEM: Value = Value::Null;
|
||||
|
||||
/// `next_item` — 19L next eligible item. Paused sessions dequeue
|
||||
/// nothing (state untouched); otherwise the first outstanding item is
|
||||
/// selected and its behaviour_id becomes current_item.
|
||||
pub fn next_item(
|
||||
/// selected and its behaviour_id becomes current_item. The selected
|
||||
/// item is handed back by reference — zero-copy off the facade's list.
|
||||
pub fn next_item<'a>(
|
||||
state: &Map<String, Value>,
|
||||
outstanding: &[Value],
|
||||
) -> (Map<String, Value>, Value) {
|
||||
if py_truthy(state.get("paused").unwrap_or(&Value::Null)) {
|
||||
return (state.clone(), Value::Null);
|
||||
outstanding: &'a [Value],
|
||||
) -> (Map<String, Value>, &'a Value) {
|
||||
match PauseState::classify(state) {
|
||||
PauseState::Paused => (state.clone(), &NULL_ITEM),
|
||||
PauseState::Active => {
|
||||
let mut out = state.clone();
|
||||
let item = outstanding.first().unwrap_or(&NULL_ITEM);
|
||||
let current = item
|
||||
.as_object()
|
||||
.and_then(|o| o.get("behaviour_id"))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
out.insert("current_item".to_string(), current);
|
||||
(out, item)
|
||||
}
|
||||
}
|
||||
let mut out = state.clone();
|
||||
let item = outstanding.first().cloned().unwrap_or(Value::Null);
|
||||
let current = item
|
||||
.as_object()
|
||||
.and_then(|o| o.get("behaviour_id"))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
out.insert("current_item".to_string(), current);
|
||||
(out, item)
|
||||
}
|
||||
|
||||
/// `record` — append the completed-session bookkeeping entry (the
|
||||
@@ -240,11 +328,15 @@ pub fn close_observation(
|
||||
for rec in records {
|
||||
let mut rec = rec.clone();
|
||||
if rec.get("observation_id") == Some(observation_id) {
|
||||
let verdict = match (behaviour_retest_passed, user_confirmed) {
|
||||
(true, true) => ClosureVerdict::Closed,
|
||||
_ => ClosureVerdict::Open,
|
||||
};
|
||||
rec["closure"] = json!({
|
||||
"behaviour_retest_passed": behaviour_retest_passed,
|
||||
"user_confirmed": user_confirmed,
|
||||
"contract_ref": contract_ref,
|
||||
"closed": behaviour_retest_passed && user_confirmed,
|
||||
"closed": bool::from(verdict),
|
||||
"at": now,
|
||||
});
|
||||
result = rec.clone();
|
||||
@@ -289,43 +381,46 @@ fn truncate_chars(s: &str, n: usize) -> String {
|
||||
pub fn aggregate(entries: &[Value]) -> Vec<Value> {
|
||||
entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let project = entry
|
||||
.get("project_id")
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
match entry.get("summary") {
|
||||
None | Some(Value::Null) => json!({
|
||||
"project": project,
|
||||
"outstanding": Value::Null,
|
||||
"note": "no behaviour ledger yet",
|
||||
}),
|
||||
Some(s) => json!({
|
||||
"project": project,
|
||||
"outstanding": s.get("outstanding").cloned().unwrap_or(Value::Null),
|
||||
"states": s.get("states").cloned().unwrap_or(Value::Null),
|
||||
}),
|
||||
}
|
||||
})
|
||||
.map(AggregateRow::classify)
|
||||
.map(AggregateRow::render)
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ── FFI (doc-envelope law) ──────────────────────────────────────────────────
|
||||
|
||||
fn read_arg(ptr: *const 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 fmt::Display for FfiError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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(ptr: *const c_char) -> Result<Value, FfiError> {
|
||||
if ptr.is_null() {
|
||||
return Err("null argument pointer".to_string());
|
||||
return Err(FfiError::NullPtr);
|
||||
}
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
|
||||
.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 c_char {
|
||||
fn envelope(r: Result<Value, FfiError>) -> *mut 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())
|
||||
|
||||
Reference in new issue
Block a user