From 8981696f58892a1ace9baeb6224ac55b436b8110 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 08:21:08 +0900 Subject: [PATCH] feat(membership): refuse customer-competitor overlap Customer, partner, and competitor remain contextual roles (ADR 0003). A customer/competitor pair in one group fails closed. Recovery is the computed share of role labels that match known truth versus collapsing every commercial role to customer. --- ARCHITECTURE.md | 1 + CHANGELOG.md | 1 + Cargo.lock | 4 + Cargo.toml | 2 + README.md | 3 +- crates/role_contradiction/Cargo.toml | 17 ++ crates/role_contradiction/src/error.rs | 55 ++++++ crates/role_contradiction/src/lib.rs | 24 +++ crates/role_contradiction/src/role.rs | 177 ++++++++++++++++++ .../tests/crate_contract.rs | 7 + .../tests/role_contradiction_contract.rs | 103 ++++++++++ docs/TRACEABILITY.md | 2 +- ...03-relational-event-multiple-membership.md | 2 +- docs/adr/README.md | 2 +- docs/research/role-contradiction-identity.md | 33 ++++ docs/research/standards-and-literature.md | 8 + docs/validation/temporal-event-foundation.md | 1 + scripts/check_workspace_contract.py | 1 + 18 files changed, 439 insertions(+), 4 deletions(-) create mode 100644 crates/role_contradiction/Cargo.toml create mode 100644 crates/role_contradiction/src/error.rs create mode 100644 crates/role_contradiction/src/lib.rs create mode 100644 crates/role_contradiction/src/role.rs create mode 100644 crates/role_contradiction/tests/crate_contract.rs create mode 100644 crates/role_contradiction/tests/role_contradiction_contract.rs create mode 100644 docs/research/role-contradiction-identity.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ffe514db..9eb904eb 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 | +| `role_contradiction` | customer and competitor cannot occupy the same group | 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 c1cc6e87..3a3bb84e 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 +- `role_contradiction` identity gate: customer and competitor cannot occupy the same group; recovered commercial-role labels match known truth at a higher computed rate than collapsing every role to customer (ADR 0003). - `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. - `tepp_api` naruon HTTP interchange: versioned `https` POST contracts for analysis-run create and modular export authorization that refuse table-access URLs, review/Copilot credential headers, reserved standard-header redefinition, principal-only export idempotency keys, and lexical inference claims (ADR 0011). diff --git a/Cargo.lock b/Cargo.lock index 372a55f4..a1d64478 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -943,6 +943,10 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "role_contradiction" +version = "0.1.0" + [[package]] name = "rustc-demangle" version = "0.1.28" diff --git a/Cargo.toml b/Cargo.toml index 92565940..65a36594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/role_contradiction", ] default-members = [ "crates/evidence_core", @@ -23,6 +24,7 @@ default-members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/role_contradiction", ] [workspace.package] diff --git a/README.md b/README.md index ae74015d..f2cde563 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/role_contradiction ``` ## Local verification diff --git a/crates/role_contradiction/Cargo.toml b/crates/role_contradiction/Cargo.toml new file mode 100644 index 00000000..330cc6f3 --- /dev/null +++ b/crates/role_contradiction/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "role_contradiction" +description = "Customer and competitor cannot occupy the same group at once." +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/role_contradiction/src/error.rs b/crates/role_contradiction/src/error.rs new file mode 100644 index 00000000..d7cfaf1c --- /dev/null +++ b/crates/role_contradiction/src/error.rs @@ -0,0 +1,55 @@ +//! Fail-closed role-contradiction errors. + +use std::fmt; + +/// A fail-closed role-contradiction error. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum RoleContradictionError { + /// Customer and competitor were assigned in the same group. + CustomerCompetitorOverlap, + /// A contextual role was treated as a permanent entity class. + RoleIsNotEntityClass, + /// A recovery slice was empty or length-mismatched. + InvalidRolePayload, +} + +impl fmt::Display for RoleContradictionError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::CustomerCompetitorOverlap => { + "customer and competitor cannot occupy the same group" + } + Self::RoleIsNotEntityClass => "contextual role is not an entity class", + Self::InvalidRolePayload => "invalid role-contradiction payload", + }; + formatter.write_str(message) + } +} + +impl std::error::Error for RoleContradictionError {} + +#[cfg(test)] +mod tests { + use super::RoleContradictionError; + + #[test] + fn error_messages_are_stable() { + for (error, message) in [ + ( + RoleContradictionError::CustomerCompetitorOverlap, + "customer and competitor cannot occupy the same group", + ), + ( + RoleContradictionError::RoleIsNotEntityClass, + "contextual role is not an entity class", + ), + ( + RoleContradictionError::InvalidRolePayload, + "invalid role-contradiction payload", + ), + ] { + assert_eq!(error.to_string(), message); + } + } +} diff --git a/crates/role_contradiction/src/lib.rs b/crates/role_contradiction/src/lib.rs new file mode 100644 index 00000000..808a9c10 --- /dev/null +++ b/crates/role_contradiction/src/lib.rs @@ -0,0 +1,24 @@ +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![allow(clippy::cast_precision_loss)] +//! Customer and competitor cannot occupy the same group at once. +//! +//! Customer, partner, and competitor are time-varying roles, not permanent +//! entity classes. A customer/competitor pair in one group fails closed +//! (ADR 0003). + +mod error; +mod role; + +/// Fail-closed role-contradiction errors. +pub use error::RoleContradictionError; +/// Fraction of recovered contextual roles that match known truth. +pub use role::identity_recovery_rate; +/// Refuse a contradictory customer/competitor pair in one group. +pub use role::refuse_contradictory_roles; +/// Refuse to treat a contextual role as a permanent entity class. +pub use role::refuse_role_as_entity_class; +/// Return whether two roles contradict in the same group. +pub use role::roles_contradict; +/// Closed vocabulary of commercial roles that can change over time. +pub use role::ContextualRole; diff --git a/crates/role_contradiction/src/role.rs b/crates/role_contradiction/src/role.rs new file mode 100644 index 00000000..8d37287b --- /dev/null +++ b/crates/role_contradiction/src/role.rs @@ -0,0 +1,177 @@ +//! Contextual commercial roles that are not permanent entity classes. + +use crate::RoleContradictionError; + +/// Closed vocabulary of commercial roles that can change over time. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ContextualRole { + /// Customer role in one commercial context. + Customer, + /// Partner role in one commercial context. + Partner, + /// Competitor role in one commercial context. + Competitor, +} + +impl ContextualRole { + /// Return the stable wire role name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::Customer => "customer", + Self::Partner => "partner", + Self::Competitor => "competitor", + } + } + + /// Parse a stable wire role name. + /// + /// # Errors + /// + /// Returns [`RoleContradictionError::InvalidRolePayload`] for unrecognized + /// names. + pub fn from_wire_name(name: &str) -> Result { + match name { + "customer" => Ok(Self::Customer), + "partner" => Ok(Self::Partner), + "competitor" => Ok(Self::Competitor), + _ => Err(RoleContradictionError::InvalidRolePayload), + } + } +} + +/// Return whether two roles contradict in the same group. +/// +/// Customer and competitor cannot occupy the same group. Partner may coexist +/// with either role (coopetition or a customer-partner) without rewriting +/// entity identity. +#[must_use] +pub const fn roles_contradict(left: ContextualRole, right: ContextualRole) -> bool { + matches!( + (left, right), + (ContextualRole::Customer, ContextualRole::Competitor) + | (ContextualRole::Competitor, ContextualRole::Customer) + ) +} + +/// Refuse a contradictory customer/competitor pair in one group. +/// +/// # Errors +/// +/// Returns [`RoleContradictionError::CustomerCompetitorOverlap`] when the pair +/// is customer and competitor. Compatible pairs succeed. +pub fn refuse_contradictory_roles( + left: ContextualRole, + right: ContextualRole, +) -> Result<(), RoleContradictionError> { + if roles_contradict(left, right) { + return Err(RoleContradictionError::CustomerCompetitorOverlap); + } + Ok(()) +} + +/// Refuse to treat a contextual role as a permanent entity class. +/// +/// # Errors +/// +/// Always returns [`RoleContradictionError::RoleIsNotEntityClass`]. +pub fn refuse_role_as_entity_class(_role: ContextualRole) -> Result<(), RoleContradictionError> { + Err(RoleContradictionError::RoleIsNotEntityClass) +} + +/// Fraction of recovered contextual roles that match known truth. +/// +/// # Errors +/// +/// Returns [`RoleContradictionError::InvalidRolePayload`] when either slice is +/// empty or the lengths differ. +pub fn identity_recovery_rate( + truth: &[ContextualRole], + decided: &[ContextualRole], +) -> Result { + if truth.is_empty() || truth.len() != decided.len() { + return Err(RoleContradictionError::InvalidRolePayload); + } + 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) +} + +#[cfg(test)] +mod tests { + use super::{ + identity_recovery_rate, refuse_contradictory_roles, refuse_role_as_entity_class, + roles_contradict, ContextualRole, + }; + use crate::RoleContradictionError; + + #[test] + fn local_branches_cover_pairs_payloads_and_wire_names() { + assert!(roles_contradict( + ContextualRole::Customer, + ContextualRole::Competitor + )); + assert!(roles_contradict( + ContextualRole::Competitor, + ContextualRole::Customer + )); + assert!(!roles_contradict( + ContextualRole::Customer, + ContextualRole::Partner + )); + assert!(!roles_contradict( + ContextualRole::Partner, + ContextualRole::Competitor + )); + assert!(!roles_contradict( + ContextualRole::Customer, + ContextualRole::Customer + )); + assert_eq!( + refuse_contradictory_roles(ContextualRole::Customer, ContextualRole::Competitor), + Err(RoleContradictionError::CustomerCompetitorOverlap) + ); + assert_eq!( + refuse_contradictory_roles(ContextualRole::Competitor, ContextualRole::Customer), + Err(RoleContradictionError::CustomerCompetitorOverlap) + ); + refuse_contradictory_roles(ContextualRole::Customer, ContextualRole::Partner) + .expect("compatible"); + refuse_contradictory_roles(ContextualRole::Partner, ContextualRole::Competitor) + .expect("coopetition"); + for role in [ + ContextualRole::Customer, + ContextualRole::Partner, + ContextualRole::Competitor, + ] { + assert_eq!( + ContextualRole::from_wire_name(role.wire_name()).expect("round-trip"), + role + ); + assert_eq!( + refuse_role_as_entity_class(role), + Err(RoleContradictionError::RoleIsNotEntityClass) + ); + } + assert_eq!( + ContextualRole::from_wire_name("organization"), + Err(RoleContradictionError::InvalidRolePayload) + ); + let matched = + identity_recovery_rate(&[ContextualRole::Customer], &[ContextualRole::Customer]) + .expect("rate"); + assert!((matched - 1.0).abs() < f64::EPSILON); + assert_eq!( + identity_recovery_rate(&[], &[]), + Err(RoleContradictionError::InvalidRolePayload) + ); + assert_eq!( + identity_recovery_rate(&[ContextualRole::Customer], &[]), + Err(RoleContradictionError::InvalidRolePayload) + ); + } +} diff --git a/crates/role_contradiction/tests/crate_contract.rs b/crates/role_contradiction/tests/crate_contract.rs new file mode 100644 index 00000000..920f4e8a --- /dev/null +++ b/crates/role_contradiction/tests/crate_contract.rs @@ -0,0 +1,7 @@ +//! Integration contract for the `role_contradiction` package identity. + +#[test] +fn package_identity_is_stable() { + let observed = std::hint::black_box(env!("CARGO_PKG_NAME")); + assert_eq!(observed, "role_contradiction"); +} diff --git a/crates/role_contradiction/tests/role_contradiction_contract.rs b/crates/role_contradiction/tests/role_contradiction_contract.rs new file mode 100644 index 00000000..0cf83f46 --- /dev/null +++ b/crates/role_contradiction/tests/role_contradiction_contract.rs @@ -0,0 +1,103 @@ +//! Customer and competitor cannot occupy the same group at once. + +use role_contradiction::{ + identity_recovery_rate, refuse_contradictory_roles, refuse_role_as_entity_class, + roles_contradict, ContextualRole, RoleContradictionError, +}; + +#[test] +fn customer_and_competitor_cannot_share_a_group() { + assert!(roles_contradict( + ContextualRole::Customer, + ContextualRole::Competitor + )); + assert!(roles_contradict( + ContextualRole::Competitor, + ContextualRole::Customer + )); + assert!(!roles_contradict( + ContextualRole::Customer, + ContextualRole::Partner + )); + assert!(!roles_contradict( + ContextualRole::Partner, + ContextualRole::Competitor + )); + assert!(!roles_contradict( + ContextualRole::Customer, + ContextualRole::Customer + )); + assert_eq!( + refuse_contradictory_roles(ContextualRole::Customer, ContextualRole::Competitor), + Err(RoleContradictionError::CustomerCompetitorOverlap) + ); + assert_eq!( + refuse_contradictory_roles(ContextualRole::Competitor, ContextualRole::Customer), + Err(RoleContradictionError::CustomerCompetitorOverlap) + ); + refuse_contradictory_roles(ContextualRole::Customer, ContextualRole::Partner) + .expect("customer-partner is not a contradiction"); + refuse_contradictory_roles(ContextualRole::Partner, ContextualRole::Competitor) + .expect("partner-competitor is coopetition, not a contradiction"); +} + +#[test] +fn commercial_roles_are_not_entity_classes() { + for role in [ + ContextualRole::Customer, + ContextualRole::Partner, + ContextualRole::Competitor, + ] { + assert_eq!( + refuse_role_as_entity_class(role), + Err(RoleContradictionError::RoleIsNotEntityClass) + ); + } +} + +#[test] +fn recovered_roles_match_known_truth_better_than_a_commercial_collapse() { + let truth = [ + ContextualRole::Customer, + ContextualRole::Partner, + ContextualRole::Competitor, + ]; + let recovered = truth; + let collapsed = [ + ContextualRole::Customer, + ContextualRole::Customer, + ContextualRole::Customer, + ]; + let recovered_rate = identity_recovery_rate(&truth, &recovered).expect("recovered"); + let collapsed_rate = identity_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!( + identity_recovery_rate(&[], &[]), + Err(RoleContradictionError::InvalidRolePayload) + ); + assert_eq!( + identity_recovery_rate(&[ContextualRole::Customer], &[]), + Err(RoleContradictionError::InvalidRolePayload) + ); + assert_eq!( + identity_recovery_rate( + &[ContextualRole::Customer, ContextualRole::Competitor], + &[ContextualRole::Customer] + ), + Err(RoleContradictionError::InvalidRolePayload) + ); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index c29d9743..1791d505 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -14,7 +14,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | 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 | | 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 | +| time-varying cross-classified multiple membership | PRD; ADR 0003 | `membership_core` network on protected main; `role_contradiction` customer/competitor identity on the active PR; 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 | diff --git a/docs/adr/0003-relational-event-multiple-membership.md b/docs/adr/0003-relational-event-multiple-membership.md index c5b1a154..8182d7ce 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; customer/competitor role-contradiction identity in `role_contradiction` on the active PR; typed relation graph with forward-only transitions 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. diff --git a/docs/adr/README.md b/docs/adr/README.md index 1a9a7b31..ce093437 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -8,7 +8,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio |---|---|---|---|---| | [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. | +| [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 implemented-main (PR #12); customer/competitor contradiction is `role_contradiction` 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/role-contradiction-identity.md b/docs/research/role-contradiction-identity.md new file mode 100644 index 00000000..539a58ad --- /dev/null +++ b/docs/research/role-contradiction-identity.md @@ -0,0 +1,33 @@ +# Customer/competitor role contradiction (doctoring) + +## Scope + +`role_contradiction` keeps customer, partner, and competitor as +time-varying contextual roles. A customer/competitor pair in one group +fails closed. Recovery is the computed share of recovered role labels +that match known truth. + +This slice does not persist memberships, allocate migration `0008`, or +replace `membership_core`. + +## Authority + +### Normative TEPP contract + +- `docs/adr/0003-relational-event-multiple-membership.md` — customer, + partner, and competitor are role assignments, not immutable entity + classes; contradictory role assertions fail closed. + +### Supporting literature + +Biddle (1986) treats roles as contextual, potentially conflicting +positions rather than permanent types. Browne, Goldstein, and Rasbash +(2001) keep crossed classifications distinct so one unit can belong to +several non-nested groups without collapsing those roles. + +Biddle, B. J. (1986). Recent developments in role theory. *Annual Review +of Sociology, 12*, 67–92. https://doi.org/10.1146/annurev.so.12.080186.000435 + +Browne, W. J., Goldstein, H., & Rasbash, J. (2001). Multiple membership +multiple classification (MMMC) models. *Statistical Modelling, 1*(2), +103–124. https://doi.org/10.1177/1471082X0100100201 diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index b4b14468..9714f10d 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. +## Roles, membership, and multilevel structure + +Biddle, B. J. (1986). Recent developments in role theory. *Annual Review of Sociology, 12*, 67–92. https://doi.org/10.1146/annurev.so.12.080186.000435 + +Browne, W. J., Goldstein, H., & Rasbash, J. (2001). Multiple membership multiple classification (MMMC) models. *Statistical Modelling, 1*(2), 103–124. https://doi.org/10.1177/1471082X0100100201 + +Customer, partner, and competitor are contextual roles. TEPP refuses a customer/competitor pair in the same group and does not collapse those roles into one commercial class (Biddle, 1986; Browne et al., 2001). + ## 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/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index 295fbae0..96c8d4fb 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -17,6 +17,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Allen path-consistency | `temporal_core` | implemented-main | — | unit + budget tests | Task 4 / PR #9 | | 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 | +| Customer/competitor role contradiction | `role_contradiction` | accepted-target | active PR | refuse overlap + recovery vs commercial collapse | ADR 0003 role assertions | | Forward transition DAG | `relation_graph` | implemented-main | — | unit + cycle rejection | Task 6 / PR #14 | | 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 | diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index c7b1ecf5..c10b6351 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -23,6 +23,7 @@ "tepp_simulation", "validation_core", "tepp_api", + "role_contradiction", ) REQUIRED_CI_SNIPPETS: tuple[str, ...] = (