Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ TEPP stores event/valid time, assertion time, document time, system time, availa
\operatorname{available\_time}(d) \leq \operatorname{knowledge\_cutoff}.
\]

When availability is an interval, every possible instant in that interval must satisfy the inequality. Unknown or open-ended availability that can extend past the cutoff fails closed; event time and document time cannot substitute for availability.

Forward transition edges require a temporally valid partial order. Retrospective, revision, translation, citation, support, and contradiction relations retain their direction and provenance but do not create reverse state transitions.

## Measurement invariants
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

### Added

- `temporal_core` interval-aware historical eligibility: `evaluate_historical_eligibility` admits an `AvailableTime` interval only when every possible availability instant is at or before `KnowledgeCutoff`; unknown and open-ended upper availability fail closed, and event/document time cannot be substituted.
- `tepp_api` naruon live loopback HTTP/1.1 listener: `serve_one` installs a read/write deadline, requires a loopback `Host`, refuses `Transfer-Encoding` and NIM/proxy credential headers, parses `knowledge_cutoff` as RFC 3339 and refuses a future cutoff, keys analysis-run idempotency by tenant plus key, and proves both analysis-run and export POSTs over a real `TcpStream`. Not a production TLS/`$PORT` service (ADR 0011).
- `tepp_api` adaptive orchestration router (ADR 0010): versioned `direct`/`verify`/`committee`/`conductor`/`abstain` selection from CPU `f64` risk, ambiguity, evidence, and token-budget inputs; recorded stages, recursion, decomposition, access lists, and role-specific reasoning effort; fail-closed document-controlled policy/access/credentials; LLM plans remain proposals under deterministic statistical authority; comparable-budget ablation requires a direct baseline; credential-free contextual-orchestrator binding. Live NIM HTTP remains accepted-target.
- `tepp_api` purpose-bound provider-payload minimization: time-bounded `PurposeGrant` evaluation, fail-closed expired/not-yet-valid/inverted/cross-tenant/impossible-calendar denial, semantic UTC calendar validation, refusal to copy identity mappings into model-provider payloads or ordinary logs, preservation of opaque analytical identifiers and membership roles (no blanket PII mask), a separately authorized scientific re-identification path, and an internally bound FIPS 180-4 SHA-256 audit digest appended through `ReidentificationAuditSink` before disclosure.
Expand Down
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| Foundation implementation plan | [`docs/superpowers/plans/2026-08-05-temporal-event-foundation.md`](docs/superpowers/plans/2026-08-05-temporal-event-foundation.md) |
| Foundation validation ledger | [`docs/validation/temporal-event-foundation.md`](docs/validation/temporal-event-foundation.md) |
| Standards and APA 7 literature | [`docs/research/standards-and-literature.md`](docs/research/standards-and-literature.md) |
| Interval cutoff eligibility doctoring | [`docs/research/interval-cutoff-eligibility.md`](docs/research/interval-cutoff-eligibility.md) |
| Governance | [`GOVERNANCE.md`](GOVERNANCE.md) |
| Agent development rules | [`AGENTS.md`](AGENTS.md) |
| Agent context | [`CLAUDE.md`](CLAUDE.md) |
Expand Down
79 changes: 79 additions & 0 deletions crates/temporal_core/src/eligibility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! Interval-aware historical eligibility against a knowledge cutoff.

use crate::{
AvailableTime, KnowledgeCutoff, TemporalBoundary, TemporalCertainty, TemporalError,
TemporalInterval,
};

