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
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

- `relation_graph` causal-identification gate: only `causes` and `intervenes_on` may be described as causal; association, temporal precedence, production, and provenance fail closed.
- `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 @@ -33,6 +33,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| Hourly NIM product-development operations | [`docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md`](docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md) |
| Actions workflow fleet audit | [`docs/operations/ACTIONS_WORKFLOW_FLEET.md`](docs/operations/ACTIONS_WORKFLOW_FLEET.md) |
| Actions fleet research doctoring | [`docs/research/actions-workflow-fleet.md`](docs/research/actions-workflow-fleet.md) |
| Causal-identification gate doctoring | [`docs/research/causal-identification-gate.md`](docs/research/causal-identification-gate.md) |
| Retention/deletion/legal-hold doctoring | [`docs/research/retention-deletion-legal-hold.md`](docs/research/retention-deletion-legal-hold.md) |
| Provider-payload minimization doctoring | [`docs/research/provider-payload-minimization.md`](docs/research/provider-payload-minimization.md) |
| Adaptive orchestration router doctoring | [`docs/research/adaptive-orchestration-router.md`](docs/research/adaptive-orchestration-router.md) |
Expand Down
7 changes: 7 additions & 0 deletions crates/relation_graph/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum RelationError {
InvalidWirePayload,
/// A wire payload used a schema version this crate does not support.
UnsupportedWireVersion,
/// An association, precedence, or provenance edge was treated as causation.
CausalClaimNotIdentified,
}

impl fmt::Display for RelationError {
Expand All @@ -35,6 +37,7 @@ impl fmt::Display for RelationError {
Self::DuplicateRelationEdge => "duplicate relation edge",
Self::InvalidWirePayload => "invalid relation wire payload",
Self::UnsupportedWireVersion => "unsupported relation wire version",
Self::CausalClaimNotIdentified => "causal claim is not identified",
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -72,6 +75,10 @@ mod tests {
RelationError::UnsupportedWireVersion,
"unsupported relation wire version",
),
(
RelationError::CausalClaimNotIdentified,
"causal claim is not identified",
),
] {
assert_eq!(error.to_string(), message);
}
Expand Down
32 changes: 32 additions & 0 deletions crates/relation_graph/src/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ pub enum RelationKind {
}

impl RelationKind {
/// Return whether this kind may carry an identified causal claim.
///
/// `Causes` and `IntervenesOn` are the only vocabulary members that may be
/// described as causal. Temporal precedence, enabling, production, and
/// provenance remain non-causal until a later identified design.
#[must_use]
pub const fn is_identified_causal_claim(self) -> bool {
matches!(self, Self::Causes | Self::IntervenesOn)
}

/// Return whether this kind is a forward state-transition edge.
#[must_use]
pub const fn is_transition_edge(self) -> bool {
Expand Down Expand Up @@ -112,11 +122,33 @@ impl RelationKind {
}
}

/// Refuse treating association, precedence, or provenance as causation.
///
/// # Errors
///
/// Returns [`RelationError::CausalClaimNotIdentified`] unless `kind` is
/// [`RelationKind::Causes`] or [`RelationKind::IntervenesOn`].
pub fn refuse_association_as_cause(kind: RelationKind) -> Result<(), RelationError> {
if kind.is_identified_causal_claim() {
Ok(())
} else {
Err(RelationError::CausalClaimNotIdentified)
}
}

#[cfg(test)]
mod tests {
use super::RelationKind;
use crate::RelationError;

#[test]
fn identified_causal_kinds_are_only_causes_and_intervention() {
assert!(RelationKind::Causes.is_identified_causal_claim());
assert!(RelationKind::IntervenesOn.is_identified_causal_claim());
assert!(!RelationKind::LeadsTo.is_identified_causal_claim());
super::refuse_association_as_cause(RelationKind::Causes).expect("causes");
}

#[test]
fn transition_vocabulary_matches_erd_contract() {
for kind in [
Expand Down
2 changes: 2 additions & 0 deletions crates/relation_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub use identifier::RelationEdgeId;
pub use identifier::RelationEndpointId;
/// Closed relation vocabulary with derived transition classification.
pub use kind::RelationKind;
/// Refuse treating association or precedence as causation.
pub use kind::refuse_association_as_cause;
/// Observed versus inferred relation evidence status.
pub use provenance::RelationEvidenceStatus;
/// Validate forward-only event-time order for transition edges.
Expand Down
32 changes: 32 additions & 0 deletions crates/relation_graph/tests/causal_identification_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Association and temporal precedence are not causal identification.

use relation_graph::{RelationError, RelationKind, refuse_association_as_cause};

#[test]
fn identified_causal_vocabulary_is_allowed_and_associations_are_not() {
refuse_association_as_cause(RelationKind::Causes).expect("causes");
refuse_association_as_cause(RelationKind::IntervenesOn).expect("intervention");

for kind in [
RelationKind::LeadsTo,
RelationKind::Enables,
RelationKind::References,
RelationKind::Summarizes,
RelationKind::Revises,
RelationKind::Translates,
RelationKind::RetrospectivelyReports,
RelationKind::Supports,
RelationKind::Contradicts,
RelationKind::OutcomeOf,
RelationKind::InputTo,
RelationKind::ProcessTo,
RelationKind::Produces,
RelationKind::TransitionsTo,
] {
assert_eq!(
refuse_association_as_cause(kind),
Err(RelationError::CausalClaimNotIdentified),
"{kind:?} must not be treated as identified causation"
);
}
}
1 change: 1 addition & 0 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The full APA 7th standards/literature register remains `docs/research/standards-
| 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 |
| no unidentified causal language from association/precedence | ADR 0002/0003; research | `relation_graph` causal-identification gate on the active PR | active-PR |
| 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 |
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0011-standalone-modular-msa-boundary.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
2 changes: 1 addition & 1 deletion docs/connectors/naruon-artifact-consumer.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
25 changes: 25 additions & 0 deletions docs/research/causal-identification-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Causal identification versus association

## Scope

This note doctors the `relation_graph` gate that keeps TEPP from converting association, temporal precedence, or document links into causal language:

1. only `causes` and `intervenes_on` may be described as identified causal claims;
2. `leads_to`, `enables`, production, input/process, and all provenance kinds fail closed.

No database migration is allocated. A later identified design can widen the allowed set with an ADR.

## Authoritative sources

Pearl, J. (2009). *Causality: Models, reasoning, and inference* (2nd ed.). Cambridge University Press.

Holland, P. W. (1986). Statistics and causal inference. *Journal of the American Statistical Association, 81*(396), 945–960. https://doi.org/10.1080/01621459.1986.10478354

## Application

Holland (1986) and Pearl (2009) distinguish association and temporal order from an identified causal effect. TEPP therefore refuses to treat `references`, `leads_to`, or `enables` as `causes` without a later identification argument (Holland, 1986; Pearl, 2009).

## Verification

- `refuse_association_as_cause(Causes)` and `IntervenesOn` succeed;
- every other closed vocabulary kind returns `CausalClaimNotIdentified`.
1 change: 1 addition & 0 deletions docs/validation/temporal-event-foundation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| Causal-identification gate | `relation_graph` | active-PR | association ≠ cause | LeadsTo/References denied | ADR 0003; `docs/research/causal-identification-gate.md` |
| 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` |
Expand Down
Loading