Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ boundaries above remain the target modular MSA architecture.
|---|---|
| `evidence_core` | immutable evidence domain primitives |
| `temporal_core` | typed clocks, intervals, and temporal reasoning |
| `event_core` | event instances, mentions, roles, and provenance |
| `event_core` | event instances, mentions, roles, provenance, and CHRONOS occurrence-prediction calibration |
| `relation_graph` | typed relations and forward-transition validation |
| `membership_core` | time-varying cross-classified multiple membership |
| `persistence_postgres` | PostgreSQL repositories and migrations |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

### Added

- `event_core` CHRONOS occurrence-prediction calibration: forecasts stay hypothetical, refuse promotion to event instances, and recover a computed Brier score against later-observed occurrence truth, with empty or mismatched streams failing closed.
- `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.
- `persistence_postgres` backup/restore integrity: restored snapshots stay unusable until tenant, canonical `SHA-256`, knowledge-cutoff eligibility, temporal window order, and append-only triggers revalidate; SQL probes raise `restore integrity failed` (ADR 0013).
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) |
| CHRONOS prediction-calibration doctoring | [`docs/research/chronos-prediction-calibration.md`](docs/research/chronos-prediction-calibration.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 CHRONOS occurrence prediction was treated as an event instance.
PredictionIsNotEventInstance,
/// An unknown occurrence-truth label was supplied.
UnknownOccurrenceTruth,
}

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::PredictionIsNotEventInstance => "CHRONOS prediction is not an event instance",
Self::UnknownOccurrenceTruth => "unknown occurrence truth label",
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -65,6 +71,14 @@ mod tests {
"unsupported event wire version",
),
(EventError::UnknownEventRole, "unknown event role"),
(
EventError::PredictionIsNotEventInstance,
"CHRONOS prediction is not an event instance",
),
(
EventError::UnknownOccurrenceTruth,
"unknown occurrence truth label",
),
] {
assert_eq!(error.to_string(), message);
}
Expand Down
14 changes: 13 additions & 1 deletion crates/event_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +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 and CHRONOS occurrence forecasts never
//! silently become instances.

mod confidence;
mod error;
mod identifier;
mod instance;
mod mention;
mod prediction;
mod registry;
mod role;

Expand All @@ -30,6 +32,16 @@ pub use instance::EventInstance;
pub use instance::refuse_mention_as_instance;
/// Fallible textual event mention.
pub use mention::EventMention;
/// One CHRONOS occurrence forecast that remains hypothetical.
pub use prediction::ChronosOccurrenceForecast;
/// Opaque CHRONOS occurrence-prediction identity.
pub use prediction::ChronosPredictionId;
/// Later-observed occurrence truth for a CHRONOS forecast.
pub use prediction::OccurrenceTruth;
/// Mean squared error of CHRONOS occurrence forecasts against later truth.
pub use prediction::chronos_prediction_brier_score;
/// Explicit refusal to treat a CHRONOS prediction as an event instance.
pub use prediction::refuse_prediction_as_instance;
/// In-memory registry separating mentions from instances.
pub use registry::EventRegistry;
/// Typed event role kind.
Expand Down
207 changes: 207 additions & 0 deletions crates/event_core/src/prediction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//! CHRONOS occurrence forecasts stay hypothetical until later evidence.

use crate::{EventConfidence, EventError, EventInstanceId};

/// Opaque CHRONOS occurrence-prediction identity.
///
/// A forecast is hypothesized future or schema-completion evidence. It is
/// never a promoted event instance.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ChronosPredictionId(u32);

impl ChronosPredictionId {
/// Reconstruct a prediction identity from a raw fixture or estimator label.
#[must_use]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}

/// Return the raw prediction label.
#[must_use]
pub const fn raw(self) -> u32 {
self.0
}
}

/// Later-observed occurrence truth for a CHRONOS forecast.
///
/// Truth is recovered from later evidence. It does not rewrite the forecast
/// into an event instance.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OccurrenceTruth {
/// Later evidence established that the predicted event occurred.
Occurred,
/// Later evidence established that the predicted event did not occur.
DidNotOccur,
}

impl OccurrenceTruth {
/// Return the stable wire label name.
#[must_use]
pub const fn wire_name(self) -> &'static str {
match self {
Self::Occurred => "occurred",
Self::DidNotOccur => "did_not_occur",
}
}

/// Parse a stable wire occurrence-truth label.
///
/// # Errors
///
/// Returns [`EventError::UnknownOccurrenceTruth`] for unrecognized names.
pub fn from_wire_name(name: &str) -> Result<Self, EventError> {
match name {
"occurred" => Ok(Self::Occurred),
"did_not_occur" => Ok(Self::DidNotOccur),
_ => Err(EventError::UnknownOccurrenceTruth),
}
}

/// Return whether later evidence established occurrence.
#[must_use]
pub const fn occurred(self) -> bool {
matches!(self, Self::Occurred)
}

/// Return the binary probability target used for Brier scoring.
///
/// Occurred truth is `1.0`; non-occurrence is `0.0`.
#[must_use]
pub const fn as_probability_target(self) -> f64 {
match self {
Self::Occurred => 1.0,
Self::DidNotOccur => 0.0,
}
}
}

/// One CHRONOS occurrence forecast that remains hypothetical.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ChronosOccurrenceForecast {
prediction_id: ChronosPredictionId,
probability: EventConfidence,
}

impl ChronosOccurrenceForecast {
/// Bind a prediction identity to an occurrence probability.
#[must_use]
pub const fn new(prediction_id: ChronosPredictionId, probability: EventConfidence) -> Self {
Self {
prediction_id,
probability,
}
}

/// Return the prediction identity.
#[must_use]
pub const fn prediction_id(self) -> ChronosPredictionId {
self.prediction_id
}

/// Return the hypothesized occurrence probability.
#[must_use]
pub const fn probability(self) -> EventConfidence {
self.probability
}
}

/// Explicit refusal to treat a CHRONOS occurrence prediction as an event instance.
///
/// # Errors
///
/// Always returns [`EventError::PredictionIsNotEventInstance`].
pub fn refuse_prediction_as_instance(
_prediction: ChronosPredictionId,
) -> Result<EventInstanceId, EventError> {
Err(EventError::PredictionIsNotEventInstance)
}

/// Mean squared error of CHRONOS occurrence probabilities against later truth.
///
/// # Errors
///
/// Returns [`EventError::InvalidWirePayload`] when the slices are empty or
/// have unequal length.
pub fn chronos_prediction_brier_score(
forecasts: &[ChronosOccurrenceForecast],
outcomes: &[OccurrenceTruth],
) -> Result<f64, EventError> {
if forecasts.is_empty() || forecasts.len() != outcomes.len() {
return Err(EventError::InvalidWirePayload);
}
let mut square_sum = 0.0_f64;
for (forecast, outcome) in forecasts.iter().zip(outcomes) {
let residual = forecast.probability().value() - outcome.as_probability_target();
square_sum += residual * residual;
}
mean_square(square_sum, forecasts.len())
}

fn mean_square(square_sum: f64, count: usize) -> Result<f64, EventError> {
let n = u32::try_from(count).map_err(|_| EventError::InvalidWirePayload)?;
if n == 0 {
return Err(EventError::InvalidWirePayload);
}
Ok(square_sum / f64::from(n))
}

#[cfg(test)]
mod tests {
use super::{
ChronosOccurrenceForecast, ChronosPredictionId, OccurrenceTruth,
chronos_prediction_brier_score, refuse_prediction_as_instance,
};
use crate::{EventConfidence, EventError};

#[test]
fn prediction_helpers_cover_local_branches() {
let prediction = ChronosPredictionId::from_raw(9);
assert_eq!(prediction.raw(), 9);
assert_eq!(
refuse_prediction_as_instance(prediction),
Err(EventError::PredictionIsNotEventInstance)
);
assert_eq!(OccurrenceTruth::Occurred.wire_name(), "occurred");
assert_eq!(OccurrenceTruth::DidNotOccur.wire_name(), "did_not_occur");
assert_eq!(
OccurrenceTruth::from_wire_name("occurred").expect("parse"),
OccurrenceTruth::Occurred
);
assert_eq!(
OccurrenceTruth::from_wire_name("did_not_occur").expect("parse"),
OccurrenceTruth::DidNotOccur
);
assert_eq!(
OccurrenceTruth::from_wire_name("maybe"),
Err(EventError::UnknownOccurrenceTruth)
);
assert!(OccurrenceTruth::Occurred.occurred());
assert!(!OccurrenceTruth::DidNotOccur.occurred());
assert!((OccurrenceTruth::Occurred.as_probability_target() - 1.0).abs() < f64::EPSILON);
assert!((OccurrenceTruth::DidNotOccur.as_probability_target() - 0.0).abs() < f64::EPSILON);

let forecast = ChronosOccurrenceForecast::new(
prediction,
EventConfidence::new(0.25).expect("probability"),
);
assert_eq!(forecast.prediction_id(), prediction);
assert!((forecast.probability().value() - 0.25).abs() < f64::EPSILON);
let miss = chronos_prediction_brier_score(&[forecast], &[OccurrenceTruth::Occurred])
.expect("miss");
assert!((miss - 0.5625).abs() < 1e-15);
assert_eq!(
chronos_prediction_brier_score(&[], &[OccurrenceTruth::Occurred]),
Err(EventError::InvalidWirePayload)
);
assert_eq!(
super::mean_square(0.0, 0),
Err(EventError::InvalidWirePayload)
);
assert_eq!(
super::mean_square(1.0, usize::MAX),
Err(EventError::InvalidWirePayload)
);
assert!((super::mean_square(1.0, 2).expect("half") - 0.5).abs() < f64::EPSILON);
}
}
59 changes: 59 additions & 0 deletions crates/event_core/tests/prediction_calibration_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! CHRONOS occurrence forecasts stay hypothetical and recover Brier scores.

use event_core::{
ChronosOccurrenceForecast, ChronosPredictionId, EventConfidence, EventError, OccurrenceTruth,
chronos_prediction_brier_score, refuse_prediction_as_instance,
};

fn forecast(raw: u32, probability: f64) -> ChronosOccurrenceForecast {
ChronosOccurrenceForecast::new(
ChronosPredictionId::from_raw(raw),
EventConfidence::new(probability).expect("probability"),
)
}

#[test]
fn chronos_prediction_cannot_be_cast_to_an_instance() {
let prediction = ChronosPredictionId::from_raw(7);
assert_eq!(
refuse_prediction_as_instance(prediction),
Err(EventError::PredictionIsNotEventInstance)
);
}

#[test]
fn perfect_occurrence_forecasts_recover_zero_brier() {
let forecasts = [forecast(1, 1.0), forecast(2, 0.0), forecast(3, 1.0)];
let outcomes = [
OccurrenceTruth::Occurred,
OccurrenceTruth::DidNotOccur,
OccurrenceTruth::Occurred,
];
let score = chronos_prediction_brier_score(&forecasts, &outcomes).expect("brier");
assert!(score.abs() < 1e-15, "perfect Brier {score}");
}

#[test]
fn calibrated_forecasts_beat_overconfident_always_occur_and_mismatches_fail_closed() {
let calibrated = [forecast(1, 0.8), forecast(2, 0.2), forecast(3, 0.7)];
let overconfident = [forecast(1, 1.0), forecast(2, 1.0), forecast(3, 1.0)];
let outcomes = [
OccurrenceTruth::Occurred,
OccurrenceTruth::DidNotOccur,
OccurrenceTruth::Occurred,
];
let calibrated_brier = chronos_prediction_brier_score(&calibrated, &outcomes).expect("cal");
let naive_brier = chronos_prediction_brier_score(&overconfident, &outcomes).expect("naive");
assert!(
calibrated_brier < naive_brier,
"calibrated Brier {calibrated_brier} must be below always-occur Brier {naive_brier}"
);
assert_eq!(
chronos_prediction_brier_score(&calibrated, &[OccurrenceTruth::Occurred]),
Err(EventError::InvalidWirePayload)
);
assert_eq!(
chronos_prediction_brier_score(&[], &[]),
Err(EventError::InvalidWirePayload)
);
}
4 changes: 2 additions & 2 deletions docs/LLM_ORCHESTRATION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TEPP LLM Orchestration and Test-Time Compute Contract

**Status:** Partial — `tepp_api::route_orchestration` is the governed selector; live provider execution is not yet shipped.
**Status:** Partial — `tepp_api::route_orchestration` is the governed selector; live provider execution is not yet shipped.
**Last reviewed:** 2026-08-13

## 1. Purpose
Expand Down Expand Up @@ -132,4 +132,4 @@ Before claiming an orchestration mode materially improves TEPP, compare at least
4. adaptive/learned-conductor-style workflow where available;
5. at least two reasoning-effort/budget settings.

Report uncertainty and failure modes, not only the best benchmark score.
Report uncertainty and failure modes, not only the best benchmark score.
4 changes: 2 additions & 2 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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.

Expand Down Expand Up @@ -30,7 +30,7 @@ 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` occurrence-prediction Brier calibration on the active PR; remaining detection/schema/temporal-consistency stack 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 |
Expand Down
Loading
Loading