Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 |
| `revision_order` | later document revisions must have later system 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
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

- `revision_order` system-time gate: a higher document revision number cannot carry earlier or equal system time; recovered order flags match known truth at a higher computed rate than accepting every pair (ADR 0002/0013).
- `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/revision_order",
]
default-members = [
"crates/evidence_core",
Expand All @@ -23,6 +24,7 @@ default-members = [
"crates/tepp_simulation",
"crates/validation_core",
"crates/tepp_api",
"crates/revision_order",
]

[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/revision_order
```

## Local verification
Expand Down
17 changes: 17 additions & 0 deletions crates/revision_order/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "revision_order"
description = "Later document revisions cannot move backward in system 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
48 changes: 48 additions & 0 deletions crates/revision_order/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Fail-closed revision-order errors.

use std::fmt;

/// A fail-closed revision-order error.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum RevisionOrderError {
/// A later revision did not have a later system time.
SystemTimeDidNotIncrease,
/// A revision number or recovery slice was empty, zero, or mismatched.
InvalidRevisionPayload,
}

impl fmt::Display for RevisionOrderError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::SystemTimeDidNotIncrease => {
"later document revisions must have later system time"
}
Self::InvalidRevisionPayload => "invalid revision-order payload",
};
formatter.write_str(message)
}
}

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

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

#[test]
fn error_messages_are_stable() {
for (error, message) in [
(
RevisionOrderError::SystemTimeDidNotIncrease,
"later document revisions must have later system time",
),
(
RevisionOrderError::InvalidRevisionPayload,
"invalid revision-order payload",
),
] {
assert_eq!(error.to_string(), message);
}
}
}
21 changes: 21 additions & 0 deletions crates/revision_order/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![allow(clippy::cast_precision_loss)]
//! Later document revisions cannot move backward in system time.
//!
//! A higher revision number is a later assertion about the same document
//! identity. Its system time must strictly increase (ADR 0002/0013).

mod error;
mod revision;

/// Fail-closed revision-order errors.
pub use error::RevisionOrderError;
/// One document revision with a positive revision number and system time.
pub use revision::DocumentRevision;
/// Fraction of recovered order flags that match known truth.
pub use revision::order_recovery_rate;
/// Refuse a later revision whose system time did not increase.
pub use revision::refuse_nonincreasing_system_time;
/// Return whether a later revision has a later system time.
pub use revision::revisions_are_increasing;
138 changes: 138 additions & 0 deletions crates/revision_order/src/revision.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Document revisions stamped with system time.

use crate::RevisionOrderError;

/// One document revision with a positive revision number and system time.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DocumentRevision {
revision_number: u32,
system_time_seconds: i64,
}

impl DocumentRevision {
/// Construct a revision whose number is at least one.
///
/// # Errors
///
/// Returns [`RevisionOrderError::InvalidRevisionPayload`] when
/// `revision_number` is zero.
pub const fn new(
revision_number: u32,
system_time_seconds: i64,
) -> Result<Self, RevisionOrderError> {
if revision_number == 0 {
return Err(RevisionOrderError::InvalidRevisionPayload);
}
Ok(Self {
revision_number,
system_time_seconds,
})
}

/// Positive revision number.
#[must_use]
pub const fn revision_number(self) -> u32 {
self.revision_number
}

/// System/record time in seconds.
#[must_use]
pub const fn system_time_seconds(self) -> i64 {
self.system_time_seconds
}
}

/// Return whether `later` has a greater revision number and later system time.
///
/// # Errors
///
/// Returns [`RevisionOrderError::InvalidRevisionPayload`] when `later` is not
/// a strictly greater revision number than `earlier`.
pub fn revisions_are_increasing(
earlier: DocumentRevision,
later: DocumentRevision,
) -> Result<bool, RevisionOrderError> {
if later.revision_number <= earlier.revision_number {
return Err(RevisionOrderError::InvalidRevisionPayload);
}
Ok(later.system_time_seconds > earlier.system_time_seconds)
}
Comment on lines +51 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: System-time gate is consistent with bitemporal revision semantics

The gate refuses a later revision unless its system_time_seconds strictly increases (revision.rs, 55-58). This is transaction/system-time ordering only and does not constrain event/valid time, so it remains consistent with AGENTS.md #5 (revision edges may point to the past in event time). The strict > comparison also correctly rejects equal system times, matching the PR's stated 'earlier or equal' refusal. No bug; noting because the distinction between system time and event time is the crux of correctness here.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +45 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Doc comment on revisions_are_increasing understates behavior

The doc for revisions_are_increasing at revision.rs says it returns whether later has "a greater revision number and later system time," but the function only returns the system-time comparison (revision.rs); the revision-number condition is enforced by returning an error, not folded into the boolean. This is a documentation/behavior wording mismatch, not a correctness bug, since callers get an Err when revision numbers are non-increasing rather than a false.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


