feat(evidence): record provenance and stop derived evidence counting as corroboration

Every evidence row now records its derivation (direct, assertion, operator,
external, prediction, consolidation, inference, reflection, dream,
retrieval), its independent-source family (an actor, an integration, an
operator, a prediction) and its root event or memory ids. Confidence
groups evidence by family: the strongest primary item counts in full, each
further primary item adds a quarter, capped at 1.5x, and derived items
count only when the family has no primary member. Three derived
re-expressions of one utterance therefore weigh exactly what the utterance
weighs, while three independent families still add up.

Producers (conversation, association, consolidation, predictions,
metacognition, dreaming, operator and seed input, contradictions) supply
their ancestry. Existing rows are marked legacy_unknown with family =
source; nothing is invented. The belief page shows per-family
contributions and the replay test checks that provenance is reconstructed
identically in the sandbox. Migration 0017 is additive.
This commit is contained in:
Andy Dixon
2026-07-31 21:14:52 +01:00
parent d86740af89
commit 0071658c93
18 changed files with 394 additions and 48 deletions
+23 -2
View File
@@ -386,8 +386,10 @@ func (s *Service) hypothesise(ctx context.Context, m minds.Mind, dream ids.ID, p
return out[0], nil
}
ev := []beliefs.EvidenceInput{
{Direction: "for", Kind: "memory", RefID: string(p.A.ID), Source: "dreaming/" + p.A.Source, Weight: 0.1, Credibility: p.A.Credibility, Note: "dream replay: " + excerpt(p.A.Content, 80)},
{Direction: "for", Kind: "memory", RefID: string(p.B.ID), Source: "dreaming/" + p.B.Source, Weight: 0.1, Credibility: p.B.Credibility, Note: "dream replay: " + excerpt(p.B.Content, 80)},
{Direction: "for", Kind: "memory", RefID: string(p.A.ID), Source: "dreaming/" + p.A.Source, Weight: 0.1, Credibility: p.A.Credibility, Note: "dream replay: " + excerpt(p.A.Content, 80),
Derivation: beliefs.DerivationDream, Family: s.familyOf(ctx, p.A), RootIDs: rootsOf(p.A)},
{Direction: "for", Kind: "memory", RefID: string(p.B.ID), Source: "dreaming/" + p.B.Source, Weight: 0.1, Credibility: p.B.Credibility, Note: "dream replay: " + excerpt(p.B.Content, 80),
Derivation: beliefs.DerivationDream, Family: s.familyOf(ctx, p.B), RootIDs: rootsOf(p.B)},
}
detail := map[string]any{"dream_id": dream, "wording": w.Wording, "rationale": w.Rationale, "shared": p.Shared}
if !w.CallID.IsZero() {
@@ -517,3 +519,22 @@ func excerpt(s string, n int) string {
}
return s
}
// familyOf traces a memory to the actor or channel behind its origin event.
func (s *Service) familyOf(ctx context.Context, m memory.Memory) string {
if !m.OriginEvent.IsZero() && s.Events != nil {
if ev, err := s.Events.Get(ctx, m.OriginEvent); err == nil {
if f := beliefs.FamilyFromEvent(ev); f != "" {
return f
}
}
}
return "memory:" + m.Source
}
func rootsOf(m memory.Memory) []string {
if !m.OriginEvent.IsZero() {
return []string{string(m.OriginEvent)}
}
return []string{string(m.ID)}
}