diff --git a/.github/workflows/foundation-ci.yml b/.github/workflows/foundation-ci.yml index 6b475d6f2..f911a4af6 100644 --- a/.github/workflows/foundation-ci.yml +++ b/.github/workflows/foundation-ci.yml @@ -64,6 +64,7 @@ jobs: PYTHONPATH=packages/migration-adapter/src COVERAGE_FILE=/tmp/orgmetra-migration-adapter.coverage python -m pytest -c packages/migration-adapter/pyproject.toml packages/migration-adapter/tests PYTHONPATH=packages/naruon-adapter/src COVERAGE_FILE=/tmp/orgmetra-naruon-adapter.coverage python -m pytest -c packages/naruon-adapter/pyproject.toml packages/naruon-adapter/tests PYTHONPATH=packages/offer-approval/src COVERAGE_FILE=/tmp/orgmetra-offer-approval.coverage python -m pytest -c packages/offer-approval/pyproject.toml packages/offer-approval/tests + PYTHONPATH=packages/outbox-delivery-receipt/src COVERAGE_FILE=/tmp/orgmetra-outbox-delivery-receipt.coverage python -m pytest -c packages/outbox-delivery-receipt/pyproject.toml packages/outbox-delivery-receipt/tests PYTHONPATH=packages/requisition-review/src COVERAGE_FILE=/tmp/orgmetra-requisition-review.coverage python -m pytest -c packages/requisition-review/pyproject.toml packages/requisition-review/tests PYTHONPATH=packages/selection-review/src COVERAGE_FILE=/tmp/orgmetra-selection-review.coverage python -m pytest -c packages/selection-review/pyproject.toml packages/selection-review/tests PYTHONPATH=services/job-analysis-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src COVERAGE_FILE=/tmp/orgmetra-job-analysis-api.coverage python -m pytest -c services/job-analysis-api/pyproject.toml services/job-analysis-api/tests diff --git a/docs/adr/0151-governed-external-delivery-receipt.md b/docs/adr/0151-governed-external-delivery-receipt.md new file mode 100644 index 000000000..ff565969d --- /dev/null +++ b/docs/adr/0151-governed-external-delivery-receipt.md @@ -0,0 +1,86 @@ +# ADR 0151: Govern external transport delivery receipt evidence before outbox completion + +- **Status:** Proposed — active successor PR #448 carrying PR #151; not protected-main truth +- **Date:** 2026-08-29 +- **Owners:** Orgmetra integration/audit boundary +- **Decision scope:** Evidence needed between an external transport response and an + authoritative Orgmetra outbox completion transaction + +## Context + +Protected `develop` already persists immutable audit events and mutable outbox delivery +state. `complete_outbox_delivery(...)` correctly requires a current tenant-scoped live +lease, but the protected function does not itself require evidence from the external +transport that handled the attempt. + +ADR 0006 identified external delivery receipts as subsequent work. No open Orgmetra PR was +found that owned a generic external outbox receipt contract; HR export PR #120 owns a +different one-time export-egress receipt boundary, and retry-policy PR #82 owns scheduling, +not transport acknowledgement. + +## Decision + +Add a standalone package that constructs a value-minimized +`ExternalDeliveryReceiptEvidence` and verifies exact-attempt correlation. + +The evidence binds tenant/outbox/audit/target/attempt, a descriptive transport-provider +code, a host-normalized opaque receipt reference, SHA-256 of the exact external receipt +artifact, provider-reported delivery time, host observation time, and evidence version. + +External transport evidence remains explicitly untrusted and carries +`not_authorized_to_mutate_delivery_state`. It excludes raw provider responses and protected +HR values. The public receipt is a sealed non-tuple value: constructor validation stores +detached fields in a closure-private identity registry, the object exposes no marker or +value slot, every public field read resolves the exact issued identity, raw allocations +are unusable, and `tuple.__new__` cannot construct the type. Canonical export revalidates +and serializes one immutable registry snapshot so seal cloning, state replacement, and +time-of-check/time-of-use switching cannot bypass fixed safety-state, shape, chronology, +or identifier invariants. +Separately constructed receipts remain untrusted and still require authoritative +exact-attempt and artifact reconciliation. + +Trust-bearing primitive values are accepted only as their exact built-in Python types, +not caller-defined subclasses whose equality or serialization behavior can be overridden. +Caller-owned aware datetimes are normalized once during construction into detached, +built-in UTC `datetime` values before the evidence object retains them. Later changes to a +caller-owned timezone provider therefore cannot rewrite the canonical JSON or digest. +Every low-level reconstructed instance is rejected because it has no exact issued identity +in the closure-private registry. Canonical export accepts only registry-owned frozen +built-in UTC timestamps. Exact-attempt verification likewise accepts only the exact +`ExternalDeliveryReceiptEvidence` type so a subclass cannot override the returned digest +or other trust behavior. + +This value-object boundary is not cryptographic secrecy against arbitrary same-process +reflection, bytecode rewriting, debugger access, or native-memory access. The host must +isolate code with those capabilities at the process/runtime boundary. + +## Why not modify the outbox migration here + +Open Orgmetra stacks already carry many provisional database migrations. Adding another +durable migration before the evidence contract is reviewed would increase collision and +restack risk. This slice establishes the package/API evidence boundary first. A subsequent +authoritative persistence change may bind the canonical receipt digest into outbox +completion after dependency order permits; it must not backfill or rewrite immutable audit +history. + +## Consequences + +- A caller can correlate a normalized external receipt to one exact current outbox attempt + before authoritative completion. +- A receipt cannot by itself authorize `complete_outbox_delivery(...)`. +- Provider raw payloads, addresses, credentials, HR content, and employment decisions stay + out of governance evidence. +- Receipt replay across retry attempts fails exact-attempt reconciliation. +- Mutable timezone providers and behavior-overriding primitive subclasses cannot remain + embedded in canonical receipt evidence after construction. +- The contract remains independently extractable as an MSA/API boundary. + +## Cryptographic and time references + +SHA-256 is used only as deterministic artifact-correlation evidence, not as a signature or +proof of provider identity. NIST continues to list SHA-2/SHA-256 under FIPS 180-4 while a +revision of FIPS 180-4 has been announced. UTC `Z` rendering follows the RFC 3339 timestamp +form with RFC 9557's update to the semantics of `Z`; Orgmetra uses it here simply as a +canonical zero-offset representation. + +See `docs/doctoring/outbox-delivery-receipt-references.md`. diff --git a/docs/doctoring/outbox-delivery-receipt-references.md b/docs/doctoring/outbox-delivery-receipt-references.md new file mode 100644 index 000000000..ea98b1385 --- /dev/null +++ b/docs/doctoring/outbox-delivery-receipt-references.md @@ -0,0 +1,25 @@ +# External delivery receipt — primary references + +These references support only the narrow cryptographic/time representation decisions in +PR #151. They do not imply certification, provider authenticity, delivery guarantees, or +employment-law compliance. + +## APA 7 references + +National Institute of Standards and Technology. (2015). *Secure Hash Standard (SHS)* +(FIPS PUB 180-4). U.S. Department of Commerce. https://doi.org/10.6028/NIST.FIPS.180-4 + +National Institute of Standards and Technology. (2023, March 7). *Decision to revise FIPS +180-4, Secure Hash Standard (SHS).* https://csrc.nist.gov/News/2023/decision-to-revise-fips-180-4 + +Sharma, U., & Bormann, C. (2024). *Date and time on the Internet: Timestamps with +additional information* (RFC 9557). RFC Editor. https://doi.org/10.17487/RFC9557 + +## Decision notes + +- SHA-256 is used for content correlation, not signing. NIST's current CAVP secure-hashing + material continues to list SHA-256 in the SHA-2 family under FIPS 180-4; NIST has also + announced that FIPS 180-4 will be revised. +- RFC 9557 updates RFC 3339's interpretation of the `Z` local-offset marker. Orgmetra uses + `Z` only to produce one deterministic UTC/zero-offset text representation for evidence + hashing; it does not encode a source time zone. diff --git a/docs/traceability/outbox-delivery-receipt.md b/docs/traceability/outbox-delivery-receipt.md new file mode 100644 index 000000000..5feaaff52 --- /dev/null +++ b/docs/traceability/outbox-delivery-receipt.md @@ -0,0 +1,35 @@ +# External delivery receipt traceability + +**State:** Active successor PR #448 carries PR #151. Protected `develop` does not yet expose this package. + +| Requirement | Executable evidence | Production boundary | +| --- | --- | --- | +| Exact tenant/outbox/audit/target/attempt binding | `test_verifies_only_the_exact_outbox_attempt` | `verify_exact_delivery_attempt` | +| No HR payload, destination, or credential in the canonical evidence | `test_builds_value_minimized_untrusted_transport_evidence`; fixed-contract parametrization | `ExternalDeliveryReceiptEvidence` fixed safety fields | +| External receipt remains untrusted and non-authorizing | fixed-contract parametrization; hostile trust-state subclass regression | exact-type fixed-state validation | +| Opaque normalized receipt identity | receipt-reference parametrization | `_validate_receipt_reference` | +| Exact provider artifact correlation | digest parametrization | `_validate_digest`, `transport_receipt_digest` | +| Temporal evidence is detached from caller-owned timezone behavior and canonical UTC; observation cannot predate reported delivery | UTC precision, mutable-timezone, provider-failure, no-offset, and chronology regressions | `_freeze_timestamp`, `_canonical_timestamp`, `_validate_contract` | +| Trust-bearing text cannot retain behavior-overriding `str` subclasses | exact-attempt equality and fixed trust-state subclass regressions | exact built-in primitive validation | +| Receipt subclasses cannot override verification/digest behavior | `test_exact_attempt_verification_rejects_receipt_subclasses` | exact-type check in `verify_exact_delivery_attempt` | +| Retry replay cannot cross attempt boundaries | exact-attempt mismatch regression | `delivery_attempt_count` in reconciliation tuple | +| Copy/low-level reconstruction cannot bypass fixed safety/trust invariants | tuple-surface, raw-allocation, seal-cloning, replaceable-slot, and identity-registry lifecycle regressions | closure-private identity registry plus single-snapshot canonical export revalidation | +| Structural mutation is rejected | `test_evidence_is_structurally_immutable_after_construction` | sealed non-tuple evidence type | +| Sealing preserves immutable value semantics | `test_preserves_value_equality_and_hashing_after_sealing` | issued-value equality and hashing | +| Exact owned statement/branch coverage | canonical hosted `Foundation CI` package command | pytest-cov gate at 100% | + +## Upstream protected-main truth + +- `database/migrations/0003_audit_outbox_persistence.sql` owns immutable audit events and + durable outbox state. +- `database/migrations/0005_outbox_delivery_finalization.sql` owns live-lease completion + and retry mutation. +- This successor does not change either migration and does not claim a durable receipt column. + +## Downstream acceptance + +Before any later durable receipt persistence or `delivered` transition is considered +commercial truth, the authoritative host must re-resolve the current tenant-scoped leased +attempt, verify the raw external artifact against the stored digest, preserve immutable +audit evidence, and pass the then-current exact-head migration/recovery/security/review +gates. diff --git a/manifest.json b/manifest.json index f7b6cf55e..f85d4b085 100644 --- a/manifest.json +++ b/manifest.json @@ -5,9 +5,9 @@ "files": [ { "path": ".github/workflows/foundation-ci.yml", - "sha256": "b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7", - "bytes": 6651, - "lines": 125 + "sha256": "db5cce86f12dce282c299acba38630aa3147271d46e0d20ffa041cc8369b14a3", + "bytes": 6877, + "lines": 126 }, { "path": ".gitignore", diff --git a/packages/outbox-delivery-receipt/CHANGELOG.md b/packages/outbox-delivery-receipt/CHANGELOG.md new file mode 100644 index 000000000..763e5f92e --- /dev/null +++ b/packages/outbox-delivery-receipt/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +## Unreleased + +- Integrate package verification into canonical Foundation CI rather than a leaf-owned workflow. +- Document the public receipt constructor and its non-overridable fixed safety fields. +- Replace tuple-backed receipt storage with closure-sealed non-tuple evidence whose values + live in an identity registry rather than readable object slots, so `tuple.__new__`, raw + allocation, seal cloning, state replacement, and validation/serialization switching + cannot expose forged public fields while preserving immutable value equality and hashing. +- Define value-minimized external transport delivery receipt evidence. +- Bind receipts to an exact tenant/outbox/audit/target/attempt coordinate. +- Keep transport evidence untrusted and explicitly non-authorizing for delivery-state + mutation. +- Require canonical UTC chronology, opaque normalized receipt references, SHA-256 artifact + correlation, structural immutability, copy-bypass revalidation, and exact 100% owned + statement/branch coverage. +- Detach caller-owned timezone behavior into built-in UTC timestamps before evidence is + retained, reject behavior-overriding trust/identifier string subclasses, and reject + receipt subclasses at exact-attempt verification so canonical evidence and returned + digests cannot be rewritten through caller-controlled runtime behavior. diff --git a/packages/outbox-delivery-receipt/README.md b/packages/outbox-delivery-receipt/README.md new file mode 100644 index 000000000..c90d2291a --- /dev/null +++ b/packages/outbox-delivery-receipt/README.md @@ -0,0 +1,58 @@ +# Orgmetra external delivery receipt evidence + +This package gives Orgmetra a small, value-minimized evidence object for the moment an +external transport reports that one outbox attempt was delivered. + +It **does not** mark an outbox row delivered. A transport response is untrusted evidence. +The authoritative Orgmetra host must re-read the live tenant-scoped leased attempt, verify +the normalized receipt artifact, apply purpose-bound authorization, and persist its own +immutable audit/outbox evidence in the same governed completion transaction. + +## What the evidence binds + +`ExternalDeliveryReceiptEvidence` binds one exact: + +- tenant, outbox delivery, and audit event; +- delivery target and attempt number; +- transport provider code; +- host-normalized opaque `transport_receipt:` reference; +- SHA-256 digest of the exact external receipt artifact; +- provider-reported delivery instant and host observation instant; and +- evidence version. + +The canonical packet never carries the HR payload, destination address, credentials, +compensation, assessment/rating values, free-form model output, or an employment decision. + +## Safe next action + +Call `verify_exact_delivery_attempt(...)` only after resolving the authoritative current +outbox attempt. A successful match returns the canonical evidence digest for correlation; +it is still **not** permission to call `complete_outbox_delivery(...)`. The host must verify +the external receipt artifact against `transport_receipt_digest` and complete its normal +lease, authorization, audit, and persistence checks. + +## Integrity model + +The public evidence type is a sealed non-tuple immutable value. Its validated constructor +stores detached fields in a closure-private identity registry; the object itself exposes +no marker or value slot, and every public field read resolves the exact issued identity. +Raw `object.__new__` allocations remain unusable, while `tuple.__new__` cannot allocate the +type at all. Ordinary mutation and deletion fail, and canonical export revalidates one +immutable registry snapshot so state replacement and time-of-check/time-of-use switching +cannot bypass the fixed safety-state, shape, chronology, or identifier invariants. A +separately constructed receipt is still untrusted evidence and must independently match +the authoritative exact attempt plus the external receipt artifact before any governed +completion can occur. + +This is an application evidence contract, not a digital-signature scheme. Durable +cross-process authenticity and retention belong to the authoritative persistence and +audit/outbox boundary. Code with arbitrary same-process reflection, bytecode rewriting, +debugger access, or native-memory access is outside this value-object boundary and must be +isolated by the host runtime. + +## Current integration status + +This package is proposed by successor PR #448, which carries the valid package and +documentation delta from PR #151 while integrating validation into canonical Foundation CI. +Until #448 integrates into protected `develop`, it is active-PR truth, not a commercially +available protected-main capability. diff --git a/packages/outbox-delivery-receipt/SECURITY.md b/packages/outbox-delivery-receipt/SECURITY.md new file mode 100644 index 000000000..724ffd554 --- /dev/null +++ b/packages/outbox-delivery-receipt/SECURITY.md @@ -0,0 +1,34 @@ +# Security and privacy boundary + +External transport receipts are attacker-controlled input until reconciled by Orgmetra. + +The contract therefore: + +- treats transport evidence as `untrusted_transport_evidence`; +- binds it to one tenant/outbox/audit/target/attempt coordinate; +- stores only a host-normalized opaque reference and SHA-256 digest, not raw provider + response bodies; +- excludes HR payloads, destinations, credentials, free-form text, compensation, ratings, + assessment outcomes, and model output; +- rejects noncanonical/sentinel UUID identities, malformed governance codes, non-UUIDv4 + normalized receipt references, invalid digests, unbounded attempt/version values, and + impossible observation chronology; +- issues a sealed non-tuple value only after validation, retaining fields in a + closure-private identity registry instead of a readable or replaceable object slot, so + raw object allocation, seal cloning, and tuple reconstruction cannot expose forged + public receipt fields; +- revalidates one immutable field snapshot on canonical export, preventing a stateful + replacement from switching values between validation and serialization; and +- never grants authority to mutate `outbox_delivery_record`. + +A consumer must not interpret a provider-reported receipt as proof that the intended human +or system actually consumed the message. It is evidence that the configured transport +reported delivery for the correlated attempt. Downstream business semantics need their +own explicit acknowledgement contract. + +No secret, provider token, destination address, or raw transport payload belongs in this +evidence packet or routine logs. + +This boundary does not claim cryptographic secrecy against code with arbitrary +same-process reflection, bytecode rewriting, debugger access, or native-memory access. +Hosts must isolate such untrusted code at the process/runtime boundary. diff --git a/packages/outbox-delivery-receipt/pyproject.toml b/packages/outbox-delivery-receipt/pyproject.toml new file mode 100644 index 000000000..a4d4b1fc7 --- /dev/null +++ b/packages/outbox-delivery-receipt/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + +[project] +name = "orgmetra-outbox-delivery-receipt" +version = "0.1.0" +description = "PII-minimized external transport delivery receipt evidence for Orgmetra outbox reconciliation." +requires-python = ">=3.12" + +[project.optional-dependencies] +test = ["pytest>=8.3", "pytest-cov>=5.0"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = [ + "--cov=orgmetra_outbox_delivery_receipt", + "--cov-branch", + "--cov-report=term-missing", + "--cov-fail-under=100", +] diff --git a/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/__init__.py b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/__init__.py new file mode 100644 index 000000000..447f58939 --- /dev/null +++ b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/__init__.py @@ -0,0 +1,13 @@ +"""Public contract for Orgmetra external outbox delivery receipt evidence.""" + +from .receipt import ( + ExternalDeliveryReceiptEvidence, + build_external_delivery_receipt_evidence, + verify_exact_delivery_attempt, +) + +__all__ = [ + "ExternalDeliveryReceiptEvidence", + "build_external_delivery_receipt_evidence", + "verify_exact_delivery_attempt", +] diff --git a/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py new file mode 100644 index 000000000..fae5e843e --- /dev/null +++ b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py @@ -0,0 +1,499 @@ +"""Value-minimized external transport delivery receipt evidence for Orgmetra. + +The provider receipt is untrusted evidence, not delivery-state mutation authority. Raw +provider payloads, destinations, credentials, and HR values remain outside this packet. +The authoritative host must match this evidence to one live leased outbox attempt before +it can consider a separately governed completion transaction. +""" +from __future__ import annotations + +from datetime import datetime, timezone +from hashlib import sha256 +import json +import re +from uuid import UUID +from weakref import ref + +_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") +_DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") +_RECEIPT_PREFIX = "transport_receipt" +_MAX_INT = 2_147_483_647 + +_DELIVERY_OUTCOME_CODE = "transport_reported_delivered" +_TRUST_STATE = "untrusted_transport_evidence" +_RECONCILIATION_STATE = "requires_exact_attempt_reconciliation" +_MUTATION_AUTHORITY = "not_authorized_to_mutate_delivery_state" +_NEXT_ACTION = ( + "Reconcile this normalized receipt to the exact live tenant/outbox/audit/target/attempt " + "under the authoritative Orgmetra lease and purpose-bound authorization boundary; verify " + "the external receipt artifact against transport_receipt_digest, then persist immutable " + "audit/outbox evidence atomically before marking delivery complete." +) + + +def _validate_operational_uuid(value: object, field_name: str) -> str: + """Return canonical built-in UUID text or fail closed.""" + if type(value) is not str: + raise ValueError(f"{field_name} must be canonical UUID text") + try: + parsed = UUID(value) + except ValueError as exc: + raise ValueError(f"{field_name} must be canonical UUID text") from exc + if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUID") + return value + + +def _validate_code(value: object, field_name: str) -> str: + """Return a bounded built-in two-or-more-word lower snake_case code.""" + if type(value) is not str or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): + raise ValueError(f"{field_name} must be bounded two-or-more-word lower snake_case") + return value + + +def _validate_positive_int(value: object, field_name: str) -> int: + """Return a positive bounded integer while rejecting booleans.""" + if type(value) is not int or value < 1 or value > _MAX_INT: + raise ValueError(f"{field_name} must be an integer from 1 through {_MAX_INT}") + return value + + +def _validate_receipt_reference(value: object) -> str: + """Require a built-in Orgmetra-normalized opaque UUIDv4 receipt reference.""" + message = "transport_receipt_reference must be an opaque transport_receipt: UUIDv4 reference" + if type(value) is not str or len(value) > 160 or not value.startswith(f"{_RECEIPT_PREFIX}:"): + raise ValueError(message) + suffix = value.split(":", 1)[1] + try: + parsed = UUID(suffix) + except ValueError as exc: + raise ValueError(message) from exc + if str(parsed) != suffix or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): + raise ValueError(message) + return value + + +def _validate_digest(value: object) -> str: + """Require built-in lowercase SHA-256 evidence for the external receipt artifact.""" + if type(value) is not str or not _DIGEST_PATTERN.fullmatch(value): + raise ValueError("transport_receipt_digest must be lowercase SHA-256 hex") + return value + + +def _freeze_timestamp(value: object, field_name: str) -> datetime: + """Detach caller-owned timezone behavior into one built-in UTC datetime.""" + if type(value) is not datetime or value.tzinfo is None: + raise ValueError(f"{field_name} must be timezone-aware") + try: + if value.utcoffset() is None: + raise ValueError(f"{field_name} must be timezone-aware") + normalized = value.astimezone(timezone.utc) + except Exception as exc: + if isinstance(exc, ValueError) and str(exc) == f"{field_name} must be timezone-aware": + raise + raise ValueError(f"{field_name} must be safely normalizable to UTC") from exc + return datetime( + normalized.year, + normalized.month, + normalized.day, + normalized.hour, + normalized.minute, + normalized.second, + normalized.microsecond, + tzinfo=timezone.utc, + ) + + +def _canonical_timestamp(value: object, field_name: str) -> str: + """Return canonical text only for already-frozen built-in UTC evidence.""" + if type(value) is not datetime or value.tzinfo is not timezone.utc: + raise ValueError(f"{field_name} must be frozen built-in UTC datetime evidence") + return value.isoformat().replace("+00:00", "Z") + + +def _validate_contract( + *, + tenant_record_id: object, + outbox_delivery_record_id: object, + audit_event_record_id: object, + delivery_target_code: object, + delivery_attempt_count: object, + transport_provider_code: object, + transport_receipt_reference: object, + transport_receipt_digest: object, + transport_delivered_at: object, + observed_at: object, + evidence_version: object, + contains_hr_payload: object, + contains_destination: object, + contains_credentials: object, + delivery_outcome_code: object, + trust_state: object, + reconciliation_state: object, + mutation_authority: object, + next_action: object, +) -> tuple[str, str]: + """Revalidate every trust-bearing field, including copy/bypass-created instances.""" + _validate_operational_uuid(tenant_record_id, "tenant_record_id") + _validate_operational_uuid(outbox_delivery_record_id, "outbox_delivery_record_id") + _validate_operational_uuid(audit_event_record_id, "audit_event_record_id") + _validate_code(delivery_target_code, "delivery_target_code") + _validate_positive_int(delivery_attempt_count, "delivery_attempt_count") + _validate_code(transport_provider_code, "transport_provider_code") + _validate_receipt_reference(transport_receipt_reference) + _validate_digest(transport_receipt_digest) + transport_delivered_at_utc = _canonical_timestamp( + transport_delivered_at, "transport_delivered_at" + ) + observed_at_utc = _canonical_timestamp(observed_at, "observed_at") + if observed_at < transport_delivered_at: + raise ValueError("observed_at cannot precede transport_delivered_at") + _validate_positive_int(evidence_version, "evidence_version") + + fixed_values = { + "contains_hr_payload": (contains_hr_payload, False), + "contains_destination": (contains_destination, False), + "contains_credentials": (contains_credentials, False), + "delivery_outcome_code": (delivery_outcome_code, _DELIVERY_OUTCOME_CODE), + "trust_state": (trust_state, _TRUST_STATE), + "reconciliation_state": (reconciliation_state, _RECONCILIATION_STATE), + "mutation_authority": (mutation_authority, _MUTATION_AUTHORITY), + "next_action": (next_action, _NEXT_ACTION), + } + for field_name, (actual, required) in fixed_values.items(): + if type(actual) is not type(required) or actual != required: + raise ValueError(f"{field_name} must remain fixed by the governed receipt contract") + return transport_delivered_at_utc, observed_at_utc + + +_RECEIPT_FIELD_NAMES = ( + "tenant_record_id", + "outbox_delivery_record_id", + "audit_event_record_id", + "delivery_target_code", + "delivery_attempt_count", + "transport_provider_code", + "transport_receipt_reference", + "transport_receipt_digest", + "transport_delivered_at", + "observed_at", + "evidence_version", + "contains_hr_payload", + "contains_destination", + "contains_credentials", + "delivery_outcome_code", + "trust_state", + "reconciliation_state", + "mutation_authority", + "next_action", +) +_RECEIPT_FIELD_INDEX = { + field_name: field_index for field_index, field_name in enumerate(_RECEIPT_FIELD_NAMES) +} + + +class ExternalDeliveryReceiptEvidence: + """Sealed immutable evidence that an external transport reported delivery.""" + + __slots__ = ("__weakref__",) + + def __new__( + cls, + *, + tenant_record_id: str, + outbox_delivery_record_id: str, + audit_event_record_id: str, + delivery_target_code: str, + delivery_attempt_count: int, + transport_provider_code: str, + transport_receipt_reference: str, + transport_receipt_digest: str, + transport_delivered_at: datetime, + observed_at: datetime, + evidence_version: int = 1, + contains_hr_payload: bool = False, + contains_destination: bool = False, + contains_credentials: bool = False, + delivery_outcome_code: str = _DELIVERY_OUTCOME_CODE, + trust_state: str = _TRUST_STATE, + reconciliation_state: str = _RECONCILIATION_STATE, + mutation_authority: str = _MUTATION_AUTHORITY, + next_action: str = _NEXT_ACTION, + ) -> "ExternalDeliveryReceiptEvidence": + """Build validated receipt evidence whose fixed safety fields callers cannot override.""" + return _construct_external_delivery_receipt_evidence( + cls=cls, + tenant_record_id=tenant_record_id, + outbox_delivery_record_id=outbox_delivery_record_id, + audit_event_record_id=audit_event_record_id, + delivery_target_code=delivery_target_code, + delivery_attempt_count=delivery_attempt_count, + transport_provider_code=transport_provider_code, + transport_receipt_reference=transport_receipt_reference, + transport_receipt_digest=transport_receipt_digest, + transport_delivered_at=transport_delivered_at, + observed_at=observed_at, + evidence_version=evidence_version, + contains_hr_payload=contains_hr_payload, + contains_destination=contains_destination, + contains_credentials=contains_credentials, + delivery_outcome_code=delivery_outcome_code, + trust_state=trust_state, + reconciliation_state=reconciliation_state, + mutation_authority=mutation_authority, + next_action=next_action, + ) + + def __getattribute__(self, name: str) -> object: + """Expose receipt fields only after verifying closure-private issuance.""" + field_index = _RECEIPT_FIELD_INDEX.get(name) + if field_index is not None: + values = _external_delivery_receipt_evidence_values(self) + return values[field_index] + return object.__getattribute__(self, name) + + def __setattr__(self, name: str, value: object) -> None: + """Reject mutation of issued receipt evidence.""" + raise AttributeError("ExternalDeliveryReceiptEvidence is immutable") + + def __delattr__(self, name: str) -> None: + """Reject deletion of issued receipt evidence.""" + raise AttributeError("ExternalDeliveryReceiptEvidence is immutable") + + def __repr__(self) -> str: + """Redact correlation identifiers from routine logs.""" + return "ExternalDeliveryReceiptEvidence()" + + def __eq__(self, other: object) -> bool: + """Compare two issued receipts by their complete immutable values.""" + if type(other) is not ExternalDeliveryReceiptEvidence: + return NotImplemented + return _external_delivery_receipt_evidence_values( + self + ) == _external_delivery_receipt_evidence_values( + other + ) + + def __hash__(self) -> int: + """Hash the complete immutable values of one issued receipt.""" + return hash(_external_delivery_receipt_evidence_values(self)) + + @property + def transport_delivered_at_utc(self) -> str: + """Return the provider-reported delivery instant in canonical UTC text.""" + return _canonical_timestamp(self.transport_delivered_at, "transport_delivered_at") + + @property + def observed_at_utc(self) -> str: + """Return the host observation instant in canonical UTC text.""" + return _canonical_timestamp(self.observed_at, "observed_at") + + def canonical_json(self) -> str: + """Return deterministic value-minimized JSON for immutable audit correlation.""" + field_values = dict( + zip( + _RECEIPT_FIELD_NAMES, + _external_delivery_receipt_evidence_values(self), + strict=True, + ) + ) + transport_delivered_at_utc, observed_at_utc = _validate_contract( + **field_values, + ) + payload = { + "audit_event_record_id": field_values["audit_event_record_id"], + "contains_credentials": field_values["contains_credentials"], + "contains_destination": field_values["contains_destination"], + "contains_hr_payload": field_values["contains_hr_payload"], + "delivery_attempt_count": field_values["delivery_attempt_count"], + "delivery_outcome_code": field_values["delivery_outcome_code"], + "delivery_target_code": field_values["delivery_target_code"], + "evidence_version": field_values["evidence_version"], + "mutation_authority": field_values["mutation_authority"], + "next_action": field_values["next_action"], + "observed_at": observed_at_utc, + "outbox_delivery_record_id": field_values["outbox_delivery_record_id"], + "reconciliation_state": field_values["reconciliation_state"], + "tenant_record_id": field_values["tenant_record_id"], + "transport_delivered_at": transport_delivered_at_utc, + "transport_provider_code": field_values["transport_provider_code"], + "transport_receipt_digest": field_values["transport_receipt_digest"], + "transport_receipt_reference": field_values["transport_receipt_reference"], + "trust_state": field_values["trust_state"], + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def sha256_digest(self) -> str: + """Return SHA-256 over the exact canonical UTF-8 receipt evidence.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + +def _build_external_delivery_receipt_evidence_runtime(): + """Create closure-private issuance state for validated receipt evidence.""" + issued_values: dict[ + int, tuple[ref[ExternalDeliveryReceiptEvidence], tuple[object, ...]] + ] = {} + + def values_for(evidence: ExternalDeliveryReceiptEvidence) -> tuple[object, ...]: + """Return closure-owned values only for the exact registered instance.""" + registered = issued_values.get(id(evidence)) + if registered is None or registered[0]() is not evidence: + raise ValueError("receipt evidence was not issued by the validated constructor") + return registered[1] + + def construct( + *, + cls: type[ExternalDeliveryReceiptEvidence], + tenant_record_id: object, + outbox_delivery_record_id: object, + audit_event_record_id: object, + delivery_target_code: object, + delivery_attempt_count: object, + transport_provider_code: object, + transport_receipt_reference: object, + transport_receipt_digest: object, + transport_delivered_at: object, + observed_at: object, + evidence_version: object, + contains_hr_payload: object, + contains_destination: object, + contains_credentials: object, + delivery_outcome_code: object, + trust_state: object, + reconciliation_state: object, + mutation_authority: object, + next_action: object, + ) -> ExternalDeliveryReceiptEvidence: + """Validate and detach one receipt before registering its exact identity.""" + if cls is not ExternalDeliveryReceiptEvidence: + raise TypeError("ExternalDeliveryReceiptEvidence cannot be subclassed") + frozen_transport_delivered_at = _freeze_timestamp( + transport_delivered_at, "transport_delivered_at" + ) + frozen_observed_at = _freeze_timestamp(observed_at, "observed_at") + _validate_contract( + tenant_record_id=tenant_record_id, + outbox_delivery_record_id=outbox_delivery_record_id, + audit_event_record_id=audit_event_record_id, + delivery_target_code=delivery_target_code, + delivery_attempt_count=delivery_attempt_count, + transport_provider_code=transport_provider_code, + transport_receipt_reference=transport_receipt_reference, + transport_receipt_digest=transport_receipt_digest, + transport_delivered_at=frozen_transport_delivered_at, + observed_at=frozen_observed_at, + evidence_version=evidence_version, + contains_hr_payload=contains_hr_payload, + contains_destination=contains_destination, + contains_credentials=contains_credentials, + delivery_outcome_code=delivery_outcome_code, + trust_state=trust_state, + reconciliation_state=reconciliation_state, + mutation_authority=mutation_authority, + next_action=next_action, + ) + values = ( + tenant_record_id, + outbox_delivery_record_id, + audit_event_record_id, + delivery_target_code, + delivery_attempt_count, + transport_provider_code, + transport_receipt_reference, + transport_receipt_digest, + frozen_transport_delivered_at, + frozen_observed_at, + evidence_version, + contains_hr_payload, + contains_destination, + contains_credentials, + delivery_outcome_code, + trust_state, + reconciliation_state, + mutation_authority, + next_action, + ) + evidence = object.__new__(ExternalDeliveryReceiptEvidence) + evidence_identity = id(evidence) + + def retire(reference: ref[ExternalDeliveryReceiptEvidence]) -> None: + """Discard registry state only for the collected issued instance.""" + registered = issued_values.get(evidence_identity) + if registered is not None and registered[0] is reference: # pragma: no branch + del issued_values[evidence_identity] + + evidence_reference = ref(evidence, retire) + issued_values[evidence_identity] = (evidence_reference, values) + return evidence + + return values_for, construct + + +( + _external_delivery_receipt_evidence_values, + _construct_external_delivery_receipt_evidence, +) = _build_external_delivery_receipt_evidence_runtime() +del _build_external_delivery_receipt_evidence_runtime + + +def build_external_delivery_receipt_evidence( + *, + tenant_record_id: str, + outbox_delivery_record_id: str, + audit_event_record_id: str, + delivery_target_code: str, + delivery_attempt_count: int, + transport_provider_code: str, + transport_receipt_reference: str, + transport_receipt_digest: str, + transport_delivered_at: datetime, + observed_at: datetime, + evidence_version: int = 1, +) -> ExternalDeliveryReceiptEvidence: + """Build one untrusted normalized receipt for later exact-attempt reconciliation.""" + return ExternalDeliveryReceiptEvidence( + tenant_record_id=tenant_record_id, + outbox_delivery_record_id=outbox_delivery_record_id, + audit_event_record_id=audit_event_record_id, + delivery_target_code=delivery_target_code, + delivery_attempt_count=delivery_attempt_count, + transport_provider_code=transport_provider_code, + transport_receipt_reference=transport_receipt_reference, + transport_receipt_digest=transport_receipt_digest, + transport_delivered_at=transport_delivered_at, + observed_at=observed_at, + evidence_version=evidence_version, + ) + + +def verify_exact_delivery_attempt( + evidence: ExternalDeliveryReceiptEvidence, + *, + tenant_record_id: str, + outbox_delivery_record_id: str, + audit_event_record_id: str, + delivery_target_code: str, + delivery_attempt_count: int, +) -> str: + """Fail closed unless receipt evidence matches the exact authoritative attempt scope.""" + if type(evidence) is not ExternalDeliveryReceiptEvidence: + raise TypeError("evidence must be ExternalDeliveryReceiptEvidence") + + evidence_digest = evidence.sha256_digest() + expected = ( + _validate_operational_uuid(tenant_record_id, "tenant_record_id"), + _validate_operational_uuid(outbox_delivery_record_id, "outbox_delivery_record_id"), + _validate_operational_uuid(audit_event_record_id, "audit_event_record_id"), + _validate_code(delivery_target_code, "delivery_target_code"), + _validate_positive_int(delivery_attempt_count, "delivery_attempt_count"), + ) + actual = ( + evidence.tenant_record_id, + evidence.outbox_delivery_record_id, + evidence.audit_event_record_id, + evidence.delivery_target_code, + evidence.delivery_attempt_count, + ) + if actual != expected: + raise ValueError("receipt evidence does not match the exact outbox delivery attempt") + return evidence_digest diff --git a/packages/outbox-delivery-receipt/tests/test_receipt.py b/packages/outbox-delivery-receipt/tests/test_receipt.py new file mode 100644 index 000000000..4a9cf6002 --- /dev/null +++ b/packages/outbox-delivery-receipt/tests/test_receipt.py @@ -0,0 +1,313 @@ +from __future__ import annotations + +from datetime import datetime, timedelta, timezone, tzinfo +from uuid import UUID, uuid4 + +import pytest + +from orgmetra_outbox_delivery_receipt import ( + ExternalDeliveryReceiptEvidence, + build_external_delivery_receipt_evidence, + verify_exact_delivery_attempt, +) + + +class _MutableTimezone(tzinfo): + def __init__(self, offset: timedelta) -> None: + self.offset = offset + + def utcoffset(self, dt: datetime | None) -> timedelta: + return self.offset + + def dst(self, dt: datetime | None) -> timedelta: + return timedelta(0) + + +class _EqualityForgingStr(str): + def __eq__(self, other: object) -> bool: + return True + + def __ne__(self, other: object) -> bool: + return False + + __hash__ = str.__hash__ + + +def _uuid() -> str: + return str(uuid4()) + + +def _reference(prefix: str) -> str: + return f"{prefix}:{uuid4()}" + + +def _kwargs() -> dict[str, object]: + return { + "tenant_record_id": _uuid(), + "outbox_delivery_record_id": _uuid(), + "audit_event_record_id": _uuid(), + "delivery_target_code": "naruon_calendar", + "delivery_attempt_count": 2, + "transport_provider_code": "calendar_gateway", + "transport_receipt_reference": _reference("transport_receipt"), + "transport_receipt_digest": "a" * 64, + "transport_delivered_at": datetime(2026, 8, 29, 1, 2, 3, 456789, tzinfo=timezone.utc), + "observed_at": datetime(2026, 8, 29, 1, 2, 4, tzinfo=timezone.utc), + "evidence_version": 1, + } + + +def test_builds_value_minimized_untrusted_transport_evidence() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + assert evidence.contains_hr_payload is False + assert evidence.contains_destination is False + assert evidence.contains_credentials is False + assert evidence.delivery_outcome_code == "transport_reported_delivered" + assert evidence.trust_state == "untrusted_transport_evidence" + assert evidence.reconciliation_state == "requires_exact_attempt_reconciliation" + assert evidence.mutation_authority == "not_authorized_to_mutate_delivery_state" + assert "reconcile" in evidence.next_action.lower() + assert repr(evidence) == "ExternalDeliveryReceiptEvidence()" + + payload = evidence.canonical_json() + assert '"contains_hr_payload":false' in payload + assert '"transport_receipt_digest":"' + "a" * 64 + '"' in payload + assert "destination" in payload + assert evidence.sha256_digest() == evidence.sha256_digest() + assert len(evidence.sha256_digest()) == 64 + + +def test_preserves_value_equality_and_hashing_after_sealing() -> None: + values = _kwargs() + first = build_external_delivery_receipt_evidence(**values) + equivalent = build_external_delivery_receipt_evidence(**values) + changed_values = dict(values) + changed_values["delivery_attempt_count"] = 3 + different = build_external_delivery_receipt_evidence(**changed_values) + + assert first == equivalent + assert hash(first) == hash(equivalent) + assert first != different + assert first != object() + + +def test_public_constructor_documents_fixed_safety_fields() -> None: + assert "fixed safety fields" in (ExternalDeliveryReceiptEvidence.__new__.__doc__ or "") + + +def test_canonicalizes_aware_timestamps_to_utc_without_losing_precision() -> None: + values = _kwargs() + values["transport_delivered_at"] = datetime( + 2026, 8, 29, 10, 2, 3, 456789, tzinfo=timezone(timedelta(hours=9)) + ) + values["observed_at"] = datetime( + 2026, 8, 29, 10, 2, 4, 123, tzinfo=timezone(timedelta(hours=9)) + ) + evidence = build_external_delivery_receipt_evidence(**values) + + assert evidence.transport_delivered_at_utc == "2026-08-29T01:02:03.456789Z" + assert evidence.observed_at_utc == "2026-08-29T01:02:04.000123Z" + + +def test_freezes_caller_owned_timezone_before_evidence_is_retained() -> None: + mutable_timezone = _MutableTimezone(timedelta(hours=9)) + values = _kwargs() + values["transport_delivered_at"] = datetime( + 2026, 8, 29, 10, 2, 3, 456789, tzinfo=mutable_timezone + ) + evidence = build_external_delivery_receipt_evidence(**values) + original_json = evidence.canonical_json() + original_digest = evidence.sha256_digest() + + mutable_timezone.offset = timedelta(0) + + assert evidence.transport_delivered_at.tzinfo is timezone.utc + assert evidence.canonical_json() == original_json + assert evidence.sha256_digest() == original_digest + + +def test_rejects_string_subclass_that_can_forge_exact_attempt_equality() -> None: + values = _kwargs() + values["delivery_target_code"] = _EqualityForgingStr("naruon_calendar") + + with pytest.raises(ValueError, match="delivery_target_code"): + build_external_delivery_receipt_evidence(**values) + + +def test_rejects_string_subclass_that_can_forge_fixed_trust_state() -> None: + values = _kwargs() + values["trust_state"] = _EqualityForgingStr("trusted_transport_evidence") + + with pytest.raises(ValueError, match="trust_state"): + ExternalDeliveryReceiptEvidence(**values) + + +def test_verifies_only_the_exact_outbox_attempt() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + digest = verify_exact_delivery_attempt( + evidence, + tenant_record_id=evidence.tenant_record_id, + outbox_delivery_record_id=evidence.outbox_delivery_record_id, + audit_event_record_id=evidence.audit_event_record_id, + delivery_target_code=evidence.delivery_target_code, + delivery_attempt_count=evidence.delivery_attempt_count, + ) + assert digest == evidence.sha256_digest() + + with pytest.raises(ValueError, match="exact outbox delivery attempt"): + verify_exact_delivery_attempt( + evidence, + tenant_record_id=evidence.tenant_record_id, + outbox_delivery_record_id=evidence.outbox_delivery_record_id, + audit_event_record_id=evidence.audit_event_record_id, + delivery_target_code=evidence.delivery_target_code, + delivery_attempt_count=evidence.delivery_attempt_count + 1, + ) + + with pytest.raises(TypeError, match="ExternalDeliveryReceiptEvidence"): + verify_exact_delivery_attempt( + object(), + tenant_record_id=evidence.tenant_record_id, + outbox_delivery_record_id=evidence.outbox_delivery_record_id, + audit_event_record_id=evidence.audit_event_record_id, + delivery_target_code=evidence.delivery_target_code, + delivery_attempt_count=evidence.delivery_attempt_count, + ) + + +@pytest.mark.parametrize( + ("field_name", "bad_value"), + [ + ("tenant_record_id", "not-a-uuid"), + ("outbox_delivery_record_id", "00000000-0000-0000-0000-000000000000"), + ("audit_event_record_id", "ffffffff-ffff-ffff-ffff-ffffffffffff"), + ("tenant_record_id", UUID("12345678-1234-5678-9234-567812345678")), + ], +) +def test_rejects_non_operational_or_noncanonical_uuid_identity( + field_name: str, bad_value: object +) -> None: + values = _kwargs() + values[field_name] = bad_value + with pytest.raises(ValueError, match=field_name): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize( + ("field_name", "bad_value"), + [ + ("delivery_target_code", "calendar"), + ("delivery_target_code", "Calendar_Gateway"), + ("delivery_target_code", 3), + ("delivery_target_code", "a_" + "b" * 64), + ("transport_provider_code", "provider"), + ("transport_provider_code", "bad-provider"), + ], +) +def test_rejects_unbounded_or_free_form_codes(field_name: str, bad_value: object) -> None: + values = _kwargs() + values[field_name] = bad_value + with pytest.raises(ValueError, match=field_name): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize( + "bad_value", + [ + "receipt:550e8400-e29b-41d4-a716-446655440000", + "transport_receipt:not-a-uuid", + "transport_receipt:550e8400-e29b-11d4-a716-446655440000", + 5, + "transport_receipt:" + "x" * 200, + ], +) +def test_requires_host_normalized_opaque_transport_receipt_reference(bad_value: object) -> None: + values = _kwargs() + values["transport_receipt_reference"] = bad_value + with pytest.raises(ValueError, match="transport_receipt_reference"): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize("bad_value", ["A" * 64, "a" * 63, 7]) +def test_requires_lowercase_sha256_receipt_digest(bad_value: object) -> None: + values = _kwargs() + values["transport_receipt_digest"] = bad_value + with pytest.raises(ValueError, match="transport_receipt_digest"): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize("bad_value", [True, 0, 2_147_483_648]) +def test_requires_positive_bounded_delivery_attempt_count(bad_value: object) -> None: + values = _kwargs() + values["delivery_attempt_count"] = bad_value + with pytest.raises(ValueError, match="delivery_attempt_count"): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize("bad_value", [True, 0, 2_147_483_648]) +def test_requires_positive_bounded_evidence_version(bad_value: object) -> None: + values = _kwargs() + values["evidence_version"] = bad_value + with pytest.raises(ValueError, match="evidence_version"): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize( + ("field_name", "bad_value"), + [ + ("transport_delivered_at", "2026-08-29T01:02:03Z"), + ("transport_delivered_at", datetime(2026, 8, 29, 1, 2, 3)), + ("observed_at", datetime(2026, 8, 29, 1, 2, 4)), + ], +) +def test_requires_timezone_aware_datetime_evidence(field_name: str, bad_value: object) -> None: + values = _kwargs() + values[field_name] = bad_value + with pytest.raises(ValueError, match=field_name): + build_external_delivery_receipt_evidence(**values) + + +def test_rejects_receipt_observed_before_reported_delivery() -> None: + values = _kwargs() + values["observed_at"] = values["transport_delivered_at"] - timedelta(microseconds=1) + with pytest.raises(ValueError, match="observed_at"): + build_external_delivery_receipt_evidence(**values) + + +@pytest.mark.parametrize( + ("field_name", "bad_value"), + [ + ("contains_hr_payload", True), + ("contains_destination", True), + ("contains_credentials", True), + ("delivery_outcome_code", "delivered"), + ("trust_state", "trusted"), + ("reconciliation_state", "reconciled"), + ("mutation_authority", "authorized"), + ("next_action", "Mark delivered."), + ], +) +def test_fixed_safety_contract_cannot_be_overridden(field_name: str, bad_value: object) -> None: + values = _kwargs() + values[field_name] = bad_value + with pytest.raises(ValueError): + ExternalDeliveryReceiptEvidence(**values) + + +def test_evidence_is_structurally_immutable_after_construction() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + with pytest.raises(AttributeError): + evidence.delivery_attempt_count = 99 + with pytest.raises(AttributeError): + del evidence.delivery_attempt_count + + +def test_tuple_copy_bypass_surface_is_absent() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + with pytest.raises(AttributeError): + evidence._replace(trust_state="trusted_transport_evidence") + with pytest.raises(TypeError): + tuple.__new__(ExternalDeliveryReceiptEvidence, ()) diff --git a/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py new file mode 100644 index 000000000..b616ff12e --- /dev/null +++ b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py @@ -0,0 +1,183 @@ +from __future__ import annotations + +import gc +from datetime import datetime, timedelta, timezone, tzinfo +from uuid import uuid4 +from weakref import ref + +import pytest + +from orgmetra_outbox_delivery_receipt import ( + ExternalDeliveryReceiptEvidence, + build_external_delivery_receipt_evidence, + verify_exact_delivery_attempt, +) +from orgmetra_outbox_delivery_receipt.receipt import ( + _canonical_timestamp, + _external_delivery_receipt_evidence_values, +) + + +class _FailingTimezone(tzinfo): + def utcoffset(self, dt: datetime | None) -> timedelta: + raise RuntimeError("hostile timezone provider") + + def dst(self, dt: datetime | None) -> timedelta: + return timedelta(0) + + +class _NoOffsetTimezone(tzinfo): + def utcoffset(self, dt: datetime | None) -> None: + return None + + def dst(self, dt: datetime | None) -> None: + return None + + +def _kwargs() -> dict[str, object]: + return { + "tenant_record_id": str(uuid4()), + "outbox_delivery_record_id": str(uuid4()), + "audit_event_record_id": str(uuid4()), + "delivery_target_code": "naruon_calendar", + "delivery_attempt_count": 2, + "transport_provider_code": "calendar_gateway", + "transport_receipt_reference": f"transport_receipt:{uuid4()}", + "transport_receipt_digest": "a" * 64, + "transport_delivered_at": datetime(2026, 8, 29, 1, 2, 3, tzinfo=timezone.utc), + "observed_at": datetime(2026, 8, 29, 1, 2, 4, tzinfo=timezone.utc), + "evidence_version": 1, + } + + +def test_timezone_provider_exception_fails_closed_as_value_error() -> None: + values = _kwargs() + values["transport_delivered_at"] = datetime( + 2026, 8, 29, 1, 2, 3, tzinfo=_FailingTimezone() + ) + + with pytest.raises(ValueError, match="transport_delivered_at"): + build_external_delivery_receipt_evidence(**values) + + +def test_timezone_provider_without_offset_fails_closed() -> None: + values = _kwargs() + values["transport_delivered_at"] = datetime( + 2026, 8, 29, 1, 2, 3, tzinfo=_NoOffsetTimezone() + ) + + with pytest.raises(ValueError, match="transport_delivered_at"): + build_external_delivery_receipt_evidence(**values) + + +def test_low_level_reconstruction_with_nonfrozen_timestamp_fails_closed() -> None: + reconstructed = object.__new__(ExternalDeliveryReceiptEvidence) + + with pytest.raises(ValueError, match="validated constructor"): + reconstructed.canonical_json() + + +def test_low_level_state_replacement_is_impossible() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + with pytest.raises(AttributeError): + object.__setattr__(evidence, "_field_values", object()) + + +def test_low_level_allocation_with_wrong_marker_fails_closed() -> None: + reconstructed = object.__new__(ExternalDeliveryReceiptEvidence) + + with pytest.raises(AttributeError): + object.__setattr__(reconstructed, "_issuance_marker", object()) + + with pytest.raises(ValueError, match="validated constructor"): + _ = reconstructed.tenant_record_id + + +def test_issued_marker_cannot_be_cloned_into_a_forged_receipt() -> None: + issued = build_external_delivery_receipt_evidence(**_kwargs()) + + with pytest.raises(AttributeError): + object.__getattribute__(issued, "_issuance_marker") + with pytest.raises(AttributeError): + object.__getattribute__(issued, "_field_values") + + forged = object.__new__(ExternalDeliveryReceiptEvidence) + with pytest.raises(ValueError, match="validated constructor"): + _ = forged.contains_hr_payload + + +def test_issued_receipt_has_no_replaceable_internal_value_slot() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + with pytest.raises(AttributeError): + object.__setattr__(evidence, "_field_values", ()) + + +def test_identity_registry_does_not_retain_collected_receipts() -> None: + registry = next( + cell.cell_contents + for cell in _external_delivery_receipt_evidence_values.__closure__ or () + if isinstance(cell.cell_contents, dict) + ) + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + evidence_reference = ref(evidence) + evidence_identity = id(evidence) + assert evidence_identity in registry + + del evidence + gc.collect() + + assert evidence_reference() is None + assert evidence_identity not in registry + + +def test_canonical_timestamp_rejects_nonfrozen_runtime_values() -> None: + with pytest.raises(ValueError, match="must be frozen built-in UTC datetime evidence"): + _canonical_timestamp("2026-08-29T01:02:03Z", "observed_at") + + +def test_public_receipt_is_not_reconstructable_through_tuple_new() -> None: + """Keep tuple allocation from bypassing the public receipt constructor.""" + with pytest.raises(TypeError): + tuple.__new__(ExternalDeliveryReceiptEvidence, ()) + + +def test_exact_attempt_verification_rejects_receipt_subclasses() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + class _ForgedReceipt(ExternalDeliveryReceiptEvidence): + __slots__ = () + + def sha256_digest(self) -> str: + return "f" * 64 + + with pytest.raises(TypeError, match="cannot be subclassed"): + _ForgedReceipt(**_kwargs()) + + forged = object.__new__(_ForgedReceipt) + + with pytest.raises(TypeError, match="ExternalDeliveryReceiptEvidence"): + verify_exact_delivery_attempt( + forged, + tenant_record_id=evidence.tenant_record_id, + outbox_delivery_record_id=evidence.outbox_delivery_record_id, + audit_event_record_id=evidence.audit_event_record_id, + delivery_target_code=evidence.delivery_target_code, + delivery_attempt_count=evidence.delivery_attempt_count, + ) + + +def test_exact_attempt_verification_validates_evidence_before_scope_comparison() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + reconstructed = object.__new__(ExternalDeliveryReceiptEvidence) + + with pytest.raises(ValueError, match="validated constructor"): + verify_exact_delivery_attempt( + reconstructed, + tenant_record_id=evidence.tenant_record_id, + outbox_delivery_record_id=evidence.outbox_delivery_record_id, + audit_event_record_id=evidence.audit_event_record_id, + delivery_target_code=evidence.delivery_target_code, + delivery_attempt_count=evidence.delivery_attempt_count, + ) diff --git a/tests/test_foundation_ci_dependency_hygiene.sh b/tests/test_foundation_ci_dependency_hygiene.sh index 2c0f5087f..48a512502 100644 --- a/tests/test_foundation_ci_dependency_hygiene.sh +++ b/tests/test_foundation_ci_dependency_hygiene.sh @@ -14,6 +14,7 @@ expected_pythonpaths=( "packages/migration-adapter/src" "packages/naruon-adapter/src" "packages/offer-approval/src" + "packages/outbox-delivery-receipt/src" "packages/requisition-review/src" "packages/selection-review/src" "services/job-analysis-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src"