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
5 changes: 5 additions & 0 deletions .codegraph/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CodeGraph data files — local to each machine, not for committing.
# Ignore everything in .codegraph/ except this file itself, so transient
# files (the database, daemon.pid, sockets, logs) never show up in git.
*
!.gitignore
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` 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) |
| 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/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
2 changes: 1 addition & 1 deletion docs/adr/0011-standalone-modular-msa-boundary.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ADR 0011 — Standalone operation and modular CWL MSA boundary

**Decision status:** Accepted
**Implementation maturity:** partial — Rust crates are independently usable; naruon HTTP interchange and loopback live listener (`POST /v1/analysis-runs` and `/v1/exports`, fail-closed table-access, NIM/proxy headers, RFC 3339 cutoff, stream deadline) are on the active PR (not implemented-main); production TLS/`$PORT` and remaining persistence integrations remain accepted-target
**Implementation maturity:** partial — Rust crates are independently usable; naruon HTTP interchange and loopback live listener (`POST /v1/analysis-runs` and `/v1/exports`, fail-closed table-access, NIM/proxy headers, RFC 3339 cutoff, stream deadline) are on the active PR (not implemented-main); production TLS/`$PORT` and remaining persistence integrations remain accepted-target
**Date:** 2026-08-10
**Supersedes:** The broad cross-service ownership wording in ADR 0001. ADR 0001 remains authoritative for Rust-first numerical architecture.

Expand Down
Loading
Loading