diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c2e8dd..2f8fbf34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ### Added +- `event_core` now requires and retains `EventEvidenceLayer::PromotedTransition` when constructing an `EventInstance`; every other layer is rejected at the promotion boundary, and TDT story classification uses a caller-owned hash set for expected constant-time membership checks. +- `event_core` ADR 0016 evidence-status gates: TDT detections and CHRONOS predictions cannot admit a forward state transition; first-story detection scores miss/false-alarm rates against a known story stream (Allan 2002 task). - `tepp_api` naruon live loopback HTTP/1.1 listener: `serve_one` installs a read/write deadline, requires a loopback `Host`, refuses `Transfer-Encoding` and NIM/proxy credential headers, parses `knowledge_cutoff` as RFC 3339 and refuses a future cutoff, keys analysis-run idempotency by tenant plus key, and proves both analysis-run and export POSTs over a real `TcpStream`. Not a production TLS/`$PORT` service (ADR 0011). - `tepp_api` adaptive orchestration router (ADR 0010): versioned `direct`/`verify`/`committee`/`conductor`/`abstain` selection from CPU `f64` risk, ambiguity, evidence, and token-budget inputs; recorded stages, recursion, decomposition, access lists, and role-specific reasoning effort; fail-closed document-controlled policy/access/credentials; LLM plans remain proposals under deterministic statistical authority; comparable-budget ablation requires a direct baseline; credential-free contextual-orchestrator binding. Live NIM HTTP remains accepted-target. - `tepp_api` purpose-bound provider-payload minimization: time-bounded `PurposeGrant` evaluation, fail-closed expired/not-yet-valid/inverted/cross-tenant/impossible-calendar denial, semantic UTC calendar validation, refusal to copy identity mappings into model-provider payloads or ordinary logs, preservation of opaque analytical identifiers and membership roles (no blanket PII mask), a separately authorized scientific re-identification path, and an internally bound FIPS 180-4 SHA-256 audit digest appended through `ReidentificationAuditSink` before disclosure. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 3f094947..d16a2050 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -33,6 +33,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Hourly NIM product-development operations | [`docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md`](docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md) | | 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) | +| Event-intelligence status-gate doctoring | [`docs/research/event-intelligence-status-gates.md`](docs/research/event-intelligence-status-gates.md) | | Retention/deletion/legal-hold doctoring | [`docs/research/retention-deletion-legal-hold.md`](docs/research/retention-deletion-legal-hold.md) | | Provider-payload minimization doctoring | [`docs/research/provider-payload-minimization.md`](docs/research/provider-payload-minimization.md) | | Adaptive orchestration router doctoring | [`docs/research/adaptive-orchestration-router.md`](docs/research/adaptive-orchestration-router.md) | diff --git a/crates/event_core/src/error.rs b/crates/event_core/src/error.rs index 6c795fef..f1ce2533 100644 --- a/crates/event_core/src/error.rs +++ b/crates/event_core/src/error.rs @@ -20,6 +20,10 @@ pub enum EventError { UnsupportedWireVersion, /// An unknown event-role name was supplied. UnknownEventRole, + /// A TDT detection or mention was treated as a state transition. + DetectionIsNotTransition, + /// A CHRONOS prediction was treated as an observed or promoted fact. + PredictionIsNotFact, } impl fmt::Display for EventError { @@ -32,6 +36,8 @@ impl fmt::Display for EventError { Self::InvalidWirePayload => "invalid event wire payload", Self::UnsupportedWireVersion => "unsupported event wire version", Self::UnknownEventRole => "unknown event role", + Self::DetectionIsNotTransition => "detection is not a state transition", + Self::PredictionIsNotFact => "prediction is not an observed fact", }; formatter.write_str(message) } @@ -65,6 +71,14 @@ mod tests { "unsupported event wire version", ), (EventError::UnknownEventRole, "unknown event role"), + ( + EventError::DetectionIsNotTransition, + "detection is not a state transition", + ), + ( + EventError::PredictionIsNotFact, + "prediction is not an observed fact", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/event_core/src/instance.rs b/crates/event_core/src/instance.rs index 98121632..4952cd74 100644 --- a/crates/event_core/src/instance.rs +++ b/crates/event_core/src/instance.rs @@ -1,6 +1,9 @@ //! Versioned event instances distinct from mentions. -use crate::{EventConfidence, EventError, EventInstanceId, EventMentionId, EventRoleKind}; +use crate::{ + EventConfidence, EventError, EventEvidenceLayer, EventInstanceId, EventMentionId, + EventRoleKind, admit_state_transition, +}; use temporal_core::{EventTime, TemporalBoundary, TemporalInterval, TemporalPrecision}; /// A versioned event instance with event-time support and typed roles. @@ -14,6 +17,7 @@ pub struct EventInstance { supporting_mentions: Vec, event_time: TemporalInterval, confidence: EventConfidence, + evidence_layer: EventEvidenceLayer, roles: Vec<(EventRoleKind, String)>, } @@ -24,12 +28,15 @@ impl EventInstance { /// /// Returns confidence or validity errors when inputs fail validation. /// At least one supporting mention is required. + /// The evidence layer must be [`EventEvidenceLayer::PromotedTransition`]. pub fn promote_from_mentions( supporting_mentions: Vec, valid_from: EventTime, valid_to: EventTime, confidence: EventConfidence, + evidence_layer: EventEvidenceLayer, ) -> Result { + admit_state_transition(evidence_layer)?; if supporting_mentions.is_empty() { return Err(EventError::InvalidWirePayload); } @@ -44,6 +51,7 @@ impl EventInstance { supporting_mentions, event_time, confidence, + evidence_layer, roles: Vec::new(), }) } @@ -72,6 +80,12 @@ impl EventInstance { self.confidence } + /// Return the independently promoted evidence layer retained by the instance. + #[must_use] + pub const fn evidence_layer(&self) -> EventEvidenceLayer { + self.evidence_layer + } + /// Attach a typed role argument. pub fn assign_role(&mut self, role: EventRoleKind, argument: impl Into) { self.roles.push((role, argument.into())); diff --git a/crates/event_core/src/intelligence.rs b/crates/event_core/src/intelligence.rs new file mode 100644 index 00000000..f0c24fe2 --- /dev/null +++ b/crates/event_core/src/intelligence.rs @@ -0,0 +1,222 @@ +//! Evidence-status gates for TDT detection and CHRONOS prediction. + +use crate::EventError; +use std::{collections::HashSet, hash::BuildHasher}; + +/// Epistemic layer of an event-intelligence output. +/// +/// Only [`EventEvidenceLayer::PromotedTransition`] may enter the forward +/// state/input-process-outcome graph. TDT detections and CHRONOS predictions +/// remain measurement or hypothesis artifacts. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum EventEvidenceLayer { + /// Fallible textual mention grounded in evidence. + ObservedMention, + /// TDT-style detection, link, or track output. + TdtDetection, + /// CHRONOS-style schema completion or predicted event. + ChronosPrediction, + /// Symbolic temporal-consistency judgment. + TemporalConsistency, + /// Independently promoted forward state transition. + PromotedTransition, +} + +impl EventEvidenceLayer { + /// Stable wire name for this layer. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::ObservedMention => "observed_mention", + Self::TdtDetection => "tdt_detection", + Self::ChronosPrediction => "chronos_prediction", + Self::TemporalConsistency => "temporal_consistency", + Self::PromotedTransition => "promoted_transition", + } + } + + /// Whether this layer may admit a forward state-transition edge. + #[must_use] + pub const fn may_admit_state_transition(self) -> bool { + matches!(self, Self::PromotedTransition) + } +} + +/// Admit a layer into the forward state graph or fail closed. +/// +/// # Errors +/// +/// Returns [`EventError::PredictionIsNotFact`] for CHRONOS predictions and +/// [`EventError::DetectionIsNotTransition`] for every other non-promoted layer. +pub fn admit_state_transition(layer: EventEvidenceLayer) -> Result<(), EventError> { + if layer.may_admit_state_transition() { + Ok(()) + } else if matches!(layer, EventEvidenceLayer::ChronosPrediction) { + Err(EventError::PredictionIsNotFact) + } else { + Err(EventError::DetectionIsNotTransition) + } +} + +/// First-story versus subsequent-track decision for one candidate story. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TdtStoryDecision { + /// The story identity has not been seen in the stream. + FirstStory, + /// The story identity continues a previously seen event. + Track, +} + +/// Classify one candidate against previously seen story identities. +#[must_use] +pub fn classify_tdt_story( + seen_story_ids: &HashSet, + candidate_story_id: u64, +) -> TdtStoryDecision { + if seen_story_ids.contains(&candidate_story_id) { + TdtStoryDecision::Track + } else { + TdtStoryDecision::FirstStory + } +} + +/// Known-truth first-story detection counts. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct FirstStoryRates { + hits: usize, + misses: usize, + false_alarms: usize, + first_story_truth: usize, + continuation_truth: usize, +} + +impl FirstStoryRates { + /// Correct first-story detections. + #[must_use] + pub const fn hits(self) -> usize { + self.hits + } + + /// Missed first stories. + #[must_use] + pub const fn misses(self) -> usize { + self.misses + } + + /// Continuations labeled as first stories. + #[must_use] + pub const fn false_alarms(self) -> usize { + self.false_alarms + } + + /// Miss rate among true first stories. + #[must_use] + pub fn miss_rate(self) -> f64 { + if self.first_story_truth == 0 { + 0.0 + } else { + #[allow(clippy::cast_precision_loss)] + { + self.misses as f64 / self.first_story_truth as f64 + } + } + } + + /// False-alarm rate among true continuations. + #[must_use] + pub fn false_alarm_rate(self) -> f64 { + if self.continuation_truth == 0 { + 0.0 + } else { + #[allow(clippy::cast_precision_loss)] + { + self.false_alarms as f64 / self.continuation_truth as f64 + } + } + } +} + +/// Score a first-story detector against a known binary stream. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when the streams are empty or +/// have unequal length. +pub fn first_story_detection_rates( + truth_is_first: &[bool], + predicted_is_first: &[bool], +) -> Result { + if truth_is_first.is_empty() || truth_is_first.len() != predicted_is_first.len() { + return Err(EventError::InvalidWirePayload); + } + let mut hits = 0; + let mut misses = 0; + let mut false_alarms = 0; + let mut first_story_truth = 0; + let mut continuation_truth = 0; + for (&truth, &predicted) in truth_is_first.iter().zip(predicted_is_first) { + if truth { + first_story_truth += 1; + if predicted { + hits += 1; + } else { + misses += 1; + } + } else { + continuation_truth += 1; + if predicted { + false_alarms += 1; + } + } + } + Ok(FirstStoryRates { + hits, + misses, + false_alarms, + first_story_truth, + continuation_truth, + }) +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::{ + EventEvidenceLayer, TdtStoryDecision, classify_tdt_story, first_story_detection_rates, + }; + + #[test] + fn zero_denominator_rates_are_zero_and_track_is_not_first() { + assert_eq!( + classify_tdt_story(&HashSet::from([7]), 7), + TdtStoryDecision::Track + ); + assert_eq!( + classify_tdt_story(&HashSet::new(), 8), + TdtStoryDecision::FirstStory + ); + assert!(first_story_detection_rates(&[], &[]).is_err()); + assert!(first_story_detection_rates(&[true], &[true, false]).is_err()); + let no_first_story = std::hint::black_box( + first_story_detection_rates(&[false], &[false]).expect("no first"), + ); + assert!(no_first_story.miss_rate() < 1e-15); + let no_continuation = + std::hint::black_box(first_story_detection_rates(&[true], &[true]).expect("no track")); + assert!(no_continuation.false_alarm_rate() < 1e-15); + let all_first = first_story_detection_rates(&[true, true], &[true, false]).expect("all"); + assert!((all_first.miss_rate() - 0.5).abs() < 1e-15); + assert!(all_first.false_alarm_rate() < 1e-15); + let continuations = + first_story_detection_rates(&[false, false], &[true, false]).expect("continuations"); + assert_eq!(continuations.false_alarms(), 1); + assert_eq!(continuations.misses(), 0); + assert!(continuations.miss_rate() < 1e-15); + assert!((continuations.false_alarm_rate() - 0.5).abs() < 1e-15); + assert_eq!( + EventEvidenceLayer::TdtDetection.wire_name(), + "tdt_detection" + ); + } +} diff --git a/crates/event_core/src/lib.rs b/crates/event_core/src/lib.rs index 25fd1022..9a1715b1 100644 --- a/crates/event_core/src/lib.rs +++ b/crates/event_core/src/lib.rs @@ -4,12 +4,15 @@ //! //! TEPP separates **fallible event mentions** grounded in evidence from //! **versioned event instances** used for temporal state, multilevel membership, -//! and scientific estimation. Mentions never silently become instances. +//! and scientific estimation. Mentions never silently become instances. TDT +//! detections and CHRONOS predictions remain measurement or hypothesis +//! artifacts until independently promoted. mod confidence; mod error; mod identifier; mod instance; +mod intelligence; mod mention; mod registry; mod role; @@ -28,6 +31,18 @@ pub use instance::EVENT_INSTANCE_WIRE_SCHEMA_VERSION; pub use instance::EventInstance; /// Explicit refusal to cast a mention as an instance. pub use instance::refuse_mention_as_instance; +/// Epistemic layer of an event-intelligence output. +pub use intelligence::EventEvidenceLayer; +/// Known-truth first-story detection counts. +pub use intelligence::FirstStoryRates; +/// First-story versus subsequent-track decision. +pub use intelligence::TdtStoryDecision; +/// Admit only promoted transitions into the forward state graph. +pub use intelligence::admit_state_transition; +/// Classify a candidate story as first-story or track. +pub use intelligence::classify_tdt_story; +/// Score first-story detections against a known stream. +pub use intelligence::first_story_detection_rates; /// Fallible textual event mention. pub use mention::EventMention; /// In-memory registry separating mentions from instances. diff --git a/crates/event_core/tests/intelligence_status_contract.rs b/crates/event_core/tests/intelligence_status_contract.rs new file mode 100644 index 00000000..258a2a26 --- /dev/null +++ b/crates/event_core/tests/intelligence_status_contract.rs @@ -0,0 +1,80 @@ +//! TDT/CHRONOS outputs cannot become transitions or historical facts. + +use event_core::{ + EventError, EventEvidenceLayer, TdtStoryDecision, admit_state_transition, classify_tdt_story, + first_story_detection_rates, +}; +use std::collections::HashSet; + +#[test] +fn only_promoted_transitions_enter_the_state_graph() { + admit_state_transition(EventEvidenceLayer::PromotedTransition).expect("promoted"); + assert!(EventEvidenceLayer::PromotedTransition.may_admit_state_transition()); + + assert_eq!( + admit_state_transition(EventEvidenceLayer::TdtDetection), + Err(EventError::DetectionIsNotTransition) + ); + assert_eq!( + admit_state_transition(EventEvidenceLayer::ObservedMention), + Err(EventError::DetectionIsNotTransition) + ); + assert_eq!( + admit_state_transition(EventEvidenceLayer::ChronosPrediction), + Err(EventError::PredictionIsNotFact) + ); + assert_eq!( + admit_state_transition(EventEvidenceLayer::TemporalConsistency), + Err(EventError::DetectionIsNotTransition) + ); + + for layer in [ + EventEvidenceLayer::ObservedMention, + EventEvidenceLayer::TdtDetection, + EventEvidenceLayer::ChronosPrediction, + EventEvidenceLayer::TemporalConsistency, + EventEvidenceLayer::PromotedTransition, + ] { + assert!(!layer.wire_name().is_empty()); + } +} + +#[test] +fn first_story_detector_recovers_known_stream_with_computed_rates() { + // Appearance order of news stories: first occurrence is a first story. + let stream = [10_u64, 20, 10, 30, 20]; + let mut seen = HashSet::new(); + let mut predicted = Vec::new(); + let mut truth = Vec::new(); + for story in stream { + let decision = classify_tdt_story(&seen, story); + predicted.push(matches!(decision, TdtStoryDecision::FirstStory)); + truth.push(!seen.contains(&story)); + seen.insert(story); + } + let rates = first_story_detection_rates(&truth, &predicted).expect("rates"); + assert_eq!(rates.hits(), 3); + assert_eq!(rates.misses(), 0); + assert_eq!(rates.false_alarms(), 0); + assert!(rates.miss_rate() < 1e-15); + assert!(rates.false_alarm_rate() < 1e-15); + + let always_first = [true, true, true, true, true]; + let noisy = first_story_detection_rates(&truth, &always_first).expect("noisy"); + assert_eq!(noisy.false_alarms(), 2); + let expected_fa = 2.0 / 2.0; + assert!((noisy.false_alarm_rate() - expected_fa).abs() < 1e-15); + assert_eq!(noisy.misses(), 0); +} + +#[test] +fn first_story_rates_fail_closed_on_empty_or_mismatched_streams() { + assert_eq!( + first_story_detection_rates(&[], &[]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + first_story_detection_rates(&[true], &[true, false]), + Err(EventError::InvalidWirePayload) + ); +} diff --git a/crates/event_core/tests/mention_instance_contract.rs b/crates/event_core/tests/mention_instance_contract.rs index c5e92396..6aa48fe3 100644 --- a/crates/event_core/tests/mention_instance_contract.rs +++ b/crates/event_core/tests/mention_instance_contract.rs @@ -1,8 +1,8 @@ //! Realistic contracts: mentions are not instances; promotion is explicit. use event_core::{ - EventConfidence, EventError, EventInstance, EventMention, EventRegistry, EventRoleKind, - refuse_mention_as_instance, + EventConfidence, EventError, EventEvidenceLayer, EventInstance, EventMention, EventRegistry, + EventRoleKind, refuse_mention_as_instance, }; use evidence_core::{DocumentRecord, EvidenceId, SourceArtifact}; use temporal_core::EventTime; @@ -50,6 +50,7 @@ fn registry_requires_supporting_mentions_before_instance_insert() { start, end, EventConfidence::certain().expect("certain"), + EventEvidenceLayer::PromotedTransition, ) .expect("instance"); assert_eq!( @@ -65,6 +66,7 @@ fn registry_requires_supporting_mentions_before_instance_insert() { start, end, EventConfidence::certain().expect("certain"), + EventEvidenceLayer::PromotedTransition, ) .expect("instance"); instance.assign_role(EventRoleKind::Product, "contract award"); @@ -78,6 +80,10 @@ fn registry_requires_supporting_mentions_before_instance_insert() { assert!(registry.mention(mention.mention_id()).is_some()); let stored = registry.instance(instance.instance_id()).expect("stored"); assert_eq!(stored.supporting_mentions(), &[mention.mention_id()]); + assert_eq!( + stored.evidence_layer(), + EventEvidenceLayer::PromotedTransition + ); assert_eq!(stored.roles().len(), 2); assert!(stored.is_active_at(start)); } @@ -96,7 +102,8 @@ fn empty_surface_and_empty_mention_sets_fail_closed() { Vec::new(), start, end, - EventConfidence::certain().expect("c") + EventConfidence::certain().expect("c"), + EventEvidenceLayer::PromotedTransition, ) .map(|_| ()), Err(EventError::InvalidWirePayload) @@ -124,6 +131,7 @@ fn accessors_and_duplicate_identity_paths_are_covered() { start, end, EventConfidence::new(0.9).expect("c"), + EventEvidenceLayer::PromotedTransition, ) .expect("instance"); assert!((instance.confidence().value() - 0.9).abs() < f64::EPSILON); @@ -146,3 +154,47 @@ fn accessors_and_duplicate_identity_paths_are_covered() { Err(EventError::DuplicateEventIdentity) ); } + +#[test] +fn promotion_rejects_every_non_promoted_evidence_layer() { + let evidence_id = document_evidence(); + let mention = EventMention::new( + evidence_id, + "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"); + + for (layer, expected) in [ + ( + EventEvidenceLayer::ObservedMention, + EventError::DetectionIsNotTransition, + ), + ( + EventEvidenceLayer::TdtDetection, + EventError::DetectionIsNotTransition, + ), + ( + EventEvidenceLayer::ChronosPrediction, + EventError::PredictionIsNotFact, + ), + ( + EventEvidenceLayer::TemporalConsistency, + EventError::DetectionIsNotTransition, + ), + ] { + assert_eq!( + EventInstance::promote_from_mentions( + vec![mention.mention_id()], + start, + end, + EventConfidence::certain().expect("confidence"), + layer, + ) + .map(|_| ()), + Err(expected) + ); + } +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index a3e674cf..d4a5397c 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -1,23 +1,23 @@ # TEPP Requirements, Research, and Evidence Traceability **Status:** Accepted cross-cutting traceability baseline -**Last reviewed:** 2026-08-13 +**Last reviewed:** 2026-08-20 The full APA 7th standards/literature register remains `docs/research/standards-and-literature.md`. This matrix links durable requirements to their owning decisions and implementation/evidence maturity without duplicating the bibliography. | Requirement / decision | Canonical basis | Source/evidence boundary | Maturity | |---|---|---|---| -| immutable source evidence and exact spans | PRD; Architecture; ADR 0008 | `evidence_core`, Task 2 tests/doctoring; `persistence_postgres` source-artifact SQL insert/lookup plus idempotent retry (#40 implemented-main) | implemented-main | +| immutable source evidence and exact spans | PRD; Architecture; ADR 0008 | `evidence_core`, Task 2 tests/doctoring | implemented-main | | Rust numerical authority / CPU `f64` reference | ADR 0001 | current workspace foundation; future estimators | partial | | Rust workspace/quality foundation | ADR 0007 | workspace/CI/repository contract | implemented-main | | six distinct clocks and uncertain intervals | PRD; ADR 0002 | PR #8 `temporal_core` on protected main; PR #5 historical only | implemented-main | | Allen relation algebra/bounded closure | ADR 0002; temporal research | PR #9 `temporal_core` path-consistency on protected main | implemented-main | | forward-only transition subgraph | PRD; ADR 0002/0003 | `relation_graph` on protected main | implemented-main | -| event ontology/evidence mentions | PRD; ADR 0003 | `event_core` mention/instance separation on protected main; `persistence_postgres` mention SQL implemented-main refuses mention-as-instance; event-instance SQL (#39 implemented-main) refuses inverted windows; full intelligence stack remaining | partial | +| event ontology/evidence mentions | PRD; ADR 0003 | `event_core` mention/instance separation on protected main; full intelligence stack remaining | partial | | time-varying cross-classified multiple membership | PRD; ADR 0003 | `membership_core` network on protected main; multilevel estimators remaining | partial | | leakage-safe availability/cutoff snapshots | PRD; ADR 0002/0013 | `corpus_split` on protected main | implemented-main | | recovery metrics (RMSE, bias, coverage, graph, temporal order, Monte Carlo SE gates) | PRD; Test Strategy; ADR 0007/0014 | `validation_core` on protected main (PR #19); SE-aware Monte Carlo gates included | implemented-main | -| PostgreSQL bitemporal/lineage persistence | ADR 0013; Architecture/ERD | `persistence_postgres` migration contracts, in-memory adapters, live SQL session/document SQL port, tenant RLS (`0002` + session GUC/role helpers), `DATABASE_URL` SQLx gate, optional `live-sqlx` `PgPool` driver, exact-head live PostgreSQL CI with isolation proof, append-only immutability triggers (`0004`), temporal interval ordering CHECKs (`0005`), typed membership assignment (`0006` implemented-main), event-relation/mention/instance SQL (#37–#39 implemented-main), source-artifact SQL (#40 implemented-main), audit-event SQL (#41 implemented-main), concurrent document-write stress (#43 implemented-main), backup/restore integrity revalidation (active PR); remaining physical ERD constraints | partial | +| PostgreSQL bitemporal/lineage persistence | ADR 0013; Architecture/ERD | `persistence_postgres` migration contracts, in-memory adapters, live SQL session/document SQL port, tenant RLS (`0002` + session GUC/role helpers), `DATABASE_URL` SQLx gate, optional `live-sqlx` `PgPool` driver, exact-head live PostgreSQL CI with isolation proof, append-only immutability triggers (`0004`), temporal interval ordering CHECKs (`0005`), typed membership assignment (`0006` implemented-main), event-relation/mention/instance SQL (`#37–#39` implemented-main), source-artifact SQL (`#40` implemented-main), audit-event SQL (`#41` implemented-main), concurrent document-write stress (`#43` implemented-main), backup/restore integrity revalidation (`#44` implemented-main); remaining physical ERD constraints | partial | | known-truth temporal/event simulation manifests | PRD; TRD; Test Strategy | `tepp_simulation` on protected main; recovery metrics in `validation_core` | implemented-main | | versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); HTTP service remaining accepted-target | partial | | immutable split/run/reproducibility manifests | ADR 0013; ERD | `tepp_api` reproducibility manifest contract on protected main; `persistence_postgres` append-only SQL insert/lookup for `reproducibility_manifest`, `corpus_split_manifest`, `model_run`, and `model_artifact` (migration `0003`); full physical ERD constraints remaining | partial | @@ -30,10 +30,10 @@ The full APA 7th standards/literature register remains `docs/research/standards- | compositional topic correlation / stable clustering | ADR 0005/0012; research | future `network_analysis` | accepted-target | | posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | future `psychometric_core` | accepted-target | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | -| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | future `event_intelligence` | accepted-target | +| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` evidence-layer admission and known-stream first-story rates on PR #50; full tracking/calibration and schema extraction remain future | active-PR | | evidence-bounded LLM interpretation | ADR 0010/0012; PRD | `tepp_api` router plus future `interpretation_gateway` | partial | -| 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 | -| purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | `tepp_api` export authorization plus provider-payload minimization / elevated re-identification implemented-main; persistence retention/deletion remaining | partial | +| 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; deployment/provider evidence remains accepted-target | partial | | 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 | | standalone + modular CWL MSA / no cross-service DB coupling | ADR 0011; `docs/API_CONTRACT.md` | current standalone crates; future service ports | partial | | naruon modular artifact consumer boundary | ADR 0011/0012; API contract | `docs/connectors/naruon-artifact-consumer.md` + PR #22 versioned consumer contract on protected main; `tepp_api` HTTP interchange (PR #42 implemented-main); loopback live listener on the active PR; production TLS remaining | partial | diff --git a/docs/adr/0009-purpose-bound-pii-governance.md b/docs/adr/0009-purpose-bound-pii-governance.md index 88ed1341..e06de6e1 100644 --- a/docs/adr/0009-purpose-bound-pii-governance.md +++ b/docs/adr/0009-purpose-bound-pii-governance.md @@ -1,7 +1,7 @@ # ADR 0009 — Purpose-bound PII governance without blanket masking **Decision status:** Accepted -**Implementation maturity:** partial — persistence retention/deletion/legal-hold (migration `0007`) is implemented-main; purpose-bound provider-payload minimization (expired-purpose denial, log/source separation, separately authorized re-identification) is on the active PR and is not implemented-main until exact-head checks, review, and protected-main integration complete; deployment/provider-region evidence remains accepted-target +**Implementation maturity:** partial — persistence retention/deletion/legal-hold (migration `0007`) and purpose-bound provider-payload minimization (expired-purpose denial, log/source separation, separately authorized re-identification) are implemented-main; deployment/provider-region evidence remains accepted-target **Date:** 2026-08-10 **Supersedes:** None. diff --git a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md index b85ee0b4..45731fdb 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:** accepted-target +**Implementation maturity:** active-PR — evidence-layer admission and first-story detection rates are implemented in `event_core` on the active PR; full TDT tracking/calibration and CHRONOS schema extraction remain accepted-target. **Date:** 2026-08-12 **Supersedes:** None; complements ADR 0002 temporal semantics and ADR 0003 event ontology/membership. diff --git a/docs/adr/README.md b/docs/adr/README.md index 258eb7f3..42c50b32 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -15,13 +15,13 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [0007](0007-rust-workspace-quality-gates.md) | Explicit Rust workspace, pinned toolchains, and exact quality gates | Accepted | implemented-main | ADR 0014 governs scientific/product claim promotion beyond repository-quality tooling. | | [0008](0008-immutable-evidence-identities-digests-and-spans.md) | Immutable evidence identities, `SHA-256` digests, exact spans, and strict wire reconstruction | Accepted | implemented-main | ADR 0013 governs future persistence/reproducibility/split authority. | | [0009](0009-purpose-bound-pii-governance.md) | Purpose-bound PII governance without blanket masking | Accepted | partial | Persistence retention/deletion/legal-hold (`0007`) and provider-payload minimization implemented-main; deployment evidence remains accepted-target. | -| [0010](0010-adaptive-llm-orchestration.md) | Adaptive LLM orchestration and test-time compute | Accepted | partial | `tepp_api` router/ablation/orchestrator binding on the active PR; live NIM execution and production ablation evidence remain accepted-target. | +| [0010](0010-adaptive-llm-orchestration.md) | Adaptive LLM orchestration and test-time compute | Accepted | partial | `tepp_api` router/ablation/orchestrator binding implemented-main; live NIM execution and production ablation evidence remain accepted-target. | | [0011](0011-standalone-modular-msa-boundary.md) | Standalone operation and modular CWL MSA boundary | Accepted | partial | Owns cross-service persistence/credential/API authority; no direct cross-service application-table coupling. | | [0012](0012-temporal-relational-shared-latent-topic-measurement.md) | Temporal Relational Shared-Latent Topic Measurement (TRSL-TM) | Accepted | accepted-target | Owns topic backend compatibility, global topic identity, method effects, K/model-selection prerequisites, and compositional topic coordinates. | | [0013](0013-bitemporal-persistence-reproducibility-and-split-authority.md) | Bitemporal persistence, reproducibility manifests, and relation-aware split authority | Accepted | partial | Owns PostgreSQL adapter semantics, immutable run/split manifests, leakage-safe partitions, and recovery identity; optional `live-sqlx` `PgPool`, live PG CI, tenant RLS, and `0006` membership implemented-main; `0007` retention/deletion/legal-hold on the active PR; remaining physical ERD/backup accepted-target. | | [0014](0014-scientific-claim-promotion-and-release-evidence.md) | Scientific claim promotion and release evidence authority | Accepted | partial | Separates design, implementation, scientific/product claim, and release authority; repository SBOM/provenance generator implemented, full release bundle remaining. | | [0015](0015-autonomous-development-review-and-merge-authority.md) | Autonomous development, review, and merge authority separation | Accepted | active-PR | Separates model proposal, deterministic verification, publication, independent review, and merge/release authority. | -| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | accepted-target | Separates observed evidence, detection/tracking, prediction/schema inference, temporal consistency, and promoted transition authority. | +| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | active-PR | Evidence-layer admission and first-story rates are on the active PR; full TDT tracking/calibration and CHRONOS schema extraction remain accepted-target. | ## Decision ownership summary diff --git a/docs/research/event-intelligence-status-gates.md b/docs/research/event-intelligence-status-gates.md new file mode 100644 index 00000000..dc4a24f6 --- /dev/null +++ b/docs/research/event-intelligence-status-gates.md @@ -0,0 +1,32 @@ +# Event-intelligence status gates + +## Scope + +This note doctors the first ADR 0016 production slice in `event_core`: + +1. every event-intelligence output carries an epistemic layer (`observed_mention`, `tdt_detection`, `chronos_prediction`, `temporal_consistency`, `promoted_transition`); +2. only an independently promoted transition may enter the forward state graph; +3. CHRONOS predictions are never treated as observed fact; +4. the concrete instance-promotion API retains the accepted layer and rejects every non-promoted layer; +5. a first-story detector is scored with miss and false-alarm rates against a known story stream. + +Full TDT tracking/calibration and CHRONOS schema extraction remain accepted-target. No database migration is allocated. + +## Authoritative sources + +Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based information organization*. Kluwer Academic Publishers. + +Anagnostopoulos, E., Batsakis, S., & Petrakis, E. G. M. (2013). CHRONOS: A reasoning engine for qualitative temporal information in OWL. *Procedia Computer Science, 22*, 70–77. https://doi.org/10.1016/j.procs.2013.09.082 + +## Application + +Allan (2002) defines first-story detection as a scored measurement task with miss and false-alarm rates, not as automatic promotion into a chronology. Anagnostopoulos et al. (2013) keep qualitative temporal reasoning distinct from asserted event identity. TEPP therefore refuses to admit TDT detections or CHRONOS predictions as state transitions and reports computed first-story rates on a known stream (Allan, 2002; Anagnostopoulos et al., 2013). + +## Verification + +- `admit_state_transition(PromotedTransition)` succeeds; +- `EventInstance::promote_from_mentions` retains `PromotedTransition` and rejects observed mentions, TDT detections, CHRONOS predictions, and temporal-consistency judgments; +- TDT/mention/consistency layers return `DetectionIsNotTransition`; +- CHRONOS predictions return `PredictionIsNotFact`; +- stream `[10,20,10,30,20]` recovers three first stories with miss rate 0 and false-alarm rate 0; +- an always-first detector yields a computed false-alarm rate of 1.0 on the two continuations. diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index aae1a06e..e9b32b84 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -1,7 +1,7 @@ # Temporal Event Foundation — validation and release-readiness report **Status:** Living validation ledger for the Temporal/Event foundation program -**Last reviewed:** 2026-08-12 +**Last reviewed:** 2026-08-20 **Authority:** ADR 0014 (claim promotion), ADR 0007 (quality gates), AGENTS.md scientific acceptance ## Scope @@ -18,13 +18,14 @@ This report tracks exact-head scientific and engineering evidence required befor | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | | Multiple membership | `membership_core` | partial | — | unit + ESS weights | Task 7 / PR #12 + #25 | | Forward transition DAG | `relation_graph` | implemented-main | — | unit + cycle rejection | Task 6 / PR #14 | -| Bitemporal persistence + live SQL port | `persistence_postgres` | partial | backup/restore integrity | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005`/`0006` + event relation/mention/instance + source-artifact + audit-event + concurrent-write (#37–#43 implemented-main) + restore integrity probes (active PR) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#43 + restore integrity | +| Bitemporal persistence + live SQL port | `persistence_postgres` | partial | — | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005`/`0006` interval and membership contracts + event relation/mention/instance + source-artifact + audit-event + concurrent-write + restore-integrity contracts implemented-main | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#44 | | Leakage-safe splits | `corpus_split` | implemented-main | — | cutoff + co-partition tests | Task 9 / PR #17 | | Truth corpora / manifests | `tepp_simulation` | implemented-main | — | deterministic generator tests | Task 10 / PR #18 | | Recovery metrics | `validation_core` | implemented-main | — | RMSE/bias/coverage/MC gates | Task 11 / PR #19 | | Versioned API/export contracts | `tepp_api` | implemented-main | naruon HTTP interchange | unknown-field/version/limit + naruon HTTPS interchange tests | Task 12 / PR #21; live HTTP service remaining | -| Purpose-bound provider payloads | `tepp_api` | implemented-main | provider-payload minimization | expired/not-yet-valid/inverted/cross-tenant/impossible-calendar grant, mapping refusal, audited elevated re-id replay | ADR 0009; `docs/research/provider-payload-minimization.md` | -| Adaptive orchestration router | `tepp_api` | accepted-target | active PR | mode selection, document-control denial, ablation, credential-free bind | ADR 0010; `docs/research/adaptive-orchestration-router.md` | +| TDT/CHRONOS evidence-status gates | `event_core` | active-PR | PR #50 | admission + first-story rates | known-stream miss/FA; full tracking/calibration/schema extraction remains future; ADR 0016; `docs/research/event-intelligence-status-gates.md` | +| Purpose-bound provider payloads | `tepp_api` | implemented-main | — | expired/not-yet-valid/inverted/cross-tenant/impossible-calendar grant, mapping refusal, audited elevated re-id replay | ADR 0009; `docs/research/provider-payload-minimization.md` | +| Adaptive orchestration router | `tepp_api` | partial | — | mode selection, document-control denial, ablation, credential-free bind; live NIM execution remains future | ADR 0010; `docs/research/adaptive-orchestration-router.md` | | CWL modular connectors | `docs/connectors/*` | implemented-main | — | contract docs + examples | PR #22; live HTTP ports remaining | | Release SBOM/provenance generator | `scripts/release_evidence.py` | partial | — | generate+validate in CI | Task 13 partial / PR #28 |