Skip to content

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

Open
mysma-9403 wants to merge 1 commit into
tinyhumansai:mainfrom
mysma-9403:feat/learning-facet-provenance-fields
Open

feat(learning): surface facet provenance (cue_families, evidence_refs) in RPC#5043
mysma-9403 wants to merge 1 commit into
tinyhumansai:mainfrom
mysma-9403:feat/learning-facet-provenance-fields

Conversation

@mysma-9403

@mysma-9403 mysma-9403 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Surface the two provenance fields the learning store already persists — evidence_refs and cue_families — through the facet RPC serializer, which was silently dropping them at the boundary.
  • Additive-only: two keys added to facet_to_json, so every facet-returning controller (list_facets, get_facet, update/pin/unpin/forget) gains them at once.
  • Add a unit test asserting both fields round-trip.

Problem

facet_to_json is the single serializer feeding all facet-returning learning.* controllers. ProfileFacet persists (and row_to_facet hydrates) two provenance columns:

  • evidence_refs: Vec<EvidenceRef> (from evidence_refs_json) — the citations behind a facet.
  • cue_families: Option<HashMap<String, u32>> (from cue_families_json) — per-cue-family evidence counts written by the stability detector.

Both derive Serialize (EvidenceRef is a #[serde(tag = "type")] enum), are documented as first-class ProfileFacet state, 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 in facet_to_json:

"cue_families": f.cue_families,
"evidence_refs": f.evidence_refs,

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 changes.

Scope note: evidence_refs are keyed to a facet's (class, key), not to the specific winning value (the stability detector merges every candidate's evidence for a key before persisting the single value). This PR only stops the serializer from dropping already-persisted state; it does not change what the detector stores. Value-scoped provenance would be a separate, larger change.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — facet_to_json_includes_cue_families_and_evidence_refs builds a facet with populated and empty provenance and asserts both keys serialize correctly (present-and-populated + null/empty edge).
  • Diff coverage ≥ 80% — the two added serializer lines are exercised by the new test. cargo test -p openhuman --lib learning::schemas passes.
  • Coverage matrix updated — N/A: additive read-only field exposure, no new controller/feature row.
  • All affected feature IDs listed under ## RelatedN/A.
  • No new external network dependencies introduced.
  • Manual smoke checklist updated — N/A: does not touch a release-cut surface.
  • Linked issue closed via Closes #NNN — no existing issue; found by inspection.

Impact

  • Desktop/CLI: learning.list_facets / get_facet (and the mutating controllers' echoed facet) now include cue_families and evidence_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

  • Closes:
  • Follow-up PR(s)/TODOs: value-scoped provenance (persist/return refs per winning value) — separate change to the learning storage model.

AI Authored PR Metadata

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/learning-facet-provenance-fields
  • Commit SHA: e4f6889af

Validation Run

  • pnpm --filter openhuman-app format:check — N/A (no frontend change)
  • pnpm typecheck — N/A (no frontend change)
  • Focused tests: cargo test -p openhuman --lib learning::schemas
  • Rust fmt/check (if changed): cargo fmt
  • Tauri fmt/check (if changed): N/A (core-only change)

Behavior Changes

  • Intended behavior change: facet RPC output gains two already-persisted provenance fields.
  • User-visible effect: provenance (evidence_refs, cue_families) is now retrievable via the learning facet controllers.

Parity Contract

  • Legacy behavior preserved: all existing keys unchanged; opaque-JSON output schema contract unaffected; no store/query change.
  • Guard/fallback/dispatch parity checks: None/empty provenance serializes to null/[] (verified by the test's empty-facet case).

…) 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.
@mysma-9403
mysma-9403 requested a review from a team July 18, 2026 13:11
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The learning facet JSON serializer now includes cue_families and evidence_refs. Unit tests cover populated and absent provenance values, and the RPC documentation describes the expanded response.

Changes

Facet provenance output

Layer / File(s) Summary
Serialize and validate facet provenance
src/openhuman/learning/schemas.rs, src/openhuman/learning/README.md
facet_to_json now emits cue_families and evidence_refs; tests verify populated, empty, and missing values, while documentation describes the fields.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: senamakel

Poem

I’m a bunny with fields in my nest,
Provenance now travels with every request.
Cue families hop, evidence refs gleam,
JSON reflects the facet’s full dream.

(\_/)
( •_•)
/ >🥕
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: exposing facet provenance fields in learning RPC output.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mysma-9403

Copy link
Copy Markdown
Contributor Author

CI note — the red checks are not from this change

Rust Core Coverage is red on a pre-existing flaky test, not this PR. The failing test is learning::startup::tests::learning_subscriber_fires_with_no_channel_configured (the email-signature subscriber path in startup.rs). This PR only adds two keys to facet_to_json in schemas.rs plus a serializer unit test — it does not touch startup.rs, the subscriber, or the process-global candidate::global() buffer that test asserts on.

Verified against the PR base d13a559b9 (i.e. without this change): the full openhuman::learning suite — including that test — passes 5/5 local runs. The failure is timing/parallelism-dependent (the test asserts on a shared, process-global candidate buffer); it flaked once under CI parallelism. So it is not a regression introduced here.

Rust Feature-Gate Smoke (gates off) is also unrelated: it fails identically on every fresh-main-based PR with ::error::Gated-test file set changed. Update the EXPECTED allowlist … (see #5022) — a stale allowlist on main, not touched by this change. PR Submission Checklist was my miss (unchecked N/A boxes) and is now fixed in the description.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant