diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a0cb13b45..381747835 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 | +| `provider_receipt` | provider-disclosure field-code receipts; source text and identity are not disclosable | | `operational_log` | operational logs; `try_record` is the only recording API; source text and source identity are not loggable; `persistence_postgres` `audit_event` inserts call the same gate | | `service_tls` | production TLS bind gates and rustls server config | | `derived_sensitivity` | derived topic/factor/relation outputs inherit source sensitivity | diff --git a/CHANGELOG.md b/CHANGELOG.md index babb6427b..bc16f8252 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 +- `provider_receipt` disclosure audit: a receipt records purpose and field codes sent to a model provider; source text, source identity, and blanket PII masking fail closed; recovered field codes match known truth at a higher computed rate than a collapsed set (ADR 0009). - `tepp_api` corpus-split leakage-audit manifest v1: cutoff exclusion counts, relation-component and partition digests, governed link-kind vocabulary, and a canonical `SHA-256` that binds to `corpus_split_manifest` without exporting source text. - `persistence_postgres` `audit_event` inserts call `operational_log::try_record` before SQL is rendered: author/customer/project source text, source identity, and blanket-mask grants cannot enter `INSERT INTO audit_event`; clear inspection still persists a validated action code (ADR 0009; ISO/IEC 29100:2024). No new migration number. `OperationalLogRecord::new` stays crate-private. - `operational_log` source separation: `try_record` is the only recording API and inspects source text, source identity, and blanket-mask intent before creating a line; `OperationalLogRecord::new` is crate-private; a source-identity `&str` cannot become an `AnalyticalSubject`; privileged-export / identity-mapping / diagnosis action codes keep author, customer, and project memberships distinct; replayed lines match known truth at a higher computed rate than a collapsed single-action or collapsed-subject log (ADR 0009; ISO/IEC 29100:2024). The live docstring crate-root count is bound to `EXPECTED_CRATES` so the eleventh crate cannot fail a hard-coded `10`. @@ -104,6 +105,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ### Changed +- Kept one maturity row per capability in the traceability matrix while recording the active provider-receipt evidence without duplicating or downgrading existing capabilities. - `tepp_api` corpus-split manifest validation now rejects governed link-kind arrays that are unsorted or duplicated, keeping untrusted JSON aligned with the schema's unique canonical representation and preventing equivalent audits from receiving different valid digests. - Grounded `derived_sensitivity` doctoring on GDPR Article 4(1)/Recital 26 and WP29 Opinion 4/2007 (WP 136) as read from the official texts, and replaced the withdrawn ISO/IEC 29100:2011 use-limitation overclaim with the current 29100:2024 catalogue edition without quoting unread clause text. - Added APA 7th method citations (Allen 1983; ISO 24617-1:2012; Hobbs & Pan 2017; Fox & Glas 2001; AERA/APA/NCME 2014; Blei & Lafferty 2006; Roberts et al. 2014, 2019; Chang & Blei 2009; Mimno et al. 2009; Asparouhov & Muthén 2009; Asparouhov et al. 2018; Marsh et al. 2014; Aitchison 1982; Allan 2002; Li et al. 2021; Anagnostopoulos et al. 2013) into ADRs 0002–0005, 0012, and 0016, plus TRACEABILITY/ARCHITECTURE/TRD method rows. Clarified that TRSL-TM is the product contract, STM-style logistic-normal is the reference family, ESEM/DSEM/TDT/CHRONOS remain accepted-target, and merged PRs #8/#9—not superseded drafts #5/#6—are the protected-main temporal lineage. diff --git a/Cargo.lock b/Cargo.lock index 657128564..adbee7f20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,6 +888,10 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "provider_receipt" +version = "0.1.0" + [[package]] name = "quote" version = "1.0.47" diff --git a/Cargo.toml b/Cargo.toml index 899064dac..fb389453a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/provider_receipt", "crates/operational_log", "crates/service_tls", "crates/derived_sensitivity", @@ -32,6 +33,7 @@ default-members = [ "crates/tepp_simulation", "crates/validation_core", "crates/tepp_api", + "crates/provider_receipt", "crates/operational_log", "crates/service_tls", "crates/derived_sensitivity", diff --git a/README.md b/README.md index 8f82f80ef..be1837256 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ crates/corpus_split crates/tepp_simulation crates/validation_core crates/tepp_api +crates/provider_receipt crates/operational_log crates/service_tls crates/derived_sensitivity diff --git a/crates/provider_receipt/Cargo.toml b/crates/provider_receipt/Cargo.toml new file mode 100644 index 000000000..cecc91c3b --- /dev/null +++ b/crates/provider_receipt/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "provider_receipt" +description = "Provider-disclosure receipts that refuse source text and identity." +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/provider_receipt/src/error.rs b/crates/provider_receipt/src/error.rs new file mode 100644 index 000000000..805b5e8e3 --- /dev/null +++ b/crates/provider_receipt/src/error.rs @@ -0,0 +1,64 @@ +//! Fail-closed provider-receipt errors. + +use std::fmt; + +/// A fail-closed provider-receipt error. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum ProviderReceiptError { + /// Raw source text was supplied to a provider receipt. + SourceTextNotDisclosable, + /// Source identity was supplied to a provider receipt. + SourceIdentityNotDisclosable, + /// Blanket PII masking was treated as a disclosure grant. + BlanketMaskIsNotAuthorization, + /// A receipt or recovery slice was empty or length-mismatched. + InvalidReceiptPayload, +} + +impl fmt::Display for ProviderReceiptError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::SourceTextNotDisclosable => "source text cannot appear in a provider receipt", + Self::SourceIdentityNotDisclosable => { + "source identity cannot appear in a provider receipt" + } + Self::BlanketMaskIsNotAuthorization => { + "blanket PII masking is not provider-disclosure authorization" + } + Self::InvalidReceiptPayload => "invalid provider-receipt payload", + }; + formatter.write_str(message) + } +} + +impl std::error::Error for ProviderReceiptError {} + +#[cfg(test)] +mod tests { + use super::ProviderReceiptError; + + #[test] + fn error_messages_are_stable() { + for (error, message) in [ + ( + ProviderReceiptError::SourceTextNotDisclosable, + "source text cannot appear in a provider receipt", + ), + ( + ProviderReceiptError::SourceIdentityNotDisclosable, + "source identity cannot appear in a provider receipt", + ), + ( + ProviderReceiptError::BlanketMaskIsNotAuthorization, + "blanket PII masking is not provider-disclosure authorization", + ), + ( + ProviderReceiptError::InvalidReceiptPayload, + "invalid provider-receipt payload", + ), + ] { + assert_eq!(error.to_string(), message); + } + } +} diff --git a/crates/provider_receipt/src/lib.rs b/crates/provider_receipt/src/lib.rs new file mode 100644 index 000000000..376aea957 --- /dev/null +++ b/crates/provider_receipt/src/lib.rs @@ -0,0 +1,24 @@ +#![forbid(unsafe_code)] +#![deny(missing_docs)] +#![allow(clippy::cast_precision_loss)] +//! Provider-disclosure receipts that refuse source text and identity. +//! +//! A receipt records which field codes were sent to a model provider under one +//! purpose. It cannot carry source text or source identity, and blanket PII +//! masking is not a disclosure grant (ADR 0009). + +mod error; +mod receipt; + +/// Fail-closed provider-receipt errors. +pub use error::ProviderReceiptError; +/// One provider-disclosure receipt of field codes under a purpose. +pub use receipt::ProviderReceipt; +/// Fraction of recovered field codes that match known truth. +pub use receipt::receipt_recovery_rate; +/// Refuse to treat a blanket PII mask as provider-disclosure authorization. +pub use receipt::refuse_blanket_mask_as_disclosure; +/// Refuse to place source identity in a provider receipt. +pub use receipt::refuse_source_identity_in_receipt; +/// Refuse to place raw source text in a provider receipt. +pub use receipt::refuse_source_text_in_receipt; diff --git a/crates/provider_receipt/src/receipt.rs b/crates/provider_receipt/src/receipt.rs new file mode 100644 index 000000000..af3d98e7b --- /dev/null +++ b/crates/provider_receipt/src/receipt.rs @@ -0,0 +1,128 @@ +//! Purpose-bound field-code receipts for provider disclosure. + +use crate::ProviderReceiptError; + +/// One provider-disclosure receipt of field codes under a purpose. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProviderReceipt { + purpose_code: u16, + field_codes: Vec, +} + +impl ProviderReceipt { + /// Record the field codes sent to a provider under one purpose. + /// + /// # Errors + /// + /// Returns [`ProviderReceiptError::InvalidReceiptPayload`] when no field + /// codes are supplied. + pub fn new(purpose_code: u16, field_codes: &[u16]) -> Result { + if field_codes.is_empty() { + return Err(ProviderReceiptError::InvalidReceiptPayload); + } + Ok(Self { + purpose_code, + field_codes: field_codes.to_vec(), + }) + } + + /// Purpose bound to the disclosure. + #[must_use] + pub const fn purpose_code(&self) -> u16 { + self.purpose_code + } + + /// Field codes sent, never source text. + #[must_use] + pub fn field_codes(&self) -> &[u16] { + &self.field_codes + } +} + +/// Refuse to place raw source text in a provider receipt. +/// +/// # Errors +/// +/// Always returns [`ProviderReceiptError::SourceTextNotDisclosable`]. +pub fn refuse_source_text_in_receipt() -> Result<(), ProviderReceiptError> { + Err(ProviderReceiptError::SourceTextNotDisclosable) +} + +/// Refuse to place source identity in a provider receipt. +/// +/// # Errors +/// +/// Always returns [`ProviderReceiptError::SourceIdentityNotDisclosable`]. +pub fn refuse_source_identity_in_receipt() -> Result<(), ProviderReceiptError> { + Err(ProviderReceiptError::SourceIdentityNotDisclosable) +} + +/// Refuse to treat a blanket PII mask as provider-disclosure authorization. +/// +/// # Errors +/// +/// Always returns [`ProviderReceiptError::BlanketMaskIsNotAuthorization`]. +pub fn refuse_blanket_mask_as_disclosure() -> Result<(), ProviderReceiptError> { + Err(ProviderReceiptError::BlanketMaskIsNotAuthorization) +} + +/// Fraction of recovered field codes that match known truth. +/// +/// # Errors +/// +/// Returns [`ProviderReceiptError::InvalidReceiptPayload`] when the field-code +/// lengths differ. +pub fn receipt_recovery_rate( + truth: &ProviderReceipt, + decided: &ProviderReceipt, +) -> Result { + if truth.field_codes.len() != decided.field_codes.len() { + return Err(ProviderReceiptError::InvalidReceiptPayload); + } + let mut matches = 0_u32; + for (truth_field, decided_field) in truth.field_codes.iter().zip(&decided.field_codes) { + if truth_field == decided_field { + matches += 1; + } + } + Ok(f64::from(matches) / truth.field_codes.len() as f64) +} + +#[cfg(test)] +mod tests { + use super::{ + ProviderReceipt, receipt_recovery_rate, refuse_blanket_mask_as_disclosure, + refuse_source_identity_in_receipt, refuse_source_text_in_receipt, + }; + use crate::ProviderReceiptError; + + #[test] + fn local_branches_cover_construct_and_fail_closed_paths() { + let receipt = ProviderReceipt::new(7, &[1, 2]).expect("receipt"); + assert_eq!(receipt.purpose_code(), 7); + assert_eq!(receipt.field_codes(), &[1, 2]); + let matched = receipt_recovery_rate(&receipt, &receipt).expect("rate"); + assert!((matched - 1.0).abs() < f64::EPSILON); + assert_eq!( + ProviderReceipt::new(7, &[]), + Err(ProviderReceiptError::InvalidReceiptPayload) + ); + let short = ProviderReceipt::new(7, &[1]).expect("short"); + assert_eq!( + receipt_recovery_rate(&receipt, &short), + Err(ProviderReceiptError::InvalidReceiptPayload) + ); + assert_eq!( + refuse_source_text_in_receipt(), + Err(ProviderReceiptError::SourceTextNotDisclosable) + ); + assert_eq!( + refuse_source_identity_in_receipt(), + Err(ProviderReceiptError::SourceIdentityNotDisclosable) + ); + assert_eq!( + refuse_blanket_mask_as_disclosure(), + Err(ProviderReceiptError::BlanketMaskIsNotAuthorization) + ); + } +} diff --git a/crates/provider_receipt/tests/crate_contract.rs b/crates/provider_receipt/tests/crate_contract.rs new file mode 100644 index 000000000..4292829c0 --- /dev/null +++ b/crates/provider_receipt/tests/crate_contract.rs @@ -0,0 +1,7 @@ +//! Integration contract for the `provider_receipt` package identity. + +#[test] +fn package_identity_is_stable() { + let observed = std::hint::black_box(env!("CARGO_PKG_NAME")); + assert_eq!(observed, "provider_receipt"); +} diff --git a/crates/provider_receipt/tests/receipt_contract.rs b/crates/provider_receipt/tests/receipt_contract.rs new file mode 100644 index 000000000..44af33d66 --- /dev/null +++ b/crates/provider_receipt/tests/receipt_contract.rs @@ -0,0 +1,62 @@ +//! Provider receipts cannot carry source text, identity, or a blanket mask. + +use provider_receipt::{ + ProviderReceipt, ProviderReceiptError, receipt_recovery_rate, + refuse_blanket_mask_as_disclosure, refuse_source_identity_in_receipt, + refuse_source_text_in_receipt, +}; + +fn receipt(purpose: u16, fields: &[u16]) -> ProviderReceipt { + ProviderReceipt::new(purpose, fields).expect("receipt") +} + +#[test] +fn source_text_identity_and_blanket_mask_cannot_enter_a_receipt() { + assert_eq!( + refuse_source_text_in_receipt(), + Err(ProviderReceiptError::SourceTextNotDisclosable) + ); + assert_eq!( + refuse_source_identity_in_receipt(), + Err(ProviderReceiptError::SourceIdentityNotDisclosable) + ); + assert_eq!( + refuse_blanket_mask_as_disclosure(), + Err(ProviderReceiptError::BlanketMaskIsNotAuthorization) + ); +} + +#[test] +fn recovered_field_codes_match_known_truth_better_than_a_collapsed_set() { + let truth = receipt(7, &[1, 2, 3]); + let recovered = receipt(7, &[1, 2, 3]); + let collapsed = receipt(7, &[1, 1, 1]); + let recovered_rate = receipt_recovery_rate(&truth, &recovered).expect("recovered"); + let collapsed_rate = receipt_recovery_rate(&truth, &collapsed).expect("collapsed"); + let expected = { + let mut matches = 0_u32; + for (truth_field, decided_field) in truth.field_codes().iter().zip(recovered.field_codes()) + { + if truth_field == decided_field { + matches += 1; + } + } + f64::from(matches) / f64::from(u32::try_from(truth.field_codes().len()).expect("len")) + }; + assert!((recovered_rate - expected).abs() < f64::EPSILON); + assert!(recovered_rate > collapsed_rate); +} + +#[test] +fn empty_or_mismatched_receipt_payloads_fail_closed() { + assert_eq!( + ProviderReceipt::new(7, &[]), + Err(ProviderReceiptError::InvalidReceiptPayload) + ); + let truth = receipt(7, &[1, 2]); + let short = receipt(7, &[1]); + assert_eq!( + receipt_recovery_rate(&truth, &short), + Err(ProviderReceiptError::InvalidReceiptPayload) + ); +} diff --git a/docs/PRIVACY_DATA_GOVERNANCE.md b/docs/PRIVACY_DATA_GOVERNANCE.md index 6aa17b1dc..b442056c2 100644 --- a/docs/PRIVACY_DATA_GOVERNANCE.md +++ b/docs/PRIVACY_DATA_GOVERNANCE.md @@ -82,5 +82,6 @@ Ordinary logs contain identifiers/digests sufficient for diagnosis without copyi ## 10. Privacy validation +Required tests include cross-tenant denial, expired-purpose denial, re-identification-boundary checks, export authorization, provider payload minimization, raw-source log absence, deletion/retention behavior, audit replay, and derived-sensitive-data classification. Privacy controls must be tested with realistic author/customer/project/multiple-membership cases rather than only anonymous fixtures. The in-memory `provider_receipt` crate is the current disclosure-audit gate; persistence of receipts remains accepted-target. Required tests include cross-tenant denial, expired-purpose denial, re-identification-boundary checks, export authorization, provider payload minimization, raw-source log absence, deletion/retention behavior, audit replay, and derived-sensitive-data classification. Privacy controls must be tested with realistic author/customer/project/multiple-membership cases rather than only anonymous fixtures. The in-memory `operational_log` crate is the current source-separation gate: `try_record` is the only recording API and inspects source text, source identity, and blanket-mask intent; a source-identity `&str` cannot become an analytical subject. `persistence_postgres` `audit_event` inserts call the same gate before SQL is rendered. Live HTTP and provider adapters remain accepted-target. Required tests include cross-tenant denial, expired-purpose denial, re-identification-boundary checks, export authorization, provider payload minimization, raw-source log absence, deletion/retention behavior, audit replay, and derived-sensitive-data classification. Privacy controls must be tested with realistic author/customer/project/multiple-membership cases rather than only anonymous fixtures. The in-memory `derived_sensitivity` crate is the current inheritance gate; persistence of classifications remains accepted-target. diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 4e8ac5480..f958ba8b2 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -31,9 +31,8 @@ The full APA 7th standards/literature register remains `docs/research/standards- | longitudinal within/between decomposition | ADR 0005 | `longitudinal_core` decomposition, component RMSE, and known-truth recovery on the active PR | active-PR | | posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | future `psychometric_core`; invariance and ESEM/DSEM fit remain accepted-target | accepted-target | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | -| TDT detection/tracking | ADR 0016; Allan (2002) | future `event_intelligence` | accepted-target | -| neural event-schema induction and prediction | ADR 0016; Li et al. (2021) | future `event_intelligence` | accepted-target | -| symbolic qualitative temporal consistency | ADR 0016; Anagnostopoulos et al. (2013) | future `event_intelligence` | accepted-target | +| TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | future `event_intelligence` | accepted-target | +| purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | `provider_receipt` field-code disclosure audit on the active PR; persistence/live HTTP remaining | active-PR | | evidence-bounded LLM interpretation | ADR 0010/0012; PRD | `tepp_api` router plus future `interpretation_gateway` | partial | | adaptive direct/verify/committee/conductor test-time compute | ADR 0010; `docs/LLM_ORCHESTRATION.md` | `tepp_api::route_orchestration` and ablation records implemented-main; live contextual-orchestrator execution remaining | partial | | purpose-bound PII handling without blanket masking | ADR 0009; `docs/PRIVACY_DATA_GOVERNANCE.md` | provider-payload minimization, elevated re-identification, retention/deletion, and legal hold implemented-main; `operational_log::try_record` and inspected `audit_event` inserts active-PR; live HTTP/provider adapters remaining | active-PR | diff --git a/docs/adr/0009-purpose-bound-pii-governance.md b/docs/adr/0009-purpose-bound-pii-governance.md index 22f6e476c..a97a1df0b 100644 --- a/docs/adr/0009-purpose-bound-pii-governance.md +++ b/docs/adr/0009-purpose-bound-pii-governance.md @@ -1,6 +1,7 @@ # ADR 0009 — Purpose-bound PII governance without blanket masking **Decision status:** Accepted +**Implementation maturity:** partial — persistence retention/deletion/legal-hold (migration `0007`) and purpose-bound provider-payload minimization are implemented-main; provider-disclosure receipts with field-code-only evidence are on this active PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; deployment/provider-region evidence remains accepted-target **Implementation maturity:** active-PR — persistence retention/deletion/legal-hold (migration `0007`) and purpose-bound provider-payload minimization are implemented-main; `operational_log::try_record` and inspected `audit_event` inserts are on this active PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; deployment/provider-region evidence remains accepted-target **Date:** 2026-08-10 **Implementation maturity:** active-PR — persistence retention/deletion/legal-hold (migration `0007`) and purpose-bound provider-payload minimization are implemented-main; `derived_sensitivity` inheritance is on the active PR and is not implemented-main until exact-head checks, review, and protected-main integration complete; deployment/provider-region evidence remains accepted-target diff --git a/docs/adr/README.md b/docs/adr/README.md index d5bf8c5b2..a9f588462 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -17,6 +17,7 @@ Read [`ADR_POLICY.md`](ADR_POLICY.md) first. **Decision status and implementatio | [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. | | [0007](0007-rust-workspace-quality-gates.md) | Explicit Rust workspace, pinned toolchains, and exact quality gates | Accepted | implemented-main | ADR 0014 governs scientific/product claim promotion beyond repository-quality tooling. | | [0008](0008-immutable-evidence-identities-digests-and-spans.md) | Immutable evidence identities, `SHA-256` digests, exact spans, and strict wire reconstruction | Accepted | implemented-main | ADR 0013 governs future persistence/reproducibility/split authority. | +| [0009](0009-purpose-bound-pii-governance.md) | Purpose-bound PII governance without blanket masking | Accepted | partial | Retention/deletion/legal-hold and provider-payload minimization are implemented-main; provider-disclosure receipts are active-PR; deployment evidence remains accepted-target. | | [0009](0009-purpose-bound-pii-governance.md) | Purpose-bound PII governance without blanket masking | Accepted | active-PR | Persistence retention/deletion/legal-hold (`0007`) and provider-payload minimization are implemented-main; `operational_log::try_record` and inspected `audit_event` inserts are on the active PR; tenant/purpose/role/lifetime storage, live HTTP, and certification evidence remain accepted-target. | | [0010](0010-adaptive-llm-orchestration.md) | Adaptive LLM orchestration and test-time compute | Accepted | partial | `tepp_api` router/ablation/orchestrator binding are implemented-main; live NIM execution and production ablation evidence remain accepted-target. | | [0009](0009-purpose-bound-pii-governance.md) | Purpose-bound PII governance without blanket masking | Accepted | active-PR | Persistence retention/deletion/legal-hold (`0007`) and provider-payload minimization implemented-main; `derived_sensitivity` inheritance on the active PR; deployment evidence remains accepted-target. | diff --git a/docs/research/provider-disclosure-receipt.md b/docs/research/provider-disclosure-receipt.md new file mode 100644 index 000000000..32031f8f3 --- /dev/null +++ b/docs/research/provider-disclosure-receipt.md @@ -0,0 +1,30 @@ +# Provider-disclosure receipts (doctoring) + +## Scope + +`provider_receipt` records the purpose and field codes sent to a model +provider. Source text and source identity cannot enter the receipt. +Blanket PII masking is not a disclosure grant. Recovery is the computed +share of field codes that match known truth. + +This slice does not send HTTP, persist receipts, or claim CSAP, SOC 2, or +legal sufficiency. + +## Authority + +### Normative TEPP contract + +- `docs/adr/0009-purpose-bound-pii-governance.md` — model/provider payloads + are evidence-minimized and version/audit bound. +- `docs/PRIVACY_DATA_GOVERNANCE.md` — provider payload minimization and + raw-source log absence are required tests. + +### Supporting literature + +ISO/IEC 29100 treats data minimization and purpose specification as +distinct controls. They do **not** authorize copying source text into a +provider audit artifact, and they do not certify TEPP. + +International Organization for Standardization and International +Electrotechnical Commission. (2011). *Information technology—Security +techniques—Privacy framework* (ISO/IEC Standard No. 29100:2011). diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index de3592aa5..c8a1381a9 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -127,6 +127,8 @@ International Organization for Standardization and International Electrotechnica International Organization for Standardization and International Electrotechnical Commission. (2022). *Information security, cybersecurity and privacy protection—Information security controls* (ISO/IEC Standard No. 27002:2022). Logging is an operational control, not authorization to copy source text or source identity into the log. +International Organization for Standardization and International Electrotechnical Commission. (2011). *Information technology—Security techniques—Privacy framework* (ISO/IEC Standard No. 29100:2011). Data minimization informs `provider_receipt`; it is not a certification claim. + ## Privacy lifecycle, retention, and legal hold European Union. (2016). *Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of natural persons with regard to the processing of personal data and on the free movement of such data (General Data Protection Regulation)*. Official Journal of the European Union, L 119, 1–88. https://eur-lex.europa.eu/eli/reg/2016/679/oj diff --git a/docs/validation/temporal-event-foundation.md b/docs/validation/temporal-event-foundation.md index dab71b18c..7ef64b606 100644 --- a/docs/validation/temporal-event-foundation.md +++ b/docs/validation/temporal-event-foundation.md @@ -27,6 +27,7 @@ This report tracks exact-head scientific and engineering evidence required befor | Mention-confidence Brier score | `event_core` | active-PR | calibration vs binary truth | perfect 0 / half 0.25 RMSE | ADR 0003; `docs/research/mention-confidence-brier.md` | | Checkpoint is not the estimator | `checkpoint_authority` | accepted-target | active PR | refuse checkpoint-as-estimator + unvalidated artifact + recovery vs estimator collapse | ADR 0001/0014 | | Versioned API/export contracts | `tepp_api` | implemented-main | naruon HTTP interchange | unknown-field/version/limit + naruon HTTPS interchange tests | Task 12 / PR #21; live HTTP service remaining | +| Provider-disclosure receipts | `provider_receipt` | active-PR | this PR | recovered field-code rate vs collapsed set | ADR 0009 | | 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` | | Production TLS bind gates | `service_tls` | accepted-target | active PR | plaintext production, table-access host, mismatched PEM, and orchestrator loopback refusal plus recovery computed from `authorize_production_tls` / `authorize_orchestrator_live_port` | ADR 0011; rustls config is not a deployed listener | diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index 98d2c6764..5093eda93 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -23,6 +23,7 @@ "tepp_simulation", "validation_core", "tepp_api", + "provider_receipt", "operational_log", "service_tls", "derived_sensitivity", diff --git a/tests/quality/test_check_docstrings.py b/tests/quality/test_check_docstrings.py index 5b0b3cba3..7886d777c 100644 --- a/tests/quality/test_check_docstrings.py +++ b/tests/quality/test_check_docstrings.py @@ -10,6 +10,7 @@ from pathlib import Path from unittest import mock +from scripts import check_workspace_contract as contract from scripts import check_docstrings as docstrings from scripts import check_workspace_contract as contract from scripts import check_workspace_contract as workspace_contract