diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2aa881675..e32090584 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -61,6 +61,7 @@ boundaries above remain the target modular MSA architecture. | `tepp_simulation` | known-truth temporal/event data generation | | `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo metrics | | `tepp_api` | versioned DTO, schema, and export contracts | +| `outcome_order` | input-process-outcome edges cannot move backward in event time | | `retrospective_edge` | retrospective reporting cannot become a transition or a translation | | `payload_bound` | untrusted documents, records, checkpoints, and LLM outputs fail closed without identity, provenance, size, and depth | | `inferred_status` | inferred relations cannot be promoted to observed evidence or transitions | diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b3ac668..c158db0f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ### Added +- `outcome_order` identity gate: `input_to` and `process_to` cannot move backward or stay contemporaneous in event-time rank; `outcome_of` may point at an earlier producer and cannot become a state transition; recovered kinds match known truth at a higher computed rate than collapsing every kind to `input_to` (ADR 0002/0003). +- `persistence_postgres` retention/deletion/legal-hold (migration `0007`): policy rows, legal holds that block completed deletion, evidence tombstones without raw-source restore, analysis exclusion only for `logical_revocation`/`identity_tombstone` (not `cache_export_removal`), and deletion requests bound to the cited retention policy's tenant/class/purpose. - `retrospective_edge` identity gate: retrospective reporting may point to earlier event time but cannot become a state transition or a translation; recovered reporting kinds match known truth at a higher computed rate than collapsing every report to a contemporaneous forward report (ADR 0002/0003). - `persistence_postgres` retention/deletion/legal-hold (migration `0007`): policy rows, legal holds that block completed deletion, evidence tombstones without raw-source restore, analysis exclusion only for `logical_revocation`/`identity_tombstone` (not `cache_export_removal`), and deletion requests bound to the cited retention policy's tenant/class/purpose. - `payload_bound` identity gate: documents, serialized records, model checkpoints, and LLM outputs stay untrusted until identity, provenance, size, and depth validate; recovered accept/reject flags match known truth at a higher computed rate than accepting every payload (ADR 0008/0013). diff --git a/Cargo.lock b/Cargo.lock index b48bc6c2f..1e2fc7db6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -955,6 +955,10 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" name = "operational_log" version = "0.1.0" +[[package]] +name = "outcome_order" +version = "0.1.0" + [[package]] name = "parking" version = "2.2.1" diff --git a/Cargo.toml b/Cargo.toml index b4db0b738..aac827ff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/outcome_order", "crates/retrospective_edge", "crates/payload_bound", "crates/inferred_status", @@ -49,6 +50,7 @@ default-members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/outcome_order", "crates/retrospective_edge", "crates/payload_bound", "crates/inferred_status", diff --git a/README.md b/README.md index d5e7d3e86..a3dba4041 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ crates/corpus_split crates/tepp_simulation crates/validation_core crates/tepp_api +crates/outcome_order crates/retrospective_edge crates/payload_bound crates/inferred_status diff --git a/crates/outcome_order/Cargo.toml b/crates/outcome_order/Cargo.toml new file mode 100644 index 000000000..bcf3d41cb --- /dev/null +++ b/crates/outcome_order/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "outcome_order" +description = "Input-process-outcome edges never move backward in event time." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +readme.workspace = true +keywords.workspace = true +categories.workspace = true +publish = false + +[lints] +workspace = true diff --git a/crates/outcome_order/src/error.rs b/crates/outcome_order/src/error.rs new file mode 100644 index 000000000..b82ff151a --- /dev/null +++ b/crates/outcome_order/src/error.rs @@ -0,0 +1,64 @@ +//! Fail-closed input-process-outcome order errors. + +use std::fmt; + +/// A fail-closed input-process-outcome order error. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum OutcomeOrderError { + /// An `input_to` or `process_to` edge moved backward in event time. + ReverseIpoOrder, + /// A transition IPO edge used equal event-time ranks. + UncertainIpoOrder, + /// An `outcome_of` provenance edge was treated as a state transition. + OutcomeOfIsNotTransition, + /// A recovery slice was empty or length-mismatched. + InvalidEdgePayload, +} + +impl fmt::Display for OutcomeOrderError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::ReverseIpoOrder => { + "input-process-outcome transitions cannot move backward in event time" + } + Self::UncertainIpoOrder => { + "input-process-outcome transitions require a strict event-time order" + } + Self::OutcomeOfIsNotTransition => "outcome_of is not a state transition", + Self::InvalidEdgePayload => "invalid outcome-order payload", + }; + formatter.write_str(message) + } +} + +impl std::error::Error for OutcomeOrderError {} + +#[cfg(test)] +mod tests { + use super::OutcomeOrderError; + + #[test] + fn error_messages_are_stable() { + for (error, message) in [ + ( + OutcomeOrderError::ReverseIpoOrder, + "input-process-outcome transitions cannot move backward in event time", + ), + ( + OutcomeOrderError::UncertainIpoOrder, + "input-process-outcome transitions require a strict event-time order", + ), + ( + OutcomeOrderError::OutcomeOfIsNotTransition, + "outcome_of is not a state transition", + ), + ( + OutcomeOrderError::InvalidEdgePayload, + "invalid outcome-order payload", + ), + ] { + assert_eq!(error.to_string(), message); + } + } +} diff --git a/crates/outcome_order/src/kind.rs b/crates/outcome_order/src/kind.rs new file mode 100644 index 000000000..c2ba42910 --- /dev/null +++ b/crates/outcome_order/src/kind.rs @@ -0,0 +1,176 @@ +//! Input, process, and outcome-of kinds with event-time order gates. + +use crate::OutcomeOrderError; +use std::cmp::Ordering; + +/// Closed vocabulary of input-process-outcome edges. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum OutcomeKind { + /// Input feeding a later process (forward transition). + InputTo, + /// Process feeding a later process or outcome (forward transition). + ProcessTo, + /// Outcome pointing back at its producer (provenance; may look backward). + OutcomeOf, +} + +impl OutcomeKind { + /// Return the stable wire kind name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::InputTo => "input_to", + Self::ProcessTo => "process_to", + Self::OutcomeOf => "outcome_of", + } + } + + /// Parse a stable wire kind name. + /// + /// # Errors + /// + /// Returns [`OutcomeOrderError::InvalidEdgePayload`] for unrecognized names. + pub fn from_wire_name(name: &str) -> Result { + match name { + "input_to" => Ok(Self::InputTo), + "process_to" => Ok(Self::ProcessTo), + "outcome_of" => Ok(Self::OutcomeOf), + _ => Err(OutcomeOrderError::InvalidEdgePayload), + } + } + + /// Return whether this kind is a forward state-transition edge. + /// + /// `outcome_of` is provenance (the inverse of `produces`). + #[must_use] + pub const fn is_transition_edge(self) -> bool { + match self { + Self::InputTo | Self::ProcessTo => true, + Self::OutcomeOf => false, + } + } +} + +/// Refuse reverse event-time order on input and process transitions. +/// +/// `source_rank` and `target_rank` are opaque event-time ordinals, not clock +/// identities. Transition kinds require `source_rank < target_rank`. +/// [`OutcomeKind::OutcomeOf`] may point at an earlier producer. +/// +/// # Errors +/// +/// Returns [`OutcomeOrderError::ReverseIpoOrder`] when a transition moves +/// backward and [`OutcomeOrderError::UncertainIpoOrder`] when a transition +/// uses equal ranks. +pub fn refuse_reverse_ipo_order( + kind: OutcomeKind, + source_rank: u64, + target_rank: u64, +) -> Result<(), OutcomeOrderError> { + match kind { + OutcomeKind::InputTo | OutcomeKind::ProcessTo => match source_rank.cmp(&target_rank) { + Ordering::Less => Ok(()), + Ordering::Greater => Err(OutcomeOrderError::ReverseIpoOrder), + Ordering::Equal => Err(OutcomeOrderError::UncertainIpoOrder), + }, + OutcomeKind::OutcomeOf => Ok(()), + } +} + +/// Refuse to treat `outcome_of` as a forward state transition. +/// +/// # Errors +/// +/// Returns [`OutcomeOrderError::OutcomeOfIsNotTransition`] when `kind` is +/// [`OutcomeKind::OutcomeOf`]. +pub fn refuse_outcome_of_as_transition(kind: OutcomeKind) -> Result<(), OutcomeOrderError> { + match kind { + OutcomeKind::OutcomeOf => Err(OutcomeOrderError::OutcomeOfIsNotTransition), + OutcomeKind::InputTo | OutcomeKind::ProcessTo => Ok(()), + } +} + +/// Fraction of recovered IPO kinds that match known truth. +/// +/// # Errors +/// +/// Returns [`OutcomeOrderError::InvalidEdgePayload`] when either slice is +/// empty or the lengths differ. +pub fn kind_recovery_rate( + truth: &[OutcomeKind], + decided: &[OutcomeKind], +) -> Result { + if truth.is_empty() || truth.len() != decided.len() { + return Err(OutcomeOrderError::InvalidEdgePayload); + } + let mut matches = 0_u32; + for (truth_kind, decided_kind) in truth.iter().zip(decided) { + if truth_kind == decided_kind { + matches += 1; + } + } + Ok(f64::from(matches) / truth.len() as f64) +} + +#[cfg(test)] +mod tests { + use super::{ + OutcomeKind, kind_recovery_rate, refuse_outcome_of_as_transition, refuse_reverse_ipo_order, + }; + use crate::OutcomeOrderError; + + #[test] + fn local_branches_cover_kinds_order_and_payloads() { + for kind in [ + OutcomeKind::InputTo, + OutcomeKind::ProcessTo, + OutcomeKind::OutcomeOf, + ] { + assert_eq!( + OutcomeKind::from_wire_name(kind.wire_name()).expect("round-trip"), + kind + ); + } + assert!(OutcomeKind::InputTo.is_transition_edge()); + assert!(OutcomeKind::ProcessTo.is_transition_edge()); + assert!(!OutcomeKind::OutcomeOf.is_transition_edge()); + assert_eq!( + OutcomeKind::from_wire_name("causes"), + Err(OutcomeOrderError::InvalidEdgePayload) + ); + refuse_reverse_ipo_order(OutcomeKind::InputTo, 1, 2).expect("forward"); + refuse_reverse_ipo_order(OutcomeKind::ProcessTo, 2, 3).expect("forward"); + refuse_reverse_ipo_order(OutcomeKind::OutcomeOf, 9, 1).expect("look-back"); + assert_eq!( + refuse_reverse_ipo_order(OutcomeKind::InputTo, 4, 1), + Err(OutcomeOrderError::ReverseIpoOrder) + ); + assert_eq!( + refuse_reverse_ipo_order(OutcomeKind::ProcessTo, 8, 8), + Err(OutcomeOrderError::UncertainIpoOrder) + ); + assert_eq!( + refuse_outcome_of_as_transition(OutcomeKind::OutcomeOf), + Err(OutcomeOrderError::OutcomeOfIsNotTransition) + ); + refuse_outcome_of_as_transition(OutcomeKind::InputTo).expect("transition"); + refuse_outcome_of_as_transition(OutcomeKind::ProcessTo).expect("transition"); + let matched = + kind_recovery_rate(&[OutcomeKind::InputTo], &[OutcomeKind::InputTo]).expect("rate"); + assert!((matched - 1.0).abs() < f64::EPSILON); + let partial = kind_recovery_rate( + &[OutcomeKind::InputTo, OutcomeKind::OutcomeOf], + &[OutcomeKind::InputTo, OutcomeKind::InputTo], + ) + .expect("partial"); + assert!((partial - 0.5).abs() < f64::EPSILON); + assert_eq!( + kind_recovery_rate(&[], &[]), + Err(OutcomeOrderError::InvalidEdgePayload) + ); + assert_eq!( + kind_recovery_rate(&[OutcomeKind::InputTo], &[]), + Err(OutcomeOrderError::InvalidEdgePayload) + ); + } +} diff --git a/crates/outcome_order/src/lib.rs b/crates/outcome_order/src/lib.rs new file mode 100644 index 000000000..ce70cffe8 --- /dev/null +++ b/crates/outcome_order/src/lib.rs @@ -0,0 +1,22 @@ +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![allow(clippy::cast_precision_loss)] +//! Input-process-outcome edges never move backward in event time. +//! +//! `input_to` and `process_to` are forward transitions. `outcome_of` is +//! provenance (the inverse of `produces`) and may point at an earlier +//! producer without becoming a reverse state transition (ADR 0002/0003). + +mod error; +mod kind; + +/// Fail-closed input-process-outcome order errors. +pub use error::OutcomeOrderError; +/// Closed vocabulary of input, process, and outcome-of edges. +pub use kind::OutcomeKind; +/// Fraction of recovered IPO kinds that match known truth. +pub use kind::kind_recovery_rate; +/// Refuse to treat `outcome_of` as a forward state transition. +pub use kind::refuse_outcome_of_as_transition; +/// Refuse reverse event-time order on input and process transitions. +pub use kind::refuse_reverse_ipo_order; diff --git a/crates/outcome_order/tests/crate_contract.rs b/crates/outcome_order/tests/crate_contract.rs new file mode 100644 index 000000000..4a27dc826 --- /dev/null +++ b/crates/outcome_order/tests/crate_contract.rs @@ -0,0 +1,7 @@ +//! Integration contract for the `outcome_order` package identity. + +#[test] +fn package_identity_is_stable() { + let observed = std::hint::black_box(env!("CARGO_PKG_NAME")); + assert_eq!(observed, "outcome_order"); +} diff --git a/crates/outcome_order/tests/outcome_order_contract.rs b/crates/outcome_order/tests/outcome_order_contract.rs new file mode 100644 index 000000000..5cb150e77 --- /dev/null +++ b/crates/outcome_order/tests/outcome_order_contract.rs @@ -0,0 +1,83 @@ +//! Input→process→outcome edges never move backward in event time. + +use outcome_order::{ + OutcomeKind, OutcomeOrderError, kind_recovery_rate, refuse_outcome_of_as_transition, + refuse_reverse_ipo_order, +}; + +#[test] +fn input_and_process_edges_cannot_move_backward_in_event_time() { + refuse_reverse_ipo_order(OutcomeKind::InputTo, 1, 2).expect("forward input"); + refuse_reverse_ipo_order(OutcomeKind::ProcessTo, 2, 3).expect("forward process"); + assert_eq!( + refuse_reverse_ipo_order(OutcomeKind::InputTo, 3, 1), + Err(OutcomeOrderError::ReverseIpoOrder) + ); + assert_eq!( + refuse_reverse_ipo_order(OutcomeKind::ProcessTo, 5, 4), + Err(OutcomeOrderError::ReverseIpoOrder) + ); + assert_eq!( + refuse_reverse_ipo_order(OutcomeKind::InputTo, 7, 7), + Err(OutcomeOrderError::UncertainIpoOrder) + ); +} + +#[test] +fn outcome_of_may_point_backward_and_is_not_a_transition() { + refuse_reverse_ipo_order(OutcomeKind::OutcomeOf, 9, 2).expect("provenance may look back"); + refuse_reverse_ipo_order(OutcomeKind::OutcomeOf, 2, 2).expect("same-rank provenance"); + assert_eq!( + refuse_outcome_of_as_transition(OutcomeKind::OutcomeOf), + Err(OutcomeOrderError::OutcomeOfIsNotTransition) + ); + refuse_outcome_of_as_transition(OutcomeKind::InputTo).expect("input_to is a transition"); + refuse_outcome_of_as_transition(OutcomeKind::ProcessTo).expect("process_to is a transition"); +} + +#[test] +fn recovered_kinds_match_known_truth_better_than_an_input_collapse() { + let truth = [ + OutcomeKind::InputTo, + OutcomeKind::ProcessTo, + OutcomeKind::OutcomeOf, + ]; + let recovered = truth; + let collapsed = [ + OutcomeKind::InputTo, + OutcomeKind::InputTo, + OutcomeKind::InputTo, + ]; + let recovered_rate = kind_recovery_rate(&truth, &recovered).expect("recovered"); + let collapsed_rate = kind_recovery_rate(&truth, &collapsed).expect("collapsed"); + let expected = { + let mut matches = 0_u32; + for (truth_kind, decided_kind) in truth.iter().zip(recovered.iter()) { + if truth_kind == decided_kind { + matches += 1; + } + } + f64::from(matches) / f64::from(u32::try_from(truth.len()).expect("len")) + }; + assert!((recovered_rate - expected).abs() < f64::EPSILON); + assert!(recovered_rate > collapsed_rate); +} + +#[test] +fn empty_or_mismatched_kind_payloads_fail_closed() { + assert_eq!( + kind_recovery_rate(&[], &[]), + Err(OutcomeOrderError::InvalidEdgePayload) + ); + assert_eq!( + kind_recovery_rate(&[OutcomeKind::InputTo], &[]), + Err(OutcomeOrderError::InvalidEdgePayload) + ); + assert_eq!( + kind_recovery_rate( + &[OutcomeKind::InputTo, OutcomeKind::ProcessTo], + &[OutcomeKind::InputTo] + ), + Err(OutcomeOrderError::InvalidEdgePayload) + ); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index a8ba5e7fe..302748537 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -12,7 +12,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | Rust workspace/quality foundation | ADR 0007 | workspace/CI/repository contract | implemented-main | | six distinct clocks and uncertain intervals | PRD; ADR 0002 | PR #8 `temporal_core` on protected main; `system_clock` system-vs-other-clock identity on the active PR | active-PR | | Allen relation algebra/bounded closure | ADR 0002; temporal research | PR #9 `temporal_core` path-consistency on protected main | implemented-main | -| forward-only transition subgraph | PRD; ADR 0002/0003 | `relation_graph` on protected main; `retrospective_edge` retrospective-versus-translation identity on the active PR | partial | +| forward-only transition subgraph | PRD; ADR 0002/0003 | `relation_graph` on protected main; `outcome_order` IPO event-time order on the active PR | partial | | event ontology/evidence mentions | PRD; ADR 0003 | `event_core` mention/instance separation on protected main; `persistence_postgres` mention SQL implemented-main refuses mention-as-instance; event-instance SQL (#39 implemented-main) refuses inverted windows; full intelligence stack remaining | partial | | time-varying cross-classified multiple membership | PRD; ADR 0003 | `membership_core` network on protected main; `inferred_status` inferred-versus-observed identity on the active PR; multilevel estimators remaining | partial | | leakage-safe availability/cutoff snapshots | PRD; ADR 0002/0013 | `corpus_split` on protected main | implemented-main | diff --git a/docs/adr/0002-six-clock-temporal-semantics.md b/docs/adr/0002-six-clock-temporal-semantics.md index 7f2481bb3..866ffcb84 100644 --- a/docs/adr/0002-six-clock-temporal-semantics.md +++ b/docs/adr/0002-six-clock-temporal-semantics.md @@ -1,6 +1,7 @@ # ADR 0002 — Six-clock temporal semantics and leakage prevention **Decision status:** Accepted +**Implementation maturity:** partial — typed clocks/intervals are implemented-main (PR #8); input-process-outcome event-time order is `outcome_order` on the active PR; remaining clock-identity and split enforcement stay accepted-target **Implementation maturity:** partial — typed clocks/intervals implemented-main via `temporal_core`; retrospective-reporting identity in `retrospective_edge` on the active PR; downstream transition/split enforcement remains accepted-target **Implementation maturity:** active-PR — evidential-vs-transition gate in `support_edge` on the active PR; remaining graph/split enforcement stays accepted-target **Implementation maturity:** active-PR — system-clock identity in `system_clock` on the active PR; remaining graph/split enforcement stays accepted-target diff --git a/docs/adr/0003-relational-event-multiple-membership.md b/docs/adr/0003-relational-event-multiple-membership.md index c3c828c86..55998d359 100644 --- a/docs/adr/0003-relational-event-multiple-membership.md +++ b/docs/adr/0003-relational-event-multiple-membership.md @@ -1,6 +1,7 @@ # ADR 0003 — Relational event ontology and time-varying multiple membership **Decision status:** Accepted +**Implementation maturity:** partial — membership network and event mention/instance separation implemented-main; typed relation graph with forward-only transitions implemented-main; IPO event-time order in `outcome_order` on the active PR; multilevel estimators and persistence remain accepted-target **Implementation maturity:** partial — membership network and event mention/instance separation implemented-main; retrospective-reporting identity in `retrospective_edge` on the active PR; typed relation graph with forward-only transitions active-PR; multilevel estimators and persistence remain accepted-target **Implementation maturity:** partial — membership network and event mention/instance separation implemented-main; inferred-versus-observed identity in `inferred_status` on the active PR; typed relation graph with forward-only transitions active-PR; multilevel estimators and persistence remain accepted-target **Implementation maturity:** partial — membership network and event mention/instance separation implemented-main; evidential-vs-transition identity in `support_edge` on the active PR; typed relation graph with forward-only transitions implemented-main; multilevel estimators and persistence remain accepted-target diff --git a/docs/adr/README.md b/docs/adr/README.md index 93dcd2c02..7e20f6220 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -7,6 +7,8 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | ADR | Decision | Decision status | Implementation maturity | Clarification / supersession | |---|---|---|---|---| | [0001](0001-rust-first-modular-msa.md) | Rust-first numerical core and CPU `f64` reference | Accepted | partial | ADR 0011 owns cross-service/MSA authority; 0001 retains numerical/backend authority. | +| [0002](0002-six-clock-temporal-semantics.md) | Six-clock temporal semantics and fail-closed historical leakage prevention | Accepted | partial | Typed clocks/intervals are implemented-main via `temporal_core`; input-process-outcome event-time order is `outcome_order` on the active PR. Remaining clock-identity and split enforcement stay accepted-target. | +| [0003](0003-relational-event-multiple-membership.md) | Relational event ontology and time-varying cross-classified multiple membership | Accepted | partial | Weighted time-varying membership network/roles and the forward-transition graph are implemented-main; IPO event-time order is `outcome_order` on the active PR; full multilevel estimators and persistence remain accepted-target. ADR 0016 owns event-intelligence tasks. | | [0002](0002-six-clock-temporal-semantics.md) | Six-clock temporal semantics and fail-closed historical leakage prevention | Accepted | partial | Typed clocks/intervals are implemented-main via `temporal_core`; retrospective-reporting identity is `retrospective_edge` on the active PR. Later graph/split enforcement remains target work. | | [0003](0003-relational-event-multiple-membership.md) | Relational event ontology and time-varying cross-classified multiple membership | Accepted | partial | Weighted time-varying membership network/roles are implemented-main (PR #12); retrospective-reporting identity is `retrospective_edge` on the active PR; full multilevel estimators and persistence remain accepted-target. ADR 0016 owns event-intelligence tasks. | | [0002](0002-six-clock-temporal-semantics.md) | Six-clock temporal semantics and fail-closed historical leakage prevention | Accepted | active-PR | Unmerged PR #8 is the canonical Task 3 replacement implementing typed clocks/intervals against the current protected-main lineage; conflicted PR #5 is superseded lineage. Later graph/split enforcement remains target work. | diff --git a/docs/research/outcome-order-identity.md b/docs/research/outcome-order-identity.md new file mode 100644 index 000000000..df9020123 --- /dev/null +++ b/docs/research/outcome-order-identity.md @@ -0,0 +1,33 @@ +# Input-process-outcome edges keep event-time order (doctoring) + +## Scope + +`outcome_order` keeps `input_to` and `process_to` forward in event-time +rank and keeps `outcome_of` out of the transition vocabulary. Recovery +is the computed share of recovered kinds that match known truth. + +This slice does not persist the graph, allocate migration `0008`, or +replace `relation_graph`, `citation_edge`, `translation_edge`, or +`retrospective_edge`. Event-time ranks are opaque ordinals, not clock +identities. + +## Authority + +### Normative TEPP contract + +- `docs/adr/0002-six-clock-temporal-semantics.md` — forward + state-transition and input-process-outcome edges never move backward + in event time; provenance edges may point to the past but never + become reverse state transitions. +- `docs/adr/0003-relational-event-multiple-membership.md` — typed + relations distinguish transition from provenance. + +### Supporting literature + +Allen (1983) classifies interval relations; it does **not** authorize +a later outcome to precede its input, or `outcome_of` to become +`input_to`. + +Allen, J. F. (1983). Maintaining knowledge about temporal intervals. +*Communications of the ACM, 26*(11), 832–843. +https://doi.org/10.1145/182.358434 diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 0d2147d2b..0e7777bd8 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -79,7 +79,13 @@ 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. Retrospective reporting may point at earlier event time; it is not a state transition and not a translation (Allen, 1983). +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. Input→process→outcome transitions require a strict event-time partial order; `outcome_of` may point at an earlier producer and is not a reverse state transition (Allen, 1983). + +## Input-process-outcome order + +Allen, J. F. (1983). Maintaining knowledge about temporal intervals. *Communications of the ACM, 26*(11), 832–843. https://doi.org/10.1145/182.358434 + +Allen (1983) classifies interval relations; it does **not** authorize treating a later outcome as an earlier input, nor treating `outcome_of` provenance as `input_to` or `process_to`. ## Unicode, language tags, and multilingual structure diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index 6c082a1e5..ecae7b989 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -20,6 +20,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | | Multiple membership | `membership_core` | partial | nested ICC + non-nested refusal | unit + ESS + nested ICC recovery | Task 7 / PR #12 + #25 + this increment | | Forward transition DAG | `relation_graph` | implemented-main | — | unit + cycle rejection | Task 6 / PR #14 | +| Input-process-outcome event-time order | `outcome_order` | accepted-target | active PR | refuse reverse/uncertain IPO order + outcome_of-is-not-transition + recovery vs input collapse | ADR 0002/0003 | | Retrospective reporting identity | `retrospective_edge` | accepted-target | active PR | refuse retrospective-as-transition/translation + recovery vs forward collapse | ADR 0002/0003 | | Inferred-versus-observed promotion | `inferred_status` | accepted-target | active PR | refuse inferred-as-observed/transition + recovery vs observed collapse | ADR 0003 | | Evidential-vs-transition gate | `support_edge` | active-PR | this PR | recovered kind rate vs support collapse | ADR 0002/0003 | diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index 4558edd73..57867dbb5 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -23,6 +23,7 @@ "tepp_simulation", "validation_core", "tepp_api", + "outcome_order", "retrospective_edge", "payload_bound", "inferred_status",