Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| `system_clock` | system time cannot be replaced by event, assertion, document, available, or cutoff time |

No crate exposes placeholder production behavior in Task 1. This prevents an
empty façade from becoming a de facto public API before its invariants and tests
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Comment thread
seonghobae marked this conversation as resolved.
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

- `system_clock` identity gate: event, assertion, document, availability, and knowledge-cutoff time cannot stand in for system time; recovered system stamps match known truth at a higher computed rate than treating every stamp as event time (ADR 0002).
- `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).
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/tepp_simulation",
"crates/validation_core",
"crates/tepp_api",
"crates/system_clock",
]
default-members = [
"crates/evidence_core",
Expand All @@ -23,6 +24,7 @@ default-members = [
"crates/tepp_simulation",
"crates/validation_core",
"crates/tepp_api",
"crates/system_clock",
]

[workspace.package]
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ implemented in Rust.
## Current implementation state

This branch establishes the Task 1 Rust workspace and quality-gate foundation.
The ten bounded crates compile independently but intentionally expose no
The eleven bounded crates compile independently but intentionally expose no
placeholder production APIs. Domain behavior begins in Task 2 with immutable
evidence identifiers and source records.

Expand All @@ -22,6 +22,7 @@ crates/corpus_split
crates/tepp_simulation
crates/validation_core
crates/tepp_api
crates/system_clock
```

## Local verification
Expand Down
17 changes: 17 additions & 0 deletions crates/system_clock/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "system_clock"
description = "System time cannot be replaced by event, assertion, document, available, or cutoff 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
145 changes: 145 additions & 0 deletions crates/system_clock/src/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! Clock-family identity for system stamps.

use crate::SystemClockError;

/// Closed vocabulary of clocks that must not be confused with system time.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClockFamily {
/// Event/valid time.
EventTime,
/// Assertion time.
AssertionTime,
/// Document creation or revision time.
DocumentTime,
/// Availability time.
AvailableTime,
/// Knowledge-cutoff time.
CutoffTime,
/// System/record time.
SystemTime,
}

/// Return whether a stamp is on the system clock.
///
/// # Errors
///
/// This function is infallible for the closed vocabulary and exists to keep
/// the public comparison surface explicit.
#[allow(clippy::unnecessary_wraps)]
pub fn stamp_is_system(family: ClockFamily) -> Result<bool, SystemClockError> {
Ok(matches!(family, ClockFamily::SystemTime))
}

/// Refuse to treat event time as system time.
///
/// # Errors
///
/// Always returns [`SystemClockError::EventTimeIsNotSystemTime`].
pub fn refuse_event_time_as_system() -> Result<(), SystemClockError> {
Err(SystemClockError::EventTimeIsNotSystemTime)
}

/// Refuse to treat assertion time as system time.
///
/// # Errors
///
/// Always returns [`SystemClockError::AssertionTimeIsNotSystemTime`].
pub fn refuse_assertion_time_as_system() -> Result<(), SystemClockError> {
Err(SystemClockError::AssertionTimeIsNotSystemTime)
}

/// Refuse to treat document time as system time.
///
/// # Errors
///
/// Always returns [`SystemClockError::DocumentTimeIsNotSystemTime`].
pub fn refuse_document_time_as_system() -> Result<(), SystemClockError> {
Err(SystemClockError::DocumentTimeIsNotSystemTime)
}

/// Refuse to treat availability time as system time.
///
/// # Errors
///
/// Always returns [`SystemClockError::AvailableTimeIsNotSystemTime`].
pub fn refuse_available_time_as_system() -> Result<(), SystemClockError> {
Err(SystemClockError::AvailableTimeIsNotSystemTime)
}

/// Refuse to treat knowledge-cutoff time as system time.
///
/// # Errors
///
/// Always returns [`SystemClockError::CutoffTimeIsNotSystemTime`].
pub fn refuse_cutoff_time_as_system() -> Result<(), SystemClockError> {
Err(SystemClockError::CutoffTimeIsNotSystemTime)
}
Comment thread
seonghobae marked this conversation as resolved.

