Skip to content

Commit de9cdef

Browse files
fix(temporal): keep unknown-validity sources eligible, add summary lineage
Ingested messages carry no valid-time assertion, so rejecting summary lineage on UnknownSourceValidTime made every production summary node permanently unavailable; that uncertainty already surfaces through the occurrence-level coverage axis. Eligible summaries now also emit Supports lineage edges to their source anchors so describes stay traceable without a stored assertion row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 67a7f25 commit de9cdef

3 files changed

Lines changed: 52 additions & 13 deletions

File tree

src/query/temporal/mod.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use std::fmt;
1212
use thiserror::Error;
1313
use tracedecay_domain::{
1414
CompactContextConflictV1, CompactContextLineageEdgeV1, CompactContextOmissionV1,
15-
ContextOmissionReasonV1, HydrationStateV1, RetrievalAnchorId, SessionSummaryRecordV1,
16-
TemporalAssertionKindV1, TemporalCoverageCountsV1, TemporalModeV1,
15+
ContextOmissionReasonV1, HydrationStateV1, RetrievalAnchorId, SessionAuthorityClassV1,
16+
SessionSummaryRecordV1, TemporalAssertionKindV1, TemporalCoverageCountsV1, TemporalModeV1,
1717
};
1818
use zeroize::Zeroizing;
1919

@@ -311,6 +311,7 @@ pub async fn execute_temporal_kernel(
311311
&resolved,
312312
&resolved.lineage_edges,
313313
&hydration,
314+
&records.summaries,
314315
&summary_eligibility,
315316
);
316317
let context = assemble_context_with_frames_controlled(
@@ -472,6 +473,7 @@ fn temporal_context_frames(
472473
resolved: &[ResolvedOccurrence],
473474
lineage_edges: &[ResolutionLineageEdge],
474475
hydration: &HydrationBatch,
476+
summaries: &[SessionSummaryRecordV1],
475477
summary_eligibility: &SummaryLineageEligibility,
476478
) -> TemporalContextFrames {
477479
let unknown_anchors = resolved
@@ -517,7 +519,31 @@ fn temporal_context_frames(
517519
supporting_anchor_ids: item.supporting_anchor_ids.clone(),
518520
})
519521
.collect();
520-
let lineage = lineage_edges.iter().map(context_lineage_edge).collect();
522+
let mut lineage: Vec<CompactContextLineageEdgeV1> =
523+
lineage_edges.iter().map(context_lineage_edge).collect();
524+
// Eligible summaries carry their own provenance: each summary anchor
525+
// supports-derives from its source anchors. Surfacing that as Supports
526+
// lineage keeps summary describes traceable without a stored assertion
527+
// row per source.
528+
for summary in summaries {
529+
if !summary_eligibility
530+
.eligible_anchor_ids
531+
.contains(summary.summary_anchor_id())
532+
{
533+
continue;
534+
}
535+
for source_anchor in summary.source_anchors() {
536+
lineage.push(CompactContextLineageEdgeV1 {
537+
kind: TemporalAssertionKindV1::Supports,
538+
subject_anchor_id: summary.summary_anchor_id().clone(),
539+
object_anchor_id: source_anchor.clone(),
540+
knowledge_at: summary.created_at(),
541+
authority: SessionAuthorityClassV1::ImmutableSummary,
542+
authorized: true,
543+
supporting_anchor_ids: BTreeSet::new(),
544+
});
545+
}
546+
}
521547
let summary_omissions = public_summary_omissions(summary_eligibility);
522548
let omissions = summary_omissions
523549
.iter()
@@ -811,6 +837,7 @@ mod scope_tests {
811837
&[],
812838
&[],
813839
&HydrationBatch::default(),
840+
&[],
814841
&eligibility,
815842
);
816843

@@ -885,6 +912,7 @@ mod scope_tests {
885912
&[],
886913
&[],
887914
&HydrationBatch::default(),
915+
&[],
888916
&eligibility,
889917
);
890918

@@ -958,6 +986,7 @@ mod scope_tests {
958986
&[],
959987
&[],
960988
&HydrationBatch::default(),
989+
&[],
961990
&eligibility,
962991
);
963992

src/query/temporal/resolution/summary.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,13 @@ fn summary_source_rejection(
195195
anchor_id: anchor_id.clone(),
196196
})
197197
}
198-
(TemporalValidityV1::Unknown, Some(_)) => {
199-
Some(SummaryLineageRejection::UnknownSourceValidTime {
200-
anchor_id: anchor_id.clone(),
201-
})
202-
}
198+
// Sources routinely carry no valid-time assertion (all
199+
// ingested messages today): that uncertainty is already
200+
// surfaced per-occurrence through the coverage
201+
// `unknown` axis, so it must not reject the summary's
202+
// whole lineage — only a provably out-of-horizon
203+
// source does.
204+
(TemporalValidityV1::Unknown, Some(_)) => None,
203205
(_, None) => None,
204206
}
205207
}

src/query/temporal/resolution/tests.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,7 +1583,7 @@ fn non_current_summary_modes_retain_eligible_predecessors() {
15831583
}
15841584

15851585
#[test]
1586-
fn missing_and_unknown_validity_sources_have_distinct_rejections() {
1586+
fn unknown_validity_sources_stay_eligible_while_missing_sources_reject() {
15871587
let session_id: SessionId = serde_json::from_str("\"session-1\"").expect("valid session id");
15881588
let missing = summary("missing", "summary-missing", "missing-source", 7, 7);
15891589
let unknown_valid = summary("unknown-valid", "summary-unknown", "unknown-source", 7, 7);
@@ -1614,12 +1614,20 @@ fn missing_and_unknown_validity_sources_have_distinct_rejections() {
16141614
.get(&SessionSummaryIdV1::new("missing").expect("valid id")),
16151615
Some(SummaryLineageRejection::MissingSource { .. })
16161616
));
1617-
assert!(matches!(
1617+
// Ingested messages carry no valid-time assertion today; that
1618+
// uncertainty surfaces through occurrence-level coverage, not by
1619+
// rejecting the summary's lineage outright.
1620+
assert!(
16181621
eligibility
16191622
.rejections
1620-
.get(&SessionSummaryIdV1::new("unknown-valid").expect("valid id")),
1621-
Some(SummaryLineageRejection::UnknownSourceValidTime { .. })
1622-
));
1623+
.get(&SessionSummaryIdV1::new("unknown-valid").expect("valid id"))
1624+
.is_none()
1625+
);
1626+
assert!(
1627+
eligibility
1628+
.eligible_anchor_ids
1629+
.contains(&anchor("summary-unknown"))
1630+
);
16231631
}
16241632

16251633
#[test]

0 commit comments

Comments
 (0)