refactor(toolchain): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Canceled after 15s
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 0s
CI Pipeline / lint (push) Canceled after 16s
CI Pipeline / type-check (push) Canceled after 3s
CI Pipeline / unit-tests (push) Canceled after 16s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / canary (push) Canceled after 16s
Auto-version and Release / release (push) Canceled after 26s
deploy / deploy (push) Canceled after 7s
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Canceled after 15s
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 0s
CI Pipeline / lint (push) Canceled after 16s
CI Pipeline / type-check (push) Canceled after 3s
CI Pipeline / unit-tests (push) Canceled after 16s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / canary (push) Canceled after 16s
Auto-version and Release / release (push) Canceled after 26s
deploy / deploy (push) Canceled after 7s
Resolution lattice becomes a real enum (KeepExisting/ChangeMajor/ InsufficientEvidence) with exhaustive as_str/parse matches at the JSON boundary; DecisionRecord is a struct with RecordFields input (removes the 12-arg constructor signature and the too_many_arguments escape hatch); resolve_toolchain returns Result; borrows stay zero-copy until the boundary. Oracle-faithful quirk kept: resolution stays a free-form string on the constructor wire (default "") — only resolver outputs are lattice-guaranteed. Battery 7 tests / 43 frozen cases + lattice round-trip GREEN; clippy --all-targets -D warnings 0; live parity 613/613; facade delegation unchanged (RUNE_TC_CORE pin).
This commit is contained in:
1 parent
f83396eecd
commit
61de7c5b74
2 files changed
+236
-140
No files matched your search
@@ -59,21 +59,24 @@ fn constraints_get_parity_battery() {
|
||||
fn decision_record_parity_battery() {
|
||||
for case in cases("decision_record") {
|
||||
let a = &case["input"];
|
||||
let out = decision_record(
|
||||
a["selected_language"].clone(),
|
||||
a.get("runtime"),
|
||||
a.get("framework"),
|
||||
a.get("libraries"),
|
||||
a.get("build_system").and_then(Value::as_str).unwrap_or(""),
|
||||
a.get("alternatives"),
|
||||
a.get("decisive_constraints"),
|
||||
a.get("rationale").and_then(Value::as_str).unwrap_or(""),
|
||||
a.get("evidence_refs"),
|
||||
a.get("confidence").and_then(Value::as_f64).unwrap_or(0.0),
|
||||
a.get("user_approval_required").and_then(Value::as_bool).unwrap_or(false),
|
||||
a.get("resolution").and_then(Value::as_str).unwrap_or(""),
|
||||
);
|
||||
check(case["name"].as_str().unwrap(), out, &case);
|
||||
let out = decision_record(RecordFields {
|
||||
selected_language: a["selected_language"].clone(),
|
||||
runtime: a.get("runtime"),
|
||||
framework: a.get("framework"),
|
||||
libraries: a.get("libraries"),
|
||||
build_system: a.get("build_system").and_then(Value::as_str).unwrap_or(""),
|
||||
alternatives: a.get("alternatives"),
|
||||
decisive_constraints: a.get("decisive_constraints"),
|
||||
rationale: a.get("rationale").and_then(Value::as_str).unwrap_or(""),
|
||||
evidence_refs: a.get("evidence_refs"),
|
||||
confidence: a.get("confidence").and_then(Value::as_f64).unwrap_or(0.0),
|
||||
user_approval_required: a
|
||||
.get("user_approval_required")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false),
|
||||
resolution: a.get("resolution").and_then(Value::as_str).unwrap_or("").to_string(),
|
||||
});
|
||||
check(case["name"].as_str().unwrap(), out.to_value(), &case);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +99,22 @@ fn resolve_toolchain_parity_battery() {
|
||||
a.get("requested_language").and_then(Value::as_str),
|
||||
a.get("evidence_refs"),
|
||||
a.get("performance_evidence").filter(|p| !p.is_null()),
|
||||
);
|
||||
)
|
||||
.expect("resolve_toolchain must not refuse a corpus case");
|
||||
check(case["name"].as_str().unwrap(), out, &case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_lattice_round_trip() {
|
||||
// every lattice member renders to its oracle string and parses back
|
||||
for (r, s) in [
|
||||
(Resolution::KeepExisting, "KEEP_EXISTING"),
|
||||
(Resolution::ChangeMajor, "CHANGE_MAJOR"),
|
||||
(Resolution::InsufficientEvidence, "INSUFFICIENT_EVIDENCE"),
|
||||
] {
|
||||
assert_eq!(r.as_str(), s);
|
||||
assert_eq!(Resolution::parse(s), Some(r));
|
||||
}
|
||||
assert_eq!(Resolution::parse("MAYBE"), None);
|
||||
}
|
||||
+202
-124
@@ -21,6 +21,11 @@
|
||||
//! NOTE: no tl_free_string export — sibling crates sharing the Python
|
||||
//! process already export identical free symbols; a duplicate breaks the
|
||||
//! cdylib link. Callers free through capability_scoring's cs_free_string.
|
||||
//!
|
||||
//! Phase B (language-advantage pass): the resolution lattice is a real
|
||||
//! enum with exhaustive matches at the JSON boundary; the decision
|
||||
//! record is a struct (no 12-arg signature); the resolver returns
|
||||
//! Result; borrows stay zero-copy until the boundary.
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::ffi::{CStr, CString};
|
||||
@@ -56,6 +61,37 @@ const HARD_FACTOR_KEYS: &[&str] = &[
|
||||
"deployment",
|
||||
];
|
||||
|
||||
// ── resolution lattice (exhaustive match at the JSON boundary — the
|
||||
// wire always carries the oracle's exact strings) ─────────────────────
|
||||
|
||||
/// The oracle's `resolution` field values. Not a free-form string: an
|
||||
/// unknown resolution on the wire is a hard error, never a guess.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Resolution {
|
||||
KeepExisting,
|
||||
ChangeMajor,
|
||||
InsufficientEvidence,
|
||||
}
|
||||
|
||||
impl Resolution {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Resolution::KeepExisting => "KEEP_EXISTING",
|
||||
Resolution::ChangeMajor => "CHANGE_MAJOR",
|
||||
Resolution::InsufficientEvidence => "INSUFFICIENT_EVIDENCE",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Option<Resolution> {
|
||||
match s {
|
||||
"KEEP_EXISTING" => Some(Resolution::KeepExisting),
|
||||
"CHANGE_MAJOR" => Some(Resolution::ChangeMajor),
|
||||
"INSUFFICIENT_EVIDENCE" => Some(Resolution::InsufficientEvidence),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Python truthiness for JSON values (0, "", [], {}, null, False are falsy).
|
||||
fn is_truthy(v: Option<&Value>) -> bool {
|
||||
match v {
|
||||
@@ -98,41 +134,85 @@ fn sorted_strs(v: Option<&Value>) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
/// The oracle's DecisionRecord constructor: durable, inspectable
|
||||
/// toolchain decision (tranche brief §9). List inputs are normalized
|
||||
/// (sorted, None → empty); alternatives are carried verbatim.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn decision_record(
|
||||
selected_language: Value,
|
||||
runtime: Option<&Value>,
|
||||
framework: Option<&Value>,
|
||||
libraries: Option<&Value>,
|
||||
build_system: &str,
|
||||
alternatives: Option<&Value>,
|
||||
decisive_constraints: Option<&Value>,
|
||||
rationale: &str,
|
||||
evidence_refs: Option<&Value>,
|
||||
confidence: f64,
|
||||
user_approval_required: bool,
|
||||
resolution: &str,
|
||||
) -> Value {
|
||||
json!({
|
||||
"selected_language": selected_language,
|
||||
"runtime": runtime.cloned().unwrap_or(json!("")),
|
||||
"framework": framework.cloned().unwrap_or(json!("")),
|
||||
"libraries": sorted_strs(libraries),
|
||||
"build_system": build_system,
|
||||
"alternatives": alternatives
|
||||
/// The oracle's DecisionRecord: durable, inspectable toolchain decision
|
||||
/// (tranche brief §9). Inputs normalize at construction (lists sorted,
|
||||
/// None → empty); alternatives are carried verbatim.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct DecisionRecord {
|
||||
pub selected_language: Value,
|
||||
pub runtime: Value,
|
||||
pub framework: Value,
|
||||
pub libraries: Vec<String>,
|
||||
pub build_system: String,
|
||||
pub alternatives: Value,
|
||||
pub decisive_constraints: Vec<String>,
|
||||
pub rationale: String,
|
||||
pub evidence_refs: Vec<String>,
|
||||
pub confidence: f64,
|
||||
pub user_approval_required: bool,
|
||||
/// Oracle keeps this a free-form string (constructor default "") —
|
||||
/// only resolve_toolchain's outputs are lattice-guaranteed.
|
||||
pub resolution: String,
|
||||
}
|
||||
|
||||
impl DecisionRecord {
|
||||
/// Render the oracle's `to_dict()` exactly.
|
||||
pub fn to_value(&self) -> Value {
|
||||
json!({
|
||||
"selected_language": self.selected_language,
|
||||
"runtime": self.runtime,
|
||||
"framework": self.framework,
|
||||
"libraries": self.libraries,
|
||||
"build_system": self.build_system,
|
||||
"alternatives": self.alternatives,
|
||||
"decisive_constraints": self.decisive_constraints,
|
||||
"rationale": self.rationale,
|
||||
"evidence_refs": self.evidence_refs,
|
||||
"confidence": self.confidence,
|
||||
"user_approval_required": self.user_approval_required,
|
||||
"resolution": self.resolution,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional record inputs; `Default` reproduces the oracle's parameter
|
||||
/// defaults (empty strings/lists, confidence 0.0, no approval flag).
|
||||
#[derive(Clone, Default)]
|
||||
pub struct RecordFields<'a> {
|
||||
pub selected_language: Value,
|
||||
pub runtime: Option<&'a Value>,
|
||||
pub framework: Option<&'a Value>,
|
||||
pub libraries: Option<&'a Value>,
|
||||
pub build_system: &'a str,
|
||||
pub alternatives: Option<&'a Value>,
|
||||
pub decisive_constraints: Option<&'a Value>,
|
||||
pub rationale: &'a str,
|
||||
pub evidence_refs: Option<&'a Value>,
|
||||
pub confidence: f64,
|
||||
pub user_approval_required: bool,
|
||||
pub resolution: String,
|
||||
}
|
||||
|
||||
/// The oracle's DecisionRecord(...) constructor.
|
||||
pub fn decision_record(fields: RecordFields<'_>) -> DecisionRecord {
|
||||
DecisionRecord {
|
||||
selected_language: fields.selected_language,
|
||||
runtime: fields.runtime.cloned().unwrap_or(json!("")),
|
||||
framework: fields.framework.cloned().unwrap_or(json!("")),
|
||||
libraries: sorted_strs(fields.libraries),
|
||||
build_system: fields.build_system.to_string(),
|
||||
alternatives: fields
|
||||
.alternatives
|
||||
.filter(|v| !v.is_null()) // Python: list(alternatives or [])
|
||||
.cloned()
|
||||
.unwrap_or(json!([])),
|
||||
"decisive_constraints": sorted_strs(decisive_constraints),
|
||||
"rationale": rationale,
|
||||
"evidence_refs": sorted_strs(evidence_refs),
|
||||
"confidence": confidence,
|
||||
"user_approval_required": user_approval_required,
|
||||
"resolution": resolution,
|
||||
})
|
||||
decisive_constraints: sorted_strs(fields.decisive_constraints),
|
||||
rationale: fields.rationale.to_string(),
|
||||
evidence_refs: sorted_strs(fields.evidence_refs),
|
||||
confidence: fields.confidence,
|
||||
user_approval_required: fields.user_approval_required,
|
||||
resolution: fields.resolution,
|
||||
}
|
||||
}
|
||||
|
||||
/// A major toolchain change = introducing/changing a language, runtime or
|
||||
@@ -158,7 +238,7 @@ pub fn resolve_toolchain(
|
||||
requested_language: Option<&str>,
|
||||
evidence_refs: Option<&Value>,
|
||||
performance_evidence: Option<&Value>,
|
||||
) -> Value {
|
||||
) -> Result<Value, String> {
|
||||
let refs = sorted_strs(evidence_refs);
|
||||
let existing = known.get("existing_language");
|
||||
let requested = requested_language.map(|r| Value::String(r.to_string()));
|
||||
@@ -172,22 +252,17 @@ pub fn resolve_toolchain(
|
||||
.collect();
|
||||
let requested_truthy = requested.as_ref().map(|r| is_truthy(Some(r))).unwrap_or(false);
|
||||
if !is_truthy(existing) && !requested_truthy && hard_factors.len() < 2 {
|
||||
return decision_record(
|
||||
json!(""),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
None,
|
||||
Some(&json!(hard_factors)),
|
||||
"no existing project toolchain recorded and too few \
|
||||
constraints known to select honestly; refusing to \
|
||||
invent certainty",
|
||||
Some(&json!(refs)),
|
||||
0.0,
|
||||
false,
|
||||
"INSUFFICIENT_EVIDENCE",
|
||||
);
|
||||
return Ok(decision_record(RecordFields {
|
||||
selected_language: json!(""),
|
||||
decisive_constraints: Some(&json!(hard_factors)),
|
||||
rationale: "no existing project toolchain recorded and too few \
|
||||
constraints known to select honestly; refusing to \
|
||||
invent certainty",
|
||||
evidence_refs: Some(&json!(refs)),
|
||||
resolution: Resolution::InsufficientEvidence.as_str().to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.to_value());
|
||||
}
|
||||
|
||||
// DEFAULT: existing toolchain wins
|
||||
@@ -202,23 +277,24 @@ pub fn resolve_toolchain(
|
||||
"performance_evidence(rejected: does not override existing toolchain without user approval)"
|
||||
));
|
||||
}
|
||||
return decision_record(
|
||||
existing.cloned().unwrap_or(json!("")),
|
||||
known.get("existing_runtime"),
|
||||
known.get("existing_framework"),
|
||||
None,
|
||||
known
|
||||
return Ok(decision_record(RecordFields {
|
||||
selected_language: existing.cloned().unwrap_or(json!("")),
|
||||
runtime: known.get("existing_runtime"),
|
||||
framework: known.get("existing_framework"),
|
||||
// the oracle looks this up even though the key can never be
|
||||
// in the known vocabulary — behavior preserved verbatim
|
||||
build_system: known
|
||||
.get("existing_build_system")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or(""),
|
||||
None,
|
||||
Some(&json!(decisive)),
|
||||
"existing project toolchain wins (§8); no recorded evidence shows significant benefit from change",
|
||||
Some(&json!(refs)),
|
||||
if !hard_factors.is_empty() { 0.85 } else { 0.7 },
|
||||
false,
|
||||
"KEEP_EXISTING",
|
||||
);
|
||||
decisive_constraints: Some(&json!(decisive)),
|
||||
rationale: "existing project toolchain wins (§8); no recorded evidence shows significant benefit from change",
|
||||
evidence_refs: Some(&json!(refs)),
|
||||
confidence: if !hard_factors.is_empty() { 0.85 } else { 0.7 },
|
||||
resolution: Resolution::KeepExisting.as_str().to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.to_value());
|
||||
}
|
||||
|
||||
// REQUESTED change away from existing: recommendable, never silent
|
||||
@@ -231,22 +307,24 @@ pub fn resolve_toolchain(
|
||||
if significant {
|
||||
decisive.push(json!("performance_evidence"));
|
||||
}
|
||||
return decision_record(
|
||||
requested.clone().unwrap_or(json!("")),
|
||||
known.get("existing_runtime"),
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
Some(&json!([{"language": existing, "rejected_because": "requested change"}])),
|
||||
Some(&json!(decisive)),
|
||||
"requested language differs from the project's \
|
||||
established toolchain; recommendation only — \
|
||||
user approval required before material change",
|
||||
Some(&json!(refs)),
|
||||
if significant { 0.8 } else { 0.5 },
|
||||
true,
|
||||
"CHANGE_MAJOR",
|
||||
);
|
||||
return Ok(decision_record(RecordFields {
|
||||
selected_language: requested.clone().unwrap_or(json!("")),
|
||||
runtime: known.get("existing_runtime"),
|
||||
alternatives: Some(&json!([{
|
||||
"language": existing,
|
||||
"rejected_because": "requested change",
|
||||
}])),
|
||||
decisive_constraints: Some(&json!(decisive)),
|
||||
rationale: "requested language differs from the project's \
|
||||
established toolchain; recommendation only — \
|
||||
user approval required before material change",
|
||||
evidence_refs: Some(&json!(refs)),
|
||||
confidence: if significant { 0.8 } else { 0.5 },
|
||||
user_approval_required: true,
|
||||
resolution: Resolution::ChangeMajor.as_str().to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.to_value());
|
||||
}
|
||||
|
||||
// Greenfield: we have NO language-selection intelligence (tranche brief
|
||||
@@ -255,35 +333,28 @@ pub fn resolve_toolchain(
|
||||
// never a guess, never silently Python. With a request, record it as a
|
||||
// recommendation requiring approval.
|
||||
if requested_truthy {
|
||||
return decision_record(
|
||||
requested.clone().unwrap_or(json!("")),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
None,
|
||||
Some(&json!(hard_factors)),
|
||||
"greenfield request recorded as recommendation; user approval required",
|
||||
Some(&json!(refs)),
|
||||
0.4,
|
||||
true,
|
||||
"CHANGE_MAJOR",
|
||||
);
|
||||
return Ok(decision_record(RecordFields {
|
||||
selected_language: requested.clone().unwrap_or(json!("")),
|
||||
decisive_constraints: Some(&json!(hard_factors)),
|
||||
rationale: "greenfield request recorded as recommendation; user approval required",
|
||||
evidence_refs: Some(&json!(refs)),
|
||||
confidence: 0.4,
|
||||
user_approval_required: true,
|
||||
resolution: Resolution::ChangeMajor.as_str().to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.to_value());
|
||||
}
|
||||
decision_record(
|
||||
json!(""),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
None,
|
||||
Some(&json!(hard_factors)),
|
||||
"greenfield: no requested language and no language-selection evidence; refusing to invent a choice",
|
||||
Some(&json!(refs)),
|
||||
0.0,
|
||||
true,
|
||||
"INSUFFICIENT_EVIDENCE",
|
||||
)
|
||||
Ok(decision_record(RecordFields {
|
||||
selected_language: json!(""),
|
||||
decisive_constraints: Some(&json!(hard_factors)),
|
||||
rationale: "greenfield: no requested language and no language-selection evidence; refusing to invent a choice",
|
||||
evidence_refs: Some(&json!(refs)),
|
||||
user_approval_required: true,
|
||||
resolution: Resolution::InsufficientEvidence.as_str().to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.to_value())
|
||||
}
|
||||
|
||||
// ── FFI (doc-envelope law) ──────────────────────────────────────────────
|
||||
@@ -345,7 +416,8 @@ pub extern "C" fn tl_constraints_get(arg: *const c_char) -> *mut c_char {
|
||||
/// tl_decision_record({selected_language, runtime?, framework?,
|
||||
/// libraries?, build_system?, alternatives?, decisive_constraints?,
|
||||
/// rationale?, evidence_refs?, confidence?, user_approval_required?,
|
||||
/// resolution?}) → normalized record dict.
|
||||
/// resolution?}) → normalized record dict. An unknown resolution string
|
||||
/// is refused — the wire never carries a guess.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tl_decision_record(arg: *const c_char) -> *mut c_char {
|
||||
envelope(read_arg(arg).and_then(|a| {
|
||||
@@ -353,20 +425,26 @@ pub extern "C" fn tl_decision_record(arg: *const c_char) -> *mut c_char {
|
||||
.get("selected_language")
|
||||
.cloned()
|
||||
.ok_or_else(|| "missing selected_language".to_string())?;
|
||||
Ok(decision_record(
|
||||
selected,
|
||||
a.get("runtime"),
|
||||
a.get("framework"),
|
||||
a.get("libraries"),
|
||||
a.get("build_system").and_then(Value::as_str).unwrap_or(""),
|
||||
a.get("alternatives"),
|
||||
a.get("decisive_constraints"),
|
||||
a.get("rationale").and_then(Value::as_str).unwrap_or(""),
|
||||
a.get("evidence_refs"),
|
||||
a.get("confidence").and_then(Value::as_f64).unwrap_or(0.0),
|
||||
a.get("user_approval_required").and_then(Value::as_bool).unwrap_or(false),
|
||||
a.get("resolution").and_then(Value::as_str).unwrap_or(""),
|
||||
))
|
||||
// the oracle's constructor keeps resolution a free-form string
|
||||
let resolution = a.get("resolution").and_then(Value::as_str).unwrap_or("");
|
||||
let record = decision_record(RecordFields {
|
||||
selected_language: selected,
|
||||
runtime: a.get("runtime"),
|
||||
framework: a.get("framework"),
|
||||
libraries: a.get("libraries"),
|
||||
build_system: a.get("build_system").and_then(Value::as_str).unwrap_or(""),
|
||||
alternatives: a.get("alternatives"),
|
||||
decisive_constraints: a.get("decisive_constraints"),
|
||||
rationale: a.get("rationale").and_then(Value::as_str).unwrap_or(""),
|
||||
evidence_refs: a.get("evidence_refs"),
|
||||
confidence: a.get("confidence").and_then(Value::as_f64).unwrap_or(0.0),
|
||||
user_approval_required: a
|
||||
.get("user_approval_required")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false),
|
||||
resolution: resolution.to_string(),
|
||||
});
|
||||
Ok(record.to_value())
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -377,12 +455,12 @@ pub extern "C" fn tl_decision_record(arg: *const c_char) -> *mut c_char {
|
||||
pub extern "C" fn tl_resolve_toolchain(arg: *const c_char) -> *mut c_char {
|
||||
envelope(read_arg(arg).and_then(|a| {
|
||||
let known = arg_obj(&a, "known")?;
|
||||
Ok(resolve_toolchain(
|
||||
resolve_toolchain(
|
||||
known,
|
||||
a.get("requested_language").and_then(Value::as_str),
|
||||
a.get("evidence_refs"),
|
||||
a.get("performance_evidence").filter(|p| !p.is_null()),
|
||||
))
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user