feat(learning): surface facet provenance (cue_families, evidence_refs) in RPC - #5043
Conversation
📝 WalkthroughWalkthroughThe learning facet JSON serializer now includes ChangesFacet provenance output
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
CI note — the red checks are not from this changeRust Core Coverage is red on a pre-existing flaky test, not this PR. The failing test is Verified against the PR base Rust Feature-Gate Smoke (gates off) is also unrelated: it fails identically on every fresh- Happy to open a small follow-up to de-flake that startup test (give it its own isolated candidate buffer) if a maintainer wants it. |
…) in RPC facet_to_json is the single serializer feeding every facet-returning learning controller (list_facets, get_facet, and the echoed facet on update/pin/unpin/ forget). ProfileFacet persists and row_to_facet hydrates two provenance columns — evidence_refs (Vec<EvidenceRef>, from evidence_refs_json) and cue_families (Option<HashMap<String,u32>>, from cue_families_json) — but the serializer emitted only 10 keys and silently dropped both, so no RPC consumer could see the evidence behind a facet. Add the two fields to the json! macro. Both types already derive Serialize (EvidenceRef is a #[serde(tag = "type")] enum), None/empty serialize to null/[]. The controller output schemas declare facet/facets as opaque TypeSchema::Json, so the schema contract is unchanged; the addition is purely additive metadata. No store or query change. Scope: evidence_refs are (class, key)-scoped, not value-scoped (the stability detector merges every candidate's evidence for a key before persisting the one value). This only stops the serializer dropping already-persisted state. Test: facet_to_json_includes_cue_families_and_evidence_refs asserts populated provenance round-trips and empty/None serializes to []/null. README get_facet row notes the provenance fields.
e4f6889 to
5df19d9
Compare
|
| Filename | Overview |
|---|---|
| src/openhuman/learning/schemas.rs | Adds cue_families and evidence_refs to facet_to_json; both fields are correctly typed and tested. The list_facets schema comment's explicit field enumeration is now stale. |
| src/openhuman/learning/README.md | Updates the get_facet row to mention provenance fields, but omits the same update for list_facets and the five other facet-returning controllers that are equally affected. |
Sequence Diagram
sequenceDiagram
participant Client
participant Controller as learning.* controller
participant Cache as FacetCache
participant Store as memory_store::profile
participant Ser as facet_to_json
Client->>Controller: RPC call (list_facets / get_facet / update / pin / unpin / forget)
Controller->>Cache: list_all() / get() / upsert()
Cache->>Store: SQL query (row_to_facet hydrates evidence_refs, cue_families)
Store-->>Cache: ProfileFacet (with evidence_refs, cue_families populated)
Cache-->>Controller: ProfileFacet
Controller->>Ser: "facet_to_json(&facet)"
Note over Ser: Previously dropped evidence_refs + cue_families
Note over Ser: Now includes both fields
Ser-->>Controller: JSON Value (+ evidence_refs, cue_families)
Controller-->>Client: RPC response with provenance
Comments Outside Diff (1)
-
src/openhuman/learning/schemas.rs, line 243-244 (link)The
list_facetsschema comment explicitly enumerates the fields of each facet object ("key, value, state, user_state, stability") but omits the two newly surfaced provenance fields. Any tooling or client that reads this comment to understand the contract will have a misleading view of the output shape.
Reviews (1): Last reviewed commit: "feat(learning): surface facet provenance..." | Re-trigger Greptile
| | `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. | |
There was a problem hiding this comment.
The README table was updated only for
get_facet, but all six facet-returning controllers (list_facets, get_facet, update_facet, pin_facet, unpin_facet, forget_facet) now carry provenance fields since they all go through facet_to_json. A reader consulting the table for list_facets would not know the provenance fields are present.
| | `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.list_facets` | List Active + Provisional facets, optional `class` filter. Each returned facet carries its provenance (`evidence_refs`, `cue_families`) alongside the value/state fields. | | |
| | `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. | |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
evidence_refsandcue_families— through the facet RPC serializer, which was silently dropping them at the boundary.facet_to_json, so every facet-returning controller (list_facets,get_facet,update/pin/unpin/forget) gains them at once.Problem
facet_to_jsonis the single serializer feeding all facet-returninglearning.*controllers.ProfileFacetpersists (androw_to_facethydrates) two provenance columns:evidence_refs: Vec<EvidenceRef>(fromevidence_refs_json) — the citations behind a facet.cue_families: Option<HashMap<String, u32>>(fromcue_families_json) — per-cue-family evidence counts written by the stability detector.Both derive
Serialize(EvidenceRefis a#[serde(tag = "type")]enum), are documented as first-classProfileFacetstate, and exist specifically as provenance — yet the serializer emits only 10 keys and drops both, so no RPC consumer can see the evidence behind a facet.Solution
Add the two fields to the
json!macro infacet_to_json:The controller output schemas declare
facet/facetsas opaqueTypeSchema::Json, so the schema contract is unchanged; the addition is purely additive metadata. No store or query changes.Submission Checklist
facet_to_json_includes_cue_families_and_evidence_refsbuilds a facet with populated and empty provenance and asserts both keys serialize correctly (present-and-populated + null/empty edge).cargo test -p openhuman --lib learning::schemaspasses.N/A: additive read-only field exposure, no new controller/feature row.## Related—N/A.N/A: does not touch a release-cut surface.Closes #NNN— no existing issue; found by inspection.Impact
learning.list_facets/get_facet(and the mutating controllers' echoed facet) now includecue_familiesandevidence_refs, enabling a transparency/provenance UI to cite the evidence behind a learned facet without a second round-trip. Purely additive; no behaviour change for existing consumers.Related
AI Authored PR Metadata
Linear Issue
Commit & Branch
feat/learning-facet-provenance-fieldse4f6889afValidation Run
pnpm --filter openhuman-app format:check— N/A (no frontend change)pnpm typecheck— N/A (no frontend change)cargo test -p openhuman --lib learning::schemascargo fmtBehavior Changes
evidence_refs,cue_families) is now retrievable via the learning facet controllers.Parity Contract
None/empty provenance serializes tonull/[](verified by the test's empty-facet case).