/// Fraction of recovered system-clock flags that match known truth.
///
/// # Errors
///
/// Returns [`SystemClockError::InvalidSystemPayload`] when either slice is
/// empty or the lengths differ.
pub fn identity_recovery_rate(truth: &[bool], decided: &[bool]) -> Result<f64, SystemClockError> {
if truth.is_empty() || truth.len() != decided.len() {
return Err(SystemClockError::InvalidSystemPayload);
}
let mut matches = 0_u32;
for (truth_flag, decided_flag) in truth.iter().zip(decided) {
if truth_flag == decided_flag {
matches += 1;
}
}
Ok(f64::from(matches) / truth.len() as f64)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

#[cfg(test)]
mod tests {
use super::{
ClockFamily, identity_recovery_rate, refuse_assertion_time_as_system,
refuse_available_time_as_system, refuse_cutoff_time_as_system,
refuse_document_time_as_system, refuse_event_time_as_system, stamp_is_system,
};
use crate::SystemClockError;

#[test]
fn local_branches_cover_families_and_payloads() {
assert!(stamp_is_system(ClockFamily::SystemTime).expect("system"));
assert!(!stamp_is_system(ClockFamily::EventTime).expect("event"));
assert!(!stamp_is_system(ClockFamily::AssertionTime).expect("assertion"));
assert!(!stamp_is_system(ClockFamily::DocumentTime).expect("document"));
assert!(!stamp_is_system(ClockFamily::AvailableTime).expect("available"));
assert!(!stamp_is_system(ClockFamily::CutoffTime).expect("cutoff"));
assert_eq!(
refuse_event_time_as_system(),
Err(SystemClockError::EventTimeIsNotSystemTime)
);
assert_eq!(
refuse_assertion_time_as_system(),
Err(SystemClockError::AssertionTimeIsNotSystemTime)
);
assert_eq!(
refuse_document_time_as_system(),
Err(SystemClockError::DocumentTimeIsNotSystemTime)
);
assert_eq!(
refuse_available_time_as_system(),
Err(SystemClockError::AvailableTimeIsNotSystemTime)
);
assert_eq!(
refuse_cutoff_time_as_system(),
Err(SystemClockError::CutoffTimeIsNotSystemTime)
);
let matched = identity_recovery_rate(&[true], &[true]).expect("rate");
assert!((matched - 1.0).abs() < f64::EPSILON);
assert_eq!(
identity_recovery_rate(&[], &[]),
Err(SystemClockError::InvalidSystemPayload)
);
assert_eq!(
identity_recovery_rate(&[true], &[]),
Err(SystemClockError::InvalidSystemPayload)
);
}
}
74 changes: 74 additions & 0 deletions crates/system_clock/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Fail-closed system-clock errors.

use std::fmt;

/// A fail-closed system-clock error.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SystemClockError {
/// Event time was treated as system time.
EventTimeIsNotSystemTime,
/// Assertion time was treated as system time.
AssertionTimeIsNotSystemTime,
/// Document time was treated as system time.
DocumentTimeIsNotSystemTime,
/// Availability time was treated as system time.
AvailableTimeIsNotSystemTime,
/// Knowledge-cutoff time was treated as system time.
CutoffTimeIsNotSystemTime,
/// A recovery slice was empty or length-mismatched.
InvalidSystemPayload,
}

impl fmt::Display for SystemClockError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::EventTimeIsNotSystemTime => "event time is not system time",
Self::AssertionTimeIsNotSystemTime => "assertion time is not system time",
Self::DocumentTimeIsNotSystemTime => "document time is not system time",
Self::AvailableTimeIsNotSystemTime => "availability time is not system time",
Self::CutoffTimeIsNotSystemTime => "knowledge cutoff is not system time",
Self::InvalidSystemPayload => "invalid system-clock payload",
};
formatter.write_str(message)
}
}

impl std::error::Error for SystemClockError {}

#[cfg(test)]
mod tests {
use super::SystemClockError;

#[test]
fn error_messages_are_stable() {
for (error, message) in [
(
SystemClockError::EventTimeIsNotSystemTime,
"event time is not system time",
),
(
SystemClockError::AssertionTimeIsNotSystemTime,
"assertion time is not system time",
),
(
SystemClockError::DocumentTimeIsNotSystemTime,
"document time is not system time",
),
(
SystemClockError::AvailableTimeIsNotSystemTime,
"availability time is not system time",
),
(
SystemClockError::CutoffTimeIsNotSystemTime,
"knowledge cutoff is not system time",
),
(
SystemClockError::InvalidSystemPayload,
"invalid system-clock payload",
),
] {
assert_eq!(error.to_string(), message);
}
}
}
29 changes: 29 additions & 0 deletions crates/system_clock/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![allow(clippy::cast_precision_loss)]
//! System time cannot be replaced by the other TEPP clocks.
//!
//! System/record time is when TEPP recorded a change. Event, assertion,
//! document, available, and cutoff times are not substitutes (ADR 0002).

mod clock;
mod error;

/// Closed vocabulary of clocks that must not be confused with system time.
pub use clock::ClockFamily;
/// Fraction of recovered system-clock flags that match known truth.
pub use clock::identity_recovery_rate;
/// Refuse to treat assertion time as system time.
pub use clock::refuse_assertion_time_as_system;
/// Refuse to treat availability time as system time.
pub use clock::refuse_available_time_as_system;
/// Refuse to treat knowledge-cutoff time as system time.
pub use clock::refuse_cutoff_time_as_system;
/// Refuse to treat document time as system time.
pub use clock::refuse_document_time_as_system;
/// Refuse to treat event time as system time.
pub use clock::refuse_event_time_as_system;
/// Return whether a stamp is on the system clock.
pub use clock::stamp_is_system;
/// Fail-closed system-clock errors.
pub use error::SystemClockError;
7 changes: 7 additions & 0 deletions crates/system_clock/tests/crate_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Integration contract for the `system_clock` package identity.

#[test]
fn package_identity_is_stable() {
let observed = std::hint::black_box(env!("CARGO_PKG_NAME"));
assert_eq!(observed, "system_clock");
}
Loading
Loading