/// Decide whether an availability interval is fully eligible under `knowledge_cutoff`.
///
/// Evidence may enter a historical analysis only when every possible
/// availability instant is at or before the cutoff. Unknown availability and
/// open-ended upper bounds fail closed because they can extend past the cutoff.
/// Event time and document time cannot be substituted: the interval is typed as
/// [`AvailableTime`].
///
/// ```compile_fail,E0308
/// use temporal_core::{
/// EventTime, KnowledgeCutoff, TemporalInterval, TemporalPrecision,
/// evaluate_historical_eligibility,
/// };
///
/// let event = TemporalInterval::exact(
/// EventTime::parse_rfc3339("2026-01-01T00:00:00Z")?,
/// TemporalPrecision::Second,
/// )?;
/// let cutoff = KnowledgeCutoff::parse_rfc3339("2026-06-01T00:00:00Z")?;
/// evaluate_historical_eligibility(&event, &cutoff)?;
/// # Ok::<(), temporal_core::TemporalError>(())
/// ```
///
/// # Errors
///
/// Returns [`TemporalError::UncertainAvailability`] when the interval cannot
/// prove an upper bound, or [`TemporalError::IneligibleAtCutoff`] when the
/// latest possible availability is after the cutoff.
pub fn evaluate_historical_eligibility(
availability: &TemporalInterval<AvailableTime>,
knowledge_cutoff: &KnowledgeCutoff,
) -> Result<(), TemporalError> {
if matches!(availability.certainty(), TemporalCertainty::Unknown) {
return Err(TemporalError::UncertainAvailability);
}

let latest = match availability.upper() {
TemporalBoundary::Unbounded => return Err(TemporalError::UncertainAvailability),
TemporalBoundary::Included(value) => value.instant().as_nanosecond(),
TemporalBoundary::Excluded(value) => value.instant().as_nanosecond() - 1,
};
if latest <= knowledge_cutoff.instant().as_nanosecond() {
Ok(())
} else {
Err(TemporalError::IneligibleAtCutoff)
}
}

#[cfg(test)]
mod tests {
use super::evaluate_historical_eligibility;
use crate::{
AvailableTime, KnowledgeCutoff, TemporalBoundary, TemporalInterval, TemporalPrecision,
};

fn available(stamp: &str) -> AvailableTime {
AvailableTime::parse_rfc3339(stamp).expect("available")
}

#[test]
fn excluded_upper_one_nanosecond_after_cutoff_is_eligible() {
let cutoff = KnowledgeCutoff::parse_rfc3339("2026-06-01T00:00:00Z").expect("cutoff");
let just_after = available("2026-06-01T00:00:00.000000001Z");
let interval = TemporalInterval::bounded(
TemporalBoundary::Unbounded,
TemporalBoundary::Excluded(just_after),
TemporalPrecision::Nanosecond,
)
.expect("interval");
assert_eq!(evaluate_historical_eligibility(&interval, &cutoff), Ok(()));
}
}
6 changes: 6 additions & 0 deletions crates/temporal_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ pub enum TemporalError {
UnsupportedWireVersion,
/// A JSON wire record declared a different nominal clock type.
ClockTypeMismatch,
/// Availability is unknown or open-ended and can extend past the cutoff.
UncertainAvailability,
/// The latest possible availability instant is after the knowledge cutoff.
IneligibleAtCutoff,
}

impl fmt::Display for TemporalError {
Expand All @@ -40,6 +44,8 @@ impl fmt::Display for TemporalError {
Self::InvalidWirePayload => "invalid temporal wire payload",
Self::UnsupportedWireVersion => "unsupported temporal wire version",
Self::ClockTypeMismatch => "temporal clock type mismatch",
Self::UncertainAvailability => "uncertain availability fails closed at cutoff",
Self::IneligibleAtCutoff => "availability is ineligible at knowledge cutoff",
};
formatter.write_str(message)
}
Expand Down
7 changes: 7 additions & 0 deletions crates/temporal_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@
//! relations. Relation sets support inverse and complete composition, while a
//! resource-bounded path-consistency reasoner preserves direct assertions,
//! derived narrowing, and conservative supporting-assertion provenance.
//!
//! Historical eligibility requires the entire [`AvailableTime`] interval to
//! fall at or before [`KnowledgeCutoff`]. Unknown or open-ended availability
//! fails closed and cannot be replaced by event or document time.

