Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang
- `prompt_source` identity gate: instruction and prompt boilerplate is not unique latent content and is not erased by a stopword list; `identity_recovery_rate` reports exact kind matches, with a contract test comparing correct recovery with an all-unique collapse on a mixed known-truth fixture (ADR 0004/0012).
- `location_membership` identity gate: geographic and market assignments are time-varying memberships, not permanent entity identity and not language channels; recovered location kinds match known truth at a higher computed rate than collapsing every assignment to entity identity (ADR 0003).
- `membership_target` identity gate: language, episode, template, department, and opportunity-pool memberships cannot collapse into the entity/project pair stored by migration `0006`; comparison-contract tests record recovered target kinds against an entity-collapse baseline (ADR 0003).
- `event_core` TDT link-detection contracts: undirected mention-pair hypotheses, fail-closed self-links, refusal to treat a detected link as an instance or state transition, and computed precision/recall plus RMSE against known-truth pairs.
- `event_core` first-story detection gate: first-story versus follow-up labels stay distinct from promoted instances, false-alarm and miss rates are computed from known truth, and calibrated detection scores recover the binary first-story target with lower RMSE than an always-first detector.
- `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.
Expand Down
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| 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) |
| 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) |
| 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) |
Expand Down
19 changes: 19 additions & 0 deletions crates/event_core/src/error.rs

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Pre-existing fused error-message tuple

The UnknownFirstStoryLabel entry is already fused with EventTrackIsNotEventInstance into a four-element tuple in the merge-base, the same defect as the newly introduced one. The test array was already structurally broken; fixing it in the same pass would restore both message assertions.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ pub enum EventError {
DetectionIsNotTransition,
/// A CHRONOS prediction was treated as an observed or promoted fact.
PredictionIsNotFact,
/// A TDT link detection was treated as an event instance.
EventLinkIsNotEventInstance,
/// A TDT link detection was treated as a state transition.
EventLinkIsNotStateTransition,
/// An unknown event-link label was supplied.
UnknownEventLinkLabel,
/// A first-story detection was treated as an event instance.
FirstStoryIsNotEventInstance,
/// An unknown first-story label name was supplied.
Expand Down Expand Up @@ -64,6 +70,9 @@ impl fmt::Display for EventError {
Self::UnknownEventRole => "unknown event role",
Self::DetectionIsNotTransition => "detection is not a state transition",
Self::PredictionIsNotFact => "prediction is not an observed fact",
Self::EventLinkIsNotEventInstance => "event link is not an event instance",
Self::EventLinkIsNotStateTransition => "event link is not a state transition",
Self::UnknownEventLinkLabel => "unknown event link label",
Self::FirstStoryIsNotEventInstance => "first-story detection is not an event instance",
Self::UnknownFirstStoryLabel => "unknown first-story label",
Self::EventTrackIsNotEventInstance => "event track is not an event instance",
Expand Down Expand Up @@ -127,6 +136,16 @@ mod tests {
"prediction is not an observed fact",
),
(
EventError::EventLinkIsNotEventInstance,
"event link is not an event instance",
),
(
EventError::EventLinkIsNotStateTransition,
"event link is not a state transition",
),
(
EventError::UnknownEventLinkLabel,
"unknown event link label",
Comment on lines +146 to +148

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Link-label error test fused with first-story case

The added UnknownEventLinkLabel test entry never closes its tuple, so it absorbs the following FirstStoryIsNotEventInstance entry into one four-element tuple. The array now mixes tuple shapes and the message checks for both error variants no longer run.

Suggested change
(
EventError::UnknownEventLinkLabel,
"unknown event link label",
(
EventError::UnknownEventLinkLabel,
"unknown event link label",
),
(
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

EventError::FirstStoryIsNotEventInstance,
"first-story detection is not an event instance",
),
Expand Down
15 changes: 15 additions & 0 deletions crates/event_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod first_story;
mod identifier;
mod instance;
mod intelligence;
mod link;
mod mention;
mod prediction;
mod registry;
Expand Down Expand Up @@ -66,6 +67,20 @@ pub use intelligence::admit_state_transition;
pub use intelligence::classify_tdt_story;
/// Score first-story detections against a known stream.
pub use intelligence::first_story_detection_rates;
/// TDT same-event versus distinct-event link label.
pub use link::EventLinkLabel;
/// Undirected TDT link hypothesis between two mentions.
pub use link::EventLinkPair;
/// Threshold a link probability into a detection label.
pub use link::decide_event_link;
/// Precision of recovered TDT links against known-truth pairs.
pub use link::event_link_precision;
/// Recall of recovered TDT links against known-truth pairs.
pub use link::event_link_recall;
/// Explicit refusal to treat a TDT link as an event instance.
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.
pub use mention::EventMention;
/// One CHRONOS occurrence forecast that remains hypothetical.
Expand Down
223 changes: 223 additions & 0 deletions crates/event_core/src/link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
//! TDT link-detection scores stay distinct from instances and transitions.

use crate::{EventConfidence, EventError, EventInstanceId, EventMentionId};
use std::collections::BTreeSet;

/// TDT same-event versus distinct-event link label.
///
/// A link decision is detection evidence. It is never a promoted event instance
/// and cannot create a forward state transition by itself.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EventLinkLabel {
/// The mention pair is scored as the same event or story.
Linked,
/// The mention pair is scored as distinct events.
Unlinked,
}

impl EventLinkLabel {
/// Return the stable wire label name.
#[must_use]
pub const fn wire_name(self) -> &'static str {
match self {
Self::Linked => "linked",
Self::Unlinked => "unlinked",
}
}

/// Parse a stable wire link label.
///
/// # Errors
///
/// Returns [`EventError::UnknownEventLinkLabel`] for unrecognized names.
pub fn from_wire_name(name: &str) -> Result<Self, EventError> {
match name {
"linked" => Ok(Self::Linked),
"unlinked" => Ok(Self::Unlinked),
_ => Err(EventError::UnknownEventLinkLabel),
}
}

/// Return whether this label is a positive link detection.
#[must_use]
pub const fn is_linked(self) -> bool {
matches!(self, Self::Linked)
}

/// Return the binary probability target used for RMSE.
///
/// Linked truth is `1.0`; unlinked truth is `0.0`.
#[must_use]
pub const fn as_probability_target(self) -> f64 {
match self {
Self::Linked => 1.0,
Self::Unlinked => 0.0,
}
}
}

/// An undirected TDT link hypothesis between two mentions.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct EventLinkPair {
left: EventMentionId,
right: EventMentionId,
}

impl EventLinkPair {
/// Construct a normalized undirected mention pair.
///
/// # Errors
///
/// Returns [`EventError::InvalidWirePayload`] when both mentions are the
/// same identity. A mention cannot link to itself.
pub fn new(left: EventMentionId, right: EventMentionId) -> Result<Self, EventError> {
if left == right {
return Err(EventError::InvalidWirePayload);
}
if left <= right {
Ok(Self { left, right })
} else {
Ok(Self {
left: right,
right: left,
})
}
}

/// Return the lexicographically smaller mention identifier.
#[must_use]
pub const fn left(self) -> EventMentionId {
self.left
}

/// Return the lexicographically larger mention identifier.
#[must_use]
pub const fn right(self) -> EventMentionId {
self.right
}
}

/// Threshold a link probability into a detection label.
///
/// The threshold is inclusive: `probability >= threshold` is linked.
#[must_use]
pub fn decide_event_link(
probability: EventConfidence,
threshold: EventConfidence,
) -> EventLinkLabel {
if probability.value() >= threshold.value() {
EventLinkLabel::Linked
} else {
EventLinkLabel::Unlinked
}
}

/// Explicit refusal to treat a TDT link as an event instance.
///
/// # Errors
///
/// Always returns [`EventError::EventLinkIsNotEventInstance`].
pub fn refuse_event_link_as_instance(_link: EventLinkPair) -> Result<EventInstanceId, EventError> {
Err(EventError::EventLinkIsNotEventInstance)
}

/// Explicit refusal to treat a TDT link as a state transition.
///
/// # Errors
///
/// Always returns [`EventError::EventLinkIsNotStateTransition`].
pub fn refuse_event_link_as_transition(_link: EventLinkPair) -> Result<(), EventError> {
Err(EventError::EventLinkIsNotStateTransition)
}

/// Precision of recovered TDT links against the known-truth pair set.
///
/// # Errors
///
/// Returns [`EventError::InvalidWirePayload`] when the recovered set is empty.
pub fn event_link_precision(
truth: &[EventLinkPair],
recovered: &[EventLinkPair],
) -> Result<f64, EventError> {
let truth_set: BTreeSet<_> = truth.iter().copied().collect();
let recovered_set: BTreeSet<_> = recovered.iter().copied().collect();
counted_rate(
recovered_set.intersection(&truth_set).count(),
recovered_set.len(),
)
}
Comment thread
seonghobae marked this conversation as resolved.

/// Recall of recovered TDT links against the known-truth pair set.
///
/// # Errors
///
/// Returns [`EventError::InvalidWirePayload`] when the truth set is empty.
pub fn event_link_recall(
truth: &[EventLinkPair],
recovered: &[EventLinkPair],
) -> Result<f64, EventError> {
let truth_set: BTreeSet<_> = truth.iter().copied().collect();
let recovered_set: BTreeSet<_> = recovered.iter().copied().collect();
counted_rate(
recovered_set.intersection(&truth_set).count(),
truth_set.len(),
)
Comment thread
seonghobae marked this conversation as resolved.
}

fn counted_rate(numerator: usize, denominator: usize) -> Result<f64, EventError> {
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))
}
Comment thread
seonghobae marked this conversation as resolved.
Comment on lines +167 to +174

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Empty-set precision/recall fails closed by design

event_link_precision errors on empty recovered set and event_link_recall errors on empty truth set, via the denominator==0 guard in counted_rate. This is a deliberate fail-closed choice matching the doctoring note and tests, not a defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


#[cfg(test)]
mod tests {
use super::{
EventLinkLabel, EventLinkPair, counted_rate, decide_event_link, event_link_precision,
event_link_recall, refuse_event_link_as_instance, refuse_event_link_as_transition,
};
use crate::{EventConfidence, EventError, EventMentionId};

#[test]
fn link_helpers_cover_local_branches() {
let left = EventMentionId::new();
let right = EventMentionId::new();
let link = EventLinkPair::new(left, right).expect("pair");
assert_eq!(
refuse_event_link_as_instance(link),
Err(EventError::EventLinkIsNotEventInstance)
);
assert_eq!(
refuse_event_link_as_transition(link),
Err(EventError::EventLinkIsNotStateTransition)
);
let high = EventConfidence::new(0.8).expect("high");
let low = EventConfidence::new(0.2).expect("low");
assert_eq!(decide_event_link(high, low), EventLinkLabel::Linked);
assert_eq!(decide_event_link(low, high), EventLinkLabel::Unlinked);
let truth = [link];
let recovered = [link];
assert!((event_link_precision(&truth, &recovered).expect("p") - 1.0).abs() < f64::EPSILON);
assert!((event_link_recall(&truth, &recovered).expect("r") - 1.0).abs() < f64::EPSILON);
assert_eq!(
EventLinkPair::new(left, left),
Err(EventError::InvalidWirePayload)
);
assert_eq!(
event_link_precision(&truth, &[]),
Err(EventError::InvalidWirePayload)
);
assert_eq!(
event_link_recall(&[], &recovered),
Err(EventError::InvalidWirePayload)
);
assert_eq!(
counted_rate(0, usize::MAX),
Err(EventError::InvalidWirePayload)
);
assert_eq!(counted_rate(1, 0), Err(EventError::InvalidWirePayload));
}
}
Loading
Loading