From 443c7737470fea737278824b1c72f03ce41f99c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 00:12:50 -0700 Subject: [PATCH 01/12] test(outbox): define external delivery receipt contract --- .../outbox-delivery-receipt-quality.yml | 56 +++++ .../outbox-delivery-receipt/pyproject.toml | 24 ++ .../tests/test_receipt.py | 230 ++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 .github/workflows/outbox-delivery-receipt-quality.yml create mode 100644 packages/outbox-delivery-receipt/pyproject.toml create mode 100644 packages/outbox-delivery-receipt/tests/test_receipt.py diff --git a/.github/workflows/outbox-delivery-receipt-quality.yml b/.github/workflows/outbox-delivery-receipt-quality.yml new file mode 100644 index 000000000..7530c8873 --- /dev/null +++ b/.github/workflows/outbox-delivery-receipt-quality.yml @@ -0,0 +1,56 @@ +name: Outbox Delivery Receipt Quality + +on: + pull_request: + branches: + - develop + paths: + - "packages/outbox-delivery-receipt/**" + - ".github/requirements/foundation-test.txt" + - ".github/workflows/outbox-delivery-receipt-quality.yml" + - "docs/adr/0151-governed-external-delivery-receipt.md" + - "docs/traceability/outbox-delivery-receipt.md" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: outbox-delivery-receipt-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit: + name: External delivery receipt contract and 100% coverage + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Prove exact candidate checkout + env: + ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + check-latest: false + - name: Install reviewed test toolchain + run: | + python -m pip install --require-hashes --no-deps --only-binary=:all: -r .github/requirements/foundation-test.txt + python -m pip check + - name: Compile delivery receipt package + run: python -m compileall -q packages/outbox-delivery-receipt/src packages/outbox-delivery-receipt/tests + - name: Test external delivery receipt with exact statement and branch coverage + env: + PYTHONPATH: packages/outbox-delivery-receipt/src + COVERAGE_FILE: /tmp/orgmetra-outbox-delivery-receipt.coverage + run: python -m pytest -c packages/outbox-delivery-receipt/pyproject.toml packages/outbox-delivery-receipt/tests + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" 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/tests/test_receipt.py b/packages/outbox-delivery-receipt/tests/test_receipt.py new file mode 100644 index 000000000..5f76e44bc --- /dev/null +++ b/packages/outbox-delivery-receipt/tests/test_receipt.py @@ -0,0 +1,230 @@ +from __future__ import annotations + +from datetime import datetime, timedelta, timezone +from uuid import UUID, uuid4 + +import pytest + +from orgmetra_outbox_delivery_receipt import ( + ExternalDeliveryReceiptEvidence, + build_external_delivery_receipt_evidence, + verify_exact_delivery_attempt, +) + + +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_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_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): + object.__setattr__(evidence, "delivery_attempt_count", 99) From d48b018c29e1da912d54b0e27a7132e8fd0de8d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 00:18:14 -0700 Subject: [PATCH 02/12] feat(outbox): implement governed external delivery receipt evidence --- .../outbox-delivery-receipt-quality.yml | 1 + ...0151-governed-external-delivery-receipt.md | 62 +++ .../outbox-delivery-receipt-references.md | 25 ++ docs/traceability/outbox-delivery-receipt.md | 32 ++ packages/outbox-delivery-receipt/CHANGELOG.md | 11 + packages/outbox-delivery-receipt/README.md | 48 +++ packages/outbox-delivery-receipt/SECURITY.md | 26 ++ .../__init__.py | 13 + .../receipt.py | 369 ++++++++++++++++++ .../tests/test_receipt.py | 14 + 10 files changed, 601 insertions(+) create mode 100644 docs/adr/0151-governed-external-delivery-receipt.md create mode 100644 docs/doctoring/outbox-delivery-receipt-references.md create mode 100644 docs/traceability/outbox-delivery-receipt.md create mode 100644 packages/outbox-delivery-receipt/CHANGELOG.md create mode 100644 packages/outbox-delivery-receipt/README.md create mode 100644 packages/outbox-delivery-receipt/SECURITY.md create mode 100644 packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/__init__.py create mode 100644 packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py diff --git a/.github/workflows/outbox-delivery-receipt-quality.yml b/.github/workflows/outbox-delivery-receipt-quality.yml index 7530c8873..15971ca24 100644 --- a/.github/workflows/outbox-delivery-receipt-quality.yml +++ b/.github/workflows/outbox-delivery-receipt-quality.yml @@ -10,6 +10,7 @@ on: - ".github/workflows/outbox-delivery-receipt-quality.yml" - "docs/adr/0151-governed-external-delivery-receipt.md" - "docs/traceability/outbox-delivery-receipt.md" + - "docs/doctoring/outbox-delivery-receipt-references.md" workflow_dispatch: permissions: 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..a3719ef64 --- /dev/null +++ b/docs/adr/0151-governed-external-delivery-receipt.md @@ -0,0 +1,62 @@ +# ADR 0151: Govern external transport delivery receipt evidence before outbox completion + +- **Status:** Proposed — active 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. Canonical export revalidates every trust-bearing field, including instances +created through copy or low-level tuple construction. + +## 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. +- 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..6cfb94038 --- /dev/null +++ b/docs/traceability/outbox-delivery-receipt.md @@ -0,0 +1,32 @@ +# External delivery receipt traceability + +**State:** Active PR #151 only. 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 | `trust_state`, `reconciliation_state`, `mutation_authority` | +| Opaque normalized receipt identity | receipt-reference parametrization | `_validate_receipt_reference` | +| Exact provider artifact correlation | digest parametrization | `_validate_digest`, `transport_receipt_digest` | +| Temporal evidence is aware and canonical UTC; observation cannot predate reported delivery | timestamp and chronology regressions | `_canonical_timestamp`, `_validate_contract` | +| Retry replay cannot cross attempt boundaries | exact-attempt mismatch regression | `delivery_attempt_count` in reconciliation tuple | +| Copy/low-level reconstruction cannot create a second accepted canonical truth | `test_copy_bypass_cannot_create_a_second_canonical_truth` | canonical export revalidation | +| Structural mutation is rejected | `test_evidence_is_structurally_immutable_after_construction` | tuple-backed evidence type | +| Exact owned statement/branch coverage | hosted `Outbox Delivery Receipt Quality` | 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 PR 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/packages/outbox-delivery-receipt/CHANGELOG.md b/packages/outbox-delivery-receipt/CHANGELOG.md new file mode 100644 index 000000000..be2c8d52b --- /dev/null +++ b/packages/outbox-delivery-receipt/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## Unreleased + +- 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. diff --git a/packages/outbox-delivery-receipt/README.md b/packages/outbox-delivery-receipt/README.md new file mode 100644 index 000000000..32d4116f9 --- /dev/null +++ b/packages/outbox-delivery-receipt/README.md @@ -0,0 +1,48 @@ +# 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 tuple-backed immutable value. Ordinary mutation through +`setattr` or `object.__setattr__` fails. Canonical export also revalidates every +trust-bearing field, so copy helpers or low-level tuple construction cannot turn a modified +instance into a second accepted canonical truth. + +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. + +## Current integration status + +This package is proposed by PR #151. Until that PR 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..e487b5326 --- /dev/null +++ b/packages/outbox-delivery-receipt/SECURITY.md @@ -0,0 +1,26 @@ +# 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; +- revalidates trust-bearing fields on canonical export to catch copy/bypass-created + instances; 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. 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..cd9c829ee --- /dev/null +++ b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py @@ -0,0 +1,369 @@ +"""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 collections import namedtuple +from datetime import datetime, timezone +from hashlib import sha256 +import json +import re +from uuid import UUID + +_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 operational UUID text or fail closed.""" + if not isinstance(value, 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 descriptive two-or-more-word lower snake_case code.""" + if not isinstance(value, 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 an Orgmetra-normalized opaque UUIDv4 receipt reference.""" + message = "transport_receipt_reference must be an opaque transport_receipt: UUIDv4 reference" + if not isinstance(value, 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 lowercase SHA-256 evidence for the external receipt artifact.""" + if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + raise ValueError("transport_receipt_digest must be lowercase SHA-256 hex") + return value + + +def _canonical_timestamp(value: object, field_name: str) -> str: + """Return precision-preserving UTC RFC 3339 text for an aware datetime.""" + if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: + raise ValueError(f"{field_name} must be timezone-aware") + return value.astimezone(timezone.utc).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.astimezone(timezone.utc) < transport_delivered_at.astimezone(timezone.utc): + 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 actual != required: + raise ValueError(f"{field_name} must remain fixed by the governed receipt contract") + return transport_delivered_at_utc, observed_at_utc + + +_BaseReceipt = namedtuple( + "_BaseReceipt", + [ + "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", + ], +) + + +class ExternalDeliveryReceiptEvidence(_BaseReceipt): + """Structurally immutable evidence that an external transport reported delivery.""" + + __slots__ = () + + 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": + _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=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, + ) + + instance = super().__new__( + cls, + 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, + ) + return instance + + def __repr__(self) -> str: + """Redact correlation identifiers from routine logs.""" + return "ExternalDeliveryReceiptEvidence()" + + @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.""" + transport_delivered_at_utc, observed_at_utc = _validate_contract( + tenant_record_id=self.tenant_record_id, + outbox_delivery_record_id=self.outbox_delivery_record_id, + audit_event_record_id=self.audit_event_record_id, + delivery_target_code=self.delivery_target_code, + delivery_attempt_count=self.delivery_attempt_count, + transport_provider_code=self.transport_provider_code, + transport_receipt_reference=self.transport_receipt_reference, + transport_receipt_digest=self.transport_receipt_digest, + transport_delivered_at=self.transport_delivered_at, + observed_at=self.observed_at, + evidence_version=self.evidence_version, + contains_hr_payload=self.contains_hr_payload, + contains_destination=self.contains_destination, + contains_credentials=self.contains_credentials, + delivery_outcome_code=self.delivery_outcome_code, + trust_state=self.trust_state, + reconciliation_state=self.reconciliation_state, + mutation_authority=self.mutation_authority, + next_action=self.next_action, + ) + payload = { + "audit_event_record_id": self.audit_event_record_id, + "contains_credentials": self.contains_credentials, + "contains_destination": self.contains_destination, + "contains_hr_payload": self.contains_hr_payload, + "delivery_attempt_count": self.delivery_attempt_count, + "delivery_outcome_code": self.delivery_outcome_code, + "delivery_target_code": self.delivery_target_code, + "evidence_version": self.evidence_version, + "mutation_authority": self.mutation_authority, + "next_action": self.next_action, + "observed_at": observed_at_utc, + "outbox_delivery_record_id": self.outbox_delivery_record_id, + "reconciliation_state": self.reconciliation_state, + "tenant_record_id": self.tenant_record_id, + "transport_delivered_at": transport_delivered_at_utc, + "transport_provider_code": self.transport_provider_code, + "transport_receipt_digest": self.transport_receipt_digest, + "transport_receipt_reference": self.transport_receipt_reference, + "trust_state": self.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( + *, + 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 not isinstance(evidence, ExternalDeliveryReceiptEvidence): + raise TypeError("evidence must be ExternalDeliveryReceiptEvidence") + + 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.sha256_digest() diff --git a/packages/outbox-delivery-receipt/tests/test_receipt.py b/packages/outbox-delivery-receipt/tests/test_receipt.py index 5f76e44bc..2659a9c4f 100644 --- a/packages/outbox-delivery-receipt/tests/test_receipt.py +++ b/packages/outbox-delivery-receipt/tests/test_receipt.py @@ -228,3 +228,17 @@ def test_evidence_is_structurally_immutable_after_construction() -> None: evidence = build_external_delivery_receipt_evidence(**_kwargs()) with pytest.raises(AttributeError): object.__setattr__(evidence, "delivery_attempt_count", 99) + + +def test_copy_bypass_cannot_create_a_second_canonical_truth() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + + replaced = evidence._replace(trust_state="trusted_transport_evidence") + with pytest.raises(ValueError, match="trust_state"): + replaced.canonical_json() + + raw_values = list(evidence) + raw_values[11] = True + reconstructed = tuple.__new__(ExternalDeliveryReceiptEvidence, tuple(raw_values)) + with pytest.raises(ValueError, match="contains_hr_payload"): + reconstructed.sha256_digest() From a133e4807221e5a25662ceb7b7f6452a9faed0fb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 00:21:06 -0700 Subject: [PATCH 03/12] docs(outbox): narrow delivery receipt integrity claim --- docs/adr/0151-governed-external-delivery-receipt.md | 5 ++++- docs/traceability/outbox-delivery-receipt.md | 2 +- packages/outbox-delivery-receipt/README.md | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/adr/0151-governed-external-delivery-receipt.md b/docs/adr/0151-governed-external-delivery-receipt.md index a3719ef64..3a9af3837 100644 --- a/docs/adr/0151-governed-external-delivery-receipt.md +++ b/docs/adr/0151-governed-external-delivery-receipt.md @@ -30,7 +30,10 @@ artifact, provider-reported delivery time, host observation time, and evidence v External transport evidence remains explicitly untrusted and carries `not_authorized_to_mutate_delivery_state`. It excludes raw provider responses and protected HR values. Canonical export revalidates every trust-bearing field, including instances -created through copy or low-level tuple construction. +created through copy or low-level tuple construction, so those construction paths cannot +bypass fixed safety-state, shape, chronology, or identifier invariants. Separately +constructed receipts remain untrusted and still require authoritative exact-attempt and +artifact reconciliation. ## Why not modify the outbox migration here diff --git a/docs/traceability/outbox-delivery-receipt.md b/docs/traceability/outbox-delivery-receipt.md index 6cfb94038..da8ac2d32 100644 --- a/docs/traceability/outbox-delivery-receipt.md +++ b/docs/traceability/outbox-delivery-receipt.md @@ -11,7 +11,7 @@ | Exact provider artifact correlation | digest parametrization | `_validate_digest`, `transport_receipt_digest` | | Temporal evidence is aware and canonical UTC; observation cannot predate reported delivery | timestamp and chronology regressions | `_canonical_timestamp`, `_validate_contract` | | Retry replay cannot cross attempt boundaries | exact-attempt mismatch regression | `delivery_attempt_count` in reconciliation tuple | -| Copy/low-level reconstruction cannot create a second accepted canonical truth | `test_copy_bypass_cannot_create_a_second_canonical_truth` | canonical export revalidation | +| Copy/low-level reconstruction cannot bypass fixed safety/trust invariants | `test_copy_bypass_cannot_create_a_second_canonical_truth` | canonical export revalidation | | Structural mutation is rejected | `test_evidence_is_structurally_immutable_after_construction` | tuple-backed evidence type | | Exact owned statement/branch coverage | hosted `Outbox Delivery Receipt Quality` | pytest-cov gate at 100% | diff --git a/packages/outbox-delivery-receipt/README.md b/packages/outbox-delivery-receipt/README.md index 32d4116f9..b250c9ad1 100644 --- a/packages/outbox-delivery-receipt/README.md +++ b/packages/outbox-delivery-receipt/README.md @@ -35,8 +35,10 @@ lease, authorization, audit, and persistence checks. The public evidence type is a tuple-backed immutable value. Ordinary mutation through `setattr` or `object.__setattr__` fails. Canonical export also revalidates every -trust-bearing field, so copy helpers or low-level tuple construction cannot turn a modified -instance into a second accepted canonical truth. +trust-bearing field, so copy helpers or low-level tuple construction 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 From 5d55430333bbf5d3ca505446434e802531a6a01d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:06:08 -0700 Subject: [PATCH 04/12] test(outbox): reject mutable receipt evidence inputs --- .../tests/test_receipt.py | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/outbox-delivery-receipt/tests/test_receipt.py b/packages/outbox-delivery-receipt/tests/test_receipt.py index 2659a9c4f..8f584a2e5 100644 --- a/packages/outbox-delivery-receipt/tests/test_receipt.py +++ b/packages/outbox-delivery-receipt/tests/test_receipt.py @@ -1,6 +1,6 @@ from __future__ import annotations -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta, timezone, tzinfo from uuid import UUID, uuid4 import pytest @@ -12,6 +12,27 @@ ) +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()) @@ -71,6 +92,39 @@ def test_canonicalizes_aware_timestamps_to_utc_without_losing_precision() -> Non 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()) From 70c10c54aba720bbc3af566777c043c20615eb9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:07:49 -0700 Subject: [PATCH 05/12] fix(outbox): freeze receipt evidence inputs --- .../receipt.py | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) 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 index cd9c829ee..21aa915da 100644 --- a/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py +++ b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py @@ -32,8 +32,8 @@ def _validate_operational_uuid(value: object, field_name: str) -> str: - """Return canonical operational UUID text or fail closed.""" - if not isinstance(value, 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) @@ -45,8 +45,8 @@ def _validate_operational_uuid(value: object, field_name: str) -> str: def _validate_code(value: object, field_name: str) -> str: - """Return a bounded descriptive two-or-more-word lower snake_case code.""" - if not isinstance(value, str) or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): + """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 @@ -59,9 +59,9 @@ def _validate_positive_int(value: object, field_name: str) -> int: def _validate_receipt_reference(value: object) -> str: - """Require an Orgmetra-normalized opaque UUIDv4 receipt reference.""" + """Require a built-in Orgmetra-normalized opaque UUIDv4 receipt reference.""" message = "transport_receipt_reference must be an opaque transport_receipt: UUIDv4 reference" - if not isinstance(value, str) or len(value) > 160 or not value.startswith(f"{_RECEIPT_PREFIX}:"): + 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: @@ -74,17 +74,41 @@ def _validate_receipt_reference(value: object) -> str: def _validate_digest(value: object) -> str: - """Require lowercase SHA-256 evidence for the external receipt artifact.""" - if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + """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 _canonical_timestamp(value: object, field_name: str) -> str: - """Return precision-preserving UTC RFC 3339 text for an aware datetime.""" - if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: +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") - return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + 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( @@ -122,7 +146,7 @@ def _validate_contract( transport_delivered_at, "transport_delivered_at" ) observed_at_utc = _canonical_timestamp(observed_at, "observed_at") - if observed_at.astimezone(timezone.utc) < transport_delivered_at.astimezone(timezone.utc): + if observed_at < transport_delivered_at: raise ValueError("observed_at cannot precede transport_delivered_at") _validate_positive_int(evidence_version, "evidence_version") @@ -137,7 +161,7 @@ def _validate_contract( "next_action": (next_action, _NEXT_ACTION), } for field_name, (actual, required) in fixed_values.items(): - if actual != required: + 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 @@ -196,6 +220,10 @@ def __new__( mutation_authority: str = _MUTATION_AUTHORITY, next_action: str = _NEXT_ACTION, ) -> "ExternalDeliveryReceiptEvidence": + 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, @@ -205,8 +233,8 @@ def __new__( 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, + 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, @@ -228,8 +256,8 @@ def __new__( transport_provider_code, transport_receipt_reference, transport_receipt_digest, - transport_delivered_at, - observed_at, + frozen_transport_delivered_at, + frozen_observed_at, evidence_version, contains_hr_payload, contains_destination, @@ -347,7 +375,7 @@ def verify_exact_delivery_attempt( delivery_attempt_count: int, ) -> str: """Fail closed unless receipt evidence matches the exact authoritative attempt scope.""" - if not isinstance(evidence, ExternalDeliveryReceiptEvidence): + if type(evidence) is not ExternalDeliveryReceiptEvidence: raise TypeError("evidence must be ExternalDeliveryReceiptEvidence") expected = ( From 7af5e939fa4681255a617beabb187a57f23a8783 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:08:42 -0700 Subject: [PATCH 06/12] test(outbox): cover hostile receipt runtime inputs --- .../tests/test_runtime_integrity.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 packages/outbox-delivery-receipt/tests/test_runtime_integrity.py 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..4dc22544c --- /dev/null +++ b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from datetime import datetime, timedelta, timezone, tzinfo +from uuid import uuid4 + +import pytest + +from orgmetra_outbox_delivery_receipt import ( + ExternalDeliveryReceiptEvidence, + build_external_delivery_receipt_evidence, + verify_exact_delivery_attempt, +) + + +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_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 + + forged = tuple.__new__(_ForgedReceipt, tuple(evidence)) + + 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, + ) From 4b5b8e28a745e20c61ca7981fa1eb66ccf7b9d9b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:09:03 -0700 Subject: [PATCH 07/12] docs(outbox): record receipt runtime integrity boundary --- docs/adr/0151-governed-external-delivery-receipt.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/adr/0151-governed-external-delivery-receipt.md b/docs/adr/0151-governed-external-delivery-receipt.md index 3a9af3837..7ef1274cd 100644 --- a/docs/adr/0151-governed-external-delivery-receipt.md +++ b/docs/adr/0151-governed-external-delivery-receipt.md @@ -35,6 +35,16 @@ bypass fixed safety-state, shape, chronology, or identifier invariants. Separate 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. +Canonical export rejects low-level reconstructed evidence unless both stored timestamps +are already those frozen built-in UTC values. Exact-attempt verification likewise accepts +only the exact `ExternalDeliveryReceiptEvidence` type so a subclass cannot override the +returned digest or other trust behavior. + ## Why not modify the outbox migration here Open Orgmetra stacks already carry many provisional database migrations. Adding another @@ -52,6 +62,8 @@ history. - 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 From 2ca1d09f7deeb3241cc54e75577d171b83eaa606 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:09:16 -0700 Subject: [PATCH 08/12] docs(outbox): trace receipt input hardening --- docs/traceability/outbox-delivery-receipt.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/traceability/outbox-delivery-receipt.md b/docs/traceability/outbox-delivery-receipt.md index da8ac2d32..30b1351c4 100644 --- a/docs/traceability/outbox-delivery-receipt.md +++ b/docs/traceability/outbox-delivery-receipt.md @@ -6,10 +6,12 @@ | --- | --- | --- | | 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 | `trust_state`, `reconciliation_state`, `mutation_authority` | +| 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 aware and canonical UTC; observation cannot predate reported delivery | timestamp and chronology regressions | `_canonical_timestamp`, `_validate_contract` | +| 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 | `test_copy_bypass_cannot_create_a_second_canonical_truth` | canonical export revalidation | | Structural mutation is rejected | `test_evidence_is_structurally_immutable_after_construction` | tuple-backed evidence type | From 069b8a6c7d4516e4b416cb79fa64504dbd29b660 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:09:30 -0700 Subject: [PATCH 09/12] docs(outbox): record receipt integrity repair --- packages/outbox-delivery-receipt/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/outbox-delivery-receipt/CHANGELOG.md b/packages/outbox-delivery-receipt/CHANGELOG.md index be2c8d52b..0adad89b5 100644 --- a/packages/outbox-delivery-receipt/CHANGELOG.md +++ b/packages/outbox-delivery-receipt/CHANGELOG.md @@ -9,3 +9,7 @@ - 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. From cecd4a571172eb5120094583197009389d256509 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:13:59 -0700 Subject: [PATCH 10/12] test(outbox): cover reconstructed timestamp fail-closed branch --- .../tests/test_runtime_integrity.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py index 4dc22544c..7eb7b4225 100644 --- a/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py +++ b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py @@ -64,6 +64,18 @@ def test_timezone_provider_without_offset_fails_closed() -> None: build_external_delivery_receipt_evidence(**values) +def test_low_level_reconstruction_with_nonfrozen_timestamp_fails_closed() -> None: + evidence = build_external_delivery_receipt_evidence(**_kwargs()) + raw_values = list(evidence) + raw_values[8] = datetime( + 2026, 8, 29, 10, 2, 3, tzinfo=timezone(timedelta(hours=9)) + ) + reconstructed = tuple.__new__(ExternalDeliveryReceiptEvidence, tuple(raw_values)) + + with pytest.raises(ValueError, match="transport_delivered_at"): + reconstructed.canonical_json() + + def test_exact_attempt_verification_rejects_receipt_subclasses() -> None: evidence = build_external_delivery_receipt_evidence(**_kwargs()) From 2844edb5f9135945a1b79cfeb82e15c74a3e9ef2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:01:23 -0700 Subject: [PATCH 11/12] test(outbox): reject hostile receipt equality before scope match --- .../tests/test_runtime_integrity.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py index 7eb7b4225..e7709c3e8 100644 --- a/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py +++ b/packages/outbox-delivery-receipt/tests/test_runtime_integrity.py @@ -28,6 +28,11 @@ def dst(self, dt: datetime | None) -> None: return None +class _ExplosiveEquality: + def __eq__(self, other: object) -> bool: + raise RuntimeError("untrusted evidence equality executed") + + def _kwargs() -> dict[str, object]: return { "tenant_record_id": str(uuid4()), @@ -96,3 +101,20 @@ def sha256_digest(self) -> str: 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()) + raw_values = list(evidence) + raw_values[0] = _ExplosiveEquality() + reconstructed = tuple.__new__(ExternalDeliveryReceiptEvidence, tuple(raw_values)) + + with pytest.raises(ValueError, match="tenant_record_id"): + 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, + ) From a18bb75254af23e5658c4672e063b04bd98cc3de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:03:13 -0700 Subject: [PATCH 12/12] fix(outbox): validate receipt evidence before scope comparison --- .../src/orgmetra_outbox_delivery_receipt/receipt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 21aa915da..7d2dd08c0 100644 --- a/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py +++ b/packages/outbox-delivery-receipt/src/orgmetra_outbox_delivery_receipt/receipt.py @@ -378,6 +378,7 @@ def verify_exact_delivery_attempt( 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"), @@ -394,4 +395,4 @@ def verify_exact_delivery_attempt( ) if actual != expected: raise ValueError("receipt evidence does not match the exact outbox delivery attempt") - return evidence.sha256_digest() + return evidence_digest