diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 00000000..d20c0fe4 --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ffe514db..5d5b236c 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 | +| `checkpoint_authority` | a model checkpoint is not the CPU `f64` estimator | 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c2e8dd..aecb431b 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 +- `checkpoint_authority` estimator gate: a model checkpoint remains an untrusted run artifact until identity, canonical `SHA-256`, and model-run provenance validate, and it cannot replace the CPU `f64` estimator or promote a scientific claim; recovered roles match known truth at a higher computed rate than collapsing every artifact to the estimator (ADR 0001/0014). +- `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. - `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. @@ -63,7 +65,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - Topic correlation, consensus clustering, TDT, CHRONOS, and evidence-grounded LLM interpretation requirements. - APA 7th research traceability, source archive manifests, ADRs, governance, security, and contribution contracts. - Hourly centralized PR-maintenance workflow and a documented requirement for a future credential-separated NVIDIA NIM/OpenCode product-development loop. -- Rust 1.97.1 virtual Cargo workspace with ten explicit modular foundation crates. +- Rust 1.97.1 virtual Cargo workspace with eleven explicit modular foundation crates. - Repository contract, public-rustdoc, line-coverage, and nightly branch-coverage gates. - Pinned `cargo-nextest` 0.9.140, `cargo-llvm-cov` 0.8.6, `cargo-deny` 0.19.7, and Coverage.py 7.15.2 quality tooling. - Task 1 architecture decision and workspace-foundation validation report. diff --git a/Cargo.lock b/Cargo.lock index fb502b9c..cfc9b1b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,10 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "checkpoint_authority" +version = "0.1.0" + [[package]] name = "corpus_split" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 92565940..00a87305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/checkpoint_authority", ] default-members = [ "crates/evidence_core", @@ -23,6 +24,7 @@ default-members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/checkpoint_authority", ] [workspace.package] diff --git a/README.md b/README.md index ae74015d..79c0bc9e 100644 --- a/README.md +++ b/README.md @@ -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. @@ -22,6 +22,7 @@ crates/corpus_split crates/tepp_simulation crates/validation_core crates/tepp_api +crates/checkpoint_authority ``` ## Local verification diff --git a/crates/checkpoint_authority/Cargo.toml b/crates/checkpoint_authority/Cargo.toml new file mode 100644 index 00000000..92b5c4a0 --- /dev/null +++ b/crates/checkpoint_authority/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "checkpoint_authority" +description = "A model checkpoint is not the CPU f64 estimator." +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/checkpoint_authority/src/authority.rs b/crates/checkpoint_authority/src/authority.rs new file mode 100644 index 00000000..b41774a1 --- /dev/null +++ b/crates/checkpoint_authority/src/authority.rs @@ -0,0 +1,201 @@ +//! Checkpoint artifacts versus the CPU `f64` estimator. + +use crate::CheckpointAuthorityError; + +/// Closed vocabulary of scientific-authority roles for a run artifact. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ArtifactRole { + /// The production CPU `f64` reference estimator. + CpuF64Estimator, + /// A serialized model checkpoint produced by a run. + ModelCheckpoint, +} + +impl ArtifactRole { + /// Return the stable wire role name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::CpuF64Estimator => "cpu_f64_estimator", + Self::ModelCheckpoint => "model_checkpoint", + } + } + + /// Parse a stable wire role name. + /// + /// # Errors + /// + /// Returns [`CheckpointAuthorityError::InvalidAuthorityPayload`] for + /// unrecognized names. + pub fn from_wire_name(name: &str) -> Result { + match name { + "cpu_f64_estimator" => Ok(Self::CpuF64Estimator), + "model_checkpoint" => Ok(Self::ModelCheckpoint), + _ => Err(CheckpointAuthorityError::InvalidAuthorityPayload), + } + } +} + +/// Identity, digest, and run provenance required to accept a checkpoint +/// as an artifact (never as the estimator). +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CheckpointOffer<'a> { + /// Opaque artifact identity assigned by the owning boundary. + pub artifact_identity: &'a str, + /// Canonical lowercase hex `SHA-256` of the checkpoint bytes. + pub content_digest: &'a str, + /// Model-run identity that produced the checkpoint. + pub model_run_identity: &'a str, +} + +/// Refuse to treat a checkpoint as the CPU `f64` estimator. +/// +/// # Errors +/// +/// Returns [`CheckpointAuthorityError::CheckpointIsNotEstimator`] when +/// `role` is [`ArtifactRole::ModelCheckpoint`]. +pub fn refuse_checkpoint_as_estimator(role: ArtifactRole) -> Result<(), CheckpointAuthorityError> { + match role { + ArtifactRole::ModelCheckpoint => Err(CheckpointAuthorityError::CheckpointIsNotEstimator), + ArtifactRole::CpuF64Estimator => Ok(()), + } +} + +/// Accept a checkpoint only as a validated run artifact. +/// +/// Identity, model-run provenance, and a canonical digest are required. +/// Success does not grant estimator authority. +/// +/// # Errors +/// +/// Returns a missing-field or digest error when the offer is untrusted. +pub fn accept_checkpoint_artifact( + offer: &CheckpointOffer<'_>, +) -> Result<(), CheckpointAuthorityError> { + if offer.artifact_identity.is_empty() { + return Err(CheckpointAuthorityError::MissingIdentity); + } + if offer.model_run_identity.is_empty() { + return Err(CheckpointAuthorityError::MissingProvenance); + } + validate_sha256_hex(offer.content_digest) +} + +/// Fraction of recovered artifact roles that match known truth. +/// +/// # Errors +/// +/// Returns [`CheckpointAuthorityError::InvalidAuthorityPayload`] when either +/// slice is empty or the lengths differ. +pub fn authority_recovery_rate( + truth: &[ArtifactRole], + decided: &[ArtifactRole], +) -> Result { + if truth.is_empty() || truth.len() != decided.len() { + return Err(CheckpointAuthorityError::InvalidAuthorityPayload); + } + let mut matches = 0_u32; + for (truth_role, decided_role) in truth.iter().zip(decided) { + if truth_role == decided_role { + matches += 1; + } + } + Ok(f64::from(matches) / truth.len() as f64) +} + +fn validate_sha256_hex(digest: &str) -> Result<(), CheckpointAuthorityError> { + if digest.is_empty() { + return Err(CheckpointAuthorityError::MissingDigest); + } + if digest.len() != 64 + || !digest + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + { + return Err(CheckpointAuthorityError::InvalidDigest); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{ + ArtifactRole, CheckpointOffer, accept_checkpoint_artifact, authority_recovery_rate, + refuse_checkpoint_as_estimator, + }; + use crate::CheckpointAuthorityError; + + #[test] + fn local_branches_cover_roles_payloads_and_wire_names() { + assert_eq!( + refuse_checkpoint_as_estimator(ArtifactRole::ModelCheckpoint), + Err(CheckpointAuthorityError::CheckpointIsNotEstimator) + ); + refuse_checkpoint_as_estimator(ArtifactRole::CpuF64Estimator).expect("estimator"); + for role in [ArtifactRole::CpuF64Estimator, ArtifactRole::ModelCheckpoint] { + assert_eq!( + ArtifactRole::from_wire_name(role.wire_name()).expect("round-trip"), + role + ); + } + assert_eq!( + ArtifactRole::from_wire_name("posterior_summary"), + Err(CheckpointAuthorityError::InvalidAuthorityPayload) + ); + let offer = CheckpointOffer { + artifact_identity: "artifact-01", + content_digest: &"cd".repeat(32), + model_run_identity: "run-01", + }; + accept_checkpoint_artifact(&offer).expect("artifact"); + assert_eq!( + accept_checkpoint_artifact(&CheckpointOffer { + artifact_identity: "", + ..offer + }), + Err(CheckpointAuthorityError::MissingIdentity) + ); + assert_eq!( + accept_checkpoint_artifact(&CheckpointOffer { + model_run_identity: "", + ..offer + }), + Err(CheckpointAuthorityError::MissingProvenance) + ); + assert_eq!( + accept_checkpoint_artifact(&CheckpointOffer { + content_digest: "", + ..offer + }), + Err(CheckpointAuthorityError::MissingDigest) + ); + assert_eq!( + accept_checkpoint_artifact(&CheckpointOffer { + content_digest: "ab", + ..offer + }), + Err(CheckpointAuthorityError::InvalidDigest) + ); + assert_eq!( + accept_checkpoint_artifact(&CheckpointOffer { + content_digest: &"gh".repeat(32), + ..offer + }), + Err(CheckpointAuthorityError::InvalidDigest) + ); + let matched = authority_recovery_rate( + &[ArtifactRole::ModelCheckpoint], + &[ArtifactRole::ModelCheckpoint], + ) + .expect("rate"); + assert!((matched - 1.0).abs() < f64::EPSILON); + assert_eq!( + authority_recovery_rate(&[], &[]), + Err(CheckpointAuthorityError::InvalidAuthorityPayload) + ); + assert_eq!( + authority_recovery_rate(&[ArtifactRole::ModelCheckpoint], &[]), + Err(CheckpointAuthorityError::InvalidAuthorityPayload) + ); + } +} diff --git a/crates/checkpoint_authority/src/error.rs b/crates/checkpoint_authority/src/error.rs new file mode 100644 index 00000000..d753683c --- /dev/null +++ b/crates/checkpoint_authority/src/error.rs @@ -0,0 +1,74 @@ +//! Fail-closed checkpoint-authority errors. + +use std::fmt; + +/// A fail-closed checkpoint-authority error. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum CheckpointAuthorityError { + /// A checkpoint was treated as the CPU `f64` estimator. + CheckpointIsNotEstimator, + /// Artifact identity was missing or empty. + MissingIdentity, + /// Model-run provenance was missing or empty. + MissingProvenance, + /// Content digest was missing or empty. + MissingDigest, + /// Content digest was not canonical lowercase hex `SHA-256`. + InvalidDigest, + /// A recovery slice was empty or length-mismatched. + InvalidAuthorityPayload, +} + +impl fmt::Display for CheckpointAuthorityError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::CheckpointIsNotEstimator => "a model checkpoint is not the cpu f64 estimator", + Self::MissingIdentity => "checkpoint artifact is missing identity", + Self::MissingProvenance => "checkpoint artifact is missing model-run provenance", + Self::MissingDigest => "checkpoint artifact is missing content digest", + Self::InvalidDigest => "checkpoint artifact digest is not canonical sha-256", + Self::InvalidAuthorityPayload => "invalid checkpoint-authority payload", + }; + formatter.write_str(message) + } +} + +impl std::error::Error for CheckpointAuthorityError {} + +#[cfg(test)] +mod tests { + use super::CheckpointAuthorityError; + + #[test] + fn error_messages_are_stable() { + for (error, message) in [ + ( + CheckpointAuthorityError::CheckpointIsNotEstimator, + "a model checkpoint is not the cpu f64 estimator", + ), + ( + CheckpointAuthorityError::MissingIdentity, + "checkpoint artifact is missing identity", + ), + ( + CheckpointAuthorityError::MissingProvenance, + "checkpoint artifact is missing model-run provenance", + ), + ( + CheckpointAuthorityError::MissingDigest, + "checkpoint artifact is missing content digest", + ), + ( + CheckpointAuthorityError::InvalidDigest, + "checkpoint artifact digest is not canonical sha-256", + ), + ( + CheckpointAuthorityError::InvalidAuthorityPayload, + "invalid checkpoint-authority payload", + ), + ] { + assert_eq!(error.to_string(), message); + } + } +} diff --git a/crates/checkpoint_authority/src/lib.rs b/crates/checkpoint_authority/src/lib.rs new file mode 100644 index 00000000..b92eaf17 --- /dev/null +++ b/crates/checkpoint_authority/src/lib.rs @@ -0,0 +1,24 @@ +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![allow(clippy::cast_precision_loss)] +//! A model checkpoint is not the CPU `f64` estimator. +//! +//! Checkpoints stay untrusted run artifacts until identity, digest, and +//! model-run provenance validate. They cannot replace the reference +//! estimator or promote a scientific claim (ADR 0001/0014). + +mod authority; +mod error; + +/// Closed vocabulary of scientific-authority roles for a run artifact. +pub use authority::ArtifactRole; +/// Identity, digest, and run provenance for one checkpoint offer. +pub use authority::CheckpointOffer; +/// Accept a checkpoint only as a validated run artifact. +pub use authority::accept_checkpoint_artifact; +/// Fraction of recovered artifact roles that match known truth. +pub use authority::authority_recovery_rate; +/// Refuse to treat a checkpoint as the CPU `f64` estimator. +pub use authority::refuse_checkpoint_as_estimator; +/// Fail-closed checkpoint-authority errors. +pub use error::CheckpointAuthorityError; diff --git a/crates/checkpoint_authority/tests/checkpoint_authority_contract.rs b/crates/checkpoint_authority/tests/checkpoint_authority_contract.rs new file mode 100644 index 00000000..dd9c1e2d --- /dev/null +++ b/crates/checkpoint_authority/tests/checkpoint_authority_contract.rs @@ -0,0 +1,126 @@ +//! A model checkpoint is not the CPU `f64` estimator. + +use checkpoint_authority::{ + ArtifactRole, CheckpointAuthorityError, CheckpointOffer, accept_checkpoint_artifact, + authority_recovery_rate, refuse_checkpoint_as_estimator, +}; + +#[test] +fn a_checkpoint_cannot_become_the_cpu_f64_estimator() { + assert_eq!( + refuse_checkpoint_as_estimator(ArtifactRole::ModelCheckpoint), + Err(CheckpointAuthorityError::CheckpointIsNotEstimator) + ); + refuse_checkpoint_as_estimator(ArtifactRole::CpuF64Estimator).expect("estimator"); +} + +#[test] +fn an_unvalidated_checkpoint_fails_closed() { + let valid = CheckpointOffer { + artifact_identity: "artifact-01", + content_digest: &"ab".repeat(32), + model_run_identity: "run-01", + }; + accept_checkpoint_artifact(&valid).expect("validated artifact"); + + let missing_identity = CheckpointOffer { + artifact_identity: "", + ..valid + }; + assert_eq!( + accept_checkpoint_artifact(&missing_identity), + Err(CheckpointAuthorityError::MissingIdentity) + ); + + let missing_provenance = CheckpointOffer { + model_run_identity: "", + ..valid + }; + assert_eq!( + accept_checkpoint_artifact(&missing_provenance), + Err(CheckpointAuthorityError::MissingProvenance) + ); + + let missing_digest = CheckpointOffer { + content_digest: "", + ..valid + }; + assert_eq!( + accept_checkpoint_artifact(&missing_digest), + Err(CheckpointAuthorityError::MissingDigest) + ); + + let short_digest = CheckpointOffer { + content_digest: "abcd", + ..valid + }; + assert_eq!( + accept_checkpoint_artifact(&short_digest), + Err(CheckpointAuthorityError::InvalidDigest) + ); + + let uppercase_digest = CheckpointOffer { + content_digest: &"AB".repeat(32), + ..valid + }; + assert_eq!( + accept_checkpoint_artifact(&uppercase_digest), + Err(CheckpointAuthorityError::InvalidDigest) + ); + + let non_hex_digest = CheckpointOffer { + content_digest: &"zz".repeat(32), + ..valid + }; + assert_eq!( + accept_checkpoint_artifact(&non_hex_digest), + Err(CheckpointAuthorityError::InvalidDigest) + ); +} + +#[test] +fn recovered_roles_match_known_truth_better_than_an_estimator_collapse() { + let truth = [ + ArtifactRole::ModelCheckpoint, + ArtifactRole::CpuF64Estimator, + ArtifactRole::ModelCheckpoint, + ]; + let recovered = truth; + let collapsed = [ + ArtifactRole::CpuF64Estimator, + ArtifactRole::CpuF64Estimator, + ArtifactRole::CpuF64Estimator, + ]; + let recovered_rate = authority_recovery_rate(&truth, &recovered).expect("recovered"); + let collapsed_rate = authority_recovery_rate(&truth, &collapsed).expect("collapsed"); + let expected = { + let mut matches = 0_u32; + for (truth_role, decided_role) in truth.iter().zip(recovered.iter()) { + if truth_role == decided_role { + 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_role_payloads_fail_closed() { + assert_eq!( + authority_recovery_rate(&[], &[]), + Err(CheckpointAuthorityError::InvalidAuthorityPayload) + ); + assert_eq!( + authority_recovery_rate(&[ArtifactRole::ModelCheckpoint], &[]), + Err(CheckpointAuthorityError::InvalidAuthorityPayload) + ); + assert_eq!( + authority_recovery_rate( + &[ArtifactRole::ModelCheckpoint, ArtifactRole::CpuF64Estimator], + &[ArtifactRole::ModelCheckpoint] + ), + Err(CheckpointAuthorityError::InvalidAuthorityPayload) + ); +} diff --git a/crates/checkpoint_authority/tests/crate_contract.rs b/crates/checkpoint_authority/tests/crate_contract.rs new file mode 100644 index 00000000..1b4f2d72 --- /dev/null +++ b/crates/checkpoint_authority/tests/crate_contract.rs @@ -0,0 +1,7 @@ +//! Integration contract for the `checkpoint_authority` package identity. + +#[test] +fn package_identity_is_stable() { + let observed = std::hint::black_box(env!("CARGO_PKG_NAME")); + assert_eq!(observed, "checkpoint_authority"); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index a3e674cf..ab6787e7 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -8,11 +8,11 @@ The full APA 7th standards/literature register remains `docs/research/standards- | Requirement / decision | Canonical basis | Source/evidence boundary | Maturity | |---|---|---|---| | immutable source evidence and exact spans | PRD; Architecture; ADR 0008 | `evidence_core`, Task 2 tests/doctoring; `persistence_postgres` source-artifact SQL insert/lookup plus idempotent retry (#40 implemented-main) | implemented-main | -| Rust numerical authority / CPU `f64` reference | ADR 0001 | current workspace foundation; future estimators | partial | +| Rust numerical authority / CPU `f64` reference | ADR 0001 | current workspace foundation; `checkpoint_authority` checkpoint-versus-estimator gate on the active PR; future estimators | partial | | 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; PR #5 historical only | implemented-main | | 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 | implemented-main | +| forward-only transition subgraph | PRD; ADR 0002/0003 | `relation_graph` on protected main; downstream transition/split enforcement remains accepted-target | implemented-main | | 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 | diff --git a/docs/adr/0001-rust-first-modular-msa.md b/docs/adr/0001-rust-first-modular-msa.md index 4b499482..e10190f5 100644 --- a/docs/adr/0001-rust-first-modular-msa.md +++ b/docs/adr/0001-rust-first-modular-msa.md @@ -1,7 +1,7 @@ # ADR 0001 — Rust-first numerical core and CPU `f64` reference **Decision status:** Accepted -**Implementation maturity:** partial — workspace/quality foundation is implemented-main; estimator/reference-backend behavior is accepted-target +**Implementation maturity:** partial — workspace/quality foundation is implemented-main; checkpoint-versus-estimator authority is `checkpoint_authority` on the active PR; estimator/reference-backend implementations remain accepted-target **Date:** 2026-08-05 **Supersession:** ADR 0011 governs cross-service ownership and standalone/MSA integration. This ADR remains authoritative for Rust-first numerical implementation and backend parity. diff --git a/docs/adr/0002-six-clock-temporal-semantics.md b/docs/adr/0002-six-clock-temporal-semantics.md index c06f7d38..758a9419 100644 --- a/docs/adr/0002-six-clock-temporal-semantics.md +++ b/docs/adr/0002-six-clock-temporal-semantics.md @@ -1,7 +1,7 @@ # ADR 0002 — Six-clock temporal semantics and leakage prevention **Decision status:** Accepted -**Implementation maturity:** active-PR — unmerged 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:** active-PR — unmerged PR #8 is the canonical Task 3 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 **Date:** 2026-08-05 **Supersedes:** None. ADR 0013 owns persistence/split representation; ADR 0016 owns event-intelligence reasoning above these temporal primitives. diff --git a/docs/adr/0003-relational-event-multiple-membership.md b/docs/adr/0003-relational-event-multiple-membership.md index c5b1a154..6b7cd347 100644 --- a/docs/adr/0003-relational-event-multiple-membership.md +++ b/docs/adr/0003-relational-event-multiple-membership.md @@ -1,7 +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 active-PR; multilevel estimators and persistence remain accepted-target +**Implementation maturity:** partial — membership network and event mention/instance separation implemented-main; typed relation graph with forward-only transitions implemented-main; multilevel estimators and persistence remain accepted-target **Date:** 2026-08-05 **Supersedes:** None. ADR 0016 owns TDT/CHRONOS event-intelligence task semantics; this ADR remains authoritative for ontology, relation, role, and membership structure. diff --git a/docs/adr/0011-standalone-modular-msa-boundary.md b/docs/adr/0011-standalone-modular-msa-boundary.md index d545ee23..04181fb3 100644 --- a/docs/adr/0011-standalone-modular-msa-boundary.md +++ b/docs/adr/0011-standalone-modular-msa-boundary.md @@ -1,7 +1,7 @@ # 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 +**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. diff --git a/docs/adr/0014-scientific-claim-promotion-and-release-evidence.md b/docs/adr/0014-scientific-claim-promotion-and-release-evidence.md index 81ebb373..6af68237 100644 --- a/docs/adr/0014-scientific-claim-promotion-and-release-evidence.md +++ b/docs/adr/0014-scientific-claim-promotion-and-release-evidence.md @@ -1,7 +1,7 @@ # ADR 0014 — Scientific claim promotion and release evidence authority **Decision status:** Accepted -**Implementation maturity:** partial — claim/promotion authority documented; repository SBOM/provenance evidence generator and CI validation implemented; full package/image release bundle and scientific claim promotion packages remain accepted-target +**Implementation maturity:** partial — claim/promotion authority documented; repository SBOM/provenance evidence generator and CI validation implemented; checkpoint-versus-estimator refusal is `checkpoint_authority` on the active PR; full package/image release bundle and scientific claim promotion packages remain accepted-target **Date:** 2026-08-12 **Supersedes:** None; extends ADR 0007 from repository quality tooling to product/scientific claim authority. diff --git a/docs/adr/README.md b/docs/adr/README.md index 258eb7f3..6256255e 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -6,7 +6,7 @@ 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. | +| [0001](0001-rust-first-modular-msa.md) | Rust-first numerical core and CPU `f64` reference | Accepted | partial | Workspace foundation is implemented-main; checkpoint-versus-estimator authority is `checkpoint_authority` on the active PR. 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 | 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. | | [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 active-PR (PR #12); full multilevel estimators, graph ontology, and persistence remain accepted-target. ADR 0016 owns event-intelligence tasks. | | [0004](0004-shared-multilingual-latent-space.md) | One shared multilingual latent space with explicit invariance status | Accepted | accepted-target | ADR 0012 owns the full topic-estimator/backend/global-topic contract. | @@ -19,7 +19,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [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. | -| [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. | +| [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; checkpoint-versus-estimator refusal is `checkpoint_authority` on the active PR; 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. | diff --git a/docs/connectors/naruon-artifact-consumer.md b/docs/connectors/naruon-artifact-consumer.md index 2e4f4d6c..5fe0424c 100644 --- a/docs/connectors/naruon-artifact-consumer.md +++ b/docs/connectors/naruon-artifact-consumer.md @@ -1,6 +1,6 @@ # naruon modular consumer contract for TEPP artifacts -**Status:** Partial — versioned DTO, HTTP interchange, and loopback live listener on the active PR; production TLS/`$PORT` remaining +**Status:** Partial — versioned DTO, HTTP interchange, and loopback live listener on the active PR; production TLS/`$PORT` remaining **Last reviewed:** 2026-08-16 ## Boundary diff --git a/docs/research/checkpoint-estimator-authority.md b/docs/research/checkpoint-estimator-authority.md new file mode 100644 index 00000000..12846177 --- /dev/null +++ b/docs/research/checkpoint-estimator-authority.md @@ -0,0 +1,44 @@ +# A model checkpoint is not the estimator (doctoring) + +## Scope + +`checkpoint_authority` keeps serialized model checkpoints out of CPU +`f64` estimator authority. A checkpoint may be accepted as a run +artifact only after identity, canonical `SHA-256`, and model-run +provenance validate. Recovery is the computed share of recovered roles +that match known truth. + +This slice does not persist artifacts, allocate migration `0008`, or +replace `validation_core` recovery metrics or `persistence_postgres` +model-run SQL. + +## Authority + +### Normative TEPP contract + +- `docs/adr/0001-rust-first-modular-msa.md` — production arithmetic is + the Rust CPU `f64` reference; optimized backends and artifacts cannot + redefine the estimand. +- `docs/adr/0014-scientific-claim-promotion-and-release-evidence.md` — + checkpoint bytes cannot promote a scientific claim without + claim-specific recovery evidence. +- `AGENTS.md` — model checkpoints stay untrusted until identity, + provenance, size/depth, authorization, and scientific semantics + validate. + +### Supporting literature + +Peng (2011) and the National Academies (2019) treat a reproducible +computational artifact as evidence of a procedure, not as the +procedure. FIPS PUB 180-4 supplies the canonical digest used to detect +checkpoint substitution without granting estimator authority. + +Peng, R. D. (2011). Reproducible research in computational science. +*Science, 334*(6060), 1226–1227. https://doi.org/10.1126/science.1213847 + +National Academies of Sciences, Engineering, and Medicine. (2019). +*Reproducibility and replicability in science*. The National Academies +Press. https://doi.org/10.17226/25303 + +National Institute of Standards and Technology. (2015). *Secure Hash +Standard (SHS)* (FIPS PUB 180-4). https://doi.org/10.6028/NIST.FIPS.180-4 diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 28e62d5c..7b24026e 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -68,6 +68,8 @@ Anagnostopoulos, E., Batsakis, S., & Petrakis, E. G. M. (2013). CHRONOS: A reaso 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. +Allen, J. F. (1983). Maintaining knowledge about temporal intervals. *Communications of the ACM, 26*(11), 832–843. https://doi.org/10.1145/182.358434. Interval relations constrain temporal reasoning; they do not make support, contradiction, summary, or `outcome_of` a state transition. + ## Unicode, language tags, and multilingual structure Davis, M., Iancu, L., & Whistler, K. (Eds.). (2024). *Unicode Standard Annex #15: Unicode normalization forms*. Unicode Consortium. @@ -94,7 +96,11 @@ Lebo, T., Sahoo, S., & McGuinness, D. (Eds.). (2013). *PROV-O: The PROV ontology Moreau, L., & Missier, P. (Eds.). (2013). *PROV-DM: The PROV data model*. World Wide Web Consortium. https://www.w3.org/TR/prov-dm/ -TEPP separates stable record identity, content equality, exact text location, wire representation, authorization, and provenance. JSON wire records are explicit versioned DTOs with unknown-field rejection and reconstruct through domain validation. `SHA-256` detects content substitution but is not treated as proof of origin, authority, or chain of custody. +National Academies of Sciences, Engineering, and Medicine. (2019). *Reproducibility and replicability in science*. The National Academies Press. https://doi.org/10.17226/25303 + +Peng, R. D. (2011). Reproducible research in computational science. *Science, 334*(6060), 1226–1227. https://doi.org/10.1126/science.1213847 + +TEPP separates stable record identity, content equality, exact text location, wire representation, authorization, and provenance. JSON wire records are explicit versioned DTOs with unknown-field rejection and reconstruct through domain validation. `SHA-256` detects content substitution but is not treated as proof of origin, authority, or chain of custody. A model checkpoint is a derived run artifact whose digest verifies bytes (National Institute of Standards and Technology, 2015); it does not become the CPU `f64` estimator or a scientific claim (Peng, 2011; National Academies of Sciences, Engineering, and Medicine, 2019). ## Privacy lifecycle, retention, and legal hold diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index aae1a06e..7d83beb9 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -22,6 +22,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Leakage-safe splits | `corpus_split` | implemented-main | — | cutoff + co-partition tests | Task 9 / PR #17 | | Truth corpora / manifests | `tepp_simulation` | implemented-main | — | deterministic generator tests | Task 10 / PR #18 | | Recovery metrics | `validation_core` | implemented-main | — | RMSE/bias/coverage/MC gates | Task 11 / PR #19 | +| Checkpoint is not the estimator | `checkpoint_authority` | accepted-target | active PR | refuse checkpoint-as-estimator + unvalidated artifact + recovery vs estimator collapse | ADR 0001/0014 | | Versioned API/export contracts | `tepp_api` | implemented-main | naruon HTTP interchange | unknown-field/version/limit + naruon HTTPS interchange tests | Task 12 / PR #21; live HTTP service remaining | | Purpose-bound provider payloads | `tepp_api` | implemented-main | provider-payload minimization | expired/not-yet-valid/inverted/cross-tenant/impossible-calendar grant, mapping refusal, audited elevated re-id replay | ADR 0009; `docs/research/provider-payload-minimization.md` | | Adaptive orchestration router | `tepp_api` | accepted-target | active PR | mode selection, document-control denial, ablation, credential-free bind | ADR 0010; `docs/research/adaptive-orchestration-router.md` | diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index c7b1ecf5..a4da9db3 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -23,6 +23,7 @@ "tepp_simulation", "validation_core", "tepp_api", + "checkpoint_authority", ) REQUIRED_CI_SNIPPETS: tuple[str, ...] = ( diff --git a/tests/quality/test_check_docstrings.py b/tests/quality/test_check_docstrings.py index 2c11f7a5..da59f799 100644 --- a/tests/quality/test_check_docstrings.py +++ b/tests/quality/test_check_docstrings.py @@ -11,6 +11,7 @@ from unittest import mock from scripts import check_docstrings as docstrings +from scripts import check_workspace_contract as workspace_contract REPOSITORY_ROOT = Path(__file__).resolve().parents[2] @@ -24,7 +25,7 @@ def test_live_repository_is_documented(self) -> None: sources = docstrings.rust_sources(REPOSITORY_ROOT) crate_roots = sorted(REPOSITORY_ROOT.glob("crates/*/src/lib.rs")) - self.assertEqual(len(crate_roots), 10) + self.assertEqual(len(crate_roots), len(workspace_contract.EXPECTED_CRATES)) self.assertTrue(set(crate_roots).issubset(sources)) self.assertGreaterEqual(len(sources), len(crate_roots)) self.assertEqual(docstrings.validate_repository(REPOSITORY_ROOT), [])