/// Refuse a later revision whose system time did not increase.
///
/// # Errors
///
/// Returns revision-construction errors, or
/// [`RevisionOrderError::SystemTimeDidNotIncrease`] when the system times
/// are not strictly increasing.
pub fn refuse_nonincreasing_system_time(
earlier: DocumentRevision,
later: DocumentRevision,
) -> Result<(), RevisionOrderError> {
if revisions_are_increasing(earlier, later)? {
return Ok(());
}
Err(RevisionOrderError::SystemTimeDidNotIncrease)
}
Comment on lines +68 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: refuse_nonincreasing_system_time returns InvalidRevisionPayload for equal/backward revision numbers

refuse_nonincreasing_system_time propagates InvalidRevisionPayload (via revisions_are_increasing) when the later revision number is not strictly greater, rather than SystemTimeDidNotIncrease. This is documented ("Returns revision-construction errors, or SystemTimeDidNotIncrease...") and tested, so it is intentional, but callers should be aware that a same-numbered revision pair is rejected as an invalid payload, not as a time-order violation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +68 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Error-propagation branch in refuse_nonincreasing_system_time may be untested

In refuse_nonincreasing_system_time (revision.rs), the ? operator on revisions_are_increasing(...) has an implicit error-propagation branch that fires only when the two revision numbers are equal or reversed. Every test call (both the unit test at revision.rs and the integration tests at order_contract.rs) passes strictly increasing revision numbers, so that inner Err path is never exercised through this function. Given the repo's 100% branch-coverage gate (AGENTS.md #8), this is worth confirming against the nightly branch-coverage run; the author claims 12/12 branches, but LLVM branch coverage of the ? desugaring is worth double-checking. Not reported as a bug because it is a coverage/CI concern that will be caught by the pinned coverage gate, not a runtime defect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


/// Fraction of recovered order flags that match known truth.
///
/// # Errors
///
/// Returns [`RevisionOrderError::InvalidRevisionPayload`] when either slice
/// is empty or the lengths differ.
pub fn order_recovery_rate(truth: &[bool], decided: &[bool]) -> Result<f64, RevisionOrderError> {
if truth.is_empty() || truth.len() != decided.len() {
return Err(RevisionOrderError::InvalidRevisionPayload);
}
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)
}

#[cfg(test)]
mod tests {
use super::{
DocumentRevision, order_recovery_rate, refuse_nonincreasing_system_time,
revisions_are_increasing,
};
use crate::RevisionOrderError;

#[test]
fn local_branches_cover_order_and_payloads() {
let first = DocumentRevision::new(1, 10).expect("first");
let second = DocumentRevision::new(2, 20).expect("second");
assert_eq!(first.revision_number(), 1);
assert_eq!(first.system_time_seconds(), 10);
assert!(revisions_are_increasing(first, second).expect("increasing"));
refuse_nonincreasing_system_time(first, second).expect("ok");
let same_time = DocumentRevision::new(3, 20).expect("same");
assert!(!revisions_are_increasing(second, same_time).expect("flat"));
assert_eq!(
refuse_nonincreasing_system_time(second, same_time),
Err(RevisionOrderError::SystemTimeDidNotIncrease)
);
assert_eq!(
revisions_are_increasing(second, first),
Err(RevisionOrderError::InvalidRevisionPayload)
);
assert_eq!(
DocumentRevision::new(0, 1),
Err(RevisionOrderError::InvalidRevisionPayload)
);
let matched = order_recovery_rate(&[true], &[true]).expect("rate");
assert!((matched - 1.0).abs() < f64::EPSILON);
assert_eq!(
order_recovery_rate(&[], &[]),
Err(RevisionOrderError::InvalidRevisionPayload)
);
assert_eq!(
order_recovery_rate(&[true], &[]),
Err(RevisionOrderError::InvalidRevisionPayload)
);
}
}
7 changes: 7 additions & 0 deletions crates/revision_order/tests/crate_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Integration contract for the `revision_order` package identity.

#[test]
fn package_identity_is_stable() {
let observed = std::hint::black_box(env!("CARGO_PKG_NAME"));
assert_eq!(observed, "revision_order");
}
77 changes: 77 additions & 0 deletions crates/revision_order/tests/order_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Later revisions cannot carry earlier or equal system time.

use revision_order::{
DocumentRevision, RevisionOrderError, order_recovery_rate, refuse_nonincreasing_system_time,
revisions_are_increasing,
};

fn revision(number: u32, system: i64) -> DocumentRevision {
DocumentRevision::new(number, system).expect("revision")
}

#[test]
fn later_revisions_cannot_move_backward_in_system_time() {
let first = revision(1, 10);
let second = revision(2, 20);
let backward = revision(3, 15);
assert!(revisions_are_increasing(first, second).expect("increasing"));
refuse_nonincreasing_system_time(first, second).expect("ok");
assert!(!revisions_are_increasing(second, backward).expect("backward"));
assert_eq!(
refuse_nonincreasing_system_time(second, backward),
Err(RevisionOrderError::SystemTimeDidNotIncrease)
);
assert_eq!(
refuse_nonincreasing_system_time(second, revision(4, 20)),
Err(RevisionOrderError::SystemTimeDidNotIncrease)
);
}

