From 2667aec1f6ef6a5e647372cb92a57f5d5645fa34 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 25 Aug 2026 07:10:16 +0000 Subject: [PATCH 1/4] Add span-grounded event mentions with exact-extent recovery. Protected main already isolates TDT/CHRONOS scoring gates. Mentions still lacked exact source extents. This slice binds each mention to one SourceSpan, six-clock cutoff evidence, extractor version, and review status, and scores ACE exact-extent precision/recall against known documentary truth. Co-authored-by: Seongho Bae --- ARCHITECTURE.md | 2 +- CHANGELOG.md | 1 + DOCUMENTATION.md | 6 + crates/event_core/src/error.rs | 33 ++ crates/event_core/src/lib.rs | 18 +- crates/event_core/src/span_mention.rs | 432 ++++++++++++++++++ .../tests/span_grounded_mention_contract.rs | 263 +++++++++++ docs/TRACEABILITY.md | 1 + ...tdt-chronos-event-intelligence-boundary.md | 1 + docs/adr/README.md | 2 +- docs/doctoring/span-grounded-mentions.md | 39 ++ docs/product-technical-gap-baseline.md | 2 +- docs/research/span-grounded-mentions.md | 43 ++ docs/research/standards-and-literature.md | 2 + docs/validation/temporal-event-foundation.md | 1 + 15 files changed, 842 insertions(+), 4 deletions(-) create mode 100644 crates/event_core/src/span_mention.rs create mode 100644 crates/event_core/tests/span_grounded_mention_contract.rs create mode 100644 docs/doctoring/span-grounded-mentions.md create mode 100644 docs/research/span-grounded-mentions.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1505c7da..fd7f8e4f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -60,7 +60,7 @@ boundaries above remain the target modular MSA architecture. | `semantic_core` | span-grounded semantic units; language is not identity | | `location_membership` | location is not entity identity and not a language channel | | `temporal_core` | typed clocks, intervals, and temporal reasoning | -| `event_core` | event instances, mentions, roles, provenance, and CHRONOS occurrence-prediction calibration | +| `event_core` | event instances, span-grounded mentions, roles, provenance, and CHRONOS occurrence-prediction calibration | | `relation_graph` | typed relations and forward-transition validation | | `membership_core` | time-varying cross-classified multiple membership, Kish ESS, nested ICC with non-nested refusal | | `role_contradiction` | customer and competitor cannot occupy the same group | diff --git a/CHANGELOG.md b/CHANGELOG.md index 262bffc0..1cb007e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `event_core` span-grounded mentions: a mention cites one exact `SourceSpan`, six-clock evidence, extractor/model version, and proposed/reviewed status; exact-extent precision/recall recover known ACE extents with occupancy RMSE 0 versus whole-document RMSE 1; reviewed status and TDT/CHRONOS detections still cannot promote an instance. Interval consistency, persistence, and exports remain later #170 work. - Restored protected-main gate integrity after the consolidation merges: hourly-scheduler prompt-contract tests now assert the gap-baseline-derived task contract (Gap ID naming, no invented weights) instead of stale increment-specific tokens, the operator-gap register inventory matches the live 33-PR queue, `evidence_core::image_unit` non-image/empty-subtype refusals and `load_union_branch_totals` valid-record accumulation have exact coverage, and the README crate fence plus duplicate registry entries stay deduped. - Branch-coverage diagnostics on the post-consolidation head exposed two uncovered outcomes in `evidence_core::image_unit` (`is_image_media_type_token` non-image prefix and empty-subtype refusals), one uncovered authored line (the strip-prefix refusal), and lost valid-record coverage for `load_union_branch_totals`; exact red-to-green cases now cover the non-image/empty-subtype data URIs and per-coordinate True/False accumulation. - Repaired post-consolidation merge fallout that left protected `main` red: restored the lost `return True` in the `check_coverage.py` match-guard branch, removed the shadowed duplicate `load_union_branch_totals` and `_is_multiline_match_guard` definitions plus duplicate workspace-crate entries (`episode_membership`, `analysis_engine`) from the contract tuple and Cargo member arrays, split two union-fused four-tuples back into `(variant, message)` pairs in the `event_core` error table, repaired the fused `identity_recovery_rate` body in `episode_membership::window`, deduplicated the checked-arithmetic eligible-count block in `analysis_engine`, fixed four-argument `unit()` test call sites, rebalanced the README crate-list fence around all 54 unique crates, and deduplicated the `location_membership`/`validation_core`/`tepp_api` architecture-table rows. Also documents private `PLAUSIBLE_IMAGE_MEDIA_TYPES` so `cargo doc -D warnings` passes. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index a2ca132f..6fa4b968 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -44,6 +44,8 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Rubin total-variance doctoring | [`docs/research/rubin-total-variance.md`](docs/research/rubin-total-variance.md) | | Strong-invariance latent-mean doctoring | [`docs/research/strong-invariance-latent-means.md`](docs/research/strong-invariance-latent-means.md) | | Mention-confidence Brier doctoring | [`docs/research/mention-confidence-brier.md`](docs/research/mention-confidence-brier.md) | +| Span-grounded mention extent doctoring | [`docs/research/span-grounded-mentions.md`](docs/research/span-grounded-mentions.md) | +| Span-grounded mention numeric provenance | [`docs/doctoring/span-grounded-mentions.md`](docs/doctoring/span-grounded-mentions.md) | | Event-intelligence status-gate doctoring | [`docs/research/event-intelligence-status-gates.md`](docs/research/event-intelligence-status-gates.md) | | TDT link-detection precision/recall doctoring | [`docs/research/event-link-detection-calibration.md`](docs/research/event-link-detection-calibration.md) | | First-story detection FAR/miss doctoring | [`docs/research/first-story-detection-calibration.md`](docs/research/first-story-detection-calibration.md) | @@ -61,6 +63,8 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Relation absence is not negative evidence | [`docs/research/relation-absence-not-negative.md`](docs/research/relation-absence-not-negative.md) | | Adaptive orchestration router doctoring | [`docs/research/adaptive-orchestration-router.md`](docs/research/adaptive-orchestration-router.md) | | Mention-confidence Brier doctoring | [`docs/research/mention-confidence-brier.md`](docs/research/mention-confidence-brier.md) | +| Span-grounded mention extent doctoring | [`docs/research/span-grounded-mentions.md`](docs/research/span-grounded-mentions.md) | +| Span-grounded mention numeric provenance | [`docs/doctoring/span-grounded-mentions.md`](docs/doctoring/span-grounded-mentions.md) | | Event-intelligence status-gate doctoring | [`docs/research/event-intelligence-status-gates.md`](docs/research/event-intelligence-status-gates.md) | | Stopword-deletion doctoring | [`docs/research/stopword-deletion.md`](docs/research/stopword-deletion.md) | | Topic log-ratio coordinate doctoring | [`docs/research/topic-logratio-coordinates.md`](docs/research/topic-logratio-coordinates.md) | @@ -131,6 +135,8 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Actions workflow fleet audit | [`docs/operations/ACTIONS_WORKFLOW_FLEET.md`](docs/operations/ACTIONS_WORKFLOW_FLEET.md) | | Actions fleet research doctoring | [`docs/research/actions-workflow-fleet.md`](docs/research/actions-workflow-fleet.md) | | Mention-confidence Brier doctoring | [`docs/research/mention-confidence-brier.md`](docs/research/mention-confidence-brier.md) | +| Span-grounded mention extent doctoring | [`docs/research/span-grounded-mentions.md`](docs/research/span-grounded-mentions.md) | +| Span-grounded mention numeric provenance | [`docs/doctoring/span-grounded-mentions.md`](docs/doctoring/span-grounded-mentions.md) | | Event-intelligence status-gate doctoring | [`docs/research/event-intelligence-status-gates.md`](docs/research/event-intelligence-status-gates.md) | | VRAM budget / GPU fallback doctoring | [`docs/research/vram-budget-types.md`](docs/research/vram-budget-types.md) | | Causal-identification gate doctoring | [`docs/research/causal-identification-gate.md`](docs/research/causal-identification-gate.md) | diff --git a/crates/event_core/src/error.rs b/crates/event_core/src/error.rs index cf6bd4f7..37ba2bab 100644 --- a/crates/event_core/src/error.rs +++ b/crates/event_core/src/error.rs @@ -56,6 +56,16 @@ pub enum EventError { PredictionIsNotEventInstance, /// An unknown occurrence-truth label was supplied. UnknownOccurrenceTruth, + /// A span-grounded mention was treated as an event instance. + SpanMentionIsNotEventInstance, + /// Mention availability is after the knowledge cutoff. + MentionIneligibleAtCutoff, + /// The extractor or model version was empty or whitespace-only. + EmptyExtractorVersion, + /// The mention span does not belong to the supplied document. + MentionSpanDocumentMismatch, + /// An unknown mention-review status name was supplied. + UnknownMentionReviewStatus, } impl fmt::Display for EventError { @@ -94,6 +104,11 @@ impl fmt::Display for EventError { Self::UnknownStoryBoundaryLabel => "unknown story boundary label", Self::PredictionIsNotEventInstance => "CHRONOS prediction is not an event instance", Self::UnknownOccurrenceTruth => "unknown occurrence truth label", + Self::SpanMentionIsNotEventInstance => "span-grounded mention is not an event instance", + Self::MentionIneligibleAtCutoff => "mention availability is after the knowledge cutoff", + Self::EmptyExtractorVersion => "empty extractor version", + Self::MentionSpanDocumentMismatch => "mention span does not belong to the document", + Self::UnknownMentionReviewStatus => "unknown mention review status", }; formatter.write_str(message) } @@ -106,6 +121,7 @@ mod tests { use super::EventError; #[test] + #[allow(clippy::too_many_lines)] fn error_messages_are_stable() { for (error, message) in [ ( @@ -199,6 +215,23 @@ mod tests { EventError::UnknownOccurrenceTruth, "unknown occurrence truth label", ), + ( + EventError::SpanMentionIsNotEventInstance, + "span-grounded mention is not an event instance", + ), + ( + EventError::MentionIneligibleAtCutoff, + "mention availability is after the knowledge cutoff", + ), + (EventError::EmptyExtractorVersion, "empty extractor version"), + ( + EventError::MentionSpanDocumentMismatch, + "mention span does not belong to the document", + ), + ( + EventError::UnknownMentionReviewStatus, + "unknown mention review status", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/event_core/src/lib.rs b/crates/event_core/src/lib.rs index 4c8eb716..7bc2667b 100644 --- a/crates/event_core/src/lib.rs +++ b/crates/event_core/src/lib.rs @@ -12,7 +12,10 @@ //! artifacts until independently promoted. Track assignments, story //! segmentations, CHRONOS schema-slot predictions, and occurrence forecasts //! remain measurement or hypothesis artifacts and cannot promote an instance -//! without an explicit evidence-backed promotion gate. +//! without an explicit evidence-backed promotion gate. Span-grounded mentions +//! cite exact source extents, six-clock evidence, extractor version, and +//! review status, and they remain observed evidence until independently +//! promoted. mod confidence; mod error; @@ -27,6 +30,7 @@ mod registry; mod role; mod schema; mod segment; +mod span_mention; mod track; /// Finite confidence on the closed unit interval. @@ -131,6 +135,18 @@ pub use segment::story_boundary_recall; pub use segment::story_pk; /// Pevzner–Hearst `WindowDiff` against a known-truth segmentation. pub use segment::story_window_diff; +/// Six-clock evidence bound to one mention. +pub use span_mention::MentionEvidenceClocks; +/// Proposed-versus-reviewed mention inspection status. +pub use span_mention::MentionReviewStatus; +/// Fallible mention grounded in one exact source extent. +pub use span_mention::SpanGroundedMention; +/// Precision of recovered mention extents against known truth. +pub use span_mention::mention_span_precision; +/// Recall of recovered mention extents against known truth. +pub use span_mention::mention_span_recall; +/// Explicit refusal to treat a span-grounded mention as an instance. +pub use span_mention::refuse_span_mention_as_instance; /// Assignment of one mention to one hypothesized TDT track. pub use track::EventTrackAssignment; /// Opaque TDT track identity. diff --git a/crates/event_core/src/span_mention.rs b/crates/event_core/src/span_mention.rs new file mode 100644 index 00000000..73678252 --- /dev/null +++ b/crates/event_core/src/span_mention.rs @@ -0,0 +1,432 @@ +//! Span-grounded mentions stay distinct from promoted event instances. + +use crate::{EventConfidence, EventError, EventEvidenceLayer, EventInstanceId, EventMentionId}; +use evidence_core::{DocumentRecord, EvidenceId, SourceSpan}; +use std::collections::BTreeSet; +use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, +}; + +/// Review status of one fallible mention. +/// +/// Reviewed status is authorized inspection. It is not promotion into an +/// event instance or a forward state transition. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MentionReviewStatus { + /// The mention is hypothesized and has not been reviewed. + Proposed, + /// An authorized reviewer inspected the mention. + Reviewed, +} + +impl MentionReviewStatus { + /// Return the stable wire review-status name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::Proposed => "proposed", + Self::Reviewed => "reviewed", + } + } + + /// Parse a stable wire review-status name. + /// + /// # Errors + /// + /// Returns [`EventError::UnknownMentionReviewStatus`] for unrecognized names. + pub fn from_wire_name(name: &str) -> Result { + match name { + "proposed" => Ok(Self::Proposed), + "reviewed" => Ok(Self::Reviewed), + _ => Err(EventError::UnknownMentionReviewStatus), + } + } + + /// Return whether an authorized reviewer inspected the mention. + #[must_use] + pub const fn is_reviewed(self) -> bool { + matches!(self, Self::Reviewed) + } +} + +/// Six-clock evidence bound to one mention. +/// +/// Availability after the knowledge cutoff fails closed. Event time, assertion +/// time, and document time cannot replace availability. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MentionEvidenceClocks { + event_time: EventTime, + assertion_time: AssertionTime, + document_time: DocumentTime, + system_time: SystemTime, + available_time: AvailableTime, + knowledge_cutoff: KnowledgeCutoff, +} + +impl MentionEvidenceClocks { + /// Bind six typed clocks and refuse late availability. + /// + /// # Errors + /// + /// Returns [`EventError::MentionIneligibleAtCutoff`] when availability is + /// after the knowledge cutoff. + pub fn new( + event_time: EventTime, + assertion_time: AssertionTime, + document_time: DocumentTime, + system_time: SystemTime, + available_time: AvailableTime, + knowledge_cutoff: KnowledgeCutoff, + ) -> Result { + if available_time.instant().as_nanosecond() > knowledge_cutoff.instant().as_nanosecond() { + return Err(EventError::MentionIneligibleAtCutoff); + } + Ok(Self { + event_time, + assertion_time, + document_time, + system_time, + available_time, + knowledge_cutoff, + }) + } + + /// Return the claimed event time. + #[must_use] + pub const fn event_time(self) -> EventTime { + self.event_time + } + + /// Return the assertion time. + #[must_use] + pub const fn assertion_time(self) -> AssertionTime { + self.assertion_time + } + + /// Return the document time. + #[must_use] + pub const fn document_time(self) -> DocumentTime { + self.document_time + } + + /// Return the system time. + #[must_use] + pub const fn system_time(self) -> SystemTime { + self.system_time + } + + /// Return the availability time. + #[must_use] + pub const fn available_time(self) -> AvailableTime { + self.available_time + } + + /// Return the knowledge cutoff. + #[must_use] + pub const fn knowledge_cutoff(self) -> KnowledgeCutoff { + self.knowledge_cutoff + } +} + +/// A fallible mention grounded in one exact source extent. +/// +/// The surface form is the document substring selected by the span. The +/// mention remains observed evidence and cannot promote an instance. +#[derive(Clone, Debug, PartialEq)] +pub struct SpanGroundedMention { + mention_id: EventMentionId, + evidence_id: EvidenceId, + surface_form: String, + confidence: EventConfidence, + source_span: SourceSpan, + clocks: MentionEvidenceClocks, + extractor_version: String, + review_status: MentionReviewStatus, +} + +impl SpanGroundedMention { + /// Bind a mention to one validated document span and six-clock evidence. + /// + /// # Errors + /// + /// Returns [`EventError::MentionSpanDocumentMismatch`] when the span does + /// not belong to `document`, or [`EventError::EmptyExtractorVersion`] when + /// the extractor version is empty or whitespace-only. + pub fn new( + document: &DocumentRecord, + source_span: SourceSpan, + confidence: EventConfidence, + clocks: MentionEvidenceClocks, + extractor_version: impl Into, + review_status: MentionReviewStatus, + ) -> Result { + if document.id() != source_span.document_id() { + return Err(EventError::MentionSpanDocumentMismatch); + } + let extractor_version = extractor_version.into(); + if extractor_version.trim().is_empty() { + return Err(EventError::EmptyExtractorVersion); + } + let surface_form = + document.text()[source_span.byte_start()..source_span.byte_end()].to_string(); + Ok(Self { + mention_id: EventMentionId::new(), + evidence_id: source_span.document_id(), + surface_form, + confidence, + source_span, + clocks, + extractor_version, + review_status, + }) + } + + /// Return the mention identifier. + #[must_use] + pub const fn mention_id(&self) -> EventMentionId { + self.mention_id + } + + /// Return the grounding document identifier. + #[must_use] + pub const fn document_id(&self) -> EvidenceId { + self.source_span.document_id() + } + + /// Return the grounding evidence identifier. + #[must_use] + pub const fn evidence_id(&self) -> EvidenceId { + self.evidence_id + } + + /// Return the exact document substring selected by the span. + #[must_use] + pub fn surface_form(&self) -> &str { + &self.surface_form + } + + /// Return mention confidence. + #[must_use] + pub const fn confidence(&self) -> EventConfidence { + self.confidence + } + + /// Return the exact source extent. + #[must_use] + pub const fn source_span(&self) -> SourceSpan { + self.source_span + } + + /// Return the six-clock evidence. + #[must_use] + pub const fn clocks(&self) -> MentionEvidenceClocks { + self.clocks + } + + /// Return the extractor or model version. + #[must_use] + pub fn extractor_version(&self) -> &str { + &self.extractor_version + } + + /// Return the review status. + #[must_use] + pub const fn review_status(&self) -> MentionReviewStatus { + self.review_status + } + + /// Return the epistemic layer retained by the mention. + #[must_use] + pub const fn evidence_layer(&self) -> EventEvidenceLayer { + EventEvidenceLayer::ObservedMention + } +} + +/// Explicit refusal to treat a span-grounded mention as an event instance. +/// +/// # Errors +/// +/// Always returns [`EventError::SpanMentionIsNotEventInstance`]. +pub fn refuse_span_mention_as_instance( + _mention: &SpanGroundedMention, +) -> Result { + Err(EventError::SpanMentionIsNotEventInstance) +} + +/// Precision of recovered mention extents against known-truth extents. +/// +/// An extent matches when document identity and exact byte bounds agree. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when either extent set is empty +/// or a `(document, byte_start, byte_end)` triple is duplicated. +pub fn mention_span_precision( + truth: &[SourceSpan], + recovered: &[SourceSpan], +) -> Result { + let truth_spans = unique_extent_set(truth)?; + let recovered_spans = unique_extent_set(recovered)?; + counted_rate( + recovered_spans.intersection(&truth_spans).count(), + recovered_spans.len(), + ) +} + +/// Recall of recovered mention extents against known-truth extents. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when either extent set is empty +/// or a `(document, byte_start, byte_end)` triple is duplicated. +pub fn mention_span_recall( + truth: &[SourceSpan], + recovered: &[SourceSpan], +) -> Result { + let truth_spans = unique_extent_set(truth)?; + let recovered_spans = unique_extent_set(recovered)?; + counted_rate( + recovered_spans.intersection(&truth_spans).count(), + truth_spans.len(), + ) +} + +fn unique_extent_set(spans: &[SourceSpan]) -> Result, EventError> { + if spans.is_empty() { + return Err(EventError::InvalidWirePayload); + } + let mut extents = BTreeSet::new(); + for span in spans { + if !extents.insert(( + span.document_id().as_uuid().as_u128(), + span.byte_start(), + span.byte_end(), + )) { + return Err(EventError::InvalidWirePayload); + } + } + Ok(extents) +} + +fn counted_rate(numerator: usize, denominator: usize) -> Result { + let numerator = u32::try_from(numerator).map_err(|_| EventError::InvalidWirePayload)?; + let denominator = u32::try_from(denominator).map_err(|_| EventError::InvalidWirePayload)?; + if denominator == 0 { + return Err(EventError::InvalidWirePayload); + } + Ok(f64::from(numerator) / f64::from(denominator)) +} + +#[cfg(test)] +mod tests { + use super::{ + MentionEvidenceClocks, MentionReviewStatus, SpanGroundedMention, counted_rate, + mention_span_precision, mention_span_recall, refuse_span_mention_as_instance, + unique_extent_set, + }; + use crate::{EventConfidence, EventError, EventEvidenceLayer}; + use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; + use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, + }; + + fn document(text: &str) -> DocumentRecord { + let artifact = SourceArtifact::from_bytes(text.as_bytes()).expect("artifact"); + DocumentRecord::from_text(artifact.id(), text).expect("document") + } + + fn span(document: &DocumentRecord, surface: &str) -> SourceSpan { + let byte_start = document.text().find(surface).expect("surface"); + let byte_end = byte_start + surface.len(); + let scalar_start = document.text()[..byte_start].chars().count(); + let scalar_end = scalar_start + surface.chars().count(); + SourceSpan::new( + document, + byte_start, + byte_end, + scalar_start, + scalar_end, + None, + ) + .expect("span") + } + + fn clocks_at(available: &str, cutoff: &str) -> Result { + MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339(available).expect("available"), + KnowledgeCutoff::parse_rfc3339(cutoff).expect("cutoff"), + ) + } + + #[test] + fn local_helpers_cover_review_clock_and_rate_branches() { + assert!(MentionReviewStatus::Reviewed.is_reviewed()); + assert!(!MentionReviewStatus::Proposed.is_reviewed()); + assert_eq!( + MentionReviewStatus::from_wire_name("proposed").expect("parse"), + MentionReviewStatus::Proposed + ); + assert_eq!(MentionReviewStatus::Reviewed.wire_name(), "reviewed"); + let cutoff_equal = + clocks_at("2026-03-31T00:00:00Z", "2026-03-31T00:00:00Z").expect("equal"); + assert_eq!( + cutoff_equal.event_time(), + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event") + ); + assert_eq!( + cutoff_equal.assertion_time(), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion") + ); + assert_eq!( + cutoff_equal.document_time(), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document") + ); + assert_eq!( + cutoff_equal.system_time(), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system") + ); + assert_eq!( + cutoff_equal.knowledge_cutoff(), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff") + ); + let record = document("award filed"); + let mention = SpanGroundedMention::new( + &record, + span(&record, "award"), + EventConfidence::certain().expect("certain"), + cutoff_equal, + "ace-extent-extractor/1", + MentionReviewStatus::Reviewed, + ) + .expect("mention"); + assert_eq!( + refuse_span_mention_as_instance(&mention), + Err(EventError::SpanMentionIsNotEventInstance) + ); + let _ = mention.mention_id(); + assert_eq!( + mention.evidence_layer(), + EventEvidenceLayer::ObservedMention + ); + let award = span(&record, "award"); + assert!( + (mention_span_precision(&[award], &[award]).expect("p") - 1.0).abs() < f64::EPSILON + ); + assert!((mention_span_recall(&[award], &[award]).expect("r") - 1.0).abs() < f64::EPSILON); + assert_eq!(unique_extent_set(&[]), Err(EventError::InvalidWirePayload)); + assert_eq!(counted_rate(0, 0), Err(EventError::InvalidWirePayload)); + assert_eq!( + counted_rate(usize::MAX, 1), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + counted_rate(1, usize::MAX), + Err(EventError::InvalidWirePayload) + ); + assert!((counted_rate(1, 2).expect("half") - 0.5).abs() < f64::EPSILON); + } +} diff --git a/crates/event_core/tests/span_grounded_mention_contract.rs b/crates/event_core/tests/span_grounded_mention_contract.rs new file mode 100644 index 00000000..67dd8c50 --- /dev/null +++ b/crates/event_core/tests/span_grounded_mention_contract.rs @@ -0,0 +1,263 @@ +//! Span-grounded mentions recover exact ACE extents against known truth. + +use event_core::{ + EventConfidence, EventError, EventEvidenceLayer, MentionEvidenceClocks, MentionReviewStatus, + SpanGroundedMention, mention_span_precision, mention_span_recall, + refuse_span_mention_as_instance, +}; +use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; +use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, +}; + +const DOCUMENT_TEXT: &str = "The procurement office awarded the river-crossing contract on 1 March 2026 after the earlier protest was withdrawn."; + +fn documentary_record() -> DocumentRecord { + let artifact = SourceArtifact::from_bytes(DOCUMENT_TEXT.as_bytes()).expect("artifact"); + DocumentRecord::from_text(artifact.id(), DOCUMENT_TEXT).expect("document") +} + +fn span_for(document: &DocumentRecord, surface: &str) -> SourceSpan { + let byte_start = document.text().find(surface).expect("surface present"); + let byte_end = byte_start + surface.len(); + let scalar_start = document.text()[..byte_start].chars().count(); + let scalar_end = scalar_start + surface.chars().count(); + SourceSpan::new( + document, + byte_start, + byte_end, + scalar_start, + scalar_end, + None, + ) + .expect("span") +} + +fn eligible_clocks() -> MentionEvidenceClocks { + MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T12:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T09:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T08:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-03T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-03T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("eligible clocks") +} + +fn grounded( + document: &DocumentRecord, + surface: &str, + review: MentionReviewStatus, +) -> SpanGroundedMention { + SpanGroundedMention::new( + document, + span_for(document, surface), + EventConfidence::new(0.91).expect("confidence"), + eligible_clocks(), + "ace-extent-extractor/1", + review, + ) + .expect("grounded mention") +} + +fn occupancy(truth: &[SourceSpan], recovered: &[SourceSpan]) -> Vec { + truth + .iter() + .map(|span| { + if recovered.iter().any(|candidate| { + candidate.document_id() == span.document_id() + && candidate.byte_start() == span.byte_start() + && candidate.byte_end() == span.byte_end() + }) { + 1.0 + } else { + 0.0 + } + }) + .collect() +} + +fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { + assert_eq!(truth.len(), recovered.len()); + let n = f64::from(u32::try_from(truth.len()).expect("tiny fixture")); + let sse: f64 = truth + .iter() + .zip(recovered) + .map(|(truth_value, recovered_value)| { + let residual = truth_value - recovered_value; + residual * residual + }) + .sum(); + (sse / n).sqrt() +} + +#[test] +fn span_mention_cannot_be_cast_to_an_instance() { + let document = documentary_record(); + let mention = grounded( + &document, + "awarded the river-crossing contract", + MentionReviewStatus::Reviewed, + ); + assert_eq!( + refuse_span_mention_as_instance(&mention), + Err(EventError::SpanMentionIsNotEventInstance) + ); + assert_eq!( + mention.evidence_layer(), + EventEvidenceLayer::ObservedMention + ); +} + +#[test] +fn exact_extractor_recovers_known_truth_extents_better_than_whole_document() { + let document = documentary_record(); + let award = span_for(&document, "awarded the river-crossing contract"); + let protest = span_for(&document, "protest"); + let truth = [award, protest]; + let exact = [award, protest]; + let whole_document = [span_for(&document, DOCUMENT_TEXT)]; + let shifted = [ + span_for(&document, "awarded the river-crossing"), + span_for(&document, "protest was"), + ]; + + let exact_precision = mention_span_precision(&truth, &exact).expect("exact p"); + let exact_recall = mention_span_recall(&truth, &exact).expect("exact r"); + let naive_precision = mention_span_precision(&truth, &whole_document).expect("naive p"); + let naive_recall = mention_span_recall(&truth, &whole_document).expect("naive r"); + let shifted_precision = mention_span_precision(&truth, &shifted).expect("shifted p"); + let shifted_recall = mention_span_recall(&truth, &shifted).expect("shifted r"); + + assert!((exact_precision - 1.0).abs() < f64::EPSILON); + assert!((exact_recall - 1.0).abs() < f64::EPSILON); + assert!(naive_precision.abs() < f64::EPSILON); + assert!(naive_recall.abs() < f64::EPSILON); + assert!(shifted_precision.abs() < f64::EPSILON); + assert!(shifted_recall.abs() < f64::EPSILON); + + let ones = [1.0, 1.0]; + let exact_rmse = computed_rmse(&ones, &occupancy(&truth, &exact)); + let naive_rmse = computed_rmse(&ones, &occupancy(&truth, &whole_document)); + assert!(exact_rmse.abs() < 1e-15); + assert!((naive_rmse - 1.0).abs() < 1e-15); + assert!(exact_rmse < naive_rmse); +} + +#[test] +fn grounded_mention_keeps_exact_surface_document_clocks_and_review_status() { + let document = documentary_record(); + let mention = grounded( + &document, + "awarded the river-crossing contract", + MentionReviewStatus::Proposed, + ); + assert_eq!( + mention.surface_form(), + "awarded the river-crossing contract" + ); + assert_eq!(mention.document_id(), document.id()); + assert_eq!(mention.evidence_id(), document.id()); + assert_eq!( + mention.source_span().byte_start(), + document + .text() + .find("awarded the river-crossing contract") + .expect("offset") + ); + assert_eq!(mention.extractor_version(), "ace-extent-extractor/1"); + assert_eq!(mention.review_status(), MentionReviewStatus::Proposed); + assert!((mention.confidence().value() - 0.91).abs() < f64::EPSILON); + assert_eq!( + mention.clocks().available_time(), + AvailableTime::parse_rfc3339("2026-03-03T00:00:00Z").expect("available") + ); + assert_eq!( + MentionReviewStatus::from_wire_name("reviewed").expect("parse"), + MentionReviewStatus::Reviewed + ); + assert_eq!(MentionReviewStatus::Proposed.wire_name(), "proposed"); +} + +#[test] +fn delayed_reporting_before_cutoff_is_kept_and_late_availability_fails_closed() { + let document = documentary_record(); + let span = span_for(&document, "protest"); + let late = MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T12:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-04-02T09:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-04-02T08:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-04-03T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-04-03T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ); + assert_eq!(late, Err(EventError::MentionIneligibleAtCutoff)); + + let delayed = MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T12:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-15T09:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-15T08:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-16T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-16T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("delayed but eligible"); + let mention = SpanGroundedMention::new( + &document, + span, + EventConfidence::certain().expect("certain"), + delayed, + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .expect("delayed mention"); + assert_eq!(mention.surface_form(), "protest"); +} + +#[test] +fn empty_extractor_foreign_span_and_empty_or_duplicate_extent_sets_fail_closed() { + let document = documentary_record(); + let other = documentary_record(); + let span = span_for(&document, "protest"); + assert_eq!( + SpanGroundedMention::new( + &document, + span, + EventConfidence::certain().expect("certain"), + eligible_clocks(), + " ", + MentionReviewStatus::Proposed, + ) + .map(|_| ()), + Err(EventError::EmptyExtractorVersion) + ); + assert_eq!( + SpanGroundedMention::new( + &other, + span, + EventConfidence::certain().expect("certain"), + eligible_clocks(), + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .map(|_| ()), + Err(EventError::MentionSpanDocumentMismatch) + ); + assert_eq!( + mention_span_precision(&[], &[span]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + mention_span_recall(&[span], &[]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + mention_span_precision(&[span, span], &[span]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + MentionReviewStatus::from_wire_name("promoted"), + Err(EventError::UnknownMentionReviewStatus) + ); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 8709f7f6..eb5d3ea2 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -93,6 +93,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | `tepp_api::route_orchestration`, ablation record, and credential-free contextual-orchestrator binding on protected main; live execution and learned conductor calibration remain future | partial | | purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | `tepp_api` export authorization, elevated re-identification, and provider-payload minimization are implemented-main; migration `0007` retention/deletion/legal-hold SQL contracts are implemented-main; `encrypted_mapping` AES-256-GCM envelope is active on this PR; deployment/provider evidence remains accepted-target | active-PR | | tenant/purpose/role/lifetime access and identity separation | ADR 0009; Threat Model | `tepp_api` time-bounded `PurposeGrant` + cross-tenant denial implemented-main; persistent `access_grant` storage remaining | partial | +| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` span-grounded mentions with exact ACE extent precision/recall, six-clock cutoff eligibility, and review-status-is-not-promotion on this PR; remaining unified workflow, interval consistency, persistence, and exports stay accepted-target | active-PR | | TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | future `event_intelligence` | accepted-target | | evidence-bounded LLM interpretation | ADR 0010/0012; PRD | future `interpretation_gateway` | accepted-target | | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | `tepp_api::route_orchestration` + ablation record on the active PR; live contextual-orchestrator execution remaining | partial | diff --git a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md index 8f4976cf..adafb328 100644 --- a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md +++ b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md @@ -1,6 +1,7 @@ # ADR 0016 — TDT, CHRONOS, and Event Ontology intelligence boundary **Decision status:** Accepted +**Implementation maturity:** active-PR — span-grounded mentions with exact-extent precision/recall and cutoff-safe six-clock evidence live in existing `event_core`; remaining unified TDT/CHRONOS workflow, interval consistency, persistence, and exports remain accepted-target **Implementation maturity:** active-PR — evidence-layer admission, TDT link precision/recall, and detection-versus-instance refusal live in existing `event_core`, alongside the bounded predicted-vs-observed Allen promotion gate whose coverage authorization precedes any unmatched predicted mass; full TDT tracking/calibration and CHRONOS schema extraction/prediction layers remain accepted-target **Implementation maturity:** active-PR — evidence-layer admission, first-story false-alarm/miss rates, and detection-versus-instance refusal live in existing `event_core`, alongside the bounded predicted-vs-observed Allen promotion gate whose coverage authorization precedes any unmatched predicted mass; full TDT tracking/calibration and CHRONOS schema extraction/prediction layers remain accepted-target **Date:** 2026-08-12 diff --git a/docs/adr/README.md b/docs/adr/README.md index b5efd463..655e0b19 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -21,7 +21,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [0013](0013-bitemporal-persistence-reproducibility-and-split-authority.md) | Bitemporal persistence, reproducibility, and split authority | Accepted | partial | Migration, tenant, append-only, interval, and live SQL contracts are present; physical ERD and recovery depth remain target work. | | [0014](0014-scientific-claim-promotion-and-release-evidence.md) | Scientific claim promotion and release evidence | Accepted | partial | Exact-head promotion authority and repository evidence exist; the complete release bundle remains target work. | | [0015](0015-autonomous-development-review-and-merge-authority.md) | Autonomous development, review, and merge authority separation | Accepted | active-PR | Proposal, deterministic verification, publication, independent review, and merge/release authority remain separate. | -| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and event-intelligence boundary | Accepted | active-PR | Evidence admission, tracking, schema, segmentation, and Brier gates are active; complete intelligence remains target work. | +| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and event-intelligence boundary | Accepted | active-PR | Span-grounded mentions with exact-extent recovery are active on this PR; unified TDT/CHRONOS workflow, interval consistency, persistence, and exports remain target work. | | [0017](0017-hourly-contextual-orchestrator-gateway.md) | Hourly contextual-orchestrator gateway and provider discovery | Accepted | active-PR | Proposal-model execution is pinned behind a loopback gateway and remains separate from verification and merge authority. | | [0018](0018-consumer-scoped-analysis-run-ingress.md) | Consumer-scoped modular analysis-run ingress | Accepted | active-PR | Closed consumer registry, credential-free exchange, and consumer-qualified idempotency are active. | | [0019](0019-project-history-wire-size-symmetry.md) | Symmetric LineageWeave project-history wire-size enforcement | Accepted | active-PR | Request serialization and generated project-history projections share bounded size rules. | diff --git a/docs/doctoring/span-grounded-mentions.md b/docs/doctoring/span-grounded-mentions.md new file mode 100644 index 00000000..b43f92bb --- /dev/null +++ b/docs/doctoring/span-grounded-mentions.md @@ -0,0 +1,39 @@ +# Span-grounded event mentions (doctoring) + +## Claim boundary + +`SpanGroundedMention` binds a fallible event mention to one exact +`evidence_core::SourceSpan`, six typed clocks, an extractor/model version, and +a proposed-or-reviewed inspection status. Exact-extent precision and recall +are computed against known-truth spans. A reviewed mention is not a promoted +event instance. This slice does not run TDT tracking, CHRONOS schema +extraction, interval consistency, persistence, or GraphML/JSON-LD export, and +it does not close issue #170. + +## Numeric constants + +| Constant | Value | Provenance | +|---|---|---| +| extent identity | `(document_id, byte_start, byte_end)` | Doddington et al. (2004) map a system mention to a reference mention when their extents match. TEPP uses the already-validated `SourceSpan` byte bounds plus document identity. | +| availability eligibility | `available_time ≤ knowledge_cutoff` | ADR 0002 / `evaluate_historical_eligibility`: every available instant must be at or before cutoff. Instant equality is eligible. | +| precision | `\|recovered ∩ truth\| / \|recovered\|` | Standard set precision of mapped extents; no extra weight. | +| recall | `\|recovered ∩ truth\| / \|truth\|` | Standard set recall of mapped extents; no extra weight. | + +There is no probability threshold, similarity weight, or overlap heuristic. +Partial or shifted extents do not count. Empty or duplicated extent sets fail +closed. + +## Primary sources + +Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & +Weischedel, R. (2004). The Automatic Content Extraction (ACE) program—Tasks, +data, and evaluation. In *Proceedings of the Fourth International Conference +on Language Resources and Evaluation (LREC’04)* (pp. 837–840). European +Language Resources Association. + +Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based +information organization*. Kluwer Academic Publishers. + +Jensen, C. S., & Snodgrass, R. T. (1999). Temporal data management. *IEEE +Transactions on Knowledge and Data Engineering, 11*(1), 36–44. +https://doi.org/10.1109/69.755613 diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index c870b156..8fd97e31 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -202,7 +202,7 @@ visual workspace, or a supported multi-tenant release. | GAP-004 | The central shared-latent temporal/relational topic estimator is absent. | `partial` | product vertical | CPU `f64` reference plus fitted candidate-`K` scoring | [#167](https://github.com/ContextualWisdomLab/TEPP/issues/167) | this PR | GPU, method effects, full Bayesian sampling, and topic birth/split/merge remain. This is not full #167 closure. | | GAP-005 | Real multilingual documents are not yet transformed into validated exact-span semantic units and versioned shared concepts. | `partial` | product vertical | `e65cd66` (semantic_core exact-span units and language-profile validation are implemented-main as the first slice) | [#168](https://github.com/ContextualWisdomLab/TEPP/issues/168) / [PR #201](https://github.com/ContextualWisdomLab/TEPP/pull/201) (merged) | `—` | Closure still requires concept alignment, Unicode/layout/language-tailored processing, unknown-concept review, multilingual calibration/invariance, image-position evidence, and prompt-injection tests. | | GAP-006 | Posterior topic measurements cannot yet be fitted through a complete cross-classified longitudinal ESEM/DSEM engine. | `accepted-target` | product vertical | `e65cd66` (temporal and membership primitives only) | [#169](https://github.com/ContextualWisdomLab/TEPP/issues/169) / [PR #119](https://github.com/ContextualWisdomLab/TEPP/pull/119) | `47ab763d49b1` | Plausible-value/joint uncertainty, invariance, irregular event time, within/between separation, multiple membership, true-parameter recovery, and causal-claim refusal. | -| GAP-007 | TDT detection/tracking and CHRONOS schema/forecast/temporal reasoning remain isolated bounded gates rather than one calibrated product workflow. | `accepted-target` | product vertical | `e65cd66` (event/time primitives only) | [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) / [PR #70](https://github.com/ContextualWisdomLab/TEPP/pull/70) | `7a1f33aa68c1` | Span-grounded mentions, calibrated TDT metrics, schema/forecast hypothesis states, interval consistency, known-truth recovery, persistence, and exports. | +| GAP-007 | TDT detection/tracking and CHRONOS schema/forecast/temporal reasoning remain isolated bounded gates rather than one calibrated product workflow. | `accepted-target` | product vertical | protected main already has isolated TDT/CHRONOS scoring gates in `event_core`; this PR adds span-grounded mentions | [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) | this PR | This PR: exact-extent mention recovery. Remaining: one versioned TDT/CHRONOS workflow, interval consistency, persistence, and exports. | | GAP-008 | GPU support is policy-only; no production estimator kernel has real hardware parity or declared VRAM evidence. | `accepted-target` | product vertical | `e65cd66` (VRAM policy only) | [#171](https://github.com/ContextualWisdomLab/TEPP/issues/171) / [PR #51](https://github.com/ContextualWisdomLab/TEPP/pull/51) | `1801501c4d7c` | Real CUDA/portable backend execution, CPU parity, streamed memory, bounded OOM/fallback, hardware profiles, telemetry, and no skipped-support claim. | | GAP-009 | Topic association and cluster outputs lack posterior-valid estimation, uncertainty, edge stability, and consensus communities. | `accepted-target` | product vertical | `e65cd66` (network primitives only) | [#172](https://github.com/ContextualWisdomLab/TEPP/issues/172) / [PR #71](https://github.com/ContextualWisdomLab/TEPP/pull/71) | `2588f38281b9` | Valid log-ratio coordinates, interval/stability-bearing edges, repeated Leiden consensus, known-truth network/cluster recovery, and reproducible exports. | | GAP-010 | Operators lack coordinated accessible visual analytics and exact-value export workflows. | `accepted-target` | product vertical | `e65cd66` (no visual workspace) | [#173](https://github.com/ContextualWisdomLab/TEPP/issues/173) | `—` (Figma work not started) | Real Figma File ID in ADR, Storybook/design tokens, ten PRD views, exact-value tables, accessible interaction/print/PDF states, provenance, and source-consistent exports. | diff --git a/docs/research/span-grounded-mentions.md b/docs/research/span-grounded-mentions.md new file mode 100644 index 00000000..e4c8b71d --- /dev/null +++ b/docs/research/span-grounded-mentions.md @@ -0,0 +1,43 @@ +# Span-grounded event mentions + +## Scope + +This note doctors the `event_core` contract that keeps documentary event mentions +span-grounded and distinct from promoted instances: + +1. a mention cites one exact source extent, document identity, six-clock + evidence, extractor/model version, and review status; +2. exact-extent precision and recall are computed from known-truth spans; +3. a reviewed mention remains observed evidence and cannot promote an instance. + +No database migration is allocated. Interval consistency, persistence, and +versioned GraphML/JSON-LD exports remain later #170 work. + +## Authoritative sources + +Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & Weischedel, R. (2004). The Automatic Content Extraction (ACE) program—Tasks, data, and evaluation. In *Proceedings of the Fourth International Conference on Language Resources and Evaluation (LREC’04)* (pp. 837–840). European Language Resources Association. + +Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based information organization*. Kluwer Academic Publishers. + +Jensen, C. S., & Snodgrass, R. T. (1999). Temporal data management. *IEEE Transactions on Knowledge and Data Engineering, 11*(1), 36–44. https://doi.org/10.1109/69.755613 + +## Application + +Doddington et al. (2004) evaluate ACE mentions by mapping system mentions to +reference mentions when their extents match. Allan (2002) keeps TDT detection +outputs in the measurement layer. Jensen and Snodgrass (1999) separate valid +time from transaction/availability time, so event time cannot replace the +cutoff test. TEPP therefore requires exact `(document, byte_start, byte_end)` +identity, refuses availability after cutoff, and refuses to cast a +span-grounded mention as an event instance (Doddington et al., 2004; Allan, +2002; Jensen & Snodgrass, 1999). Numeric provenance is recorded in +[`docs/doctoring/span-grounded-mentions.md`](../doctoring/span-grounded-mentions.md). + +## Verification + +- `refuse_span_mention_as_instance` always returns `SpanMentionIsNotEventInstance`; +- `SpanGroundedMention::new` derives the surface from the exact document span; +- availability after cutoff returns `MentionIneligibleAtCutoff`; +- delayed reporting at or before cutoff is kept; +- `mention_span_precision` and `mention_span_recall` fail closed on empty or duplicate extents; +- an exact extractor recovers precision 1 and recall 1, with occupancy RMSE 0, against a two-mention documentary fixture, while a whole-document extractor recovers precision 0, recall 0, and RMSE 1. diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index cdff5d0e..3aebb09f 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -213,6 +213,8 @@ Chambers, N., & Jurafsky, D. (2009). Unsupervised learning of narrative schemas Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & Weischedel, R. (2004). The Automatic Content Extraction (ACE) program—Tasks, data, and evaluation. In *Proceedings of the Fourth International Conference on Language Resources and Evaluation (LREC’04)* (pp. 837–840). European Language Resources Association. +TEPP uses exact ACE mention-extent identity `(document, byte_start, byte_end)` for span-grounded event mentions. A system extent that does not match a reference extent is a miss or false alarm; review status is not instance promotion (Doddington et al., 2004; Allan, 2002). + TEPP uses interval and partial-order reasoning, bitemporal availability, leakage-safe cutoffs, TDT segmentation/link/detection/first-story/tracking tasks (Allan, 2002), qualitative temporal reasoning (Anagnostopoulos et al., 2013), and separate neural/symbolic event-schema and narrative participant-slot layers (Chambers & Jurafsky, 2009). Under [ADR 0016](../adr/0016-tdt-chronos-event-intelligence-boundary.md), predicted schema-slot fills stay hypothetical until independently promoted; this is a TEPP policy boundary, not a literature result. Brier, G. W. (1950). Verification of forecasts expressed in terms of probability. *Monthly Weather Review, 78*(1), 1–3. https://doi.org/10.1175/1520-0493(1950)078<0001:VOFEIT>2.0.CO;2 diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index 3c0b3754..ca605f7d 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -19,6 +19,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Six-clock temporal | `temporal_core` | implemented-main | `document_clocks` omitted assertion/document time | unit + wire | Task 3 / PR #8; document-row clocks on this PR | | Knowledge-cutoff identity | `cutoff_clock` | active-PR | this PR | recovered cutoff flags vs availability-time stand-in | ADR 0002 | | Allen path-consistency | `temporal_core` | implemented-main | — | unit + budget tests | Task 4 / PR #9 | +| Span-grounded mentions | `event_core` | active-PR | this PR | exact-extent P/R + occupancy RMSE vs whole-document | ADR 0016; `docs/research/span-grounded-mentions.md` | | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | | TDT link precision/recall | `event_core` | active-PR | this PR | computed precision/recall + RMSE vs always-link | ADR 0016; `docs/research/event-link-detection-calibration.md` | | First-story FAR/miss | `event_core` | active-PR | this PR | computed FAR/miss + RMSE vs always-first | ADR 0016; `docs/research/first-story-detection-calibration.md` | From cbb2df7da40fe75bd192607ccc4c3312ead3cfd5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 25 Aug 2026 07:14:11 +0000 Subject: [PATCH 2/4] Cover empty extractor, duplicate recovered extents, and mixed P/R. Close remaining fail-closed and half-rate paths on span-grounded mention scoring so exact-extent recovery stays fully exercised. Co-authored-by: Seongho Bae --- .../tests/span_grounded_mention_contract.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/event_core/tests/span_grounded_mention_contract.rs b/crates/event_core/tests/span_grounded_mention_contract.rs index 67dd8c50..56cd35e1 100644 --- a/crates/event_core/tests/span_grounded_mention_contract.rs +++ b/crates/event_core/tests/span_grounded_mention_contract.rs @@ -108,6 +108,7 @@ fn span_mention_cannot_be_cast_to_an_instance() { mention.evidence_layer(), EventEvidenceLayer::ObservedMention ); + assert_eq!(mention.evidence_layer().wire_name(), "observed_mention"); } #[test] @@ -232,6 +233,18 @@ fn empty_extractor_foreign_span_and_empty_or_duplicate_extent_sets_fail_closed() .map(|_| ()), Err(EventError::EmptyExtractorVersion) ); + assert_eq!( + SpanGroundedMention::new( + &document, + span, + EventConfidence::certain().expect("certain"), + eligible_clocks(), + "", + MentionReviewStatus::Proposed, + ) + .map(|_| ()), + Err(EventError::EmptyExtractorVersion) + ); assert_eq!( SpanGroundedMention::new( &other, @@ -256,6 +269,19 @@ fn empty_extractor_foreign_span_and_empty_or_duplicate_extent_sets_fail_closed() mention_span_precision(&[span, span], &[span]), Err(EventError::InvalidWirePayload) ); + assert_eq!( + mention_span_precision(&[span], &[span, span]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + mention_span_recall(&[span], &[span, span]), + Err(EventError::InvalidWirePayload) + ); + let award = span_for(&document, "awarded the river-crossing contract"); + let mixed_precision = mention_span_precision(&[award], &[award, span]).expect("mixed"); + assert!((mixed_precision - 0.5).abs() < f64::EPSILON); + let mixed_recall = mention_span_recall(&[award, span], &[award]).expect("mixed recall"); + assert!((mixed_recall - 0.5).abs() < f64::EPSILON); assert_eq!( MentionReviewStatus::from_wire_name("promoted"), Err(EventError::UnknownMentionReviewStatus) From 0a86d8d110702c6ec088bd7ec40d70e637300a48 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 25 Aug 2026 07:25:16 +0000 Subject: [PATCH 3/4] Fold span grounding into EventMention as the only mention type. Issue #170 requires every mention to cite an exact source span. Remove the unused parallel mention type so EventMention can be constructed only from a document span, six-clock evidence, extractor version, and review status. Co-authored-by: Seongho Bae --- ARCHITECTURE.md | 2 +- CHANGELOG.md | 2 +- crates/event_core/src/lib.rs | 29 ++- crates/event_core/src/mention.rs | 199 ++++++++++++++++-- crates/event_core/src/span_mention.rs | 143 +------------ .../event_core/tests/first_story_contract.rs | 38 +++- .../tests/link_detection_contract.rs | 84 ++++++-- .../tests/mention_instance_contract.rs | 127 +++++++---- .../tests/span_grounded_mention_contract.rs | 20 +- crates/event_core/tests/tracking_contract.rs | 61 +++++- docs/TRACEABILITY.md | 2 +- ...tdt-chronos-event-intelligence-boundary.md | 2 +- docs/doctoring/span-grounded-mentions.md | 15 +- docs/product-technical-gap-baseline.md | 2 +- docs/research/span-grounded-mentions.md | 2 +- docs/validation/temporal-event-foundation.md | 2 +- 16 files changed, 480 insertions(+), 250 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index fd7f8e4f..d2fc9432 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -60,7 +60,7 @@ boundaries above remain the target modular MSA architecture. | `semantic_core` | span-grounded semantic units; language is not identity | | `location_membership` | location is not entity identity and not a language channel | | `temporal_core` | typed clocks, intervals, and temporal reasoning | -| `event_core` | event instances, span-grounded mentions, roles, provenance, and CHRONOS occurrence-prediction calibration | +| `event_core` | event instances, span-grounded `EventMention` (the only constructible mention type), roles, provenance, and CHRONOS occurrence-prediction calibration | | `relation_graph` | typed relations and forward-transition validation | | `membership_core` | time-varying cross-classified multiple membership, Kish ESS, nested ICC with non-nested refusal | | `role_contradiction` | customer and competitor cannot occupy the same group | diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb007e2..69214525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] -- `event_core` span-grounded mentions: a mention cites one exact `SourceSpan`, six-clock evidence, extractor/model version, and proposed/reviewed status; exact-extent precision/recall recover known ACE extents with occupancy RMSE 0 versus whole-document RMSE 1; reviewed status and TDT/CHRONOS detections still cannot promote an instance. Interval consistency, persistence, and exports remain later #170 work. +- `event_core` `EventMention` is the only constructible mention type and must be span-grounded: it cites one exact `SourceSpan`, document identity, six-clock evidence, extractor/model version, and proposed/reviewed status, and the surface is the document substring at that span. Exact-extent precision/recall recover known ACE extents with occupancy RMSE 0 versus whole-document RMSE 1; reviewed status and TDT/CHRONOS detections still cannot promote an instance. Interval consistency, persistence, and exports remain later #170 work. - Restored protected-main gate integrity after the consolidation merges: hourly-scheduler prompt-contract tests now assert the gap-baseline-derived task contract (Gap ID naming, no invented weights) instead of stale increment-specific tokens, the operator-gap register inventory matches the live 33-PR queue, `evidence_core::image_unit` non-image/empty-subtype refusals and `load_union_branch_totals` valid-record accumulation have exact coverage, and the README crate fence plus duplicate registry entries stay deduped. - Branch-coverage diagnostics on the post-consolidation head exposed two uncovered outcomes in `evidence_core::image_unit` (`is_image_media_type_token` non-image prefix and empty-subtype refusals), one uncovered authored line (the strip-prefix refusal), and lost valid-record coverage for `load_union_branch_totals`; exact red-to-green cases now cover the non-image/empty-subtype data URIs and per-coordinate True/False accumulation. - Repaired post-consolidation merge fallout that left protected `main` red: restored the lost `return True` in the `check_coverage.py` match-guard branch, removed the shadowed duplicate `load_union_branch_totals` and `_is_multiline_match_guard` definitions plus duplicate workspace-crate entries (`episode_membership`, `analysis_engine`) from the contract tuple and Cargo member arrays, split two union-fused four-tuples back into `(variant, message)` pairs in the `event_core` error table, repaired the fused `identity_recovery_rate` body in `episode_membership::window`, deduplicated the checked-arithmetic eligible-count block in `analysis_engine`, fixed four-argument `unit()` test call sites, rebalanced the README crate-list fence around all 54 unique crates, and deduplicated the `location_membership`/`validation_core`/`tepp_api` architecture-table rows. Also documents private `PLAUSIBLE_IMAGE_MEDIA_TYPES` so `cargo doc -D warnings` passes. diff --git a/crates/event_core/src/lib.rs b/crates/event_core/src/lib.rs index 7bc2667b..122d154e 100644 --- a/crates/event_core/src/lib.rs +++ b/crates/event_core/src/lib.rs @@ -4,18 +4,15 @@ //! //! TEPP separates **fallible event mentions** grounded in evidence from //! **versioned event instances** used for temporal state, multilevel membership, -//! and scientific estimation. Mentions and first-story detections never -//! silently become instances, and TDT detections and CHRONOS predictions -//! remain measurement or hypothesis artifacts until independently promoted. -//! and scientific estimation. Mentions never silently become instances. TDT -//! detections and CHRONOS predictions remain measurement or hypothesis -//! artifacts until independently promoted. Track assignments, story -//! segmentations, CHRONOS schema-slot predictions, and occurrence forecasts -//! remain measurement or hypothesis artifacts and cannot promote an instance -//! without an explicit evidence-backed promotion gate. Span-grounded mentions -//! cite exact source extents, six-clock evidence, extractor version, and -//! review status, and they remain observed evidence until independently -//! promoted. +//! and scientific estimation. [`EventMention`] is the only constructible +//! mention type: it cites one exact source extent, document identity, +//! six-clock evidence, extractor version, and review status, and the surface +//! form is the document substring at that span. Mentions, first-story +//! detections, TDT detections, and CHRONOS predictions never silently become +//! instances. Track assignments, story segmentations, CHRONOS schema-slot +//! predictions, and occurrence forecasts remain measurement or hypothesis +//! artifacts and cannot promote an instance without an explicit +//! evidence-backed promotion gate. mod confidence; mod error; @@ -85,8 +82,10 @@ pub use link::event_link_recall; pub use link::refuse_event_link_as_instance; /// Explicit refusal to treat a TDT link as a state transition. pub use link::refuse_event_link_as_transition; -/// Fallible textual event mention. +/// Fallible textual event mention grounded in one exact source extent. pub use mention::EventMention; +/// Explicit refusal to treat a span-grounded mention as an instance. +pub use mention::refuse_span_mention_as_instance; /// One CHRONOS occurrence forecast that remains hypothetical. pub use prediction::ChronosOccurrenceForecast; /// Opaque CHRONOS occurrence-prediction identity. @@ -139,14 +138,10 @@ pub use segment::story_window_diff; pub use span_mention::MentionEvidenceClocks; /// Proposed-versus-reviewed mention inspection status. pub use span_mention::MentionReviewStatus; -/// Fallible mention grounded in one exact source extent. -pub use span_mention::SpanGroundedMention; /// Precision of recovered mention extents against known truth. pub use span_mention::mention_span_precision; /// Recall of recovered mention extents against known truth. pub use span_mention::mention_span_recall; -/// Explicit refusal to treat a span-grounded mention as an instance. -pub use span_mention::refuse_span_mention_as_instance; /// Assignment of one mention to one hypothesized TDT track. pub use track::EventTrackAssignment; /// Opaque TDT track identity. diff --git a/crates/event_core/src/mention.rs b/crates/event_core/src/mention.rs index c38d97a5..2e085d1f 100644 --- a/crates/event_core/src/mention.rs +++ b/crates/event_core/src/mention.rs @@ -1,11 +1,15 @@ -//! Fallible event mentions grounded in evidence. +//! Fallible event mentions grounded in one exact source extent. -use crate::{EventConfidence, EventError, EventMentionId}; -use evidence_core::EvidenceId; +use crate::span_mention::{MentionEvidenceClocks, MentionReviewStatus}; +use crate::{EventConfidence, EventError, EventEvidenceLayer, EventInstanceId, EventMentionId}; +use evidence_core::{DocumentRecord, EvidenceId, SourceSpan}; /// A fallible textual event mention that is **not** an event instance. /// -/// Mentions may be wrong, incomplete, or contradictory. Psychometric and +/// Mentions may be wrong, incomplete, or contradictory. Every mention cites +/// one exact [`SourceSpan`], document identity, six-clock evidence, extractor +/// or model version, and proposed-or-reviewed inspection status. The surface +/// form is the document substring selected by that span. Psychometric and /// temporal estimators must not treat a mention as a ground-truth event without /// an explicit promotion step that creates a distinct [`crate::EventInstance`]. #[derive(Clone, Debug, PartialEq)] @@ -14,29 +18,49 @@ pub struct EventMention { evidence_id: EvidenceId, surface_form: String, confidence: EventConfidence, + source_span: SourceSpan, + clocks: MentionEvidenceClocks, + extractor_version: String, + review_status: MentionReviewStatus, } impl EventMention { - /// Construct a validated event mention. + /// Bind a mention to one validated document span and six-clock evidence. + /// + /// The surface form is the document substring at `source_span`. Reviewed + /// status is authorized inspection and is not instance promotion. /// /// # Errors /// - /// Returns [`EventError::InvalidEventConfidence`] when confidence is invalid. - /// Empty surface forms are rejected as invalid wire/domain payloads. + /// Returns [`EventError::MentionSpanDocumentMismatch`] when the span does + /// not belong to `document`, or [`EventError::EmptyExtractorVersion`] when + /// the extractor version is empty or whitespace-only. pub fn new( - evidence_id: EvidenceId, - surface_form: impl Into, + document: &DocumentRecord, + source_span: SourceSpan, confidence: EventConfidence, + clocks: MentionEvidenceClocks, + extractor_version: impl Into, + review_status: MentionReviewStatus, ) -> Result { - let surface_form = surface_form.into(); - if surface_form.trim().is_empty() { - return Err(EventError::InvalidWirePayload); + if document.id() != source_span.document_id() { + return Err(EventError::MentionSpanDocumentMismatch); + } + let extractor_version = extractor_version.into(); + if extractor_version.trim().is_empty() { + return Err(EventError::EmptyExtractorVersion); } + let surface_form = + document.text()[source_span.byte_start()..source_span.byte_end()].to_string(); Ok(Self { mention_id: EventMentionId::new(), - evidence_id, + evidence_id: source_span.document_id(), surface_form, confidence, + source_span, + clocks, + extractor_version, + review_status, }) } @@ -46,13 +70,19 @@ impl EventMention { self.mention_id } + /// Return the grounding document identifier. + #[must_use] + pub const fn document_id(&self) -> EvidenceId { + self.source_span.document_id() + } + /// Return the grounding evidence identifier. #[must_use] pub const fn evidence_id(&self) -> EvidenceId { self.evidence_id } - /// Return the surface text. + /// Return the exact document substring selected by the span. #[must_use] pub fn surface_form(&self) -> &str { &self.surface_form @@ -63,4 +93,145 @@ impl EventMention { pub const fn confidence(&self) -> EventConfidence { self.confidence } + + /// Return the exact source extent. + #[must_use] + pub const fn source_span(&self) -> SourceSpan { + self.source_span + } + + /// Return the six-clock evidence. + #[must_use] + pub const fn clocks(&self) -> MentionEvidenceClocks { + self.clocks + } + + /// Return the extractor or model version. + #[must_use] + pub fn extractor_version(&self) -> &str { + &self.extractor_version + } + + /// Return the review status. + #[must_use] + pub const fn review_status(&self) -> MentionReviewStatus { + self.review_status + } + + /// Return the epistemic layer retained by the mention. + #[must_use] + pub const fn evidence_layer(&self) -> EventEvidenceLayer { + EventEvidenceLayer::ObservedMention + } +} + +/// Explicit refusal to treat a span-grounded mention as an event instance. +/// +/// # Errors +/// +/// Always returns [`EventError::SpanMentionIsNotEventInstance`]. +pub fn refuse_span_mention_as_instance( + _mention: &EventMention, +) -> Result { + Err(EventError::SpanMentionIsNotEventInstance) +} + +#[cfg(test)] +mod tests { + use super::{EventMention, refuse_span_mention_as_instance}; + use crate::span_mention::{MentionEvidenceClocks, MentionReviewStatus}; + use crate::{EventConfidence, EventError, EventEvidenceLayer}; + use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; + use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, + }; + + fn document(text: &str) -> DocumentRecord { + let artifact = SourceArtifact::from_bytes(text.as_bytes()).expect("artifact"); + DocumentRecord::from_text(artifact.id(), text).expect("document") + } + + fn span(document: &DocumentRecord, surface: &str) -> SourceSpan { + let byte_start = document.text().find(surface).expect("surface"); + let byte_end = byte_start + surface.len(); + let scalar_start = document.text()[..byte_start].chars().count(); + let scalar_end = scalar_start + surface.chars().count(); + SourceSpan::new( + document, + byte_start, + byte_end, + scalar_start, + scalar_end, + None, + ) + .expect("span") + } + + fn clocks() -> MentionEvidenceClocks { + MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("clocks") + } + + #[test] + fn grounded_mention_accessors_and_refusals_are_covered() { + let record = document("award filed"); + let mention = EventMention::new( + &record, + span(&record, "award"), + EventConfidence::certain().expect("certain"), + clocks(), + "ace-extent-extractor/1", + MentionReviewStatus::Reviewed, + ) + .expect("mention"); + let _ = mention.mention_id(); + assert_eq!(mention.document_id(), record.id()); + assert_eq!(mention.evidence_id(), record.id()); + assert_eq!(mention.surface_form(), "award"); + assert!((mention.confidence().value() - 1.0).abs() < f64::EPSILON); + assert_eq!(mention.source_span().byte_start(), 0); + assert_eq!(mention.clocks().available_time(), clocks().available_time()); + assert_eq!(mention.extractor_version(), "ace-extent-extractor/1"); + assert_eq!(mention.review_status(), MentionReviewStatus::Reviewed); + assert_eq!( + mention.evidence_layer(), + EventEvidenceLayer::ObservedMention + ); + assert_eq!( + refuse_span_mention_as_instance(&mention), + Err(EventError::SpanMentionIsNotEventInstance) + ); + let other = document("award filed"); + assert_eq!( + EventMention::new( + &other, + span(&record, "award"), + EventConfidence::certain().expect("certain"), + clocks(), + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .map(|_| ()), + Err(EventError::MentionSpanDocumentMismatch) + ); + assert_eq!( + EventMention::new( + &record, + span(&record, "award"), + EventConfidence::certain().expect("certain"), + clocks(), + "", + MentionReviewStatus::Proposed, + ) + .map(|_| ()), + Err(EventError::EmptyExtractorVersion) + ); + } } diff --git a/crates/event_core/src/span_mention.rs b/crates/event_core/src/span_mention.rs index 73678252..dce17b38 100644 --- a/crates/event_core/src/span_mention.rs +++ b/crates/event_core/src/span_mention.rs @@ -1,7 +1,7 @@ -//! Span-grounded mentions stay distinct from promoted event instances. +//! Span identity, six-clock eligibility, and exact-extent recovery. -use crate::{EventConfidence, EventError, EventEvidenceLayer, EventInstanceId, EventMentionId}; -use evidence_core::{DocumentRecord, EvidenceId, SourceSpan}; +use crate::EventError; +use evidence_core::SourceSpan; use std::collections::BTreeSet; use temporal_core::{ AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, @@ -128,131 +128,6 @@ impl MentionEvidenceClocks { } } -/// A fallible mention grounded in one exact source extent. -/// -/// The surface form is the document substring selected by the span. The -/// mention remains observed evidence and cannot promote an instance. -#[derive(Clone, Debug, PartialEq)] -pub struct SpanGroundedMention { - mention_id: EventMentionId, - evidence_id: EvidenceId, - surface_form: String, - confidence: EventConfidence, - source_span: SourceSpan, - clocks: MentionEvidenceClocks, - extractor_version: String, - review_status: MentionReviewStatus, -} - -impl SpanGroundedMention { - /// Bind a mention to one validated document span and six-clock evidence. - /// - /// # Errors - /// - /// Returns [`EventError::MentionSpanDocumentMismatch`] when the span does - /// not belong to `document`, or [`EventError::EmptyExtractorVersion`] when - /// the extractor version is empty or whitespace-only. - pub fn new( - document: &DocumentRecord, - source_span: SourceSpan, - confidence: EventConfidence, - clocks: MentionEvidenceClocks, - extractor_version: impl Into, - review_status: MentionReviewStatus, - ) -> Result { - if document.id() != source_span.document_id() { - return Err(EventError::MentionSpanDocumentMismatch); - } - let extractor_version = extractor_version.into(); - if extractor_version.trim().is_empty() { - return Err(EventError::EmptyExtractorVersion); - } - let surface_form = - document.text()[source_span.byte_start()..source_span.byte_end()].to_string(); - Ok(Self { - mention_id: EventMentionId::new(), - evidence_id: source_span.document_id(), - surface_form, - confidence, - source_span, - clocks, - extractor_version, - review_status, - }) - } - - /// Return the mention identifier. - #[must_use] - pub const fn mention_id(&self) -> EventMentionId { - self.mention_id - } - - /// Return the grounding document identifier. - #[must_use] - pub const fn document_id(&self) -> EvidenceId { - self.source_span.document_id() - } - - /// Return the grounding evidence identifier. - #[must_use] - pub const fn evidence_id(&self) -> EvidenceId { - self.evidence_id - } - - /// Return the exact document substring selected by the span. - #[must_use] - pub fn surface_form(&self) -> &str { - &self.surface_form - } - - /// Return mention confidence. - #[must_use] - pub const fn confidence(&self) -> EventConfidence { - self.confidence - } - - /// Return the exact source extent. - #[must_use] - pub const fn source_span(&self) -> SourceSpan { - self.source_span - } - - /// Return the six-clock evidence. - #[must_use] - pub const fn clocks(&self) -> MentionEvidenceClocks { - self.clocks - } - - /// Return the extractor or model version. - #[must_use] - pub fn extractor_version(&self) -> &str { - &self.extractor_version - } - - /// Return the review status. - #[must_use] - pub const fn review_status(&self) -> MentionReviewStatus { - self.review_status - } - - /// Return the epistemic layer retained by the mention. - #[must_use] - pub const fn evidence_layer(&self) -> EventEvidenceLayer { - EventEvidenceLayer::ObservedMention - } -} - -/// Explicit refusal to treat a span-grounded mention as an event instance. -/// -/// # Errors -/// -/// Always returns [`EventError::SpanMentionIsNotEventInstance`]. -pub fn refuse_span_mention_as_instance( - _mention: &SpanGroundedMention, -) -> Result { - Err(EventError::SpanMentionIsNotEventInstance) -} - /// Precision of recovered mention extents against known-truth extents. /// /// An extent matches when document identity and exact byte bounds agree. @@ -320,11 +195,13 @@ fn counted_rate(numerator: usize, denominator: usize) -> Result #[cfg(test)] mod tests { use super::{ - MentionEvidenceClocks, MentionReviewStatus, SpanGroundedMention, counted_rate, - mention_span_precision, mention_span_recall, refuse_span_mention_as_instance, - unique_extent_set, + MentionEvidenceClocks, MentionReviewStatus, counted_rate, mention_span_precision, + mention_span_recall, unique_extent_set, + }; + use crate::{ + EventConfidence, EventError, EventEvidenceLayer, EventMention, + refuse_span_mention_as_instance, }; - use crate::{EventConfidence, EventError, EventEvidenceLayer}; use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; use temporal_core::{ AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, @@ -394,7 +271,7 @@ mod tests { KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff") ); let record = document("award filed"); - let mention = SpanGroundedMention::new( + let mention = EventMention::new( &record, span(&record, "award"), EventConfidence::certain().expect("certain"), diff --git a/crates/event_core/tests/first_story_contract.rs b/crates/event_core/tests/first_story_contract.rs index 4a189c6e..d4f49566 100644 --- a/crates/event_core/tests/first_story_contract.rs +++ b/crates/event_core/tests/first_story_contract.rs @@ -1,9 +1,41 @@ //! First-story detections are not instances; FAR/miss are computed from truth. use event_core::{ - EventConfidence, EventError, EventMentionId, FirstStoryLabel, decide_first_story, - first_story_false_alarm_rate, first_story_miss_rate, refuse_first_story_as_instance, + EventConfidence, EventError, EventMention, EventMentionId, FirstStoryLabel, + MentionEvidenceClocks, MentionReviewStatus, decide_first_story, first_story_false_alarm_rate, + first_story_miss_rate, refuse_first_story_as_instance, }; +use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; +use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, +}; + +fn grounded_mention_id() -> EventMentionId { + let text = "first story onset"; + let artifact = SourceArtifact::from_bytes(text.as_bytes()).expect("artifact"); + let document = DocumentRecord::from_text(artifact.id(), text).expect("document"); + let span = + SourceSpan::new(&document, 0, text.len(), 0, text.chars().count(), None).expect("span"); + let clocks = MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("clocks"); + EventMention::new( + &document, + span, + EventConfidence::new(0.8).expect("confidence"), + clocks, + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .expect("grounded mention") + .mention_id() +} fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { assert_eq!(truth.len(), recovered.len()); @@ -30,7 +62,7 @@ fn decide_all(scores: &[f64], threshold: f64) -> Vec { #[test] fn first_story_detection_cannot_be_cast_to_an_instance() { assert_eq!( - refuse_first_story_as_instance(EventMentionId::new()), + refuse_first_story_as_instance(grounded_mention_id()), Err(EventError::FirstStoryIsNotEventInstance) ); } diff --git a/crates/event_core/tests/link_detection_contract.rs b/crates/event_core/tests/link_detection_contract.rs index 83389c1c..89c35754 100644 --- a/crates/event_core/tests/link_detection_contract.rs +++ b/crates/event_core/tests/link_detection_contract.rs @@ -1,10 +1,66 @@ //! TDT link detections are not instances; precision/recall come from truth. use event_core::{ - EventConfidence, EventError, EventLinkLabel, EventLinkPair, EventMentionId, decide_event_link, - event_link_precision, event_link_recall, refuse_event_link_as_instance, - refuse_event_link_as_transition, + EventConfidence, EventError, EventLinkLabel, EventLinkPair, EventMention, EventMentionId, + MentionEvidenceClocks, MentionReviewStatus, decide_event_link, event_link_precision, + event_link_recall, refuse_event_link_as_instance, refuse_event_link_as_transition, }; +use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; +use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, +}; + +const DOCUMENT_TEXT: &str = "alpha bravo charlie delta echo foxtrot"; + +fn documentary_record() -> DocumentRecord { + let artifact = SourceArtifact::from_bytes(DOCUMENT_TEXT.as_bytes()).expect("artifact"); + DocumentRecord::from_text(artifact.id(), DOCUMENT_TEXT).expect("document") +} + +fn span_for(document: &DocumentRecord, surface: &str) -> SourceSpan { + let byte_start = document.text().find(surface).expect("surface present"); + let byte_end = byte_start + surface.len(); + let scalar_start = document.text()[..byte_start].chars().count(); + let scalar_end = scalar_start + surface.chars().count(); + SourceSpan::new( + document, + byte_start, + byte_end, + scalar_start, + scalar_end, + None, + ) + .expect("span") +} + +fn eligible_clocks() -> MentionEvidenceClocks { + MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("eligible clocks") +} + +fn grounded_mention(surface: &str) -> EventMention { + let document = documentary_record(); + EventMention::new( + &document, + span_for(&document, surface), + EventConfidence::new(0.8).expect("confidence"), + eligible_clocks(), + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .expect("grounded mention") +} + +fn mention_id(surface: &str) -> EventMentionId { + grounded_mention(surface).mention_id() +} fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { assert_eq!(truth.len(), recovered.len()); @@ -26,8 +82,8 @@ fn pair(left: EventMentionId, right: EventMentionId) -> EventLinkPair { #[test] fn event_link_detection_cannot_be_cast_to_an_instance_or_transition() { - let left = EventMentionId::new(); - let right = EventMentionId::new(); + let left = mention_id("alpha"); + let right = mention_id("bravo"); let link = pair(left, right); assert_eq!( refuse_event_link_as_instance(link), @@ -41,10 +97,10 @@ fn event_link_detection_cannot_be_cast_to_an_instance_or_transition() { #[test] fn precision_and_recall_are_computed_from_known_truth_pairs() { - let a = EventMentionId::new(); - let b = EventMentionId::new(); - let c = EventMentionId::new(); - let d = EventMentionId::new(); + let a = mention_id("alpha"); + let b = mention_id("bravo"); + let c = mention_id("charlie"); + let d = mention_id("delta"); let truth = [pair(a, b), pair(b, c)]; let calibrated = [pair(a, b)]; let always_link = [pair(a, b), pair(b, c), pair(c, d), pair(a, d)]; @@ -79,13 +135,13 @@ fn calibrated_link_scores_have_lower_rmse_than_always_link() { #[test] fn pair_helpers_fail_closed_on_self_links_empty_and_missing_sets() { - let mention = EventMentionId::new(); + let mention = mention_id("echo"); assert_eq!( EventLinkPair::new(mention, mention), Err(EventError::InvalidWirePayload) ); - let a = EventMentionId::new(); - let b = EventMentionId::new(); + let a = mention_id("alpha"); + let b = mention_id("bravo"); let truth = [pair(a, b)]; assert_eq!( event_link_precision(&truth, &[]), @@ -125,8 +181,8 @@ fn labels_round_trip_and_threshold_is_inclusive() { EventLinkLabel::Unlinked ); - let left = EventMentionId::new(); - let right = EventMentionId::new(); + let left = mention_id("echo"); + let right = mention_id("foxtrot"); assert_eq!(pair(left, right), pair(right, left)); assert_ne!(pair(left, right).left(), pair(left, right).right()); } diff --git a/crates/event_core/tests/mention_instance_contract.rs b/crates/event_core/tests/mention_instance_contract.rs index 6aa48fe3..2ee6c0cb 100644 --- a/crates/event_core/tests/mention_instance_contract.rs +++ b/crates/event_core/tests/mention_instance_contract.rs @@ -2,45 +2,85 @@ use event_core::{ EventConfidence, EventError, EventEvidenceLayer, EventInstance, EventMention, EventRegistry, - EventRoleKind, refuse_mention_as_instance, + EventRoleKind, MentionEvidenceClocks, MentionReviewStatus, refuse_mention_as_instance, + refuse_span_mention_as_instance, }; -use evidence_core::{DocumentRecord, EvidenceId, SourceArtifact}; -use temporal_core::EventTime; - -fn document_evidence() -> EvidenceId { - let artifact = - SourceArtifact::from_bytes(b"contract award announced on 2026-03-01").expect("artifact"); - let document = - DocumentRecord::from_text(artifact.id(), "contract award announced on 2026-03-01") - .expect("document"); - document.id() +use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; +use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, +}; + +const DOCUMENT_TEXT: &str = "contract award announced on 2026-03-01 merger filed"; + +fn documentary_record() -> DocumentRecord { + let artifact = SourceArtifact::from_bytes(DOCUMENT_TEXT.as_bytes()).expect("artifact"); + DocumentRecord::from_text(artifact.id(), DOCUMENT_TEXT).expect("document") +} + +fn span_for(document: &DocumentRecord, surface: &str) -> SourceSpan { + let byte_start = document.text().find(surface).expect("surface present"); + let byte_end = byte_start + surface.len(); + let scalar_start = document.text()[..byte_start].chars().count(); + let scalar_end = scalar_start + surface.chars().count(); + SourceSpan::new( + document, + byte_start, + byte_end, + scalar_start, + scalar_end, + None, + ) + .expect("span") +} + +fn eligible_clocks() -> MentionEvidenceClocks { + MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("eligible clocks") +} + +fn grounded_mention(surface: &str, confidence: EventConfidence) -> EventMention { + let document = documentary_record(); + EventMention::new( + &document, + span_for(&document, surface), + confidence, + eligible_clocks(), + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .expect("grounded mention") } #[test] fn mention_cannot_be_cast_to_instance_without_promotion() { - let evidence_id = document_evidence(); - let mention = EventMention::new( - evidence_id, + let mention = grounded_mention( "contract award announced", EventConfidence::new(0.72).expect("confidence"), - ) - .expect("mention"); + ); assert_eq!( refuse_mention_as_instance(mention.mention_id()), Err(EventError::MentionIsNotEventInstance) ); + assert_eq!( + refuse_span_mention_as_instance(&mention), + Err(EventError::SpanMentionIsNotEventInstance) + ); } #[test] fn registry_requires_supporting_mentions_before_instance_insert() { - let evidence_id = document_evidence(); - let mention = EventMention::new( - evidence_id, + let mention = grounded_mention( "contract award announced", EventConfidence::new(0.8).expect("confidence"), - ) - .expect("mention"); + ); let start = EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("start"); let end = EventTime::parse_rfc3339("2026-03-01T23:59:59Z").expect("end"); @@ -77,7 +117,14 @@ fn registry_requires_supporting_mentions_before_instance_insert() { assert_eq!(registry.mention_count(), 1); assert_eq!(registry.instance_count(), 1); - assert!(registry.mention(mention.mention_id()).is_some()); + let stored_mention = registry + .mention(mention.mention_id()) + .expect("stored mention"); + assert_eq!(stored_mention.surface_form(), "contract award announced"); + assert_eq!( + stored_mention.source_span().byte_end() - stored_mention.source_span().byte_start(), + "contract award announced".len() + ); let stored = registry.instance(instance.instance_id()).expect("stored"); assert_eq!(stored.supporting_mentions(), &[mention.mention_id()]); assert_eq!( @@ -89,11 +136,20 @@ fn registry_requires_supporting_mentions_before_instance_insert() { } #[test] -fn empty_surface_and_empty_mention_sets_fail_closed() { - let evidence_id = document_evidence(); +fn empty_extractor_and_empty_mention_sets_fail_closed() { + let document = documentary_record(); + let span = span_for(&document, "award"); assert_eq!( - EventMention::new(evidence_id, " ", EventConfidence::new(0.5).expect("c")).map(|_| ()), - Err(EventError::InvalidWirePayload) + EventMention::new( + &document, + span, + EventConfidence::new(0.5).expect("c"), + eligible_clocks(), + " ", + MentionReviewStatus::Proposed, + ) + .map(|_| ()), + Err(EventError::EmptyExtractorVersion) ); let start = EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("start"); let end = EventTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("end"); @@ -112,15 +168,11 @@ fn empty_surface_and_empty_mention_sets_fail_closed() { #[test] fn accessors_and_duplicate_identity_paths_are_covered() { - let evidence_id = document_evidence(); - let mention = EventMention::new( - evidence_id, - "merger filed", - EventConfidence::certain().expect("certain"), - ) - .expect("mention"); - assert_eq!(mention.evidence_id(), evidence_id); + let mention = grounded_mention("merger filed", EventConfidence::certain().expect("certain")); assert_eq!(mention.surface_form(), "merger filed"); + assert_eq!(mention.document_id(), mention.evidence_id()); + assert_eq!(mention.extractor_version(), "ace-extent-extractor/1"); + assert_eq!(mention.review_status(), MentionReviewStatus::Proposed); assert!((mention.confidence().value() - 1.0).abs() < f64::EPSILON); assert_eq!(event_core::EVENT_INSTANCE_WIRE_SCHEMA_VERSION, 1); @@ -157,13 +209,10 @@ fn accessors_and_duplicate_identity_paths_are_covered() { #[test] fn promotion_rejects_every_non_promoted_evidence_layer() { - let evidence_id = document_evidence(); - let mention = EventMention::new( - evidence_id, + let mention = grounded_mention( "contract award announced", EventConfidence::certain().expect("confidence"), - ) - .expect("mention"); + ); let start = EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("start"); let end = EventTime::parse_rfc3339("2026-03-01T23:59:59Z").expect("end"); diff --git a/crates/event_core/tests/span_grounded_mention_contract.rs b/crates/event_core/tests/span_grounded_mention_contract.rs index 56cd35e1..cf4980d7 100644 --- a/crates/event_core/tests/span_grounded_mention_contract.rs +++ b/crates/event_core/tests/span_grounded_mention_contract.rs @@ -1,8 +1,8 @@ //! Span-grounded mentions recover exact ACE extents against known truth. use event_core::{ - EventConfidence, EventError, EventEvidenceLayer, MentionEvidenceClocks, MentionReviewStatus, - SpanGroundedMention, mention_span_precision, mention_span_recall, + EventConfidence, EventError, EventEvidenceLayer, EventMention, MentionEvidenceClocks, + MentionReviewStatus, mention_span_precision, mention_span_recall, refuse_span_mention_as_instance, }; use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; @@ -45,12 +45,8 @@ fn eligible_clocks() -> MentionEvidenceClocks { .expect("eligible clocks") } -fn grounded( - document: &DocumentRecord, - surface: &str, - review: MentionReviewStatus, -) -> SpanGroundedMention { - SpanGroundedMention::new( +fn grounded(document: &DocumentRecord, surface: &str, review: MentionReviewStatus) -> EventMention { + EventMention::new( document, span_for(document, surface), EventConfidence::new(0.91).expect("confidence"), @@ -204,7 +200,7 @@ fn delayed_reporting_before_cutoff_is_kept_and_late_availability_fails_closed() KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), ) .expect("delayed but eligible"); - let mention = SpanGroundedMention::new( + let mention = EventMention::new( &document, span, EventConfidence::certain().expect("certain"), @@ -222,7 +218,7 @@ fn empty_extractor_foreign_span_and_empty_or_duplicate_extent_sets_fail_closed() let other = documentary_record(); let span = span_for(&document, "protest"); assert_eq!( - SpanGroundedMention::new( + EventMention::new( &document, span, EventConfidence::certain().expect("certain"), @@ -234,7 +230,7 @@ fn empty_extractor_foreign_span_and_empty_or_duplicate_extent_sets_fail_closed() Err(EventError::EmptyExtractorVersion) ); assert_eq!( - SpanGroundedMention::new( + EventMention::new( &document, span, EventConfidence::certain().expect("certain"), @@ -246,7 +242,7 @@ fn empty_extractor_foreign_span_and_empty_or_duplicate_extent_sets_fail_closed() Err(EventError::EmptyExtractorVersion) ); assert_eq!( - SpanGroundedMention::new( + EventMention::new( &other, span, EventConfidence::certain().expect("certain"), diff --git a/crates/event_core/tests/tracking_contract.rs b/crates/event_core/tests/tracking_contract.rs index b22500d3..318e3456 100644 --- a/crates/event_core/tests/tracking_contract.rs +++ b/crates/event_core/tests/tracking_contract.rs @@ -1,10 +1,63 @@ //! TDT tracks are not instances; pair P/R and switch rate come from truth. use event_core::{ - EventConfidence, EventError, EventMentionId, EventTrackAssignment, EventTrackId, - EventTrackLabel, decide_track_continue, refuse_track_as_instance, refuse_track_as_transition, - tracking_identity_switch_rate, tracking_pair_precision, tracking_pair_recall, + EventConfidence, EventError, EventMention, EventMentionId, EventTrackAssignment, EventTrackId, + EventTrackLabel, MentionEvidenceClocks, MentionReviewStatus, decide_track_continue, + refuse_track_as_instance, refuse_track_as_transition, tracking_identity_switch_rate, + tracking_pair_precision, tracking_pair_recall, }; +use evidence_core::{DocumentRecord, SourceArtifact, SourceSpan}; +use temporal_core::{ + AssertionTime, AvailableTime, DocumentTime, EventTime, KnowledgeCutoff, SystemTime, +}; + +const DOCUMENT_TEXT: &str = "alpha bravo charlie delta echo foxtrot golf hotel"; + +fn documentary_record() -> DocumentRecord { + let artifact = SourceArtifact::from_bytes(DOCUMENT_TEXT.as_bytes()).expect("artifact"); + DocumentRecord::from_text(artifact.id(), DOCUMENT_TEXT).expect("document") +} + +fn span_for(document: &DocumentRecord, surface: &str) -> SourceSpan { + let byte_start = document.text().find(surface).expect("surface present"); + let byte_end = byte_start + surface.len(); + let scalar_start = document.text()[..byte_start].chars().count(); + let scalar_end = scalar_start + surface.chars().count(); + SourceSpan::new( + document, + byte_start, + byte_end, + scalar_start, + scalar_end, + None, + ) + .expect("span") +} + +fn eligible_clocks() -> MentionEvidenceClocks { + MentionEvidenceClocks::new( + EventTime::parse_rfc3339("2026-03-01T00:00:00Z").expect("event"), + AssertionTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("assertion"), + DocumentTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("document"), + SystemTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("system"), + AvailableTime::parse_rfc3339("2026-03-02T00:00:00Z").expect("available"), + KnowledgeCutoff::parse_rfc3339("2026-03-31T00:00:00Z").expect("cutoff"), + ) + .expect("eligible clocks") +} + +fn grounded_mention(surface: &str) -> EventMention { + let document = documentary_record(); + EventMention::new( + &document, + span_for(&document, surface), + EventConfidence::new(0.8).expect("confidence"), + eligible_clocks(), + "ace-extent-extractor/1", + MentionReviewStatus::Proposed, + ) + .expect("grounded mention") +} fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { assert_eq!(truth.len(), recovered.len()); @@ -21,7 +74,7 @@ fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { } fn mention() -> EventMentionId { - EventMentionId::new() + grounded_mention("alpha").mention_id() } fn assignment(mention_id: EventMentionId, track: u32) -> EventTrackAssignment { diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index eb5d3ea2..a56ef978 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -93,7 +93,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | `tepp_api::route_orchestration`, ablation record, and credential-free contextual-orchestrator binding on protected main; live execution and learned conductor calibration remain future | partial | | purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | `tepp_api` export authorization, elevated re-identification, and provider-payload minimization are implemented-main; migration `0007` retention/deletion/legal-hold SQL contracts are implemented-main; `encrypted_mapping` AES-256-GCM envelope is active on this PR; deployment/provider evidence remains accepted-target | active-PR | | tenant/purpose/role/lifetime access and identity separation | ADR 0009; Threat Model | `tepp_api` time-bounded `PurposeGrant` + cross-tenant denial implemented-main; persistent `access_grant` storage remaining | partial | -| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` span-grounded mentions with exact ACE extent precision/recall, six-clock cutoff eligibility, and review-status-is-not-promotion on this PR; remaining unified workflow, interval consistency, persistence, and exports stay accepted-target | active-PR | +| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` `EventMention` is the only constructible mention type and is span-grounded, with exact ACE extent precision/recall, six-clock cutoff eligibility, and review-status-is-not-promotion on this PR; remaining unified workflow, interval consistency, persistence, and exports stay accepted-target | active-PR | | TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | future `event_intelligence` | accepted-target | | evidence-bounded LLM interpretation | ADR 0010/0012; PRD | future `interpretation_gateway` | accepted-target | | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | `tepp_api::route_orchestration` + ablation record on the active PR; live contextual-orchestrator execution remaining | partial | diff --git a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md index adafb328..addd29b3 100644 --- a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md +++ b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md @@ -1,7 +1,7 @@ # ADR 0016 — TDT, CHRONOS, and Event Ontology intelligence boundary **Decision status:** Accepted -**Implementation maturity:** active-PR — span-grounded mentions with exact-extent precision/recall and cutoff-safe six-clock evidence live in existing `event_core`; remaining unified TDT/CHRONOS workflow, interval consistency, persistence, and exports remain accepted-target +**Implementation maturity:** active-PR — `EventMention` is the only constructible mention type and is span-grounded, with exact-extent precision/recall and cutoff-safe six-clock evidence in existing `event_core`; remaining unified TDT/CHRONOS workflow, interval consistency, persistence, and exports remain accepted-target **Implementation maturity:** active-PR — evidence-layer admission, TDT link precision/recall, and detection-versus-instance refusal live in existing `event_core`, alongside the bounded predicted-vs-observed Allen promotion gate whose coverage authorization precedes any unmatched predicted mass; full TDT tracking/calibration and CHRONOS schema extraction/prediction layers remain accepted-target **Implementation maturity:** active-PR — evidence-layer admission, first-story false-alarm/miss rates, and detection-versus-instance refusal live in existing `event_core`, alongside the bounded predicted-vs-observed Allen promotion gate whose coverage authorization precedes any unmatched predicted mass; full TDT tracking/calibration and CHRONOS schema extraction/prediction layers remain accepted-target **Date:** 2026-08-12 diff --git a/docs/doctoring/span-grounded-mentions.md b/docs/doctoring/span-grounded-mentions.md index b43f92bb..02d6aeb8 100644 --- a/docs/doctoring/span-grounded-mentions.md +++ b/docs/doctoring/span-grounded-mentions.md @@ -2,13 +2,14 @@ ## Claim boundary -`SpanGroundedMention` binds a fallible event mention to one exact -`evidence_core::SourceSpan`, six typed clocks, an extractor/model version, and -a proposed-or-reviewed inspection status. Exact-extent precision and recall -are computed against known-truth spans. A reviewed mention is not a promoted -event instance. This slice does not run TDT tracking, CHRONOS schema -extraction, interval consistency, persistence, or GraphML/JSON-LD export, and -it does not close issue #170. +`EventMention` is the only constructible mention type. It binds a fallible +event mention to one exact `evidence_core::SourceSpan`, six typed clocks, an +extractor/model version, and a proposed-or-reviewed inspection status. The +surface form is the document substring at that span. Exact-extent precision +and recall are computed against known-truth spans. A reviewed mention is not +a promoted event instance. This slice does not run TDT tracking, CHRONOS +schema extraction, interval consistency, persistence, or GraphML/JSON-LD +export, and it does not close issue #170. ## Numeric constants diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 8fd97e31..6901188b 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -202,7 +202,7 @@ visual workspace, or a supported multi-tenant release. | GAP-004 | The central shared-latent temporal/relational topic estimator is absent. | `partial` | product vertical | CPU `f64` reference plus fitted candidate-`K` scoring | [#167](https://github.com/ContextualWisdomLab/TEPP/issues/167) | this PR | GPU, method effects, full Bayesian sampling, and topic birth/split/merge remain. This is not full #167 closure. | | GAP-005 | Real multilingual documents are not yet transformed into validated exact-span semantic units and versioned shared concepts. | `partial` | product vertical | `e65cd66` (semantic_core exact-span units and language-profile validation are implemented-main as the first slice) | [#168](https://github.com/ContextualWisdomLab/TEPP/issues/168) / [PR #201](https://github.com/ContextualWisdomLab/TEPP/pull/201) (merged) | `—` | Closure still requires concept alignment, Unicode/layout/language-tailored processing, unknown-concept review, multilingual calibration/invariance, image-position evidence, and prompt-injection tests. | | GAP-006 | Posterior topic measurements cannot yet be fitted through a complete cross-classified longitudinal ESEM/DSEM engine. | `accepted-target` | product vertical | `e65cd66` (temporal and membership primitives only) | [#169](https://github.com/ContextualWisdomLab/TEPP/issues/169) / [PR #119](https://github.com/ContextualWisdomLab/TEPP/pull/119) | `47ab763d49b1` | Plausible-value/joint uncertainty, invariance, irregular event time, within/between separation, multiple membership, true-parameter recovery, and causal-claim refusal. | -| GAP-007 | TDT detection/tracking and CHRONOS schema/forecast/temporal reasoning remain isolated bounded gates rather than one calibrated product workflow. | `accepted-target` | product vertical | protected main already has isolated TDT/CHRONOS scoring gates in `event_core`; this PR adds span-grounded mentions | [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) | this PR | This PR: exact-extent mention recovery. Remaining: one versioned TDT/CHRONOS workflow, interval consistency, persistence, and exports. | +| GAP-007 | TDT detection/tracking and CHRONOS schema/forecast/temporal reasoning remain isolated bounded gates rather than one calibrated product workflow. | `accepted-target` | product vertical | protected main already has isolated TDT/CHRONOS scoring gates in `event_core`; this PR folds span grounding into `EventMention` | [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) | this PR | This PR: `EventMention` is the only constructible mention type and must cite an exact source span. Remaining: one versioned TDT/CHRONOS workflow, interval consistency, persistence, and exports. | | GAP-008 | GPU support is policy-only; no production estimator kernel has real hardware parity or declared VRAM evidence. | `accepted-target` | product vertical | `e65cd66` (VRAM policy only) | [#171](https://github.com/ContextualWisdomLab/TEPP/issues/171) / [PR #51](https://github.com/ContextualWisdomLab/TEPP/pull/51) | `1801501c4d7c` | Real CUDA/portable backend execution, CPU parity, streamed memory, bounded OOM/fallback, hardware profiles, telemetry, and no skipped-support claim. | | GAP-009 | Topic association and cluster outputs lack posterior-valid estimation, uncertainty, edge stability, and consensus communities. | `accepted-target` | product vertical | `e65cd66` (network primitives only) | [#172](https://github.com/ContextualWisdomLab/TEPP/issues/172) / [PR #71](https://github.com/ContextualWisdomLab/TEPP/pull/71) | `2588f38281b9` | Valid log-ratio coordinates, interval/stability-bearing edges, repeated Leiden consensus, known-truth network/cluster recovery, and reproducible exports. | | GAP-010 | Operators lack coordinated accessible visual analytics and exact-value export workflows. | `accepted-target` | product vertical | `e65cd66` (no visual workspace) | [#173](https://github.com/ContextualWisdomLab/TEPP/issues/173) | `—` (Figma work not started) | Real Figma File ID in ADR, Storybook/design tokens, ten PRD views, exact-value tables, accessible interaction/print/PDF states, provenance, and source-consistent exports. | diff --git a/docs/research/span-grounded-mentions.md b/docs/research/span-grounded-mentions.md index e4c8b71d..86204b2a 100644 --- a/docs/research/span-grounded-mentions.md +++ b/docs/research/span-grounded-mentions.md @@ -36,7 +36,7 @@ span-grounded mention as an event instance (Doddington et al., 2004; Allan, ## Verification - `refuse_span_mention_as_instance` always returns `SpanMentionIsNotEventInstance`; -- `SpanGroundedMention::new` derives the surface from the exact document span; +- `EventMention::new` derives the surface from the exact document span and is the only mention constructor; - availability after cutoff returns `MentionIneligibleAtCutoff`; - delayed reporting at or before cutoff is kept; - `mention_span_precision` and `mention_span_recall` fail closed on empty or duplicate extents; diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index ca605f7d..8663e961 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -19,7 +19,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Six-clock temporal | `temporal_core` | implemented-main | `document_clocks` omitted assertion/document time | unit + wire | Task 3 / PR #8; document-row clocks on this PR | | Knowledge-cutoff identity | `cutoff_clock` | active-PR | this PR | recovered cutoff flags vs availability-time stand-in | ADR 0002 | | Allen path-consistency | `temporal_core` | implemented-main | — | unit + budget tests | Task 4 / PR #9 | -| Span-grounded mentions | `event_core` | active-PR | this PR | exact-extent P/R + occupancy RMSE vs whole-document | ADR 0016; `docs/research/span-grounded-mentions.md` | +| Span-grounded mentions | `event_core` | active-PR | this PR | `EventMention` is the only constructible mention type; exact-extent P/R + occupancy RMSE vs whole-document | ADR 0016; `docs/research/span-grounded-mentions.md` | | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | | TDT link precision/recall | `event_core` | active-PR | this PR | computed precision/recall + RMSE vs always-link | ADR 0016; `docs/research/event-link-detection-calibration.md` | | First-story FAR/miss | `event_core` | active-PR | this PR | computed FAR/miss + RMSE vs always-first | ADR 0016; `docs/research/first-story-detection-calibration.md` | From 8b24b7be5d6219aa5a5d55ff0040878680a79a9c Mon Sep 17 00:00:00 2001 From: seonghobae Date: Wed, 26 Aug 2026 02:04:30 +0900 Subject: [PATCH 4/4] docs(gap): keep refreshed post-#239 register on span-grounding stack The merge-resolution pass kept the stale 04:24Z snapshot; restore main's c482ccea-refreshed register (merged through #246) so the draft does not regress the canonical operator-gap authority. --- docs/product-technical-gap-baseline.md | 300 ++++++++++++------------- 1 file changed, 144 insertions(+), 156 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 6901188b..87cb2412 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,18 +1,27 @@ # Product and Technical Gap Baseline +## 2026-08-25 Event Lineage anchor contract slice + +- Exact base: protected `main` `cf0e0ad74d23c5d2e0e33d389bb0bb4d37067c31`. +- This branch publishes TEPP's strict request identity and + `tepp.lineage_criterion_anchor.v1` accepted/rejected artifact contract. +- The buyer-visible integrity gain is fail-closed: LineageWeave cannot promote + fast-mlsirm's internal response structure into calibrated Event Lineage + weights without an exact TEPP-authored criterion result. +- Remaining product gap: the registered TEPP criterion estimator and terminal + artifact delivery are not implemented by this contract slice. Until they + exist and pass scientific recovery/validity gates, production activation + remains unavailable; the consumer must not invent a substitute. +- Acceptance evidence for this slice: complete `tepp_api` tests, warning-free + clippy, strict unknown-field/provenance rejection, schema and ADR/API + traceability, followed by exact-head protected checks and independent review. + **Status:** Live delivery baseline **Product:** Temporal Event Psychometrics Platform (TEPP) -**Snapshot:** 2026-08-25T04:24:53Z -**Protected-main evidence:** `0e7479c96c080036deed14a5925bb0ca715fa524` (full SHA fetched live before every mutation) | Signal | Snapshot evidence | Delivery implication | -|---|---:|---| -| Protected-main SHA | `5c8599442e85…` (2026-08-25T02:12Z, merge of #215) | All as-built claims are bounded to this commit. | -| Workspace members | 48 unique Rust crates | The repository is modular, but the approved target still lacks complete semantic, estimator, compute, psychometric, event-intelligence, network, interpretation, artifact, and visual product boundaries. | -| Open pull requests | **33** | The consolidation program drained all main-conflicting slices; the residual queue is 31 stacked drafts plus two non-draft slices and the coverage repairs in flight. | -| Draft pull requests | **31** | Most queued work is not independently review-ready; the psychometric recovery stack dominates. | -**Snapshot:** 2026-08-25T02:30:00Z -**Protected-main evidence:** `abbd89874b0087ae0645c7a5c493557a1be8c2d5` (full SHA fetched live before every mutation) -**Workspace version on protected main:** `0.1.0` -**Canonical gap-baseline authority:** [PR #164](https://github.com/ContextualWisdomLab/TEPP/pull/164). [PR #164](https://github.com/ContextualWisdomLab/TEPP/pull/164) merged; this file is now maintained by follow-up refresh PRs against protected main. Duplicate [PR #179] stays closed. +**Snapshot:** 2026-08-25T10:12:54Z +**Protected-main evidence:** `c482ccea82692f1cf6bc179f2f827c0861e00b39` (full SHA fetched live before every mutation; merge of [PR #239](https://github.com/ContextualWisdomLab/TEPP/pull/239) at 2026-08-25T09:23:20Z) +**Workspace version on protected main:** `0.2.0` +**Canonical gap-baseline authority:** [PR #164](https://github.com/ContextualWisdomLab/TEPP/pull/164). [PR #164](https://github.com/ContextualWisdomLab/TEPP/pull/164) merged; this file is now maintained by follow-up refresh PRs against protected main. ## Purpose @@ -31,111 +40,83 @@ GitHub state before any customer, release, certification, or valuation claim. | Signal | Snapshot evidence | Delivery implication | |---|---:|---| -| Protected-main SHA | `5c8599442e85…` (2026-08-25T02:12Z, merge of #215) | All as-built claims are bounded to this commit. | -| Workspace members | 48 unique Rust crates | The repository is modular, but the approved target still lacks complete semantic, estimator, compute, psychometric, event-intelligence, network, interpretation, artifact, and visual product boundaries. | -| Open pull requests | **59** | The consolidation vehicle landed; the residual queue is stacked drafts plus nine non-draft slices and one coverage repair. | -| Draft pull requests | **33** | Most queued work is not independently review-ready; the psychometric recovery stack dominates. | -| Non-draft pull requests | **0** | A non-draft state is not a qualifying review or required-check result. | -| Open product issues | **10** | Issue #156 plus open product-completion issues #166–#169 and #171–#176. | -| Duplicate gap-baseline PRs | **#164** (authority); **#179** closed as superseded | Only one live register remains; queued Checks on #164 are not protected-main evidence. | -| Current package version | `0.1.0` | No supported product release is established by the repository version alone. | +| Protected-main SHA | `c482ccea…` (2026-08-25T09:23Z, merge of [#239](https://github.com/ContextualWisdomLab/TEPP/pull/239)) | All as-built claims are bounded to this commit. | +| Workspace members | 57 unique Rust crates | The repository is modular, but the approved target still lacks complete semantic, compute, psychometric-engine, event-intelligence, interpretation, artifact, and visual product boundaries. | +| Workspace version | `0.2.0` (aligned across every crate manifest by #239) | A version number alone does not establish a supported product release; no signed artifact or support policy exists yet. | +| Open pull requests | **7** | Near-zero queue: four non-draft slices (#235 v0.2.0 cut, #236 register refresh, #237 lineage anchor contract, #241 final branch-gap coverage) plus three drafts (#240 rustfmt/test-arity repair, #238 image-token coverage arm, #233 span-grounding fold). | +| Draft pull requests | **3** | The 31-draft psychometric recovery stack fully drained into integration vehicles #231/#232 (both merged 2026-08-25T06:2xZ); remaining drafts are repair and event-intelligence slices, not a stacked chain. | +| Open product issues | **9** | Issues #166–#167 and #169–#174 plus #176 remain open. Result-contract issue #156, semantic-units issue #168, and queue-consolidation issue #175 are all CLOSED. | +| Current package version | `0.2.0` | No supported product release is established by the repository version alone; #235 proposes the tagged cut but has not merged. | The pull-request counts come from the live GitHub search at this snapshot. The -full exact-head classification is owned by -[issue #175](https://github.com/ContextualWisdomLab/TEPP/issues/175); this file -keeps the operator-level summary while the exact-head register below records the -volatile queue (live counts above). Passing or queued Checks on an open PR never +full exact-head classification lives in this register; re-read live GitHub +state immediately before every mutation. Passing or queued Checks on an open PR never promote that PR to implemented-main. -### Queue-consolidation progress (GAP-012) — COMPLETE +### Post-#239 state note + +[#239](https://github.com/ContextualWisdomLab/TEPP/pull/239) merged as squash +`c482ccea` and landed two things operators must know: + +1. **network_analysis estimator repairs before consumer dependence:** exact + two-sided Fisher z-transform p-values replace pseudo-p-values; fail-closed + guard ordering for non-finite correlations and short samples; negative-effect + edges excluded from the whole consensus perturbation pipeline; explicit + validated `edge_drop_probability`; bounds-safe admission helpers; and exact + red-to-green branch cases. This advances GAP-009's estimator core beyond the + #230 merge (`a69eb3e2`) it builds on. +2. **Workspace version alignment 0.1.0 → 0.2.0** across every crate manifest, + matching the CHANGELOG `[0.2.0] - 2026-08-25` entry. The version bump is not + itself a release: no tag, signed artifact, SBOM/provenance bundle, or support + policy exists yet ([GAP-011](#operator-gap-register)). + +### Queue-consolidation progress (GAP-012) — COMPLETE (issue #175 closed) -The main non-draft pull-request queue reached **zero** at 2026-08-25T02:30Z. +The main non-draft pull-request queue reached **zero** at 2026-08-25T02:30Z and +issue [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) is CLOSED. All previously queued slices landed on protected main through: 1. the hourly scheduler (independent merges of ~40 PRs); 2. batch integration vehicle [#215](https://github.com/ContextualWisdomLab/TEPP/pull/215) (31 folded green slices with per-slice merge-commit provenance); and -3. individual rebase-and-admin-merge passes for the remainder. - -The 33 remaining open PRs are all **drafts** forming stacked psychometric -recovery chains (T0VAR/drift/diffusion standardised parameter families). -They follow the stacking plan and are not independent landing vehicles. - -### Historical consolidation notes - -The hourly scheduler plus one batch integration vehicle drained the -main-conflicting green queue, and the vehicle itself is now merged: - -- [PR #215](https://github.com/ContextualWisdomLab/TEPP/pull/215) **merged at - 2026-08-25T02:12:17Z** as protected-main `5c8599442e85…`, landing the 19 - remaining folded slices (#51 #57 #60 #61 #68 #70 #72 #85 #113 #139 #141 - #143 #145 #146-content #148 #150 #151 #152 #153) with per-slice - merge-commit provenance. Folded PR heads whose exact commits are ancestors - of main auto-close as merged; advanced heads (for example the newer - #146 head) remain live work. -- The scheduler independently landed #110, #114, #115, #118–#128, #130, - #133–#135, #137–#138, and #164 while #215 was in Checks; #215 was refreshed - onto that main and re-verified before merge. -- Transient Strix provider failures (`provider/backend unavailable`) on - #48/#49/#63/#65/#137/#149/#157 were re-run rather than treated as code - defects. -- Central control-plane repair landed on `.github` main: commit `8fd471a3` - aligned the required-path Strix smoke assertions with the `gpt-5.4` - fallback contract after `a724582` replaced the nonexistent - `gpt-5.6-luna` model. New Strix runs resolve the fixed smoke. -- Post-merge branch-coverage audit of the vehicle found two production - branches (`validate_entity_label`/`validate_project_label` uppercase-label - clause) and three Python arcs in `scripts/check_coverage.py` uncovered; - [PR #219](https://github.com/ContextualWisdomLab/TEPP/pull/219) carries the - exact red-to-green tests and must land before the next coverage-gated slice. - -Remaining queue after consolidation: strix-rerun set (#48 #49 #63 #65 #157), -pending-check slices (#58 #62 #66), advanced-head #146, toolchain bump #214, -coverage repair #219, and the 49 draft PRs retained per the stacking plan, -including the psychometric recovery stack and non-psychometric drafts. +3. individual rebase-and-admin-merge passes for the remainder, including the + psychometric recovery stack drained through vehicles + [#231](https://github.com/ContextualWisdomLab/TEPP/pull/231)/[#232](https://github.com/ContextualWisdomLab/TEPP/pull/232), + coverage repair [#219](https://github.com/ContextualWisdomLab/TEPP/pull/219) + (merged 2026-08-25T03:17Z), terminal-result contract + [#157](https://github.com/ContextualWisdomLab/TEPP/pull/157) (merged + 2026-08-25T02:53Z), posterior network estimator + [#230](https://github.com/ContextualWisdomLab/TEPP/pull/230) (merged + 2026-08-25T06:24Z), and network-repair/version-alignment + [#239](https://github.com/ContextualWisdomLab/TEPP/pull/239). + +The residual seven open PRs are new forward work (release cut, register refresh, +API anchor contract, coverage completion, event-intelligence fold), not backlog. ## Snapshot open pull-request evidence The following exact-head register was fetched live from GitHub at -2026-08-25T04:24:53Z. Review decisions, required Checks, and mergeability remain -volatile; the live GitHub API supersedes this snapshot. `draft=false` is not approval, mergeability, or a +2026-08-25T10:12:54Z against protected main `c482ccea`. Review decisions, +required Checks, and mergeability remain volatile; the live GitHub API +supersedes this snapshot. `draft=false` is not approval, mergeability, or a passing-check claim. Re-read the full SHA, current review decision, required Checks, and branch rules immediately before every mutation. | PR | Exact current head | Draft | Base | Title | |---:|---|:---:|---|---| -| #92 | `56fadb1d99be2478f86c7dd493d9240f4f7af349` | false | main | feat(orchestrator): serve interpretation POSTs on a loopback HTTP listener | -| #132 | `426c2d38e8d46914b3d6743aa616bd3383481715` | false | main | feat(relation): refuse unobserved pairs as no-relationship | -| #181 | `96e8d1c21924124d144f6273893eda8ac6b41b2d` | true | agent/psychometric-posterior-esem-input | feat(psychometric): recover Driver later-occasion variance of predetermined T0VAR | -| #182 | `ee999e1ed23327a75d9cf4eb09a4652561f07cab` | true | agent/psychometric-predetermined-later-t0var | feat(psychometric): recover Driver lagged covariance of predetermined T0VAR | -| #183 | `c10097be0f6f4ff8dd8e6db21dce8818630fe17f` | true | agent/psychometric-predetermined-lagged-t0var | feat(psychometric): recover Driver first-occasion variance of predetermined T0VAR | -| #184 | `6b93147e668ff414240f3ea94997f2937c0817f1` | true | agent/psychometric-predetermined-initial-t0var | feat(psychometric): recover Driver later-start lagged covariance of predetermined T0VAR | -| #185 | `69ffec6425863b2842094ff1b1d6c07d8eee6b5b` | true | agent/psychometric-predetermined-later-lagged-t0var | feat(psychometric): recover Driver later-start later-occasion variance of predetermined T0VAR | -| #187 | `6afd048d1a78bd7e1f68555182ec6a1f0db9ea8c` | true | agent/psychometric-predetermined-later-start-later-t0var | feat(psychometric): recover Driver p.16 discreteDRIFTstd after positive asymDIFFUSION | -| #188 | `444304d2d702cceb47cd4fdf695ba630d7e7c83a` | true | agent/psychometric-standardised-discrete-drift | feat(psychometric): recover Driver p.16 discreteDIFFUSIONstd after positive asymDIFFUSION | -| #189 | `0836c359fab7878084ac3a711920550eb6cacb0d` | true | agent/psychometric-standardised-discrete-diffusion | feat(psychometric): recover Driver p.16 DIFFUSIONstd after positive asymDIFFUSION | -| #190 | `adb0bc6f2a1b0684eb9242bcb0d6cddd1ff09ae3` | true | agent/psychometric-standardised-continuous-diffusion | feat(psychometric): recover Driver p.16 DRIFTstd after positive asymDIFFUSION | -| #192 | `1690c7b65693cf27c8f1730150d67084594aef07` | true | agent/psychometric-standardised-continuous-drift | feat(psychometric): recover Driver p.16 asymTIPREDEFFECTstd after positive variances | -| #193 | `6235a3fc6fd6899f85f74aed71053bd50529c34c` | true | agent/psychometric-standardised-asymptotic-tipred-effect | feat(psychometric): recover Driver p.16 finite-interval TIPREDEFFECTstd after positive variances | -| #194 | `471b115e891be11fb41eb75a5cc3073258ab0f34` | true | agent/psychometric-standardised-asymptotic-tipred-effect | feat(psychometric): recover Driver p.16 TIPREDEFFECTstd after positive variances | -| #195 | `cf275b25611953bd405550f96df257c77df01751` | true | agent/psychometric-standardised-continuous-tipred-effect | feat(psychometric): recover Driver p.16 CINTstd after positive asymDIFFUSION | -| #196 | `1cb91b43002765bf5b855be51e55cb29db41a599` | true | agent/psychometric-standardised-continuous-tipred-effect | feat(psychometric): recover Driver Table 3 T0TIPREDEFFECTstd after positive T0VAR | -| #197 | `a19ea1ecba3d773470f12ed4dbbeb8a01b987f26` | true | agent/psychometric-standardised-initial-tipred-effect | feat(psychometric): recover Driver Table 3 T0TDPREDEFFECTstd after positive T0VAR | -| #198 | `52a9b4ceb26bdd89e6e49c56fb473d96c68ca352` | true | agent/psychometric-standardised-initial-tipred-effect | feat(psychometric): recover Driver 2017-era addedT0TIPREDVAR after T0TIPREDEFFECT | -| #199 | `7a476ed3c39e4ba14c9c51f5cbbafc33310979d2` | true | agent/psychometric-added-t0-tipred-var | feat(psychometric): recover Driver Eq. 5 of addedT0TIPREDVAR extra observed TI variance | -| #200 | `8f429471e2b249e76c9b722eaf2443518b666c98` | true | agent/psychometric-initial-tipred-observed-variance | feat(psychometric): recover Driver Eq. 5 of addedTIPREDVAR extra observed TI variance | -| #202 | `2d2d3efe5fd2f742a335e071f22fc1738c51ab27` | true | agent/psychometric-asymptotic-tipred-observed-variance | feat(psychometric): recover Driver p.16 TDPREDEFFECTstd after positive variances | -| #203 | `e5a63d0c7ffac91e9945f6c483152a0bdf267525` | true | agent/psychometric-asymptotic-tipred-observed-variance | feat(psychometric): recover Driver 2017-era addedTIPREDVARstd after addedTIPREDVAR | -| #204 | `ac757e5e4f3651cad98fa88d5a44db570eb1c3cf` | true | agent/psychometric-standardised-continuous-tdpred-effect | feat(psychometric): recover Driver Table 3 T0TDPREDEFFECTstd after positive T0VAR | -| #205 | `db60fe4eb47abf1efb793ccef37597689d11d09a` | true | agent/psychometric-standardised-initial-tdpred-std | feat(psychometric): recover Driver p.16 T0VARstd after positive T0VAR | -| #206 | `dcb339f3e2e5ac2dd2dda9b61b5d2c9b811c7636` | true | agent/psychometric-standardised-initial-tdpred-std | feat(psychometric): recover analog of addedT0TIPREDVAR for first-occasion TD extra | -| #207 | `5dd0c01549c93fa05f57df5658058244c7b675e4` | true | agent/psychometric-standardised-initial-latent-variance | feat(psychometric): recover Driver p.16 TRAITVARstd after positive TRAITVAR | -| #208 | `adcd68a582725130c046239753290d6cb5f3b8b7` | true | agent/psychometric-standardised-trait-variance | feat(psychometric): recover Driver p.16 MANIFESTTRAITVARstd after positive Ψ_τ | -| #209 | `5e3ef38256f42ce09a934eb0f3893e02a40ad6b7` | true | agent/psychometric-added-initial-tdpred-var | feat(psychometric): recover Eq. 5 of analog addedT0TIPREDVAR extra observed TD variance | -| #210 | `22202c57c0818d3efb28438e95bdde5d0e7143b0` | true | agent/psychometric-standardised-manifest-trait-variance | feat(psychometric): recover Driver p.16 MANIFESTVARstd after positive Θ | -| #211 | `643b9beb76ea6bc6b3a468bdb0f08e1a820cae9f` | true | agent/psychometric-standardised-manifest-variance | feat(psychometric): recover Driver p.16 TIPREDVARstd after positive TIPREDVAR | -| #213 | `d32c069c5653434302dc2507e272ff916cb7b16a` | true | agent/psychometric-standardised-asymptotic-diffusion | feat(psychometric): recover Driver p.16 discreteCINTstd after positive p | -| #216 | `91903db726f1da6b398e0a32c5a401126598154f` | true | agent/psychometric-standardised-discrete-intercept | feat(psychometric): recover Driver p.16 asymCINTstd after positive p | -| #218 | `19279547f5a8622d9584d95822ec36ae3830beeb` | true | agent/psychometric-standardised-asymptotic-intercept | feat(psychometric): recover Driver p.16 T0MEANSstd after positive T0VAR | +| #233 | `0a86d8d110702c6ec088bd7ec40d70e637300a48` | true | main | Fold span grounding into EventMention (#170 / GAP-007) | +| #235 | `b3bc508219810c0282f53b0391514950177be923` | false | main | chore(release): cut v0.2.0 — version alignment and psychometric stack restoration | +| #236 | `0236d3cdb06788967134e66b68e4638c9ebc532d` | false | main | docs(gap): refresh register after stack drain and #230 merge | +| #237 | `a5e97b50a9a17b196a3825438f1830016e92235e` | false | main | feat(api): publish TEPP lineage criterion anchor contract | +| #238 | `1521f1d4e905f97c41172f6ee294c369df078710` | true | main | Cover is_image_media_type_token non-image prefix arm | +| #240 | `1ca11653ae9220229dde760a3ef50b7537118e7d` | true | main | Repair #232 rustfmt and psychometric test arity | +| #241 | `a12e48c1e61b3af2a288c221ca67e25213fb2f90` | false | main | fix(coverage): close final branch gaps in evidence and network estimators | + +Note on #241: the #239 squash-merge landed through its pre-fix head, so #241 +cherry-picks the final coverage-and-semantics commit (dead singleton-guard +removal in `network_analysis::consensus`, unreachable let-else replacement in +`evidence_core::image_unit`) onto current main. Until #241 merges, protected +main carries those two dead-branch semantics points; they are test debt, not a +weakened 100% gate. Review decisions, required Checks, and mergeability remain volatile; re-read them immediately before every mutation. This snapshot is not merge authorization @@ -157,10 +138,10 @@ and does not treat queued or passing Checks as shipped protected-main behavior. ## Protected-main as-built baseline -Protected `main` contains 48 unique Rust crate boundaries in the current -workspace manifest. The `members` and `default-members` arrays enumerate the -same crate set for distinct Cargo commands; the unique-crate count is the -authoritative modularity measure. +Protected `main` contains 57 unique Rust crate boundaries in the current +workspace manifest (as of `c482ccea`). The `members` and `default-members` +arrays enumerate the same crate set for distinct Cargo commands; the +unique-crate count is the authoritative modularity measure. The core boundaries include: ```text @@ -174,99 +155,105 @@ corpus_split tepp_simulation validation_core tepp_api +topic_measurement +network_analysis +psychometric_core +analysis_engine +interpretation_gateway +compute_backend ``` The traceability ledger records meaningful protected-main implementation in immutable evidence, six clocks and interval reasoning, forward transitions, event mention/instance separation, weighted multiple membership, cutoff-safe splits, validation metrics, simulations, PostgreSQL slices, versioned API/export -contracts, orchestration routing, privacy authorization, and release-evidence -generation. +contracts, orchestration routing, privacy authorization, release-evidence +generation, the CPU topic-measurement reference estimator, the repaired +posterior network estimator (#230 + #239), the Driver et al. (2017) SDE +recovery suite (#231/#232), the deterministic analysis-run execution engine, +the loopback interpretation gateway, and VRAM-policy compute types. Protected `main` does **not** yet establish the complete approved product. In -particular, it does not contain the full multilingual semantic pipeline, a -shared-latent temporal topic estimator, a production longitudinal ESEM/DSEM -estimator, a calibrated TDT/CHRONOS workflow, a posterior network estimator, -real accelerator kernels, a production interpretation gateway, the coordinated -visual workspace, or a supported multi-tenant release. +particular, it does not contain the full multilingual semantic pipeline beyond +the first span slice, full Bayesian candidate-`K` topic fitting, a composed +longitudinal ESEM/DSEM estimation engine, a calibrated TDT/CHRONOS workflow, +repeated Leiden consensus clustering with buyer-facing exports, real accelerator +kernels with hardware parity, an executed contextual-orchestrator interpreter, +the coordinated visual workspace, or a supported multi-tenant release. ## Operator-gap register | ID | Operator-visible gap | Maturity | Delivery status | Protected-main authority | Current delivery authority | Current head SHA | Closure evidence | |---|---|---|---|---|---|---|---| -| GAP-001 | Submission can produce a durable accepted receipt, but protected `main` lacks the separate deterministic terminal-result lifecycle. | `active-PR` | landing vehicle | `e65cd66` (accepted receipt only) | [#156](https://github.com/ContextualWisdomLab/TEPP/issues/156) / [PR #157](https://github.com/ContextualWisdomLab/TEPP/pull/157) | `340087494b0a` | Exact request/result/snapshot/cutoff/model/profile binding, typed terminal failures, deterministic retrieval, current-head checks, and qualifying review. Cutoff-safe execution from closed stacked PR #178 is on this head only and is not implemented-main. | -| GAP-002 | LineageWeave and other modular consumers cannot yet rely on the complete protected-main HTTP evidence/result boundary. | `partial` | landing vehicle | `e65cd66` (LineageWeave consumer ingress and versioned API contract are implemented-main; durable terminal-result completion remains separate) | [PR #155](https://github.com/ContextualWisdomLab/TEPP/pull/155) (merged) | `—` | Complete the durable result lifecycle under #156/#157 and preserve versioned consumer contracts; closed stacked PRs #158 and #159 are provenance, not additional implementation authority. | +| GAP-001 | Submission produces a durable accepted receipt, and the deterministic terminal-result lifecycle is now implemented-main. | `implemented-main` | closed on protected main | `340087494b0a` lineage merged through [PR #157](https://github.com/ContextualWisdomLab/TEPP/pull/157) (merged 2026-08-25T02:53Z); [#156](https://github.com/ContextualWisdomLab/TEPP/issues/156) CLOSED | — | — | Exact request/result/snapshot/cutoff/model/profile binding, typed terminal failures, deterministic retrieval, and cutoff-safe execution are protected-main behavior as of the #157 merge. | +| GAP-002 | LineageWeave and other modular consumers can rely on the complete protected-main HTTP evidence/result boundary. | `partial` | consumer hardening remains | Terminal-result lifecycle implemented-main via #157; versioned API contract intact | [#156](https://github.com/ContextualWisdomLab/TEPP/issues/156) (closed) / [PR #155](https://github.com/ContextualWisdomLab/TEPP/pull/155) (merged) | — | Remaining work is consumer-side adoption evidence and any versioned-contract drift discovered during integration; core boundary is no longer the gap it was. | | GAP-003A | Immutable evidence cannot yet be submitted to a durable validation run that produces operator-usable scientific acceptance evidence. | `accepted-target` | product-completion | `e65cd66` (validation metrics are library-level only) | [#166](https://github.com/ContextualWisdomLab/TEPP/issues/166) | `—` (issue program; no current implementation PR) | Compose/CLI/API execution must bind immutable evidence, cutoffs, model configuration, validation metrics, and reproducibility manifests to one idempotent run. | | GAP-003B | Scientific result artifacts cannot yet be persisted, restarted, and recovered as one supported operator workflow. | `accepted-target` | product-completion | `e65cd66` (persistence contracts lack E2E recovery) | [#166](https://github.com/ContextualWisdomLab/TEPP/issues/166) | `—` (issue program; no current implementation PR) | Durable storage, migration/rollback, restart/recovery, artifact digest verification, and terminal retrieval must pass against a real Compose deployment. | | GAP-003C | The persistence slice classifies concurrent-write SQLSTATEs, but has no measured hot-partition detection, routing, or mitigation for tenant/result workloads. | `accepted-target` | product-completion | `e65cd66` (conflict classification only; no measured partition control) | [#166](https://github.com/ContextualWisdomLab/TEPP/issues/166) | `—` (issue program; no current implementation PR) | A real Compose/PostgreSQL workload identifies hot keys and partition skew, applies bounded tenant/time or result routing without weakening 3NF or temporal authority, and proves conflict rate, latency, recovery, and migration/rollback behavior under load. | -| GAP-004 | The central shared-latent temporal/relational topic estimator is absent. | `partial` | product vertical | CPU `f64` reference plus fitted candidate-`K` scoring | [#167](https://github.com/ContextualWisdomLab/TEPP/issues/167) | this PR | GPU, method effects, full Bayesian sampling, and topic birth/split/merge remain. This is not full #167 closure. | -| GAP-005 | Real multilingual documents are not yet transformed into validated exact-span semantic units and versioned shared concepts. | `partial` | product vertical | `e65cd66` (semantic_core exact-span units and language-profile validation are implemented-main as the first slice) | [#168](https://github.com/ContextualWisdomLab/TEPP/issues/168) / [PR #201](https://github.com/ContextualWisdomLab/TEPP/pull/201) (merged) | `—` | Closure still requires concept alignment, Unicode/layout/language-tailored processing, unknown-concept review, multilingual calibration/invariance, image-position evidence, and prompt-injection tests. | -| GAP-006 | Posterior topic measurements cannot yet be fitted through a complete cross-classified longitudinal ESEM/DSEM engine. | `accepted-target` | product vertical | `e65cd66` (temporal and membership primitives only) | [#169](https://github.com/ContextualWisdomLab/TEPP/issues/169) / [PR #119](https://github.com/ContextualWisdomLab/TEPP/pull/119) | `47ab763d49b1` | Plausible-value/joint uncertainty, invariance, irregular event time, within/between separation, multiple membership, true-parameter recovery, and causal-claim refusal. | -| GAP-007 | TDT detection/tracking and CHRONOS schema/forecast/temporal reasoning remain isolated bounded gates rather than one calibrated product workflow. | `accepted-target` | product vertical | protected main already has isolated TDT/CHRONOS scoring gates in `event_core`; this PR folds span grounding into `EventMention` | [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) | this PR | This PR: `EventMention` is the only constructible mention type and must cite an exact source span. Remaining: one versioned TDT/CHRONOS workflow, interval consistency, persistence, and exports. | +| GAP-004 | The central shared-latent temporal/relational topic estimator is absent. | `partial` | product vertical | CPU `f64` TRSL-TM reference estimator with ALR/ILR coordinates and refusal gates is implemented-main (v0.2.0 `topic_measurement`); fitted candidate-`K` scoring present | [#167](https://github.com/ContextualWisdomLab/TEPP/issues/167) | — | GPU, method effects, full Bayesian sampling, and topic birth/split/merge remain. This is not full #167 closure. | +| GAP-005 | Real multilingual documents are not yet transformed into validated exact-span semantic units and versioned shared concepts. | `partial` | product vertical | `e65cd66` lineage (semantic_core exact-span units and language-profile validation are implemented-main as the first slice from [PR #201](https://github.com/ContextualWisdomLab/TEPP/pull/201)) | [#168](https://github.com/ContextualWisdomLab/TEPP/issues/168) CLOSED COMPLETED 2026-08-24; residual evidence tracked under product completion (#166/#169) | `—` | Remaining evidence beyond the closed first slice: concept alignment, Unicode/layout/language-tailored processing, unknown-concept review, multilingual calibration/invariance, image-position evidence, and prompt-injection tests. | +| GAP-006 | Posterior topic measurements cannot yet be fitted through a complete cross-classified longitudinal ESEM/DSEM engine. | `partial` | product vertical | Psychometric recovery stack drained onto protected main through integration vehicles [#231](https://github.com/ContextualWisdomLab/TEPP/pull/231)/[#232](https://github.com/ContextualWisdomLab/TEPP/pull/232) (both merged 2026-08-25T06:2xZ): Driver et al. (2017) SDE discrete-time recovery suite with standardised-parameter families and true-parameter RMSE tests is implemented-main (`psychometric_core`) | [#169](https://github.com/ContextualWisdomLab/TEPP/issues/169) | — | Remaining: joint plausible-value uncertainty wiring, full invariance evidence, irregular event-time fitting at production scale, multiple-membership integration with posterior coordinates, and end-to-end composition under #166/#167. Recovery primitives alone are not the ESEM/DSEM engine. | +| GAP-007 | TDT detection/tracking and CHRONOS schema/forecast/temporal reasoning remain isolated bounded gates rather than one calibrated product workflow. | `accepted-target` | product vertical | `e65cd66` (event/time primitives only) | [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) / [PR #70](https://github.com/ContextualWisdomLab/TEPP/pull/70) | `7a1f33aa68c1` | Span-grounded mentions, calibrated TDT metrics, schema/forecast hypothesis states, interval consistency, known-truth recovery, persistence, and exports. | | GAP-008 | GPU support is policy-only; no production estimator kernel has real hardware parity or declared VRAM evidence. | `accepted-target` | product vertical | `e65cd66` (VRAM policy only) | [#171](https://github.com/ContextualWisdomLab/TEPP/issues/171) / [PR #51](https://github.com/ContextualWisdomLab/TEPP/pull/51) | `1801501c4d7c` | Real CUDA/portable backend execution, CPU parity, streamed memory, bounded OOM/fallback, hardware profiles, telemetry, and no skipped-support claim. | -| GAP-009 | Topic association and cluster outputs lack posterior-valid estimation, uncertainty, edge stability, and consensus communities. | `accepted-target` | product vertical | `e65cd66` (network primitives only) | [#172](https://github.com/ContextualWisdomLab/TEPP/issues/172) / [PR #71](https://github.com/ContextualWisdomLab/TEPP/pull/71) | `2588f38281b9` | Valid log-ratio coordinates, interval/stability-bearing edges, repeated Leiden consensus, known-truth network/cluster recovery, and reproducible exports. | +| GAP-009 | Topic association and cluster outputs lacked posterior-valid estimation, uncertainty, edge stability, and consensus communities. | `partial` (estimator core + repairs landed; Leiden consensus + buyer workflow remain) | product vertical | `a69eb3e2` (posterior log-ratio edge estimator merged from [PR #230](https://github.com/ContextualWisdomLab/TEPP/pull/230)) advanced by [#239](https://github.com/ContextualWisdomLab/TEPP/pull/239) (`c482ccea`): exact two-sided Fisher z-transform p-values driving Benjamini–Hochberg admission (Benjamini & Hochberg, 1995), percentile-bootstrap credible intervals and selection fractions (Efron, 1979), fail-closed guard ordering for non-finite correlations and short samples, negative-effect edges excluded from the whole consensus perturbation pipeline, explicit validated `edge_drop_probability`, bounds-safe admission helpers | [#172](https://github.com/ContextualWisdomLab/TEPP/issues/172) | — (#241 carries two dead-guard removals) | Remaining closure evidence: repeated Leiden consensus replacing the union-find stand-in (Traag et al., 2019), known-truth network/cluster recovery at production scale, and reproducible exports wired into the end-to-end run (#166). | | GAP-010 | Operators lack coordinated accessible visual analytics and exact-value export workflows. | `accepted-target` | product vertical | `e65cd66` (no visual workspace) | [#173](https://github.com/ContextualWisdomLab/TEPP/issues/173) | `—` (Figma work not started) | Real Figma File ID in ADR, Storybook/design tokens, ten PRD views, exact-value tables, accessible interaction/print/PDF states, provenance, and source-consistent exports. | | GAP-011 | TEPP is not yet an operable multi-tenant service or supported release. | `accepted-target` | product vertical | `e65cd66` (library contracts only) | [#174](https://github.com/ContextualWisdomLab/TEPP/issues/174) | `—` (issue program; no current implementation PR) | Durable queue/storage, OIDC/RLS/purpose controls, OpenTelemetry/SLOs, load/recovery, migrations, signed release/SBOM/provenance, assurance evidence, and support policy. | -| GAP-012 | The 71-PR queue obscured authority, repeatedly staled exact-head evidence, and fragmented product boundaries. | `partial` | release blocker (draining) | `5c8599442e85` (vehicle #215 merged 2026-08-25T02:12Z) | [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) | `—` (live queue program) | Residual queue: nine non-draft slices, coverage repair #219, and the 49-draft stack. Remaining PRs classified; superseded work closed with provenance; scheduler prioritizes consolidation; queue reaches zero before GA. | -| GAP-013 | Evidence-grounded LLM interpretation is routed but not executed and validated as a production interpreter/verifier port. | `partial` | active integration | `e65cd66` (routing and refusal contracts only) | [#176](https://github.com/ContextualWisdomLab/TEPP/issues/176), [PR #69](https://github.com/ContextualWisdomLab/TEPP/pull/69), [PR #165](https://github.com/ContextualWisdomLab/TEPP/pull/165) | `8e4a3ca9cc80` / `34083c3f5d66` | Contextual-orchestrator execution, evidence citations, verifier refusals, comparable-budget ablations, provider eligibility/fallback, abstention, live/offline contract tests, and no numerical-authority escalation. | -| GAP-014 | README/TRD and some PR descriptions lag protected-main and live queue reality. | `partial` | documentation drift | `e65cd66` (documentation is not fully synchronized) | [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) | `—` (queue-consolidation work) | Reconcile README, TRD, traceability, ADR maturity, CHANGELOG, preferred-merge declarations, and exact protected-main evidence. | -| GAP-015 | There was no canonical live product/operator-gap register tied to documentation validation. | `implemented-main` (register + validator); this refresh is the live maintenance slice | register refresh | `5c8599442e85` (register and validator are implemented-main; this snapshot refresh keeps the volatile queue current) | [PR #217](https://github.com/ContextualWisdomLab/TEPP/pull/217) merged; [PR #164](https://github.com/ContextualWisdomLab/TEPP/pull/164) is the merged authority; closed [PR #179](https://github.com/ContextualWisdomLab/TEPP/pull/179) is not a second register | `—` (each refresh PR carries its own current head at merge time) | Land each refresh after exact-head checks and independent review, then regenerate it whenever protected-main or the live queue changes. | -| GAP-018 | The #215 consolidation vehicle landed with two uncovered production branches in `persistence_postgres` label validation and three uncovered Python arcs in `scripts/check_coverage.py`. | `active-PR` | coverage repair | `5c8599442e85` (gates still enforce 100%; the gaps are test debt, not weakened gates) | [PR #219](https://github.com/ContextualWisdomLab/TEPP/pull/219) | branch `fix/coverage-union-gaps-post215` | Exact red-to-green cases: uppercase-label clause for entity/project validators, blank-history comma-scan arcs, escaped-char-literal scanner arc. Closure requires exact-head Checks plus independent review on protected main. | -| GAP-016 | Hourly PR maintenance used an older central scheduler revision whose per-repository sweep budgets could amplify the queued review workload. | `active-PR` | operability hardening | `e65cd66` (caller pin before central budget hardening) | [PR #177](https://github.com/ContextualWisdomLab/TEPP/pull/177) | `580d45206536` | The change pins a verified central revision immutably; closure still requires exact-head hosted Checks, resolved threads, and independent review. | -| GAP-017 | Accepted analysis runs have a terminal DTO, and cutoff-safe execution exists only on the #157 head after stacked PR #178 merged into that branch. | `active-PR` | product vertical | `e65cd66` (accepted receipt only) | [#166](https://github.com/ContextualWisdomLab/TEPP/issues/166) / [PR #157](https://github.com/ContextualWisdomLab/TEPP/pull/157) | `340087494b0a` | Exact availability cutoff, snapshot binding, multiple-membership preservation, digest integrity, redacted no-eligible failure, and realistic end-to-end tests on protected main. Closed stacked PR #178 is not implemented-main. | +| GAP-012 | The 71-PR queue obscured authority, repeatedly staled exact-head evidence, and fragmented product boundaries. | `implemented-main` (consolidation complete) | closed | `c482ccea` (#239 merged 2026-08-25T09:23Z); [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) CLOSED; queue drained from 71 through #215, the hourly scheduler, vehicles #231/#232, and individual passes | — | — | Residual queue is seven forward-work PRs (release cut, register refresh, API anchor contract, coverage completion, event-intelligence fold); no backlog remains. Exact-head discipline stays enforced by this register's refresh rule. | +| GAP-013 | Evidence-grounded LLM interpretation is routed but not executed and validated as a production interpreter/verifier port. | `partial` | active integration | `e65cd66` lineage (routing and refusal contracts implemented-main; loopback interpretation POSTs landed via #92/#107) | [#176](https://github.com/ContextualWisdomLab/TEPP/issues/176), [PR #69](https://github.com/ContextualWisdomLab/TEPP/pull/69), [PR #165](https://github.com/ContextualWisdomLab/TEPP/pull/165) | `8e4a3ca9cc80` / `34083c3f5d66` | Contextual-orchestrator execution, evidence citations, verifier refusals, comparable-budget ablations, provider eligibility/fallback, abstention, live/offline contract tests, and no numerical-authority escalation. | +| GAP-014 | README/TRD and some PR descriptions lag protected-main and live queue reality. | `partial` | documentation drift | Documentation synchronized to `c482ccea` through register refreshes including this file and [#236](https://github.com/ContextualWisdomLab/TEPP/pull/236) | [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) (closed) | — | Remaining: reconcile README/TRD crate counts to 57, retire CHANGELOG `[Unreleased]` bullets superseded by the v0.2.0 entry (duplicated gate-integrity bullets and the now-landed network_analysis repair bullet), and keep ADR maturity current. | +| GAP-015 | There was no canonical live product/operator-gap register tied to documentation validation. | `implemented-main` (register + validator); this refresh is the live maintenance slice | register refresh | `c482ccea` (register and validator are implemented-main; this snapshot refresh keeps the volatile queue current) | [PR #217](https://github.com/ContextualWisdomLab/TEPP/pull/217) merged; [PR #164](https://github.com/ContextualWisdomLab/TEPP/pull/164) is the merged authority; open [#236](https://github.com/ContextualWisdomLab/TEPP/pull/236) is a parallel refresh whose base predates #239 | `—` (each refresh PR carries its own current head at merge time) | Land each refresh after exact-head checks and independent review, then regenerate it whenever protected-main or the live queue changes. Rebase or supersede #236 against `c482ccea` so only one refresh authority lands per queue state. | +| GAP-016 | Hourly PR maintenance used an older central scheduler revision whose per-repository sweep budgets could amplify the queued review workload. | `active-PR` | operability hardening | `e65cd66` (caller pin before central budget hardening) | [PR #177](https://github.com/ContextualWisdomLab/TEPP/pull/177) | `580d45206536` | The change pins a verified central revision immutably; closure still requires exact-head hosted Checks, resolved threads, and independent review. With the queue near zero the urgency is low but the slice remains unlanded. | +| GAP-017 | Accepted analysis runs have a terminal DTO and cutoff-safe execution on protected main after #157 merged. | `implemented-main` | closed on protected main | [PR #157](https://github.com/ContextualWisdomLab/TEPP/pull/157) merged 2026-08-25T02:53Z carrying the terminal result contract and folded cutoff-safe execution from closed stacked PR #178 | [#156](https://github.com/ContextualWisdomLab/TEPP/issues/156) (closed) / [#166](https://github.com/ContextualWisdomLab/TEPP/issues/166) | — | Exact availability cutoff, snapshot binding, multiple-membership preservation, digest integrity, redacted no-eligible failure, and realistic end-to-end tests are protected-main behavior; remaining E2E composition work belongs to #166. | +| GAP-018 | Coverage debt: the #219 repair landed, and #239 added 27 exact branch cases, leaving two dead-guard semantics points queued on [#241](https://github.com/ContextualWisdomLab/TEPP/pull/241). | `active-PR` | coverage completion | `5c8599442e85` lineage + #219 (merged 2026-08-25T03:17Z) + #239 (`c482ccea`): gates enforce 100% unique production arms; remaining items are dead-branch removals in `network_analysis::consensus` and `evidence_core::image_unit` | [PR #241](https://github.com/ContextualWisdomLab/TEPP/pull/241) | `a12e48c1e61b` | Merge #241 after exact-head Checks plus independent review so protected main carries the final semantics cleanup; two provably-dead singleton guards and an unreachable let-else are the last known items. | ## Product-completion issue register | Issue | Product vertical | Depends on / constrains | |---:|---|---| -| [#156](https://github.com/ContextualWisdomLab/TEPP/issues/156) | Completed analysis-run result contract | Must land before consumers can call accepted work a measurement result. | +| [#156](https://github.com/ContextualWisdomLab/TEPP/issues/156) **CLOSED** | Completed analysis-run result contract | Landed on protected main through PR #157 (merged 2026-08-25T02:53Z). | | [#166](https://github.com/ContextualWisdomLab/TEPP/issues/166) | Executable end-to-end analysis run, recovery, and hot-partition readiness | Integrates all scientific/service verticals; cannot substitute placeholders or hide write skew behind an unmeasured queue. | -| [#167](https://github.com/ContextualWisdomLab/TEPP/issues/167) | Shared-latent temporal topic CPU estimator | Numerical foundation for K selection, networks, psychometrics, interpretation, and product E2E. | -| [#168](https://github.com/ContextualWisdomLab/TEPP/issues/168) | Multilingual semantic units and concept dictionary | Supplies validated span-grounded estimator evidence. | -| [#169](https://github.com/ContextualWisdomLab/TEPP/issues/169) | Multilevel longitudinal ESEM/DSEM | Consumes posterior topic coordinates and membership/time contracts. | -| [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) | TDT/CHRONOS event intelligence | Consumes evidence/time/event contracts and supplies calibrated event artifacts. | +| [#167](https://github.com/ContextualWisdomLab/TEPP/issues/167) | Shared-latent temporal topic CPU estimator | Numerical foundation for K selection, networks, psychometrics, interpretation, and product E2E; CPU reference landed, full estimator remains. | +| [#168](https://github.com/ContextualWisdomLab/TEPP/issues/168) **CLOSED** | Multilingual semantic units and concept dictionary | Closed COMPLETED 2026-08-24; first-slice span units are implemented-main, remaining invariance/calibration evidence tracks product completion elsewhere. | +| [#169](https://github.com/ContextualWisdomLab/TEPP/issues/169) | Multilevel longitudinal ESEM/DSEM | Consumes posterior topic coordinates and membership/time contracts; recovery stack landed via #231/#232, engine composition remains. | +| [#170](https://github.com/ContextualWisdomLab/TEPP/issues/170) | TDT/CHRONOS event intelligence | Consumes evidence/time/event contracts and supplies calibrated event artifacts; span-grounding fold queued as draft #233. | | [#171](https://github.com/ContextualWisdomLab/TEPP/issues/171) | Real GPU compute and parity | Accelerates production estimators only after CPU authority is stable. | -| [#172](https://github.com/ContextualWisdomLab/TEPP/issues/172) | Posterior network and consensus clustering | Depends on a real fitted topic posterior. | +| [#172](https://github.com/ContextualWisdomLab/TEPP/issues/172) | Posterior network and consensus clustering | Estimator core plus #239 repairs landed; Leiden consensus and buyer workflow remain. | | [#173](https://github.com/ContextualWisdomLab/TEPP/issues/173) | Accessible visual analytics and exports | Starts after stable API/artifact contracts; requires Figma and Storybook evidence. | -| [#174](https://github.com/ContextualWisdomLab/TEPP/issues/174) | Commercial deployment/release/support | Wraps a scientifically complete product without weakening gates. | -| [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) | PR queue and delivery consolidation | Immediate release-system priority; prevents further unbounded fragmentation. | +| [#174](https://github.com/ContextualWisdomLab/TEPP/issues/174) | Commercial deployment/release/support | Wraps a scientifically complete product without weakening gates; v0.2.0 version alignment (#239) is a prerequisite slice, not closure. | +| [#175](https://github.com/ContextualWisdomLab/TEPP/issues/175) **CLOSED** | PR queue and delivery consolidation | Queue consolidation completed at near-zero; issue closed after the residual queue drained through #239. | | [#176](https://github.com/ContextualWisdomLab/TEPP/issues/176) | Contextual-orchestrator interpreter/verifier | Consumes validated artifacts and cannot promote scientific truth. | ## Priority pull-request queue -This table is intentionally non-exhaustive. Issue #175 owns the full exact-head -inventory and classification. The pull request's live page is authoritative -because its head can change after this file is committed. +This table lists every open pull request at snapshot time. The pull request's +live page is authoritative because its head can change after this file is +committed. | PR | Current delivery role | Required next action | |---:|---|---| -| [#219](https://github.com/ContextualWisdomLab/TEPP/pull/219) | Post-#215 branch-coverage repair (two Rust label-validation branches, three Python scanner arcs) | Exact-head Checks plus independent review, then merge before the next coverage-gated slice. | -| [#157](https://github.com/ContextualWisdomLab/TEPP/pull/157) | Terminal result contract for #156, including folded cutoff-safe execution from closed #178 | Complete exact-head review/check gates; keep #156 open until protected-main verification. | -| [#146](https://github.com/ContextualWisdomLab/TEPP/pull/146) | Advanced head of the episode-boundary slice beyond the folded vehicle content | Re-run exact-head Checks on `aac43e83`; land only the delta over protected main. | -| [#66](https://github.com/ContextualWisdomLab/TEPP/pull/66) | TDT mention-link precision/recall scoring | Re-run exact-head Checks; fold into event-intelligence landing under #170. | -| [#65](https://github.com/ContextualWisdomLab/TEPP/pull/65) | First-story detection FAR/miss-rate scoring | Re-run exact-head Checks; fold into event-intelligence landing under #170. | -| [#63](https://github.com/ContextualWisdomLab/TEPP/pull/63) | Refuse TF-IDF/BM25 as inferential weights | Re-run exact-head Checks; connect to estimator weights under #167/#172. | -| [#62](https://github.com/ContextualWisdomLab/TEPP/pull/62) | Simulation excludes delayed documents before availability | Re-run exact-head Checks; keep temporal eligibility authority intact. | -| [#58](https://github.com/ContextualWisdomLab/TEPP/pull/58) | Embedded image URIs as positional non-lexical units | Re-run exact-head Checks; feeds GAP-005 image-position evidence. | -| [#49](https://github.com/ContextualWisdomLab/TEPP/pull/49) | Posterior ESEM input gates with true-parameter RMSE | Re-run exact-head Checks; connect to psychometric engine under #169. | -| [#48](https://github.com/ContextualWisdomLab/TEPP/pull/48) | Logistic-normal/ALR coordinate slice, including folded stacked #191 TRSL lineage artifacts | Resolve conflict/dirty state onto current main; coordinates and lineage artifacts are not candidate-K fitting. | -| [#74](https://github.com/ContextualWisdomLab/TEPP/pull/74)–[#218](https://github.com/ContextualWisdomLab/TEPP/pull/218) drafts | Draft stacked temporal/privacy/method/psychometric recoveries | Keep stacked; convert to ready one link at a time from the stack root upward; never promote drafts or predecessor heads as protected-main. Closed stacked #191 landed on #48 only. | +| [#235](https://github.com/ContextualWisdomLab/TEPP/pull/235) | Tagged v0.2.0 release cut over the aligned workspace (#239 already aligned versions on main) | Exact-head Checks plus independent review; verify tag/artifact/SBOM evidence before treating 0.2.0 as a supported release (#174). | +| [#236](https://github.com/ContextualWisdomLab/TEPP/pull/236) | Register refresh against pre-#239 base `cf0e0ad7` | Rebase onto `c482ccea` or supersede against this file so only one post-#239 refresh authority lands. | +| [#237](https://github.com/ContextualWisdomLab/TEPP/pull/237) | TEPP lineage criterion anchor contract for API consumers | Exact-head Checks plus independent review; keep versioned consumer contracts intact (GAP-002). | +| [#241](https://github.com/ContextualWisdomLab/TEPP/pull/241) | Final branch-gap coverage cherry-pick after the #239 squash-merge (dead singleton guards, unreachable let-else) | Merge before further network/evidence coverage-gated slices; exact-head Checks plus review (GAP-018). | +| [#240](https://github.com/ContextualWisdomLab/TEPP/pull/240) | Draft repair of #232 rustfmt and psychometric test-arity fallout | Convert from draft once the repair is complete; land before or with the next psychometric slice. | +| [#238](https://github.com/ContextualWisdomLab/TEPP/pull/238) | Draft coverage of `is_image_media_type_token` non-image prefix arm | Fold into #241 or land independently after exact-head Checks; do not let it stale behind #241. | +| [#233](https://github.com/ContextualWisdomLab/TEPP/pull/233) | Draft fold of span grounding into EventMention (#170 / GAP-007) | Keep as event-intelligence forward work; convert when #170 landing sequence resumes. | ## Delivery sequence -The dependency-aware product order is: +The dependency-aware product order is (✓ = landed on protected main): -1. **Consolidate delivery authority:** #175 and PR #164. -2. **Finish live result contracts:** #156/#157; the LineageWeave consumer parent #155 is implemented-main (loopback PR #107 and closed stacked #158/#159/#178 remain provenance). -3. **Build validated multilingual evidence:** #168, extending the first implemented-main span slice from #201. -4. **Build the CPU topic estimator:** #167. -5. **Build event intelligence and posterior networks:** #170 and #172. -6. **Build the posterior-aware longitudinal psychometric engine:** #169. +1. ✓ **Consolidate delivery authority:** #175 closed; PR #164 merged; queue drained through #239. +2. ✓ **Finish live result contracts:** #156/#157 merged; the LineageWeave consumer parent #155 is implemented-main. +3. ✓ **Build validated multilingual evidence (first slice):** #168 closed COMPLETED 2026-08-24 with span units implemented-main from #201; remaining alignment/invariance evidence tracks product completion under #166/#169. +4. **Build the CPU topic estimator:** #167 — reference estimator landed, full Bayesian/candidate-K fitting remains. +5. **Build event intelligence and posterior networks:** #170 and #172 — network estimator core plus #239 repairs landed; Leiden consensus, buyer workflow, and event-intelligence calibration remain (#233 queued). +6. **Build the posterior-aware longitudinal psychometric engine:** #169 — recovery stack landed via #231/#232; engine composition remains. 7. **Accelerate real kernels with parity:** #171. -8. **Complete the durable end-to-end run:** #166. +8. **Complete the durable end-to-end run:** #166 — terminal-result lifecycle and analysis-run execution engine are implemented-main; full E2E validation remains. 9. **Execute and validate interpretation:** #176. 10. **Design and implement the operator workspace:** #173. -11. **Productionize and release:** #174. +11. **Productionize and release:** #174 — v0.2.0 version alignment landed (#239); tagged cut pending on #235. Stacking is appropriate where public contracts make dependencies explicit. Stacking is not a reason to leave multiple unexplained implementation authorities @@ -344,6 +331,7 @@ Refresh this file when any of the following changes materially: - the dependency/landing order; - a release, deprecation, replacement, Figma file, or standards/research basis. -Keep this file operator-oriented. Store the volatile per-PR classification in the -artifact required by issue #175, and link it here. Never rewrite an active-PR +Keep this file operator-oriented. The volatile per-PR classification lives in +this register's snapshot tables (issue #175 is closed; no separate artifact is +required). Never rewrite an active-PR capability as protected-main before merge and exact-head verification.