From 7825c778f39bb8d1ab9f6fe18c227559f8e78fee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 13:13:08 +0900 Subject: [PATCH] feat(relation): refuse translation edges as state transitions Translation, same-language copy, and revision stay provenance (ADR 0002/0003). A shared RFC 5646 primary language tag cannot be classified as a translation. Recovery is the computed share of recovered kinds versus collapsing every kind to translation. No migration 0008. --- ARCHITECTURE.md | 1 + CHANGELOG.md | 1 + Cargo.lock | 4 + Cargo.toml | 2 + README.md | 3 +- crates/translation_edge/Cargo.toml | 17 ++ crates/translation_edge/src/error.rs | 62 ++++++ crates/translation_edge/src/kind.rs | 181 ++++++++++++++++++ crates/translation_edge/src/lib.rs | 21 ++ .../translation_edge/tests/crate_contract.rs | 7 + .../tests/edge_kind_contract.rs | 99 ++++++++++ docs/TRACEABILITY.md | 1 + docs/adr/0002-six-clock-temporal-semantics.md | 2 +- ...03-relational-event-multiple-membership.md | 4 +- docs/adr/README.md | 4 +- docs/research/standards-and-literature.md | 8 + docs/research/translation-not-transition.md | 39 ++++ docs/validation/temporal-event-foundation.md | 1 + scripts/check_workspace_contract.py | 1 + tests/quality/test_check_docstrings.py | 3 +- 20 files changed, 454 insertions(+), 7 deletions(-) create mode 100644 crates/translation_edge/Cargo.toml create mode 100644 crates/translation_edge/src/error.rs create mode 100644 crates/translation_edge/src/kind.rs create mode 100644 crates/translation_edge/src/lib.rs create mode 100644 crates/translation_edge/tests/crate_contract.rs create mode 100644 crates/translation_edge/tests/edge_kind_contract.rs create mode 100644 docs/research/translation-not-transition.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ffe514db..d50ac897 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 | +| `translation_edge` | translation, copy, and revision edges are not state transitions | 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 f3764d25..2542c138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ### Added +- `translation_edge` identity gate: translation, same-language copy, and revision edges cannot become state transitions; a shared primary language tag cannot be classified as a translation; recovered kinds match known truth at a higher computed rate than collapsing every kind to translation (ADR 0002/0003). - `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. - `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). - `persistence_postgres` concurrent document-write stress: atomic revise `DO` block that requires exactly one open `system_to` close, SQLSTATE mapping onto `ConcurrentWriteConflict` / `DuplicateDocumentRecord`, and live multi-session insert/revise/append-only proofs. No new migration number. diff --git a/Cargo.lock b/Cargo.lock index 372a55f4..5950bf51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1402,6 +1402,10 @@ dependencies = [ "once_cell", ] +[[package]] +name = "translation_edge" +version = "0.1.0" + [[package]] name = "typenum" version = "1.20.1" diff --git a/Cargo.toml b/Cargo.toml index 92565940..cf336ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/translation_edge", ] default-members = [ "crates/evidence_core", @@ -23,6 +24,7 @@ default-members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/translation_edge", ] [workspace.package] diff --git a/README.md b/README.md index ae74015d..9552ac61 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/translation_edge ``` ## Local verification diff --git a/crates/translation_edge/Cargo.toml b/crates/translation_edge/Cargo.toml new file mode 100644 index 00000000..8c8fcdbd --- /dev/null +++ b/crates/translation_edge/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "translation_edge" +description = "Translation, same-language copy, and revision edges cannot become state transitions." +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/translation_edge/src/error.rs b/crates/translation_edge/src/error.rs new file mode 100644 index 00000000..27f83800 --- /dev/null +++ b/crates/translation_edge/src/error.rs @@ -0,0 +1,62 @@ +//! Fail-closed translation-edge errors. + +use std::fmt; + +/// A fail-closed translation-edge error. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum TranslationEdgeError { + /// A translation, copy, or revision edge was treated as a state transition. + TranslationIsNotTransition, + /// A same-language pair was classified as a translation. + SameLanguageIsNotTranslation, + /// A language tag was empty or lacked a primary subtag. + InvalidLanguageTag, + /// A kind slice was empty or length-mismatched. + InvalidEdgePayload, +} + +impl fmt::Display for TranslationEdgeError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::TranslationIsNotTransition => { + "translation, same-language copy, and revision edges are not state transitions" + } + Self::SameLanguageIsNotTranslation => "same primary language is not a translation", + Self::InvalidLanguageTag => "invalid language tag", + Self::InvalidEdgePayload => "invalid translation-edge payload", + }; + formatter.write_str(message) + } +} + +impl std::error::Error for TranslationEdgeError {} + +#[cfg(test)] +mod tests { + use super::TranslationEdgeError; + + #[test] + fn error_messages_are_stable() { + for (error, message) in [ + ( + TranslationEdgeError::TranslationIsNotTransition, + "translation, same-language copy, and revision edges are not state transitions", + ), + ( + TranslationEdgeError::SameLanguageIsNotTranslation, + "same primary language is not a translation", + ), + ( + TranslationEdgeError::InvalidLanguageTag, + "invalid language tag", + ), + ( + TranslationEdgeError::InvalidEdgePayload, + "invalid translation-edge payload", + ), + ] { + assert_eq!(error.to_string(), message); + } + } +} diff --git a/crates/translation_edge/src/kind.rs b/crates/translation_edge/src/kind.rs new file mode 100644 index 00000000..149de463 --- /dev/null +++ b/crates/translation_edge/src/kind.rs @@ -0,0 +1,181 @@ +//! Translation-related provenance kinds that may point to the past. + +use crate::TranslationEdgeError; + +/// Closed vocabulary of translation-related edges that are not state transitions. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TranslationKind { + /// A translation of an earlier document into a different language. + Translation, + /// A same-language template or copied variant. + SameLanguageCopy, + /// A same-language revision of an earlier document. + Revision, +} + +impl TranslationKind { + /// Return the stable wire kind name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::Translation => "translates", + Self::SameLanguageCopy => "template_copy_of", + Self::Revision => "revises", + } + } + + /// Parse a stable wire kind name. + /// + /// # Errors + /// + /// Returns [`TranslationEdgeError::InvalidEdgePayload`] for unrecognized + /// names, including transition names such as `causes`. + pub fn from_wire_name(name: &str) -> Result { + match name { + "translates" => Ok(Self::Translation), + "template_copy_of" => Ok(Self::SameLanguageCopy), + "revises" => Ok(Self::Revision), + _ => Err(TranslationEdgeError::InvalidEdgePayload), + } + } + + /// Return whether this kind is a forward state-transition edge. + /// + /// Translation-related provenance kinds are never transitions. + #[must_use] + pub const fn is_transition_edge(self) -> bool { + match self { + Self::Translation | Self::SameLanguageCopy | Self::Revision => false, + } + } +} + +/// Refuse to treat a translation-related edge as a forward state transition. +/// +/// # Errors +/// +/// Always returns [`TranslationEdgeError::TranslationIsNotTransition`]. +pub fn refuse_translation_as_transition( + _kind: TranslationKind, +) -> Result<(), TranslationEdgeError> { + Err(TranslationEdgeError::TranslationIsNotTransition) +} + +/// Refuse to classify a same primary-language pair as a translation. +/// +/// Primary subtags are compared case-insensitively. `en` and `en-US` share +/// a primary language and therefore cannot be a translation. +/// +/// # Errors +/// +/// Returns [`TranslationEdgeError::InvalidLanguageTag`] when either tag is +/// empty or lacks a primary subtag, and +/// [`TranslationEdgeError::SameLanguageIsNotTranslation`] when the primary +/// subtags match. +pub fn refuse_same_language_as_translation( + source_language: &str, + target_language: &str, +) -> Result<(), TranslationEdgeError> { + let source = primary_language_subtag(source_language)?; + let target = primary_language_subtag(target_language)?; + if source.eq_ignore_ascii_case(target) { + return Err(TranslationEdgeError::SameLanguageIsNotTranslation); + } + Ok(()) +} + +/// Fraction of recovered provenance kinds that match known truth. +/// +/// # Errors +/// +/// Returns [`TranslationEdgeError::InvalidEdgePayload`] when either slice is +/// empty or the lengths differ. +pub fn edge_kind_recovery_rate( + truth: &[TranslationKind], + decided: &[TranslationKind], +) -> Result { + if truth.is_empty() || truth.len() != decided.len() { + return Err(TranslationEdgeError::InvalidEdgePayload); + } + let mut matches = 0_u32; + for (truth_kind, decided_kind) in truth.iter().zip(decided) { + if truth_kind == decided_kind { + matches += 1; + } + } + Ok(f64::from(matches) / truth.len() as f64) +} + +fn primary_language_subtag(tag: &str) -> Result<&str, TranslationEdgeError> { + let trimmed = tag.trim(); + if trimmed.is_empty() { + return Err(TranslationEdgeError::InvalidLanguageTag); + } + match trimmed.split_once('-') { + Some(("", _)) => Err(TranslationEdgeError::InvalidLanguageTag), + Some((primary, _)) => Ok(primary), + None => Ok(trimmed), + } +} + +#[cfg(test)] +mod tests { + use super::{ + TranslationKind, edge_kind_recovery_rate, primary_language_subtag, + refuse_same_language_as_translation, refuse_translation_as_transition, + }; + use crate::TranslationEdgeError; + + #[test] + fn local_branches_cover_kinds_languages_and_payloads() { + for kind in [ + TranslationKind::Translation, + TranslationKind::SameLanguageCopy, + TranslationKind::Revision, + ] { + assert!(!kind.is_transition_edge()); + assert_eq!( + TranslationKind::from_wire_name(kind.wire_name()).expect("round-trip"), + kind + ); + assert_eq!( + refuse_translation_as_transition(kind), + Err(TranslationEdgeError::TranslationIsNotTransition) + ); + } + assert_eq!( + TranslationKind::from_wire_name("causes"), + Err(TranslationEdgeError::InvalidEdgePayload) + ); + assert_eq!( + refuse_same_language_as_translation("en-US", "EN"), + Err(TranslationEdgeError::SameLanguageIsNotTranslation) + ); + refuse_same_language_as_translation("en", "fr").expect("cross-language"); + assert_eq!( + primary_language_subtag(""), + Err(TranslationEdgeError::InvalidLanguageTag) + ); + assert_eq!( + primary_language_subtag("-"), + Err(TranslationEdgeError::InvalidLanguageTag) + ); + let truth = [TranslationKind::Translation, TranslationKind::Revision]; + let matched = edge_kind_recovery_rate(&truth, &truth).expect("rate"); + assert!((matched - 1.0).abs() < f64::EPSILON); + let partial = edge_kind_recovery_rate( + &truth, + &[TranslationKind::Translation, TranslationKind::Translation], + ) + .expect("partial"); + assert!((partial - 0.5).abs() < f64::EPSILON); + assert_eq!( + edge_kind_recovery_rate(&[], &[]), + Err(TranslationEdgeError::InvalidEdgePayload) + ); + assert_eq!( + edge_kind_recovery_rate(&truth, &[]), + Err(TranslationEdgeError::InvalidEdgePayload) + ); + } +} diff --git a/crates/translation_edge/src/lib.rs b/crates/translation_edge/src/lib.rs new file mode 100644 index 00000000..ad1d264f --- /dev/null +++ b/crates/translation_edge/src/lib.rs @@ -0,0 +1,21 @@ +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![allow(clippy::cast_precision_loss)] +//! Translation, same-language copy, and revision are not state transitions. +//! +//! Provenance may point to earlier event time. A same primary language tag +//! cannot be classified as a translation (ADR 0002/0003). + +mod error; +mod kind; + +/// Fail-closed translation-edge errors. +pub use error::TranslationEdgeError; +/// Closed vocabulary of translation-related provenance that is not a transition. +pub use kind::TranslationKind; +/// Fraction of recovered provenance kinds that match known truth. +pub use kind::edge_kind_recovery_rate; +/// Refuse to treat a same-language pair as a translation. +pub use kind::refuse_same_language_as_translation; +/// Refuse to treat a translation-related edge as a forward state transition. +pub use kind::refuse_translation_as_transition; diff --git a/crates/translation_edge/tests/crate_contract.rs b/crates/translation_edge/tests/crate_contract.rs new file mode 100644 index 00000000..4d76ba4c --- /dev/null +++ b/crates/translation_edge/tests/crate_contract.rs @@ -0,0 +1,7 @@ +//! Integration contract for the `translation_edge` package identity. + +#[test] +fn package_identity_is_stable() { + let observed = std::hint::black_box(env!("CARGO_PKG_NAME")); + assert_eq!(observed, "translation_edge"); +} diff --git a/crates/translation_edge/tests/edge_kind_contract.rs b/crates/translation_edge/tests/edge_kind_contract.rs new file mode 100644 index 00000000..b48db0f1 --- /dev/null +++ b/crates/translation_edge/tests/edge_kind_contract.rs @@ -0,0 +1,99 @@ +//! Translation, same-language copy, and revision are not state transitions. + +use translation_edge::{ + TranslationEdgeError, TranslationKind, edge_kind_recovery_rate, + refuse_same_language_as_translation, refuse_translation_as_transition, +}; + +#[test] +fn provenance_kinds_cannot_become_state_transitions() { + assert_eq!( + refuse_translation_as_transition(TranslationKind::Translation), + Err(TranslationEdgeError::TranslationIsNotTransition) + ); + assert_eq!( + refuse_translation_as_transition(TranslationKind::SameLanguageCopy), + Err(TranslationEdgeError::TranslationIsNotTransition) + ); + assert_eq!( + refuse_translation_as_transition(TranslationKind::Revision), + Err(TranslationEdgeError::TranslationIsNotTransition) + ); +} + +#[test] +fn same_primary_language_cannot_be_classified_as_translation() { + assert_eq!( + refuse_same_language_as_translation("en", "en-US"), + Err(TranslationEdgeError::SameLanguageIsNotTranslation) + ); + assert_eq!( + refuse_same_language_as_translation("KO", "ko-KR"), + Err(TranslationEdgeError::SameLanguageIsNotTranslation) + ); + refuse_same_language_as_translation("en", "ko").expect("cross-language"); + refuse_same_language_as_translation("zh-Hans", "en-GB").expect("cross-language"); +} + +#[test] +fn empty_or_malformed_language_tags_fail_closed() { + assert_eq!( + refuse_same_language_as_translation("", "en"), + Err(TranslationEdgeError::InvalidLanguageTag) + ); + assert_eq!( + refuse_same_language_as_translation("en", " "), + Err(TranslationEdgeError::InvalidLanguageTag) + ); + assert_eq!( + refuse_same_language_as_translation("-US", "en"), + Err(TranslationEdgeError::InvalidLanguageTag) + ); +} + +#[test] +fn recovered_kinds_match_known_truth_better_than_a_translation_collapse() { + let truth = [ + TranslationKind::Translation, + TranslationKind::SameLanguageCopy, + TranslationKind::Revision, + ]; + let recovered = truth; + let collapsed = [ + TranslationKind::Translation, + TranslationKind::Translation, + TranslationKind::Translation, + ]; + let recovered_rate = edge_kind_recovery_rate(&truth, &recovered).expect("recovered"); + let collapsed_rate = edge_kind_recovery_rate(&truth, &collapsed).expect("collapsed"); + let expected = { + let mut matches = 0_u32; + for (truth_kind, decided_kind) in truth.iter().zip(recovered.iter()) { + if truth_kind == decided_kind { + 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_kind_payloads_fail_closed() { + assert_eq!( + edge_kind_recovery_rate(&[], &[]), + Err(TranslationEdgeError::InvalidEdgePayload) + ); + assert_eq!( + edge_kind_recovery_rate(&[TranslationKind::Translation], &[]), + Err(TranslationEdgeError::InvalidEdgePayload) + ); + assert_eq!( + edge_kind_recovery_rate( + &[TranslationKind::Translation, TranslationKind::Revision], + &[TranslationKind::Translation] + ), + Err(TranslationEdgeError::InvalidEdgePayload) + ); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index c29d9743..8000ee6d 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -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 | +| translation is not a state transition | PRD; ADR 0002/0003 | `translation_edge` translation/copy/revision-vs-transition and same-language-is-not-translation gates 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 | diff --git a/docs/adr/0002-six-clock-temporal-semantics.md b/docs/adr/0002-six-clock-temporal-semantics.md index c06f7d38..49c253bf 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:** partial — typed clocks/intervals are implemented-main (PR #8); translation/copy/revision provenance identity is `translation_edge` on the active PR; remaining clock-identity and split enforcement stay 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..8e8225b1 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; translation/copy/revision identity in `translation_edge` on the active PR; 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. @@ -17,7 +17,7 @@ TEPP represents document, passage, event, entity, revision, translation, evidenc Authors, departments, organizations, customers, partners, competitors, projects, opportunity pools, templates, languages, locations, and episodes form cross-classified, time-varying, multiple-membership assignments. Memberships carry explicit weights where scientifically justified and governed validity intervals. Customer/partner/competitor are role assignments, not immutable entity classes. -Observed relation evidence, inferred relations, and promoted transition edges remain distinct. Relation absence is not silently interpreted as evidence of no relationship. +Observed relation evidence, inferred relations, and promoted transition edges remain distinct. Relation absence is not silently interpreted as evidence of no relationship. Translation, same-language copy, and revision remain provenance and never become reverse state transitions. ## Non-goals diff --git a/docs/adr/README.md b/docs/adr/README.md index 5d3aa546..dcc330ac 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -7,8 +7,8 @@ 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. | -| [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. | +| [0002](0002-six-clock-temporal-semantics.md) | Six-clock temporal semantics and fail-closed historical leakage prevention | Accepted | partial | Typed clocks/intervals are implemented-main (PR #8). Translation/copy/revision provenance identity is `translation_edge` on the active PR. Remaining clock-identity and split enforcement stay accepted-target. | +| [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 and the forward-transition graph are implemented-main; translation/copy/revision identity is `translation_edge` on the active PR; full multilevel estimators 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. | | [0005](0005-posterior-esem-dsem.md) | Posterior-aware ESEM/DSEM and valid compositional coordinates | Accepted | accepted-target | Downstream psychometric authority; upstream topic/network model is clarified by ADR 0012. | | [0006](0006-vram-gpu-nvidia-orchestration.md) | VRAM-adaptive GPU compute and model-credential boundary | Accepted | accepted-target | LLM orchestration policy superseded by ADR 0010; autonomous development authority governed by ADR 0015. | diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 75710ed3..82604540 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -68,6 +68,14 @@ 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. +## Translation versus transition + +Allen, J. F. (1983). Maintaining knowledge about temporal intervals. *Communications of the ACM, 26*(11), 832–843. https://doi.org/10.1145/182.358434 + +Phillips, A., & Davis, M. (2009). *Tags for identifying languages* (RFC 5646). Internet Engineering Task Force. https://doi.org/10.17487/RFC5646 + +Allen (1983) classifies interval relations; it does **not** authorize treating a translation, same-language copy, or revision as a `causes` or `transitions_to` edge. RFC 5646 primary language subtags distinguish translation from same-language variants. + ## Unicode, language tags, and multilingual structure Davis, M., Iancu, L., & Whistler, K. (Eds.). (2024). *Unicode Standard Annex #15: Unicode normalization forms*. Unicode Consortium. diff --git a/docs/research/translation-not-transition.md b/docs/research/translation-not-transition.md new file mode 100644 index 00000000..f7100ca5 --- /dev/null +++ b/docs/research/translation-not-transition.md @@ -0,0 +1,39 @@ +# Translation edges are not state transitions (doctoring) + +## Scope + +`translation_edge` keeps translation, same-language copy, and revision +edges out of the forward state-transition vocabulary. A shared RFC 5646 +primary language subtag cannot be classified as a translation. Recovery +is the computed share of recovered kinds that match known truth. + +This slice does not persist the graph, implement Allen composition, +allocate migration `0008`, or replace `relation_graph`. + +## Authority + +### Normative TEPP contract + +- `docs/adr/0002-six-clock-temporal-semantics.md` — forward + state-transition and input-process-outcome edges never move backward + in event time; citation, revision, translation, and retrospective + edges may point to the past but never become reverse state + transitions. +- `docs/adr/0003-relational-event-multiple-membership.md` — typed + relations distinguish transition from provenance; translation, copy, + and revision remain provenance. + +### Supporting literature + +Allen (1983) classifies interval relations; it does **not** authorize +treating a translation as `causes` or `transitions_to`. RFC 5646 +primary subtags separate translation from same-language copy or +revision. + +Allen, J. F. (1983). Maintaining knowledge about temporal intervals. +*Communications of the ACM, 26*(11), 832–843. +https://doi.org/10.1145/182.358434 + +Phillips, A., & Davis, M. (2009). *Tags for identifying languages* +(RFC 5646). Internet Engineering Task Force. +https://doi.org/10.17487/RFC5646 diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index 295fbae0..13211657 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -18,6 +18,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 | | Multiple membership | `membership_core` | partial | — | unit + ESS weights | Task 7 / PR #12 + #25 | | Forward transition DAG | `relation_graph` | implemented-main | — | unit + cycle rejection | Task 6 / PR #14 | +| Translation is not a state transition | `translation_edge` | accepted-target | active PR | refuse translation/copy/revision-as-transition + same-language-is-not-translation + recovery vs translation collapse | ADR 0002/0003 | | Bitemporal persistence + live SQL port | `persistence_postgres` | partial | backup/restore integrity | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005`/`0006` + event relation/mention/instance + source-artifact + audit-event + concurrent-write (#37–#43 implemented-main) + restore integrity probes (active PR) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#43 + restore integrity | | 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 | diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index c7b1ecf5..d1e8e33a 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -23,6 +23,7 @@ "tepp_simulation", "validation_core", "tepp_api", + "translation_edge", ) REQUIRED_CI_SNIPPETS: tuple[str, ...] = ( diff --git a/tests/quality/test_check_docstrings.py b/tests/quality/test_check_docstrings.py index 2c11f7a5..56d553d2 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 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(contract.EXPECTED_CRATES)) self.assertTrue(set(crate_roots).issubset(sources)) self.assertGreaterEqual(len(sources), len(crate_roots)) self.assertEqual(docstrings.validate_repository(REPOSITORY_ROOT), [])