mod clock;
mod eligibility;
mod error;
mod instant;
mod interval;
Expand All @@ -48,6 +53,8 @@ pub use clock::KnowledgeCutoff;
pub use clock::SystemTime;
/// A sealed nominal TEPP clock over one absolute instant representation.
pub use clock::TemporalClock;
/// Decide whether an availability interval is fully eligible at a cutoff.
pub use eligibility::evaluate_historical_eligibility;
/// A fail-closed temporal-domain validation error.
pub use error::TemporalError;
/// An absolute UTC instant represented to nanosecond precision.
Expand Down
128 changes: 128 additions & 0 deletions crates/temporal_core/tests/eligibility_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! Interval-aware historical eligibility against a knowledge cutoff.

use temporal_core::{
AvailableTime, KnowledgeCutoff, TemporalBoundary, TemporalError, TemporalInterval,
TemporalPrecision, evaluate_historical_eligibility,
};

fn available(stamp: &str) -> AvailableTime {
AvailableTime::parse_rfc3339(stamp).expect("available")
}

fn cutoff(stamp: &str) -> KnowledgeCutoff {
KnowledgeCutoff::parse_rfc3339(stamp).expect("cutoff")
}

fn exact(stamp: &str) -> TemporalInterval<AvailableTime> {
TemporalInterval::exact(available(stamp), TemporalPrecision::Second).expect("exact")
}

/// Independently compute the latest representable availability nanosecond.
///
/// The production gate must agree with this comparison: eligible iff the
/// latest possible availability instant is `<=` the cutoff. Unknown or
/// open-ended availability has no latest instant and must fail closed.
fn latest_possible_ns(
availability: &TemporalInterval<AvailableTime>,
) -> Result<i128, TemporalError> {
if !availability.is_known() {
return Err(TemporalError::UncertainAvailability);
}
match availability.upper() {
TemporalBoundary::Unbounded => Err(TemporalError::UncertainAvailability),
TemporalBoundary::Included(value) => Ok(value.instant().as_nanosecond()),
TemporalBoundary::Excluded(value) => Ok(value.instant().as_nanosecond() - 1),
}
}

fn expected_decision(
availability: &TemporalInterval<AvailableTime>,
knowledge_cutoff: &KnowledgeCutoff,
) -> Result<(), TemporalError> {
match latest_possible_ns(availability) {
Ok(latest) if latest <= knowledge_cutoff.instant().as_nanosecond() => Ok(()),
Ok(_) => Err(TemporalError::IneligibleAtCutoff),
Err(error) => Err(error),
}
}

#[test]
fn computed_latest_instant_agrees_with_the_eligibility_gate() {
let cut = cutoff("2026-06-01T00:00:00Z");
let closed = |start: &str, end: &str| {
TemporalInterval::bounded(
TemporalBoundary::Included(available(start)),
TemporalBoundary::Included(available(end)),
TemporalPrecision::Second,
)
.expect("closed")
};
let upper_open = |end: &str| {
TemporalInterval::bounded(
TemporalBoundary::Unbounded,
TemporalBoundary::Excluded(available(end)),
TemporalPrecision::Second,
)
.expect("upper open")
};
let lower_open = |start: &str| {
TemporalInterval::bounded(
TemporalBoundary::Included(available(start)),
TemporalBoundary::Unbounded,
TemporalPrecision::Second,
)
.expect("lower open")
};

let cases = [
exact("2026-06-01T00:00:00Z"),
exact("2026-05-01T00:00:00Z"),
exact("2026-06-01T00:00:01Z"),
closed("2026-01-01T00:00:00Z", "2026-06-01T00:00:00Z"),
closed("2026-01-01T00:00:00Z", "2026-06-01T00:00:01Z"),
upper_open("2026-06-01T00:00:00Z"),
upper_open("2026-06-01T00:00:00.000000001Z"),
upper_open("2026-06-01T00:00:00.000000002Z"),
lower_open("2026-01-01T00:00:00Z"),
TemporalInterval::<AvailableTime>::unknown(),
];

for availability in cases {
assert_eq!(
evaluate_historical_eligibility(&availability, &cut),
expected_decision(&availability, &cut)
);
}
}

