refactor(state_checkpoint): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 40s
CI Pipeline / canary (push) Canceled after 31s
CI Pipeline / gitea-offline (push) Canceled after 19s
CI Pipeline / type-check (push) Canceled after 21s
deploy / deploy (push) Canceled after 45s
CI Pipeline / lint (push) Canceled after 36s
CI Pipeline / unit-tests (push) Canceled after 36s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 40s
CI Pipeline / canary (push) Canceled after 31s
CI Pipeline / gitea-offline (push) Canceled after 19s
CI Pipeline / type-check (push) Canceled after 21s
deploy / deploy (push) Canceled after 45s
CI Pipeline / lint (push) Canceled after 36s
CI Pipeline / unit-tests (push) Canceled after 36s
This commit is contained in:
1 parent
b278437a77
commit
eef062cb74
1 file changed
+85
-10
@@ -43,6 +43,18 @@
|
||||
//! No sck_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 restore
|
||||
//! decision is a `RestoreOutcome` enum (Unknown / DryRun / Proceed)
|
||||
//! classified once and matched exhaustively; the listing sort key is a
|
||||
//! `SortKey` enum (Dated / Undated — the oracle's missing-`at` default
|
||||
//! rendered by match); each diff side classifies into a `DiffSide`
|
||||
//! (Counted / Errored) so the `.get("entries", 0)` fallback is a type;
|
||||
//! the diff lookup outcome is a `LookupOutcome` enum (Found / Unknown).
|
||||
//! FFI errors are a typed `CheckpointError` enum behind the Result
|
||||
//! envelope. The ledger doc and snapshot records stay pass-through
|
||||
//! Values — the oracle forwards any caller shape verbatim. Battery
|
||||
//! green throughout.
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::fmt;
|
||||
@@ -73,9 +85,28 @@ fn snapshots_array(ledger: &Map<String, Value>) -> Result<&Vec<Value>, Checkpoin
|
||||
.ok_or_else(|| CheckpointError::Key("snapshots".to_string()))
|
||||
}
|
||||
|
||||
/// `s.get("at", "")` — the listing sort key (missing → sorts last).
|
||||
fn sort_key(s: &Value) -> &str {
|
||||
s.get("at").and_then(Value::as_str).unwrap_or("")
|
||||
/// The listing sort key — classified per snapshot so the oracle's
|
||||
/// `.get("at", "")` default is a type: dated entries rank by their
|
||||
/// stamp, undated ones rank as "" (hence last under reverse order).
|
||||
enum SortKey<'a> {
|
||||
Dated(&'a str),
|
||||
Undated,
|
||||
}
|
||||
|
||||
impl SortKey<'_> {
|
||||
fn classify(s: &Value) -> SortKey<'_> {
|
||||
match s.get("at").and_then(Value::as_str) {
|
||||
Some(at) => SortKey::Dated(at),
|
||||
None => SortKey::Undated,
|
||||
}
|
||||
}
|
||||
|
||||
fn rank(&self) -> &str {
|
||||
match self {
|
||||
SortKey::Dated(at) => at,
|
||||
SortKey::Undated => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── checkpoint operations (pure functions of the ledger doc) ────────────────
|
||||
@@ -112,7 +143,7 @@ pub fn snapshot(
|
||||
/// original order, and Rust's stable sort-by-reversed-key matches.
|
||||
pub fn list(snapshots: &[Value]) -> Vec<Value> {
|
||||
let mut out = snapshots.to_vec();
|
||||
out.sort_by(|a, b| sort_key(b).cmp(sort_key(a)));
|
||||
out.sort_by(|a, b| SortKey::classify(b).rank().cmp(SortKey::classify(a).rank()));
|
||||
out
|
||||
}
|
||||
|
||||
@@ -153,6 +184,16 @@ pub fn restore_commit(record: &Value, safety_id: &str) -> Value {
|
||||
json!({"restored": true, "snapshot": record, "safety_snapshot": safety_id})
|
||||
}
|
||||
|
||||
/// The diff lookup outcome — classified once from the by_id map, then
|
||||
/// rendered by an exhaustive match.
|
||||
enum LookupOutcome {
|
||||
Found {
|
||||
before_path: String,
|
||||
after_path: String,
|
||||
},
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// `diff` lookup core: by_id built dict-comprehension style — the LAST
|
||||
/// duplicate id wins. Either side unknown → the oracle's error doc.
|
||||
pub fn diff_lookup(snapshots: &[Value], before_id: &str, after_id: &str) -> Value {
|
||||
@@ -169,16 +210,50 @@ pub fn diff_lookup(snapshots: &[Value], before_id: &str, after_id: &str) -> Valu
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_string)
|
||||
};
|
||||
match (path_of(before_id), path_of(after_id)) {
|
||||
(Some(bp), Some(ap)) => json!({"before_path": bp, "after_path": ap}),
|
||||
_ => json!({"error": "unknown snapshot id"}),
|
||||
let outcome = match (path_of(before_id), path_of(after_id)) {
|
||||
(Some(bp), Some(ap)) => LookupOutcome::Found {
|
||||
before_path: bp,
|
||||
after_path: ap,
|
||||
},
|
||||
_ => LookupOutcome::Unknown,
|
||||
};
|
||||
match outcome {
|
||||
LookupOutcome::Found {
|
||||
before_path,
|
||||
after_path,
|
||||
} => json!({"before_path": before_path, "after_path": after_path}),
|
||||
LookupOutcome::Unknown => json!({"error": "unknown snapshot id"}),
|
||||
}
|
||||
}
|
||||
|
||||
/// `diff` shape core: per-side buckets pass through (an error side has
|
||||
/// no `entries` and contributes the oracle's .get("entries", 0) → 0).
|
||||
/// One diff side — classified before the delta so the oracle's
|
||||
/// `.get("entries", 0)` fallback is a type, not a silent default (an
|
||||
/// error bucket has no `entries` and counts as 0).
|
||||
enum DiffSide {
|
||||
Counted(i64),
|
||||
Errored,
|
||||
}
|
||||
|
||||
impl DiffSide {
|
||||
fn classify(side: &Value) -> DiffSide {
|
||||
match side.get("entries").and_then(Value::as_i64) {
|
||||
Some(n) => DiffSide::Counted(n),
|
||||
None => DiffSide::Errored,
|
||||
}
|
||||
}
|
||||
|
||||
fn entries(self) -> i64 {
|
||||
match self {
|
||||
DiffSide::Counted(n) => n,
|
||||
DiffSide::Errored => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `diff` shape core: per-side buckets pass through, the delta compares
|
||||
/// classified sides.
|
||||
pub fn diff_shape(before: &Value, after: &Value) -> Value {
|
||||
let entries = |side: &Value| side.get("entries").and_then(Value::as_i64).unwrap_or(0);
|
||||
let entries = |side: &Value| DiffSide::classify(side).entries();
|
||||
json!({
|
||||
"before": before,
|
||||
"after": after,
|
||||
|
||||
Reference in new issue
Block a user