diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c2e8dd..ed5485bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ### Added +- `event_core` CHRONOS schema-slot gate: predicted role fillers stay distinct from promoted instances and transitions, slot precision/recall are computed from known-truth fills, and production label/confidence APIs produce calibrated occupancy RMSE ≈ 0.1411 versus always-fill ≈ 0.7071 in the contract fixture. - `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/crates/event_core/src/error.rs b/crates/event_core/src/error.rs index 6c795fef..5139b444 100644 --- a/crates/event_core/src/error.rs +++ b/crates/event_core/src/error.rs @@ -20,6 +20,12 @@ pub enum EventError { UnsupportedWireVersion, /// An unknown event-role name was supplied. UnknownEventRole, + /// A CHRONOS schema prediction was treated as an event instance. + SchemaPredictionIsNotEventInstance, + /// A CHRONOS schema prediction was treated as a state transition. + SchemaPredictionIsNotStateTransition, + /// An unknown schema-slot occupancy label was supplied. + UnknownSchemaSlotLabel, } impl fmt::Display for EventError { @@ -32,6 +38,13 @@ impl fmt::Display for EventError { Self::InvalidWirePayload => "invalid event wire payload", Self::UnsupportedWireVersion => "unsupported event wire version", Self::UnknownEventRole => "unknown event role", + Self::SchemaPredictionIsNotEventInstance => { + "schema prediction is not an event instance" + } + Self::SchemaPredictionIsNotStateTransition => { + "schema prediction is not a state transition" + } + Self::UnknownSchemaSlotLabel => "unknown schema slot label", }; formatter.write_str(message) } @@ -65,6 +78,18 @@ mod tests { "unsupported event wire version", ), (EventError::UnknownEventRole, "unknown event role"), + ( + EventError::SchemaPredictionIsNotEventInstance, + "schema prediction is not an event instance", + ), + ( + EventError::SchemaPredictionIsNotStateTransition, + "schema prediction is not a state transition", + ), + ( + EventError::UnknownSchemaSlotLabel, + "unknown schema slot label", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/event_core/src/lib.rs b/crates/event_core/src/lib.rs index 25fd1022..a2b237e1 100644 --- a/crates/event_core/src/lib.rs +++ b/crates/event_core/src/lib.rs @@ -4,7 +4,8 @@ //! //! 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 schema-slot predictions +//! never silently become instances. mod confidence; mod error; @@ -13,6 +14,7 @@ mod instance; mod mention; mod registry; mod role; +mod schema; /// Finite confidence on the closed unit interval. pub use confidence::EventConfidence; @@ -34,3 +36,19 @@ pub use mention::EventMention; pub use registry::EventRegistry; /// Typed event role kind. pub use role::EventRoleKind; +/// Opaque CHRONOS schema-prediction identity. +pub use schema::SchemaPredictionId; +/// Predicted or observed filler for one schema slot. +pub use schema::SchemaSlotAssignment; +/// Filled-versus-empty occupancy label. +pub use schema::SchemaSlotLabel; +/// Threshold a slot-occupancy probability into a fill label. +pub use schema::decide_schema_slot; +/// Explicit refusal to treat a schema prediction as an instance. +pub use schema::refuse_schema_prediction_as_instance; +/// Explicit refusal to treat a schema prediction as a state transition. +pub use schema::refuse_schema_prediction_as_transition; +/// Precision of recovered filled slots against known truth. +pub use schema::schema_slot_precision; +/// Recall of recovered filled slots against known truth. +pub use schema::schema_slot_recall; diff --git a/crates/event_core/src/schema.rs b/crates/event_core/src/schema.rs new file mode 100644 index 00000000..1345ae84 --- /dev/null +++ b/crates/event_core/src/schema.rs @@ -0,0 +1,258 @@ +//! CHRONOS schema-slot predictions stay distinct from instances and transitions. + +use crate::{EventConfidence, EventError, EventInstanceId, EventRoleKind}; +use std::collections::BTreeSet; + +/// Opaque CHRONOS schema-prediction identity. +/// +/// A schema prediction is a hypothesized slot-fill. It is never a promoted +/// event instance and cannot create a forward state transition. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SchemaPredictionId(u32); + +impl SchemaPredictionId { + /// 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 + } +} + +/// CHRONOS filled-versus-empty occupancy for one schema slot. +/// +/// A fill decision is prediction 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 SchemaSlotLabel { + /// The slot is scored as occupied by a filler. + Filled, + /// The slot is scored as unoccupied. + Empty, +} + +impl SchemaSlotLabel { + /// Return the stable wire label name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::Filled => "filled", + Self::Empty => "empty", + } + } + + /// Parse a stable wire schema-slot occupancy label. + /// + /// # Errors + /// + /// Returns [`EventError::UnknownSchemaSlotLabel`] for unrecognized names. + pub fn from_wire_name(name: &str) -> Result { + match name { + "filled" => Ok(Self::Filled), + "empty" => Ok(Self::Empty), + _ => Err(EventError::UnknownSchemaSlotLabel), + } + } + + /// Return whether this label marks a filled slot. + #[must_use] + pub const fn is_filled(self) -> bool { + matches!(self, Self::Filled) + } + + /// Return the binary probability target used for RMSE. + /// + /// Filled truth is `1.0`; empty truth is `0.0`. + #[must_use] + pub const fn as_probability_target(self) -> f64 { + match self { + Self::Filled => 1.0, + Self::Empty => 0.0, + } + } +} + +/// Predicted or observed filler for one CHRONOS schema slot. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SchemaSlotAssignment { + role: EventRoleKind, + argument: String, +} + +impl SchemaSlotAssignment { + /// Bind a role to a hypothesized filler argument. + /// + /// # Errors + /// + /// Returns [`EventError::InvalidWirePayload`] when the argument is empty + /// or whitespace-only. + pub fn new(role: EventRoleKind, argument: impl Into) -> Result { + let argument = argument.into(); + let argument = argument.trim(); + if argument.is_empty() { + return Err(EventError::InvalidWirePayload); + } + Ok(Self { + role, + argument: argument.to_string(), + }) + } + + /// Return the typed role for this slot. + #[must_use] + pub const fn role(&self) -> EventRoleKind { + self.role + } + + /// Return the hypothesized filler argument. + #[must_use] + pub fn argument(&self) -> &str { + &self.argument + } +} + +/// Threshold a slot-occupancy probability into a filled/empty label. +/// +/// The threshold is inclusive: `probability >= threshold` fills the slot. +#[must_use] +pub fn decide_schema_slot( + probability: EventConfidence, + threshold: EventConfidence, +) -> SchemaSlotLabel { + if probability.value() >= threshold.value() { + SchemaSlotLabel::Filled + } else { + SchemaSlotLabel::Empty + } +} + +/// Explicit refusal to treat a CHRONOS schema prediction as an event instance. +/// +/// # Errors +/// +/// Always returns [`EventError::SchemaPredictionIsNotEventInstance`]. +pub fn refuse_schema_prediction_as_instance( + _prediction: SchemaPredictionId, +) -> Result { + Err(EventError::SchemaPredictionIsNotEventInstance) +} + +/// Explicit refusal to treat a CHRONOS schema prediction as a state transition. +/// +/// # Errors +/// +/// Always returns [`EventError::SchemaPredictionIsNotStateTransition`]. +pub fn refuse_schema_prediction_as_transition( + _prediction: SchemaPredictionId, +) -> Result<(), EventError> { + Err(EventError::SchemaPredictionIsNotStateTransition) +} + +/// Precision of recovered filled slots against known truth fills. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when either fill set is empty +/// or a `(role, argument)` pair is duplicated. +pub fn schema_slot_precision( + truth: &[SchemaSlotAssignment], + recovered: &[SchemaSlotAssignment], +) -> Result { + let truth_slots = unique_slot_set(truth)?; + let recovered_slots = unique_slot_set(recovered)?; + counted_rate( + recovered_slots.intersection(&truth_slots).count(), + recovered_slots.len(), + ) +} + +/// Recall of recovered filled slots against known truth fills. +/// +/// # Errors +/// +/// Returns [`EventError::InvalidWirePayload`] when either fill set is empty +/// or a `(role, argument)` pair is duplicated. +pub fn schema_slot_recall( + truth: &[SchemaSlotAssignment], + recovered: &[SchemaSlotAssignment], +) -> Result { + let truth_slots = unique_slot_set(truth)?; + let recovered_slots = unique_slot_set(recovered)?; + counted_rate( + recovered_slots.intersection(&truth_slots).count(), + truth_slots.len(), + ) +} + +fn unique_slot_set( + assignments: &[SchemaSlotAssignment], +) -> Result, EventError> { + if assignments.is_empty() { + return Err(EventError::InvalidWirePayload); + } + let mut slots = BTreeSet::new(); + for assignment in assignments { + if !slots.insert((assignment.role(), assignment.argument().to_string())) { + return Err(EventError::InvalidWirePayload); + } + } + Ok(slots) +} + +fn counted_rate(numerator: usize, denominator: usize) -> Result { + 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)) +} + +#[cfg(test)] +mod tests { + use super::{ + SchemaPredictionId, SchemaSlotAssignment, SchemaSlotLabel, counted_rate, + decide_schema_slot, refuse_schema_prediction_as_instance, + refuse_schema_prediction_as_transition, schema_slot_precision, schema_slot_recall, + }; + use crate::{EventConfidence, EventError, EventRoleKind}; + + fn filled(role: EventRoleKind, argument: &str) -> SchemaSlotAssignment { + SchemaSlotAssignment::new(role, argument).expect("slot") + } + + #[test] + fn schema_helpers_cover_local_branches() { + let prediction = SchemaPredictionId::from_raw(3); + assert_eq!( + refuse_schema_prediction_as_instance(prediction), + Err(EventError::SchemaPredictionIsNotEventInstance) + ); + assert_eq!( + refuse_schema_prediction_as_transition(prediction), + Err(EventError::SchemaPredictionIsNotStateTransition) + ); + let high = EventConfidence::new(0.8).expect("high"); + let low = EventConfidence::new(0.2).expect("low"); + assert_eq!(decide_schema_slot(high, low), SchemaSlotLabel::Filled); + assert_eq!(decide_schema_slot(low, high), SchemaSlotLabel::Empty); + let truth = [filled(EventRoleKind::Agent, "procurement office")]; + assert!((schema_slot_precision(&truth, &truth).expect("p") - 1.0).abs() < f64::EPSILON); + assert!((schema_slot_recall(&truth, &truth).expect("r") - 1.0).abs() < f64::EPSILON); + assert_eq!(counted_rate(0, 0), Err(EventError::InvalidWirePayload)); + assert_eq!( + counted_rate(usize::MAX, 1), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + counted_rate(1, usize::MAX), + Err(EventError::InvalidWirePayload) + ); + assert!((counted_rate(1, 2).expect("half") - 0.5).abs() < f64::EPSILON); + } +} diff --git a/crates/event_core/tests/schema_slot_contract.rs b/crates/event_core/tests/schema_slot_contract.rs new file mode 100644 index 00000000..6afa450b --- /dev/null +++ b/crates/event_core/tests/schema_slot_contract.rs @@ -0,0 +1,180 @@ +//! CHRONOS schema-slot predictions are not instances; accuracy is computed from truth. + +use event_core::{ + EventConfidence, EventError, EventRoleKind, SchemaPredictionId, SchemaSlotAssignment, + SchemaSlotLabel, decide_schema_slot, refuse_schema_prediction_as_instance, + refuse_schema_prediction_as_transition, schema_slot_precision, schema_slot_recall, +}; + +fn computed_rmse(truth: &[f64], recovered: &[f64]) -> f64 { + assert_eq!(truth.len(), recovered.len()); + let n = f64::from(u32::try_from(truth.len()).expect("tiny fixture")); + let sse: f64 = truth + .iter() + .zip(recovered) + .map(|(truth_value, recovered_value)| { + let residual = truth_value - recovered_value; + residual * residual + }) + .sum(); + (sse / n).sqrt() +} + +fn slot(role: EventRoleKind, argument: &str) -> SchemaSlotAssignment { + SchemaSlotAssignment::new(role, argument).expect("slot") +} + +#[test] +fn schema_prediction_cannot_be_cast_to_an_instance_or_transition() { + let prediction = SchemaPredictionId::from_raw(1); + assert_eq!( + refuse_schema_prediction_as_instance(prediction), + Err(EventError::SchemaPredictionIsNotEventInstance) + ); + assert_eq!( + refuse_schema_prediction_as_transition(prediction), + Err(EventError::SchemaPredictionIsNotStateTransition) + ); +} + +#[test] +fn slot_precision_and_recall_are_computed_from_known_truth_fills() { + let truth = [ + slot(EventRoleKind::Agent, "procurement office"), + slot(EventRoleKind::Product, "contract award"), + ]; + let calibrated = [ + slot(EventRoleKind::Agent, "procurement office"), + slot(EventRoleKind::Product, "contract award"), + slot(EventRoleKind::Place, "seoul"), + ]; + let always_fill = [ + slot(EventRoleKind::Agent, "procurement office"), + slot(EventRoleKind::Product, "contract award"), + slot(EventRoleKind::Place, "seoul"), + slot(EventRoleKind::Patient, "vendor"), + slot(EventRoleKind::Factor, "budget"), + slot(EventRoleKind::Instrument, "tender"), + ]; + + let calibrated_precision = schema_slot_precision(&truth, &calibrated).expect("precision"); + let naive_precision = schema_slot_precision(&truth, &always_fill).expect("naive p"); + let calibrated_recall = schema_slot_recall(&truth, &calibrated).expect("recall"); + let naive_recall = schema_slot_recall(&truth, &always_fill).expect("naive r"); + + assert!((calibrated_precision - (2.0 / 3.0)).abs() < 1.0e-12); + assert!((naive_precision - (1.0 / 3.0)).abs() < 1.0e-12); + assert!((calibrated_recall - 1.0).abs() < f64::EPSILON); + assert!((naive_recall - 1.0).abs() < f64::EPSILON); +} + +#[test] +fn calibrated_slot_occupancy_scores_have_lower_rmse_than_always_fill() { + let fixture = [ + ( + SchemaSlotLabel::Filled, + EventConfidence::new(0.90).expect("score"), + ), + ( + SchemaSlotLabel::Filled, + EventConfidence::new(0.85).expect("score"), + ), + ( + SchemaSlotLabel::Empty, + EventConfidence::new(0.15).expect("score"), + ), + ( + SchemaSlotLabel::Empty, + EventConfidence::new(0.10).expect("score"), + ), + ( + SchemaSlotLabel::Empty, + EventConfidence::new(0.20).expect("score"), + ), + ( + SchemaSlotLabel::Filled, + EventConfidence::new(0.88).expect("score"), + ), + ]; + let truth: Vec = fixture + .iter() + .map(|(label, _)| label.as_probability_target()) + .collect(); + let calibrated: Vec = fixture + .iter() + .map(|(_, confidence)| confidence.value()) + .collect(); + let always_fill = vec![1.0_f64; fixture.len()]; + let calibrated_rmse = computed_rmse(&truth, &calibrated); + let naive_rmse = computed_rmse(&truth, &always_fill); + assert!((calibrated_rmse - 0.141_067_359_796_658_94).abs() < 1.0e-12); + assert!((naive_rmse - std::f64::consts::FRAC_1_SQRT_2).abs() < 1.0e-12); + assert!( + calibrated_rmse < naive_rmse, + "computed calibrated RMSE {calibrated_rmse} must be below always-fill RMSE {naive_rmse}" + ); +} + +#[test] +fn assignment_helpers_fail_closed_on_empty_duplicate_and_blank_arguments() { + let one = [slot(EventRoleKind::Agent, "procurement office")]; + assert_eq!( + schema_slot_precision(&[], &[]), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + schema_slot_recall(&[], &one), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + schema_slot_precision(&one, &[]), + Err(EventError::InvalidWirePayload) + ); + let duplicate = [ + slot(EventRoleKind::Agent, "procurement office"), + slot(EventRoleKind::Agent, "procurement office"), + ]; + assert_eq!( + schema_slot_recall(&duplicate, &duplicate), + Err(EventError::InvalidWirePayload) + ); + assert_eq!( + SchemaSlotAssignment::new(EventRoleKind::Agent, " "), + Err(EventError::InvalidWirePayload) + ); + assert!((schema_slot_precision(&one, &one).expect("singleton") - 1.0).abs() < f64::EPSILON); +} + +#[test] +fn labels_round_trip_and_threshold_is_inclusive() { + assert_eq!(SchemaSlotLabel::Filled.wire_name(), "filled"); + assert_eq!(SchemaSlotLabel::Empty.wire_name(), "empty"); + assert_eq!( + SchemaSlotLabel::from_wire_name("filled").expect("parse"), + SchemaSlotLabel::Filled + ); + assert_eq!( + SchemaSlotLabel::from_wire_name("empty").expect("parse"), + SchemaSlotLabel::Empty + ); + assert_eq!( + SchemaSlotLabel::from_wire_name("maybe_slot"), + Err(EventError::UnknownSchemaSlotLabel) + ); + assert!(SchemaSlotLabel::Filled.is_filled()); + assert!(!SchemaSlotLabel::Empty.is_filled()); + assert!((SchemaSlotLabel::Filled.as_probability_target() - 1.0).abs() < f64::EPSILON); + assert!((SchemaSlotLabel::Empty.as_probability_target() - 0.0).abs() < f64::EPSILON); + + let half = EventConfidence::new(0.5).expect("half"); + assert_eq!(decide_schema_slot(half, half), SchemaSlotLabel::Filled); + assert_eq!( + decide_schema_slot(EventConfidence::new(0.49).expect("below"), half), + SchemaSlotLabel::Empty + ); + + let assigned = slot(EventRoleKind::Place, "seoul"); + assert_eq!(assigned.role(), EventRoleKind::Place); + assert_eq!(assigned.argument(), "seoul"); + assert_eq!(SchemaPredictionId::from_raw(7).raw(), 7); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index a3e674cf..43be5eb8 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -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` CHRONOS schema-slot precision/recall against known truth, `refuse_schema_prediction_as_instance` and `refuse_schema_prediction_as_transition`, label-target-derived calibrated occupancy RMSE `0.1410673598` versus always-fill `0.7071067812` in `schema_slot_contract.rs` on the active PR; remaining TDT detection/tracking, symbolic temporal consistency, and any future `event_intelligence` crate remain accepted-target | 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 | diff --git a/docs/adr/0009-purpose-bound-pii-governance.md b/docs/adr/0009-purpose-bound-pii-governance.md index 88ed1341..a4fa9980 100644 --- a/docs/adr/0009-purpose-bound-pii-governance.md +++ b/docs/adr/0009-purpose-bound-pii-governance.md @@ -1,9 +1,9 @@ # ADR 0009 — Purpose-bound PII governance without blanket masking -**Decision status:** Accepted +**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 -**Date:** 2026-08-10 +**Date:** 2026-08-10 **Supersedes:** None. ## Context diff --git a/docs/adr/0010-adaptive-llm-orchestration.md b/docs/adr/0010-adaptive-llm-orchestration.md index a3378983..093617df 100644 --- a/docs/adr/0010-adaptive-llm-orchestration.md +++ b/docs/adr/0010-adaptive-llm-orchestration.md @@ -1,8 +1,8 @@ # ADR 0010 — Adaptive LLM orchestration and test-time compute -**Decision status:** Accepted +**Decision status:** Accepted **Implementation maturity:** partial — `tepp_api` governed router, comparable-budget ablation record, and credential-free contextual-orchestrator binding are implemented on the active PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; live NIM execution, learned conductor calibration, and production ablation evidence remain accepted-target -**Date:** 2026-08-10 +**Date:** 2026-08-10 **Supersedes:** The LLM orchestration-selection/ablation clauses previously co-located in ADR 0006. ADR 0006 remains authoritative for GPU/VRAM and model-credential separation; ADR 0015 governs autonomous repository-write/review/merge authority. ## Context diff --git a/docs/adr/0011-standalone-modular-msa-boundary.md b/docs/adr/0011-standalone-modular-msa-boundary.md index d545ee23..5e16e4a4 100644 --- a/docs/adr/0011-standalone-modular-msa-boundary.md +++ b/docs/adr/0011-standalone-modular-msa-boundary.md @@ -1,8 +1,8 @@ # 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 -**Date:** 2026-08-10 +**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 +**Date:** 2026-08-10 **Supersedes:** The broad cross-service ownership wording in ADR 0001. ADR 0001 remains authoritative for Rust-first numerical architecture. ## Context diff --git a/docs/adr/0013-bitemporal-persistence-reproducibility-and-split-authority.md b/docs/adr/0013-bitemporal-persistence-reproducibility-and-split-authority.md index a593ddb1..71a56b8d 100644 --- a/docs/adr/0013-bitemporal-persistence-reproducibility-and-split-authority.md +++ b/docs/adr/0013-bitemporal-persistence-reproducibility-and-split-authority.md @@ -1,8 +1,8 @@ # ADR 0013 — Bitemporal persistence, reproducibility manifests, and split authority -**Decision status:** Accepted -**Implementation maturity:** partial — migration contracts, cutoff eligibility, in-memory bitemporal adapters, live SQL session/migration port, document SQL contracts, `DATABASE_URL` SQLx gate, optional `live-sqlx` `PgPool` open/execute driver, exact-head live PostgreSQL CI, tenant RLS (`tepp_app_runtime` + session GUC), append-only reproducibility-manifest SQL insert/lookup, model-run / model-artifact / corpus-split-manifest chain (migration `0003`), append-only immutability triggers (migration `0004`), temporal interval ordering CHECK constraints (migration `0005`), typed membership-assignment storage (migration `0006`), event-relation/mention/instance SQL, source-artifact SQL, audit-event action-code validation, and concurrent document-write stress implemented-main; backup/restore integrity revalidation on the active PR -**Date:** 2026-08-12 +**Decision status:** Accepted +**Implementation maturity:** partial — migration contracts, cutoff eligibility, in-memory bitemporal adapters, live SQL session/migration port, document SQL contracts, `DATABASE_URL` SQLx gate, optional `live-sqlx` `PgPool` open/execute driver, exact-head live PostgreSQL CI, tenant RLS (`tepp_app_runtime` + session GUC), append-only reproducibility-manifest SQL insert/lookup, model-run / model-artifact / corpus-split-manifest chain (migration `0003`), append-only immutability triggers (migration `0004`), temporal interval ordering CHECK constraints (migration `0005`), typed membership-assignment storage (migration `0006`), event-relation/mention/instance SQL, source-artifact SQL, audit-event action-code validation, and concurrent document-write stress implemented-main; backup/restore integrity revalidation on the active PR +**Date:** 2026-08-12 **Supersedes:** None; complements ADR 0002 (temporal semantics), ADR 0008 (evidence identity), and ADR 0011 (service ownership). ## Context diff --git a/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md b/docs/adr/0016-tdt-chronos-event-intelligence-boundary.md index b85ee0b4..574f2a55 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 — CHRONOS schema-slot precision/recall and prediction-versus-instance refusal live in existing `event_core`; remaining TDT detection/tracking and symbolic temporal-consistency layers 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..6ee05a90 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -21,7 +21,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [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 | CHRONOS schema-slot precision/recall in existing `event_core`; remaining TDT/CHRONOS stack remains accepted-target. | ## Decision ownership summary diff --git a/docs/connectors/naruon-artifact-consumer.md b/docs/connectors/naruon-artifact-consumer.md index 2e4f4d6c..5fe0424c 100644 --- a/docs/connectors/naruon-artifact-consumer.md +++ b/docs/connectors/naruon-artifact-consumer.md @@ -1,6 +1,6 @@ # naruon modular consumer contract for TEPP artifacts -**Status:** Partial — versioned DTO, HTTP interchange, and loopback live listener on the active PR; production TLS/`$PORT` remaining +**Status:** Partial — versioned DTO, HTTP interchange, and loopback live listener on the active PR; production TLS/`$PORT` remaining **Last reviewed:** 2026-08-16 ## Boundary diff --git a/docs/research/chronos-schema-slot-calibration.md b/docs/research/chronos-schema-slot-calibration.md new file mode 100644 index 00000000..a6fcb45c --- /dev/null +++ b/docs/research/chronos-schema-slot-calibration.md @@ -0,0 +1,31 @@ +# CHRONOS schema-slot calibration + +## Scope + +This note doctors the `event_core` gate that keeps CHRONOS schema-slot prediction distinct from event-instance promotion: + +1. a filled versus empty slot label is prediction evidence, not a promoted instance or state transition; +2. slot precision and recall are computed from known-truth `(role, argument)` fills; +3. calibrated occupancy probabilities recover the binary fill target with lower RMSE than an always-fill predictor. + +No database migration is allocated. A later CHRONOS reasoner may consume these scores as hypothetical schema evidence only. + +## Authoritative sources + +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 + +Chambers, N., & Jurafsky, D. (2009). Unsupervised learning of narrative schemas and their participants. In *Proceedings of the Joint Conference of the 47th Annual Meeting of the ACL and the 4th International Joint Conference on Natural Language Processing of the AFNLP* (pp. 602–610). Association for Computational Linguistics. + +Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & Weischedel, R. (2004). The Automatic Content Extraction (ACE) program—Tasks, data, and evaluation. In *Proceedings of the Fourth International Conference on Language Resources and Evaluation (LREC’04)* (pp. 837–840). European Language Resources Association. + +## Application + +Anagnostopoulos et al. (2013) describe CHRONOS as a reasoner for qualitative temporal information and inferred temporal relations. Chambers and Jurafsky (2009) provide a narrative-schema participant-slot precedent, while Doddington et al. (2004) describe system-to-reference mapping and application-level evaluation; none of these sources defines TEPP's metric contract. TEPP independently refuses prediction promotion through `refuse_schema_prediction_as_instance` and `refuse_schema_prediction_as_transition`, computes slot precision and recall through `schema_slot_precision` and `schema_slot_recall`, and evaluates known-truth RMSE in [`schema_slot_contract.rs`](../../crates/event_core/tests/schema_slot_contract.rs) using production `SchemaSlotLabel` and `EventConfidence` APIs (Anagnostopoulos et al., 2013; Chambers & Jurafsky, 2009; Doddington et al., 2004). + +## Verification + +- `refuse_schema_prediction_as_instance` always returns `SchemaPredictionIsNotEventInstance`; +- `refuse_schema_prediction_as_transition` always returns `SchemaPredictionIsNotStateTransition`; +- `decide_schema_slot` uses an inclusive probability threshold; +- `schema_slot_precision` and `schema_slot_recall` fail closed on empty or duplicate fill sets; +- computed RMSE of known occupancy targets is lower under calibrated probabilities than under an always-fill predictor. diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 28e62d5c..a582366d 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -66,7 +66,11 @@ Allan, J. (Ed.). (2002). *Topic detection and tracking: Event-based information 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 -TEPP uses interval and partial-order reasoning, bitemporal availability, leakage-safe cutoffs, TDT segmentation/link/detection/first-story/tracking tasks, and separate neural/symbolic event-schema and temporal-consistency layers. +Chambers, N., & Jurafsky, D. (2009). Unsupervised learning of narrative schemas and their participants. In *Proceedings of the Joint Conference of the 47th Annual Meeting of the ACL and the 4th International Joint Conference on Natural Language Processing of the AFNLP* (pp. 602–610). Association for Computational Linguistics. + +Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & Weischedel, R. (2004). The Automatic Content Extraction (ACE) program—Tasks, data, and evaluation. In *Proceedings of the Fourth International Conference on Language Resources and Evaluation (LREC’04)* (pp. 837–840). European Language Resources Association. + +TEPP uses interval and partial-order reasoning, bitemporal availability, leakage-safe cutoffs, TDT segmentation/link/detection/first-story/tracking tasks (Allan, 2002), qualitative temporal reasoning (Anagnostopoulos et al., 2013), and separate neural/symbolic event-schema and narrative participant-slot layers (Chambers & Jurafsky, 2009). Under [ADR 0016](../adr/0016-tdt-chronos-event-intelligence-boundary.md), predicted schema-slot fills stay hypothetical until independently promoted; this is a TEPP policy boundary, not a literature result. ## Unicode, language tags, and multilingual structure diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index aae1a06e..97813257 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -16,6 +16,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Six-clock temporal | `temporal_core` | implemented-main | — | unit + wire | Task 3 / PR #8 | | Allen path-consistency | `temporal_core` | implemented-main | — | unit + budget tests | Task 4 / PR #9 | | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | +| CHRONOS schema-slot accuracy | `event_core` | active-PR | this PR | computed slot P/R + RMSE vs always-fill | ADR 0016; `docs/research/chronos-schema-slot-calibration.md` | | 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 |