#[test]
fn recovered_order_flags_match_known_truth_better_than_accepting_all() {
let pairs = [
(revision(1, 10), revision(2, 20)),
(revision(2, 20), revision(3, 15)),
(revision(3, 30), revision(4, 40)),
];
let truth = [true, false, true];
let recovered = [
revisions_are_increasing(pairs[0].0, pairs[0].1).expect("p0"),
revisions_are_increasing(pairs[1].0, pairs[1].1).expect("p1"),
revisions_are_increasing(pairs[2].0, pairs[2].1).expect("p2"),
];
let collapsed = [true, true, true];
let recovered_rate = order_recovery_rate(&truth, &recovered).expect("recovered");
let collapsed_rate = order_recovery_rate(&truth, &collapsed).expect("collapsed");
let expected = {
let mut matches = 0_u32;
for (truth_flag, decided_flag) in truth.iter().zip(recovered.iter()) {
if truth_flag == decided_flag {
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_invalid_revision_payloads_fail_closed() {
assert_eq!(
DocumentRevision::new(0, 10),
Err(RevisionOrderError::InvalidRevisionPayload)
);
assert_eq!(
order_recovery_rate(&[], &[]),
Err(RevisionOrderError::InvalidRevisionPayload)
);
assert_eq!(
order_recovery_rate(&[true], &[]),
Err(RevisionOrderError::InvalidRevisionPayload)
);
assert_eq!(
order_recovery_rate(&[true, false], &[true]),
Err(RevisionOrderError::InvalidRevisionPayload)
);
}
2 changes: 1 addition & 1 deletion docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The full APA 7th standards/literature register remains `docs/research/standards-
| 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 |
| 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 |
| PostgreSQL bitemporal/lineage persistence | ADR 0013; Architecture/ERD | `persistence_postgres` on protected main as before; `revision_order` later-revision system-time gate on the 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 |
| versioned service/API contracts and exports | PRD; API contract; ADR 0011/0013 | `tepp_api` analysis-run/export/JSON-LD/GraphML contracts on protected main (PR #21); HTTP service remaining accepted-target | partial |
| immutable split/run/reproducibility manifests | ADR 0013; ERD | `tepp_api` reproducibility manifest contract on protected main; `persistence_postgres` append-only SQL insert/lookup for `reproducibility_manifest`, `corpus_split_manifest`, `model_run`, and `model_artifact` (migration `0003`); full physical ERD constraints remaining | partial |
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio
| [0010](0010-adaptive-llm-orchestration.md) | Adaptive LLM orchestration and test-time compute | Accepted | partial | `tepp_api` router/ablation/orchestrator binding on the active PR; live NIM execution and production ablation evidence remain accepted-target. |
| [0011](0011-standalone-modular-msa-boundary.md) | Standalone operation and modular CWL MSA boundary | Accepted | partial | Owns cross-service persistence/credential/API authority; no direct cross-service application-table coupling. |
| [0012](0012-temporal-relational-shared-latent-topic-measurement.md) | Temporal Relational Shared-Latent Topic Measurement (TRSL-TM) | Accepted | accepted-target | Owns topic backend compatibility, global topic identity, method effects, K/model-selection prerequisites, and compositional topic coordinates. |
| [0013](0013-bitemporal-persistence-reproducibility-and-split-authority.md) | Bitemporal persistence, reproducibility manifests, and relation-aware split authority | Accepted | partial | Owns PostgreSQL adapter semantics, immutable run/split manifests, leakage-safe partitions, and recovery identity; optional `live-sqlx` `PgPool`, live PG CI, tenant RLS, and `0006` membership implemented-main; `0007` retention/deletion/legal-hold on the active PR; remaining physical ERD/backup accepted-target. |
| [0013](0013-bitemporal-persistence-reproducibility-and-split-authority.md) | Bitemporal persistence, reproducibility manifests, and relation-aware split authority | Accepted | partial | Owns PostgreSQL adapter semantics, immutable run/split manifests, leakage-safe partitions, and recovery identity; optional `live-sqlx` `PgPool`, live PG CI, tenant RLS, and `0006` membership implemented-main; `0007` retention/deletion/legal-hold implemented-main; document revision system-time order in `revision_order` is active on this PR; remaining physical ERD/backup accepted-target. |
| [0014](0014-scientific-claim-promotion-and-release-evidence.md) | Scientific claim promotion and release evidence authority | Accepted | partial | Separates design, implementation, scientific/product claim, and release authority; repository SBOM/provenance generator implemented, full release bundle remaining. |
| [0015](0015-autonomous-development-review-and-merge-authority.md) | Autonomous development, review, and merge authority separation | Accepted | active-PR | Separates model proposal, deterministic verification, publication, independent review, and merge/release authority. |
| [0016](0016-tdt-chronos-event-intelligence-boundary.md) | TDT, CHRONOS, and Event Ontology intelligence boundary | Accepted | accepted-target | Separates observed evidence, detection/tracking, prediction/schema inference, temporal consistency, and promoted transition authority. |
Expand Down
Loading
Loading