From 1690c0ba1781c2bcafee14756e1a5de800304324 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 02:16:58 -0700 Subject: [PATCH 01/32] feat(integration): add governed TEPP analysis request boundary --- .github/workflows/tepp-adapter-quality.yml | 59 ++++ ...governed-tepp-analysis-request-boundary.md | 32 +++ .../tepp-analysis-adapter-references.md | 9 + docs/traceability/tepp-analysis-adapter.md | 18 ++ packages/tepp-adapter/CHANGELOG.md | 7 + packages/tepp-adapter/README.md | 32 +++ packages/tepp-adapter/pyproject.toml | 24 ++ .../src/orgmetra_tepp_adapter/__init__.py | 15 + .../src/orgmetra_tepp_adapter/analysis.py | 259 ++++++++++++++++++ packages/tepp-adapter/tests/test_analysis.py | 221 +++++++++++++++ 10 files changed, 676 insertions(+) create mode 100644 .github/workflows/tepp-adapter-quality.yml create mode 100644 docs/adr/0023-governed-tepp-analysis-request-boundary.md create mode 100644 docs/doctoring/tepp-analysis-adapter-references.md create mode 100644 docs/traceability/tepp-analysis-adapter.md create mode 100644 packages/tepp-adapter/CHANGELOG.md create mode 100644 packages/tepp-adapter/README.md create mode 100644 packages/tepp-adapter/pyproject.toml create mode 100644 packages/tepp-adapter/src/orgmetra_tepp_adapter/__init__.py create mode 100644 packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py create mode 100644 packages/tepp-adapter/tests/test_analysis.py diff --git a/.github/workflows/tepp-adapter-quality.yml b/.github/workflows/tepp-adapter-quality.yml new file mode 100644 index 000000000..c401d6c54 --- /dev/null +++ b/.github/workflows/tepp-adapter-quality.yml @@ -0,0 +1,59 @@ +name: TEPP Adapter Quality + +on: + pull_request: + branches: + - bootstrap + - develop + - main + paths: + - "packages/tepp-adapter/**" + - "docs/adr/0023-governed-tepp-analysis-request-boundary.md" + - "docs/doctoring/tepp-analysis-adapter-references.md" + - "docs/traceability/tepp-analysis-adapter.md" + - ".github/requirements/foundation-test.txt" + - ".github/workflows/tepp-adapter-quality.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tepp-adapter-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit: + name: TEPP request boundary 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 TEPP adapter boundary + run: python -m compileall -q packages/tepp-adapter/src packages/tepp-adapter/tests + - name: Test TEPP request contract with exact statement and branch coverage + env: + PYTHONPATH: packages/tepp-adapter/src + COVERAGE_FILE: /tmp/orgmetra-tepp-adapter.coverage + run: python -m pytest -c packages/tepp-adapter/pyproject.toml packages/tepp-adapter/tests + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" diff --git a/docs/adr/0023-governed-tepp-analysis-request-boundary.md b/docs/adr/0023-governed-tepp-analysis-request-boundary.md new file mode 100644 index 000000000..f26da451f --- /dev/null +++ b/docs/adr/0023-governed-tepp-analysis-request-boundary.md @@ -0,0 +1,32 @@ +# ADR-0023: Governed TEPP analysis-request boundary + +**Status:** Proposed on active PR +**Decision owner:** Orgmetra +**Foreign owner:** TEPP (read-only dependency) + +## Context + +Orgmetra needs temporal/event analytical evidence for workforce validation without duplicating TEPP kernels or violating dedicated-writer ownership. TEPP protected revision `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a` exposes `AnalysisRunRequest` contract v1 in `tepp_api`, with immutable snapshot identity, knowledge cutoff, model contract, output profile, and idempotency key. The same TEPP revision documents that protected main is not yet a production HTTP service. + +A direct HTTP client in Orgmetra would therefore overstate foreign runtime maturity. Conversely, copying TEPP's analytical implementation would create split scientific authority and an acquisition-grade integration liability. + +## Decision + +Orgmetra owns a small pre-transport adapter that: + +1. emits exactly the seven fields accepted by TEPP `AnalysisRunRequest` v1; +2. pins the reviewed TEPP protected revision and contract version as evidence, not as permission to mutate TEPP; +3. binds the request to an Orgmetra tenant, validation study, accountable actor, immutable snapshot digest, evidence version, and deterministic request digest; +4. canonicalizes the temporal cutoff to RFC 3339 UTC and rejects naive instants; +5. distinguishes exact same-key retries from same-key semantic conflicts; +6. treats opaque correlations as linkable personal/governance data while refusing direct identity values, source text, and credentials at this boundary; +7. performs no network transport until the host proves a compatible executable TEPP service contract is published and authorized; and +8. treats returned TEPP/LLM output as untrusted analytical evidence requiring accountable human scientific review before any high-impact employment use. + +Orgmetra does not read TEPP application tables, mutate TEPP source/configuration, or reimplement TEPP's temporal/event/statistical kernels. + +## Consequences + +The integration can be unit-tested and audited now without manufacturing runtime evidence. Future transport can be added behind the same adapter only after TEPP publishes an executable service contract; that future change must add consumer/provider compatibility, timeout/deadline, authentication, retry, observability, privacy, and failure-mode tests against the then-current foreign contract. + +The adapter deliberately requires host re-resolution of tenant/workspace/snapshot/model/output authority. Syntax validation alone never proves tenant membership, artifact existence, policy applicability, or scientific suitability. diff --git a/docs/doctoring/tepp-analysis-adapter-references.md b/docs/doctoring/tepp-analysis-adapter-references.md new file mode 100644 index 000000000..3572df807 --- /dev/null +++ b/docs/doctoring/tepp-analysis-adapter-references.md @@ -0,0 +1,9 @@ +# TEPP analysis adapter references + +Primary technical evidence reviewed for ADR-0023 and the active adapter slice: + +- ContextualWisdomLab. (2026). *TEPP API and modular integration contract* (protected revision `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`, `docs/API_CONTRACT.md`). GitHub repository source. Reviewed August 20, 2026. +- ContextualWisdomLab. (2026). *TEPP analysis-run request and accepted-run response contracts* (protected revision `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`, `crates/tepp_api/src/analysis_run.rs`). GitHub repository source. Reviewed August 20, 2026. +- Klyne, G., & Newman, C. (2002). *Date and time on the Internet: Timestamps* (RFC 3339). Internet Engineering Task Force. https://doi.org/10.17487/RFC3339 + +The TEPP revision is evidence of the reviewed foreign contract, not a claim that Orgmetra owns or may modify TEPP. Before executable transport, re-resolve TEPP's then-current protected contract and service maturity rather than treating this snapshot as perpetual compatibility evidence. diff --git a/docs/traceability/tepp-analysis-adapter.md b/docs/traceability/tepp-analysis-adapter.md new file mode 100644 index 000000000..e469ae85b --- /dev/null +++ b/docs/traceability/tepp-analysis-adapter.md @@ -0,0 +1,18 @@ +# TEPP analysis adapter traceability + +| Requirement | Orgmetra evidence | Verification | Maturity | +|---|---|---|---| +| No foreign table access or duplicated TEPP kernels | `orgmetra_tepp_adapter.analysis` contains only request/governance binding logic | package source review; no TEPP runtime dependency | implemented_on_active_pr | +| Exact TEPP analysis-run v1 request shape | `TeppAnalysisRequestPacket.tepp_request()` | exact seven-field regression against reviewed TEPP DTO | implemented_on_active_pr | +| Temporal cutoff provenance | timezone-aware `knowledge_cutoff` → canonical RFC 3339 UTC | offset conversion, naive/null-offset rejection tests | implemented_on_active_pr | +| Tenant/study/actor governance | canonical UUIDv4 tenant plus namespaced opaque Orgmetra references | UUIDv1/noncanonical/wrong-namespace regressions | implemented_on_active_pr | +| Immutable analytical snapshot evidence | opaque TEPP snapshot ID plus independent SHA-256 `snapshot_digest` | digest format and governance-evidence regressions | implemented_on_active_pr | +| Durable idempotency handoff | `idempotency_key` + deterministic `request_digest()` | exact-retry and same-key conflict regressions | implemented_on_active_pr | +| Privacy minimization without anonymity claims | `contains_personal_data=true`; direct identity/source text/credentials false; redacted repr | direct-construction/replace/privacy regressions | implemented_on_active_pr | +| High-impact human review | fixed `human_scientific_review_only`, `untrusted_draft_evidence`, governed `next_action` | authority-expansion regressions | implemented_on_active_pr | +| Foreign runtime maturity is not overstated | `transport_state=requires_published_tepp_service_contract` | fixed-state regression and package/ADR documentation | implemented_on_active_pr | +| Exact owned production coverage | package pytest-cov gate | 100% statement + branch coverage | implemented_on_active_pr | + +## Foreign evidence snapshot + +Reviewed TEPP protected `main`: `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`. The reviewed `crates/tepp_api/src/analysis_run.rs` defines contract version `1` and the seven request fields above; `docs/API_CONTRACT.md` states that protected main exposes library/domain contracts rather than a production HTTP service. Orgmetra must re-resolve this evidence before enabling transport. diff --git a/packages/tepp-adapter/CHANGELOG.md b/packages/tepp-adapter/CHANGELOG.md new file mode 100644 index 000000000..0fd7077ff --- /dev/null +++ b/packages/tepp-adapter/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## Unreleased + +- Add a non-executing, fail-closed Orgmetra binding for TEPP `AnalysisRunRequest` contract v1, pinned to reviewed TEPP protected revision `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`. +- Bind workforce-validation study/actor provenance, immutable snapshot digest, RFC 3339 cutoff, evidence version, exact request digest, and same-key retry/conflict semantics without copying source text, credentials, or direct identity values into the TEPP request. +- Keep transport disabled until a compatible executable TEPP service contract is published and re-resolved by the host; returned analytical/LLM evidence remains human-scientific-review-only. diff --git a/packages/tepp-adapter/README.md b/packages/tepp-adapter/README.md new file mode 100644 index 000000000..0c9255326 --- /dev/null +++ b/packages/tepp-adapter/README.md @@ -0,0 +1,32 @@ +# Orgmetra TEPP adapter + +`orgmetra-tepp-adapter` is Orgmetra's fail-closed pre-transport boundary for TEPP analysis-run request contract v1. It lets an authorized workforce-validation host bind an Orgmetra validation study, accountable actor, immutable analytical snapshot evidence, temporal cutoff, and idempotency evidence to the exact field shape published by TEPP without reading TEPP tables or copying TEPP implementation code. + +## Current maturity + +This package is **active-PR Orgmetra code**. It does not make TEPP HTTP service availability a shipped fact. The reviewed TEPP protected revision is `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`; at that revision TEPP publishes `AnalysisRunRequest` v1 as a Rust wire DTO, while its API documentation explicitly says protected main is not yet a production HTTP service. Therefore this adapter creates a governed request packet and deterministic request digest but performs no network transport. + +## Contract + +`TeppAnalysisRequestPacket.tepp_request()` emits exactly seven TEPP v1 fields: `contract_version`, `idempotency_key`, `tenant_workspace_id`, `snapshot_id`, RFC 3339 `knowledge_cutoff`, `model_contract_version`, and `output_profile`. Unknown Orgmetra governance fields never enter the foreign body because TEPP's DTO is fail-closed on unknown fields. + +The packet additionally binds, on the Orgmetra side, canonical UUIDv4 tenant identity, opaque validation-study and actor references, an independent SHA-256 snapshot digest, evidence version, generation instant, reviewed TEPP revision, and a digest of the exact TEPP request body. Same-key exact retries are distinguishable from same-key semantic conflicts before transport. + +## Privacy and authority + +Opaque actor, study, workspace, and snapshot correlations remain linkable personal/governance data, so the packet declares `contains_personal_data=true`; it does **not** claim anonymity. Direct identity values, source text, credentials, free-form case narrative, or model output are not accepted by this boundary. The packet representation is fully redacted. + +Model-contract and output-profile strings are syntax-constrained machine codes only; syntax is not authority. Immediately before any transport the host must re-resolve the exact tenant, validation study, accountable actor, TEPP workspace/snapshot binding, snapshot digest, model contract, output profile, purpose authorization, and contract compatibility. Any TEPP/LLM result remains untrusted analytical evidence and cannot autonomously make a high-impact employment decision. + +## Idempotency + +Persist the packet's `idempotency_key` together with `request_digest()` in Orgmetra's authoritative audit/outbox transaction before a future executable transport is enabled. An exact retry has the same key and request digest. Reusing the key with a different request digest is a conflict and must fail closed. This package exposes comparison helpers but does not invent a second persistence store. + +## Test + +```bash +PYTHONPATH=packages/tepp-adapter/src \ +python -m pytest -c packages/tepp-adapter/pyproject.toml packages/tepp-adapter/tests +``` + +The package quality gate requires exact 100% owned production statement and branch coverage. diff --git a/packages/tepp-adapter/pyproject.toml b/packages/tepp-adapter/pyproject.toml new file mode 100644 index 000000000..275847131 --- /dev/null +++ b/packages/tepp-adapter/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + +[project] +name = "orgmetra-tepp-adapter" +version = "0.1.0" +description = "Fail-closed TEPP analysis-run request adapter for governed Orgmetra workforce validation." +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_tepp_adapter", + "--cov-branch", + "--cov-report=term-missing", + "--cov-fail-under=100", +] diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/__init__.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/__init__.py new file mode 100644 index 000000000..66b16cdbb --- /dev/null +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/__init__.py @@ -0,0 +1,15 @@ +"""Public Orgmetra → TEPP analysis-run integration boundary.""" + +from .analysis import ( + TEPP_ANALYSIS_RUN_CONTRACT_VERSION, + TEPP_PROTECTED_REVISION, + TeppAnalysisRequestPacket, + build_tepp_analysis_request_packet, +) + +__all__ = [ + "TEPP_ANALYSIS_RUN_CONTRACT_VERSION", + "TEPP_PROTECTED_REVISION", + "TeppAnalysisRequestPacket", + "build_tepp_analysis_request_packet", +] diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py new file mode 100644 index 000000000..6d463e738 --- /dev/null +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -0,0 +1,259 @@ +"""Fail-closed Orgmetra binding for TEPP analysis-run request contract v1.""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime, timezone +import hashlib +import json +import re +from typing import Final +from uuid import UUID + +TEPP_PROTECTED_REVISION: Final = "7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a" +TEPP_ANALYSIS_RUN_CONTRACT_VERSION: Final = 1 +_PURPOSE_CODE: Final = "workforce_validation_analysis" +_TRANSPORT_STATE: Final = "requires_published_tepp_service_contract" +_DECISION_AUTHORITY: Final = "human_scientific_review_only" +_LLM_OUTPUT_AUTHORITY: Final = "untrusted_draft_evidence" +_MAX_UUID_INT: Final = (1 << 128) - 1 +_SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") +_GOVERNED_CODE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._:-]{0,127}$") +_NEXT_ACTION: Final = ( + "Within tenant_record_id, re-resolve validation_study_reference, requested_by_actor_reference, " + "tepp_workspace_id, and tepp_snapshot_id; prove the workspace and immutable snapshot belong to " + "the authorized workforce-validation scope; verify snapshot_digest and evidence_version; verify " + f"TEPP protected revision {TEPP_PROTECTED_REVISION} remains compatible with analysis-run contract " + "version 1 and that an executable TEPP service contract is actually published before transport; " + "then record accountable human scientific review of any returned analytical evidence before it can " + "influence a high-impact employment decision." +) + + +def _validate_uuid4(value: str, field_name: str) -> None: + """Require one canonical, non-sentinel UUIDv4 string.""" + if not isinstance(value, str): + raise ValueError(f"{field_name} must be a canonical UUIDv4 string") + try: + parsed = UUID(value) + except (ValueError, AttributeError, TypeError) as error: + raise ValueError(f"{field_name} must be a canonical UUIDv4 string") from error + if str(parsed) != value or parsed.version != 4 or parsed.int in {0, _MAX_UUID_INT}: + raise ValueError(f"{field_name} must be a canonical non-sentinel UUIDv4 string") + + +def _validate_reference(value: str, namespace: str, field_name: str) -> None: + """Require one namespaced opaque UUIDv4 reference owned by Orgmetra.""" + if not isinstance(value, str) or not value.startswith(f"{namespace}:"): + raise ValueError(f"{field_name} must use the {namespace}: namespace") + _validate_uuid4(value.removeprefix(f"{namespace}:"), field_name) + + +def _validate_digest(value: str, field_name: str) -> None: + """Require one lowercase SHA-256 evidence digest.""" + if not isinstance(value, str) or _SHA256_PATTERN.fullmatch(value) is None: + raise ValueError(f"{field_name} must be a lowercase SHA-256 digest") + + +def _validate_evidence_version(value: int) -> None: + """Require one bounded positive evidence version.""" + if type(value) is not int or not 1 <= value <= 2_147_483_647: + raise ValueError("evidence_version must be an integer from 1 through 2147483647") + + +def _validate_aware_datetime(value: datetime, field_name: str) -> None: + """Require a datetime with a real UTC offset.""" + if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: + raise ValueError(f"{field_name} must be a timezone-aware datetime") + + +def _canonical_rfc3339(value: datetime) -> str: + """Render an aware instant as deterministic RFC 3339 UTC text.""" + _validate_aware_datetime(value, "knowledge_cutoff") + return value.astimezone(timezone.utc).isoformat(timespec="microseconds").replace("+00:00", "Z") + + +def _validate_opaque_identifier(value: str, field_name: str, maximum_length: int = 256) -> None: + """Require a bounded printable opaque identifier with no whitespace or control bytes.""" + if not isinstance(value, str) or not 1 <= len(value) <= maximum_length: + raise ValueError(f"{field_name} must be a bounded opaque identifier") + if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): + raise ValueError(f"{field_name} must contain only visible ASCII without whitespace") + + +def _validate_idempotency_key(value: str) -> None: + """Require a durable-correlation-safe idempotency key stronger than TEPP's nonempty minimum.""" + if not isinstance(value, str) or not 16 <= len(value) <= 128: + raise ValueError("idempotency_key must contain 16 through 128 visible ASCII characters") + if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): + raise ValueError("idempotency_key must contain 16 through 128 visible ASCII characters") + + +def _validate_governed_code(value: str, field_name: str) -> None: + """Require a bounded machine code rather than free-form narrative.""" + if not isinstance(value, str) or _GOVERNED_CODE_PATTERN.fullmatch(value) is None: + raise ValueError(f"{field_name} must be a bounded governed machine code") + + +@dataclass(frozen=True, slots=True, repr=False) +class TeppAnalysisRequestPacket: + """Immutable pre-transport evidence for one TEPP analysis-run request. + + The packet intentionally does not perform HTTP. It binds Orgmetra governance + evidence to TEPP's published Rust wire DTO while keeping foreign service + availability and authorization authoritative at the host boundary. + """ + + tenant_record_id: str + validation_study_reference: str + requested_by_actor_reference: str + tepp_workspace_id: str + tepp_snapshot_id: str + snapshot_digest: str + idempotency_key: str + knowledge_cutoff: datetime + model_contract_version: str + output_profile: str + generated_at: datetime + evidence_version: int = 1 + purpose_code: str = _PURPOSE_CODE + tepp_contract_version: int = TEPP_ANALYSIS_RUN_CONTRACT_VERSION + tepp_protected_revision: str = TEPP_PROTECTED_REVISION + transport_state: str = _TRANSPORT_STATE + decision_authority: str = _DECISION_AUTHORITY + llm_output_authority: str = _LLM_OUTPUT_AUTHORITY + contains_personal_data: bool = True + contains_direct_identity_values: bool = False + contains_source_text: bool = False + contains_credentials: bool = False + human_confirmation_required: bool = True + next_action: str = _NEXT_ACTION + + def __repr__(self) -> str: + """Return a representation that never emits tenant or evidence correlation values.""" + return "TeppAnalysisRequestPacket()" + + def __post_init__(self) -> None: + """Fail closed when direct construction drifts from the governed boundary.""" + _validate_uuid4(self.tenant_record_id, "tenant_record_id") + _validate_reference(self.validation_study_reference, "validation_study", "validation_study_reference") + _validate_reference(self.requested_by_actor_reference, "actor", "requested_by_actor_reference") + _validate_opaque_identifier(self.tepp_workspace_id, "tepp_workspace_id") + _validate_opaque_identifier(self.tepp_snapshot_id, "tepp_snapshot_id") + _validate_digest(self.snapshot_digest, "snapshot_digest") + _validate_idempotency_key(self.idempotency_key) + _validate_aware_datetime(self.knowledge_cutoff, "knowledge_cutoff") + _validate_governed_code(self.model_contract_version, "model_contract_version") + _validate_governed_code(self.output_profile, "output_profile") + _validate_aware_datetime(self.generated_at, "generated_at") + _validate_evidence_version(self.evidence_version) + if self.purpose_code != _PURPOSE_CODE: + raise ValueError(f"purpose_code must remain {_PURPOSE_CODE}") + if self.tepp_contract_version != TEPP_ANALYSIS_RUN_CONTRACT_VERSION: + raise ValueError("tepp_contract_version must remain 1") + if self.tepp_protected_revision != TEPP_PROTECTED_REVISION: + raise ValueError("tepp_protected_revision must remain pinned to the reviewed protected revision") + if self.transport_state != _TRANSPORT_STATE: + raise ValueError(f"transport_state must remain {_TRANSPORT_STATE}") + if self.decision_authority != _DECISION_AUTHORITY: + raise ValueError(f"decision_authority must remain {_DECISION_AUTHORITY}") + if self.llm_output_authority != _LLM_OUTPUT_AUTHORITY: + raise ValueError(f"llm_output_authority must remain {_LLM_OUTPUT_AUTHORITY}") + if self.contains_personal_data is not True: + raise ValueError("contains_personal_data must remain true because governed correlations are linkable") + for field_name in ("contains_direct_identity_values", "contains_source_text", "contains_credentials"): + if getattr(self, field_name) is not False: + raise ValueError(f"{field_name} must remain false") + if self.human_confirmation_required is not True: + raise ValueError("human_confirmation_required must remain true") + if self.next_action != _NEXT_ACTION: + raise ValueError("next_action must remain the governed TEPP handoff instruction") + + def tepp_request(self) -> dict[str, object]: + """Return exactly the fields accepted by TEPP `AnalysisRunRequest` v1.""" + return { + "contract_version": self.tepp_contract_version, + "idempotency_key": self.idempotency_key, + "tenant_workspace_id": self.tepp_workspace_id, + "snapshot_id": self.tepp_snapshot_id, + "knowledge_cutoff": _canonical_rfc3339(self.knowledge_cutoff), + "model_contract_version": self.model_contract_version, + "output_profile": self.output_profile, + } + + def canonical_tepp_json(self) -> str: + """Return deterministic JSON suitable for request-digest and retry comparison.""" + return json.dumps(self.tepp_request(), sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def request_digest(self) -> str: + """Return SHA-256 over the exact canonical TEPP request body.""" + return hashlib.sha256(self.canonical_tepp_json().encode("utf-8")).hexdigest() + + def is_idempotent_retry_of(self, other: "TeppAnalysisRequestPacket") -> bool: + """Return whether another packet is the same exact-key TEPP request retry.""" + return ( + isinstance(other, TeppAnalysisRequestPacket) + and self.idempotency_key == other.idempotency_key + and self.request_digest() == other.request_digest() + ) + + def idempotency_conflicts_with(self, other: "TeppAnalysisRequestPacket") -> bool: + """Return whether one key has been rebound to different TEPP request semantics.""" + return ( + isinstance(other, TeppAnalysisRequestPacket) + and self.idempotency_key == other.idempotency_key + and self.request_digest() != other.request_digest() + ) + + def governance_evidence(self) -> dict[str, object]: + """Return value-minimized Orgmetra evidence for durable audit/outbox correlation.""" + return { + "tenant_record_id": self.tenant_record_id, + "validation_study_reference": self.validation_study_reference, + "requested_by_actor_reference": self.requested_by_actor_reference, + "snapshot_digest": self.snapshot_digest, + "evidence_version": self.evidence_version, + "generated_at": _canonical_rfc3339(self.generated_at), + "purpose_code": self.purpose_code, + "tepp_contract_version": self.tepp_contract_version, + "tepp_protected_revision": self.tepp_protected_revision, + "tepp_request_digest": self.request_digest(), + "transport_state": self.transport_state, + "decision_authority": self.decision_authority, + "llm_output_authority": self.llm_output_authority, + "contains_personal_data": self.contains_personal_data, + "contains_direct_identity_values": self.contains_direct_identity_values, + "human_confirmation_required": self.human_confirmation_required, + } + + +def build_tepp_analysis_request_packet( + *, + tenant_record_id: str, + validation_study_reference: str, + requested_by_actor_reference: str, + tepp_workspace_id: str, + tepp_snapshot_id: str, + snapshot_digest: str, + idempotency_key: str, + knowledge_cutoff: datetime, + model_contract_version: str, + output_profile: str, + generated_at: datetime, + evidence_version: int = 1, +) -> TeppAnalysisRequestPacket: + """Build one packet without exposing fixed authority or privacy controls as inputs.""" + return TeppAnalysisRequestPacket( + tenant_record_id=tenant_record_id, + validation_study_reference=validation_study_reference, + requested_by_actor_reference=requested_by_actor_reference, + tepp_workspace_id=tepp_workspace_id, + tepp_snapshot_id=tepp_snapshot_id, + snapshot_digest=snapshot_digest, + idempotency_key=idempotency_key, + knowledge_cutoff=knowledge_cutoff, + model_contract_version=model_contract_version, + output_profile=output_profile, + generated_at=generated_at, + evidence_version=evidence_version, + ) diff --git a/packages/tepp-adapter/tests/test_analysis.py b/packages/tepp-adapter/tests/test_analysis.py new file mode 100644 index 000000000..b2a4a3901 --- /dev/null +++ b/packages/tepp-adapter/tests/test_analysis.py @@ -0,0 +1,221 @@ +"""Contract, privacy, and evidence regressions for the Orgmetra → TEPP adapter.""" + +from dataclasses import FrozenInstanceError, replace +from datetime import datetime, timedelta, timezone, tzinfo +import json +from uuid import uuid1 + +import pytest + +from orgmetra_tepp_adapter import ( + TEPP_ANALYSIS_RUN_CONTRACT_VERSION, + TEPP_PROTECTED_REVISION, + TeppAnalysisRequestPacket, + build_tepp_analysis_request_packet, +) + + +def values() -> dict[str, object]: + """Return one valid, non-PII workforce-validation request.""" + return { + "tenant_record_id": "11111111-1111-4111-8111-111111111111", + "validation_study_reference": "validation_study:22222222-2222-4222-8222-222222222222", + "requested_by_actor_reference": "actor:33333333-3333-4333-8333-333333333333", + "tepp_workspace_id": "workspace-opaque-8e9f1d", + "tepp_snapshot_id": "snapshot-opaque-2f6c91", + "snapshot_digest": "a" * 64, + "idempotency_key": "orgmetra-tepp-20260820-0001", + "knowledge_cutoff": datetime(2026, 8, 20, 16, 45, 12, 345678, tzinfo=timezone(timedelta(hours=9))), + "model_contract_version": "temporal-event-v1", + "output_profile": "validation-report", + "generated_at": datetime(2026, 8, 20, 7, 50, 1, 123456, tzinfo=timezone.utc), + } + + +def build_valid() -> TeppAnalysisRequestPacket: + """Build a valid packet through the public builder.""" + return build_tepp_analysis_request_packet(**values()) + + +def test_request_matches_exact_tepp_v1_wire_shape_and_is_deterministic() -> None: + packet = build_valid() + request = packet.tepp_request() + + assert request == { + "contract_version": 1, + "idempotency_key": "orgmetra-tepp-20260820-0001", + "tenant_workspace_id": "workspace-opaque-8e9f1d", + "snapshot_id": "snapshot-opaque-2f6c91", + "knowledge_cutoff": "2026-08-20T07:45:12.345678Z", + "model_contract_version": "temporal-event-v1", + "output_profile": "validation-report", + } + assert TEPP_ANALYSIS_RUN_CONTRACT_VERSION == 1 + assert TEPP_PROTECTED_REVISION == "7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a" + assert json.loads(packet.canonical_tepp_json()) == request + assert len(packet.request_digest()) == 64 + assert packet.request_digest() == build_valid().request_digest() + + +def test_retry_comparison_detects_exact_replays_and_same_key_conflicts() -> None: + packet = build_valid() + same = build_valid() + changed_values = values() + changed_values["tepp_snapshot_id"] = "snapshot-opaque-different" + conflict = TeppAnalysisRequestPacket(**changed_values) + different_key_values = values() + different_key_values["idempotency_key"] = "orgmetra-tepp-20260820-0002" + different_key = TeppAnalysisRequestPacket(**different_key_values) + + assert packet.is_idempotent_retry_of(same) + assert not packet.idempotency_conflicts_with(same) + assert not packet.is_idempotent_retry_of(conflict) + assert packet.idempotency_conflicts_with(conflict) + assert not packet.is_idempotent_retry_of(different_key) + assert not packet.idempotency_conflicts_with(different_key) + assert not packet.is_idempotent_retry_of(object()) + assert not packet.idempotency_conflicts_with(object()) + + +def test_governance_evidence_is_value_minimized_and_actionable() -> None: + packet = build_valid() + evidence = packet.governance_evidence() + + assert evidence["tenant_record_id"] == packet.tenant_record_id + assert evidence["validation_study_reference"] == packet.validation_study_reference + assert evidence["requested_by_actor_reference"] == packet.requested_by_actor_reference + assert evidence["snapshot_digest"] == "a" * 64 + assert evidence["generated_at"] == "2026-08-20T07:50:01.123456Z" + assert evidence["tepp_contract_version"] == 1 + assert evidence["tepp_protected_revision"] == TEPP_PROTECTED_REVISION + assert evidence["tepp_request_digest"] == packet.request_digest() + assert evidence["transport_state"] == "requires_published_tepp_service_contract" + assert evidence["decision_authority"] == "human_scientific_review_only" + assert evidence["llm_output_authority"] == "untrusted_draft_evidence" + assert evidence["contains_personal_data"] is True + assert evidence["contains_direct_identity_values"] is False + assert evidence["human_confirmation_required"] is True + assert "source_text" not in evidence + assert "credentials" not in evidence + assert "salary" not in json.dumps(evidence) + assert "executable TEPP service contract is actually published before transport" in packet.next_action + assert "human scientific review" in packet.next_action + + +def test_repr_and_immutability_protect_correlation_values() -> None: + packet = build_valid() + rendered = repr(packet) + + assert rendered == "TeppAnalysisRequestPacket()" + assert packet.tenant_record_id not in rendered + assert packet.validation_study_reference not in rendered + assert packet.snapshot_digest not in rendered + with pytest.raises(FrozenInstanceError): + packet.tepp_workspace_id = "changed" # type: ignore[misc] + + +@pytest.mark.parametrize( + ("field_name", "value", "message"), + [ + ("tenant_record_id", str(uuid1()), "UUIDv4"), + ("tenant_record_id", "11111111-1111-4111-8111-11111111111A", "UUIDv4"), + ("tenant_record_id", 1, "UUIDv4"), + ("validation_study_reference", "wrong:22222222-2222-4222-8222-222222222222", "validation_study"), + ("validation_study_reference", f"validation_study:{uuid1()}", "UUIDv4"), + ("requested_by_actor_reference", "actor:not-a-uuid", "UUIDv4"), + ("snapshot_digest", "A" * 64, "lowercase SHA-256"), + ("snapshot_digest", 1, "lowercase SHA-256"), + ("evidence_version", 0, "1 through"), + ("evidence_version", True, "1 through"), + ("evidence_version", 2_147_483_648, "1 through"), + ], +) +def test_local_authority_and_evidence_identifiers_fail_closed(field_name: str, value: object, message: str) -> None: + kwargs = values() + kwargs[field_name] = value + with pytest.raises(ValueError, match=message): + TeppAnalysisRequestPacket(**kwargs) + + +@pytest.mark.parametrize( + ("field_name", "value", "message"), + [ + ("tepp_workspace_id", "", "bounded opaque"), + ("tepp_workspace_id", "a" * 257, "bounded opaque"), + ("tepp_workspace_id", "workspace with space", "visible ASCII"), + ("tepp_workspace_id", 7, "bounded opaque"), + ("tepp_snapshot_id", "snapshot\nvalue", "visible ASCII"), + ("idempotency_key", "short", "16 through 128"), + ("idempotency_key", "a" * 129, "16 through 128"), + ("idempotency_key", "valid-length-but space", "16 through 128"), + ("idempotency_key", 7, "16 through 128"), + ("model_contract_version", "Temporal Event", "governed machine code"), + ("model_contract_version", "", "governed machine code"), + ("model_contract_version", 1, "governed machine code"), + ("output_profile", "x" * 129, "governed machine code"), + ], +) +def test_foreign_contract_tokens_are_bounded_without_inventing_foreign_authority( + field_name: str, value: object, message: str +) -> None: + kwargs = values() + kwargs[field_name] = value + with pytest.raises(ValueError, match=message): + TeppAnalysisRequestPacket(**kwargs) + + +class NullOffsetTz(tzinfo): + """Timezone object whose offset is intentionally unusable.""" + + def utcoffset(self, dt: datetime | None) -> None: + return None + + def dst(self, dt: datetime | None) -> None: + return None + + def tzname(self, dt: datetime | None) -> str: + return "NULL" + + +@pytest.mark.parametrize( + ("field_name", "value"), + [ + ("knowledge_cutoff", datetime(2026, 8, 20, 7, 45)), + ("knowledge_cutoff", "2026-08-20T07:45:00Z"), + ("generated_at", datetime(2026, 8, 20, 7, 50).replace(tzinfo=NullOffsetTz())), + ], +) +def test_temporal_evidence_requires_real_timezone_aware_instants(field_name: str, value: object) -> None: + kwargs = values() + kwargs[field_name] = value + with pytest.raises(ValueError, match="timezone-aware"): + TeppAnalysisRequestPacket(**kwargs) + + +@pytest.mark.parametrize( + ("field_name", "value", "message"), + [ + ("purpose_code", "selection_decision", "workforce_validation_analysis"), + ("tepp_contract_version", 2, "remain 1"), + ("tepp_protected_revision", "0" * 40, "reviewed protected revision"), + ("transport_state", "ready_to_send", "requires_published_tepp_service_contract"), + ("decision_authority", "automated", "human_scientific_review_only"), + ("llm_output_authority", "authoritative", "untrusted_draft_evidence"), + ("contains_personal_data", False, "must remain true"), + ("contains_direct_identity_values", True, "must remain false"), + ("contains_source_text", True, "must remain false"), + ("contains_credentials", True, "must remain false"), + ("human_confirmation_required", False, "must remain true"), + ("next_action", "Send immediately.", "governed TEPP handoff"), + ], +) +def test_direct_construction_and_replace_cannot_expand_authority( + field_name: str, value: object, message: str +) -> None: + kwargs = values() + kwargs[field_name] = value + with pytest.raises(ValueError, match=message): + TeppAnalysisRequestPacket(**kwargs) + + with pytest.raises(ValueError, match=message): + replace(build_valid(), **{field_name: value}) From c0ab1cbc955add58b4ce1644f7c08c3e7f9568fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 02:18:46 -0700 Subject: [PATCH 02/32] test(integration): expose TEPP credential and evidence gaps --- packages/tepp-adapter/tests/test_analysis.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/tepp-adapter/tests/test_analysis.py b/packages/tepp-adapter/tests/test_analysis.py index b2a4a3901..9f9f2c7e3 100644 --- a/packages/tepp-adapter/tests/test_analysis.py +++ b/packages/tepp-adapter/tests/test_analysis.py @@ -84,6 +84,8 @@ def test_governance_evidence_is_value_minimized_and_actionable() -> None: assert evidence["tenant_record_id"] == packet.tenant_record_id assert evidence["validation_study_reference"] == packet.validation_study_reference assert evidence["requested_by_actor_reference"] == packet.requested_by_actor_reference + assert evidence["tepp_workspace_id"] == packet.tepp_workspace_id + assert evidence["tepp_snapshot_id"] == packet.tepp_snapshot_id assert evidence["snapshot_digest"] == "a" * 64 assert evidence["generated_at"] == "2026-08-20T07:50:01.123456Z" assert evidence["tepp_contract_version"] == 1 @@ -143,6 +145,8 @@ def test_local_authority_and_evidence_identifiers_fail_closed(field_name: str, v ("tepp_workspace_id", "", "bounded opaque"), ("tepp_workspace_id", "a" * 257, "bounded opaque"), ("tepp_workspace_id", "workspace with space", "visible ASCII"), + ("tepp_workspace_id", "sk-secret-looking-token", "credential-shaped"), + ("tepp_snapshot_id", "github_pat_secretlooking", "credential-shaped"), ("tepp_workspace_id", 7, "bounded opaque"), ("tepp_snapshot_id", "snapshot\nvalue", "visible ASCII"), ("idempotency_key", "short", "16 through 128"), From 346d31d86bb6aa6b8534692521ea556226a84cc2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 02:19:23 -0700 Subject: [PATCH 03/32] fix(integration): harden TEPP foreign identifiers and audit evidence --- .../tepp-adapter/src/orgmetra_tepp_adapter/analysis.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 6d463e738..b4f292e5a 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -19,6 +19,7 @@ _MAX_UUID_INT: Final = (1 << 128) - 1 _SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") _GOVERNED_CODE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._:-]{0,127}$") +_CREDENTIAL_PREFIXES: Final = ("sk-", "ghp_", "github_pat_", "nvapi-", "bearer") _NEXT_ACTION: Final = ( "Within tenant_record_id, re-resolve validation_study_reference, requested_by_actor_reference, " "tepp_workspace_id, and tepp_snapshot_id; prove the workspace and immutable snapshot belong to " @@ -74,11 +75,13 @@ def _canonical_rfc3339(value: datetime) -> str: def _validate_opaque_identifier(value: str, field_name: str, maximum_length: int = 256) -> None: - """Require a bounded printable opaque identifier with no whitespace or control bytes.""" + """Require a bounded printable opaque identifier with no whitespace, controls, or credential shape.""" if not isinstance(value, str) or not 1 <= len(value) <= maximum_length: raise ValueError(f"{field_name} must be a bounded opaque identifier") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): raise ValueError(f"{field_name} must contain only visible ASCII without whitespace") + if value.lower().startswith(_CREDENTIAL_PREFIXES): + raise ValueError(f"{field_name} must not contain a credential-shaped value") def _validate_idempotency_key(value: str) -> None: @@ -206,11 +209,13 @@ def idempotency_conflicts_with(self, other: "TeppAnalysisRequestPacket") -> bool ) def governance_evidence(self) -> dict[str, object]: - """Return value-minimized Orgmetra evidence for durable audit/outbox correlation.""" + """Return purpose-minimized Orgmetra evidence for durable audit/outbox correlation.""" return { "tenant_record_id": self.tenant_record_id, "validation_study_reference": self.validation_study_reference, "requested_by_actor_reference": self.requested_by_actor_reference, + "tepp_workspace_id": self.tepp_workspace_id, + "tepp_snapshot_id": self.tepp_snapshot_id, "snapshot_digest": self.snapshot_digest, "evidence_version": self.evidence_version, "generated_at": _canonical_rfc3339(self.generated_at), From 52a26de6bc225143891c14132e413de95410af50 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 03:05:51 -0700 Subject: [PATCH 04/32] test(tepp): reject credential-shaped idempotency keys --- packages/tepp-adapter/tests/test_analysis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tepp-adapter/tests/test_analysis.py b/packages/tepp-adapter/tests/test_analysis.py index 9f9f2c7e3..0568e5495 100644 --- a/packages/tepp-adapter/tests/test_analysis.py +++ b/packages/tepp-adapter/tests/test_analysis.py @@ -152,6 +152,7 @@ def test_local_authority_and_evidence_identifiers_fail_closed(field_name: str, v ("idempotency_key", "short", "16 through 128"), ("idempotency_key", "a" * 129, "16 through 128"), ("idempotency_key", "valid-length-but space", "16 through 128"), + ("idempotency_key", "sk-secret-looking-idempotency", "credential-shaped"), ("idempotency_key", 7, "16 through 128"), ("model_contract_version", "Temporal Event", "governed machine code"), ("model_contract_version", "", "governed machine code"), From de983c781b69301fda20d051d042c82cf668c827 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 03:06:26 -0700 Subject: [PATCH 05/32] fix(tepp): reject credential-shaped idempotency keys --- packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index b4f292e5a..303468f58 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -90,6 +90,8 @@ def _validate_idempotency_key(value: str) -> None: raise ValueError("idempotency_key must contain 16 through 128 visible ASCII characters") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): raise ValueError("idempotency_key must contain 16 through 128 visible ASCII characters") + if value.lower().startswith(_CREDENTIAL_PREFIXES): + raise ValueError("idempotency_key must not contain a credential-shaped value") def _validate_governed_code(value: str, field_name: str) -> None: From b23c15f548b7ba77dfe43a41bb110b2216cff859 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 03:08:54 -0700 Subject: [PATCH 06/32] docs(tepp): record idempotency credential guard --- packages/tepp-adapter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tepp-adapter/CHANGELOG.md b/packages/tepp-adapter/CHANGELOG.md index 0fd7077ff..42e91e309 100644 --- a/packages/tepp-adapter/CHANGELOG.md +++ b/packages/tepp-adapter/CHANGELOG.md @@ -4,4 +4,5 @@ - Add a non-executing, fail-closed Orgmetra binding for TEPP `AnalysisRunRequest` contract v1, pinned to reviewed TEPP protected revision `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`. - Bind workforce-validation study/actor provenance, immutable snapshot digest, RFC 3339 cutoff, evidence version, exact request digest, and same-key retry/conflict semantics without copying source text, credentials, or direct identity values into the TEPP request. +- Reject common credential-shaped values in both TEPP foreign opaque identifiers and the idempotency key so the packet cannot persist or forward an obvious secret while asserting that credentials are absent. - Keep transport disabled until a compatible executable TEPP service contract is published and re-resolved by the host; returned analytical/LLM evidence remains human-scientific-review-only. From 1c485a38041ff61ecc9f17d6bf0d5f92bc5283aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 07:04:11 -0700 Subject: [PATCH 07/32] test(tepp-adapter): cover operational tenant UUID interoperability --- packages/tepp-adapter/tests/test_analysis.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/tepp-adapter/tests/test_analysis.py b/packages/tepp-adapter/tests/test_analysis.py index 0568e5495..4fdcf3387 100644 --- a/packages/tepp-adapter/tests/test_analysis.py +++ b/packages/tepp-adapter/tests/test_analysis.py @@ -37,6 +37,18 @@ def build_valid() -> TeppAnalysisRequestPacket: return build_tepp_analysis_request_packet(**values()) +def test_tenant_identity_accepts_protected_core_operational_uuid_contract() -> None: + """Keep this leaf interoperable with authoritative Orgmetra tenant UUIDs.""" + tenant_uuid7 = "10000000-0000-7000-8000-000000000001" + kwargs = values() + kwargs["tenant_record_id"] = tenant_uuid7 + + packet = build_tepp_analysis_request_packet(**kwargs) + + assert packet.tenant_record_id == tenant_uuid7 + assert replace(build_valid(), tenant_record_id=tenant_uuid7).tenant_record_id == tenant_uuid7 + + def test_request_matches_exact_tepp_v1_wire_shape_and_is_deterministic() -> None: packet = build_valid() request = packet.tepp_request() @@ -119,9 +131,10 @@ def test_repr_and_immutability_protect_correlation_values() -> None: @pytest.mark.parametrize( ("field_name", "value", "message"), [ - ("tenant_record_id", str(uuid1()), "UUIDv4"), - ("tenant_record_id", "11111111-1111-4111-8111-11111111111A", "UUIDv4"), - ("tenant_record_id", 1, "UUIDv4"), + ("tenant_record_id", "00000000-0000-0000-0000-000000000000", "UUID"), + ("tenant_record_id", "ffffffff-ffff-ffff-ffff-ffffffffffff", "UUID"), + ("tenant_record_id", "11111111-1111-4111-8111-11111111111A", "UUID"), + ("tenant_record_id", 1, "UUID"), ("validation_study_reference", "wrong:22222222-2222-4222-8222-222222222222", "validation_study"), ("validation_study_reference", f"validation_study:{uuid1()}", "UUIDv4"), ("requested_by_actor_reference", "actor:not-a-uuid", "UUIDv4"), From 3cf53b0639ba2968c5761ab30664320a3d20f67b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 07:05:09 -0700 Subject: [PATCH 08/32] fix(tepp-adapter): honor authoritative tenant UUID contract --- .../src/orgmetra_tepp_adapter/analysis.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 303468f58..7e4f9db50 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -31,6 +31,18 @@ ) +def _validate_operational_uuid(value: str, field_name: str) -> None: + """Require canonical non-sentinel UUID text for an authoritative Orgmetra identity.""" + if not isinstance(value, str): + raise ValueError(f"{field_name} must be canonical UUID text") + try: + parsed = UUID(value) + except (ValueError, AttributeError, TypeError) as error: + raise ValueError(f"{field_name} must be canonical UUID text") from error + if str(parsed) != value or parsed.int in {0, _MAX_UUID_INT}: + raise ValueError(f"{field_name} must be a canonical operational UUID") + + def _validate_uuid4(value: str, field_name: str) -> None: """Require one canonical, non-sentinel UUIDv4 string.""" if not isinstance(value, str): @@ -140,7 +152,7 @@ def __repr__(self) -> str: def __post_init__(self) -> None: """Fail closed when direct construction drifts from the governed boundary.""" - _validate_uuid4(self.tenant_record_id, "tenant_record_id") + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") _validate_reference(self.validation_study_reference, "validation_study", "validation_study_reference") _validate_reference(self.requested_by_actor_reference, "actor", "requested_by_actor_reference") _validate_opaque_identifier(self.tepp_workspace_id, "tepp_workspace_id") From 7106925a4c1c09a2920e973adefb45040dc88546 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 07:05:28 -0700 Subject: [PATCH 09/32] docs(tepp-adapter): align tenant identity ownership --- packages/tepp-adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tepp-adapter/README.md b/packages/tepp-adapter/README.md index 0c9255326..9fce2c7a2 100644 --- a/packages/tepp-adapter/README.md +++ b/packages/tepp-adapter/README.md @@ -10,7 +10,7 @@ This package is **active-PR Orgmetra code**. It does not make TEPP HTTP service `TeppAnalysisRequestPacket.tepp_request()` emits exactly seven TEPP v1 fields: `contract_version`, `idempotency_key`, `tenant_workspace_id`, `snapshot_id`, RFC 3339 `knowledge_cutoff`, `model_contract_version`, and `output_profile`. Unknown Orgmetra governance fields never enter the foreign body because TEPP's DTO is fail-closed on unknown fields. -The packet additionally binds, on the Orgmetra side, canonical UUIDv4 tenant identity, opaque validation-study and actor references, an independent SHA-256 snapshot digest, evidence version, generation instant, reviewed TEPP revision, and a digest of the exact TEPP request body. Same-key exact retries are distinguishable from same-key semantic conflicts before transport. +The packet additionally binds, on the Orgmetra side, the authoritative canonical non-sentinel operational tenant UUID, opaque UUIDv4 validation-study and actor references, an independent SHA-256 snapshot digest, evidence version, generation instant, reviewed TEPP revision, and a digest of the exact TEPP request body. Same-key exact retries are distinguishable from same-key semantic conflicts before transport. The tenant identifier follows the protected Orgmetra core identity contract rather than imposing a duplicate UUID-version policy; packet-owned trust references remain UUIDv4-constrained. ## Privacy and authority From 3aa35ae05c21991c61813da8cff8c9ce3289037e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 07:05:39 -0700 Subject: [PATCH 10/32] docs(traceability): record TEPP tenant UUID interoperability --- docs/traceability/tepp-analysis-adapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/traceability/tepp-analysis-adapter.md b/docs/traceability/tepp-analysis-adapter.md index e469ae85b..c0e8c2386 100644 --- a/docs/traceability/tepp-analysis-adapter.md +++ b/docs/traceability/tepp-analysis-adapter.md @@ -5,7 +5,7 @@ | No foreign table access or duplicated TEPP kernels | `orgmetra_tepp_adapter.analysis` contains only request/governance binding logic | package source review; no TEPP runtime dependency | implemented_on_active_pr | | Exact TEPP analysis-run v1 request shape | `TeppAnalysisRequestPacket.tepp_request()` | exact seven-field regression against reviewed TEPP DTO | implemented_on_active_pr | | Temporal cutoff provenance | timezone-aware `knowledge_cutoff` → canonical RFC 3339 UTC | offset conversion, naive/null-offset rejection tests | implemented_on_active_pr | -| Tenant/study/actor governance | canonical UUIDv4 tenant plus namespaced opaque Orgmetra references | UUIDv1/noncanonical/wrong-namespace regressions | implemented_on_active_pr | +| Tenant/study/actor governance | authoritative canonical non-sentinel operational tenant UUID plus namespaced UUIDv4 Orgmetra references | protected-core UUIDv7 interoperability plus nil/max/noncanonical tenant rejection; wrong-namespace/reference-version regressions | implemented_on_active_pr | | Immutable analytical snapshot evidence | opaque TEPP snapshot ID plus independent SHA-256 `snapshot_digest` | digest format and governance-evidence regressions | implemented_on_active_pr | | Durable idempotency handoff | `idempotency_key` + deterministic `request_digest()` | exact-retry and same-key conflict regressions | implemented_on_active_pr | | Privacy minimization without anonymity claims | `contains_personal_data=true`; direct identity/source text/credentials false; redacted repr | direct-construction/replace/privacy regressions | implemented_on_active_pr | From 3d64d3a0371e56b15ff1aac57e7cfe0d5d90b24e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 07:05:48 -0700 Subject: [PATCH 11/32] docs(changelog): record TEPP tenant UUID repair --- packages/tepp-adapter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tepp-adapter/CHANGELOG.md b/packages/tepp-adapter/CHANGELOG.md index 42e91e309..6e63d7b05 100644 --- a/packages/tepp-adapter/CHANGELOG.md +++ b/packages/tepp-adapter/CHANGELOG.md @@ -5,4 +5,5 @@ - Add a non-executing, fail-closed Orgmetra binding for TEPP `AnalysisRunRequest` contract v1, pinned to reviewed TEPP protected revision `7c29e7c971d7940e1fb3def1ed3aae2d1bc8ad4a`. - Bind workforce-validation study/actor provenance, immutable snapshot digest, RFC 3339 cutoff, evidence version, exact request digest, and same-key retry/conflict semantics without copying source text, credentials, or direct identity values into the TEPP request. - Reject common credential-shaped values in both TEPP foreign opaque identifiers and the idempotency key so the packet cannot persist or forward an obvious secret while asserting that credentials are absent. +- Align `tenant_record_id` with protected Orgmetra's canonical non-sentinel operational UUID contract, including UUIDv7 interoperability, while keeping packet-owned validation-study and actor references UUIDv4-constrained. - Keep transport disabled until a compatible executable TEPP service contract is published and re-resolved by the host; returned analytical/LLM evidence remains human-scientific-review-only. From b6d61c1ae19b601ce77de89db9f90b6b9681a0e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 08:55:45 -0700 Subject: [PATCH 12/32] test(tepp): cover malformed tenant UUID rejection --- packages/tepp-adapter/tests/test_analysis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tepp-adapter/tests/test_analysis.py b/packages/tepp-adapter/tests/test_analysis.py index 4fdcf3387..15d74b9d1 100644 --- a/packages/tepp-adapter/tests/test_analysis.py +++ b/packages/tepp-adapter/tests/test_analysis.py @@ -134,6 +134,7 @@ def test_repr_and_immutability_protect_correlation_values() -> None: ("tenant_record_id", "00000000-0000-0000-0000-000000000000", "UUID"), ("tenant_record_id", "ffffffff-ffff-ffff-ffff-ffffffffffff", "UUID"), ("tenant_record_id", "11111111-1111-4111-8111-11111111111A", "UUID"), + ("tenant_record_id", "not-a-uuid", "UUID"), ("tenant_record_id", 1, "UUID"), ("validation_study_reference", "wrong:22222222-2222-4222-8222-222222222222", "validation_study"), ("validation_study_reference", f"validation_study:{uuid1()}", "UUIDv4"), From 98434ef6b839d98078560b9951ea146699093be1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 08:56:21 -0700 Subject: [PATCH 13/32] fix(tepp): remove unreachable UUIDv4 guard --- packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 7e4f9db50..d32fd082d 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -44,9 +44,7 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: def _validate_uuid4(value: str, field_name: str) -> None: - """Require one canonical, non-sentinel UUIDv4 string.""" - if not isinstance(value, str): - raise ValueError(f"{field_name} must be a canonical UUIDv4 string") + """Require one canonical, non-sentinel UUIDv4 string from a validated namespace suffix.""" try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as error: From 5f84b2224b5b2c959ad67dcc7326a34020ab8aa8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:15:31 -0700 Subject: [PATCH 14/32] test(tepp-adapter): reject temporal evidence subclasses --- .../tests/test_temporal_evidence_integrity.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/tepp-adapter/tests/test_temporal_evidence_integrity.py diff --git a/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py b/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py new file mode 100644 index 000000000..75bf1c411 --- /dev/null +++ b/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py @@ -0,0 +1,48 @@ +"""Regression coverage for TEPP temporal-evidence canonicalization integrity.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest + +from orgmetra_tepp_adapter import build_tepp_analysis_request_packet + + +class ForgedDateTime(datetime): + """Datetime subclass able to forge canonical TEPP/audit evidence.""" + + def astimezone(self, tz=None): # type: ignore[no-untyped-def] + """Keep the hostile subclass alive across UTC normalization.""" + return self + + def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] + """Return an instant different from the underlying evidence instant.""" + return "2099-12-31T23:59:59+00:00" + + +def valid_kwargs() -> dict[str, object]: + """Return one otherwise valid Orgmetra-to-TEPP request packet input.""" + return { + "tenant_record_id": "11111111-1111-4111-8111-111111111111", + "validation_study_reference": "validation_study:22222222-2222-4222-8222-222222222222", + "requested_by_actor_reference": "actor:33333333-3333-4333-8333-333333333333", + "tepp_workspace_id": "workspace-opaque-8e9f1d", + "tepp_snapshot_id": "snapshot-opaque-2f6c91", + "snapshot_digest": "a" * 64, + "idempotency_key": "orgmetra-tepp-20260821-0001", + "knowledge_cutoff": datetime(2026, 8, 21, 3, 30, tzinfo=timezone.utc), + "model_contract_version": "temporal-event-v1", + "output_profile": "validation-report", + "generated_at": datetime(2026, 8, 21, 4, 30, tzinfo=timezone.utc), + } + + +@pytest.mark.parametrize("field_name", ["knowledge_cutoff", "generated_at"]) +def test_rejects_datetime_subclasses_that_can_forge_temporal_evidence(field_name: str) -> None: + """TEPP request and audit evidence must not invoke caller-overridable datetime methods.""" + kwargs = valid_kwargs() + kwargs[field_name] = ForgedDateTime(2026, 8, 21, 4, 30, tzinfo=timezone.utc) + + with pytest.raises(ValueError, match="timezone-aware datetime"): + build_tepp_analysis_request_packet(**kwargs) From fd1b7e63ed9f804661b2415167d4bc9b96d3d8fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:16:10 -0700 Subject: [PATCH 15/32] fix(tepp-adapter): require exact temporal evidence types --- packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index d32fd082d..30a1d78e2 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -73,8 +73,8 @@ def _validate_evidence_version(value: int) -> None: def _validate_aware_datetime(value: datetime, field_name: str) -> None: - """Require a datetime with a real UTC offset.""" - if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: + """Require an exact built-in datetime with a real UTC offset.""" + if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: raise ValueError(f"{field_name} must be a timezone-aware datetime") From 4e24809b873614997369881d37e101f7ca6197aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:14:18 -0700 Subject: [PATCH 16/32] test(tepp-adapter): reject forged string evidence types --- .../test_string_runtime_evidence_integrity.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py diff --git a/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py b/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py new file mode 100644 index 000000000..f7df86e9f --- /dev/null +++ b/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py @@ -0,0 +1,85 @@ +"""Regression coverage for hostile string subclasses at the TEPP evidence boundary.""" + +from __future__ import annotations + +from datetime import datetime, timedelta, timezone + +import pytest + +from orgmetra_tepp_adapter import build_tepp_analysis_request_packet + + +class ForgedReference(str): + """Forge namespace removal while retaining attacker-controlled underlying text.""" + + def startswith(self, prefix, *args): # type: ignore[no-untyped-def] + return True + + def removeprefix(self, prefix): # type: ignore[no-untyped-def] + return "22222222-2222-4222-8222-222222222222" + + +class ForgedTenantUUIDText(str): + """Forge UUID parsing/canonical equality while retaining malformed tenant text.""" + + def replace(self, old, new, *args): # type: ignore[no-untyped-def] + canonical = "11111111-1111-4111-8111-111111111111" + return canonical.replace(old, new, *args) + + def __eq__(self, other): # type: ignore[no-untyped-def] + return other is not None + + def __ne__(self, other): # type: ignore[no-untyped-def] + return other is None + + +class ForgedOpaqueIdentifier(str): + """Hide a credential-shaped underlying value from the credential detector.""" + + def lower(self): # type: ignore[no-untyped-def] + return "workspace-safe" + + +def values() -> dict[str, object]: + """Return one valid governed TEPP request packet input.""" + return { + "tenant_record_id": "11111111-1111-4111-8111-111111111111", + "validation_study_reference": "validation_study:22222222-2222-4222-8222-222222222222", + "requested_by_actor_reference": "actor:33333333-3333-4333-8333-333333333333", + "tepp_workspace_id": "workspace-opaque-8e9f1d", + "tepp_snapshot_id": "snapshot-opaque-2f6c91", + "snapshot_digest": "a" * 64, + "idempotency_key": "orgmetra-tepp-20260820-0001", + "knowledge_cutoff": datetime(2026, 8, 20, 16, 45, 12, 345678, tzinfo=timezone(timedelta(hours=9))), + "model_contract_version": "temporal-event-v1", + "output_profile": "validation-report", + "generated_at": datetime(2026, 8, 20, 7, 50, 1, 123456, tzinfo=timezone.utc), + } + + +def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: + kwargs = values() + kwargs["validation_study_reference"] = ForgedReference("evil-reference") + with pytest.raises(ValueError, match="validation_study_reference"): + build_tepp_analysis_request_packet(**kwargs) + + +def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: + kwargs = values() + kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") + with pytest.raises(ValueError, match="tenant_record_id"): + build_tepp_analysis_request_packet(**kwargs) + + +def test_rejects_opaque_string_subclass_that_can_hide_credential_shape() -> None: + kwargs = values() + kwargs["tepp_workspace_id"] = ForgedOpaqueIdentifier("sk-secret-workspace") + with pytest.raises(ValueError, match="tepp_workspace_id"): + build_tepp_analysis_request_packet(**kwargs) + + +def test_rejects_idempotency_string_subclass_that_can_hide_credential_shape() -> None: + kwargs = values() + kwargs["idempotency_key"] = ForgedOpaqueIdentifier("sk-secret-idempotency-key") + with pytest.raises(ValueError, match="idempotency_key"): + build_tepp_analysis_request_packet(**kwargs) From edbe5b56b0edb1af6204fdddaa8fc03059d2dced Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:15:06 -0700 Subject: [PATCH 17/32] fix(tepp-adapter): require exact string evidence types --- .../src/orgmetra_tepp_adapter/analysis.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 30a1d78e2..93d43d0f6 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -33,7 +33,7 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: """Require canonical non-sentinel UUID text for an authoritative Orgmetra identity.""" - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be canonical UUID text") try: parsed = UUID(value) @@ -44,7 +44,9 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: def _validate_uuid4(value: str, field_name: str) -> None: - """Require one canonical, non-sentinel UUIDv4 string from a validated namespace suffix.""" + """Require one exact canonical, non-sentinel UUIDv4 string.""" + if type(value) is not str: + raise ValueError(f"{field_name} must be a canonical UUIDv4 string") try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as error: @@ -55,14 +57,14 @@ def _validate_uuid4(value: str, field_name: str) -> None: def _validate_reference(value: str, namespace: str, field_name: str) -> None: """Require one namespaced opaque UUIDv4 reference owned by Orgmetra.""" - if not isinstance(value, str) or not value.startswith(f"{namespace}:"): + if type(value) is not str or not value.startswith(f"{namespace}:"): raise ValueError(f"{field_name} must use the {namespace}: namespace") _validate_uuid4(value.removeprefix(f"{namespace}:"), field_name) def _validate_digest(value: str, field_name: str) -> None: """Require one lowercase SHA-256 evidence digest.""" - if not isinstance(value, str) or _SHA256_PATTERN.fullmatch(value) is None: + if type(value) is not str or _SHA256_PATTERN.fullmatch(value) is None: raise ValueError(f"{field_name} must be a lowercase SHA-256 digest") @@ -85,8 +87,8 @@ def _canonical_rfc3339(value: datetime) -> str: def _validate_opaque_identifier(value: str, field_name: str, maximum_length: int = 256) -> None: - """Require a bounded printable opaque identifier with no whitespace, controls, or credential shape.""" - if not isinstance(value, str) or not 1 <= len(value) <= maximum_length: + """Require exact bounded printable text with no whitespace, controls, or credential shape.""" + if type(value) is not str or not 1 <= len(value) <= maximum_length: raise ValueError(f"{field_name} must be a bounded opaque identifier") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): raise ValueError(f"{field_name} must contain only visible ASCII without whitespace") @@ -95,8 +97,8 @@ def _validate_opaque_identifier(value: str, field_name: str, maximum_length: int def _validate_idempotency_key(value: str) -> None: - """Require a durable-correlation-safe idempotency key stronger than TEPP's nonempty minimum.""" - if not isinstance(value, str) or not 16 <= len(value) <= 128: + """Require an exact durable-correlation-safe idempotency key.""" + if type(value) is not str or not 16 <= len(value) <= 128: raise ValueError("idempotency_key must contain 16 through 128 visible ASCII characters") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): raise ValueError("idempotency_key must contain 16 through 128 visible ASCII characters") @@ -105,8 +107,8 @@ def _validate_idempotency_key(value: str) -> None: def _validate_governed_code(value: str, field_name: str) -> None: - """Require a bounded machine code rather than free-form narrative.""" - if not isinstance(value, str) or _GOVERNED_CODE_PATTERN.fullmatch(value) is None: + """Require exact bounded machine-code text rather than free-form narrative.""" + if type(value) is not str or _GOVERNED_CODE_PATTERN.fullmatch(value) is None: raise ValueError(f"{field_name} must be a bounded governed machine code") @@ -273,4 +275,4 @@ def build_tepp_analysis_request_packet( output_profile=output_profile, generated_at=generated_at, evidence_version=evidence_version, - ) + ) \ No newline at end of file From ea3a1445af0536830ab0a9834ba746ef07c267f3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:38:09 -0700 Subject: [PATCH 18/32] test(tepp-adapter): bind retries to Orgmetra governance scope --- .../test_idempotency_governance_scope.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 packages/tepp-adapter/tests/test_idempotency_governance_scope.py diff --git a/packages/tepp-adapter/tests/test_idempotency_governance_scope.py b/packages/tepp-adapter/tests/test_idempotency_governance_scope.py new file mode 100644 index 000000000..4a3325174 --- /dev/null +++ b/packages/tepp-adapter/tests/test_idempotency_governance_scope.py @@ -0,0 +1,35 @@ +"""Regression coverage for Orgmetra-scoped TEPP idempotency semantics.""" + +from __future__ import annotations + +from dataclasses import replace + +import pytest + +from test_analysis import build_valid + + +@pytest.mark.parametrize( + ("field_name", "replacement"), + ( + ("tenant_record_id", "44444444-4444-4444-8444-444444444444"), + ( + "validation_study_reference", + "validation_study:55555555-5555-4555-8555-555555555555", + ), + ("requested_by_actor_reference", "actor:66666666-6666-4666-8666-666666666666"), + ("snapshot_digest", "b" * 64), + ("evidence_version", 2), + ), +) +def test_same_tepp_request_key_cannot_replay_across_governance_scope( + field_name: str, + replacement: object, +) -> None: + """Treat same-key reuse across local tenant/study/actor/evidence scope as conflict.""" + packet = build_valid() + rebound = replace(packet, **{field_name: replacement}) + + assert packet.request_digest() == rebound.request_digest() + assert not packet.is_idempotent_retry_of(rebound) + assert packet.idempotency_conflicts_with(rebound) From caa1bbdfa26b453bda21146b62c1ed339b45246c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:39:01 -0700 Subject: [PATCH 19/32] fix(tepp-adapter): scope idempotency to governance evidence --- .../src/orgmetra_tepp_adapter/analysis.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 93d43d0f6..9466c1793 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -206,20 +206,36 @@ def request_digest(self) -> str: """Return SHA-256 over the exact canonical TEPP request body.""" return hashlib.sha256(self.canonical_tepp_json().encode("utf-8")).hexdigest() + def governance_scope_digest(self) -> str: + """Return SHA-256 over local governance fields that must remain stable on retry.""" + payload = { + "evidence_version": self.evidence_version, + "requested_by_actor_reference": self.requested_by_actor_reference, + "snapshot_digest": self.snapshot_digest, + "tenant_record_id": self.tenant_record_id, + "validation_study_reference": self.validation_study_reference, + } + canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest() + def is_idempotent_retry_of(self, other: "TeppAnalysisRequestPacket") -> bool: - """Return whether another packet is the same exact-key TEPP request retry.""" + """Return whether another packet replays the same key, TEPP body, and local scope.""" return ( - isinstance(other, TeppAnalysisRequestPacket) + type(other) is TeppAnalysisRequestPacket and self.idempotency_key == other.idempotency_key and self.request_digest() == other.request_digest() + and self.governance_scope_digest() == other.governance_scope_digest() ) def idempotency_conflicts_with(self, other: "TeppAnalysisRequestPacket") -> bool: - """Return whether one key has been rebound to different TEPP request semantics.""" + """Return whether one key has been rebound to different TEPP or governance semantics.""" return ( - isinstance(other, TeppAnalysisRequestPacket) + type(other) is TeppAnalysisRequestPacket and self.idempotency_key == other.idempotency_key - and self.request_digest() != other.request_digest() + and ( + self.request_digest() != other.request_digest() + or self.governance_scope_digest() != other.governance_scope_digest() + ) ) def governance_evidence(self) -> dict[str, object]: From aa9d63b2ef16ca12efdd9677cfdccef50dd0463c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:39:21 -0700 Subject: [PATCH 20/32] test(tepp-adapter): require durable idempotency evidence --- .../tests/test_idempotency_governance_scope.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/tepp-adapter/tests/test_idempotency_governance_scope.py b/packages/tepp-adapter/tests/test_idempotency_governance_scope.py index 4a3325174..8211baad6 100644 --- a/packages/tepp-adapter/tests/test_idempotency_governance_scope.py +++ b/packages/tepp-adapter/tests/test_idempotency_governance_scope.py @@ -33,3 +33,14 @@ def test_same_tepp_request_key_cannot_replay_across_governance_scope( assert packet.request_digest() == rebound.request_digest() assert not packet.is_idempotent_retry_of(rebound) assert packet.idempotency_conflicts_with(rebound) + + +def test_governance_evidence_carries_the_key_required_for_durable_retry_control() -> None: + """Keep the durable audit/outbox evidence self-sufficient for retry/conflict checks.""" + packet = build_valid() + + evidence = packet.governance_evidence() + + assert evidence["idempotency_key"] == packet.idempotency_key + assert evidence["tepp_request_digest"] == packet.request_digest() + assert evidence["governance_scope_digest"] == packet.governance_scope_digest() From 3895bc416c693753bed479ccfdb0257d1923a29f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:39:56 -0700 Subject: [PATCH 21/32] fix(tepp-adapter): persist durable retry correlation evidence --- packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 9466c1793..4c6c54023 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -252,7 +252,9 @@ def governance_evidence(self) -> dict[str, object]: "purpose_code": self.purpose_code, "tepp_contract_version": self.tepp_contract_version, "tepp_protected_revision": self.tepp_protected_revision, + "idempotency_key": self.idempotency_key, "tepp_request_digest": self.request_digest(), + "governance_scope_digest": self.governance_scope_digest(), "transport_state": self.transport_state, "decision_authority": self.decision_authority, "llm_output_authority": self.llm_output_authority, From 3fe42ecab4a20f986556800f9b814e844dead149 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:42:18 -0700 Subject: [PATCH 22/32] test(tepp-adapter): cover exact UUIDv4 type guard --- .../tests/test_string_runtime_evidence_integrity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py b/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py index f7df86e9f..bdfb845b8 100644 --- a/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py +++ b/packages/tepp-adapter/tests/test_string_runtime_evidence_integrity.py @@ -7,6 +7,7 @@ import pytest from orgmetra_tepp_adapter import build_tepp_analysis_request_packet +from orgmetra_tepp_adapter.analysis import _validate_uuid4 class ForgedReference(str): @@ -58,6 +59,7 @@ def values() -> dict[str, object]: def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: + """Reject subclassed references before namespace helpers can be overridden.""" kwargs = values() kwargs["validation_study_reference"] = ForgedReference("evil-reference") with pytest.raises(ValueError, match="validation_study_reference"): @@ -65,13 +67,21 @@ def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: + """Reject subclassed authoritative tenant text before UUID canonicalization.""" kwargs = values() kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") with pytest.raises(ValueError, match="tenant_record_id"): build_tepp_analysis_request_packet(**kwargs) +def test_uuid4_validator_rejects_string_subclass_before_uuid_parsing() -> None: + """Keep the shared UUIDv4 validator independently fail-closed for future callers.""" + with pytest.raises(ValueError, match="probe_reference"): + _validate_uuid4(ForgedTenantUUIDText("22222222-2222-4222-8222-222222222222"), "probe_reference") + + def test_rejects_opaque_string_subclass_that_can_hide_credential_shape() -> None: + """Reject subclassed opaque identifiers before credential-shape inspection.""" kwargs = values() kwargs["tepp_workspace_id"] = ForgedOpaqueIdentifier("sk-secret-workspace") with pytest.raises(ValueError, match="tepp_workspace_id"): @@ -79,6 +89,7 @@ def test_rejects_opaque_string_subclass_that_can_hide_credential_shape() -> None def test_rejects_idempotency_string_subclass_that_can_hide_credential_shape() -> None: + """Reject subclassed idempotency keys before credential-shape inspection.""" kwargs = values() kwargs["idempotency_key"] = ForgedOpaqueIdentifier("sk-secret-idempotency-key") with pytest.raises(ValueError, match="idempotency_key"): From e068df783a4688e26114c29853bda3943a419c18 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 02:55:54 +0900 Subject: [PATCH 23/32] fix(tepp-adapter): bind cross-field scope correlation and temporal ordering Strix white-box review of head 3fe42eca found a MEDIUM IDOR-shaped gap: individual field formats were enforced without cross-field relationship checks, so reference manipulation could keep every format valid. Repairs at the packet boundary: - generated_at must not precede knowledge_cutoff (temporal ordering) - tepp_workspace_id and tepp_snapshot_id must be distinct identifiers - governance_scope_digest now binds idempotency_key, workspace and snapshot correlations so any single-reference swap changes the digest and fails the durable retry/conflict comparison Regression coverage: 67 tests, 100% owned statement/branch coverage. --- .../src/orgmetra_tepp_adapter/analysis.py | 14 ++++++++++++- .../test_idempotency_governance_scope.py | 19 ++++++++++++++++++ .../tests/test_temporal_evidence_integrity.py | 20 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index 4c6c54023..c56dbf47f 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -163,6 +163,10 @@ def __post_init__(self) -> None: _validate_governed_code(self.model_contract_version, "model_contract_version") _validate_governed_code(self.output_profile, "output_profile") _validate_aware_datetime(self.generated_at, "generated_at") + if self.generated_at < self.knowledge_cutoff: + raise ValueError("generated_at must not precede knowledge_cutoff") + if self.tepp_workspace_id == self.tepp_snapshot_id: + raise ValueError("tepp_workspace_id and tepp_snapshot_id must be distinct opaque identifiers") _validate_evidence_version(self.evidence_version) if self.purpose_code != _PURPOSE_CODE: raise ValueError(f"purpose_code must remain {_PURPOSE_CODE}") @@ -207,11 +211,19 @@ def request_digest(self) -> str: return hashlib.sha256(self.canonical_tepp_json().encode("utf-8")).hexdigest() def governance_scope_digest(self) -> str: - """Return SHA-256 over local governance fields that must remain stable on retry.""" + """Return SHA-256 over local governance fields that must remain stable on retry. + + Every tenant-, study-, actor-, workspace-, snapshot-, and idempotency-scoped + correlation participates, so swapping any single reference between packets + from different scopes changes the digest and fails the retry comparison. + """ payload = { "evidence_version": self.evidence_version, + "idempotency_key": self.idempotency_key, "requested_by_actor_reference": self.requested_by_actor_reference, "snapshot_digest": self.snapshot_digest, + "tepp_snapshot_id": self.tepp_snapshot_id, + "tepp_workspace_id": self.tepp_workspace_id, "tenant_record_id": self.tenant_record_id, "validation_study_reference": self.validation_study_reference, } diff --git a/packages/tepp-adapter/tests/test_idempotency_governance_scope.py b/packages/tepp-adapter/tests/test_idempotency_governance_scope.py index 8211baad6..2f4a3c563 100644 --- a/packages/tepp-adapter/tests/test_idempotency_governance_scope.py +++ b/packages/tepp-adapter/tests/test_idempotency_governance_scope.py @@ -44,3 +44,22 @@ def test_governance_evidence_carries_the_key_required_for_durable_retry_control( assert evidence["idempotency_key"] == packet.idempotency_key assert evidence["tepp_request_digest"] == packet.request_digest() assert evidence["governance_scope_digest"] == packet.governance_scope_digest() + + +@pytest.mark.parametrize( + ("field_name", "replacement"), + ( + ("idempotency_key", "orgmetra-tepp-20260820-9999"), + ("tepp_snapshot_id", "snapshot-opaque-tampered"), + ("tepp_workspace_id", "workspace-opaque-tampered"), + ), +) +def test_scope_digest_binds_every_retry_stable_correlation( + field_name: str, + replacement: object, +) -> None: + """Detect reference manipulation of any correlation inside the retry-stable scope.""" + packet = build_valid() + rebound = replace(packet, **{field_name: replacement}) + + assert packet.governance_scope_digest() != rebound.governance_scope_digest() diff --git a/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py b/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py index 75bf1c411..ffb693d64 100644 --- a/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py +++ b/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py @@ -46,3 +46,23 @@ def test_rejects_datetime_subclasses_that_can_forge_temporal_evidence(field_name with pytest.raises(ValueError, match="timezone-aware datetime"): build_tepp_analysis_request_packet(**kwargs) + + +def test_generated_at_must_not_precede_knowledge_cutoff() -> None: + """Reject packets whose generation instant precedes their own knowledge cutoff.""" + kwargs = valid_kwargs() + kwargs["knowledge_cutoff"] = datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc) + kwargs["generated_at"] = datetime(2026, 8, 21, 4, 59, 59, 999999, tzinfo=timezone.utc) + + with pytest.raises(ValueError, match="generated_at must not precede knowledge_cutoff"): + build_tepp_analysis_request_packet(**kwargs) + + +def test_equal_workspace_and_snapshot_identifiers_are_rejected() -> None: + """Reference manipulation cannot reuse one opaque identifier for two entity types.""" + kwargs = valid_kwargs() + kwargs["tepp_workspace_id"] = "opaque-duplicated-1f2e3d" + kwargs["tepp_snapshot_id"] = "opaque-duplicated-1f2e3d" + + with pytest.raises(ValueError, match="must be distinct opaque identifiers"): + build_tepp_analysis_request_packet(**kwargs) From d440606e357c49e1052bc81d18d70e7b8ec6205d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 22:29:38 -0700 Subject: [PATCH 24/32] test(tepp-adapter): require broad retry comparison annotations --- .../tests/test_retry_type_annotations.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 packages/tepp-adapter/tests/test_retry_type_annotations.py diff --git a/packages/tepp-adapter/tests/test_retry_type_annotations.py b/packages/tepp-adapter/tests/test_retry_type_annotations.py new file mode 100644 index 000000000..c2cadca33 --- /dev/null +++ b/packages/tepp-adapter/tests/test_retry_type_annotations.py @@ -0,0 +1,14 @@ +"""Static contract regressions for TEPP retry-comparison helper annotations.""" + +from typing import get_type_hints + +from orgmetra_tepp_adapter import TeppAnalysisRequestPacket + + +def test_retry_comparison_accepts_arbitrary_runtime_objects_in_its_type_contract() -> None: + """Keep annotations aligned with the exact-type fail-closed runtime boundary.""" + retry_hints = get_type_hints(TeppAnalysisRequestPacket.is_idempotent_retry_of) + conflict_hints = get_type_hints(TeppAnalysisRequestPacket.idempotency_conflicts_with) + + assert retry_hints["other"] is object + assert conflict_hints["other"] is object From e33acd239f389b27853e7c0a4c648830d7f6b448 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 22:30:35 -0700 Subject: [PATCH 25/32] fix(tepp-adapter): align retry comparison type contract --- packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index c56dbf47f..dcf4a5db6 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -230,7 +230,7 @@ def governance_scope_digest(self) -> str: canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) return hashlib.sha256(canonical.encode("utf-8")).hexdigest() - def is_idempotent_retry_of(self, other: "TeppAnalysisRequestPacket") -> bool: + def is_idempotent_retry_of(self, other: object) -> bool: """Return whether another packet replays the same key, TEPP body, and local scope.""" return ( type(other) is TeppAnalysisRequestPacket @@ -239,7 +239,7 @@ def is_idempotent_retry_of(self, other: "TeppAnalysisRequestPacket") -> bool: and self.governance_scope_digest() == other.governance_scope_digest() ) - def idempotency_conflicts_with(self, other: "TeppAnalysisRequestPacket") -> bool: + def idempotency_conflicts_with(self, other: object) -> bool: """Return whether one key has been rebound to different TEPP or governance semantics.""" return ( type(other) is TeppAnalysisRequestPacket From b3cbd2a53922b5a1bb222f9fc9804fe875535f35 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 04:28:17 +0900 Subject: [PATCH 26/32] style(tepp-adapter): remove ADR trailing whitespace --- docs/adr/0023-governed-tepp-analysis-request-boundary.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adr/0023-governed-tepp-analysis-request-boundary.md b/docs/adr/0023-governed-tepp-analysis-request-boundary.md index f26da451f..5b7b3612e 100644 --- a/docs/adr/0023-governed-tepp-analysis-request-boundary.md +++ b/docs/adr/0023-governed-tepp-analysis-request-boundary.md @@ -1,7 +1,7 @@ # ADR-0023: Governed TEPP analysis-request boundary -**Status:** Proposed on active PR -**Decision owner:** Orgmetra +**Status:** Proposed on active PR +**Decision owner:** Orgmetra **Foreign owner:** TEPP (read-only dependency) ## Context From 15b448baf159bc420840d9e4a2429d4eac6990a1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:40:17 +0900 Subject: [PATCH 27/32] fix(tepp): freeze request temporal evidence --- CHANGELOG.md | 1 + ...governed-tepp-analysis-request-boundary.md | 2 +- docs/traceability/tepp-analysis-adapter.md | 2 +- manifest.json | 2 +- packages/tepp-adapter/CHANGELOG.md | 1 + packages/tepp-adapter/README.md | 2 +- .../src/orgmetra_tepp_adapter/analysis.py | 40 +++++--- .../tests/test_temporal_evidence_integrity.py | 94 ++++++++++++++++++- 8 files changed, 128 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f4752d7..4e70902fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to Orgmetra will be documented in this file. - Active-PR `orgmetra_selection_review` packet for PII-minimized, evidence-bound human selection review: canonical operational tenant identity, UUID-backed opaque candidate/Job/sealed-evidence/reviewer references, explicit purpose/reason/evidence version, deterministic canonical JSON and SHA-256 correlation, mandatory human decision state, redacted packet repr, and provenance-paired model evidence that remains `untrusted_draft`, with exact 100% owned statement and branch coverage required by its quality gate. - Active performance-criterion scope hardening: `criterion_observation_scope_guard` rejects criterion outcomes for a Job the worker did not effectively hold at the observation date, observations before the relevant assignment, and observations outside the referenced performance cycle while preserving valid multiple-assignment cases and existing bitemporal correction semantics. The guard evaluates current-recorded facts, derives the date coordinate from `observed_at` in UTC so session `TimeZone` cannot alter the result, uses a trusted function search path, and adds no PII or automated employment decision authority. The Foundation PostgreSQL contract also rejects a closed `recorded_to` on each time-coordinate lookup and proves UTC midnight plus non-UTC session `TimeZone` boundaries. - Bitemporal tenant-scoped organization hierarchy validation that rejects visible indirect parent cycles and reuses single-valued recorded-time reconstruction before graph traversal. +- Active-PR TEPP analysis-request evidence now detaches knowledge and generation times to exact UTC instants and normalizes failing or overflowing timezone providers before request or governance digests are emitted. - Stacked governed job-analysis evidence contract via `JobAnalysisSnapshot`, `TaskEvidence`, `KSAORequirement`, `TaskKSAOLink`, `FunctionalJobAnalysisProfile`, and `EvidenceSource`: tenant/Job-scoped observable tasks, explicit Task-to-KSAO linkage, importance/difficulty/proficiency ratings, source/version/retrieval/SHA-256 provenance, deterministic canonical snapshot bytes, current O*NET evidence support, and historical DOT Data/People/Things compatibility. Validated snapshots require accountable human review and complete non-LLM evidence; LLM-origin material remains `analysis_draft`, and the snapshot is evidence input rather than a hiring, promotion, termination, compensation, or other high-impact employment decision. - Stacked governed audit/outbox slice via `AuditOutboxEvent`, `audit_event_record`, `outbox_delivery_record`, and `outbox_delivery_escalation_record`: CloudEvents 1.0-compatible PII-minimized metadata, exact canonical JSON bytes, database-verified SHA-256 digests, mandatory human confirmation for high-impact events, immutable audit evidence, tenant RLS, atomic audit/outbox insertion, guarded pending/leased/delivered/dead-lettered delivery state, tenant-safe `claim_outbox_delivery(...)` with deterministic due-work ordering, `FOR UPDATE ... SKIP LOCKED`, opaque worker identity, bounded future leases, immutable envelope return, and atomic takeover of genuinely expired leases only while retry attempts remain; owner-bound `complete_outbox_delivery(...)` and `retry_outbox_delivery(...)`; database-budget-governed `dead_letter_outbox_delivery(...)`; and a separately privileged `operator_dead_letter_expired_outbox_delivery(...)` recovery path for an exhausted final lease whose recorded worker identity is permanently unavailable. `maximum_attempt_count` is persisted on the delivery row, defaults to 5, is constrained to 1 through 100, and cannot be lowered by a dispatcher during finalization. Migration 0007 prevents retry or expired-lease takeover from creating attempt N+1; migration 0008 adds TRUNCATE guards, trusted function search paths, a concurrently built due-work partial index, session-independent immutable envelope validation, and operator recovery backed by separate NOLOGIN/NOBYPASSRLS owner/capability roles so the externally assignable operator role can invoke recovery without receiving direct transport-table read/write rights. Migration 0008 also rejects pre-existing reserved recovery-role names before project DDL, atomically contains the temporary schema-creation privilege used for function ownership handoff, and forces deferred escalation binding while the narrow SECURITY DEFINER owner is still active. Exponential/backoff policy selection, policy-specific producer configuration, and external delivery receipts remain subsequent work. - `orgmetra_hris_kernel` 0.4.0 with exclusive-versus-concurrent employment, staffable position coverage, exclusive-seat capacity, and `validate_assignment_write` at 100% statement and branch coverage. diff --git a/docs/adr/0023-governed-tepp-analysis-request-boundary.md b/docs/adr/0023-governed-tepp-analysis-request-boundary.md index 5b7b3612e..687eb911c 100644 --- a/docs/adr/0023-governed-tepp-analysis-request-boundary.md +++ b/docs/adr/0023-governed-tepp-analysis-request-boundary.md @@ -17,7 +17,7 @@ Orgmetra owns a small pre-transport adapter that: 1. emits exactly the seven fields accepted by TEPP `AnalysisRunRequest` v1; 2. pins the reviewed TEPP protected revision and contract version as evidence, not as permission to mutate TEPP; 3. binds the request to an Orgmetra tenant, validation study, accountable actor, immutable snapshot digest, evidence version, and deterministic request digest; -4. canonicalizes the temporal cutoff to RFC 3339 UTC and rejects naive instants; +4. detaches the knowledge cutoff and generation instant to exact UTC datetimes, canonicalizes them to RFC 3339 UTC, and rejects naive or unusable instants; 5. distinguishes exact same-key retries from same-key semantic conflicts; 6. treats opaque correlations as linkable personal/governance data while refusing direct identity values, source text, and credentials at this boundary; 7. performs no network transport until the host proves a compatible executable TEPP service contract is published and authorized; and diff --git a/docs/traceability/tepp-analysis-adapter.md b/docs/traceability/tepp-analysis-adapter.md index c0e8c2386..3199bbeb4 100644 --- a/docs/traceability/tepp-analysis-adapter.md +++ b/docs/traceability/tepp-analysis-adapter.md @@ -4,7 +4,7 @@ |---|---|---|---| | No foreign table access or duplicated TEPP kernels | `orgmetra_tepp_adapter.analysis` contains only request/governance binding logic | package source review; no TEPP runtime dependency | implemented_on_active_pr | | Exact TEPP analysis-run v1 request shape | `TeppAnalysisRequestPacket.tepp_request()` | exact seven-field regression against reviewed TEPP DTO | implemented_on_active_pr | -| Temporal cutoff provenance | timezone-aware `knowledge_cutoff` → canonical RFC 3339 UTC | offset conversion, naive/null-offset rejection tests | implemented_on_active_pr | +| Temporal cutoff provenance | timezone-aware `knowledge_cutoff` and `generated_at` → detached exact UTC instants and canonical RFC 3339 UTC | mutable-timezone stability, provider-failure, malformed-offset, arithmetic-overflow, naive/null-offset, and datetime-subclass rejection tests | implemented_on_active_pr | | Tenant/study/actor governance | authoritative canonical non-sentinel operational tenant UUID plus namespaced UUIDv4 Orgmetra references | protected-core UUIDv7 interoperability plus nil/max/noncanonical tenant rejection; wrong-namespace/reference-version regressions | implemented_on_active_pr | | Immutable analytical snapshot evidence | opaque TEPP snapshot ID plus independent SHA-256 `snapshot_digest` | digest format and governance-evidence regressions | implemented_on_active_pr | | Durable idempotency handoff | `idempotency_key` + deterministic `request_digest()` | exact-retry and same-key conflict regressions | implemented_on_active_pr | diff --git a/manifest.json b/manifest.json index 97f2bab14..aba0bdd8c 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"8f722e70756d20ce251cebd49b4066c1a3a9298d0333986143263dc27d58b35d","bytes":17511,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} diff --git a/packages/tepp-adapter/CHANGELOG.md b/packages/tepp-adapter/CHANGELOG.md index 6e63d7b05..e00c68ed8 100644 --- a/packages/tepp-adapter/CHANGELOG.md +++ b/packages/tepp-adapter/CHANGELOG.md @@ -6,4 +6,5 @@ - Bind workforce-validation study/actor provenance, immutable snapshot digest, RFC 3339 cutoff, evidence version, exact request digest, and same-key retry/conflict semantics without copying source text, credentials, or direct identity values into the TEPP request. - Reject common credential-shaped values in both TEPP foreign opaque identifiers and the idempotency key so the packet cannot persist or forward an obvious secret while asserting that credentials are absent. - Align `tenant_record_id` with protected Orgmetra's canonical non-sentinel operational UUID contract, including UUIDv7 interoperability, while keeping packet-owned validation-study and actor references UUIDv4-constrained. +- Detach timezone-aware `knowledge_cutoff` and `generated_at` values to exact UTC datetimes before canonical request and governance evidence, rejecting failing, malformed, or overflowing timezone providers. - Keep transport disabled until a compatible executable TEPP service contract is published and re-resolved by the host; returned analytical/LLM evidence remains human-scientific-review-only. diff --git a/packages/tepp-adapter/README.md b/packages/tepp-adapter/README.md index 9fce2c7a2..5c439c82e 100644 --- a/packages/tepp-adapter/README.md +++ b/packages/tepp-adapter/README.md @@ -10,7 +10,7 @@ This package is **active-PR Orgmetra code**. It does not make TEPP HTTP service `TeppAnalysisRequestPacket.tepp_request()` emits exactly seven TEPP v1 fields: `contract_version`, `idempotency_key`, `tenant_workspace_id`, `snapshot_id`, RFC 3339 `knowledge_cutoff`, `model_contract_version`, and `output_profile`. Unknown Orgmetra governance fields never enter the foreign body because TEPP's DTO is fail-closed on unknown fields. -The packet additionally binds, on the Orgmetra side, the authoritative canonical non-sentinel operational tenant UUID, opaque UUIDv4 validation-study and actor references, an independent SHA-256 snapshot digest, evidence version, generation instant, reviewed TEPP revision, and a digest of the exact TEPP request body. Same-key exact retries are distinguishable from same-key semantic conflicts before transport. The tenant identifier follows the protected Orgmetra core identity contract rather than imposing a duplicate UUID-version policy; packet-owned trust references remain UUIDv4-constrained. +The packet additionally binds, on the Orgmetra side, the authoritative canonical non-sentinel operational tenant UUID, opaque UUIDv4 validation-study and actor references, an independent SHA-256 snapshot digest, evidence version, generation instant, reviewed TEPP revision, and a digest of the exact TEPP request body. Timezone-aware knowledge and generation values are detached to exact UTC datetimes at construction, so caller-owned timezone providers cannot rewrite request or governance evidence later. Same-key exact retries are distinguishable from same-key semantic conflicts before transport. The tenant identifier follows the protected Orgmetra core identity contract rather than imposing a duplicate UUID-version policy; packet-owned trust references remain UUIDv4-constrained. ## Privacy and authority diff --git a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py index dcf4a5db6..bb1bb8288 100644 --- a/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py +++ b/packages/tepp-adapter/src/orgmetra_tepp_adapter/analysis.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone import hashlib import json import re @@ -74,16 +74,26 @@ def _validate_evidence_version(value: int) -> None: raise ValueError("evidence_version must be an integer from 1 through 2147483647") -def _validate_aware_datetime(value: datetime, field_name: str) -> None: - """Require an exact built-in datetime with a real UTC offset.""" - if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: +def _validate_aware_datetime(value: datetime, field_name: str) -> datetime: + """Validate and detach an exact built-in datetime to a UTC instant.""" + if type(value) is not datetime or value.tzinfo is None: raise ValueError(f"{field_name} must be a timezone-aware datetime") + try: + offset = value.utcoffset() + except Exception as exc: # noqa: BLE001 - normalize provider behavior at trust boundary. + raise ValueError(f"{field_name} must be a timezone-aware datetime") from exc + if type(offset) is not timedelta: + raise ValueError(f"{field_name} must be a timezone-aware datetime") + try: + return (value.replace(tzinfo=None) - offset).replace(tzinfo=timezone.utc) + except OverflowError as exc: + raise ValueError(f"{field_name} must be a timezone-aware datetime") from exc -def _canonical_rfc3339(value: datetime) -> str: +def _canonical_rfc3339(value: datetime, field_name: str = "knowledge_cutoff") -> str: """Render an aware instant as deterministic RFC 3339 UTC text.""" - _validate_aware_datetime(value, "knowledge_cutoff") - return value.astimezone(timezone.utc).isoformat(timespec="microseconds").replace("+00:00", "Z") + canonical = _validate_aware_datetime(value, field_name) + return canonical.isoformat(timespec="microseconds").replace("+00:00", "Z") def _validate_opaque_identifier(value: str, field_name: str, maximum_length: int = 256) -> None: @@ -152,6 +162,16 @@ def __repr__(self) -> str: def __post_init__(self) -> None: """Fail closed when direct construction drifts from the governed boundary.""" + object.__setattr__( + self, + "knowledge_cutoff", + _validate_aware_datetime(self.knowledge_cutoff, "knowledge_cutoff"), + ) + object.__setattr__( + self, + "generated_at", + _validate_aware_datetime(self.generated_at, "generated_at"), + ) _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") _validate_reference(self.validation_study_reference, "validation_study", "validation_study_reference") _validate_reference(self.requested_by_actor_reference, "actor", "requested_by_actor_reference") @@ -159,10 +179,8 @@ def __post_init__(self) -> None: _validate_opaque_identifier(self.tepp_snapshot_id, "tepp_snapshot_id") _validate_digest(self.snapshot_digest, "snapshot_digest") _validate_idempotency_key(self.idempotency_key) - _validate_aware_datetime(self.knowledge_cutoff, "knowledge_cutoff") _validate_governed_code(self.model_contract_version, "model_contract_version") _validate_governed_code(self.output_profile, "output_profile") - _validate_aware_datetime(self.generated_at, "generated_at") if self.generated_at < self.knowledge_cutoff: raise ValueError("generated_at must not precede knowledge_cutoff") if self.tepp_workspace_id == self.tepp_snapshot_id: @@ -260,7 +278,7 @@ def governance_evidence(self) -> dict[str, object]: "tepp_snapshot_id": self.tepp_snapshot_id, "snapshot_digest": self.snapshot_digest, "evidence_version": self.evidence_version, - "generated_at": _canonical_rfc3339(self.generated_at), + "generated_at": _canonical_rfc3339(self.generated_at, "generated_at"), "purpose_code": self.purpose_code, "tepp_contract_version": self.tepp_contract_version, "tepp_protected_revision": self.tepp_protected_revision, @@ -305,4 +323,4 @@ def build_tepp_analysis_request_packet( output_profile=output_profile, generated_at=generated_at, evidence_version=evidence_version, - ) \ No newline at end of file + ) diff --git a/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py b/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py index ffb693d64..59fa598a5 100644 --- a/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py +++ b/packages/tepp-adapter/tests/test_temporal_evidence_integrity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone, tzinfo import pytest @@ -21,6 +21,33 @@ def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] return "2099-12-31T23:59:59+00:00" +class MutableOffsetTimezone(tzinfo): + """Timezone provider whose offset can change after packet construction.""" + + def __init__(self, offset: timedelta) -> None: + self.offset = offset + + def utcoffset(self, value: datetime | None) -> timedelta: + """Return the currently configured offset.""" + return self.offset + + +class ExplodingOffsetTimezone(tzinfo): + """Timezone provider that raises while its offset is requested.""" + + def utcoffset(self, value: datetime | None) -> timedelta: + """Raise to verify provider failures become validation errors.""" + raise RuntimeError("offset provider unavailable") + + +class WrongOffsetTimezone(tzinfo): + """Timezone provider that returns a non-timedelta offset.""" + + def utcoffset(self, value: datetime | None) -> str: # type: ignore[override] + """Return an invalid offset type at the trust boundary.""" + return "not-a-timedelta" + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid Orgmetra-to-TEPP request packet input.""" return { @@ -48,6 +75,71 @@ def test_rejects_datetime_subclasses_that_can_forge_temporal_evidence(field_name build_tepp_analysis_request_packet(**kwargs) +@pytest.mark.parametrize("field_name", ["knowledge_cutoff", "generated_at"]) +def test_freezes_mutable_timezone_before_request_and_governance_evidence(field_name: str) -> None: + """Changing a caller-owned timezone cannot rewrite stored TEPP evidence.""" + provider = MutableOffsetTimezone(timedelta(hours=9)) + kwargs = valid_kwargs() + kwargs[field_name] = datetime( + 2026, + 8, + 21, + 12 if field_name == "knowledge_cutoff" else 13, + 30, + 1 if field_name == "generated_at" else 0, + 123456 if field_name == "generated_at" else 0, + tzinfo=provider, + ) + packet = build_tepp_analysis_request_packet(**kwargs) + request = packet.canonical_tepp_json() + evidence = packet.governance_evidence() + + provider.offset = timedelta(hours=10) + + assert packet.knowledge_cutoff == datetime(2026, 8, 21, 3, 30, tzinfo=timezone.utc) + assert packet.generated_at == datetime( + 2026, + 8, + 21, + 4, + 30, + 1 if field_name == "generated_at" else 0, + 123456 if field_name == "generated_at" else 0, + tzinfo=timezone.utc, + ) + assert packet.canonical_tepp_json() == request + assert packet.governance_evidence() == evidence + + +@pytest.mark.parametrize( + ("field_name", "value"), + [ + ("knowledge_cutoff", datetime.min.replace(tzinfo=timezone(timedelta(hours=1)))), + ("generated_at", datetime.max.replace(tzinfo=timezone(-timedelta(hours=1)))), + ("knowledge_cutoff", datetime(2026, 8, 21, 3, 30, tzinfo=ExplodingOffsetTimezone())), + ("generated_at", datetime(2026, 8, 21, 4, 30, tzinfo=ExplodingOffsetTimezone())), + ("knowledge_cutoff", datetime(2026, 8, 21, 3, 30, tzinfo=WrongOffsetTimezone())), + ("generated_at", datetime(2026, 8, 21, 4, 30, tzinfo=WrongOffsetTimezone())), + ], +) +def test_rejects_unusable_temporal_providers(field_name: str, value: object) -> None: + """Provider failures, malformed offsets, and UTC arithmetic overflow fail closed.""" + kwargs = valid_kwargs() + kwargs[field_name] = value + + with pytest.raises(ValueError, match="timezone-aware datetime"): + build_tepp_analysis_request_packet(**kwargs) + + +def test_canonical_evidence_rejects_low_level_datetime_reinjection() -> None: + """Unsafe post-construction mutation cannot invoke forged datetime renderers.""" + packet = build_tepp_analysis_request_packet(**valid_kwargs()) + object.__setattr__(packet, "knowledge_cutoff", ForgedDateTime(2026, 8, 21, 4, 30, tzinfo=timezone.utc)) + + with pytest.raises(ValueError, match="timezone-aware datetime"): + packet.canonical_tepp_json() + + def test_generated_at_must_not_precede_knowledge_cutoff() -> None: """Reject packets whose generation instant precedes their own knowledge cutoff.""" kwargs = valid_kwargs() From cd81e1b7e247120442bfd86a283bd8451e3e33ca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 23:15:34 +0900 Subject: [PATCH 28/32] docs(tepp): document scope digest persistence --- docs/traceability/tepp-analysis-adapter.md | 2 +- packages/tepp-adapter/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/traceability/tepp-analysis-adapter.md b/docs/traceability/tepp-analysis-adapter.md index 3199bbeb4..640e7ce40 100644 --- a/docs/traceability/tepp-analysis-adapter.md +++ b/docs/traceability/tepp-analysis-adapter.md @@ -7,7 +7,7 @@ | Temporal cutoff provenance | timezone-aware `knowledge_cutoff` and `generated_at` → detached exact UTC instants and canonical RFC 3339 UTC | mutable-timezone stability, provider-failure, malformed-offset, arithmetic-overflow, naive/null-offset, and datetime-subclass rejection tests | implemented_on_active_pr | | Tenant/study/actor governance | authoritative canonical non-sentinel operational tenant UUID plus namespaced UUIDv4 Orgmetra references | protected-core UUIDv7 interoperability plus nil/max/noncanonical tenant rejection; wrong-namespace/reference-version regressions | implemented_on_active_pr | | Immutable analytical snapshot evidence | opaque TEPP snapshot ID plus independent SHA-256 `snapshot_digest` | digest format and governance-evidence regressions | implemented_on_active_pr | -| Durable idempotency handoff | `idempotency_key` + deterministic `request_digest()` | exact-retry and same-key conflict regressions | implemented_on_active_pr | +| Durable idempotency handoff | `idempotency_key` + deterministic `request_digest()` + `governance_scope_digest()` | exact-retry and same-key request/scope conflict regressions | implemented_on_active_pr | | Privacy minimization without anonymity claims | `contains_personal_data=true`; direct identity/source text/credentials false; redacted repr | direct-construction/replace/privacy regressions | implemented_on_active_pr | | High-impact human review | fixed `human_scientific_review_only`, `untrusted_draft_evidence`, governed `next_action` | authority-expansion regressions | implemented_on_active_pr | | Foreign runtime maturity is not overstated | `transport_state=requires_published_tepp_service_contract` | fixed-state regression and package/ADR documentation | implemented_on_active_pr | diff --git a/packages/tepp-adapter/README.md b/packages/tepp-adapter/README.md index 5c439c82e..4e4e51bff 100644 --- a/packages/tepp-adapter/README.md +++ b/packages/tepp-adapter/README.md @@ -20,7 +20,7 @@ Model-contract and output-profile strings are syntax-constrained machine codes o ## Idempotency -Persist the packet's `idempotency_key` together with `request_digest()` in Orgmetra's authoritative audit/outbox transaction before a future executable transport is enabled. An exact retry has the same key and request digest. Reusing the key with a different request digest is a conflict and must fail closed. This package exposes comparison helpers but does not invent a second persistence store. +Persist the packet's `idempotency_key`, `request_digest()`, and `governance_scope_digest()` together in Orgmetra's authoritative audit/outbox transaction before a future executable transport is enabled. An exact retry has the same key, request digest, and governance-scope digest. Reusing the key with a different request or governance digest is a conflict and must fail closed. This package exposes comparison helpers but does not invent a second persistence store. ## Test From 0014b68ba096562b1aab446bcef14103768247fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:32:41 -0700 Subject: [PATCH 29/32] test(tepp-adapter): require shared config quality triggers --- .../tests/test_quality_workflow_trigger.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/tepp-adapter/tests/test_quality_workflow_trigger.py diff --git a/packages/tepp-adapter/tests/test_quality_workflow_trigger.py b/packages/tepp-adapter/tests/test_quality_workflow_trigger.py new file mode 100644 index 000000000..93c39cd2d --- /dev/null +++ b/packages/tepp-adapter/tests/test_quality_workflow_trigger.py @@ -0,0 +1,27 @@ +"""Regression tests for the TEPP-adapter quality-gate trigger surface.""" + +from pathlib import Path + + +_WORKFLOW_PATH = Path(".github/workflows/tepp-adapter-quality.yml") +_SHARED_TEST_CONFIGURATION = ( + ".gitignore", + ".python-version", + "conftest.py", + "packages/conftest.py", + "pyproject.toml", + "pytest.ini", + "setup.cfg", + "tox.ini", +) + + +def test_quality_workflow_retriggers_on_shared_test_configuration() -> None: + """Require every shared test/runtime configuration input to retrigger this gate.""" + workflow = _WORKFLOW_PATH.read_text(encoding="utf-8") + + for path in _SHARED_TEST_CONFIGURATION: + assert f'- "{path}"' in workflow, ( + f"{path} can change package test or clean-checkout behavior and must retrigger " + "TEPP Adapter Quality" + ) From 4531959cb9c71db637dfea01375c2c698a86b2ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:34:03 -0700 Subject: [PATCH 30/32] fix(tepp-adapter): retrigger quality on shared config --- .github/workflows/tepp-adapter-quality.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/tepp-adapter-quality.yml b/.github/workflows/tepp-adapter-quality.yml index c401d6c54..e4bad22f0 100644 --- a/.github/workflows/tepp-adapter-quality.yml +++ b/.github/workflows/tepp-adapter-quality.yml @@ -13,6 +13,14 @@ on: - "docs/traceability/tepp-analysis-adapter.md" - ".github/requirements/foundation-test.txt" - ".github/workflows/tepp-adapter-quality.yml" + - ".gitignore" + - ".python-version" + - "conftest.py" + - "packages/conftest.py" + - "pyproject.toml" + - "pytest.ini" + - "setup.cfg" + - "tox.ini" workflow_dispatch: permissions: From 1f24fb4279aea7593ead7e2db7adf9ef14e3110a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:37:04 -0700 Subject: [PATCH 31/32] docs(tepp-adapter): record shared-config gate integrity --- packages/tepp-adapter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tepp-adapter/CHANGELOG.md b/packages/tepp-adapter/CHANGELOG.md index e00c68ed8..da1d31642 100644 --- a/packages/tepp-adapter/CHANGELOG.md +++ b/packages/tepp-adapter/CHANGELOG.md @@ -8,3 +8,4 @@ - Align `tenant_record_id` with protected Orgmetra's canonical non-sentinel operational UUID contract, including UUIDv7 interoperability, while keeping packet-owned validation-study and actor references UUIDv4-constrained. - Detach timezone-aware `knowledge_cutoff` and `generated_at` values to exact UTC datetimes before canonical request and governance evidence, rejecting failing, malformed, or overflowing timezone providers. - Keep transport disabled until a compatible executable TEPP service contract is published and re-resolved by the host; returned analytical/LLM evidence remains human-scientific-review-only. +- Make `TEPP Adapter Quality` retrigger on shared repository Python/test/clean-checkout configuration, with an executable regression preventing stale adapter-quality evidence after shared tooling changes. From f4a9bf2ad07edb22846c136a92c6dad9b152c8ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:37:13 -0700 Subject: [PATCH 32/32] docs(tepp-adapter): trace shared-config quality evidence --- docs/traceability/tepp-analysis-adapter.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/tepp-analysis-adapter.md b/docs/traceability/tepp-analysis-adapter.md index 640e7ce40..c0e29c3f5 100644 --- a/docs/traceability/tepp-analysis-adapter.md +++ b/docs/traceability/tepp-analysis-adapter.md @@ -12,6 +12,7 @@ | High-impact human review | fixed `human_scientific_review_only`, `untrusted_draft_evidence`, governed `next_action` | authority-expansion regressions | implemented_on_active_pr | | Foreign runtime maturity is not overstated | `transport_state=requires_published_tepp_service_contract` | fixed-state regression and package/ADR documentation | implemented_on_active_pr | | Exact owned production coverage | package pytest-cov gate | 100% statement + branch coverage | implemented_on_active_pr | +| Keep adapter-quality evidence current when shared repository test/runtime configuration changes | package quality workflow explicitly retriggers on shared Python/test/clean-checkout configuration inputs | `tests/test_quality_workflow_trigger.py`; supplemental to central required workflows | implemented_on_active_pr | ## Foreign evidence snapshot