From 9b09f14d08a8b3a82fd538c6950f0286dec9fd4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 06:21:51 +0900 Subject: [PATCH 1/6] feat(event): score CHRONOS schema slots with precision and recall Predicted role fillers stay hypothetical. Slot precision/recall and occupancy RMSE are computed from known truth; schema predictions cannot become instances or transitions. --- CHANGELOG.md | 1 + crates/event_core/src/error.rs | 25 ++ crates/event_core/src/lib.rs | 20 +- crates/event_core/src/schema.rs | 258 ++++++++++++++++++ .../event_core/tests/schema_slot_contract.rs | 147 ++++++++++ docs/TRACEABILITY.md | 2 +- ...tdt-chronos-event-intelligence-boundary.md | 2 +- docs/adr/README.md | 2 +- .../chronos-schema-slot-calibration.md | 31 +++ docs/research/standards-and-literature.md | 6 +- docs/validation/temporal-event-foundation.md | 1 + 11 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 crates/event_core/src/schema.rs create mode 100644 crates/event_core/tests/schema_slot_contract.rs create mode 100644 docs/research/chronos-schema-slot-calibration.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 625b5f3c..cb535bd9 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 calibrated occupancy scores recover fill targets with lower RMSE than an always-fill predictor. - `persistence_postgres` event-instance SQL contracts: bitemporal insert and as-known-at lookup that refuse inverted valid/system windows and hostile type/lifecycle labels before SQL is rendered. - `persistence_postgres` event-mention SQL contracts: mention identity cannot equal the instance it supports; confidence must be finite and in `(0, 1]`. - `persistence_postgres` event-relation SQL contracts: closed ERD transition/provenance vocabulary bound to `transition_edge`, fail-closed unknown types and transition self-loops, live insert of `causes`/`references`. 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..0244b936 --- /dev/null +++ b/crates/event_core/tests/schema_slot_contract.rs @@ -0,0 +1,147 @@ +//! 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 > naive_precision, + "computed precision {calibrated_precision} must exceed always-fill precision {naive_precision}" + ); + assert!((calibrated_recall - naive_recall).abs() < f64::EPSILON); +} + +#[test] +fn calibrated_slot_occupancy_scores_have_lower_rmse_than_always_fill() { + let truth = [1.0_f64, 1.0, 0.0, 0.0, 0.0, 1.0]; + let calibrated = [0.90_f64, 0.85, 0.15, 0.10, 0.20, 0.88]; + let always_fill = [1.0_f64, 1.0, 1.0, 1.0, 1.0, 1.0]; + let calibrated_rmse = computed_rmse(&truth, &calibrated); + let naive_rmse = computed_rmse(&truth, &always_fill); + 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 7094cb87..a118c8c6 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 and prediction-versus-instance refusal 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 | future `interpretation_gateway` | accepted-target | | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | future contextual-orchestrator integration + ablation evidence | accepted-target | | purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | future authorization/persistence/export/provider adapters | accepted-target | 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 1a9a7b31..36d5467f 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, and tenant RLS implemented; full physical ERD remaining. | | [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/research/chronos-schema-slot-calibration.md b/docs/research/chronos-schema-slot-calibration.md new file mode 100644 index 00000000..b8f61dd6 --- /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) keep CHRONOS completions in a qualitative reasoning layer rather than treating them as observed chronology. Chambers and Jurafsky (2009) evaluate narrative schemas by recovered participant slots, and Doddington et al. (2004) score argument fills with precision and recall against known truth. TEPP therefore refuses to cast a schema prediction as an event instance or transition and requires computed slot precision, recall, and RMSE against known truth (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 b4b14468..8ab5dc1e 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, and separate neural/symbolic event-schema and temporal-consistency layers. Predicted schema-slot fills stay hypothetical until independently promoted. ## Unicode, language tags, and multilingual structure diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index 969c1d7c..44b7beae 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 | #36 typed membership | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005` interval CHECKs (implemented-main via #35) + `0006` typed membership (active PR) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#35 + `0006` | From b21e9a8b0b3b9c61e5b3c8123aa0f5146294f356 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:39:46 +0900 Subject: [PATCH 2/6] test(event): assert exact schema slot rates --- crates/event_core/tests/schema_slot_contract.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/event_core/tests/schema_slot_contract.rs b/crates/event_core/tests/schema_slot_contract.rs index 0244b936..eebcd72a 100644 --- a/crates/event_core/tests/schema_slot_contract.rs +++ b/crates/event_core/tests/schema_slot_contract.rs @@ -62,11 +62,10 @@ fn slot_precision_and_recall_are_computed_from_known_truth_fills() { 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 > naive_precision, - "computed precision {calibrated_precision} must exceed always-fill precision {naive_precision}" - ); - assert!((calibrated_recall - naive_recall).abs() < f64::EPSILON); + 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] From ac953ac01e1c14171080119427b134e51c437fb2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:26:14 +0900 Subject: [PATCH 3/6] test(event): bind schema occupancy RMSE to production labels --- .../event_core/tests/schema_slot_contract.rs | 40 +++++++++++++++++-- docs/TRACEABILITY.md | 2 +- .../chronos-schema-slot-calibration.md | 2 +- docs/research/standards-and-literature.md | 2 +- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/event_core/tests/schema_slot_contract.rs b/crates/event_core/tests/schema_slot_contract.rs index eebcd72a..6afa450b 100644 --- a/crates/event_core/tests/schema_slot_contract.rs +++ b/crates/event_core/tests/schema_slot_contract.rs @@ -70,11 +70,45 @@ fn slot_precision_and_recall_are_computed_from_known_truth_fills() { #[test] fn calibrated_slot_occupancy_scores_have_lower_rmse_than_always_fill() { - let truth = [1.0_f64, 1.0, 0.0, 0.0, 0.0, 1.0]; - let calibrated = [0.90_f64, 0.85, 0.15, 0.10, 0.20, 0.88]; - let always_fill = [1.0_f64, 1.0, 1.0, 1.0, 1.0, 1.0]; + 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}" diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 9d032e93..668da056 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 | `event_core` CHRONOS schema-slot precision/recall and prediction-versus-instance refusal on the active PR; remaining TDT detection/tracking, symbolic temporal consistency, and any future `event_intelligence` crate remain accepted-target | active-PR | +| 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/research/chronos-schema-slot-calibration.md b/docs/research/chronos-schema-slot-calibration.md index b8f61dd6..45a2a685 100644 --- a/docs/research/chronos-schema-slot-calibration.md +++ b/docs/research/chronos-schema-slot-calibration.md @@ -20,7 +20,7 @@ Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & Weisch ## Application -Anagnostopoulos et al. (2013) keep CHRONOS completions in a qualitative reasoning layer rather than treating them as observed chronology. Chambers and Jurafsky (2009) evaluate narrative schemas by recovered participant slots, and Doddington et al. (2004) score argument fills with precision and recall against known truth. TEPP therefore refuses to cast a schema prediction as an event instance or transition and requires computed slot precision, recall, and RMSE against known truth (Anagnostopoulos et al., 2013; Chambers & Jurafsky, 2009; Doddington et al., 2004). +Anagnostopoulos et al. (2013) keep CHRONOS completions in a qualitative reasoning layer rather than treating them as observed chronology. Chambers and Jurafsky (2009) provide narrative-schema participant-slot precedent, while Doddington et al. (2004) describe ACE system-to-reference mapping and application-value evaluation; neither source defines TEPP's metric contract. TEPP therefore refuses to cast a schema prediction as an event instance or transition and requires its own computed slot precision, recall, and RMSE against known truth (see [`schema_slot_contract.rs`](../../crates/event_core/tests/schema_slot_contract.rs) and the `event_core` API; Anagnostopoulos et al., 2013; Chambers & Jurafsky, 2009; Doddington et al., 2004). ## Verification diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 6a1445f8..11abd489 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -70,7 +70,7 @@ Chambers, N., & Jurafsky, D. (2009). Unsupervised learning of narrative schemas 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, and separate neural/symbolic event-schema and temporal-consistency layers. Predicted schema-slot fills stay hypothetical until independently promoted. +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 From fd4c04bda95483b62a29cee34e9ff8e478eaafd9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:30:42 +0900 Subject: [PATCH 4/6] docs(event): record schema occupancy acceptance evidence --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff22ff1e..bc60cd13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +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 calibrated occupancy scores recover fill targets with lower RMSE than an always-fill predictor. +- `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` 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). From dae7e55dc7bb2135825437a9df9c7aafc957ff42 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:35:22 +0900 Subject: [PATCH 5/6] docs(event): name schema contract APIs --- docs/research/chronos-schema-slot-calibration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/chronos-schema-slot-calibration.md b/docs/research/chronos-schema-slot-calibration.md index 45a2a685..a6fcb45c 100644 --- a/docs/research/chronos-schema-slot-calibration.md +++ b/docs/research/chronos-schema-slot-calibration.md @@ -20,7 +20,7 @@ Doddington, G., Mitchell, A., Przybocki, M., Ramshaw, L., Strassel, S., & Weisch ## Application -Anagnostopoulos et al. (2013) keep CHRONOS completions in a qualitative reasoning layer rather than treating them as observed chronology. Chambers and Jurafsky (2009) provide narrative-schema participant-slot precedent, while Doddington et al. (2004) describe ACE system-to-reference mapping and application-value evaluation; neither source defines TEPP's metric contract. TEPP therefore refuses to cast a schema prediction as an event instance or transition and requires its own computed slot precision, recall, and RMSE against known truth (see [`schema_slot_contract.rs`](../../crates/event_core/tests/schema_slot_contract.rs) and the `event_core` API; Anagnostopoulos et al., 2013; Chambers & Jurafsky, 2009; Doddington et al., 2004). +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 From 7a1f33aa68c1c9be9e9da7ac7f7dadb1092ff9e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 13:32:59 +0900 Subject: [PATCH 6/6] docs(changelog): normalize schema slot entries --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd073c7..ed5485bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ 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). +- `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. - `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).