diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e9bd6ce73..707d2477e 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -64,6 +64,7 @@ boundaries above remain the target modular MSA architecture. | `relation_graph` | typed relations and forward-transition validation | | `membership_core` | time-varying cross-classified multiple membership, Kish ESS, nested ICC with non-nested refusal | | `role_contradiction` | customer and competitor cannot occupy the same group | +| `relation_absence` | unobserved relation pairs are not evidence of no relationship | | `persistence_postgres` | PostgreSQL repositories and migrations | | `corpus_split` | cutoff-safe, relation-aware partitioning | | `tepp_simulation` | known-truth temporal/event data generation | diff --git a/CHANGELOG.md b/CHANGELOG.md index a725c012d..6b03adb92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - `psychometric_core` posterior-aware structural input gates: construct classification, refusal of raw-proportion Pearson/OLS, explicit ALR-versus-ILR geometry boundaries, CPU `f64` OLS recovery, posterior-draw loading point-estimate averaging without Rubin uncertainty claims, invariance-gated latent-mean comparison, and causal-heuristic refusal (ADR 0005 first production slice; no new migration). ### Added +- `relation_absence` identity gate: unobserved relation pairs cannot become evidence of no relationship; recovered observed/inferred/unobserved statuses match known truth at a higher computed rate than collapsing every status to observed (ADR 0003). - `orchestrator_live` loopback HTTP/1.1 listener: `POST /v1/interpretation-runs` binds loopback only, replays matching idempotency keys, and refuses non-loopback binds, table-access hosts, review/Copilot/GitHub credentials, and scientific-authority promotion. Accepted output is always hypothetical. Not TLS termination or model execution (ADR 0010; ADR 0011). - `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). - `episode_membership` identity gate: a document's episode membership cannot diff --git a/Cargo.lock b/Cargo.lock index be52f00c4..b000f155d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1222,6 +1222,10 @@ dependencies = [ "bitflags 2.13.1", ] +[[package]] +name = "relation_absence" +version = "0.1.0" + [[package]] name = "relation_graph" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b2e706ba5..008035fb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "crates/compute_backend", "crates/membership_target", "crates/role_contradiction", + "crates/relation_absence", "crates/analysis_engine", "crates/topic_measurement", "crates/psychometric_core", @@ -111,6 +112,7 @@ default-members = [ "crates/compute_backend", "crates/membership_target", "crates/role_contradiction", + "crates/relation_absence", "crates/analysis_engine", "crates/topic_measurement", "crates/psychometric_core", diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 7a97af45e..a2ca132fe 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -58,6 +58,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Scientific claim-promotion gate doctoring | [`docs/research/scientific-claim-promotion-gates.md`](docs/research/scientific-claim-promotion-gates.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) | +| Relation absence is not negative evidence | [`docs/research/relation-absence-not-negative.md`](docs/research/relation-absence-not-negative.md) | | Adaptive orchestration router doctoring | [`docs/research/adaptive-orchestration-router.md`](docs/research/adaptive-orchestration-router.md) | | Mention-confidence Brier doctoring | [`docs/research/mention-confidence-brier.md`](docs/research/mention-confidence-brier.md) | | Event-intelligence status-gate doctoring | [`docs/research/event-intelligence-status-gates.md`](docs/research/event-intelligence-status-gates.md) | @@ -143,6 +144,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin | Retention/deletion/legal-hold doctoring | [`docs/research/retention-deletion-legal-hold.md`](docs/research/retention-deletion-legal-hold.md) | | Stopword-deletion doctoring | [`docs/research/stopword-deletion.md`](docs/research/stopword-deletion.md) | | Provider-payload minimization doctoring | [`docs/research/provider-payload-minimization.md`](docs/research/provider-payload-minimization.md) | +| Relation absence is not negative evidence | [`docs/research/relation-absence-not-negative.md`](docs/research/relation-absence-not-negative.md) | | Adaptive orchestration router doctoring | [`docs/research/adaptive-orchestration-router.md`](docs/research/adaptive-orchestration-router.md) | | Hourly NIM OpenCode doctoring | [`docs/doctoring/hourly-nim-opencode-development.md`](docs/doctoring/hourly-nim-opencode-development.md) | | Corpus-split leakage-audit wire doctoring | [`docs/research/corpus-split-manifest-wire.md`](docs/research/corpus-split-manifest-wire.md) | diff --git a/README.md b/README.md index 8ca6ed550..e0f6b3dd2 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,11 @@ crates/prompt_source crates/provider_receipt crates/psychometric_core crates/psychometric_fit +crates/relation_absence crates/relation_graph crates/retrospective_edge crates/revision_order +crates/role_contradiction crates/semantic_core crates/service_tls crates/stopword_deletion diff --git a/crates/relation_absence/Cargo.toml b/crates/relation_absence/Cargo.toml new file mode 100644 index 000000000..f235158aa --- /dev/null +++ b/crates/relation_absence/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "relation_absence" +description = "Unobserved relation pairs are not evidence of no relationship." +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/relation_absence/src/error.rs b/crates/relation_absence/src/error.rs new file mode 100644 index 000000000..00e0b89af --- /dev/null +++ b/crates/relation_absence/src/error.rs @@ -0,0 +1,48 @@ +//! Fail-closed relation-absence errors. + +use std::fmt; + +/// A fail-closed relation-absence error. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum RelationAbsenceError { + /// An unobserved pair was treated as evidence of no relationship. + AbsenceIsNotNegative, + /// A recovery slice was empty or length-mismatched. + InvalidObservationPayload, +} + +impl fmt::Display for RelationAbsenceError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::AbsenceIsNotNegative => { + "unobserved relation pairs are not evidence of no relationship" + } + Self::InvalidObservationPayload => "invalid relation-absence payload", + }; + formatter.write_str(message) + } +} + +impl std::error::Error for RelationAbsenceError {} + +#[cfg(test)] +mod tests { + use super::RelationAbsenceError; + + #[test] + fn error_messages_are_stable() { + for (error, message) in [ + ( + RelationAbsenceError::AbsenceIsNotNegative, + "unobserved relation pairs are not evidence of no relationship", + ), + ( + RelationAbsenceError::InvalidObservationPayload, + "invalid relation-absence payload", + ), + ] { + assert_eq!(error.to_string(), message); + } + } +} diff --git a/crates/relation_absence/src/lib.rs b/crates/relation_absence/src/lib.rs new file mode 100644 index 000000000..eae788d7c --- /dev/null +++ b/crates/relation_absence/src/lib.rs @@ -0,0 +1,19 @@ +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![allow(clippy::cast_precision_loss)] +//! Unobserved relation pairs are not evidence of no relationship. +//! +//! Observed and inferred statuses stay distinct. Missing pairs remain +//! unobserved and never become negative edges (ADR 0003). + +mod error; +mod status; + +/// Fail-closed relation-absence errors. +pub use error::RelationAbsenceError; +/// Closed vocabulary of observed, inferred, and unobserved statuses. +pub use status::ObservationStatus; +/// Refuse to treat an unobserved pair as evidence of no relationship. +pub use status::refuse_absence_as_negative; +/// Fraction of recovered observation statuses that match known truth. +pub use status::status_recovery_rate; diff --git a/crates/relation_absence/src/status.rs b/crates/relation_absence/src/status.rs new file mode 100644 index 000000000..cdb88a4a6 --- /dev/null +++ b/crates/relation_absence/src/status.rs @@ -0,0 +1,128 @@ +//! Observed, inferred, and unobserved relation statuses stay distinct. + +use crate::RelationAbsenceError; + +/// Closed vocabulary of relation observation statuses. +/// +/// Unobserved is a missing-status, not a negative edge. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ObservationStatus { + /// Directly observed in source documents or authoritative systems. + Observed, + /// Derived by a model, reasoner, or heuristic and not yet promoted. + Inferred, + /// No observed or inferred evidence exists for this pair. + Unobserved, +} + +impl ObservationStatus { + /// Return the stable wire status name. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::Observed => "observed", + Self::Inferred => "inferred", + Self::Unobserved => "unobserved", + } + } + + /// Parse a stable wire status name. + /// + /// # Errors + /// + /// Returns [`RelationAbsenceError::InvalidObservationPayload`] for + /// unrecognized names, including `no_relationship`. + pub fn from_wire_name(name: &str) -> Result { + match name { + "observed" => Ok(Self::Observed), + "inferred" => Ok(Self::Inferred), + "unobserved" => Ok(Self::Unobserved), + _ => Err(RelationAbsenceError::InvalidObservationPayload), + } + } +} + +/// Refuse to treat an unobserved pair as evidence of no relationship. +/// +/// Observed and inferred statuses are presence evidence. They are not +/// absence, so this gate lets them through. +/// +/// # Errors +/// +/// Returns [`RelationAbsenceError::AbsenceIsNotNegative`] when `status` is +/// [`ObservationStatus::Unobserved`]. +pub fn refuse_absence_as_negative(status: ObservationStatus) -> Result<(), RelationAbsenceError> { + match status { + ObservationStatus::Unobserved => Err(RelationAbsenceError::AbsenceIsNotNegative), + ObservationStatus::Observed | ObservationStatus::Inferred => Ok(()), + } +} + +/// Fraction of recovered observation statuses that match known truth. +/// +/// # Errors +/// +/// Returns [`RelationAbsenceError::InvalidObservationPayload`] when either +/// slice is empty or the lengths differ. +pub fn status_recovery_rate( + truth: &[ObservationStatus], + decided: &[ObservationStatus], +) -> Result { + if truth.is_empty() || truth.len() != decided.len() { + return Err(RelationAbsenceError::InvalidObservationPayload); + } + let mut matches = 0_u32; + for (truth_status, decided_status) in truth.iter().zip(decided) { + if truth_status == decided_status { + matches += 1; + } + } + Ok(f64::from(matches) / truth.len() as f64) +} + +#[cfg(test)] +mod tests { + use super::{ObservationStatus, refuse_absence_as_negative, status_recovery_rate}; + use crate::RelationAbsenceError; + + #[test] + fn local_branches_cover_statuses_payloads_and_wire_names() { + assert_eq!( + refuse_absence_as_negative(ObservationStatus::Unobserved), + Err(RelationAbsenceError::AbsenceIsNotNegative) + ); + refuse_absence_as_negative(ObservationStatus::Observed).expect("observed"); + refuse_absence_as_negative(ObservationStatus::Inferred).expect("inferred"); + for status in [ + ObservationStatus::Observed, + ObservationStatus::Inferred, + ObservationStatus::Unobserved, + ] { + assert_eq!( + ObservationStatus::from_wire_name(status.wire_name()).expect("round-trip"), + status + ); + } + assert_eq!( + ObservationStatus::from_wire_name("no_relationship"), + Err(RelationAbsenceError::InvalidObservationPayload) + ); + let truth = [ObservationStatus::Observed, ObservationStatus::Unobserved]; + let matched = status_recovery_rate(&truth, &truth).expect("rate"); + assert!((matched - 1.0).abs() < f64::EPSILON); + let partial = status_recovery_rate( + &truth, + &[ObservationStatus::Observed, ObservationStatus::Observed], + ) + .expect("partial"); + assert!((partial - 0.5).abs() < f64::EPSILON); + assert_eq!( + status_recovery_rate(&[], &[]), + Err(RelationAbsenceError::InvalidObservationPayload) + ); + assert_eq!( + status_recovery_rate(&truth, &[]), + Err(RelationAbsenceError::InvalidObservationPayload) + ); + } +} diff --git a/crates/relation_absence/tests/absence_status_contract.rs b/crates/relation_absence/tests/absence_status_contract.rs new file mode 100644 index 000000000..e3e71dcb2 --- /dev/null +++ b/crates/relation_absence/tests/absence_status_contract.rs @@ -0,0 +1,62 @@ +//! Observed, inferred, and unobserved statuses stay distinct. + +use relation_absence::{ + ObservationStatus, RelationAbsenceError, refuse_absence_as_negative, status_recovery_rate, +}; + +#[test] +fn unobserved_pairs_cannot_become_negative_evidence() { + assert_eq!( + refuse_absence_as_negative(ObservationStatus::Unobserved), + Err(RelationAbsenceError::AbsenceIsNotNegative) + ); + refuse_absence_as_negative(ObservationStatus::Observed).expect("observed is not absence"); + refuse_absence_as_negative(ObservationStatus::Inferred).expect("inferred is not absence"); +} + +#[test] +fn recovered_statuses_match_known_truth_better_than_an_absence_collapse() { + let truth = [ + ObservationStatus::Observed, + ObservationStatus::Inferred, + ObservationStatus::Unobserved, + ]; + let recovered = truth; + let collapsed = [ + ObservationStatus::Observed, + ObservationStatus::Observed, + ObservationStatus::Observed, + ]; + let recovered_rate = status_recovery_rate(&truth, &recovered).expect("recovered"); + let collapsed_rate = status_recovery_rate(&truth, &collapsed).expect("collapsed"); + let expected = { + let mut matches = 0_u32; + for (truth_status, decided_status) in truth.iter().zip(recovered.iter()) { + if truth_status == decided_status { + 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_status_payloads_fail_closed() { + assert_eq!( + status_recovery_rate(&[], &[]), + Err(RelationAbsenceError::InvalidObservationPayload) + ); + assert_eq!( + status_recovery_rate(&[ObservationStatus::Observed], &[]), + Err(RelationAbsenceError::InvalidObservationPayload) + ); + assert_eq!( + status_recovery_rate( + &[ObservationStatus::Observed, ObservationStatus::Unobserved], + &[ObservationStatus::Observed] + ), + Err(RelationAbsenceError::InvalidObservationPayload) + ); +} diff --git a/crates/relation_absence/tests/crate_contract.rs b/crates/relation_absence/tests/crate_contract.rs new file mode 100644 index 000000000..940fc177d --- /dev/null +++ b/crates/relation_absence/tests/crate_contract.rs @@ -0,0 +1,7 @@ +//! Integration contract for the `relation_absence` package identity. + +#[test] +fn package_identity_is_stable() { + let observed = std::hint::black_box(env!("CARGO_PKG_NAME")); + assert_eq!(observed, "relation_absence"); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index b564e7d0d..ebf42446b 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -14,6 +14,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | six distinct clocks and uncertain intervals | PRD; ADR 0002; ISO 24617-1:2012; Hobbs & Pan (2017) | merged PR #8 `temporal_core` on protected main; PR #5 historical lineage only | implemented-main | | Allen relation algebra/bounded closure | ADR 0002; Allen (1983) | merged 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; `relation_absence` unobserved-is-not-negative gate on the active PR | partial | | 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; Brier calibration on the active PR; full intelligence stack remaining | partial | | 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 | diff --git a/docs/adr/0003-relational-event-multiple-membership.md b/docs/adr/0003-relational-event-multiple-membership.md index 069f031d2..0bed45211 100644 --- a/docs/adr/0003-relational-event-multiple-membership.md +++ b/docs/adr/0003-relational-event-multiple-membership.md @@ -7,7 +7,7 @@ **Implementation maturity:** partial — membership network, event mention/instance separation, inferred/evidential/retrospective status gates, summary/source identity separation, template-copy/source identity separation, typed forward-only relation graph, strict input-process-outcome ordering, nested ICC refusal, and subevent parent-window containment are implemented-main; full multilevel/MMMC estimators and remaining persistence remain accepted-target. **Date:** 2026-08-24 **Decision status:** Accepted -**Implementation maturity:** partial — membership network/roles with Kish ESS and nested ICC (cross-classified/multiple-membership refusal), event mention/instance separation, the typed forward-only relation graph, and the copy/summary/outcome-order/support/inferred-status/retrospective-reporting/location identity gates are implemented-main; typed target-kind membership identity in `membership_target` is on PR #131; multilevel psychometric estimators and remaining persistence details follow ADR 0013 and [`docs/TRACEABILITY.md`](../TRACEABILITY.md) as accepted-target. The customer/competitor role-contradiction gate ships in `role_contradiction` on this PR. +**Implementation maturity:** partial — membership network/roles with Kish ESS and nested ICC (cross-classified/multiple-membership refusal), event mention/instance separation, the typed forward-only relation graph, and the copy/summary/outcome-order/support/inferred-status/retrospective-reporting/location identity gates are implemented-main; typed target-kind membership identity in `membership_target` is on PR #131; multilevel psychometric estimators and remaining persistence details follow ADR 0013 and [`docs/TRACEABILITY.md`](../TRACEABILITY.md) as accepted-target. The customer/competitor role-contradiction gate ships in `role_contradiction` on this PR. The absence-is-not-negative identity gate ships in `relation_absence` on this PR. **Supersedes:** None. ADR 0016 owns TDT/CHRONOS event-intelligence task semantics; this ADR remains authoritative for ontology, relation, role, and membership structure. ## Context diff --git a/docs/adr/README.md b/docs/adr/README.md index fbe697737..04d4b23fb 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -37,6 +37,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [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 implemented-main (PR #12); copy-versus-source identity is `copy_identity` on the active PR; full multilevel estimators 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 this PR; full multilevel estimators 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); absence-is-not-negative identity is `relation_absence` on this PR; full multilevel estimators 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); summary-versus-source identity is `summarizes_edge` on the active PR; full multilevel estimators 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); inferred-versus-observed identity is `inferred_status` on the active PR; full multilevel estimators 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 | active-PR | Evidential-vs-transition gate in `support_edge` on the active PR; remaining graph/split enforcement stays accepted-target. | diff --git a/docs/research/relation-absence-not-negative.md b/docs/research/relation-absence-not-negative.md new file mode 100644 index 000000000..d3853f062 --- /dev/null +++ b/docs/research/relation-absence-not-negative.md @@ -0,0 +1,29 @@ +# Relation absence is not evidence of no relationship (doctoring) + +## Scope + +`relation_absence` keeps observed, inferred, and unobserved statuses +distinct. Recovery is the computed share of recovered statuses that +match known truth. + +This slice does not persist the graph, promote inferred edges, or +replace `relation_graph`. + +## Authority + +### Normative TEPP contract + +- `docs/adr/0003-relational-event-multiple-membership.md` — observed + relation evidence, inferred relations, and promoted transition edges + remain distinct. Relation absence is not silently interpreted as + evidence of no relationship. + +### Supporting literature + +Altman and Bland (1995) separate a missing comparison from a negative +finding. Treating an unobserved pair as "no relationship" converts +non-observation into a closed-world denial. + +Altman, D. G., & Bland, J. M. (1995). Absence of evidence is not +evidence of absence. *BMJ, 311*(7003), 485. +https://doi.org/10.1136/bmj.311.7003.485 diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 9c42da980..a24e49248 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -245,6 +245,12 @@ Browne, W. J., Goldstein, H., & Rasbash, J. (2001). Multiple membership multiple 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). +## Relation observation and missingness + +Altman, D. G., & Bland, J. M. (1995). Absence of evidence is not evidence of absence. *BMJ, 311*(7003), 485. https://doi.org/10.1136/bmj.311.7003.485 + +Unobserved relation pairs are missing statuses, not negative edges. TEPP refuses treating absence as evidence of no relationship (Altman & Bland, 1995). + ## 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 7a6977fd9..3d6881d40 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -39,6 +39,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Retrospective reporting identity | `retrospective_edge` | accepted-target | active PR | refuse retrospective-as-transition/translation + recovery vs forward collapse | ADR 0002/0003 | | Inferred-versus-observed promotion | `inferred_status` | accepted-target | active PR | refuse inferred-as-observed/transition + recovery vs observed collapse | ADR 0003 | | Evidential-vs-transition gate | `support_edge` | active-PR | this PR | recovered kind rate vs support collapse | ADR 0002/0003 | +| Relation absence is not negative evidence | `relation_absence` | accepted-target | active PR | refuse unobserved-as-negative + recovery vs observed collapse | ADR 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 | | 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 (#44 implemented-main) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#44 + restore integrity | | Bitemporal persistence + live SQL port | `persistence_postgres` | partial | typed `text_segment` SQL | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005`/`0006` + event relation/mention/instance + source-artifact + audit-event + concurrent-write + restore integrity (#37–#44 implemented-main) + typed `text_segment` insert/cutoff lookup (active PR) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#44 + text-segment SQL | diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index 0d1b5758b..b075251b0 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -66,6 +66,7 @@ "compute_backend", "membership_target", "role_contradiction", + "relation_absence", "analysis_engine", "topic_measurement", "psychometric_core",