#[test]
fn unknown_and_open_ended_availability_fail_closed() {
let cut = cutoff("2026-06-01T00:00:00Z");
assert_eq!(
evaluate_historical_eligibility(&TemporalInterval::unknown(), &cut),
Err(TemporalError::UncertainAvailability)
);
let open_upper = TemporalInterval::bounded(
TemporalBoundary::Included(available("2026-01-01T00:00:00Z")),
TemporalBoundary::Unbounded,
TemporalPrecision::Day,
)
.expect("open upper");
assert_eq!(
evaluate_historical_eligibility(&open_upper, &cut),
Err(TemporalError::UncertainAvailability)
);
}

#[test]
fn exact_availability_after_cutoff_is_ineligible() {
let cut = cutoff("2026-06-01T00:00:00Z");
assert_eq!(
evaluate_historical_eligibility(&exact("2026-06-01T00:00:00Z"), &cut),
Ok(())
);
assert_eq!(
evaluate_historical_eligibility(&exact("2026-06-01T00:00:01Z"), &cut),
Err(TemporalError::IneligibleAtCutoff)
);
}
8 changes: 8 additions & 0 deletions crates/temporal_core/tests/error_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ fn every_temporal_error_has_a_stable_content_redacting_message() {
TemporalError::ClockTypeMismatch,
"temporal clock type mismatch",
),
(
TemporalError::UncertainAvailability,
"uncertain availability fails closed at cutoff",
),
(
TemporalError::IneligibleAtCutoff,
"availability is ineligible at knowledge cutoff",
),
];

for (error, expected) in cases {
Expand Down
1 change: 1 addition & 0 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The full APA 7th standards/literature register remains `docs/research/standards-
| 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; multilevel estimators remaining | partial |
| leakage-safe availability/cutoff snapshots | PRD; ADR 0002/0013 | `corpus_split` on protected main | implemented-main |
| interval-aware historical eligibility (`available_time` fully ≤ cutoff) | ADR 0002 | `temporal_core` `evaluate_historical_eligibility` on the active PR; unknown/open-ended availability fails closed | active-PR |
| recovery metrics (RMSE, bias, coverage, graph, temporal order, Monte Carlo SE gates) | PRD; Test Strategy; ADR 0007/0014 | `validation_core` on protected main (PR #19); SE-aware Monte Carlo gates included | implemented-main |
| PostgreSQL bitemporal/lineage persistence | ADR 0013; Architecture/ERD | `persistence_postgres` migration contracts, in-memory adapters, live SQL session/document SQL port, tenant RLS (`0002` + session GUC/role helpers), `DATABASE_URL` SQLx gate, optional `live-sqlx` `PgPool` driver, exact-head live PostgreSQL CI with isolation proof, append-only immutability triggers (`0004`), temporal interval ordering CHECKs (`0005`), typed membership assignment (`0006` implemented-main), event-relation/mention/instance SQL (#37–#39 implemented-main), source-artifact SQL (#40 implemented-main), audit-event SQL (#41 implemented-main), concurrent document-write stress (#43 implemented-main), backup/restore integrity revalidation (active PR); remaining physical ERD constraints | partial |
| known-truth temporal/event simulation manifests | PRD; TRD; Test Strategy | `tepp_simulation` on protected main; recovery metrics in `validation_core` | implemented-main |
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0002-six-clock-temporal-semantics.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ADR 0002 — Six-clock temporal semantics and leakage prevention

**Decision status:** Accepted
**Implementation maturity:** active-PRunmerged PR #8 is the canonical replacement implementing typed clocks/intervals against the current protected-main lineage; superseded/conflicted PR #5 is historical lineage only; downstream transition/split enforcement remains accepted-target
**Implementation maturity:** partialtyped clocks/intervals and Allen path-consistency are implemented-main (PR #8/#9); interval-aware historical eligibility (`AvailableTime` interval fully ≤ `KnowledgeCutoff`, unknown/open-ended availability fail closed) is active-PR; remaining downstream split/persistence enforcement remains accepted-target
**Date:** 2026-08-05
**Supersedes:** None. ADR 0013 owns persistence/split representation; ADR 0016 owns event-intelligence reasoning above these temporal primitives.

Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0009-purpose-bound-pii-governance.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0010-adaptive-llm-orchestration.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/adr/0011-standalone-modular-msa-boundary.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading
Loading