Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3fced58
feat(event): refuse TDT/CHRONOS outputs as state transitions
seonghobae Aug 13, 2026
31ca187
test(event): add independent known-identity baseline contract
seonghobae Aug 14, 2026
5e0c6e6
test(event): avoid test-only public constructor
seonghobae Aug 14, 2026
786436a
fix(event): narrow first-story logic to a known-identity baseline
seonghobae Aug 14, 2026
90b1af1
ci(event): verify PR 50 known-identity baseline repair
seonghobae Aug 14, 2026
b5ae04f
ci(event): activate PR 50 repair through registered workflow
seonghobae Aug 14, 2026
a5e7609
ci(event): allow ready-for-review repair trigger
seonghobae Aug 14, 2026
955e5da
ci(event): preserve protected-main traceability during repair
seonghobae Aug 14, 2026
b9de41a
merge(main): reconcile event-intelligence status gates
seonghobae Aug 14, 2026
b8b2567
fix(event): align known-identity repair preconditions
seonghobae Aug 15, 2026
dc78049
fix(event): restore ADR repair precondition
seonghobae Aug 15, 2026
b4e0646
chore(ci): remove completed event repair loop
seonghobae Aug 20, 2026
9e15e9e
Merge current main into event intelligence gates
seonghobae Aug 20, 2026
af67f14
docs(adr): align event intelligence maturity
seonghobae Aug 20, 2026
fa4dff2
docs: align provider payload maturity evidence
seonghobae Aug 20, 2026
2a29e24
test(event): cover every intelligence branch
seonghobae Aug 20, 2026
1b12210
docs: align event intelligence maturity evidence
seonghobae Aug 20, 2026
a189b97
docs: fix temporal ledger table shape
seonghobae Aug 20, 2026
8710e24
fix(event): enforce evidence layer at promotion boundary
seonghobae Aug 21, 2026
314a6db
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 21, 2026
8f26c2f
test: cover event evidence layer accessor
seonghobae Aug 21, 2026
d4cd083
docs(adr): record provider-payload minimization as implemented-main
seonghobae Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
14 changes: 14 additions & 0 deletions crates/event_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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);
}
Expand Down
16 changes: 15 additions & 1 deletion crates/event_core/src/instance.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -14,6 +17,7 @@ pub struct EventInstance {
supporting_mentions: Vec<EventMentionId>,
event_time: TemporalInterval<EventTime>,
confidence: EventConfidence,
evidence_layer: EventEvidenceLayer,
roles: Vec<(EventRoleKind, String)>,
}

Expand All @@ -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<EventMentionId>,
valid_from: EventTime,
valid_to: EventTime,
confidence: EventConfidence,
evidence_layer: EventEvidenceLayer,
) -> Result<Self, EventError> {
admit_state_transition(evidence_layer)?;
Comment thread
seonghobae marked this conversation as resolved.
if supporting_mentions.is_empty() {
return Err(EventError::InvalidWirePayload);
}
Expand All @@ -44,6 +51,7 @@ impl EventInstance {
supporting_mentions,
event_time,
confidence,
evidence_layer,
roles: Vec::new(),
})
}
Expand Down Expand Up @@ -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
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

/// Attach a typed role argument.
pub fn assign_role(&mut self, role: EventRoleKind, argument: impl Into<String>) {
self.roles.push((role, argument.into()));
Expand Down
222 changes: 222 additions & 0 deletions crates/event_core/src/intelligence.rs
Original file line number Diff line number Diff line change
@@ -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)
}
Comment thread
seonghobae marked this conversation as resolved.
}

/// 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<S: BuildHasher>(
seen_story_ids: &HashSet<u64, S>,
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
}
}
Comment thread
seonghobae marked this conversation as resolved.
}
}

/// 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<FirstStoryRates, EventError> {
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"
);
}
}
17 changes: 16 additions & 1 deletion crates/event_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down
Loading
Loading