feat(learning): surface facet provenance (cue_families, evidence_refs) in RPC (#5043)

This commit is contained in:
mysma-9403
2026-07-23 16:54:34 +03:00
committed by GitHub
parent f0d21a2a14
commit 83d1dc31ee
2 changed files with 55 additions and 1 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ Namespace `learning` (wired into `src/core/all.rs`; 11 controllers). Methods:
| `learning.rebuild_cache` | Manually trigger a `StabilityDetector` rebuild; returns added/evicted/kept/total_size. |
| `learning.cache_stats` | Cache totals + per-state and per-class breakdown. |
| `learning.list_facets` | List Active + Provisional facets, optional `class` filter. |
| `learning.get_facet` | Fetch one facet by `class` + `key` suffix. |
| `learning.get_facet` | Fetch one facet by `class` + `key` suffix. Each returned facet carries its provenance (`evidence_refs`, `cue_families`) alongside the value/state fields. |
| `learning.update_facet` | Set a facet value and pin it (`user_state = Pinned`). |
| `learning.pin_facet` / `learning.unpin_facet` | Toggle `user_state` Pinned ↔ Auto. |
| `learning.forget_facet` | Mark `Dropped` + `user_state = Forgotten` (blocks re-promotion). |
+54
View File
@@ -475,6 +475,54 @@ mod tests {
assert_eq!(s.function, "unknown");
}
#[test]
fn facet_to_json_includes_cue_families_and_evidence_refs() {
use crate::openhuman::learning::candidate::EvidenceRef;
use crate::openhuman::memory_store::profile::{
FacetState, FacetType, ProfileFacet, UserState,
};
use std::collections::HashMap;
let mut cue_families = HashMap::new();
cue_families.insert("explicit".to_string(), 3u32);
cue_families.insert("structural".to_string(), 1u32);
let facet = ProfileFacet {
facet_id: "f1".into(),
facet_type: FacetType::Preference,
key: "style/verbosity".into(),
value: "terse".into(),
confidence: 0.8,
evidence_count: 4,
source_segment_ids: None,
first_seen_at: 1000.0,
last_seen_at: 1200.0,
state: FacetState::Active,
stability: 0.9,
user_state: UserState::Auto,
evidence_refs: vec![EvidenceRef::Episodic { episodic_id: 42 }],
class: Some("style".into()),
cue_families: Some(cue_families),
};
// Populated provenance round-trips through the serializer.
let json = facet_to_json(&facet);
assert_eq!(json["cue_families"]["explicit"].as_u64(), Some(3));
assert_eq!(json["cue_families"]["structural"].as_u64(), Some(1));
assert_eq!(json["evidence_refs"][0]["type"].as_str(), Some("episodic"));
assert_eq!(json["evidence_refs"][0]["episodic_id"].as_i64(), Some(42));
// Empty/None provenance serializes to []/null (present, not dropped).
let bare = ProfileFacet {
evidence_refs: vec![],
cue_families: None,
..facet
};
let json = facet_to_json(&bare);
assert_eq!(json["evidence_refs"].as_array().map(Vec::len), Some(0));
assert!(json["cue_families"].is_null());
}
#[test]
fn schemas_and_controllers_match() {
let s = all_learning_controller_schemas();
@@ -735,6 +783,12 @@ fn facet_to_json(f: &crate::openhuman::memory_store::profile::ProfileFacet) -> s
"first_seen_at": f.first_seen_at,
"last_seen_at": f.last_seen_at,
"class": f.class,
// Provenance the store already persists and row_to_facet hydrates, but
// the serializer previously dropped: the citations behind the facet and
// the per-cue-family evidence counts. `None`/empty serialize to
// `null`/`[]`.
"cue_families": f.cue_families,
"evidence_refs": f.evidence_refs,
})
}