From 7d483541f0aaeabde810473161cf718cb50ec15e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:17:58 -0700 Subject: [PATCH 001/241] test(authz): reject runtime type-confusion inputs --- .../test_authorization_runtime_integrity.py | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py diff --git a/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py new file mode 100644 index 000000000..c94e378d6 --- /dev/null +++ b/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py @@ -0,0 +1,121 @@ +"""Runtime-type integrity regressions for purpose-bound authorization.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter.authorization import ( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000501") + + +class _ForgedUUID(UUID): + """Attempt to render a tenant identity different from its underlying UUID.""" + + def __str__(self) -> str: + """Return caller-controlled identity text.""" + return "10000000-0000-7000-8000-ffffffffffff" + + +class _UnvalidatedPolicy(PurposeBoundAccessPolicy): + """Attempt to bypass immutable policy validation through subclass dispatch.""" + + def __post_init__(self) -> None: + """Intentionally skip the governed base validation.""" + + +class _UnvalidatedRequest(PurposeBoundAccessRequest): + """Attempt to bypass immutable request validation through subclass dispatch.""" + + def __post_init__(self) -> None: + """Intentionally skip the governed base validation.""" + + +def _policy(**overrides: object) -> PurposeBoundAccessPolicy: + """Build one exact governed People PII access policy.""" + values: dict[str, object] = { + "tenant_record_id": TENANT, + "policy_version_code": "people_pii_v1", + "resource_kind": "person_record", + "purpose_code": "hr_operations", + "operation_code": "read_person_pii", + "required_scope_code": "orgmetra.people.read", + "permitted_fields": frozenset({"legal_name", "work_email"}), + } + values.update(overrides) + return PurposeBoundAccessPolicy(**values) # type: ignore[arg-type] + + +def _request(**overrides: object) -> PurposeBoundAccessRequest: + """Build one exact governed People PII access request.""" + values: dict[str, object] = { + "tenant_record_id": TENANT, + "actor_tenant_record_id": TENANT, + "resource_tenant_record_id": TENANT, + "actor_reference": "keyverse_subject:sub_jordan_hale", + "resource_reference": "person_record:per_01J5EXACTTARGET", + "purpose_code": "hr_operations", + "operation_code": "read_person_pii", + "resource_kind": "person_record", + "requested_fields": frozenset({"work_email"}), + "granted_scope_codes": frozenset({"orgmetra.people.read"}), + } + values.update(overrides) + return PurposeBoundAccessRequest(**values) # type: ignore[arg-type] + + +@pytest.mark.parametrize( + "field_name", + ["tenant_record_id", "actor_tenant_record_id", "resource_tenant_record_id"], +) +def test_access_request_rejects_uuid_subclasses(field_name: str) -> None: + """Tenant isolation cannot depend on a UUID object with caller-controlled rendering.""" + forged = _ForgedUUID("10000000-0000-7000-8000-000000000501") + with pytest.raises(ValueError, match=f"{field_name} must be a UUID"): + _request(**{field_name: forged}) + + +def test_access_policy_rejects_uuid_subclasses() -> None: + """Persisted policy identity must use the exact built-in UUID contract.""" + forged = _ForgedUUID("10000000-0000-7000-8000-000000000501") + with pytest.raises(ValueError, match="tenant_record_id must be a UUID"): + _policy(tenant_record_id=forged) + + +def test_evaluator_rejects_policy_subclass_that_skipped_validation() -> None: + """A subclass cannot widen immutable policy attributes by skipping post-init checks.""" + forged = _UnvalidatedPolicy( + tenant_record_id=TENANT, + policy_version_code="people_pii_v1", + resource_kind="person_record", + purpose_code="hr_operations", + operation_code="read_person_pii", + required_scope_code="orgmetra.people.read", + permitted_fields={"work_email"}, # type: ignore[arg-type] + ) + with pytest.raises(TypeError, match="policy must be a PurposeBoundAccessPolicy"): + evaluate_purpose_bound_access(request=_request(), policy=forged) + + +def test_evaluator_rejects_request_subclass_that_skipped_validation() -> None: + """A subclass cannot present mutable token scopes as validated authorization input.""" + forged = _UnvalidatedRequest( + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference="keyverse_subject:sub_jordan_hale", + resource_reference="person_record:per_01J5EXACTTARGET", + purpose_code="hr_operations", + operation_code="read_person_pii", + resource_kind="person_record", + requested_fields=frozenset({"work_email"}), + granted_scope_codes={"orgmetra.people.read"}, # type: ignore[arg-type] + ) + with pytest.raises(TypeError, match="request must be a PurposeBoundAccessRequest"): + evaluate_purpose_bound_access(request=forged, policy=_policy()) From c8cb0b00cc7ce510aaf4f5f655624f85215c376f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:18:26 -0700 Subject: [PATCH 002/241] fix(authz): protect purpose-bound runtime types --- .../src/orgmetra_keyverse_adapter/authorization.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index b1a5f92c2..4c9cdd45c 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -42,8 +42,8 @@ def _validate_uuid(field_name: str, value: object) -> None: - """Require a real UUID and reject protocol-reserved Nil/Max sentinels.""" - if not isinstance(value, UUID): + """Require an exact UUID and reject protocol-reserved Nil/Max sentinels.""" + if type(value) is not UUID: raise ValueError(f"{field_name} must be a UUID.") if value.int in (0, _MAX_UUID_INT): raise ValueError(f"{field_name} must not use a reserved UUID sentinel.") @@ -247,6 +247,10 @@ def evaluate_purpose_bound_access( purpose header is insufficient when the operation scope or requested field set is not explicitly authorized. """ + if type(request) is not PurposeBoundAccessRequest: + raise TypeError("request must be a PurposeBoundAccessRequest") + if type(policy) is not PurposeBoundAccessPolicy: + raise TypeError("policy must be a PurposeBoundAccessPolicy") if ( request.tenant_record_id != policy.tenant_record_id or request.actor_tenant_record_id != policy.tenant_record_id From 3d3c81c101f913fe114cc7d5da3aaec229feb791 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 22:06:24 -0700 Subject: [PATCH 003/241] test(authz): reject hostile text and set subclasses --- .../test_authorization_runtime_integrity.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py index c94e378d6..8c2dacdd4 100644 --- a/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_runtime_integrity.py @@ -23,6 +23,26 @@ def __str__(self) -> str: return "10000000-0000-7000-8000-ffffffffffff" +class _ForgedText(str): + """Attempt to carry caller-controlled runtime behavior through text validation.""" + + +class _ForgedFieldSet(frozenset[str]): + """Attempt to bypass field containment with a validation-passing set subclass.""" + + def issubset(self, other: object) -> bool: + """Claim every requested field set is permitted.""" + return True + + +class _ForgedScopeSet(frozenset[str]): + """Attempt to bypass required-scope membership with a set subclass.""" + + def __contains__(self, item: object) -> bool: + """Claim every required scope is present.""" + return True + + class _UnvalidatedPolicy(PurposeBoundAccessPolicy): """Attempt to bypass immutable policy validation through subclass dispatch.""" @@ -88,6 +108,73 @@ def test_access_policy_rejects_uuid_subclasses() -> None: _policy(tenant_record_id=forged) +@pytest.mark.parametrize( + ("field_name", "forged_value"), + [ + ("policy_version_code", _ForgedText("people_pii_v1")), + ("resource_kind", _ForgedText("person_record")), + ("purpose_code", _ForgedText("hr_operations")), + ("operation_code", _ForgedText("read_person_pii")), + ("required_scope_code", _ForgedText("orgmetra.people.read")), + ], +) +def test_access_policy_rejects_string_subclasses(field_name: str, forged_value: str) -> None: + """Policy semantics cannot depend on caller-defined string runtime behavior.""" + with pytest.raises(ValueError): + _policy(**{field_name: forged_value}) + + +@pytest.mark.parametrize( + ("field_name", "forged_value"), + [ + ("actor_reference", _ForgedText("keyverse_subject:sub_jordan_hale")), + ("resource_reference", _ForgedText("person_record:per_01J5EXACTTARGET")), + ("purpose_code", _ForgedText("hr_operations")), + ("operation_code", _ForgedText("read_person_pii")), + ("resource_kind", _ForgedText("person_record")), + ], +) +def test_access_request_rejects_string_subclasses(field_name: str, forged_value: str) -> None: + """Request authorization cannot depend on caller-defined string runtime behavior.""" + with pytest.raises(ValueError): + _request(**{field_name: forged_value}) + + +def test_access_policy_rejects_frozenset_subclass_for_permitted_fields() -> None: + """A field-set subclass cannot control later policy containment semantics.""" + forged = _ForgedFieldSet({"legal_name", "work_email"}) + with pytest.raises(ValueError, match="permitted_fields must be a frozenset"): + _policy(permitted_fields=forged) + + +def test_access_request_rejects_frozenset_subclass_that_can_widen_fields() -> None: + """A requested-field set cannot override ``issubset`` and authorize a forbidden field.""" + forged = _ForgedFieldSet({"compensation_amount"}) + with pytest.raises(ValueError, match="requested_fields must be a frozenset"): + _request(requested_fields=forged) + + +def test_access_request_rejects_frozenset_subclass_that_can_forge_scope_membership() -> None: + """A scope set cannot override membership and fabricate the required operation scope.""" + forged = _ForgedScopeSet({"orgmetra.people.other"}) + with pytest.raises(ValueError, match="granted_scope_codes must be a frozenset"): + _request(granted_scope_codes=forged) + + +def test_access_policy_rejects_string_subclass_inside_field_set() -> None: + """Field identifiers themselves must be exact immutable built-in strings.""" + forged = frozenset({_ForgedText("work_email")}) + with pytest.raises(ValueError, match="permitted_fields must contain only"): + _policy(permitted_fields=forged) + + +def test_access_request_rejects_string_subclass_inside_scope_set() -> None: + """Scope identifiers themselves must be exact immutable built-in strings.""" + forged = frozenset({_ForgedText("orgmetra.people.read")}) + with pytest.raises(ValueError, match="granted_scope_codes must contain only"): + _request(granted_scope_codes=forged) + + def test_evaluator_rejects_policy_subclass_that_skipped_validation() -> None: """A subclass cannot widen immutable policy attributes by skipping post-init checks.""" forged = _UnvalidatedPolicy( From 7bb7bda502b85a156615929ccd3ec6ae2a327ec1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 22:08:10 -0700 Subject: [PATCH 004/241] fix(authz): require exact text and set runtime types --- .../authorization.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 4c9cdd45c..80897dc1e 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -50,20 +50,20 @@ def _validate_uuid(field_name: str, value: object) -> None: def _validate_code(field_name: str, value: object) -> None: - """Require an explicit lower snake_case policy or request code.""" - if not isinstance(value, str) or _CODE_PATTERN.fullmatch(value) is None: + """Require an exact built-in lower snake_case policy or request code.""" + if type(value) is not str or _CODE_PATTERN.fullmatch(value) is None: raise ValueError(f"{field_name} must be a lower snake_case code.") def _validate_resource_kind(value: object) -> None: - """Require a descriptive two-or-more-word lower snake_case resource kind.""" - if not isinstance(value, str) or _RESOURCE_KIND_PATTERN.fullmatch(value) is None: + """Require an exact built-in descriptive lower snake_case resource kind.""" + if type(value) is not str or _RESOURCE_KIND_PATTERN.fullmatch(value) is None: raise ValueError("resource_kind must contain two or more lower snake_case words.") def _validate_scope(field_name: str, value: object) -> None: - """Require one explicit Orgmetra operation scope rather than wildcards.""" - if not isinstance(value, str) or _SCOPE_PATTERN.fullmatch(value) is None: + """Require one exact built-in Orgmetra operation scope rather than wildcards.""" + if type(value) is not str or _SCOPE_PATTERN.fullmatch(value) is None: raise ValueError(f"{field_name} must be an explicit orgmetra.. scope.") @@ -73,36 +73,36 @@ def _validate_reference( *, expected_namespace: str | None = None, ) -> None: - """Require an opaque audit reference and optionally bind it to one resource kind.""" - if not isinstance(value, str) or _REFERENCE_PATTERN.fullmatch(value) is None: + """Require an exact built-in opaque reference and optionally bind its namespace.""" + if type(value) is not str or _REFERENCE_PATTERN.fullmatch(value) is None: raise ValueError(f"{field_name} must be a namespaced opaque reference.") if expected_namespace is not None and value.partition(":")[0] != expected_namespace: raise ValueError(f"{field_name} namespace must match resource_kind.") def _validate_version(value: object) -> None: - """Require an immutable, whitespace-free policy version token.""" - if not isinstance(value, str) or _VERSION_PATTERN.fullmatch(value) is None: + """Require an exact built-in immutable, whitespace-free policy version token.""" + if type(value) is not str or _VERSION_PATTERN.fullmatch(value) is None: raise ValueError("policy_version_code must be a whitespace-free version token.") def _validate_field_set(field_name: str, values: object) -> None: - """Require an immutable, non-empty set of explicit lower snake_case fields.""" - if not isinstance(values, frozenset): + """Require an exact immutable set of exact built-in lower snake_case fields.""" + if type(values) is not frozenset: raise ValueError(f"{field_name} must be a frozenset.") if not values: raise ValueError(f"{field_name} must not be empty.") - if any(not isinstance(value, str) or _CODE_PATTERN.fullmatch(value) is None for value in values): + if any(type(value) is not str or _CODE_PATTERN.fullmatch(value) is None for value in values): raise ValueError(f"{field_name} must contain only explicit lower snake_case field names.") def _validate_scope_set(values: object) -> None: - """Require immutable, non-empty, explicit token scopes from the authenticated principal.""" - if not isinstance(values, frozenset): + """Require an exact immutable set of exact built-in authenticated token scopes.""" + if type(values) is not frozenset: raise ValueError("granted_scope_codes must be a frozenset.") if not values: raise ValueError("granted_scope_codes must not be empty.") - if any(not isinstance(value, str) or _SCOPE_PATTERN.fullmatch(value) is None for value in values): + if any(type(value) is not str or _SCOPE_PATTERN.fullmatch(value) is None for value in values): raise ValueError("granted_scope_codes must contain only explicit Orgmetra scopes.") From c8b7f30097a30ab95956d4a73f0a38848cfe9ca1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:18:44 +0900 Subject: [PATCH 005/241] docs(authz): record runtime integrity hardening --- CHANGELOG.md | 1 + manifest.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f4752d7..7d11acc37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ All notable changes to Orgmetra will be documented in this file. - Predictive-validity cases fail closed when selection evidence, Job scope, study criterion, converted worker, or system-recorded visibility does not match; the normalized case relation is tenant-qualified, append-only, TRUNCATE-protected, and forced through row-level security. - Purpose-bound PII authorization now fails closed across active tenant, authenticated actor tenant, resource tenant, resource kind, purpose, operation, operation-specific Keyverse scope, and requested-field subset; malformed/wildcard-like attributes, mutable field/scope collections, reserved UUID sentinels, and cross-tenant confused-deputy contexts are rejected before protected values are returned. Authorization requests and allow/deny evidence now also require and preserve one namespaced opaque target-resource reference, so immutable audit correlation identifies the exact HR record without copying its protected values. Authorization evidence otherwise contains governance metadata and field names only, with stable denial reasons and actionable next steps rather than PII. +- Active-PR authorization runtime-integrity hardening at `orgmetra_keyverse_adapter` additionally requires exact built-in UUID, string, and `frozenset` values plus exact policy/request classes before evaluation, so subclass-controlled equality, membership, and validation bypasses fail closed. This is active-PR refinement to ADR 0008 and is not yet protected-`develop` truth. - LLM output constrained to draft evidence. - No direct cross-service application-table access. - Service-owned database schemas and roles inside the initially shared physical PostgreSQL cluster. diff --git a/manifest.json b/manifest.json index 97f2bab14..e115c0a3a 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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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}]} From b568b40f09bc90a24b2aafc9675bcaf9f5196910 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:02:13 +0900 Subject: [PATCH 006/241] test(authz): reject malformed authorization decision evidence --- ...uthorization_decision_runtime_integrity.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py new file mode 100644 index 000000000..f5a84ef39 --- /dev/null +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -0,0 +1,123 @@ +"""Runtime-integrity regressions for downstream authorization-decision evidence.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter.authorization import AuthorizationDecision + +TENANT = UUID("10000000-0000-7000-8000-000000000501") + + +class _ForgedUUID(UUID): + """Carry caller-defined UUID behavior inside downstream authorization evidence.""" + + +class _ForgedText(str): + """Carry caller-defined text behavior inside downstream authorization evidence.""" + + +class _ForgedFieldSet(frozenset[str]): + """Carry caller-defined set behavior inside downstream authorization evidence.""" + + +def _decision(**overrides: object) -> AuthorizationDecision: + """Build one deterministic allow decision for runtime-integrity tests.""" + values: dict[str, object] = { + "allowed": True, + "tenant_record_id": TENANT, + "actor_reference": "keyverse_subject:operator-17", + "resource_reference": "assignment_record:0198a412800070008000000000000070", + "policy_version_code": "assignment-correction-v1", + "purpose_code": "workforce_admin", + "operation_code": "correct_record", + "resource_kind": "assignment_record", + "requested_fields": frozenset({"assignment_category_code"}), + "authorized_fields": frozenset({"assignment_category_code"}), + "reason_code": "access_permitted", + "next_action": "Continue with only the authorized fields.", + } + values.update(overrides) + return AuthorizationDecision(**values) # type: ignore[arg-type] + + +def test_decision_rejects_non_boolean_allowed_flag() -> None: + """Do not let truthy integers masquerade as an authorization verdict.""" + with pytest.raises(ValueError, match="allowed must be a boolean"): + _decision(allowed=1) + + +def test_decision_rejects_uuid_subclass() -> None: + """Tenant evidence must not retain caller-defined UUID runtime behavior.""" + forged = _ForgedUUID(str(TENANT)) + with pytest.raises(ValueError, match="tenant_record_id must be a UUID"): + _decision(tenant_record_id=forged) + + +@pytest.mark.parametrize( + ("field_name", "forged_value"), + [ + ("actor_reference", _ForgedText("keyverse_subject:operator-17")), + ( + "resource_reference", + _ForgedText("assignment_record:0198a412800070008000000000000070"), + ), + ("policy_version_code", _ForgedText("assignment-correction-v1")), + ("purpose_code", _ForgedText("workforce_admin")), + ("operation_code", _ForgedText("correct_record")), + ("resource_kind", _ForgedText("assignment_record")), + ("reason_code", _ForgedText("access_permitted")), + ("next_action", _ForgedText("Continue with only the authorized fields.")), + ], +) +def test_decision_rejects_string_subclasses(field_name: str, forged_value: str) -> None: + """Decision evidence cannot retain caller-defined text runtime behavior.""" + with pytest.raises(ValueError): + _decision(**{field_name: forged_value}) + + +@pytest.mark.parametrize("field_name", ["requested_fields", "authorized_fields"]) +def test_decision_rejects_frozenset_subclasses(field_name: str) -> None: + """Field evidence cannot override containment or equality after authorization.""" + forged = _ForgedFieldSet({"assignment_category_code"}) + with pytest.raises(ValueError, match=f"{field_name} must be a frozenset"): + _decision(**{field_name: forged}) + + +@pytest.mark.parametrize("field_name", ["requested_fields", "authorized_fields"]) +def test_decision_rejects_string_subclasses_inside_field_sets(field_name: str) -> None: + """Each authorized field identifier must be an exact built-in string.""" + forged = frozenset({_ForgedText("assignment_category_code")}) + with pytest.raises(ValueError, match=f"{field_name} must contain only"): + _decision(**{field_name: forged}) + + +def test_allow_decision_requires_exact_requested_authorized_field_equality() -> None: + """An allow verdict cannot silently authorize fewer or different fields than requested.""" + with pytest.raises(ValueError, match="allow decision must authorize exactly the requested fields"): + _decision(authorized_fields=frozenset({"legal_name"})) + + +def test_deny_decision_cannot_carry_authorized_fields() -> None: + """A deny verdict cannot retain a non-empty authorized field set.""" + with pytest.raises(ValueError, match="deny decision must not authorize fields"): + _decision( + allowed=False, + authorized_fields=frozenset({"assignment_category_code"}), + reason_code="field_not_allowed", + next_action="Request only fields allowed for this purpose.", + ) + + +def test_decision_rejects_resource_reference_namespace_mismatch() -> None: + """Downstream evidence must correlate its opaque target to the declared resource kind.""" + with pytest.raises(ValueError, match="resource_reference namespace must match resource_kind"): + _decision(resource_reference="employment_record:0198a412800070008000000000000070") + + +def test_decision_rejects_blank_next_action() -> None: + """Authorization evidence must preserve an actionable bounded recovery instruction.""" + with pytest.raises(ValueError, match="next_action must be a non-blank string"): + _decision(next_action=" ") From c35d434adfdee94fa838819bba51bd2449ce940d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:04:37 +0900 Subject: [PATCH 007/241] fix(authz): validate authorization decision runtime evidence --- .../authorization.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 80897dc1e..ceb1c587d 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -96,6 +96,14 @@ def _validate_field_set(field_name: str, values: object) -> None: raise ValueError(f"{field_name} must contain only explicit lower snake_case field names.") +def _validate_authorized_field_set(values: object) -> None: + """Require an exact immutable authorized-field set while allowing an empty deny result.""" + if type(values) is not frozenset: + raise ValueError("authorized_fields must be a frozenset.") + if any(type(value) is not str or _CODE_PATTERN.fullmatch(value) is None for value in values): + raise ValueError("authorized_fields must contain only explicit lower snake_case field names.") + + def _validate_scope_set(values: object) -> None: """Require an exact immutable set of exact built-in authenticated token scopes.""" if type(values) is not frozenset: @@ -178,7 +186,7 @@ def __post_init__(self) -> None: @dataclass(frozen=True, slots=True) class AuthorizationDecision: - """PII-minimized authorization evidence safe to bind into an audit event.""" + """PII-minimized, runtime-validated authorization evidence for downstream use.""" allowed: bool tenant_record_id: UUID @@ -193,6 +201,31 @@ class AuthorizationDecision: reason_code: str next_action: str + def __post_init__(self) -> None: + """Reject malformed or executable evidence before it reaches a persistence boundary.""" + if type(self.allowed) is not bool: + raise ValueError("allowed must be a boolean.") + _validate_uuid("tenant_record_id", self.tenant_record_id) + _validate_reference("actor_reference", self.actor_reference) + _validate_resource_kind(self.resource_kind) + _validate_reference( + "resource_reference", + self.resource_reference, + expected_namespace=self.resource_kind, + ) + _validate_version(self.policy_version_code) + _validate_code("purpose_code", self.purpose_code) + _validate_code("operation_code", self.operation_code) + _validate_field_set("requested_fields", self.requested_fields) + _validate_authorized_field_set(self.authorized_fields) + _validate_code("reason_code", self.reason_code) + if type(self.next_action) is not str or not self.next_action.strip() or len(self.next_action) > 500: + raise ValueError("next_action must be a non-blank string of at most 500 characters.") + if self.allowed and self.authorized_fields != self.requested_fields: + raise ValueError("allow decision must authorize exactly the requested fields.") + if not self.allowed and self.authorized_fields: + raise ValueError("deny decision must not authorize fields.") + class AuthorizationDeniedError(PermissionError): """A purpose-bound policy denied access and tells the caller how to recover safely.""" From 57e938e643c78af65d675feea6bf3da466636ad8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:29:43 +0900 Subject: [PATCH 008/241] test(authz): reject contradictory decision evidence --- ...uthorization_decision_runtime_integrity.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index f5a84ef39..2c2f7155e 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -111,6 +111,39 @@ def test_deny_decision_cannot_carry_authorized_fields() -> None: ) +def test_allow_decision_rejects_denial_reason() -> None: + """An allow verdict cannot carry a denial reason into downstream audit evidence.""" + with pytest.raises(ValueError, match="allow decision must use access_permitted reason"): + _decision(reason_code="field_not_allowed") + + +def test_deny_decision_rejects_success_reason() -> None: + """A deny verdict cannot masquerade as successful authorization in audit evidence.""" + with pytest.raises(ValueError, match="deny decision must use a governed denial reason"): + _decision( + allowed=False, + authorized_fields=frozenset(), + reason_code="access_permitted", + ) + + +def test_allow_decision_requires_canonical_next_action() -> None: + """Successful evidence keeps the one governed continuation instruction.""" + with pytest.raises(ValueError, match="next_action must match the governed authorization reason"): + _decision(next_action="Retry later.") + + +def test_deny_decision_requires_reason_bound_next_action() -> None: + """Denial evidence cannot pair a valid reason with unrelated recovery guidance.""" + with pytest.raises(ValueError, match="next_action must match the governed authorization reason"): + _decision( + allowed=False, + authorized_fields=frozenset(), + reason_code="field_not_allowed", + next_action="Resolve the policy for the requested resource kind before retrying.", + ) + + def test_decision_rejects_resource_reference_namespace_mismatch() -> None: """Downstream evidence must correlate its opaque target to the declared resource kind.""" with pytest.raises(ValueError, match="resource_reference namespace must match resource_kind"): From fc893948d5c15fb6c2fabd42742bf854296b4158 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:30:32 +0900 Subject: [PATCH 009/241] fix(authz): bind decision reasons to recovery evidence --- .../authorization.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index ceb1c587d..cb8ccbdc7 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -21,6 +21,7 @@ _REFERENCE_PATTERN = re.compile(r"^[a-z][a-z0-9_]*:[A-Za-z0-9][A-Za-z0-9._~-]*$") _VERSION_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]*$") +_ALLOW_NEXT_ACTION = "Continue with only the authorized fields." _DENIAL_NEXT_ACTION = { "tenant_scope_mismatch": ( "Re-resolve the actor, request context, resource, and policy in one tenant before retrying." @@ -202,7 +203,7 @@ class AuthorizationDecision: next_action: str def __post_init__(self) -> None: - """Reject malformed or executable evidence before it reaches a persistence boundary.""" + """Reject malformed, contradictory, or executable downstream authorization evidence.""" if type(self.allowed) is not bool: raise ValueError("allowed must be a boolean.") _validate_uuid("tenant_record_id", self.tenant_record_id) @@ -225,6 +226,16 @@ def __post_init__(self) -> None: raise ValueError("allow decision must authorize exactly the requested fields.") if not self.allowed and self.authorized_fields: raise ValueError("deny decision must not authorize fields.") + if self.allowed: + if self.reason_code != "access_permitted": + raise ValueError("allow decision must use access_permitted reason.") + expected_next_action = _ALLOW_NEXT_ACTION + else: + if self.reason_code not in _DENIAL_NEXT_ACTION: + raise ValueError("deny decision must use a governed denial reason.") + expected_next_action = _DENIAL_NEXT_ACTION[self.reason_code] + if self.next_action != expected_next_action: + raise ValueError("next_action must match the governed authorization reason.") class AuthorizationDeniedError(PermissionError): @@ -247,11 +258,7 @@ def _decision( ) -> AuthorizationDecision: """Build one immutable allow/deny record without copying protected values.""" authorized_fields = request.requested_fields if allowed else frozenset() - next_action = ( - "Continue with only the authorized fields." - if allowed - else _DENIAL_NEXT_ACTION[reason_code] - ) + next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] return AuthorizationDecision( allowed=allowed, tenant_record_id=request.tenant_record_id, From 1693abd56614336190dd7e634caa41d2f691bd53 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:34:35 +0900 Subject: [PATCH 010/241] test(authz): preserve non-authoritative recovery guidance --- ...uthorization_decision_runtime_integrity.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 2c2f7155e..9d7d75f95 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -127,21 +127,10 @@ def test_deny_decision_rejects_success_reason() -> None: ) -def test_allow_decision_requires_canonical_next_action() -> None: - """Successful evidence keeps the one governed continuation instruction.""" - with pytest.raises(ValueError, match="next_action must match the governed authorization reason"): - _decision(next_action="Retry later.") - - -def test_deny_decision_requires_reason_bound_next_action() -> None: - """Denial evidence cannot pair a valid reason with unrelated recovery guidance.""" - with pytest.raises(ValueError, match="next_action must match the governed authorization reason"): - _decision( - allowed=False, - authorized_fields=frozenset(), - reason_code="field_not_allowed", - next_action="Resolve the policy for the requested resource kind before retrying.", - ) +def test_decision_preserves_bounded_actionable_text_as_non_authoritative_guidance() -> None: + """Recovery guidance may vary without changing the governed verdict or reason code.""" + decision = _decision(next_action="Continue after logging the reviewed evidence.") + assert decision.next_action == "Continue after logging the reviewed evidence." def test_decision_rejects_resource_reference_namespace_mismatch() -> None: From e46314c94c982de619c57ef6132a72192a89e0d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:35:14 +0900 Subject: [PATCH 011/241] fix(authz): keep recovery guidance non-authoritative --- .../src/orgmetra_keyverse_adapter/authorization.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index cb8ccbdc7..122fa756b 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -226,16 +226,10 @@ def __post_init__(self) -> None: raise ValueError("allow decision must authorize exactly the requested fields.") if not self.allowed and self.authorized_fields: raise ValueError("deny decision must not authorize fields.") - if self.allowed: - if self.reason_code != "access_permitted": - raise ValueError("allow decision must use access_permitted reason.") - expected_next_action = _ALLOW_NEXT_ACTION - else: - if self.reason_code not in _DENIAL_NEXT_ACTION: - raise ValueError("deny decision must use a governed denial reason.") - expected_next_action = _DENIAL_NEXT_ACTION[self.reason_code] - if self.next_action != expected_next_action: - raise ValueError("next_action must match the governed authorization reason.") + if self.allowed and self.reason_code != "access_permitted": + raise ValueError("allow decision must use access_permitted reason.") + if not self.allowed and self.reason_code not in _DENIAL_NEXT_ACTION: + raise ValueError("deny decision must use a governed denial reason.") class AuthorizationDeniedError(PermissionError): From 64a6a567577a720fbfbba93c94ee51abfe36f4b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:36:11 +0900 Subject: [PATCH 012/241] test(authz): keep denial vocabulary extensible --- ...test_authorization_decision_runtime_integrity.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 9d7d75f95..ffe525479 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -119,7 +119,7 @@ def test_allow_decision_rejects_denial_reason() -> None: def test_deny_decision_rejects_success_reason() -> None: """A deny verdict cannot masquerade as successful authorization in audit evidence.""" - with pytest.raises(ValueError, match="deny decision must use a governed denial reason"): + with pytest.raises(ValueError, match="deny decision must not use access_permitted reason"): _decision( allowed=False, authorized_fields=frozenset(), @@ -127,6 +127,17 @@ def test_deny_decision_rejects_success_reason() -> None: ) +def test_decision_accepts_explicit_denial_reason_outside_evaluator_vocabulary() -> None: + """The public evidence type preserves bounded downstream denial codes without widening allow semantics.""" + decision = _decision( + allowed=False, + authorized_fields=frozenset(), + reason_code="access_denied", + next_action="stop", + ) + assert decision.reason_code == "access_denied" + + def test_decision_preserves_bounded_actionable_text_as_non_authoritative_guidance() -> None: """Recovery guidance may vary without changing the governed verdict or reason code.""" decision = _decision(next_action="Continue after logging the reviewed evidence.") From edbd07337162f85ac7ab6b74e570b49e53a6d296 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:37:09 +0900 Subject: [PATCH 013/241] fix(authz): bind verdict polarity without closing denial codes --- .../src/orgmetra_keyverse_adapter/authorization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 122fa756b..f92723f0c 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -228,8 +228,8 @@ def __post_init__(self) -> None: raise ValueError("deny decision must not authorize fields.") if self.allowed and self.reason_code != "access_permitted": raise ValueError("allow decision must use access_permitted reason.") - if not self.allowed and self.reason_code not in _DENIAL_NEXT_ACTION: - raise ValueError("deny decision must use a governed denial reason.") + if not self.allowed and self.reason_code == "access_permitted": + raise ValueError("deny decision must not use access_permitted reason.") class AuthorizationDeniedError(PermissionError): From bed68090b8648bb4d4d1dea2f4ad7f3aab876db0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:41:34 +0900 Subject: [PATCH 014/241] test(authz): reject decision subclass validator bypass --- .../test_authorization_decision_runtime_integrity.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index ffe525479..c2483273e 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -43,6 +43,15 @@ def _decision(**overrides: object) -> AuthorizationDecision: return AuthorizationDecision(**values) # type: ignore[arg-type] +def test_decision_cannot_be_subclassed_to_bypass_post_init_validation() -> None: + """Caller-defined decision classes must not bypass the sealed evidence validator.""" + with pytest.raises(TypeError, match="AuthorizationDecision must not be subclassed"): + + class _ForgedDecision(AuthorizationDecision): + def __post_init__(self) -> None: + return None + + def test_decision_rejects_non_boolean_allowed_flag() -> None: """Do not let truthy integers masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): From 7239eac723d74d2d8eb0bfe6d68cdf4ce77f1879 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:43:23 +0900 Subject: [PATCH 015/241] fix(authz): seal authorization decision validation --- .../src/orgmetra_keyverse_adapter/authorization.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index f92723f0c..67ddb9369 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -202,6 +202,10 @@ class AuthorizationDecision: reason_code: str next_action: str + def __init_subclass__(cls, **kwargs: object) -> None: + """Seal the evidence type so subclasses cannot override validation hooks.""" + raise TypeError("AuthorizationDecision must not be subclassed") + def __post_init__(self) -> None: """Reject malformed, contradictory, or executable downstream authorization evidence.""" if type(self.allowed) is not bool: From 1ccada31325ce119e201a79890266a7430ff997c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:02:34 +0900 Subject: [PATCH 016/241] test(authz): prove decision evidence resists low-level mutation --- .../test_authorization_decision_runtime_integrity.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index c2483273e..a937cc45c 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -52,6 +52,15 @@ def __post_init__(self) -> None: return None +def test_decision_resists_object_setattr_after_valid_construction() -> None: + """Low-level attribute writes must not replace already-validated authorization evidence.""" + decision = _decision() + with pytest.raises((AttributeError, TypeError)): + object.__setattr__(decision, "allowed", False) + assert decision.allowed is True + assert decision.reason_code == "access_permitted" + + def test_decision_rejects_non_boolean_allowed_flag() -> None: """Do not let truthy integers masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): From 0d2268956ac230cdfa6d59b2c07659097bc5f740 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:05:11 +0900 Subject: [PATCH 017/241] test(authz): prove issued decision detaches caller UUID state --- .../test_authorization_decision_runtime_integrity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index a937cc45c..4d65c401d 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -61,6 +61,14 @@ def test_decision_resists_object_setattr_after_valid_construction() -> None: assert decision.reason_code == "access_permitted" +def test_decision_detaches_caller_owned_exact_uuid() -> None: + """Later low-level mutation of an accepted UUID object must not rewrite issued evidence.""" + tenant = UUID(str(TENANT)) + decision = _decision(tenant_record_id=tenant) + object.__setattr__(tenant, "int", 0) + assert decision.tenant_record_id == TENANT + + def test_decision_rejects_non_boolean_allowed_flag() -> None: """Do not let truthy integers masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): From 5a2bde86bd0e61a8c2c1b4547b9ac69416a61f36 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:06:06 +0900 Subject: [PATCH 018/241] test(authz): reject corrupted exact UUID decision evidence --- .../test_authorization_decision_runtime_integrity.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 4d65c401d..6803d882d 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -69,6 +69,15 @@ def test_decision_detaches_caller_owned_exact_uuid() -> None: assert decision.tenant_record_id == TENANT +@pytest.mark.parametrize("forged_int", [-1, 1 << 128, "invalid"]) +def test_decision_rejects_low_level_corrupted_exact_uuid(forged_int: object) -> None: + """An exact UUID object with corrupted internal integer state is not valid tenant evidence.""" + tenant = UUID(str(TENANT)) + object.__setattr__(tenant, "int", forged_int) + with pytest.raises(ValueError, match="tenant_record_id must contain a valid UUID integer"): + _decision(tenant_record_id=tenant) + + def test_decision_rejects_non_boolean_allowed_flag() -> None: """Do not let truthy integers masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): From a10468f1699a29f27b0cdfaf48c240046881d5b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:07:36 +0900 Subject: [PATCH 019/241] fix(authz): make issued decisions structurally immutable --- .../authorization.py | 242 +++++++++++++++--- 1 file changed, 213 insertions(+), 29 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 67ddb9369..302771934 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -12,6 +12,7 @@ from dataclasses import dataclass import re +import weakref from uuid import UUID _MAX_UUID_INT = (1 << 128) - 1 @@ -185,9 +186,83 @@ def __post_init__(self) -> None: _validate_scope_set(self.granted_scope_codes) -@dataclass(frozen=True, slots=True) +_DECISION_SNAPSHOT_REGISTRY: dict[ + int, + tuple[weakref.ReferenceType[object], tuple[object, ...]], +] = {} + + +def _validated_decision_snapshot( + *, + allowed: object, + tenant_record_id: object, + actor_reference: object, + resource_reference: object, + policy_version_code: object, + purpose_code: object, + operation_code: object, + resource_kind: object, + requested_fields: object, + authorized_fields: object, + reason_code: object, + next_action: object, +) -> tuple[object, ...]: + """Validate decision evidence and detach caller-owned mutable runtime objects.""" + if type(allowed) is not bool: + raise ValueError("allowed must be a boolean.") + _validate_uuid("tenant_record_id", tenant_record_id) + tenant_int = tenant_record_id.int + if type(tenant_int) is not int or not 0 <= tenant_int <= _MAX_UUID_INT: + raise ValueError("tenant_record_id must contain a valid UUID integer.") + _validate_reference("actor_reference", actor_reference) + _validate_resource_kind(resource_kind) + _validate_reference( + "resource_reference", + resource_reference, + expected_namespace=resource_kind, + ) + _validate_version(policy_version_code) + _validate_code("purpose_code", purpose_code) + _validate_code("operation_code", operation_code) + _validate_field_set("requested_fields", requested_fields) + _validate_authorized_field_set(authorized_fields) + _validate_code("reason_code", reason_code) + if type(next_action) is not str or not next_action.strip() or len(next_action) > 500: + raise ValueError("next_action must be a non-blank string of at most 500 characters.") + if allowed and authorized_fields != requested_fields: + raise ValueError("allow decision must authorize exactly the requested fields.") + if not allowed and authorized_fields: + raise ValueError("deny decision must not authorize fields.") + if allowed and reason_code != "access_permitted": + raise ValueError("allow decision must use access_permitted reason.") + if not allowed and reason_code == "access_permitted": + raise ValueError("deny decision must not use access_permitted reason.") + return ( + allowed, + tenant_int, + actor_reference, + resource_reference, + policy_version_code, + purpose_code, + operation_code, + resource_kind, + requested_fields, + authorized_fields, + reason_code, + next_action, + ) + + class AuthorizationDecision: - """PII-minimized, runtime-validated authorization evidence for downstream use.""" + """PII-minimized authorization evidence with detached, structurally immutable state. + + Validated values live in a module-owned snapshot rather than writable instance + slots. In particular the tenant UUID is stored as its integer value and rebuilt + on access, so a later low-level mutation of the caller's UUID cannot rewrite + already-issued authorization evidence. + """ + + __slots__ = ("__weakref__",) allowed: bool tenant_record_id: UUID @@ -202,38 +277,147 @@ class AuthorizationDecision: reason_code: str next_action: str + def __init__( + self, + *, + allowed: bool, + tenant_record_id: UUID, + actor_reference: str, + resource_reference: str, + policy_version_code: str, + purpose_code: str, + operation_code: str, + resource_kind: str, + requested_fields: frozenset[str], + authorized_fields: frozenset[str], + reason_code: str, + next_action: str, + ) -> None: + """Validate once and register a detached immutable evidence snapshot.""" + key = id(self) + if key in _DECISION_SNAPSHOT_REGISTRY: + raise TypeError("AuthorizationDecision is already initialized") + snapshot = _validated_decision_snapshot( + allowed=allowed, + tenant_record_id=tenant_record_id, + actor_reference=actor_reference, + resource_reference=resource_reference, + policy_version_code=policy_version_code, + purpose_code=purpose_code, + operation_code=operation_code, + resource_kind=resource_kind, + requested_fields=requested_fields, + authorized_fields=authorized_fields, + reason_code=reason_code, + next_action=next_action, + ) + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: _DECISION_SNAPSHOT_REGISTRY.pop( + evidence_key, + None, + ), + ) + _DECISION_SNAPSHOT_REGISTRY[key] = (reference, snapshot) + def __init_subclass__(cls, **kwargs: object) -> None: """Seal the evidence type so subclasses cannot override validation hooks.""" raise TypeError("AuthorizationDecision must not be subclassed") - def __post_init__(self) -> None: - """Reject malformed, contradictory, or executable downstream authorization evidence.""" - if type(self.allowed) is not bool: - raise ValueError("allowed must be a boolean.") - _validate_uuid("tenant_record_id", self.tenant_record_id) - _validate_reference("actor_reference", self.actor_reference) - _validate_resource_kind(self.resource_kind) - _validate_reference( - "resource_reference", - self.resource_reference, - expected_namespace=self.resource_kind, + def _snapshot(self) -> tuple[object, ...]: + """Return the issued snapshot or fail closed for low-level forged instances.""" + entry = _DECISION_SNAPSHOT_REGISTRY.get(id(self)) + if entry is None or entry[0]() is not self: + raise ValueError("AuthorizationDecision was not issued by the validated constructor") + return entry[1] + + @property + def allowed(self) -> bool: + """Return the immutable allow/deny verdict.""" + return self._snapshot()[0] # type: ignore[return-value] + + @property + def tenant_record_id(self) -> UUID: + """Return a detached UUID copy of the authorized tenant identity.""" + return UUID(int=self._snapshot()[1]) # type: ignore[arg-type] + + @property + def actor_reference(self) -> str: + """Return the PII-minimized actor reference.""" + return self._snapshot()[2] # type: ignore[return-value] + + @property + def resource_reference(self) -> str: + """Return the opaque target reference bound to the decision.""" + return self._snapshot()[3] # type: ignore[return-value] + + @property + def policy_version_code(self) -> str: + """Return the immutable policy version used for evaluation.""" + return self._snapshot()[4] # type: ignore[return-value] + + @property + def purpose_code(self) -> str: + """Return the purpose bound to the decision.""" + return self._snapshot()[5] # type: ignore[return-value] + + @property + def operation_code(self) -> str: + """Return the operation bound to the decision.""" + return self._snapshot()[6] # type: ignore[return-value] + + @property + def resource_kind(self) -> str: + """Return the governed resource kind.""" + return self._snapshot()[7] # type: ignore[return-value] + + @property + def requested_fields(self) -> frozenset[str]: + """Return the exact immutable requested-field set.""" + return self._snapshot()[8] # type: ignore[return-value] + + @property + def authorized_fields(self) -> frozenset[str]: + """Return the exact immutable authorized-field set.""" + return self._snapshot()[9] # type: ignore[return-value] + + @property + def reason_code(self) -> str: + """Return the governed allow/deny reason code.""" + return self._snapshot()[10] # type: ignore[return-value] + + @property + def next_action(self) -> str: + """Return bounded non-authoritative recovery guidance.""" + return self._snapshot()[11] # type: ignore[return-value] + + def __repr__(self) -> str: + """Preserve a deterministic value-style representation for diagnostics.""" + return ( + "AuthorizationDecision(" + f"allowed={self.allowed!r}, " + f"tenant_record_id={self.tenant_record_id!r}, " + f"actor_reference={self.actor_reference!r}, " + f"resource_reference={self.resource_reference!r}, " + f"policy_version_code={self.policy_version_code!r}, " + f"purpose_code={self.purpose_code!r}, " + f"operation_code={self.operation_code!r}, " + f"resource_kind={self.resource_kind!r}, " + f"requested_fields={self.requested_fields!r}, " + f"authorized_fields={self.authorized_fields!r}, " + f"reason_code={self.reason_code!r}, " + f"next_action={self.next_action!r})" ) - _validate_version(self.policy_version_code) - _validate_code("purpose_code", self.purpose_code) - _validate_code("operation_code", self.operation_code) - _validate_field_set("requested_fields", self.requested_fields) - _validate_authorized_field_set(self.authorized_fields) - _validate_code("reason_code", self.reason_code) - if type(self.next_action) is not str or not self.next_action.strip() or len(self.next_action) > 500: - raise ValueError("next_action must be a non-blank string of at most 500 characters.") - if self.allowed and self.authorized_fields != self.requested_fields: - raise ValueError("allow decision must authorize exactly the requested fields.") - if not self.allowed and self.authorized_fields: - raise ValueError("deny decision must not authorize fields.") - if self.allowed and self.reason_code != "access_permitted": - raise ValueError("allow decision must use access_permitted reason.") - if not self.allowed and self.reason_code == "access_permitted": - raise ValueError("deny decision must not use access_permitted reason.") + + def __eq__(self, other: object) -> bool: + """Retain dataclass-like value equality only for exact issued decisions.""" + if type(other) is not AuthorizationDecision: + return False + return self._snapshot() == other._snapshot() + + def __hash__(self) -> int: + """Retain stable value hashing over detached immutable evidence.""" + return hash(self._snapshot()) class AuthorizationDeniedError(PermissionError): From 23e2c6eb0c233ffa79aefc8a8ec823322dd269a3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:08:56 +0900 Subject: [PATCH 020/241] test(authz): cover immutable decision issuance lifecycle --- ...uthorization_decision_runtime_integrity.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 6803d882d..c4fcfd6d5 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -2,6 +2,8 @@ from __future__ import annotations +import gc +import weakref from uuid import UUID import pytest @@ -78,6 +80,54 @@ def test_decision_rejects_low_level_corrupted_exact_uuid(forged_int: object) -> _decision(tenant_record_id=tenant) +def test_decision_rejects_unissued_low_level_instance() -> None: + """Bypassing the public constructor must not yield readable authorization evidence.""" + forged = object.__new__(AuthorizationDecision) + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + _ = forged.allowed + + +def test_decision_cannot_be_reinitialized_with_new_evidence() -> None: + """The public initializer cannot replace a snapshot after the decision has been issued.""" + decision = _decision() + with pytest.raises(TypeError, match="already initialized"): + AuthorizationDecision.__init__( + decision, + allowed=False, + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference="assignment_record:0198a412800070008000000000000070", + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=frozenset({"assignment_category_code"}), + authorized_fields=frozenset(), + reason_code="field_not_allowed", + next_action="Request only fields allowed for this purpose.", + ) + + +def test_decision_preserves_value_semantics_and_deterministic_repr() -> None: + """Structural hardening must preserve equality, hashing, and diagnostic representation.""" + left = _decision() + right = _decision() + assert left == right + assert not (left == object()) + assert hash(left) == hash(right) + assert repr(left).startswith("AuthorizationDecision(allowed=True") + assert "assignment_category_code" in repr(left) + + +def test_decision_registry_does_not_retain_dead_evidence() -> None: + """Lifecycle bookkeeping must not keep authorization evidence alive after callers release it.""" + decision = _decision() + reference = weakref.ref(decision) + del decision + gc.collect() + assert reference() is None + + def test_decision_rejects_non_boolean_allowed_flag() -> None: """Do not let truthy integers masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): From 1a902114b1c455b64e43c81c48fae16efcfd1d06 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:04:17 +0900 Subject: [PATCH 021/241] test(authz): reproduce post-construction policy/request rewrite --- ...st_authorization_issued_input_integrity.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py diff --git a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py new file mode 100644 index 000000000..422cecaad --- /dev/null +++ b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py @@ -0,0 +1,78 @@ +"""Issued-input integrity regressions for purpose-bound authorization.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter.authorization import ( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000501") + + +def _policy() -> PurposeBoundAccessPolicy: + """Build one narrow policy whose creation-time field authority is auditable.""" + return PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="people_pii_v1", + resource_kind="person_record", + purpose_code="hr_operations", + operation_code="read_person_pii", + required_scope_code="orgmetra.people.read", + permitted_fields=frozenset({"work_email"}), + ) + + +def _request(*, field: str = "work_email") -> PurposeBoundAccessRequest: + """Build one request with an exact creation-time field and scope snapshot.""" + return PurposeBoundAccessRequest( + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference="keyverse_subject:sub_jordan_hale", + resource_reference="person_record:per_01J5EXACTTARGET", + purpose_code="hr_operations", + operation_code="read_person_pii", + resource_kind="person_record", + requested_fields=frozenset({field}), + granted_scope_codes=frozenset({"orgmetra.people.read"}), + ) + + +def test_evaluator_rejects_post_construction_policy_widening() -> None: + """Low-level mutation cannot widen a policy after its governed construction.""" + policy = _policy() + object.__setattr__( + policy, + "permitted_fields", + frozenset({"work_email", "compensation_amount"}), + ) + + with pytest.raises(ValueError, match="PurposeBoundAccessPolicy changed after validation"): + evaluate_purpose_bound_access( + request=_request(field="compensation_amount"), + policy=policy, + ) + + +def test_evaluator_rejects_post_construction_request_scope_rewrite() -> None: + """Low-level mutation cannot replace the authenticated scope snapshot after validation.""" + request = _request() + object.__setattr__(request, "granted_scope_codes", frozenset({"orgmetra.people.admin"})) + + with pytest.raises(ValueError, match="PurposeBoundAccessRequest changed after validation"): + evaluate_purpose_bound_access(request=request, policy=_policy()) + + +def test_evaluator_rejects_post_construction_request_field_rewrite() -> None: + """Low-level mutation cannot change which PII field the issued request asks to expose.""" + request = _request() + object.__setattr__(request, "requested_fields", frozenset({"compensation_amount"})) + + with pytest.raises(ValueError, match="PurposeBoundAccessRequest changed after validation"): + evaluate_purpose_bound_access(request=request, policy=_policy()) From 250c85253e60b54e05d1d6dec45972ab175db861 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:06:15 +0900 Subject: [PATCH 022/241] fix(authz): bind evaluation to issued policy and request snapshots --- .../authorization.py | 270 ++++++++++++++---- 1 file changed, 208 insertions(+), 62 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 302771934..d5187aa47 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -12,6 +12,7 @@ from dataclasses import dataclass import re +from typing import NamedTuple import weakref from uuid import UUID @@ -51,6 +52,15 @@ def _validate_uuid(field_name: str, value: object) -> None: raise ValueError(f"{field_name} must not use a reserved UUID sentinel.") +def _validated_uuid_int(field_name: str, value: object) -> int: + """Validate and detach an exact UUID into one immutable integer snapshot.""" + _validate_uuid(field_name, value) + value_int = value.int + if type(value_int) is not int or not 0 <= value_int <= _MAX_UUID_INT: + raise ValueError(f"{field_name} must contain a valid UUID integer.") + return value_int + + def _validate_code(field_name: str, value: object) -> None: """Require an exact built-in lower snake_case policy or request code.""" if type(value) is not str or _CODE_PATTERN.fullmatch(value) is None: @@ -116,7 +126,48 @@ def _validate_scope_set(values: object) -> None: raise ValueError("granted_scope_codes must contain only explicit Orgmetra scopes.") -@dataclass(frozen=True, slots=True) +class _PolicySnapshot(NamedTuple): + """Detached creation-time authority for one purpose-bound policy.""" + + tenant_record_id_int: int + policy_version_code: str + resource_kind: str + purpose_code: str + operation_code: str + required_scope_code: str + permitted_fields: frozenset[str] + + +class _RequestSnapshot(NamedTuple): + """Detached creation-time authority for one purpose-bound access request.""" + + tenant_record_id_int: int + actor_tenant_record_id_int: int + resource_tenant_record_id_int: int + actor_reference: str + resource_reference: str + purpose_code: str + operation_code: str + resource_kind: str + requested_fields: frozenset[str] + granted_scope_codes: frozenset[str] + + +_POLICY_SNAPSHOT_REGISTRY: dict[ + int, + tuple[weakref.ReferenceType[object], _PolicySnapshot], +] = {} +_REQUEST_SNAPSHOT_REGISTRY: dict[ + int, + tuple[weakref.ReferenceType[object], _RequestSnapshot], +] = {} +_DECISION_SNAPSHOT_REGISTRY: dict[ + int, + tuple[weakref.ReferenceType[object], tuple[object, ...]], +] = {} + + +@dataclass(frozen=True, slots=True, weakref_slot=True) class PurposeBoundAccessPolicy: """One tenant-local field policy for one purpose, resource, and operation. @@ -134,17 +185,22 @@ class PurposeBoundAccessPolicy: permitted_fields: frozenset[str] def __post_init__(self) -> None: - """Reject ambiguous or mutable policy attributes before evaluation.""" - _validate_uuid("tenant_record_id", self.tenant_record_id) - _validate_version(self.policy_version_code) - _validate_resource_kind(self.resource_kind) - _validate_code("purpose_code", self.purpose_code) - _validate_code("operation_code", self.operation_code) - _validate_scope("required_scope_code", self.required_scope_code) - _validate_field_set("permitted_fields", self.permitted_fields) + """Validate and register the creation-time authority snapshot exactly once.""" + snapshot = _validated_policy_snapshot(self) + key = id(self) + if key in _POLICY_SNAPSHOT_REGISTRY: + raise TypeError("PurposeBoundAccessPolicy is already initialized") + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: _POLICY_SNAPSHOT_REGISTRY.pop( + evidence_key, + None, + ), + ) + _POLICY_SNAPSHOT_REGISTRY[key] = (reference, snapshot) -@dataclass(frozen=True, slots=True) +@dataclass(frozen=True, slots=True, weakref_slot=True) class PurposeBoundAccessRequest: """PII access attributes resolved before any protected field is returned. @@ -169,27 +225,116 @@ class PurposeBoundAccessRequest: granted_scope_codes: frozenset[str] def __post_init__(self) -> None: - """Reject untrusted identity, target, tenant, purpose, field, or scope attributes.""" - _validate_uuid("tenant_record_id", self.tenant_record_id) - _validate_uuid("actor_tenant_record_id", self.actor_tenant_record_id) - _validate_uuid("resource_tenant_record_id", self.resource_tenant_record_id) - _validate_reference("actor_reference", self.actor_reference) - _validate_resource_kind(self.resource_kind) - _validate_reference( - "resource_reference", - self.resource_reference, - expected_namespace=self.resource_kind, + """Validate and register the creation-time request snapshot exactly once.""" + snapshot = _validated_request_snapshot(self) + key = id(self) + if key in _REQUEST_SNAPSHOT_REGISTRY: + raise TypeError("PurposeBoundAccessRequest is already initialized") + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: _REQUEST_SNAPSHOT_REGISTRY.pop( + evidence_key, + None, + ), ) - _validate_code("purpose_code", self.purpose_code) - _validate_code("operation_code", self.operation_code) - _validate_field_set("requested_fields", self.requested_fields) - _validate_scope_set(self.granted_scope_codes) + _REQUEST_SNAPSHOT_REGISTRY[key] = (reference, snapshot) -_DECISION_SNAPSHOT_REGISTRY: dict[ - int, - tuple[weakref.ReferenceType[object], tuple[object, ...]], -] = {} +def _validated_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: + """Read, validate, and detach one complete policy snapshot.""" + tenant_record_id = policy.tenant_record_id + policy_version_code = policy.policy_version_code + resource_kind = policy.resource_kind + purpose_code = policy.purpose_code + operation_code = policy.operation_code + required_scope_code = policy.required_scope_code + permitted_fields = policy.permitted_fields + + tenant_record_id_int = _validated_uuid_int("tenant_record_id", tenant_record_id) + _validate_version(policy_version_code) + _validate_resource_kind(resource_kind) + _validate_code("purpose_code", purpose_code) + _validate_code("operation_code", operation_code) + _validate_scope("required_scope_code", required_scope_code) + _validate_field_set("permitted_fields", permitted_fields) + return _PolicySnapshot( + tenant_record_id_int, + policy_version_code, + resource_kind, + purpose_code, + operation_code, + required_scope_code, + permitted_fields, + ) + + +def _validated_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnapshot: + """Read, validate, and detach one complete access-request snapshot.""" + tenant_record_id = request.tenant_record_id + actor_tenant_record_id = request.actor_tenant_record_id + resource_tenant_record_id = request.resource_tenant_record_id + actor_reference = request.actor_reference + resource_reference = request.resource_reference + purpose_code = request.purpose_code + operation_code = request.operation_code + resource_kind = request.resource_kind + requested_fields = request.requested_fields + granted_scope_codes = request.granted_scope_codes + + tenant_record_id_int = _validated_uuid_int("tenant_record_id", tenant_record_id) + actor_tenant_record_id_int = _validated_uuid_int( + "actor_tenant_record_id", + actor_tenant_record_id, + ) + resource_tenant_record_id_int = _validated_uuid_int( + "resource_tenant_record_id", + resource_tenant_record_id, + ) + _validate_reference("actor_reference", actor_reference) + _validate_resource_kind(resource_kind) + _validate_reference( + "resource_reference", + resource_reference, + expected_namespace=resource_kind, + ) + _validate_code("purpose_code", purpose_code) + _validate_code("operation_code", operation_code) + _validate_field_set("requested_fields", requested_fields) + _validate_scope_set(granted_scope_codes) + return _RequestSnapshot( + tenant_record_id_int, + actor_tenant_record_id_int, + resource_tenant_record_id_int, + actor_reference, + resource_reference, + purpose_code, + operation_code, + resource_kind, + requested_fields, + granted_scope_codes, + ) + + +def _issued_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: + """Return creation-time policy authority only when live fields still match it.""" + entry = _POLICY_SNAPSHOT_REGISTRY.get(id(policy)) + if entry is None or entry[0]() is not policy: + raise ValueError("PurposeBoundAccessPolicy was not issued by the validated constructor") + current = _validated_policy_snapshot(policy) + if current != entry[1]: + raise ValueError("PurposeBoundAccessPolicy changed after validation") + return entry[1] + + +def _issued_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnapshot: + """Return creation-time request authority only when live fields still match it.""" + entry = _REQUEST_SNAPSHOT_REGISTRY.get(id(request)) + if entry is None or entry[0]() is not request: + raise ValueError("PurposeBoundAccessRequest was not issued by the validated constructor") + current = _validated_request_snapshot(request) + if current != entry[1]: + raise ValueError("PurposeBoundAccessRequest changed after validation") + return entry[1] def _validated_decision_snapshot( @@ -210,10 +355,7 @@ def _validated_decision_snapshot( """Validate decision evidence and detach caller-owned mutable runtime objects.""" if type(allowed) is not bool: raise ValueError("allowed must be a boolean.") - _validate_uuid("tenant_record_id", tenant_record_id) - tenant_int = tenant_record_id.int - if type(tenant_int) is not int or not 0 <= tenant_int <= _MAX_UUID_INT: - raise ValueError("tenant_record_id must contain a valid UUID integer.") + tenant_int = _validated_uuid_int("tenant_record_id", tenant_record_id) _validate_reference("actor_reference", actor_reference) _validate_resource_kind(resource_kind) _validate_reference( @@ -433,17 +575,17 @@ def __init__(self, decision: AuthorizationDecision) -> None: def _decision( *, - request: PurposeBoundAccessRequest, - policy: PurposeBoundAccessPolicy, + request: _RequestSnapshot, + policy: _PolicySnapshot, allowed: bool, reason_code: str, ) -> AuthorizationDecision: - """Build one immutable allow/deny record without copying protected values.""" + """Build one immutable decision from validated creation-time authority snapshots.""" authorized_fields = request.requested_fields if allowed else frozenset() next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] return AuthorizationDecision( allowed=allowed, - tenant_record_id=request.tenant_record_id, + tenant_record_id=UUID(int=request.tenant_record_id_int), actor_reference=request.actor_reference, resource_reference=request.resource_reference, policy_version_code=policy.policy_version_code, @@ -464,64 +606,68 @@ def evaluate_purpose_bound_access( ) -> AuthorizationDecision: """Evaluate tenant, resource, purpose, operation, scope, and field attributes. - The order deliberately checks tenant isolation before policy detail and then - requires every narrowing attribute. Possessing a broad identity or a valid - purpose header is insufficient when the operation scope or requested field - set is not explicitly authorized. + The evaluator binds both inputs to their validated creation-time snapshots + before comparing any authorization attribute. A frozen dataclass is not + treated as a security boundary because ``object.__setattr__`` can still write + its slots; any post-construction rewrite fails closed and the evaluator then + uses only the detached snapshots. """ if type(request) is not PurposeBoundAccessRequest: raise TypeError("request must be a PurposeBoundAccessRequest") if type(policy) is not PurposeBoundAccessPolicy: raise TypeError("policy must be a PurposeBoundAccessPolicy") + + request_snapshot = _issued_request_snapshot(request) + policy_snapshot = _issued_policy_snapshot(policy) if ( - request.tenant_record_id != policy.tenant_record_id - or request.actor_tenant_record_id != policy.tenant_record_id - or request.resource_tenant_record_id != policy.tenant_record_id + request_snapshot.tenant_record_id_int != policy_snapshot.tenant_record_id_int + or request_snapshot.actor_tenant_record_id_int != policy_snapshot.tenant_record_id_int + or request_snapshot.resource_tenant_record_id_int != policy_snapshot.tenant_record_id_int ): return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=False, reason_code="tenant_scope_mismatch", ) - if request.resource_kind != policy.resource_kind: + if request_snapshot.resource_kind != policy_snapshot.resource_kind: return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=False, reason_code="resource_not_allowed", ) - if request.purpose_code != policy.purpose_code: + if request_snapshot.purpose_code != policy_snapshot.purpose_code: return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=False, reason_code="purpose_not_allowed", ) - if request.operation_code != policy.operation_code: + if request_snapshot.operation_code != policy_snapshot.operation_code: return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=False, reason_code="operation_not_allowed", ) - if policy.required_scope_code not in request.granted_scope_codes: + if policy_snapshot.required_scope_code not in request_snapshot.granted_scope_codes: return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=False, reason_code="required_scope_missing", ) - if not request.requested_fields.issubset(policy.permitted_fields): + if not request_snapshot.requested_fields.issubset(policy_snapshot.permitted_fields): return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=False, reason_code="field_not_allowed", ) return _decision( - request=request, - policy=policy, + request=request_snapshot, + policy=policy_snapshot, allowed=True, reason_code="access_permitted", ) From 79824637ac1e02e596622e85923b4884babf122d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:18:10 +0900 Subject: [PATCH 023/241] test(authz): require side-effect-free issued input reinitialization --- ...st_authorization_issued_input_integrity.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py index 422cecaad..ac16837d8 100644 --- a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py @@ -76,3 +76,139 @@ def test_evaluator_rejects_post_construction_request_field_rewrite() -> None: with pytest.raises(ValueError, match="PurposeBoundAccessRequest changed after validation"): evaluate_purpose_bound_access(request=request, policy=_policy()) + + +def test_rejected_policy_reinitialization_preserves_issued_value() -> None: + """A rejected second constructor call cannot rewrite observable policy state.""" + policy = _policy() + before = ( + policy.tenant_record_id, + policy.policy_version_code, + policy.resource_kind, + policy.purpose_code, + policy.operation_code, + policy.required_scope_code, + policy.permitted_fields, + repr(policy), + hash(policy), + ) + + with pytest.raises(TypeError, match="PurposeBoundAccessPolicy is already initialized"): + PurposeBoundAccessPolicy.__init__( + policy, + tenant_record_id=TENANT, + policy_version_code="people_pii_v2", + resource_kind="person_record", + purpose_code="compensation_review", + operation_code="read_person_pii", + required_scope_code="orgmetra.people.read", + permitted_fields=frozenset({"compensation_amount"}), + ) + + after = ( + policy.tenant_record_id, + policy.policy_version_code, + policy.resource_kind, + policy.purpose_code, + policy.operation_code, + policy.required_scope_code, + policy.permitted_fields, + repr(policy), + hash(policy), + ) + assert after == before + + +def test_invalid_policy_reinitialization_preserves_issued_value() -> None: + """Validation failure during a second constructor call must also be side-effect free.""" + policy = _policy() + before = repr(policy) + + with pytest.raises(ValueError, match="policy_version_code"): + PurposeBoundAccessPolicy.__init__( + policy, + tenant_record_id=TENANT, + policy_version_code="contains whitespace", + resource_kind="person_record", + purpose_code="hr_operations", + operation_code="read_person_pii", + required_scope_code="orgmetra.people.read", + permitted_fields=frozenset({"work_email"}), + ) + + assert repr(policy) == before + assert policy.policy_version_code == "people_pii_v1" + + +def test_rejected_request_reinitialization_preserves_issued_value() -> None: + """A rejected second constructor call cannot rewrite authenticated request state.""" + request = _request() + before = ( + request.tenant_record_id, + request.actor_tenant_record_id, + request.resource_tenant_record_id, + request.actor_reference, + request.resource_reference, + request.purpose_code, + request.operation_code, + request.resource_kind, + request.requested_fields, + request.granted_scope_codes, + repr(request), + hash(request), + ) + + with pytest.raises(TypeError, match="PurposeBoundAccessRequest is already initialized"): + PurposeBoundAccessRequest.__init__( + request, + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference="keyverse_subject:sub_other_actor", + resource_reference="person_record:per_01J5EXACTTARGET", + purpose_code="compensation_review", + operation_code="read_person_pii", + resource_kind="person_record", + requested_fields=frozenset({"compensation_amount"}), + granted_scope_codes=frozenset({"orgmetra.people.read"}), + ) + + after = ( + request.tenant_record_id, + request.actor_tenant_record_id, + request.resource_tenant_record_id, + request.actor_reference, + request.resource_reference, + request.purpose_code, + request.operation_code, + request.resource_kind, + request.requested_fields, + request.granted_scope_codes, + repr(request), + hash(request), + ) + assert after == before + + +def test_invalid_request_reinitialization_preserves_issued_value() -> None: + """Invalid second-constructor input cannot partially rewrite the issued request.""" + request = _request() + before = repr(request) + + with pytest.raises(ValueError, match="resource_reference"): + PurposeBoundAccessRequest.__init__( + request, + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference="keyverse_subject:sub_jordan_hale", + resource_reference="wrong_namespace:per_01J5EXACTTARGET", + purpose_code="hr_operations", + operation_code="read_person_pii", + resource_kind="person_record", + requested_fields=frozenset({"work_email"}), + granted_scope_codes=frozenset({"orgmetra.people.read"}), + ) + + assert repr(request) == before + assert request.resource_reference == "person_record:per_01J5EXACTTARGET" From 2318ff8b90c5029532b94da30396b7330405d953 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:19:45 +0900 Subject: [PATCH 024/241] fix(authz): guard issued policy and request reinitialization --- .../authorization.py | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index d5187aa47..5419a0ba1 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -167,7 +167,7 @@ class _RequestSnapshot(NamedTuple): ] = {} -@dataclass(frozen=True, slots=True, weakref_slot=True) +@dataclass(frozen=True, slots=True, weakref_slot=True, init=False) class PurposeBoundAccessPolicy: """One tenant-local field policy for one purpose, resource, and operation. @@ -184,6 +184,29 @@ class PurposeBoundAccessPolicy: required_scope_code: str permitted_fields: frozenset[str] + def __init__( + self, + *, + tenant_record_id: UUID, + policy_version_code: str, + resource_kind: str, + purpose_code: str, + operation_code: str, + required_scope_code: str, + permitted_fields: frozenset[str], + ) -> None: + """Write constructor fields only before the object has issued authority.""" + if id(self) in _POLICY_SNAPSHOT_REGISTRY: + raise TypeError("PurposeBoundAccessPolicy is already initialized") + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "policy_version_code", policy_version_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "required_scope_code", required_scope_code) + object.__setattr__(self, "permitted_fields", permitted_fields) + self.__post_init__() + def __post_init__(self) -> None: """Validate and register the creation-time authority snapshot exactly once.""" snapshot = _validated_policy_snapshot(self) @@ -200,7 +223,7 @@ def __post_init__(self) -> None: _POLICY_SNAPSHOT_REGISTRY[key] = (reference, snapshot) -@dataclass(frozen=True, slots=True, weakref_slot=True) +@dataclass(frozen=True, slots=True, weakref_slot=True, init=False) class PurposeBoundAccessRequest: """PII access attributes resolved before any protected field is returned. @@ -224,6 +247,35 @@ class PurposeBoundAccessRequest: requested_fields: frozenset[str] granted_scope_codes: frozenset[str] + def __init__( + self, + *, + tenant_record_id: UUID, + actor_tenant_record_id: UUID, + resource_tenant_record_id: UUID, + actor_reference: str, + resource_reference: str, + purpose_code: str, + operation_code: str, + resource_kind: str, + requested_fields: frozenset[str], + granted_scope_codes: frozenset[str], + ) -> None: + """Write constructor fields only before the object has issued authority.""" + if id(self) in _REQUEST_SNAPSHOT_REGISTRY: + raise TypeError("PurposeBoundAccessRequest is already initialized") + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) + object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) + object.__setattr__(self, "actor_reference", actor_reference) + object.__setattr__(self, "resource_reference", resource_reference) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "requested_fields", requested_fields) + object.__setattr__(self, "granted_scope_codes", granted_scope_codes) + self.__post_init__() + def __post_init__(self) -> None: """Validate and register the creation-time request snapshot exactly once.""" snapshot = _validated_request_snapshot(self) From e92d2ac44db0a38a126d6be3e48eec95b431bb5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:28:28 +0900 Subject: [PATCH 025/241] test(authz): align invalid reinitialization lifecycle expectations --- .../tests/test_authorization_issued_input_integrity.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py index ac16837d8..794482655 100644 --- a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py @@ -120,11 +120,11 @@ def test_rejected_policy_reinitialization_preserves_issued_value() -> None: def test_invalid_policy_reinitialization_preserves_issued_value() -> None: - """Validation failure during a second constructor call must also be side-effect free.""" + """The issuance guard precedes validation and leaves an issued policy unchanged.""" policy = _policy() before = repr(policy) - with pytest.raises(ValueError, match="policy_version_code"): + with pytest.raises(TypeError, match="PurposeBoundAccessPolicy is already initialized"): PurposeBoundAccessPolicy.__init__( policy, tenant_record_id=TENANT, @@ -191,11 +191,11 @@ def test_rejected_request_reinitialization_preserves_issued_value() -> None: def test_invalid_request_reinitialization_preserves_issued_value() -> None: - """Invalid second-constructor input cannot partially rewrite the issued request.""" + """The issuance guard precedes validation and leaves an issued request unchanged.""" request = _request() before = repr(request) - with pytest.raises(ValueError, match="resource_reference"): + with pytest.raises(TypeError, match="PurposeBoundAccessRequest is already initialized"): PurposeBoundAccessRequest.__init__( request, tenant_record_id=TENANT, From 0fef3f8e2df49ad008034d730e125d64c240eb92 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:43:26 +0900 Subject: [PATCH 026/241] test(authz): reject direct post-init issuance --- ...st_authorization_issued_input_integrity.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py index 794482655..d87308b31 100644 --- a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py @@ -44,6 +44,35 @@ def _request(*, field: str = "work_email") -> PurposeBoundAccessRequest: ) +def _policy_without_constructor() -> PurposeBoundAccessPolicy: + """Forge valid-looking policy fields without invoking the public constructor.""" + policy = object.__new__(PurposeBoundAccessPolicy) + object.__setattr__(policy, "tenant_record_id", TENANT) + object.__setattr__(policy, "policy_version_code", "people_pii_v1") + object.__setattr__(policy, "resource_kind", "person_record") + object.__setattr__(policy, "purpose_code", "hr_operations") + object.__setattr__(policy, "operation_code", "read_person_pii") + object.__setattr__(policy, "required_scope_code", "orgmetra.people.read") + object.__setattr__(policy, "permitted_fields", frozenset({"work_email"})) + return policy + + +def _request_without_constructor() -> PurposeBoundAccessRequest: + """Forge valid-looking request fields without invoking the public constructor.""" + request = object.__new__(PurposeBoundAccessRequest) + object.__setattr__(request, "tenant_record_id", TENANT) + object.__setattr__(request, "actor_tenant_record_id", TENANT) + object.__setattr__(request, "resource_tenant_record_id", TENANT) + object.__setattr__(request, "actor_reference", "keyverse_subject:sub_jordan_hale") + object.__setattr__(request, "resource_reference", "person_record:per_01J5EXACTTARGET") + object.__setattr__(request, "purpose_code", "hr_operations") + object.__setattr__(request, "operation_code", "read_person_pii") + object.__setattr__(request, "resource_kind", "person_record") + object.__setattr__(request, "requested_fields", frozenset({"work_email"})) + object.__setattr__(request, "granted_scope_codes", frozenset({"orgmetra.people.read"})) + return request + + def test_evaluator_rejects_post_construction_policy_widening() -> None: """Low-level mutation cannot widen a policy after its governed construction.""" policy = _policy() @@ -78,6 +107,28 @@ def test_evaluator_rejects_post_construction_request_field_rewrite() -> None: evaluate_purpose_bound_access(request=request, policy=_policy()) +def test_direct_policy_post_init_cannot_issue_constructor_bypassing_object() -> None: + """Public lifecycle hooks cannot mint policy authority for a forged exact object.""" + policy = _policy_without_constructor() + + with pytest.raises(TypeError, match="must be initialized through its constructor"): + PurposeBoundAccessPolicy.__post_init__(policy) + + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + evaluate_purpose_bound_access(request=_request(), policy=policy) + + +def test_direct_request_post_init_cannot_issue_constructor_bypassing_object() -> None: + """Public lifecycle hooks cannot mint request authority for a forged exact object.""" + request = _request_without_constructor() + + with pytest.raises(TypeError, match="must be initialized through its constructor"): + PurposeBoundAccessRequest.__post_init__(request) + + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + evaluate_purpose_bound_access(request=request, policy=_policy()) + + def test_rejected_policy_reinitialization_preserves_issued_value() -> None: """A rejected second constructor call cannot rewrite observable policy state.""" policy = _policy() From dc945885dee354c16ca68a2fece18c0d58973faf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:46:20 +0900 Subject: [PATCH 027/241] fix(authz): bind snapshot issuance to constructor lifecycle --- .../authorization.py | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 5419a0ba1..be2ac1536 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -165,6 +165,8 @@ class _RequestSnapshot(NamedTuple): int, tuple[weakref.ReferenceType[object], tuple[object, ...]], ] = {} +_POLICY_CONSTRUCTION_IDS: set[int] = set() +_REQUEST_CONSTRUCTION_IDS: set[int] = set() @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) @@ -195,22 +197,31 @@ def __init__( required_scope_code: str, permitted_fields: frozenset[str], ) -> None: - """Write constructor fields only before the object has issued authority.""" - if id(self) in _POLICY_SNAPSHOT_REGISTRY: + """Write fields and issue authority only inside this constructor call.""" + key = id(self) + if key in _POLICY_SNAPSHOT_REGISTRY: raise TypeError("PurposeBoundAccessPolicy is already initialized") - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "policy_version_code", policy_version_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "required_scope_code", required_scope_code) - object.__setattr__(self, "permitted_fields", permitted_fields) - self.__post_init__() + if key in _POLICY_CONSTRUCTION_IDS: + raise TypeError("PurposeBoundAccessPolicy construction is already in progress") + _POLICY_CONSTRUCTION_IDS.add(key) + try: + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "policy_version_code", policy_version_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "required_scope_code", required_scope_code) + object.__setattr__(self, "permitted_fields", permitted_fields) + self.__post_init__() + finally: + _POLICY_CONSTRUCTION_IDS.discard(key) def __post_init__(self) -> None: - """Validate and register the creation-time authority snapshot exactly once.""" - snapshot = _validated_policy_snapshot(self) + """Issue a validated snapshot only while the governed constructor is active.""" key = id(self) + if key not in _POLICY_CONSTRUCTION_IDS: + raise TypeError("PurposeBoundAccessPolicy must be initialized through its constructor") + snapshot = _validated_policy_snapshot(self) if key in _POLICY_SNAPSHOT_REGISTRY: raise TypeError("PurposeBoundAccessPolicy is already initialized") reference = weakref.ref( @@ -261,25 +272,34 @@ def __init__( requested_fields: frozenset[str], granted_scope_codes: frozenset[str], ) -> None: - """Write constructor fields only before the object has issued authority.""" - if id(self) in _REQUEST_SNAPSHOT_REGISTRY: + """Write fields and issue authority only inside this constructor call.""" + key = id(self) + if key in _REQUEST_SNAPSHOT_REGISTRY: raise TypeError("PurposeBoundAccessRequest is already initialized") - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) - object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) - object.__setattr__(self, "actor_reference", actor_reference) - object.__setattr__(self, "resource_reference", resource_reference) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "requested_fields", requested_fields) - object.__setattr__(self, "granted_scope_codes", granted_scope_codes) - self.__post_init__() + if key in _REQUEST_CONSTRUCTION_IDS: + raise TypeError("PurposeBoundAccessRequest construction is already in progress") + _REQUEST_CONSTRUCTION_IDS.add(key) + try: + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) + object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) + object.__setattr__(self, "actor_reference", actor_reference) + object.__setattr__(self, "resource_reference", resource_reference) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "requested_fields", requested_fields) + object.__setattr__(self, "granted_scope_codes", granted_scope_codes) + self.__post_init__() + finally: + _REQUEST_CONSTRUCTION_IDS.discard(key) def __post_init__(self) -> None: - """Validate and register the creation-time request snapshot exactly once.""" - snapshot = _validated_request_snapshot(self) + """Issue a validated snapshot only while the governed constructor is active.""" key = id(self) + if key not in _REQUEST_CONSTRUCTION_IDS: + raise TypeError("PurposeBoundAccessRequest must be initialized through its constructor") + snapshot = _validated_request_snapshot(self) if key in _REQUEST_SNAPSHOT_REGISTRY: raise TypeError("PurposeBoundAccessRequest is already initialized") reference = weakref.ref( From 269b52141969c699646cdacdfe9d2ed93630e04e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:12:52 +0900 Subject: [PATCH 028/241] test(authz): require evaluator-issued decisions --- ...horization_decision_issuance_provenance.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py new file mode 100644 index 000000000..e14b4d246 --- /dev/null +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -0,0 +1,70 @@ +"""Issuance-provenance regressions for purpose-bound authorization decisions.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import ( + AuthorizationDecision, + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000501") +RESOURCE_REFERENCE = "assignment_record:0198a412800070008000000000000070" +REQUESTED_FIELDS = frozenset({"assignment_category_code"}) + + +def test_direct_decision_constructor_cannot_mint_allow_authority() -> None: + """A caller must not mint an allow decision without governed policy evaluation.""" + with pytest.raises(TypeError, match="purpose-bound evaluation"): + AuthorizationDecision( + allowed=True, + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference=RESOURCE_REFERENCE, + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=REQUESTED_FIELDS, + authorized_fields=REQUESTED_FIELDS, + reason_code="access_permitted", + next_action="Continue with only the authorized fields.", + ) + + +def test_governed_evaluator_remains_the_decision_issuance_path() -> None: + """Matching issued request and policy evidence still produce one allow decision.""" + policy = PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="assignment-correction-v1", + resource_kind="assignment_record", + purpose_code="workforce_admin", + operation_code="correct_record", + required_scope_code="orgmetra.people.write", + permitted_fields=REQUESTED_FIELDS, + ) + request = PurposeBoundAccessRequest( + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference=RESOURCE_REFERENCE, + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=REQUESTED_FIELDS, + granted_scope_codes=frozenset({"orgmetra.people.write"}), + ) + + decision = evaluate_purpose_bound_access(request=request, policy=policy) + + assert type(decision) is AuthorizationDecision + assert decision.allowed is True + assert decision.tenant_record_id == TENANT + assert decision.resource_reference == RESOURCE_REFERENCE + assert decision.authorized_fields == REQUESTED_FIELDS From ffdf141e2336e0fdba320e620fadb19d39b1a313 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:15:16 +0900 Subject: [PATCH 029/241] fix(authz): bind decisions to evaluator issuance --- .../authorization.py | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index be2ac1536..7a1b7972e 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -167,6 +167,7 @@ class _RequestSnapshot(NamedTuple): ] = {} _POLICY_CONSTRUCTION_IDS: set[int] = set() _REQUEST_CONSTRUCTION_IDS: set[int] = set() +_DECISION_ISSUANCE_IDS: set[int] = set() @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) @@ -473,7 +474,9 @@ class AuthorizationDecision: Validated values live in a module-owned snapshot rather than writable instance slots. In particular the tenant UUID is stored as its integer value and rebuilt on access, so a later low-level mutation of the caller's UUID cannot rewrite - already-issued authorization evidence. + already-issued authorization evidence. The public constructor is intentionally + non-authoritative: only the module-owned purpose-bound evaluator may mint an + issued decision. """ __slots__ = ("__weakref__",) @@ -507,10 +510,12 @@ def __init__( reason_code: str, next_action: str, ) -> None: - """Validate once and register a detached immutable evidence snapshot.""" + """Register a detached snapshot only during module-owned policy evaluation.""" key = id(self) if key in _DECISION_SNAPSHOT_REGISTRY: raise TypeError("AuthorizationDecision is already initialized") + if key not in _DECISION_ISSUANCE_IDS: + raise TypeError("AuthorizationDecision must be issued by purpose-bound evaluation") snapshot = _validated_decision_snapshot( allowed=allowed, tenant_record_id=tenant_record_id, @@ -542,7 +547,7 @@ def _snapshot(self) -> tuple[object, ...]: """Return the issued snapshot or fail closed for low-level forged instances.""" entry = _DECISION_SNAPSHOT_REGISTRY.get(id(self)) if entry is None or entry[0]() is not self: - raise ValueError("AuthorizationDecision was not issued by the validated constructor") + raise ValueError("AuthorizationDecision was not issued by purpose-bound evaluation") return entry[1] @property @@ -655,20 +660,28 @@ def _decision( """Build one immutable decision from validated creation-time authority snapshots.""" authorized_fields = request.requested_fields if allowed else frozenset() next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] - return AuthorizationDecision( - allowed=allowed, - tenant_record_id=UUID(int=request.tenant_record_id_int), - actor_reference=request.actor_reference, - resource_reference=request.resource_reference, - policy_version_code=policy.policy_version_code, - purpose_code=request.purpose_code, - operation_code=request.operation_code, - resource_kind=request.resource_kind, - requested_fields=request.requested_fields, - authorized_fields=authorized_fields, - reason_code=reason_code, - next_action=next_action, - ) + decision = object.__new__(AuthorizationDecision) + key = id(decision) + _DECISION_ISSUANCE_IDS.add(key) + try: + AuthorizationDecision.__init__( + decision, + allowed=allowed, + tenant_record_id=UUID(int=request.tenant_record_id_int), + actor_reference=request.actor_reference, + resource_reference=request.resource_reference, + policy_version_code=policy.policy_version_code, + purpose_code=request.purpose_code, + operation_code=request.operation_code, + resource_kind=request.resource_kind, + requested_fields=request.requested_fields, + authorized_fields=authorized_fields, + reason_code=reason_code, + next_action=next_action, + ) + finally: + _DECISION_ISSUANCE_IDS.discard(key) + return decision def evaluate_purpose_bound_access( @@ -682,7 +695,9 @@ def evaluate_purpose_bound_access( before comparing any authorization attribute. A frozen dataclass is not treated as a security boundary because ``object.__setattr__`` can still write its slots; any post-construction rewrite fails closed and the evaluator then - uses only the detached snapshots. + uses only the detached snapshots. Decision issuance is likewise bound to this + evaluator so a caller cannot mint an allow result by constructing the evidence + class directly. """ if type(request) is not PurposeBoundAccessRequest: raise TypeError("request must be a PurposeBoundAccessRequest") From 01b55d04dd1b77cc699434f25663026bf960806d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:17:07 +0900 Subject: [PATCH 030/241] test(authz): align decision integrity with evaluator issuance --- ...uthorization_decision_runtime_integrity.py | 189 +++++++++++------- 1 file changed, 119 insertions(+), 70 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index c4fcfd6d5..9066ff9dc 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -1,4 +1,4 @@ -"""Runtime-integrity regressions for downstream authorization-decision evidence.""" +"""Runtime-integrity regressions for evaluator-issued authorization decisions.""" from __future__ import annotations @@ -8,54 +8,99 @@ import pytest -from orgmetra_keyverse_adapter.authorization import AuthorizationDecision +import orgmetra_keyverse_adapter.authorization as authorization_module +from orgmetra_keyverse_adapter import ( + AuthorizationDecision, + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) TENANT = UUID("10000000-0000-7000-8000-000000000501") +RESOURCE_REFERENCE = "assignment_record:0198a412800070008000000000000070" +REQUESTED_FIELDS = frozenset({"assignment_category_code"}) class _ForgedUUID(UUID): - """Carry caller-defined UUID behavior inside downstream authorization evidence.""" + """Carry caller-defined UUID behavior inside authorization evidence input.""" class _ForgedText(str): - """Carry caller-defined text behavior inside downstream authorization evidence.""" + """Carry caller-defined text behavior inside authorization evidence input.""" class _ForgedFieldSet(frozenset[str]): - """Carry caller-defined set behavior inside downstream authorization evidence.""" + """Carry caller-defined set behavior inside authorization evidence input.""" + + +def _policy(**overrides: object) -> PurposeBoundAccessPolicy: + """Build one deterministic issued policy for decision-integrity tests.""" + values: dict[str, object] = { + "tenant_record_id": TENANT, + "policy_version_code": "assignment-correction-v1", + "resource_kind": "assignment_record", + "purpose_code": "workforce_admin", + "operation_code": "correct_record", + "required_scope_code": "orgmetra.people.write", + "permitted_fields": REQUESTED_FIELDS, + } + values.update(overrides) + return PurposeBoundAccessPolicy(**values) # type: ignore[arg-type] + + +def _request(**overrides: object) -> PurposeBoundAccessRequest: + """Build one deterministic issued request for decision-integrity tests.""" + values: dict[str, object] = { + "tenant_record_id": TENANT, + "actor_tenant_record_id": TENANT, + "resource_tenant_record_id": TENANT, + "actor_reference": "keyverse_subject:operator-17", + "resource_reference": RESOURCE_REFERENCE, + "purpose_code": "workforce_admin", + "operation_code": "correct_record", + "resource_kind": "assignment_record", + "requested_fields": REQUESTED_FIELDS, + "granted_scope_codes": frozenset({"orgmetra.people.write"}), + } + values.update(overrides) + return PurposeBoundAccessRequest(**values) # type: ignore[arg-type] + + +def _decision() -> AuthorizationDecision: + """Return one allow decision issued only by the governed evaluator.""" + return evaluate_purpose_bound_access(request=_request(), policy=_policy()) -def _decision(**overrides: object) -> AuthorizationDecision: - """Build one deterministic allow decision for runtime-integrity tests.""" +def _validate_decision(**overrides: object) -> tuple[object, ...]: + """Exercise the internal pure evidence validator without minting authority.""" values: dict[str, object] = { "allowed": True, "tenant_record_id": TENANT, "actor_reference": "keyverse_subject:operator-17", - "resource_reference": "assignment_record:0198a412800070008000000000000070", + "resource_reference": RESOURCE_REFERENCE, "policy_version_code": "assignment-correction-v1", "purpose_code": "workforce_admin", "operation_code": "correct_record", "resource_kind": "assignment_record", - "requested_fields": frozenset({"assignment_category_code"}), - "authorized_fields": frozenset({"assignment_category_code"}), + "requested_fields": REQUESTED_FIELDS, + "authorized_fields": REQUESTED_FIELDS, "reason_code": "access_permitted", "next_action": "Continue with only the authorized fields.", } values.update(overrides) - return AuthorizationDecision(**values) # type: ignore[arg-type] + return authorization_module._validated_decision_snapshot(**values) -def test_decision_cannot_be_subclassed_to_bypass_post_init_validation() -> None: - """Caller-defined decision classes must not bypass the sealed evidence validator.""" +def test_decision_cannot_be_subclassed_to_bypass_validation() -> None: + """Caller-defined decision classes must not override issued evidence behavior.""" with pytest.raises(TypeError, match="AuthorizationDecision must not be subclassed"): class _ForgedDecision(AuthorizationDecision): - def __post_init__(self) -> None: - return None + pass -def test_decision_resists_object_setattr_after_valid_construction() -> None: - """Low-level attribute writes must not replace already-validated authorization evidence.""" +def test_decision_resists_object_setattr_after_valid_evaluation() -> None: + """Low-level attribute writes cannot replace already-issued authorization evidence.""" decision = _decision() with pytest.raises((AttributeError, TypeError)): object.__setattr__(decision, "allowed", False) @@ -64,31 +109,38 @@ def test_decision_resists_object_setattr_after_valid_construction() -> None: def test_decision_detaches_caller_owned_exact_uuid() -> None: - """Later low-level mutation of an accepted UUID object must not rewrite issued evidence.""" + """Later low-level UUID mutation must not rewrite an evaluator-issued decision.""" tenant = UUID(str(TENANT)) - decision = _decision(tenant_record_id=tenant) + decision = evaluate_purpose_bound_access( + request=_request( + tenant_record_id=tenant, + actor_tenant_record_id=tenant, + resource_tenant_record_id=tenant, + ), + policy=_policy(tenant_record_id=tenant), + ) object.__setattr__(tenant, "int", 0) assert decision.tenant_record_id == TENANT @pytest.mark.parametrize("forged_int", [-1, 1 << 128, "invalid"]) -def test_decision_rejects_low_level_corrupted_exact_uuid(forged_int: object) -> None: - """An exact UUID object with corrupted internal integer state is not valid tenant evidence.""" +def test_decision_validator_rejects_low_level_corrupted_exact_uuid(forged_int: object) -> None: + """The internal snapshot validator rejects an exact UUID with corrupted integer state.""" tenant = UUID(str(TENANT)) object.__setattr__(tenant, "int", forged_int) with pytest.raises(ValueError, match="tenant_record_id must contain a valid UUID integer"): - _decision(tenant_record_id=tenant) + _validate_decision(tenant_record_id=tenant) def test_decision_rejects_unissued_low_level_instance() -> None: - """Bypassing the public constructor must not yield readable authorization evidence.""" + """Bypassing evaluation must not yield readable authorization evidence.""" forged = object.__new__(AuthorizationDecision) - with pytest.raises(ValueError, match="was not issued by the validated constructor"): + with pytest.raises(ValueError, match="was not issued by purpose-bound evaluation"): _ = forged.allowed def test_decision_cannot_be_reinitialized_with_new_evidence() -> None: - """The public initializer cannot replace a snapshot after the decision has been issued.""" + """A previously issued decision cannot be replaced through a second initializer call.""" decision = _decision() with pytest.raises(TypeError, match="already initialized"): AuthorizationDecision.__init__( @@ -96,12 +148,12 @@ def test_decision_cannot_be_reinitialized_with_new_evidence() -> None: allowed=False, tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", - resource_reference="assignment_record:0198a412800070008000000000000070", + resource_reference=RESOURCE_REFERENCE, policy_version_code="assignment-correction-v1", purpose_code="workforce_admin", operation_code="correct_record", resource_kind="assignment_record", - requested_fields=frozenset({"assignment_category_code"}), + requested_fields=REQUESTED_FIELDS, authorized_fields=frozenset(), reason_code="field_not_allowed", next_action="Request only fields allowed for this purpose.", @@ -109,7 +161,7 @@ def test_decision_cannot_be_reinitialized_with_new_evidence() -> None: def test_decision_preserves_value_semantics_and_deterministic_repr() -> None: - """Structural hardening must preserve equality, hashing, and diagnostic representation.""" + """Issuance hardening preserves equality, hashing, and diagnostic representation.""" left = _decision() right = _decision() assert left == right @@ -120,7 +172,7 @@ def test_decision_preserves_value_semantics_and_deterministic_repr() -> None: def test_decision_registry_does_not_retain_dead_evidence() -> None: - """Lifecycle bookkeeping must not keep authorization evidence alive after callers release it.""" + """Lifecycle bookkeeping must not keep evaluator-issued evidence alive.""" decision = _decision() reference = weakref.ref(decision) del decision @@ -128,27 +180,24 @@ def test_decision_registry_does_not_retain_dead_evidence() -> None: assert reference() is None -def test_decision_rejects_non_boolean_allowed_flag() -> None: - """Do not let truthy integers masquerade as an authorization verdict.""" +def test_decision_validator_rejects_non_boolean_allowed_flag() -> None: + """Truthy integers cannot masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): - _decision(allowed=1) + _validate_decision(allowed=1) -def test_decision_rejects_uuid_subclass() -> None: - """Tenant evidence must not retain caller-defined UUID runtime behavior.""" +def test_decision_validator_rejects_uuid_subclass() -> None: + """Decision snapshots cannot retain caller-defined UUID runtime behavior.""" forged = _ForgedUUID(str(TENANT)) with pytest.raises(ValueError, match="tenant_record_id must be a UUID"): - _decision(tenant_record_id=forged) + _validate_decision(tenant_record_id=forged) @pytest.mark.parametrize( ("field_name", "forged_value"), [ ("actor_reference", _ForgedText("keyverse_subject:operator-17")), - ( - "resource_reference", - _ForgedText("assignment_record:0198a412800070008000000000000070"), - ), + ("resource_reference", _ForgedText(RESOURCE_REFERENCE)), ("policy_version_code", _ForgedText("assignment-correction-v1")), ("purpose_code", _ForgedText("workforce_admin")), ("operation_code", _ForgedText("correct_record")), @@ -157,85 +206,85 @@ def test_decision_rejects_uuid_subclass() -> None: ("next_action", _ForgedText("Continue with only the authorized fields.")), ], ) -def test_decision_rejects_string_subclasses(field_name: str, forged_value: str) -> None: - """Decision evidence cannot retain caller-defined text runtime behavior.""" +def test_decision_validator_rejects_string_subclasses(field_name: str, forged_value: str) -> None: + """Decision snapshots cannot retain caller-defined text runtime behavior.""" with pytest.raises(ValueError): - _decision(**{field_name: forged_value}) + _validate_decision(**{field_name: forged_value}) @pytest.mark.parametrize("field_name", ["requested_fields", "authorized_fields"]) -def test_decision_rejects_frozenset_subclasses(field_name: str) -> None: - """Field evidence cannot override containment or equality after authorization.""" +def test_decision_validator_rejects_frozenset_subclasses(field_name: str) -> None: + """Field evidence cannot override containment or equality after evaluation.""" forged = _ForgedFieldSet({"assignment_category_code"}) with pytest.raises(ValueError, match=f"{field_name} must be a frozenset"): - _decision(**{field_name: forged}) + _validate_decision(**{field_name: forged}) @pytest.mark.parametrize("field_name", ["requested_fields", "authorized_fields"]) -def test_decision_rejects_string_subclasses_inside_field_sets(field_name: str) -> None: - """Each authorized field identifier must be an exact built-in string.""" +def test_decision_validator_rejects_string_subclasses_inside_field_sets(field_name: str) -> None: + """Each field identifier must be an exact built-in string.""" forged = frozenset({_ForgedText("assignment_category_code")}) with pytest.raises(ValueError, match=f"{field_name} must contain only"): - _decision(**{field_name: forged}) + _validate_decision(**{field_name: forged}) def test_allow_decision_requires_exact_requested_authorized_field_equality() -> None: - """An allow verdict cannot silently authorize fewer or different fields than requested.""" + """An allow verdict cannot silently authorize fewer or different fields.""" with pytest.raises(ValueError, match="allow decision must authorize exactly the requested fields"): - _decision(authorized_fields=frozenset({"legal_name"})) + _validate_decision(authorized_fields=frozenset({"legal_name"})) def test_deny_decision_cannot_carry_authorized_fields() -> None: """A deny verdict cannot retain a non-empty authorized field set.""" with pytest.raises(ValueError, match="deny decision must not authorize fields"): - _decision( + _validate_decision( allowed=False, - authorized_fields=frozenset({"assignment_category_code"}), + authorized_fields=REQUESTED_FIELDS, reason_code="field_not_allowed", next_action="Request only fields allowed for this purpose.", ) def test_allow_decision_rejects_denial_reason() -> None: - """An allow verdict cannot carry a denial reason into downstream audit evidence.""" + """An allow verdict cannot carry a denial reason into downstream evidence.""" with pytest.raises(ValueError, match="allow decision must use access_permitted reason"): - _decision(reason_code="field_not_allowed") + _validate_decision(reason_code="field_not_allowed") def test_deny_decision_rejects_success_reason() -> None: - """A deny verdict cannot masquerade as successful authorization in audit evidence.""" + """A deny verdict cannot masquerade as successful authorization evidence.""" with pytest.raises(ValueError, match="deny decision must not use access_permitted reason"): - _decision( + _validate_decision( allowed=False, authorized_fields=frozenset(), reason_code="access_permitted", ) -def test_decision_accepts_explicit_denial_reason_outside_evaluator_vocabulary() -> None: - """The public evidence type preserves bounded downstream denial codes without widening allow semantics.""" - decision = _decision( +def test_decision_validator_accepts_bounded_internal_denial_reason() -> None: + """The pure validator preserves a bounded denial code without minting authority.""" + snapshot = _validate_decision( allowed=False, authorized_fields=frozenset(), reason_code="access_denied", next_action="stop", ) - assert decision.reason_code == "access_denied" + assert snapshot[10] == "access_denied" -def test_decision_preserves_bounded_actionable_text_as_non_authoritative_guidance() -> None: - """Recovery guidance may vary without changing the governed verdict or reason code.""" - decision = _decision(next_action="Continue after logging the reviewed evidence.") - assert decision.next_action == "Continue after logging the reviewed evidence." +def test_decision_validator_preserves_bounded_actionable_text() -> None: + """Recovery guidance validation remains independent of the governed verdict.""" + snapshot = _validate_decision(next_action="Continue after logging the reviewed evidence.") + assert snapshot[11] == "Continue after logging the reviewed evidence." -def test_decision_rejects_resource_reference_namespace_mismatch() -> None: - """Downstream evidence must correlate its opaque target to the declared resource kind.""" +def test_decision_validator_rejects_resource_reference_namespace_mismatch() -> None: + """Downstream evidence must correlate its target to the declared resource kind.""" with pytest.raises(ValueError, match="resource_reference namespace must match resource_kind"): - _decision(resource_reference="employment_record:0198a412800070008000000000000070") + _validate_decision(resource_reference="employment_record:0198a412800070008000000000000070") -def test_decision_rejects_blank_next_action() -> None: - """Authorization evidence must preserve an actionable bounded recovery instruction.""" +def test_decision_validator_rejects_blank_next_action() -> None: + """Authorization evidence must preserve a bounded recovery instruction.""" with pytest.raises(ValueError, match="next_action must be a non-blank string"): - _decision(next_action=" ") + _validate_decision(next_action=" ") From 99459d893e6876fd30ba035b20d8d8248eddd014 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:17:24 +0900 Subject: [PATCH 031/241] test(people): issue authorization fixtures through evaluator --- .../tests/authorization_test_support.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 services/people-api/tests/authorization_test_support.py diff --git a/services/people-api/tests/authorization_test_support.py b/services/people-api/tests/authorization_test_support.py new file mode 100644 index 000000000..b041d0295 --- /dev/null +++ b/services/people-api/tests/authorization_test_support.py @@ -0,0 +1,52 @@ +"""Test support that obtains authorization evidence through the public evaluator.""" + +from __future__ import annotations + +from uuid import UUID + +from orgmetra_keyverse_adapter import ( + AuthorizationDecision, + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) + + +def issued_authorization( + *, + tenant_record_id: UUID, + actor_reference: str, + resource_reference: str, + policy_version_code: str, + purpose_code: str, + operation_code: str, + resource_kind: str, + requested_fields: frozenset[str], + required_scope_code: str, + granted_scope_codes: frozenset[str] | None = None, + permitted_fields: frozenset[str] | None = None, + policy_purpose_code: str | None = None, +) -> AuthorizationDecision: + """Return evidence produced by the same purpose-bound evaluation used in production.""" + policy = PurposeBoundAccessPolicy( + tenant_record_id=tenant_record_id, + policy_version_code=policy_version_code, + resource_kind=resource_kind, + purpose_code=policy_purpose_code or purpose_code, + operation_code=operation_code, + required_scope_code=required_scope_code, + permitted_fields=permitted_fields or requested_fields, + ) + request = PurposeBoundAccessRequest( + tenant_record_id=tenant_record_id, + actor_tenant_record_id=tenant_record_id, + resource_tenant_record_id=tenant_record_id, + actor_reference=actor_reference, + resource_reference=resource_reference, + purpose_code=purpose_code, + operation_code=operation_code, + resource_kind=resource_kind, + requested_fields=requested_fields, + granted_scope_codes=granted_scope_codes or frozenset({required_scope_code}), + ) + return evaluate_purpose_bound_access(request=request, policy=policy) From 83870fcbb73f806d2aeca2ec80ca9158c57bd8ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:17:45 +0900 Subject: [PATCH 032/241] test(people): derive reason-binding authorization --- .../people-api/tests/test_decision_reason_binding.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/people-api/tests/test_decision_reason_binding.py b/services/people-api/tests/test_decision_reason_binding.py index dd1b11eed..8178ae2d9 100644 --- a/services/people-api/tests/test_decision_reason_binding.py +++ b/services/people-api/tests/test_decision_reason_binding.py @@ -10,6 +10,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision from orgmetra_people_api.mutation_http import _command_for_route from orgmetra_people_api.mutations import EmploymentMutationCommand, mutation_command_digest +from authorization_test_support import issued_authorization TENANT = UUID("0198a412-8a00-7000-8000-000000000001") PERSON = UUID("0198a412-8a00-7000-8000-000000000002") @@ -64,9 +65,8 @@ def _command(payload: dict[str, object]) -> EmploymentMutationCommand: def _authorization(command: EmploymentMutationCommand) -> AuthorizationDecision: - """Return matching PII-minimized authorization evidence for digest comparison.""" - return AuthorizationDecision( - allowed=True, + """Return evaluator-issued authorization evidence for digest comparison.""" + return issued_authorization( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-20", resource_reference=f"employment_record:{command.employment_record_id.hex}", @@ -75,9 +75,7 @@ def _authorization(command: EmploymentMutationCommand) -> AuthorizationDecision: operation_code="create_record", resource_kind="employment_record", requested_fields=frozenset({"employment_record"}), - authorized_fields=frozenset({"employment_record"}), - reason_code="access_permitted", - next_action="Continue with only the authorized fields.", + required_scope_code="orgmetra.people.write", ) From b43931aa6e9fc4d2e5677dc74a80da56ac649353 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:18:06 +0900 Subject: [PATCH 033/241] test(people): derive evidence-binding authorization --- .../test_evidence_reference_binding_regression.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/people-api/tests/test_evidence_reference_binding_regression.py b/services/people-api/tests/test_evidence_reference_binding_regression.py index 5c0d061c3..11d04640b 100644 --- a/services/people-api/tests/test_evidence_reference_binding_regression.py +++ b/services/people-api/tests/test_evidence_reference_binding_regression.py @@ -9,6 +9,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision from orgmetra_people_api.mutation_http import _command_for_route from orgmetra_people_api.mutations import mutation_command_digest +from authorization_test_support import issued_authorization TENANT = UUID("0198a412-8200-7000-8000-000000000001") PERSON = UUID("0198a412-8200-7000-8000-000000000020") @@ -54,9 +55,8 @@ def command_for(evidence_references: list[object]): def authorization() -> AuthorizationDecision: - """Return the exact allow decision used solely to derive command digests.""" - return AuthorizationDecision( - allowed=True, + """Return evaluator-issued allow evidence used solely for command digests.""" + return issued_authorization( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-99", resource_reference=f"employment_record:{EMPLOYMENT.hex}", @@ -65,9 +65,7 @@ def authorization() -> AuthorizationDecision: operation_code="create_record", resource_kind="employment_record", requested_fields=frozenset({"employment_record"}), - authorized_fields=frozenset({"employment_record"}), - reason_code="access_permitted", - next_action="continue", + required_scope_code="orgmetra.people.write", ) From bc2cf64712918965198c71babf4f5a6d0ca79b67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:19:20 +0900 Subject: [PATCH 034/241] test(people): use evaluated postgres authorization --- .../tests/test_postgres_people_mutations.py | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/services/people-api/tests/test_postgres_people_mutations.py b/services/people-api/tests/test_postgres_people_mutations.py index 0175fc6d4..49880f01c 100644 --- a/services/people-api/tests/test_postgres_people_mutations.py +++ b/services/people-api/tests/test_postgres_people_mutations.py @@ -20,6 +20,7 @@ mutation_command_digest, ) from orgmetra_people_api.postgres_mutations import PostgresPeopleMutationPort +from authorization_test_support import issued_authorization from test_people_mutations import ( TENANT, PERSON, @@ -100,9 +101,8 @@ def cursor(self) -> ScriptedCursor: def employment_authorization() -> AuthorizationDecision: - """Return the exact allow decision created by the employment policy.""" - return AuthorizationDecision( - allowed=True, + """Return the exact allow decision issued by the employment policy evaluator.""" + return issued_authorization( tenant_record_id=TENANT, actor_reference=ACTOR, resource_reference=f"employment_record:{EMPLOYMENT.hex}", @@ -111,16 +111,13 @@ def employment_authorization() -> AuthorizationDecision: operation_code="create_record", resource_kind="employment_record", requested_fields=frozenset({"employment_record"}), - authorized_fields=frozenset({"employment_record"}), - reason_code="access_permitted", - next_action="continue", + required_scope_code="orgmetra.people.write", ) def position_authorization() -> AuthorizationDecision: - """Return the exact allow decision created by the position policy.""" - return AuthorizationDecision( - allowed=True, + """Return the exact allow decision issued by the position policy evaluator.""" + return issued_authorization( tenant_record_id=TENANT, actor_reference=ACTOR, resource_reference=f"position_record:{POSITION.hex}", @@ -129,16 +126,13 @@ def position_authorization() -> AuthorizationDecision: operation_code="create_record", resource_kind="position_record", requested_fields=frozenset({"position_record"}), - authorized_fields=frozenset({"position_record"}), - reason_code="access_permitted", - next_action="continue", + required_scope_code="orgmetra.job_architecture.write", ) def assignment_authorization() -> AuthorizationDecision: - """Return the exact allow decision created by the assignment policy.""" - return AuthorizationDecision( - allowed=True, + """Return the exact allow decision issued by the assignment policy evaluator.""" + return issued_authorization( tenant_record_id=TENANT, actor_reference=ACTOR, resource_reference=f"assignment_record:{ASSIGNMENT.hex}", @@ -147,9 +141,7 @@ def assignment_authorization() -> AuthorizationDecision: operation_code="create_record", resource_kind="assignment_record", requested_fields=frozenset({"assignment_record"}), - authorized_fields=frozenset({"assignment_record"}), - reason_code="access_permitted", - next_action="continue", + required_scope_code="orgmetra.people.write", ) @@ -432,8 +424,7 @@ def factory() -> FakeConnection: port = PostgresPeopleMutationPort(factory) with self.assertRaisesRegex(PeopleMutationIntegrityError, "authorization"): port.create_employment(command=employment_command(), authorization=object()) # type: ignore[arg-type] - denied = AuthorizationDecision( - allowed=False, + denied = issued_authorization( tenant_record_id=TENANT, actor_reference=ACTOR, resource_reference=f"employment_record:{EMPLOYMENT.hex}", @@ -442,10 +433,10 @@ def factory() -> FakeConnection: operation_code="create_record", resource_kind="employment_record", requested_fields=frozenset({"employment_record"}), - authorized_fields=frozenset(), - reason_code="access_denied", - next_action="stop", + required_scope_code="orgmetra.people.write", + granted_scope_codes=frozenset({"orgmetra.people.read"}), ) + self.assertFalse(denied.allowed) with self.assertRaisesRegex(PeopleMutationIntegrityError, "authorization"): port.create_employment(command=employment_command(), authorization=denied) with self.assertRaisesRegex(TypeError, "EmploymentMutationCommand"): From cdf21f054cf82fc4411efc217ddb5f5637e4e43e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:20:14 +0900 Subject: [PATCH 035/241] test(people): derive mutation digest authorization --- services/people-api/tests/test_people_mutations.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/people-api/tests/test_people_mutations.py b/services/people-api/tests/test_people_mutations.py index b629355e9..a1bca2980 100644 --- a/services/people-api/tests/test_people_mutations.py +++ b/services/people-api/tests/test_people_mutations.py @@ -7,7 +7,7 @@ import unittest from uuid import UUID -from orgmetra_keyverse_adapter import AuthorizationDecision, AuthorizationDeniedError, PurposeBoundAccessPolicy +from orgmetra_keyverse_adapter import AuthorizationDeniedError, PurposeBoundAccessPolicy from orgmetra_people_api.auth import AuthenticatedPrincipal from orgmetra_people_api.mutations import ( AssignmentMutationCommand, @@ -25,6 +25,7 @@ parse_allocation_ratio, validate_idempotency_key, ) +from authorization_test_support import issued_authorization TENANT = UUID("0198a412-8000-7000-8000-000000000001") PERSON = UUID("0198a412-8000-7000-8000-000000000020") @@ -326,8 +327,7 @@ def test_service_requires_typed_commands_ports_and_results(self) -> None: ) def test_command_digest_excludes_generated_ids_and_changes_with_semantics(self) -> None: - authorization = AuthorizationDecision( - allowed=True, + authorization = issued_authorization( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", resource_reference=f"employment_record:{EMPLOYMENT.hex}", @@ -336,9 +336,7 @@ def test_command_digest_excludes_generated_ids_and_changes_with_semantics(self) operation_code="create_record", resource_kind="employment_record", requested_fields=frozenset({"employment_record"}), - authorized_fields=frozenset({"employment_record"}), - reason_code="access_permitted", - next_action="continue", + required_scope_code="orgmetra.people.write", ) first = mutation_command_digest(command=employment_command(), authorization=authorization) retried = mutation_command_digest( From c59359e920e2c4250cbf988e1be3aee132c8f581 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:21:13 +0900 Subject: [PATCH 036/241] test(people): use evaluated hire authorization --- .../tests/test_postgres_hire_acceptance.py | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/services/people-api/tests/test_postgres_hire_acceptance.py b/services/people-api/tests/test_postgres_hire_acceptance.py index ccd6009ff..fe581f4ba 100644 --- a/services/people-api/tests/test_postgres_hire_acceptance.py +++ b/services/people-api/tests/test_postgres_hire_acceptance.py @@ -21,6 +21,7 @@ PostgresHireAcceptancePort, _hire_command_digest, ) +from authorization_test_support import issued_authorization TENANT = UUID("0198a412-7100-7000-8000-000000000001") CANDIDATE = UUID("0198a412-7100-7000-8000-000000000010") @@ -112,9 +113,8 @@ def policy() -> PurposeBoundAccessPolicy: def allowed_authorization() -> AuthorizationDecision: - """Return the exact allow decision produced for the deterministic test command.""" - return AuthorizationDecision( - allowed=True, + """Return the exact allow decision issued for the deterministic test command.""" + return issued_authorization( tenant_record_id=TENANT, actor_reference=ACTOR, resource_reference=f"selection_decision:{DECISION.hex}", @@ -123,9 +123,7 @@ def allowed_authorization() -> AuthorizationDecision: operation_code="materialize_worker", resource_kind="selection_decision", requested_fields=frozenset({"candidate_worker_conversion"}), - authorized_fields=frozenset({"candidate_worker_conversion"}), - reason_code="access_permitted", - next_action="continue", + required_scope_code="orgmetra.people.materialize_worker", ) @@ -425,8 +423,7 @@ def factory() -> FakeConnection: return FakeConnection(FakeCursor([[], [decision_row()]])) port = PostgresHireAcceptancePort(factory) - forged = AuthorizationDecision( - allowed=False, + denied = issued_authorization( tenant_record_id=TENANT, actor_reference=ACTOR, resource_reference=f"selection_decision:{DECISION.hex}", @@ -435,28 +432,23 @@ def factory() -> FakeConnection: operation_code="materialize_worker", resource_kind="selection_decision", requested_fields=frozenset({"candidate_worker_conversion"}), - authorized_fields=frozenset(), - reason_code="access_denied", - next_action="stop", + required_scope_code="orgmetra.people.materialize_worker", + granted_scope_codes=frozenset({"orgmetra.people.read"}), ) - invalid_authorizations: tuple[object, ...] = ( - object(), - forged, - AuthorizationDecision( - allowed=True, - tenant_record_id=TENANT, - actor_reference=ACTOR, - resource_reference="selection_decision:wrong-target", - policy_version_code="people-hire-v1", - purpose_code=PURPOSE, - operation_code="materialize_worker", - resource_kind="selection_decision", - requested_fields=frozenset({"candidate_worker_conversion"}), - authorized_fields=frozenset({"candidate_worker_conversion"}), - reason_code="access_permitted", - next_action="continue", - ), + wrong_target = issued_authorization( + tenant_record_id=TENANT, + actor_reference=ACTOR, + resource_reference="selection_decision:wrong-target", + policy_version_code="people-hire-v1", + purpose_code=PURPOSE, + operation_code="materialize_worker", + resource_kind="selection_decision", + requested_fields=frozenset({"candidate_worker_conversion"}), + required_scope_code="orgmetra.people.materialize_worker", ) + self.assertFalse(denied.allowed) + self.assertTrue(wrong_target.allowed) + invalid_authorizations: tuple[object, ...] = (object(), denied, wrong_target) for authorization in invalid_authorizations: with self.subTest(authorization=authorization), self.assertRaisesRegex( HireDecisionIntegrityError, From 2351bd0c2b7ac9634bfd9e47601c8fbbb73402d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:22:33 +0900 Subject: [PATCH 037/241] docs(authz): trace evaluator-issued decision authority --- docs/TRACEABILITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 22a4178fe..c830867d6 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -21,7 +21,7 @@ | Tenant-safe atomic outbox claiming and crash recovery | Integration Hub dispatcher boundary | `outbox_delivery_record` pending/expired-lease claim indexes plus `claim_outbox_delivery(...)` | PostgreSQL already-expired-new-lease rejection, due-order claim, live-lease exclusion, pre-exhaustion takeover with `lease_expired` evidence, retry-budget claim bound, tenant-context binding, opaque-worker validation, and bounded-lease contract | ADR-0006 | implemented_on_active_pr | | Owner-bound outbox completion, retry, and terminal dead-letter escalation | Integration Hub dispatcher boundary | immutable `outbox_delivery_record.maximum_attempt_count`, `complete_outbox_delivery(...)`, `retry_outbox_delivery(...)`, `dead_letter_outbox_delivery(...)`, `outbox_delivery_escalation_record` | PostgreSQL foreign/stale-owner denial, dispatcher-budget-signature rejection, direct-terminal-DML rejection, stored-budget exhaustion, retry-attempt-N+1 denial, exhausted expired-lease non-reclaimability, recorded-owner terminalization, nonterminal-escalation rejection, terminal non-reclaimability, and append-only escalation evidence | ADR-0006 | implemented_on_active_pr | | Predictive-validity case integrity | Workforce Validation | `validity_study`, normalized `validity_study_case_record`, exact `selection_decision`, sealed `decision_evidence_set`, governed `candidate_worker_conversion_record`, `criterion_observation` | `test_validity_study_case_postgres.sh`: legacy loose-link write rejection; exact evidence-set ID, Job, criterion and worker mismatch rejection; study/observation system-recorded visibility boundaries; governed upstream decision/evidence/conversion lineage from the evidence-sealing and candidate-worker conversion contracts; UPDATE/DELETE/TRUNCATE protection; missing/foreign-tenant RLS denial. Statistical estimation remains subsequent work. | ADR-0001, SIOP Principles 5th ed., 29 C.F.R. Part 1607 | implemented_on_protected_main | -| Purpose-bound PII access | Security architecture / Keyverse adapter boundary | `PurposeBoundAccessPolicy`, `PurposeBoundAccessRequest.resource_reference`, `AuthorizationDecision.resource_reference` | exact tenant/actor/resource binding, exact opaque target correlation for allow/deny audit evidence, resource/purpose/operation matching, operation-specific scope, field-subset minimization, malformed-attribute rejection, reserved-UUID rejection, PII-minimized denial evidence, and exact 100% owned statement/branch coverage | ADR-0008 | implemented_on_protected_main | +| Purpose-bound PII access and authorization-evidence issuance | Security architecture / Keyverse adapter boundary | `PurposeBoundAccessPolicy`, `PurposeBoundAccessRequest.resource_reference`, evaluator-issued `AuthorizationDecision` | exact tenant/actor/resource binding; exact opaque target correlation; resource/purpose/operation/scope/field minimization; reserved-UUID and malformed-runtime rejection; detached creation-time policy/request/decision snapshots; policy/request constructor-provenance regressions; direct `AuthorizationDecision(allowed=True, ...)` minting rejection; evaluator-only decision issuance; weakref cleanup and exact 100% owned statement/branch coverage | ADR-0008 | implemented_on_active_pr | | Least-privilege API capability | Keyverse gateway boundary | operation scope conceptual | structural per-operation scope and confused-deputy contract tests | ADR-0002 | implemented_on_active_pr | | Client-safe failure correlation | API error boundary | `support_reference` conceptual | error disclosure and support-lookup tests | ADR-0002 | implemented_on_active_pr | | Foundation artifact integrity | Repository governance | deterministic `manifest.json` file inventory | SHA-256/byte/line validation plus Python/Node inventory-equivalence regression and explicit dispatcher/validity/criterion/job-analysis migration and execution-contract provenance regression | ADR-0001 | implemented_on_active_pr | @@ -30,7 +30,7 @@ | External contract | Orgmetra owner boundary | Integration style | Required evidence | ADR | Maturity | |---|---|---|---|---|---| -| Keyverse identity and authorization | API Gateway / purpose-bound authorization | Published OIDC/API identity and scope contract plus Orgmetra-owned `orgmetra_keyverse_adapter` policy evaluation | tenant/actor/resource agreement, exact opaque target-resource reference, purpose, operation-specific scope, requested-field minimization, opaque subject, no stored credentials or protected values in authorization evidence | ADR-0002, ADR-0008 | implemented_on_protected_main | +| Keyverse identity and authorization | API Gateway / purpose-bound authorization | Published OIDC/API identity and scope contract plus Orgmetra-owned `orgmetra_keyverse_adapter` policy evaluation | tenant/actor/resource agreement, exact opaque target-resource reference, purpose, operation-specific scope, requested-field minimization, opaque subject, no stored credentials or protected values in authorization evidence; authorization decisions are minted only by the Orgmetra evaluator, not by consumer construction | ADR-0002, ADR-0008 | implemented_on_active_pr | | naruon communication and calendar | Integration Hub | Published API/event adapter | idempotency, delivery audit, no direct table access | ADR-0002 | planned | | Psychometrics Commons @ `cc5850a0d1eacbbf16d03075534fce460a8286e6` | Workforce Validation | Immutable response/result snapshot contract | pinned revision, model/version/provenance snapshot, immutable result linkage, no direct application-table access | ADR-0002 | accepted_architecture | | fast-mlsirm @ `fb67ced09d8ee00542c05d56374537a9a7239751` | Workforce Validation | Published `orgmetra.fast_mlsirm.v1` result contract; direct calls only from approved offline validation worker | pinned revision, contract identifier, backend/result provenance, CPU/GPU parity evidence where material, no duplicated kernel | ADR-0002 | accepted_architecture | From b75fb41243ec71076fb2dcbd360ab55150398515 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:27:17 +0900 Subject: [PATCH 038/241] chore(manifest): record authorization traceability --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index e115c0a3a..57b2fecf9 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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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}]} +{"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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"cb957a525c77fc6ab7c8b174bda781b25ab3ef37761cba7e5678e2bde1d43220","bytes":11704,"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}]} From 7aebe4e4190c8742ed9047db9886778a0f010b42 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:42:29 +0900 Subject: [PATCH 039/241] test(authz): reject fabricated decision helper issuance --- ...horization_decision_issuance_provenance.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py index e14b4d246..f2671641e 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -6,6 +6,7 @@ import pytest +import orgmetra_keyverse_adapter.authorization as authorization_module from orgmetra_keyverse_adapter import ( AuthorizationDecision, PurposeBoundAccessPolicy, @@ -37,6 +38,39 @@ def test_direct_decision_constructor_cannot_mint_allow_authority() -> None: ) +def test_module_decision_helper_cannot_mint_from_fabricated_snapshots() -> None: + """Module-callable helpers must not turn fabricated snapshots into authority.""" + request_snapshot = authorization_module._RequestSnapshot( + TENANT.int, + TENANT.int, + TENANT.int, + "keyverse_subject:operator-17", + RESOURCE_REFERENCE, + "workforce_admin", + "correct_record", + "assignment_record", + REQUESTED_FIELDS, + frozenset({"orgmetra.people.write"}), + ) + policy_snapshot = authorization_module._PolicySnapshot( + TENANT.int, + "assignment-correction-v1", + "assignment_record", + "workforce_admin", + "correct_record", + "orgmetra.people.write", + REQUESTED_FIELDS, + ) + + with pytest.raises(TypeError, match="internal to evaluate_purpose_bound_access"): + authorization_module._decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=True, + reason_code="access_permitted", + ) + + def test_governed_evaluator_remains_the_decision_issuance_path() -> None: """Matching issued request and policy evidence still produce one allow decision.""" policy = PurposeBoundAccessPolicy( From 2b522170adfadcc2b3803183482a23cad4efca3c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:46:04 +0900 Subject: [PATCH 040/241] fix(authz): keep decision issuance evaluator-local --- .../authorization.py | 143 ++++++------------ 1 file changed, 45 insertions(+), 98 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 7a1b7972e..e27660554 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -167,7 +167,6 @@ class _RequestSnapshot(NamedTuple): ] = {} _POLICY_CONSTRUCTION_IDS: set[int] = set() _REQUEST_CONSTRUCTION_IDS: set[int] = set() -_DECISION_ISSUANCE_IDS: set[int] = set() @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) @@ -510,34 +509,10 @@ def __init__( reason_code: str, next_action: str, ) -> None: - """Register a detached snapshot only during module-owned policy evaluation.""" - key = id(self) - if key in _DECISION_SNAPSHOT_REGISTRY: + """Reject direct construction; only the evaluator may register evidence.""" + if id(self) in _DECISION_SNAPSHOT_REGISTRY: raise TypeError("AuthorizationDecision is already initialized") - if key not in _DECISION_ISSUANCE_IDS: - raise TypeError("AuthorizationDecision must be issued by purpose-bound evaluation") - snapshot = _validated_decision_snapshot( - allowed=allowed, - tenant_record_id=tenant_record_id, - actor_reference=actor_reference, - resource_reference=resource_reference, - policy_version_code=policy_version_code, - purpose_code=purpose_code, - operation_code=operation_code, - resource_kind=resource_kind, - requested_fields=requested_fields, - authorized_fields=authorized_fields, - reason_code=reason_code, - next_action=next_action, - ) - reference = weakref.ref( - self, - lambda _reference, evidence_key=key: _DECISION_SNAPSHOT_REGISTRY.pop( - evidence_key, - None, - ), - ) - _DECISION_SNAPSHOT_REGISTRY[key] = (reference, snapshot) + raise TypeError("AuthorizationDecision must be issued by purpose-bound evaluation") def __init_subclass__(cls, **kwargs: object) -> None: """Seal the evidence type so subclasses cannot override validation hooks.""" @@ -657,31 +632,8 @@ def _decision( allowed: bool, reason_code: str, ) -> AuthorizationDecision: - """Build one immutable decision from validated creation-time authority snapshots.""" - authorized_fields = request.requested_fields if allowed else frozenset() - next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] - decision = object.__new__(AuthorizationDecision) - key = id(decision) - _DECISION_ISSUANCE_IDS.add(key) - try: - AuthorizationDecision.__init__( - decision, - allowed=allowed, - tenant_record_id=UUID(int=request.tenant_record_id_int), - actor_reference=request.actor_reference, - resource_reference=request.resource_reference, - policy_version_code=policy.policy_version_code, - purpose_code=request.purpose_code, - operation_code=request.operation_code, - resource_kind=request.resource_kind, - requested_fields=request.requested_fields, - authorized_fields=authorized_fields, - reason_code=reason_code, - next_action=next_action, - ) - finally: - _DECISION_ISSUANCE_IDS.discard(key) - return decision + """Reject direct use of the former module-level authority-minting helper.""" + raise TypeError("decision issuance is internal to evaluate_purpose_bound_access") def evaluate_purpose_bound_access( @@ -695,9 +647,8 @@ def evaluate_purpose_bound_access( before comparing any authorization attribute. A frozen dataclass is not treated as a security boundary because ``object.__setattr__`` can still write its slots; any post-construction rewrite fails closed and the evaluator then - uses only the detached snapshots. Decision issuance is likewise bound to this - evaluator so a caller cannot mint an allow result by constructing the evidence - class directly. + uses only the detached snapshots. Decision issuance is local to this evaluator, + so caller-constructible snapshot values cannot reach the issuance registry. """ if type(request) is not PurposeBoundAccessRequest: raise TypeError("request must be a PurposeBoundAccessRequest") @@ -706,58 +657,54 @@ class directly. request_snapshot = _issued_request_snapshot(request) policy_snapshot = _issued_policy_snapshot(policy) + + def issue_decision(*, allowed: bool, reason_code: str) -> AuthorizationDecision: + """Register one decision from the already-issued policy/request snapshots.""" + authorized_fields = request_snapshot.requested_fields if allowed else frozenset() + next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] + snapshot = _validated_decision_snapshot( + allowed=allowed, + tenant_record_id=UUID(int=request_snapshot.tenant_record_id_int), + actor_reference=request_snapshot.actor_reference, + resource_reference=request_snapshot.resource_reference, + policy_version_code=policy_snapshot.policy_version_code, + purpose_code=request_snapshot.purpose_code, + operation_code=request_snapshot.operation_code, + resource_kind=request_snapshot.resource_kind, + requested_fields=request_snapshot.requested_fields, + authorized_fields=authorized_fields, + reason_code=reason_code, + next_action=next_action, + ) + decision = object.__new__(AuthorizationDecision) + key = id(decision) + reference = weakref.ref( + decision, + lambda _reference, evidence_key=key: _DECISION_SNAPSHOT_REGISTRY.pop( + evidence_key, + None, + ), + ) + _DECISION_SNAPSHOT_REGISTRY[key] = (reference, snapshot) + return decision + if ( request_snapshot.tenant_record_id_int != policy_snapshot.tenant_record_id_int or request_snapshot.actor_tenant_record_id_int != policy_snapshot.tenant_record_id_int or request_snapshot.resource_tenant_record_id_int != policy_snapshot.tenant_record_id_int ): - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=False, - reason_code="tenant_scope_mismatch", - ) + return issue_decision(allowed=False, reason_code="tenant_scope_mismatch") if request_snapshot.resource_kind != policy_snapshot.resource_kind: - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=False, - reason_code="resource_not_allowed", - ) + return issue_decision(allowed=False, reason_code="resource_not_allowed") if request_snapshot.purpose_code != policy_snapshot.purpose_code: - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=False, - reason_code="purpose_not_allowed", - ) + return issue_decision(allowed=False, reason_code="purpose_not_allowed") if request_snapshot.operation_code != policy_snapshot.operation_code: - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=False, - reason_code="operation_not_allowed", - ) + return issue_decision(allowed=False, reason_code="operation_not_allowed") if policy_snapshot.required_scope_code not in request_snapshot.granted_scope_codes: - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=False, - reason_code="required_scope_missing", - ) + return issue_decision(allowed=False, reason_code="required_scope_missing") if not request_snapshot.requested_fields.issubset(policy_snapshot.permitted_fields): - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=False, - reason_code="field_not_allowed", - ) - return _decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=True, - reason_code="access_permitted", - ) + return issue_decision(allowed=False, reason_code="field_not_allowed") + return issue_decision(allowed=True, reason_code="access_permitted") def require_purpose_bound_access( From 1e0c124ad5a5f24c62d310b7268c5e5ebd7721c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:47:08 +0900 Subject: [PATCH 041/241] test(authz): seal evaluator-local issuance lifecycle --- .../tests/test_authorization_decision_issuance_provenance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py index f2671641e..bb7f3197f 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -69,6 +69,7 @@ def test_module_decision_helper_cannot_mint_from_fabricated_snapshots() -> None: allowed=True, reason_code="access_permitted", ) + assert not hasattr(authorization_module, "_DECISION_ISSUANCE_IDS") def test_governed_evaluator_remains_the_decision_issuance_path() -> None: From 98ad797a8374925d20091d2885a2224cce337970 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:51:13 +0900 Subject: [PATCH 042/241] test(authz): reject direct decision registry forgery --- ...horization_decision_issuance_provenance.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py index bb7f3197f..686a786d8 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -2,6 +2,7 @@ from __future__ import annotations +import weakref from uuid import UUID import pytest @@ -72,6 +73,34 @@ def test_module_decision_helper_cannot_mint_from_fabricated_snapshots() -> None: assert not hasattr(authorization_module, "_DECISION_ISSUANCE_IDS") +def test_module_registry_insertion_cannot_mint_forged_decision() -> None: + """Consumer-visible module state must not provide a writable authority registry.""" + forged = object.__new__(AuthorizationDecision) + snapshot = authorization_module._validated_decision_snapshot( + allowed=True, + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference=RESOURCE_REFERENCE, + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=REQUESTED_FIELDS, + authorized_fields=REQUESTED_FIELDS, + reason_code="access_permitted", + next_action="Continue with only the authorized fields.", + ) + registry = getattr(authorization_module, "_DECISION_SNAPSHOT_REGISTRY", None) + try: + if registry is not None: + registry[id(forged)] = (weakref.ref(forged), snapshot) + with pytest.raises(ValueError, match="was not issued by purpose-bound evaluation"): + _ = forged.allowed + finally: + if registry is not None: + registry.pop(id(forged), None) + + def test_governed_evaluator_remains_the_decision_issuance_path() -> None: """Matching issued request and policy evidence still produce one allow decision.""" policy = PurposeBoundAccessPolicy( From fc4fb5c330f10bfd1eb0f8be27420d0703790aa1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:52:31 +0900 Subject: [PATCH 043/241] test(authz): model read-only registry repair --- .../test_authorization_decision_issuance_provenance.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py index 686a786d8..bf0f5c50c 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -91,13 +91,19 @@ def test_module_registry_insertion_cannot_mint_forged_decision() -> None: next_action="Continue with only the authorized fields.", ) registry = getattr(authorization_module, "_DECISION_SNAPSHOT_REGISTRY", None) + inserted = False try: if registry is not None: - registry[id(forged)] = (weakref.ref(forged), snapshot) + try: + registry[id(forged)] = (weakref.ref(forged), snapshot) + except TypeError: + pass + else: + inserted = True with pytest.raises(ValueError, match="was not issued by purpose-bound evaluation"): _ = forged.allowed finally: - if registry is not None: + if inserted: registry.pop(id(forged), None) From 3ea988edf6bf50d15765784b8b85725d055f4af5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:54:09 +0900 Subject: [PATCH 044/241] fix(authz): hide decision registry mutation capability --- .../authorization.py | 183 ++++++++++-------- 1 file changed, 97 insertions(+), 86 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index e27660554..0e244c261 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -12,6 +12,7 @@ from dataclasses import dataclass import re +from types import MappingProxyType from typing import NamedTuple import weakref from uuid import UUID @@ -161,10 +162,6 @@ class _RequestSnapshot(NamedTuple): int, tuple[weakref.ReferenceType[object], _RequestSnapshot], ] = {} -_DECISION_SNAPSHOT_REGISTRY: dict[ - int, - tuple[weakref.ReferenceType[object], tuple[object, ...]], -] = {} _POLICY_CONSTRUCTION_IDS: set[int] = set() _REQUEST_CONSTRUCTION_IDS: set[int] = set() @@ -470,11 +467,11 @@ def _validated_decision_snapshot( class AuthorizationDecision: """PII-minimized authorization evidence with detached, structurally immutable state. - Validated values live in a module-owned snapshot rather than writable instance - slots. In particular the tenant UUID is stored as its integer value and rebuilt - on access, so a later low-level mutation of the caller's UUID cannot rewrite - already-issued authorization evidence. The public constructor is intentionally - non-authoritative: only the module-owned purpose-bound evaluator may mint an + Validated values live in evaluator-private snapshot storage rather than writable + instance slots. In particular the tenant UUID is stored as its integer value + and rebuilt on access, so a later low-level mutation of the caller's UUID cannot + rewrite already-issued authorization evidence. The public constructor is + intentionally non-authoritative: only purpose-bound evaluation may mint an issued decision. """ @@ -510,20 +507,19 @@ def __init__( next_action: str, ) -> None: """Reject direct construction; only the evaluator may register evidence.""" - if id(self) in _DECISION_SNAPSHOT_REGISTRY: - raise TypeError("AuthorizationDecision is already initialized") - raise TypeError("AuthorizationDecision must be issued by purpose-bound evaluation") + try: + _decision_snapshot_for(self) + except ValueError: + raise TypeError("AuthorizationDecision must be issued by purpose-bound evaluation") from None + raise TypeError("AuthorizationDecision is already initialized") def __init_subclass__(cls, **kwargs: object) -> None: """Seal the evidence type so subclasses cannot override validation hooks.""" raise TypeError("AuthorizationDecision must not be subclassed") def _snapshot(self) -> tuple[object, ...]: - """Return the issued snapshot or fail closed for low-level forged instances.""" - entry = _DECISION_SNAPSHOT_REGISTRY.get(id(self)) - if entry is None or entry[0]() is not self: - raise ValueError("AuthorizationDecision was not issued by purpose-bound evaluation") - return entry[1] + """Return evaluator-issued state or fail closed for low-level forged instances.""" + return _decision_snapshot_for(self) @property def allowed(self) -> bool: @@ -636,75 +632,90 @@ def _decision( raise TypeError("decision issuance is internal to evaluate_purpose_bound_access") -def evaluate_purpose_bound_access( - *, - request: PurposeBoundAccessRequest, - policy: PurposeBoundAccessPolicy, -) -> AuthorizationDecision: - """Evaluate tenant, resource, purpose, operation, scope, and field attributes. - - The evaluator binds both inputs to their validated creation-time snapshots - before comparing any authorization attribute. A frozen dataclass is not - treated as a security boundary because ``object.__setattr__`` can still write - its slots; any post-construction rewrite fails closed and the evaluator then - uses only the detached snapshots. Decision issuance is local to this evaluator, - so caller-constructible snapshot values cannot reach the issuance registry. - """ - if type(request) is not PurposeBoundAccessRequest: - raise TypeError("request must be a PurposeBoundAccessRequest") - if type(policy) is not PurposeBoundAccessPolicy: - raise TypeError("policy must be a PurposeBoundAccessPolicy") - - request_snapshot = _issued_request_snapshot(request) - policy_snapshot = _issued_policy_snapshot(policy) - - def issue_decision(*, allowed: bool, reason_code: str) -> AuthorizationDecision: - """Register one decision from the already-issued policy/request snapshots.""" - authorized_fields = request_snapshot.requested_fields if allowed else frozenset() - next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] - snapshot = _validated_decision_snapshot( - allowed=allowed, - tenant_record_id=UUID(int=request_snapshot.tenant_record_id_int), - actor_reference=request_snapshot.actor_reference, - resource_reference=request_snapshot.resource_reference, - policy_version_code=policy_snapshot.policy_version_code, - purpose_code=request_snapshot.purpose_code, - operation_code=request_snapshot.operation_code, - resource_kind=request_snapshot.resource_kind, - requested_fields=request_snapshot.requested_fields, - authorized_fields=authorized_fields, - reason_code=reason_code, - next_action=next_action, - ) - decision = object.__new__(AuthorizationDecision) - key = id(decision) - reference = weakref.ref( - decision, - lambda _reference, evidence_key=key: _DECISION_SNAPSHOT_REGISTRY.pop( - evidence_key, - None, - ), - ) - _DECISION_SNAPSHOT_REGISTRY[key] = (reference, snapshot) - return decision - - if ( - request_snapshot.tenant_record_id_int != policy_snapshot.tenant_record_id_int - or request_snapshot.actor_tenant_record_id_int != policy_snapshot.tenant_record_id_int - or request_snapshot.resource_tenant_record_id_int != policy_snapshot.tenant_record_id_int - ): - return issue_decision(allowed=False, reason_code="tenant_scope_mismatch") - if request_snapshot.resource_kind != policy_snapshot.resource_kind: - return issue_decision(allowed=False, reason_code="resource_not_allowed") - if request_snapshot.purpose_code != policy_snapshot.purpose_code: - return issue_decision(allowed=False, reason_code="purpose_not_allowed") - if request_snapshot.operation_code != policy_snapshot.operation_code: - return issue_decision(allowed=False, reason_code="operation_not_allowed") - if policy_snapshot.required_scope_code not in request_snapshot.granted_scope_codes: - return issue_decision(allowed=False, reason_code="required_scope_missing") - if not request_snapshot.requested_fields.issubset(policy_snapshot.permitted_fields): - return issue_decision(allowed=False, reason_code="field_not_allowed") - return issue_decision(allowed=True, reason_code="access_permitted") +def _build_decision_runtime() -> tuple[ + object, + object, + object, +]: + """Create read-only registry visibility plus evaluator-private mutation authority.""" + registry: dict[ + int, + tuple[weakref.ReferenceType[object], tuple[object, ...]], + ] = {} + registry_view = MappingProxyType(registry) + + def decision_snapshot_for(decision: AuthorizationDecision) -> tuple[object, ...]: + """Return only state registered by this evaluator runtime.""" + entry = registry.get(id(decision)) + if entry is None or entry[0]() is not decision: + raise ValueError("AuthorizationDecision was not issued by purpose-bound evaluation") + return entry[1] + + def evaluate( + *, + request: PurposeBoundAccessRequest, + policy: PurposeBoundAccessPolicy, + ) -> AuthorizationDecision: + """Evaluate tenant, resource, purpose, operation, scope, and field attributes.""" + if type(request) is not PurposeBoundAccessRequest: + raise TypeError("request must be a PurposeBoundAccessRequest") + if type(policy) is not PurposeBoundAccessPolicy: + raise TypeError("policy must be a PurposeBoundAccessPolicy") + + request_snapshot = _issued_request_snapshot(request) + policy_snapshot = _issued_policy_snapshot(policy) + + def issue_decision(*, allowed: bool, reason_code: str) -> AuthorizationDecision: + """Register one decision from already-issued policy/request snapshots.""" + authorized_fields = request_snapshot.requested_fields if allowed else frozenset() + next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] + snapshot = _validated_decision_snapshot( + allowed=allowed, + tenant_record_id=UUID(int=request_snapshot.tenant_record_id_int), + actor_reference=request_snapshot.actor_reference, + resource_reference=request_snapshot.resource_reference, + policy_version_code=policy_snapshot.policy_version_code, + purpose_code=request_snapshot.purpose_code, + operation_code=request_snapshot.operation_code, + resource_kind=request_snapshot.resource_kind, + requested_fields=request_snapshot.requested_fields, + authorized_fields=authorized_fields, + reason_code=reason_code, + next_action=next_action, + ) + decision = object.__new__(AuthorizationDecision) + key = id(decision) + reference = weakref.ref( + decision, + lambda _reference, evidence_key=key: registry.pop(evidence_key, None), + ) + registry[key] = (reference, snapshot) + return decision + + if ( + request_snapshot.tenant_record_id_int != policy_snapshot.tenant_record_id_int + or request_snapshot.actor_tenant_record_id_int != policy_snapshot.tenant_record_id_int + or request_snapshot.resource_tenant_record_id_int != policy_snapshot.tenant_record_id_int + ): + return issue_decision(allowed=False, reason_code="tenant_scope_mismatch") + if request_snapshot.resource_kind != policy_snapshot.resource_kind: + return issue_decision(allowed=False, reason_code="resource_not_allowed") + if request_snapshot.purpose_code != policy_snapshot.purpose_code: + return issue_decision(allowed=False, reason_code="purpose_not_allowed") + if request_snapshot.operation_code != policy_snapshot.operation_code: + return issue_decision(allowed=False, reason_code="operation_not_allowed") + if policy_snapshot.required_scope_code not in request_snapshot.granted_scope_codes: + return issue_decision(allowed=False, reason_code="required_scope_missing") + if not request_snapshot.requested_fields.issubset(policy_snapshot.permitted_fields): + return issue_decision(allowed=False, reason_code="field_not_allowed") + return issue_decision(allowed=True, reason_code="access_permitted") + + return registry_view, decision_snapshot_for, evaluate + + +_DECISION_SNAPSHOT_REGISTRY, _decision_snapshot_for, evaluate_purpose_bound_access = ( + _build_decision_runtime() +) def require_purpose_bound_access( From 05d54aff528c6bfa9d0e23199e1297608a33e70e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:59:31 +0900 Subject: [PATCH 045/241] test(authz): hide decision registry from module consumers --- .../tests/test_authorization_decision_issuance_provenance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py index bf0f5c50c..a662c4d3f 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -106,6 +106,8 @@ def test_module_registry_insertion_cannot_mint_forged_decision() -> None: if inserted: registry.pop(id(forged), None) + assert registry is None + def test_governed_evaluator_remains_the_decision_issuance_path() -> None: """Matching issued request and policy evidence still produce one allow decision.""" From a5d598300cbc804f71a17bf5c0aacb899ded00ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:01:13 +0900 Subject: [PATCH 046/241] fix(authz): remove module-visible decision registry --- .../orgmetra_keyverse_adapter/authorization.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 0e244c261..16fbaa9d5 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -10,9 +10,9 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass import re -from types import MappingProxyType from typing import NamedTuple import weakref from uuid import UUID @@ -633,16 +633,14 @@ def _decision( def _build_decision_runtime() -> tuple[ - object, - object, - object, + Callable[[AuthorizationDecision], tuple[object, ...]], + Callable[..., AuthorizationDecision], ]: - """Create read-only registry visibility plus evaluator-private mutation authority.""" + """Create evaluator-private decision storage and expose only read/evaluate closures.""" registry: dict[ int, tuple[weakref.ReferenceType[object], tuple[object, ...]], ] = {} - registry_view = MappingProxyType(registry) def decision_snapshot_for(decision: AuthorizationDecision) -> tuple[object, ...]: """Return only state registered by this evaluator runtime.""" @@ -710,12 +708,10 @@ def issue_decision(*, allowed: bool, reason_code: str) -> AuthorizationDecision: return issue_decision(allowed=False, reason_code="field_not_allowed") return issue_decision(allowed=True, reason_code="access_permitted") - return registry_view, decision_snapshot_for, evaluate + return decision_snapshot_for, evaluate -_DECISION_SNAPSHOT_REGISTRY, _decision_snapshot_for, evaluate_purpose_bound_access = ( - _build_decision_runtime() -) +_decision_snapshot_for, evaluate_purpose_bound_access = _build_decision_runtime() def require_purpose_bound_access( From b190b79ce6acffa0d432b1c3e7c8c375250fd9aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:19:14 +0900 Subject: [PATCH 047/241] test(authz): reject module-level input issuance capability forgery --- ...ation_input_issuance_capability_privacy.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py diff --git a/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py b/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py new file mode 100644 index 000000000..d1d0744ac --- /dev/null +++ b/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py @@ -0,0 +1,130 @@ +"""Regressions for policy/request issuance capability privacy.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +import orgmetra_keyverse_adapter.authorization as authorization +from orgmetra_keyverse_adapter.authorization import ( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000501") + + +def _policy() -> PurposeBoundAccessPolicy: + """Build one legitimately constructor-issued narrow policy.""" + return PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="people_pii_v1", + resource_kind="person_record", + purpose_code="hr_operations", + operation_code="read_person_pii", + required_scope_code="orgmetra.people.read", + permitted_fields=frozenset({"work_email"}), + ) + + +def _request() -> PurposeBoundAccessRequest: + """Build one legitimately constructor-issued narrow request.""" + return PurposeBoundAccessRequest( + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference="keyverse_subject:sub_jordan_hale", + resource_reference="person_record:per_01J5EXACTTARGET", + purpose_code="hr_operations", + operation_code="read_person_pii", + resource_kind="person_record", + requested_fields=frozenset({"work_email"}), + granted_scope_codes=frozenset({"orgmetra.people.read"}), + ) + + +def _forged_policy() -> PurposeBoundAccessPolicy: + """Allocate valid-looking exact policy fields without its constructor.""" + policy = object.__new__(PurposeBoundAccessPolicy) + object.__setattr__(policy, "tenant_record_id", TENANT) + object.__setattr__(policy, "policy_version_code", "people_pii_v1") + object.__setattr__(policy, "resource_kind", "person_record") + object.__setattr__(policy, "purpose_code", "hr_operations") + object.__setattr__(policy, "operation_code", "read_person_pii") + object.__setattr__(policy, "required_scope_code", "orgmetra.people.read") + object.__setattr__(policy, "permitted_fields", frozenset({"work_email"})) + return policy + + +def _forged_request() -> PurposeBoundAccessRequest: + """Allocate valid-looking exact request fields without its constructor.""" + request = object.__new__(PurposeBoundAccessRequest) + object.__setattr__(request, "tenant_record_id", TENANT) + object.__setattr__(request, "actor_tenant_record_id", TENANT) + object.__setattr__(request, "resource_tenant_record_id", TENANT) + object.__setattr__(request, "actor_reference", "keyverse_subject:sub_jordan_hale") + object.__setattr__(request, "resource_reference", "person_record:per_01J5EXACTTARGET") + object.__setattr__(request, "purpose_code", "hr_operations") + object.__setattr__(request, "operation_code", "read_person_pii") + object.__setattr__(request, "resource_kind", "person_record") + object.__setattr__(request, "requested_fields", frozenset({"work_email"})) + object.__setattr__(request, "granted_scope_codes", frozenset({"orgmetra.people.read"})) + return request + + +def test_module_consumer_cannot_activate_forged_policy_through_construction_state() -> None: + """Mutable module construction state must not mint policy authority.""" + policy = _forged_policy() + construction_ids = getattr(authorization, "_POLICY_CONSTRUCTION_IDS", None) + registry = getattr(authorization, "_POLICY_SNAPSHOT_REGISTRY", None) + + try: + if construction_ids is not None: + construction_ids.add(id(policy)) + with pytest.raises(TypeError, match="must be initialized through its constructor"): + PurposeBoundAccessPolicy.__post_init__(policy) + finally: + if construction_ids is not None: + construction_ids.discard(id(policy)) + if registry is not None: + registry.pop(id(policy), None) + + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + evaluate_purpose_bound_access(request=_request(), policy=policy) + + +def test_module_consumer_cannot_activate_forged_request_through_construction_state() -> None: + """Mutable module construction state must not mint request authority.""" + request = _forged_request() + construction_ids = getattr(authorization, "_REQUEST_CONSTRUCTION_IDS", None) + registry = getattr(authorization, "_REQUEST_SNAPSHOT_REGISTRY", None) + + try: + if construction_ids is not None: + construction_ids.add(id(request)) + with pytest.raises(TypeError, match="must be initialized through its constructor"): + PurposeBoundAccessRequest.__post_init__(request) + finally: + if construction_ids is not None: + construction_ids.discard(id(request)) + if registry is not None: + registry.pop(id(request), None) + + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + evaluate_purpose_bound_access(request=request, policy=_policy()) + + +@pytest.mark.parametrize( + "attribute_name", + ( + "_POLICY_SNAPSHOT_REGISTRY", + "_REQUEST_SNAPSHOT_REGISTRY", + "_POLICY_CONSTRUCTION_IDS", + "_REQUEST_CONSTRUCTION_IDS", + ), +) +def test_module_does_not_expose_writable_input_issuance_capabilities(attribute_name: str) -> None: + """Policy/request issuance mutation capability must remain closure-private.""" + assert not hasattr(authorization, attribute_name) From 52e3940cacc3d64e72ef81cc800253936e2771e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:22:46 +0900 Subject: [PATCH 048/241] fix(authz): privatize input issuance capabilities --- .../authorization.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 16fbaa9d5..7d88a33ae 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -406,6 +406,154 @@ def _issued_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnap return entry[1] +def _privatize_input_issuance_runtime() -> tuple[ + Callable[[PurposeBoundAccessPolicy], _PolicySnapshot], + Callable[[PurposeBoundAccessRequest], _RequestSnapshot], +]: + """Move policy/request issuance mutation state behind constructor-bound closures.""" + policy_registry = _POLICY_SNAPSHOT_REGISTRY + request_registry = _REQUEST_SNAPSHOT_REGISTRY + policy_construction_ids = _POLICY_CONSTRUCTION_IDS + request_construction_ids = _REQUEST_CONSTRUCTION_IDS + + def policy_init( + self: PurposeBoundAccessPolicy, + *, + tenant_record_id: UUID, + policy_version_code: str, + resource_kind: str, + purpose_code: str, + operation_code: str, + required_scope_code: str, + permitted_fields: frozenset[str], + ) -> None: + """Write fields and issue authority only inside this constructor call.""" + key = id(self) + if key in policy_registry: + raise TypeError("PurposeBoundAccessPolicy is already initialized") + if key in policy_construction_ids: + raise TypeError("PurposeBoundAccessPolicy construction is already in progress") + policy_construction_ids.add(key) + try: + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "policy_version_code", policy_version_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "required_scope_code", required_scope_code) + object.__setattr__(self, "permitted_fields", permitted_fields) + self.__post_init__() + finally: + policy_construction_ids.discard(key) + + def policy_post_init(self: PurposeBoundAccessPolicy) -> None: + """Issue a policy snapshot only while its private constructor state is active.""" + key = id(self) + if key not in policy_construction_ids: + raise TypeError("PurposeBoundAccessPolicy must be initialized through its constructor") + snapshot = _validated_policy_snapshot(self) + if key in policy_registry: + raise TypeError("PurposeBoundAccessPolicy is already initialized") + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: policy_registry.pop(evidence_key, None), + ) + policy_registry[key] = (reference, snapshot) + + def request_init( + self: PurposeBoundAccessRequest, + *, + tenant_record_id: UUID, + actor_tenant_record_id: UUID, + resource_tenant_record_id: UUID, + actor_reference: str, + resource_reference: str, + purpose_code: str, + operation_code: str, + resource_kind: str, + requested_fields: frozenset[str], + granted_scope_codes: frozenset[str], + ) -> None: + """Write fields and issue authority only inside this constructor call.""" + key = id(self) + if key in request_registry: + raise TypeError("PurposeBoundAccessRequest is already initialized") + if key in request_construction_ids: + raise TypeError("PurposeBoundAccessRequest construction is already in progress") + request_construction_ids.add(key) + try: + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) + object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) + object.__setattr__(self, "actor_reference", actor_reference) + object.__setattr__(self, "resource_reference", resource_reference) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "requested_fields", requested_fields) + object.__setattr__(self, "granted_scope_codes", granted_scope_codes) + self.__post_init__() + finally: + request_construction_ids.discard(key) + + def request_post_init(self: PurposeBoundAccessRequest) -> None: + """Issue a request snapshot only while its private constructor state is active.""" + key = id(self) + if key not in request_construction_ids: + raise TypeError("PurposeBoundAccessRequest must be initialized through its constructor") + snapshot = _validated_request_snapshot(self) + if key in request_registry: + raise TypeError("PurposeBoundAccessRequest is already initialized") + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: request_registry.pop(evidence_key, None), + ) + request_registry[key] = (reference, snapshot) + + def issued_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: + """Return creation-time policy authority only when live fields still match it.""" + entry = policy_registry.get(id(policy)) + if entry is None or entry[0]() is not policy: + raise ValueError("PurposeBoundAccessPolicy was not issued by the validated constructor") + current = _validated_policy_snapshot(policy) + if current != entry[1]: + raise ValueError("PurposeBoundAccessPolicy changed after validation") + return entry[1] + + def issued_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnapshot: + """Return creation-time request authority only when live fields still match it.""" + entry = request_registry.get(id(request)) + if entry is None or entry[0]() is not request: + raise ValueError("PurposeBoundAccessRequest was not issued by the validated constructor") + current = _validated_request_snapshot(request) + if current != entry[1]: + raise ValueError("PurposeBoundAccessRequest changed after validation") + return entry[1] + + policy_init.__name__ = "__init__" + policy_init.__qualname__ = "PurposeBoundAccessPolicy.__init__" + policy_post_init.__name__ = "__post_init__" + policy_post_init.__qualname__ = "PurposeBoundAccessPolicy.__post_init__" + request_init.__name__ = "__init__" + request_init.__qualname__ = "PurposeBoundAccessRequest.__init__" + request_post_init.__name__ = "__post_init__" + request_post_init.__qualname__ = "PurposeBoundAccessRequest.__post_init__" + + PurposeBoundAccessPolicy.__init__ = policy_init # type: ignore[method-assign] + PurposeBoundAccessPolicy.__post_init__ = policy_post_init # type: ignore[method-assign] + PurposeBoundAccessRequest.__init__ = request_init # type: ignore[method-assign] + PurposeBoundAccessRequest.__post_init__ = request_post_init # type: ignore[method-assign] + return issued_policy_snapshot, issued_request_snapshot + + +_issued_policy_snapshot, _issued_request_snapshot = _privatize_input_issuance_runtime() +del _POLICY_SNAPSHOT_REGISTRY +del _REQUEST_SNAPSHOT_REGISTRY +del _POLICY_CONSTRUCTION_IDS +del _REQUEST_CONSTRUCTION_IDS +del _privatize_input_issuance_runtime + + def _validated_decision_snapshot( *, allowed: object, From 611ebe82225c67a477260f4725743bedb6f91560 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:25:34 +0900 Subject: [PATCH 049/241] refactor(authz): make input issuance closure-native --- .../authorization.py | 461 +++++++----------- 1 file changed, 164 insertions(+), 297 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 7d88a33ae..f3b5d9e98 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -154,161 +154,6 @@ class _RequestSnapshot(NamedTuple): granted_scope_codes: frozenset[str] -_POLICY_SNAPSHOT_REGISTRY: dict[ - int, - tuple[weakref.ReferenceType[object], _PolicySnapshot], -] = {} -_REQUEST_SNAPSHOT_REGISTRY: dict[ - int, - tuple[weakref.ReferenceType[object], _RequestSnapshot], -] = {} -_POLICY_CONSTRUCTION_IDS: set[int] = set() -_REQUEST_CONSTRUCTION_IDS: set[int] = set() - - -@dataclass(frozen=True, slots=True, weakref_slot=True, init=False) -class PurposeBoundAccessPolicy: - """One tenant-local field policy for one purpose, resource, and operation. - - A policy intentionally has no wildcard form. Separate purposes, operations, - or resources require separate reviewed policy records so a broad token cannot - silently widen access to necessary HR PII. - """ - - tenant_record_id: UUID - policy_version_code: str - resource_kind: str - purpose_code: str - operation_code: str - required_scope_code: str - permitted_fields: frozenset[str] - - def __init__( - self, - *, - tenant_record_id: UUID, - policy_version_code: str, - resource_kind: str, - purpose_code: str, - operation_code: str, - required_scope_code: str, - permitted_fields: frozenset[str], - ) -> None: - """Write fields and issue authority only inside this constructor call.""" - key = id(self) - if key in _POLICY_SNAPSHOT_REGISTRY: - raise TypeError("PurposeBoundAccessPolicy is already initialized") - if key in _POLICY_CONSTRUCTION_IDS: - raise TypeError("PurposeBoundAccessPolicy construction is already in progress") - _POLICY_CONSTRUCTION_IDS.add(key) - try: - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "policy_version_code", policy_version_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "required_scope_code", required_scope_code) - object.__setattr__(self, "permitted_fields", permitted_fields) - self.__post_init__() - finally: - _POLICY_CONSTRUCTION_IDS.discard(key) - - def __post_init__(self) -> None: - """Issue a validated snapshot only while the governed constructor is active.""" - key = id(self) - if key not in _POLICY_CONSTRUCTION_IDS: - raise TypeError("PurposeBoundAccessPolicy must be initialized through its constructor") - snapshot = _validated_policy_snapshot(self) - if key in _POLICY_SNAPSHOT_REGISTRY: - raise TypeError("PurposeBoundAccessPolicy is already initialized") - reference = weakref.ref( - self, - lambda _reference, evidence_key=key: _POLICY_SNAPSHOT_REGISTRY.pop( - evidence_key, - None, - ), - ) - _POLICY_SNAPSHOT_REGISTRY[key] = (reference, snapshot) - - -@dataclass(frozen=True, slots=True, weakref_slot=True, init=False) -class PurposeBoundAccessRequest: - """PII access attributes resolved before any protected field is returned. - - ``actor_tenant_record_id`` comes from the authenticated identity binding, - ``tenant_record_id`` is the active Orgmetra request context, and - ``resource_tenant_record_id`` comes from the target record identity. The - opaque ``resource_reference`` identifies that exact target for audit - correlation without copying its PII. All tenant identifiers must match the - policy tenant. Only field names are carried here; field values remain behind - the authoritative data boundary until access is allowed. - """ - - tenant_record_id: UUID - actor_tenant_record_id: UUID - resource_tenant_record_id: UUID - actor_reference: str - resource_reference: str - purpose_code: str - operation_code: str - resource_kind: str - requested_fields: frozenset[str] - granted_scope_codes: frozenset[str] - - def __init__( - self, - *, - tenant_record_id: UUID, - actor_tenant_record_id: UUID, - resource_tenant_record_id: UUID, - actor_reference: str, - resource_reference: str, - purpose_code: str, - operation_code: str, - resource_kind: str, - requested_fields: frozenset[str], - granted_scope_codes: frozenset[str], - ) -> None: - """Write fields and issue authority only inside this constructor call.""" - key = id(self) - if key in _REQUEST_SNAPSHOT_REGISTRY: - raise TypeError("PurposeBoundAccessRequest is already initialized") - if key in _REQUEST_CONSTRUCTION_IDS: - raise TypeError("PurposeBoundAccessRequest construction is already in progress") - _REQUEST_CONSTRUCTION_IDS.add(key) - try: - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) - object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) - object.__setattr__(self, "actor_reference", actor_reference) - object.__setattr__(self, "resource_reference", resource_reference) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "requested_fields", requested_fields) - object.__setattr__(self, "granted_scope_codes", granted_scope_codes) - self.__post_init__() - finally: - _REQUEST_CONSTRUCTION_IDS.discard(key) - - def __post_init__(self) -> None: - """Issue a validated snapshot only while the governed constructor is active.""" - key = id(self) - if key not in _REQUEST_CONSTRUCTION_IDS: - raise TypeError("PurposeBoundAccessRequest must be initialized through its constructor") - snapshot = _validated_request_snapshot(self) - if key in _REQUEST_SNAPSHOT_REGISTRY: - raise TypeError("PurposeBoundAccessRequest is already initialized") - reference = weakref.ref( - self, - lambda _reference, evidence_key=key: _REQUEST_SNAPSHOT_REGISTRY.pop( - evidence_key, - None, - ), - ) - _REQUEST_SNAPSHOT_REGISTRY[key] = (reference, snapshot) - - def _validated_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: """Read, validate, and detach one complete policy snapshot.""" tenant_record_id = policy.tenant_record_id @@ -384,131 +229,158 @@ def _validated_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestS ) -def _issued_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: - """Return creation-time policy authority only when live fields still match it.""" - entry = _POLICY_SNAPSHOT_REGISTRY.get(id(policy)) - if entry is None or entry[0]() is not policy: - raise ValueError("PurposeBoundAccessPolicy was not issued by the validated constructor") - current = _validated_policy_snapshot(policy) - if current != entry[1]: - raise ValueError("PurposeBoundAccessPolicy changed after validation") - return entry[1] - - -def _issued_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnapshot: - """Return creation-time request authority only when live fields still match it.""" - entry = _REQUEST_SNAPSHOT_REGISTRY.get(id(request)) - if entry is None or entry[0]() is not request: - raise ValueError("PurposeBoundAccessRequest was not issued by the validated constructor") - current = _validated_request_snapshot(request) - if current != entry[1]: - raise ValueError("PurposeBoundAccessRequest changed after validation") - return entry[1] - - -def _privatize_input_issuance_runtime() -> tuple[ +def _build_input_issuance_runtime() -> tuple[ + type[PurposeBoundAccessPolicy], + type[PurposeBoundAccessRequest], Callable[[PurposeBoundAccessPolicy], _PolicySnapshot], Callable[[PurposeBoundAccessRequest], _RequestSnapshot], ]: - """Move policy/request issuance mutation state behind constructor-bound closures.""" - policy_registry = _POLICY_SNAPSHOT_REGISTRY - request_registry = _REQUEST_SNAPSHOT_REGISTRY - policy_construction_ids = _POLICY_CONSTRUCTION_IDS - request_construction_ids = _REQUEST_CONSTRUCTION_IDS - - def policy_init( - self: PurposeBoundAccessPolicy, - *, - tenant_record_id: UUID, - policy_version_code: str, - resource_kind: str, - purpose_code: str, - operation_code: str, - required_scope_code: str, - permitted_fields: frozenset[str], - ) -> None: - """Write fields and issue authority only inside this constructor call.""" - key = id(self) - if key in policy_registry: - raise TypeError("PurposeBoundAccessPolicy is already initialized") - if key in policy_construction_ids: - raise TypeError("PurposeBoundAccessPolicy construction is already in progress") - policy_construction_ids.add(key) - try: - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "policy_version_code", policy_version_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "required_scope_code", required_scope_code) - object.__setattr__(self, "permitted_fields", permitted_fields) - self.__post_init__() - finally: - policy_construction_ids.discard(key) - - def policy_post_init(self: PurposeBoundAccessPolicy) -> None: - """Issue a policy snapshot only while its private constructor state is active.""" - key = id(self) - if key not in policy_construction_ids: - raise TypeError("PurposeBoundAccessPolicy must be initialized through its constructor") - snapshot = _validated_policy_snapshot(self) - if key in policy_registry: - raise TypeError("PurposeBoundAccessPolicy is already initialized") - reference = weakref.ref( + """Create policy/request classes whose issuance mutation state is closure-private.""" + policy_registry: dict[ + int, + tuple[weakref.ReferenceType[object], _PolicySnapshot], + ] = {} + request_registry: dict[ + int, + tuple[weakref.ReferenceType[object], _RequestSnapshot], + ] = {} + policy_construction_ids: set[int] = set() + request_construction_ids: set[int] = set() + + @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) + class PurposeBoundAccessPolicy: + """One tenant-local field policy for one purpose, resource, and operation. + + A policy intentionally has no wildcard form. Separate purposes, operations, + or resources require separate reviewed policy records so a broad token cannot + silently widen access to necessary HR PII. + """ + + tenant_record_id: UUID + policy_version_code: str + resource_kind: str + purpose_code: str + operation_code: str + required_scope_code: str + permitted_fields: frozenset[str] + + def __init__( self, - lambda _reference, evidence_key=key: policy_registry.pop(evidence_key, None), - ) - policy_registry[key] = (reference, snapshot) - - def request_init( - self: PurposeBoundAccessRequest, - *, - tenant_record_id: UUID, - actor_tenant_record_id: UUID, - resource_tenant_record_id: UUID, - actor_reference: str, - resource_reference: str, - purpose_code: str, - operation_code: str, - resource_kind: str, - requested_fields: frozenset[str], - granted_scope_codes: frozenset[str], - ) -> None: - """Write fields and issue authority only inside this constructor call.""" - key = id(self) - if key in request_registry: - raise TypeError("PurposeBoundAccessRequest is already initialized") - if key in request_construction_ids: - raise TypeError("PurposeBoundAccessRequest construction is already in progress") - request_construction_ids.add(key) - try: - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) - object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) - object.__setattr__(self, "actor_reference", actor_reference) - object.__setattr__(self, "resource_reference", resource_reference) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "requested_fields", requested_fields) - object.__setattr__(self, "granted_scope_codes", granted_scope_codes) - self.__post_init__() - finally: - request_construction_ids.discard(key) - - def request_post_init(self: PurposeBoundAccessRequest) -> None: - """Issue a request snapshot only while its private constructor state is active.""" - key = id(self) - if key not in request_construction_ids: - raise TypeError("PurposeBoundAccessRequest must be initialized through its constructor") - snapshot = _validated_request_snapshot(self) - if key in request_registry: - raise TypeError("PurposeBoundAccessRequest is already initialized") - reference = weakref.ref( + *, + tenant_record_id: UUID, + policy_version_code: str, + resource_kind: str, + purpose_code: str, + operation_code: str, + required_scope_code: str, + permitted_fields: frozenset[str], + ) -> None: + """Write fields and issue authority only inside this constructor call.""" + key = id(self) + if key in policy_registry: + raise TypeError("PurposeBoundAccessPolicy is already initialized") + if key in policy_construction_ids: + raise TypeError("PurposeBoundAccessPolicy construction is already in progress") + policy_construction_ids.add(key) + try: + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "policy_version_code", policy_version_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "required_scope_code", required_scope_code) + object.__setattr__(self, "permitted_fields", permitted_fields) + self.__post_init__() + finally: + policy_construction_ids.discard(key) + + def __post_init__(self) -> None: + """Issue a validated snapshot only while the governed constructor is active.""" + key = id(self) + if key not in policy_construction_ids: + raise TypeError("PurposeBoundAccessPolicy must be initialized through its constructor") + snapshot = _validated_policy_snapshot(self) + if key in policy_registry: + raise TypeError("PurposeBoundAccessPolicy is already initialized") + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: policy_registry.pop(evidence_key, None), + ) + policy_registry[key] = (reference, snapshot) + + @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) + class PurposeBoundAccessRequest: + """PII access attributes resolved before any protected field is returned. + + ``actor_tenant_record_id`` comes from the authenticated identity binding, + ``tenant_record_id`` is the active Orgmetra request context, and + ``resource_tenant_record_id`` comes from the target record identity. The + opaque ``resource_reference`` identifies that exact target for audit + correlation without copying its PII. All tenant identifiers must match the + policy tenant. Only field names are carried here; field values remain behind + the authoritative data boundary until access is allowed. + """ + + tenant_record_id: UUID + actor_tenant_record_id: UUID + resource_tenant_record_id: UUID + actor_reference: str + resource_reference: str + purpose_code: str + operation_code: str + resource_kind: str + requested_fields: frozenset[str] + granted_scope_codes: frozenset[str] + + def __init__( self, - lambda _reference, evidence_key=key: request_registry.pop(evidence_key, None), - ) - request_registry[key] = (reference, snapshot) + *, + tenant_record_id: UUID, + actor_tenant_record_id: UUID, + resource_tenant_record_id: UUID, + actor_reference: str, + resource_reference: str, + purpose_code: str, + operation_code: str, + resource_kind: str, + requested_fields: frozenset[str], + granted_scope_codes: frozenset[str], + ) -> None: + """Write fields and issue authority only inside this constructor call.""" + key = id(self) + if key in request_registry: + raise TypeError("PurposeBoundAccessRequest is already initialized") + if key in request_construction_ids: + raise TypeError("PurposeBoundAccessRequest construction is already in progress") + request_construction_ids.add(key) + try: + object.__setattr__(self, "tenant_record_id", tenant_record_id) + object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) + object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) + object.__setattr__(self, "actor_reference", actor_reference) + object.__setattr__(self, "resource_reference", resource_reference) + object.__setattr__(self, "purpose_code", purpose_code) + object.__setattr__(self, "operation_code", operation_code) + object.__setattr__(self, "resource_kind", resource_kind) + object.__setattr__(self, "requested_fields", requested_fields) + object.__setattr__(self, "granted_scope_codes", granted_scope_codes) + self.__post_init__() + finally: + request_construction_ids.discard(key) + + def __post_init__(self) -> None: + """Issue a validated snapshot only while the governed constructor is active.""" + key = id(self) + if key not in request_construction_ids: + raise TypeError("PurposeBoundAccessRequest must be initialized through its constructor") + snapshot = _validated_request_snapshot(self) + if key in request_registry: + raise TypeError("PurposeBoundAccessRequest is already initialized") + reference = weakref.ref( + self, + lambda _reference, evidence_key=key: request_registry.pop(evidence_key, None), + ) + request_registry[key] = (reference, snapshot) def issued_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: """Return creation-time policy authority only when live fields still match it.""" @@ -530,28 +402,23 @@ def issued_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnaps raise ValueError("PurposeBoundAccessRequest changed after validation") return entry[1] - policy_init.__name__ = "__init__" - policy_init.__qualname__ = "PurposeBoundAccessPolicy.__init__" - policy_post_init.__name__ = "__post_init__" - policy_post_init.__qualname__ = "PurposeBoundAccessPolicy.__post_init__" - request_init.__name__ = "__init__" - request_init.__qualname__ = "PurposeBoundAccessRequest.__init__" - request_post_init.__name__ = "__post_init__" - request_post_init.__qualname__ = "PurposeBoundAccessRequest.__post_init__" - - PurposeBoundAccessPolicy.__init__ = policy_init # type: ignore[method-assign] - PurposeBoundAccessPolicy.__post_init__ = policy_post_init # type: ignore[method-assign] - PurposeBoundAccessRequest.__init__ = request_init # type: ignore[method-assign] - PurposeBoundAccessRequest.__post_init__ = request_post_init # type: ignore[method-assign] - return issued_policy_snapshot, issued_request_snapshot - - -_issued_policy_snapshot, _issued_request_snapshot = _privatize_input_issuance_runtime() -del _POLICY_SNAPSHOT_REGISTRY -del _REQUEST_SNAPSHOT_REGISTRY -del _POLICY_CONSTRUCTION_IDS -del _REQUEST_CONSTRUCTION_IDS -del _privatize_input_issuance_runtime + PurposeBoundAccessPolicy.__qualname__ = "PurposeBoundAccessPolicy" + PurposeBoundAccessRequest.__qualname__ = "PurposeBoundAccessRequest" + return ( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + issued_policy_snapshot, + issued_request_snapshot, + ) + + +( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + _issued_policy_snapshot, + _issued_request_snapshot, +) = _build_input_issuance_runtime() +del _build_input_issuance_runtime def _validated_decision_snapshot( From b08d1e4ab4ff4d83340bb2255b3fd9602dcf7eb4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:34:40 +0900 Subject: [PATCH 050/241] test(authz): reproduce closure-cell issuance forgery --- ...ation_input_issuance_capability_privacy.py | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py b/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py index d1d0744ac..197fbd03b 100644 --- a/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py +++ b/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Callable from uuid import UUID import pytest @@ -74,6 +75,17 @@ def _forged_request() -> PurposeBoundAccessRequest: return request +def _closure_bindings(function: Callable[..., object]) -> dict[str, object]: + """Expose function cells exactly as an ordinary same-process Python consumer can.""" + cells = function.__closure__ + if cells is None: + return {} + return { + name: cell.cell_contents + for name, cell in zip(function.__code__.co_freevars, cells, strict=True) + } + + def test_module_consumer_cannot_activate_forged_policy_through_construction_state() -> None: """Mutable module construction state must not mint policy authority.""" policy = _forged_policy() @@ -116,6 +128,50 @@ def test_module_consumer_cannot_activate_forged_request_through_construction_sta evaluate_purpose_bound_access(request=request, policy=_policy()) +def test_same_process_consumer_cannot_mint_policy_by_mutating_closure_cells() -> None: + """Inspectable Python closure cells must not constitute policy issuance authority.""" + policy = _forged_policy() + bindings = _closure_bindings(PurposeBoundAccessPolicy.__post_init__) + construction_ids = bindings.get("policy_construction_ids") + registry = bindings.get("policy_registry") + + if construction_ids is not None: + construction_ids.add(id(policy)) + try: + with pytest.raises(TypeError, match="must be initialized through its constructor"): + PurposeBoundAccessPolicy.__post_init__(policy) + finally: + if construction_ids is not None: + construction_ids.discard(id(policy)) + if registry is not None: + registry.pop(id(policy), None) + + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + evaluate_purpose_bound_access(request=_request(), policy=policy) + + +def test_same_process_consumer_cannot_mint_request_by_mutating_closure_cells() -> None: + """Inspectable Python closure cells must not constitute request issuance authority.""" + request = _forged_request() + bindings = _closure_bindings(PurposeBoundAccessRequest.__post_init__) + construction_ids = bindings.get("request_construction_ids") + registry = bindings.get("request_registry") + + if construction_ids is not None: + construction_ids.add(id(request)) + try: + with pytest.raises(TypeError, match="must be initialized through its constructor"): + PurposeBoundAccessRequest.__post_init__(request) + finally: + if construction_ids is not None: + construction_ids.discard(id(request)) + if registry is not None: + registry.pop(id(request), None) + + with pytest.raises(ValueError, match="was not issued by the validated constructor"): + evaluate_purpose_bound_access(request=request, policy=_policy()) + + @pytest.mark.parametrize( "attribute_name", ( @@ -126,5 +182,5 @@ def test_module_consumer_cannot_activate_forged_request_through_construction_sta ), ) def test_module_does_not_expose_writable_input_issuance_capabilities(attribute_name: str) -> None: - """Policy/request issuance mutation capability must remain closure-private.""" + """Policy/request issuance mutation capability must not be a module attribute.""" assert not hasattr(authorization, attribute_name) From df990523b94e33a123c90688006a97e7a9d991da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:43:12 +0900 Subject: [PATCH 051/241] fix(authz): anchor authority at service trust boundary --- .../authorization.py | 812 +++++++----------- 1 file changed, 308 insertions(+), 504 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index f3b5d9e98..6c64d75af 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -1,20 +1,18 @@ """Fail-closed purpose-bound authorization at the Orgmetra Keyverse boundary. -The adapter consumes only already-authenticated Keyverse identity attributes and -Orgmetra-owned policy data. It never stores credentials and never asks Keyverse -to make an Orgmetra employment-policy decision. Authorization follows the NIST -SP 800-162 ABAC shape: subject/context, object, requested operation, and policy -attributes must all match. Purpose is one policy attribute, never a substitute -for the operation-specific token scope. +The adapter consumes authenticated Keyverse identity/scope attributes and an +Orgmetra-owned policy supplied by the trusted service composition boundary. The +Python value objects below validate and detach authorization data; they are not +unforgeable capabilities against arbitrary code already executing in the same +interpreter. Same-process arbitrary code execution is a service compromise and +belongs to deployment/workload isolation controls, not object-constructor tricks. """ from __future__ import annotations -from collections.abc import Callable from dataclasses import dataclass import re from typing import NamedTuple -import weakref from uuid import UUID _MAX_UUID_INT = (1 << 128) - 1 @@ -100,7 +98,7 @@ def _validate_version(value: object) -> None: def _validate_field_set(field_name: str, values: object) -> None: - """Require an exact immutable set of exact built-in lower snake_case fields.""" + """Require an exact immutable non-empty set of exact built-in field codes.""" if type(values) is not frozenset: raise ValueError(f"{field_name} must be a frozenset.") if not values: @@ -118,7 +116,7 @@ def _validate_authorized_field_set(values: object) -> None: def _validate_scope_set(values: object) -> None: - """Require an exact immutable set of exact built-in authenticated token scopes.""" + """Require an exact immutable non-empty set of authenticated Orgmetra scopes.""" if type(values) is not frozenset: raise ValueError("granted_scope_codes must be a frozenset.") if not values: @@ -128,7 +126,7 @@ def _validate_scope_set(values: object) -> None: class _PolicySnapshot(NamedTuple): - """Detached creation-time authority for one purpose-bound policy.""" + """Validated detached values for one trusted-source policy at evaluation time.""" tenant_record_id_int: int policy_version_code: str @@ -140,7 +138,7 @@ class _PolicySnapshot(NamedTuple): class _RequestSnapshot(NamedTuple): - """Detached creation-time authority for one purpose-bound access request.""" + """Validated detached request attributes at evaluation time.""" tenant_record_id_int: int actor_tenant_record_id_int: int @@ -154,273 +152,209 @@ class _RequestSnapshot(NamedTuple): granted_scope_codes: frozenset[str] +class _DecisionSnapshot(NamedTuple): + """Validated PII-minimized decision values at a consumer boundary.""" + + allowed: bool + tenant_record_id_int: int + actor_reference: str + resource_reference: str + policy_version_code: str + purpose_code: str + operation_code: str + resource_kind: str + requested_fields: frozenset[str] + authorized_fields: frozenset[str] + reason_code: str + next_action: str + + +@dataclass(frozen=True, slots=True) +class PurposeBoundAccessPolicy: + """One trusted-composition policy value for one tenant/resource/operation. + + Object construction is validation, not policy issuance. Production callers + must obtain this value from the Orgmetra-controlled composition/policy source; + request payloads, LLM outputs, plugins, and remote callers are not policy + authorities. + """ + + tenant_record_id: UUID + policy_version_code: str + resource_kind: str + purpose_code: str + operation_code: str + required_scope_code: str + permitted_fields: frozenset[str] + + def __post_init__(self) -> None: + """Validate policy data and detach the caller-owned UUID instance.""" + tenant_int = _validated_uuid_int("tenant_record_id", self.tenant_record_id) + _validate_version(self.policy_version_code) + _validate_resource_kind(self.resource_kind) + _validate_code("purpose_code", self.purpose_code) + _validate_code("operation_code", self.operation_code) + _validate_scope("required_scope_code", self.required_scope_code) + _validate_field_set("permitted_fields", self.permitted_fields) + object.__setattr__(self, "tenant_record_id", UUID(int=tenant_int)) + + +@dataclass(frozen=True, slots=True) +class PurposeBoundAccessRequest: + """Authorization attributes resolved from the request and authenticated identity.""" + + tenant_record_id: UUID + actor_tenant_record_id: UUID + resource_tenant_record_id: UUID + actor_reference: str + resource_reference: str + purpose_code: str + operation_code: str + resource_kind: str + requested_fields: frozenset[str] + granted_scope_codes: frozenset[str] + + def __post_init__(self) -> None: + """Validate input attributes and detach caller-owned UUID instances.""" + tenant_int = _validated_uuid_int("tenant_record_id", self.tenant_record_id) + actor_tenant_int = _validated_uuid_int( + "actor_tenant_record_id", + self.actor_tenant_record_id, + ) + resource_tenant_int = _validated_uuid_int( + "resource_tenant_record_id", + self.resource_tenant_record_id, + ) + _validate_reference("actor_reference", self.actor_reference) + _validate_resource_kind(self.resource_kind) + _validate_reference( + "resource_reference", + self.resource_reference, + expected_namespace=self.resource_kind, + ) + _validate_code("purpose_code", self.purpose_code) + _validate_code("operation_code", self.operation_code) + _validate_field_set("requested_fields", self.requested_fields) + _validate_scope_set(self.granted_scope_codes) + object.__setattr__(self, "tenant_record_id", UUID(int=tenant_int)) + object.__setattr__(self, "actor_tenant_record_id", UUID(int=actor_tenant_int)) + object.__setattr__(self, "resource_tenant_record_id", UUID(int=resource_tenant_int)) + + +@dataclass(frozen=True, slots=True) +class AuthorizationDecision: + """PII-minimized authorization decision data produced inside the service TCB. + + The type rejects dynamic subclasses and validates verdict/evidence coherence, + but it is not an unforgeable capability against arbitrary code already running + inside the service process. Persistence boundaries must treat same-process code + as trusted and still revalidate decision semantics before durable use. + """ + + allowed: bool + tenant_record_id: UUID + actor_reference: str + resource_reference: str + policy_version_code: str + purpose_code: str + operation_code: str + resource_kind: str + requested_fields: frozenset[str] + authorized_fields: frozenset[str] + reason_code: str + next_action: str + + def __post_init__(self) -> None: + """Validate decision coherence and detach the tenant UUID instance.""" + snapshot = _validated_decision_snapshot( + allowed=self.allowed, + tenant_record_id=self.tenant_record_id, + actor_reference=self.actor_reference, + resource_reference=self.resource_reference, + policy_version_code=self.policy_version_code, + purpose_code=self.purpose_code, + operation_code=self.operation_code, + resource_kind=self.resource_kind, + requested_fields=self.requested_fields, + authorized_fields=self.authorized_fields, + reason_code=self.reason_code, + next_action=self.next_action, + ) + object.__setattr__(self, "tenant_record_id", UUID(int=snapshot.tenant_record_id_int)) + + def __init_subclass__(cls, **kwargs: object) -> None: + """Prevent caller-defined decision subclasses from overriding field behavior.""" + del kwargs + raise TypeError("AuthorizationDecision must not be subclassed") + + +class AuthorizationDeniedError(PermissionError): + """A purpose-bound policy denied access and tells the caller how to recover safely.""" + + def __init__(self, decision: AuthorizationDecision) -> None: + """Preserve bounded denial metadata without including protected field values.""" + super().__init__(decision.reason_code) + self.reason_code = decision.reason_code + self.next_action = decision.next_action + self.decision = decision + + def _validated_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: - """Read, validate, and detach one complete policy snapshot.""" - tenant_record_id = policy.tenant_record_id - policy_version_code = policy.policy_version_code - resource_kind = policy.resource_kind - purpose_code = policy.purpose_code - operation_code = policy.operation_code - required_scope_code = policy.required_scope_code - permitted_fields = policy.permitted_fields - - tenant_record_id_int = _validated_uuid_int("tenant_record_id", tenant_record_id) - _validate_version(policy_version_code) - _validate_resource_kind(resource_kind) - _validate_code("purpose_code", purpose_code) - _validate_code("operation_code", operation_code) - _validate_scope("required_scope_code", required_scope_code) - _validate_field_set("permitted_fields", permitted_fields) + """Revalidate and detach one policy immediately before evaluation.""" + tenant_int = _validated_uuid_int("tenant_record_id", policy.tenant_record_id) + _validate_version(policy.policy_version_code) + _validate_resource_kind(policy.resource_kind) + _validate_code("purpose_code", policy.purpose_code) + _validate_code("operation_code", policy.operation_code) + _validate_scope("required_scope_code", policy.required_scope_code) + _validate_field_set("permitted_fields", policy.permitted_fields) return _PolicySnapshot( - tenant_record_id_int, - policy_version_code, - resource_kind, - purpose_code, - operation_code, - required_scope_code, - permitted_fields, + tenant_int, + policy.policy_version_code, + policy.resource_kind, + policy.purpose_code, + policy.operation_code, + policy.required_scope_code, + policy.permitted_fields, ) def _validated_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnapshot: - """Read, validate, and detach one complete access-request snapshot.""" - tenant_record_id = request.tenant_record_id - actor_tenant_record_id = request.actor_tenant_record_id - resource_tenant_record_id = request.resource_tenant_record_id - actor_reference = request.actor_reference - resource_reference = request.resource_reference - purpose_code = request.purpose_code - operation_code = request.operation_code - resource_kind = request.resource_kind - requested_fields = request.requested_fields - granted_scope_codes = request.granted_scope_codes - - tenant_record_id_int = _validated_uuid_int("tenant_record_id", tenant_record_id) - actor_tenant_record_id_int = _validated_uuid_int( + """Revalidate and detach request data immediately before evaluation.""" + tenant_int = _validated_uuid_int("tenant_record_id", request.tenant_record_id) + actor_tenant_int = _validated_uuid_int( "actor_tenant_record_id", - actor_tenant_record_id, + request.actor_tenant_record_id, ) - resource_tenant_record_id_int = _validated_uuid_int( + resource_tenant_int = _validated_uuid_int( "resource_tenant_record_id", - resource_tenant_record_id, + request.resource_tenant_record_id, ) - _validate_reference("actor_reference", actor_reference) - _validate_resource_kind(resource_kind) + _validate_reference("actor_reference", request.actor_reference) + _validate_resource_kind(request.resource_kind) _validate_reference( "resource_reference", - resource_reference, - expected_namespace=resource_kind, + request.resource_reference, + expected_namespace=request.resource_kind, ) - _validate_code("purpose_code", purpose_code) - _validate_code("operation_code", operation_code) - _validate_field_set("requested_fields", requested_fields) - _validate_scope_set(granted_scope_codes) + _validate_code("purpose_code", request.purpose_code) + _validate_code("operation_code", request.operation_code) + _validate_field_set("requested_fields", request.requested_fields) + _validate_scope_set(request.granted_scope_codes) return _RequestSnapshot( - tenant_record_id_int, - actor_tenant_record_id_int, - resource_tenant_record_id_int, - actor_reference, - resource_reference, - purpose_code, - operation_code, - resource_kind, - requested_fields, - granted_scope_codes, - ) - - -def _build_input_issuance_runtime() -> tuple[ - type[PurposeBoundAccessPolicy], - type[PurposeBoundAccessRequest], - Callable[[PurposeBoundAccessPolicy], _PolicySnapshot], - Callable[[PurposeBoundAccessRequest], _RequestSnapshot], -]: - """Create policy/request classes whose issuance mutation state is closure-private.""" - policy_registry: dict[ - int, - tuple[weakref.ReferenceType[object], _PolicySnapshot], - ] = {} - request_registry: dict[ - int, - tuple[weakref.ReferenceType[object], _RequestSnapshot], - ] = {} - policy_construction_ids: set[int] = set() - request_construction_ids: set[int] = set() - - @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) - class PurposeBoundAccessPolicy: - """One tenant-local field policy for one purpose, resource, and operation. - - A policy intentionally has no wildcard form. Separate purposes, operations, - or resources require separate reviewed policy records so a broad token cannot - silently widen access to necessary HR PII. - """ - - tenant_record_id: UUID - policy_version_code: str - resource_kind: str - purpose_code: str - operation_code: str - required_scope_code: str - permitted_fields: frozenset[str] - - def __init__( - self, - *, - tenant_record_id: UUID, - policy_version_code: str, - resource_kind: str, - purpose_code: str, - operation_code: str, - required_scope_code: str, - permitted_fields: frozenset[str], - ) -> None: - """Write fields and issue authority only inside this constructor call.""" - key = id(self) - if key in policy_registry: - raise TypeError("PurposeBoundAccessPolicy is already initialized") - if key in policy_construction_ids: - raise TypeError("PurposeBoundAccessPolicy construction is already in progress") - policy_construction_ids.add(key) - try: - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "policy_version_code", policy_version_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "required_scope_code", required_scope_code) - object.__setattr__(self, "permitted_fields", permitted_fields) - self.__post_init__() - finally: - policy_construction_ids.discard(key) - - def __post_init__(self) -> None: - """Issue a validated snapshot only while the governed constructor is active.""" - key = id(self) - if key not in policy_construction_ids: - raise TypeError("PurposeBoundAccessPolicy must be initialized through its constructor") - snapshot = _validated_policy_snapshot(self) - if key in policy_registry: - raise TypeError("PurposeBoundAccessPolicy is already initialized") - reference = weakref.ref( - self, - lambda _reference, evidence_key=key: policy_registry.pop(evidence_key, None), - ) - policy_registry[key] = (reference, snapshot) - - @dataclass(frozen=True, slots=True, weakref_slot=True, init=False) - class PurposeBoundAccessRequest: - """PII access attributes resolved before any protected field is returned. - - ``actor_tenant_record_id`` comes from the authenticated identity binding, - ``tenant_record_id`` is the active Orgmetra request context, and - ``resource_tenant_record_id`` comes from the target record identity. The - opaque ``resource_reference`` identifies that exact target for audit - correlation without copying its PII. All tenant identifiers must match the - policy tenant. Only field names are carried here; field values remain behind - the authoritative data boundary until access is allowed. - """ - - tenant_record_id: UUID - actor_tenant_record_id: UUID - resource_tenant_record_id: UUID - actor_reference: str - resource_reference: str - purpose_code: str - operation_code: str - resource_kind: str - requested_fields: frozenset[str] - granted_scope_codes: frozenset[str] - - def __init__( - self, - *, - tenant_record_id: UUID, - actor_tenant_record_id: UUID, - resource_tenant_record_id: UUID, - actor_reference: str, - resource_reference: str, - purpose_code: str, - operation_code: str, - resource_kind: str, - requested_fields: frozenset[str], - granted_scope_codes: frozenset[str], - ) -> None: - """Write fields and issue authority only inside this constructor call.""" - key = id(self) - if key in request_registry: - raise TypeError("PurposeBoundAccessRequest is already initialized") - if key in request_construction_ids: - raise TypeError("PurposeBoundAccessRequest construction is already in progress") - request_construction_ids.add(key) - try: - object.__setattr__(self, "tenant_record_id", tenant_record_id) - object.__setattr__(self, "actor_tenant_record_id", actor_tenant_record_id) - object.__setattr__(self, "resource_tenant_record_id", resource_tenant_record_id) - object.__setattr__(self, "actor_reference", actor_reference) - object.__setattr__(self, "resource_reference", resource_reference) - object.__setattr__(self, "purpose_code", purpose_code) - object.__setattr__(self, "operation_code", operation_code) - object.__setattr__(self, "resource_kind", resource_kind) - object.__setattr__(self, "requested_fields", requested_fields) - object.__setattr__(self, "granted_scope_codes", granted_scope_codes) - self.__post_init__() - finally: - request_construction_ids.discard(key) - - def __post_init__(self) -> None: - """Issue a validated snapshot only while the governed constructor is active.""" - key = id(self) - if key not in request_construction_ids: - raise TypeError("PurposeBoundAccessRequest must be initialized through its constructor") - snapshot = _validated_request_snapshot(self) - if key in request_registry: - raise TypeError("PurposeBoundAccessRequest is already initialized") - reference = weakref.ref( - self, - lambda _reference, evidence_key=key: request_registry.pop(evidence_key, None), - ) - request_registry[key] = (reference, snapshot) - - def issued_policy_snapshot(policy: PurposeBoundAccessPolicy) -> _PolicySnapshot: - """Return creation-time policy authority only when live fields still match it.""" - entry = policy_registry.get(id(policy)) - if entry is None or entry[0]() is not policy: - raise ValueError("PurposeBoundAccessPolicy was not issued by the validated constructor") - current = _validated_policy_snapshot(policy) - if current != entry[1]: - raise ValueError("PurposeBoundAccessPolicy changed after validation") - return entry[1] - - def issued_request_snapshot(request: PurposeBoundAccessRequest) -> _RequestSnapshot: - """Return creation-time request authority only when live fields still match it.""" - entry = request_registry.get(id(request)) - if entry is None or entry[0]() is not request: - raise ValueError("PurposeBoundAccessRequest was not issued by the validated constructor") - current = _validated_request_snapshot(request) - if current != entry[1]: - raise ValueError("PurposeBoundAccessRequest changed after validation") - return entry[1] - - PurposeBoundAccessPolicy.__qualname__ = "PurposeBoundAccessPolicy" - PurposeBoundAccessRequest.__qualname__ = "PurposeBoundAccessRequest" - return ( - PurposeBoundAccessPolicy, - PurposeBoundAccessRequest, - issued_policy_snapshot, - issued_request_snapshot, + tenant_int, + actor_tenant_int, + resource_tenant_int, + request.actor_reference, + request.resource_reference, + request.purpose_code, + request.operation_code, + request.resource_kind, + request.requested_fields, + request.granted_scope_codes, ) -( - PurposeBoundAccessPolicy, - PurposeBoundAccessRequest, - _issued_policy_snapshot, - _issued_request_snapshot, -) = _build_input_issuance_runtime() -del _build_input_issuance_runtime - - def _validated_decision_snapshot( *, allowed: object, @@ -435,8 +369,8 @@ def _validated_decision_snapshot( authorized_fields: object, reason_code: object, next_action: object, -) -> tuple[object, ...]: - """Validate decision evidence and detach caller-owned mutable runtime objects.""" +) -> _DecisionSnapshot: + """Validate PII-minimized decision values without conferring policy authority.""" if type(allowed) is not bool: raise ValueError("allowed must be a boolean.") tenant_int = _validated_uuid_int("tenant_record_id", tenant_record_id) @@ -463,7 +397,7 @@ def _validated_decision_snapshot( raise ValueError("allow decision must use access_permitted reason.") if not allowed and reason_code == "access_permitted": raise ValueError("deny decision must not use access_permitted reason.") - return ( + return _DecisionSnapshot( allowed, tenant_int, actor_reference, @@ -479,161 +413,24 @@ def _validated_decision_snapshot( ) -class AuthorizationDecision: - """PII-minimized authorization evidence with detached, structurally immutable state. - - Validated values live in evaluator-private snapshot storage rather than writable - instance slots. In particular the tenant UUID is stored as its integer value - and rebuilt on access, so a later low-level mutation of the caller's UUID cannot - rewrite already-issued authorization evidence. The public constructor is - intentionally non-authoritative: only purpose-bound evaluation may mint an - issued decision. - """ - - __slots__ = ("__weakref__",) - - allowed: bool - tenant_record_id: UUID - actor_reference: str - resource_reference: str - policy_version_code: str - purpose_code: str - operation_code: str - resource_kind: str - requested_fields: frozenset[str] - authorized_fields: frozenset[str] - reason_code: str - next_action: str - - def __init__( - self, - *, - allowed: bool, - tenant_record_id: UUID, - actor_reference: str, - resource_reference: str, - policy_version_code: str, - purpose_code: str, - operation_code: str, - resource_kind: str, - requested_fields: frozenset[str], - authorized_fields: frozenset[str], - reason_code: str, - next_action: str, - ) -> None: - """Reject direct construction; only the evaluator may register evidence.""" - try: - _decision_snapshot_for(self) - except ValueError: - raise TypeError("AuthorizationDecision must be issued by purpose-bound evaluation") from None - raise TypeError("AuthorizationDecision is already initialized") - - def __init_subclass__(cls, **kwargs: object) -> None: - """Seal the evidence type so subclasses cannot override validation hooks.""" - raise TypeError("AuthorizationDecision must not be subclassed") - - def _snapshot(self) -> tuple[object, ...]: - """Return evaluator-issued state or fail closed for low-level forged instances.""" - return _decision_snapshot_for(self) - - @property - def allowed(self) -> bool: - """Return the immutable allow/deny verdict.""" - return self._snapshot()[0] # type: ignore[return-value] - - @property - def tenant_record_id(self) -> UUID: - """Return a detached UUID copy of the authorized tenant identity.""" - return UUID(int=self._snapshot()[1]) # type: ignore[arg-type] - - @property - def actor_reference(self) -> str: - """Return the PII-minimized actor reference.""" - return self._snapshot()[2] # type: ignore[return-value] - - @property - def resource_reference(self) -> str: - """Return the opaque target reference bound to the decision.""" - return self._snapshot()[3] # type: ignore[return-value] - - @property - def policy_version_code(self) -> str: - """Return the immutable policy version used for evaluation.""" - return self._snapshot()[4] # type: ignore[return-value] - - @property - def purpose_code(self) -> str: - """Return the purpose bound to the decision.""" - return self._snapshot()[5] # type: ignore[return-value] - - @property - def operation_code(self) -> str: - """Return the operation bound to the decision.""" - return self._snapshot()[6] # type: ignore[return-value] - - @property - def resource_kind(self) -> str: - """Return the governed resource kind.""" - return self._snapshot()[7] # type: ignore[return-value] - - @property - def requested_fields(self) -> frozenset[str]: - """Return the exact immutable requested-field set.""" - return self._snapshot()[8] # type: ignore[return-value] - - @property - def authorized_fields(self) -> frozenset[str]: - """Return the exact immutable authorized-field set.""" - return self._snapshot()[9] # type: ignore[return-value] - - @property - def reason_code(self) -> str: - """Return the governed allow/deny reason code.""" - return self._snapshot()[10] # type: ignore[return-value] - - @property - def next_action(self) -> str: - """Return bounded non-authoritative recovery guidance.""" - return self._snapshot()[11] # type: ignore[return-value] - - def __repr__(self) -> str: - """Preserve a deterministic value-style representation for diagnostics.""" - return ( - "AuthorizationDecision(" - f"allowed={self.allowed!r}, " - f"tenant_record_id={self.tenant_record_id!r}, " - f"actor_reference={self.actor_reference!r}, " - f"resource_reference={self.resource_reference!r}, " - f"policy_version_code={self.policy_version_code!r}, " - f"purpose_code={self.purpose_code!r}, " - f"operation_code={self.operation_code!r}, " - f"resource_kind={self.resource_kind!r}, " - f"requested_fields={self.requested_fields!r}, " - f"authorized_fields={self.authorized_fields!r}, " - f"reason_code={self.reason_code!r}, " - f"next_action={self.next_action!r})" - ) - - def __eq__(self, other: object) -> bool: - """Retain dataclass-like value equality only for exact issued decisions.""" - if type(other) is not AuthorizationDecision: - return False - return self._snapshot() == other._snapshot() - - def __hash__(self) -> int: - """Retain stable value hashing over detached immutable evidence.""" - return hash(self._snapshot()) - - -class AuthorizationDeniedError(PermissionError): - """A purpose-bound policy denied access and tells the caller how to recover safely.""" - - def __init__(self, decision: AuthorizationDecision) -> None: - """Preserve bounded denial metadata without including protected field values.""" - super().__init__(decision.reason_code) - self.reason_code = decision.reason_code - self.next_action = decision.next_action - self.decision = decision +def validate_authorization_decision(decision: AuthorizationDecision) -> _DecisionSnapshot: + """Revalidate exact decision data before a same-process durable consumer uses it.""" + if type(decision) is not AuthorizationDecision: + raise TypeError("decision must be an AuthorizationDecision") + return _validated_decision_snapshot( + allowed=decision.allowed, + tenant_record_id=decision.tenant_record_id, + actor_reference=decision.actor_reference, + resource_reference=decision.resource_reference, + policy_version_code=decision.policy_version_code, + purpose_code=decision.purpose_code, + operation_code=decision.operation_code, + resource_kind=decision.resource_kind, + requested_fields=decision.requested_fields, + authorized_fields=decision.authorized_fields, + reason_code=decision.reason_code, + next_action=decision.next_action, + ) def _decision( @@ -643,90 +440,97 @@ def _decision( allowed: bool, reason_code: str, ) -> AuthorizationDecision: - """Reject direct use of the former module-level authority-minting helper.""" - raise TypeError("decision issuance is internal to evaluate_purpose_bound_access") - - -def _build_decision_runtime() -> tuple[ - Callable[[AuthorizationDecision], tuple[object, ...]], - Callable[..., AuthorizationDecision], -]: - """Create evaluator-private decision storage and expose only read/evaluate closures.""" - registry: dict[ - int, - tuple[weakref.ReferenceType[object], tuple[object, ...]], - ] = {} - - def decision_snapshot_for(decision: AuthorizationDecision) -> tuple[object, ...]: - """Return only state registered by this evaluator runtime.""" - entry = registry.get(id(decision)) - if entry is None or entry[0]() is not decision: - raise ValueError("AuthorizationDecision was not issued by purpose-bound evaluation") - return entry[1] - - def evaluate( - *, - request: PurposeBoundAccessRequest, - policy: PurposeBoundAccessPolicy, - ) -> AuthorizationDecision: - """Evaluate tenant, resource, purpose, operation, scope, and field attributes.""" - if type(request) is not PurposeBoundAccessRequest: - raise TypeError("request must be a PurposeBoundAccessRequest") - if type(policy) is not PurposeBoundAccessPolicy: - raise TypeError("policy must be a PurposeBoundAccessPolicy") - - request_snapshot = _issued_request_snapshot(request) - policy_snapshot = _issued_policy_snapshot(policy) - - def issue_decision(*, allowed: bool, reason_code: str) -> AuthorizationDecision: - """Register one decision from already-issued policy/request snapshots.""" - authorized_fields = request_snapshot.requested_fields if allowed else frozenset() - next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] - snapshot = _validated_decision_snapshot( - allowed=allowed, - tenant_record_id=UUID(int=request_snapshot.tenant_record_id_int), - actor_reference=request_snapshot.actor_reference, - resource_reference=request_snapshot.resource_reference, - policy_version_code=policy_snapshot.policy_version_code, - purpose_code=request_snapshot.purpose_code, - operation_code=request_snapshot.operation_code, - resource_kind=request_snapshot.resource_kind, - requested_fields=request_snapshot.requested_fields, - authorized_fields=authorized_fields, - reason_code=reason_code, - next_action=next_action, - ) - decision = object.__new__(AuthorizationDecision) - key = id(decision) - reference = weakref.ref( - decision, - lambda _reference, evidence_key=key: registry.pop(evidence_key, None), - ) - registry[key] = (reference, snapshot) - return decision - - if ( - request_snapshot.tenant_record_id_int != policy_snapshot.tenant_record_id_int - or request_snapshot.actor_tenant_record_id_int != policy_snapshot.tenant_record_id_int - or request_snapshot.resource_tenant_record_id_int != policy_snapshot.tenant_record_id_int - ): - return issue_decision(allowed=False, reason_code="tenant_scope_mismatch") - if request_snapshot.resource_kind != policy_snapshot.resource_kind: - return issue_decision(allowed=False, reason_code="resource_not_allowed") - if request_snapshot.purpose_code != policy_snapshot.purpose_code: - return issue_decision(allowed=False, reason_code="purpose_not_allowed") - if request_snapshot.operation_code != policy_snapshot.operation_code: - return issue_decision(allowed=False, reason_code="operation_not_allowed") - if policy_snapshot.required_scope_code not in request_snapshot.granted_scope_codes: - return issue_decision(allowed=False, reason_code="required_scope_missing") - if not request_snapshot.requested_fields.issubset(policy_snapshot.permitted_fields): - return issue_decision(allowed=False, reason_code="field_not_allowed") - return issue_decision(allowed=True, reason_code="access_permitted") - - return decision_snapshot_for, evaluate - - -_decision_snapshot_for, evaluate_purpose_bound_access = _build_decision_runtime() + """Build one validated decision from the current evaluation snapshots.""" + authorized_fields = request.requested_fields if allowed else frozenset() + next_action = _ALLOW_NEXT_ACTION if allowed else _DENIAL_NEXT_ACTION[reason_code] + return AuthorizationDecision( + allowed=allowed, + tenant_record_id=UUID(int=request.tenant_record_id_int), + actor_reference=request.actor_reference, + resource_reference=request.resource_reference, + policy_version_code=policy.policy_version_code, + purpose_code=request.purpose_code, + operation_code=request.operation_code, + resource_kind=request.resource_kind, + requested_fields=request.requested_fields, + authorized_fields=authorized_fields, + reason_code=reason_code, + next_action=next_action, + ) + + +def evaluate_purpose_bound_access( + *, + request: PurposeBoundAccessRequest, + policy: PurposeBoundAccessPolicy, +) -> AuthorizationDecision: + """Evaluate trusted-source policy against authenticated/request attributes. + + ``policy`` must come from the service's trusted Orgmetra policy composition + boundary. The evaluator deliberately does not attempt to prove that arbitrary + Python code in the same interpreter is trustworthy; it revalidates current + values and defends the data boundary exposed to remote/untrusted inputs. + """ + if type(request) is not PurposeBoundAccessRequest: + raise TypeError("request must be a PurposeBoundAccessRequest") + if type(policy) is not PurposeBoundAccessPolicy: + raise TypeError("policy must be a PurposeBoundAccessPolicy") + + request_snapshot = _validated_request_snapshot(request) + policy_snapshot = _validated_policy_snapshot(policy) + + if ( + request_snapshot.tenant_record_id_int != policy_snapshot.tenant_record_id_int + or request_snapshot.actor_tenant_record_id_int != policy_snapshot.tenant_record_id_int + or request_snapshot.resource_tenant_record_id_int != policy_snapshot.tenant_record_id_int + ): + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=False, + reason_code="tenant_scope_mismatch", + ) + if request_snapshot.resource_kind != policy_snapshot.resource_kind: + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=False, + reason_code="resource_not_allowed", + ) + if request_snapshot.purpose_code != policy_snapshot.purpose_code: + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=False, + reason_code="purpose_not_allowed", + ) + if request_snapshot.operation_code != policy_snapshot.operation_code: + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=False, + reason_code="operation_not_allowed", + ) + if policy_snapshot.required_scope_code not in request_snapshot.granted_scope_codes: + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=False, + reason_code="required_scope_missing", + ) + if not request_snapshot.requested_fields.issubset(policy_snapshot.permitted_fields): + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=False, + reason_code="field_not_allowed", + ) + return _decision( + request=request_snapshot, + policy=policy_snapshot, + allowed=True, + reason_code="access_permitted", + ) def require_purpose_bound_access( From c2105d2ea22a63d2149e3fb370773f82a0c43419 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:43:43 +0900 Subject: [PATCH 052/241] test(authz): bind provenance assertions to service TCB --- ...ation_input_issuance_capability_privacy.py | 170 +++++------------- 1 file changed, 49 insertions(+), 121 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py b/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py index 197fbd03b..3694dc9f2 100644 --- a/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py +++ b/packages/keyverse-adapter/tests/test_authorization_input_issuance_capability_privacy.py @@ -1,4 +1,4 @@ -"""Regressions for policy/request issuance capability privacy.""" +"""Regressions for the service-process authorization trust boundary.""" from __future__ import annotations @@ -18,7 +18,7 @@ def _policy() -> PurposeBoundAccessPolicy: - """Build one legitimately constructor-issued narrow policy.""" + """Build one trusted-composition policy value.""" return PurposeBoundAccessPolicy( tenant_record_id=TENANT, policy_version_code="people_pii_v1", @@ -31,7 +31,7 @@ def _policy() -> PurposeBoundAccessPolicy: def _request() -> PurposeBoundAccessRequest: - """Build one legitimately constructor-issued narrow request.""" + """Build one authenticated/request-derived authorization value.""" return PurposeBoundAccessRequest( tenant_record_id=TENANT, actor_tenant_record_id=TENANT, @@ -46,37 +46,8 @@ def _request() -> PurposeBoundAccessRequest: ) -def _forged_policy() -> PurposeBoundAccessPolicy: - """Allocate valid-looking exact policy fields without its constructor.""" - policy = object.__new__(PurposeBoundAccessPolicy) - object.__setattr__(policy, "tenant_record_id", TENANT) - object.__setattr__(policy, "policy_version_code", "people_pii_v1") - object.__setattr__(policy, "resource_kind", "person_record") - object.__setattr__(policy, "purpose_code", "hr_operations") - object.__setattr__(policy, "operation_code", "read_person_pii") - object.__setattr__(policy, "required_scope_code", "orgmetra.people.read") - object.__setattr__(policy, "permitted_fields", frozenset({"work_email"})) - return policy - - -def _forged_request() -> PurposeBoundAccessRequest: - """Allocate valid-looking exact request fields without its constructor.""" - request = object.__new__(PurposeBoundAccessRequest) - object.__setattr__(request, "tenant_record_id", TENANT) - object.__setattr__(request, "actor_tenant_record_id", TENANT) - object.__setattr__(request, "resource_tenant_record_id", TENANT) - object.__setattr__(request, "actor_reference", "keyverse_subject:sub_jordan_hale") - object.__setattr__(request, "resource_reference", "person_record:per_01J5EXACTTARGET") - object.__setattr__(request, "purpose_code", "hr_operations") - object.__setattr__(request, "operation_code", "read_person_pii") - object.__setattr__(request, "resource_kind", "person_record") - object.__setattr__(request, "requested_fields", frozenset({"work_email"})) - object.__setattr__(request, "granted_scope_codes", frozenset({"orgmetra.people.read"})) - return request - - def _closure_bindings(function: Callable[..., object]) -> dict[str, object]: - """Expose function cells exactly as an ordinary same-process Python consumer can.""" + """Expose closure state exactly as same-process Python code can inspect it.""" cells = function.__closure__ if cells is None: return {} @@ -86,92 +57,6 @@ def _closure_bindings(function: Callable[..., object]) -> dict[str, object]: } -def test_module_consumer_cannot_activate_forged_policy_through_construction_state() -> None: - """Mutable module construction state must not mint policy authority.""" - policy = _forged_policy() - construction_ids = getattr(authorization, "_POLICY_CONSTRUCTION_IDS", None) - registry = getattr(authorization, "_POLICY_SNAPSHOT_REGISTRY", None) - - try: - if construction_ids is not None: - construction_ids.add(id(policy)) - with pytest.raises(TypeError, match="must be initialized through its constructor"): - PurposeBoundAccessPolicy.__post_init__(policy) - finally: - if construction_ids is not None: - construction_ids.discard(id(policy)) - if registry is not None: - registry.pop(id(policy), None) - - with pytest.raises(ValueError, match="was not issued by the validated constructor"): - evaluate_purpose_bound_access(request=_request(), policy=policy) - - -def test_module_consumer_cannot_activate_forged_request_through_construction_state() -> None: - """Mutable module construction state must not mint request authority.""" - request = _forged_request() - construction_ids = getattr(authorization, "_REQUEST_CONSTRUCTION_IDS", None) - registry = getattr(authorization, "_REQUEST_SNAPSHOT_REGISTRY", None) - - try: - if construction_ids is not None: - construction_ids.add(id(request)) - with pytest.raises(TypeError, match="must be initialized through its constructor"): - PurposeBoundAccessRequest.__post_init__(request) - finally: - if construction_ids is not None: - construction_ids.discard(id(request)) - if registry is not None: - registry.pop(id(request), None) - - with pytest.raises(ValueError, match="was not issued by the validated constructor"): - evaluate_purpose_bound_access(request=request, policy=_policy()) - - -def test_same_process_consumer_cannot_mint_policy_by_mutating_closure_cells() -> None: - """Inspectable Python closure cells must not constitute policy issuance authority.""" - policy = _forged_policy() - bindings = _closure_bindings(PurposeBoundAccessPolicy.__post_init__) - construction_ids = bindings.get("policy_construction_ids") - registry = bindings.get("policy_registry") - - if construction_ids is not None: - construction_ids.add(id(policy)) - try: - with pytest.raises(TypeError, match="must be initialized through its constructor"): - PurposeBoundAccessPolicy.__post_init__(policy) - finally: - if construction_ids is not None: - construction_ids.discard(id(policy)) - if registry is not None: - registry.pop(id(policy), None) - - with pytest.raises(ValueError, match="was not issued by the validated constructor"): - evaluate_purpose_bound_access(request=_request(), policy=policy) - - -def test_same_process_consumer_cannot_mint_request_by_mutating_closure_cells() -> None: - """Inspectable Python closure cells must not constitute request issuance authority.""" - request = _forged_request() - bindings = _closure_bindings(PurposeBoundAccessRequest.__post_init__) - construction_ids = bindings.get("request_construction_ids") - registry = bindings.get("request_registry") - - if construction_ids is not None: - construction_ids.add(id(request)) - try: - with pytest.raises(TypeError, match="must be initialized through its constructor"): - PurposeBoundAccessRequest.__post_init__(request) - finally: - if construction_ids is not None: - construction_ids.discard(id(request)) - if registry is not None: - registry.pop(id(request), None) - - with pytest.raises(ValueError, match="was not issued by the validated constructor"): - evaluate_purpose_bound_access(request=request, policy=_policy()) - - @pytest.mark.parametrize( "attribute_name", ( @@ -179,8 +64,51 @@ def test_same_process_consumer_cannot_mint_request_by_mutating_closure_cells() - "_REQUEST_SNAPSHOT_REGISTRY", "_POLICY_CONSTRUCTION_IDS", "_REQUEST_CONSTRUCTION_IDS", + "_DECISION_SNAPSHOT_REGISTRY", + "_DECISION_ISSUANCE_IDS", ), ) -def test_module_does_not_expose_writable_input_issuance_capabilities(attribute_name: str) -> None: - """Policy/request issuance mutation capability must not be a module attribute.""" +def test_module_has_no_runtime_authority_registry(attribute_name: str) -> None: + """No mutable Python registry may be described as an issuance security boundary.""" assert not hasattr(authorization, attribute_name) + + +@pytest.mark.parametrize( + "function", + ( + PurposeBoundAccessPolicy.__post_init__, + PurposeBoundAccessRequest.__post_init__, + evaluate_purpose_bound_access, + ), +) +def test_authorization_boundary_does_not_hide_mutable_authority_in_closure_cells( + function: Callable[..., object], +) -> None: + """Inspectable closure cells must not carry a claimed authorization capability.""" + assert _closure_bindings(function) == {} + + +def test_policy_authority_is_not_inferred_from_python_constructor_provenance() -> None: + """Evaluation validates data; trusted composition, not object provenance, owns policy authority.""" + policy = object.__new__(PurposeBoundAccessPolicy) + object.__setattr__(policy, "tenant_record_id", TENANT) + object.__setattr__(policy, "policy_version_code", "people_pii_v1") + object.__setattr__(policy, "resource_kind", "person_record") + object.__setattr__(policy, "purpose_code", "hr_operations") + object.__setattr__(policy, "operation_code", "read_person_pii") + object.__setattr__(policy, "required_scope_code", "orgmetra.people.read") + object.__setattr__(policy, "permitted_fields", frozenset({"work_email"})) + + decision = evaluate_purpose_bound_access(request=_request(), policy=policy) + + assert decision.allowed is True + assert decision.authorized_fields == frozenset({"work_email"}) + + +def test_current_values_are_revalidated_even_inside_the_trusted_process() -> None: + """Low-level corruption still fails closed instead of relying on creation-time bookkeeping.""" + policy = _policy() + object.__setattr__(policy, "permitted_fields", {"work_email"}) + + with pytest.raises(ValueError, match="permitted_fields must be a frozenset"): + evaluate_purpose_bound_access(request=_request(), policy=policy) From eed5c96c1e5071e94e4f967edf7e5f500d93296a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:44:04 +0900 Subject: [PATCH 053/241] test(authz): revalidate live values without fake issuance state --- ...st_authorization_issued_input_integrity.py | 272 +++++------------- 1 file changed, 69 insertions(+), 203 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py index d87308b31..62323f0c9 100644 --- a/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_issued_input_integrity.py @@ -1,4 +1,4 @@ -"""Issued-input integrity regressions for purpose-bound authorization.""" +"""Live-value integrity regressions for purpose-bound authorization inputs.""" from __future__ import annotations @@ -15,25 +15,29 @@ TENANT = UUID("10000000-0000-7000-8000-000000000501") -def _policy() -> PurposeBoundAccessPolicy: - """Build one narrow policy whose creation-time field authority is auditable.""" +def _policy(*, field: str = "work_email", tenant: UUID = TENANT) -> PurposeBoundAccessPolicy: + """Build one trusted-composition policy value.""" return PurposeBoundAccessPolicy( - tenant_record_id=TENANT, + tenant_record_id=tenant, policy_version_code="people_pii_v1", resource_kind="person_record", purpose_code="hr_operations", operation_code="read_person_pii", required_scope_code="orgmetra.people.read", - permitted_fields=frozenset({"work_email"}), + permitted_fields=frozenset({field}), ) -def _request(*, field: str = "work_email") -> PurposeBoundAccessRequest: - """Build one request with an exact creation-time field and scope snapshot.""" +def _request( + *, + field: str = "work_email", + tenant: UUID = TENANT, +) -> PurposeBoundAccessRequest: + """Build one request-derived authorization value.""" return PurposeBoundAccessRequest( - tenant_record_id=TENANT, - actor_tenant_record_id=TENANT, - resource_tenant_record_id=TENANT, + tenant_record_id=tenant, + actor_tenant_record_id=tenant, + resource_tenant_record_id=tenant, actor_reference="keyverse_subject:sub_jordan_hale", resource_reference="person_record:per_01J5EXACTTARGET", purpose_code="hr_operations", @@ -44,222 +48,84 @@ def _request(*, field: str = "work_email") -> PurposeBoundAccessRequest: ) -def _policy_without_constructor() -> PurposeBoundAccessPolicy: - """Forge valid-looking policy fields without invoking the public constructor.""" - policy = object.__new__(PurposeBoundAccessPolicy) - object.__setattr__(policy, "tenant_record_id", TENANT) - object.__setattr__(policy, "policy_version_code", "people_pii_v1") - object.__setattr__(policy, "resource_kind", "person_record") - object.__setattr__(policy, "purpose_code", "hr_operations") - object.__setattr__(policy, "operation_code", "read_person_pii") - object.__setattr__(policy, "required_scope_code", "orgmetra.people.read") - object.__setattr__(policy, "permitted_fields", frozenset({"work_email"})) - return policy - - -def _request_without_constructor() -> PurposeBoundAccessRequest: - """Forge valid-looking request fields without invoking the public constructor.""" - request = object.__new__(PurposeBoundAccessRequest) - object.__setattr__(request, "tenant_record_id", TENANT) - object.__setattr__(request, "actor_tenant_record_id", TENANT) - object.__setattr__(request, "resource_tenant_record_id", TENANT) - object.__setattr__(request, "actor_reference", "keyverse_subject:sub_jordan_hale") - object.__setattr__(request, "resource_reference", "person_record:per_01J5EXACTTARGET") - object.__setattr__(request, "purpose_code", "hr_operations") - object.__setattr__(request, "operation_code", "read_person_pii") - object.__setattr__(request, "resource_kind", "person_record") - object.__setattr__(request, "requested_fields", frozenset({"work_email"})) - object.__setattr__(request, "granted_scope_codes", frozenset({"orgmetra.people.read"})) - return request - - -def test_evaluator_rejects_post_construction_policy_widening() -> None: - """Low-level mutation cannot widen a policy after its governed construction.""" - policy = _policy() - object.__setattr__( - policy, - "permitted_fields", - frozenset({"work_email", "compensation_amount"}), - ) +def test_policy_constructor_detaches_caller_owned_uuid() -> None: + """Later low-level caller UUID mutation cannot rewrite a policy value.""" + tenant = UUID(str(TENANT)) + policy = _policy(tenant=tenant) - with pytest.raises(ValueError, match="PurposeBoundAccessPolicy changed after validation"): - evaluate_purpose_bound_access( - request=_request(field="compensation_amount"), - policy=policy, - ) + object.__setattr__(tenant, "int", 0) + assert policy.tenant_record_id == TENANT -def test_evaluator_rejects_post_construction_request_scope_rewrite() -> None: - """Low-level mutation cannot replace the authenticated scope snapshot after validation.""" - request = _request() - object.__setattr__(request, "granted_scope_codes", frozenset({"orgmetra.people.admin"})) - with pytest.raises(ValueError, match="PurposeBoundAccessRequest changed after validation"): - evaluate_purpose_bound_access(request=request, policy=_policy()) +def test_request_constructor_detaches_caller_owned_uuid_instances() -> None: + """Request tenant identities do not retain caller-owned UUID objects.""" + tenant = UUID(str(TENANT)) + request = _request(tenant=tenant) + object.__setattr__(tenant, "int", 0) -def test_evaluator_rejects_post_construction_request_field_rewrite() -> None: - """Low-level mutation cannot change which PII field the issued request asks to expose.""" - request = _request() - object.__setattr__(request, "requested_fields", frozenset({"compensation_amount"})) + assert request.tenant_record_id == TENANT + assert request.actor_tenant_record_id == TENANT + assert request.resource_tenant_record_id == TENANT - with pytest.raises(ValueError, match="PurposeBoundAccessRequest changed after validation"): - evaluate_purpose_bound_access(request=request, policy=_policy()) +def test_evaluator_revalidates_post_construction_policy_runtime_type() -> None: + """Low-level policy corruption fails closed at the evaluation boundary.""" + policy = _policy() + object.__setattr__(policy, "permitted_fields", {"work_email"}) -def test_direct_policy_post_init_cannot_issue_constructor_bypassing_object() -> None: - """Public lifecycle hooks cannot mint policy authority for a forged exact object.""" - policy = _policy_without_constructor() + with pytest.raises(ValueError, match="permitted_fields must be a frozenset"): + evaluate_purpose_bound_access(request=_request(), policy=policy) - with pytest.raises(TypeError, match="must be initialized through its constructor"): - PurposeBoundAccessPolicy.__post_init__(policy) - with pytest.raises(ValueError, match="was not issued by the validated constructor"): - evaluate_purpose_bound_access(request=_request(), policy=policy) +def test_evaluator_revalidates_post_construction_request_scope_runtime_type() -> None: + """Low-level scope corruption cannot reach membership evaluation.""" + request = _request() + object.__setattr__(request, "granted_scope_codes", {"orgmetra.people.read"}) + with pytest.raises(ValueError, match="granted_scope_codes must be a frozenset"): + evaluate_purpose_bound_access(request=request, policy=_policy()) -def test_direct_request_post_init_cannot_issue_constructor_bypassing_object() -> None: - """Public lifecycle hooks cannot mint request authority for a forged exact object.""" - request = _request_without_constructor() - with pytest.raises(TypeError, match="must be initialized through its constructor"): - PurposeBoundAccessRequest.__post_init__(request) +def test_evaluator_revalidates_post_construction_request_field_runtime_type() -> None: + """Low-level field-set corruption cannot reach subset evaluation.""" + request = _request() + object.__setattr__(request, "requested_fields", {"work_email"}) - with pytest.raises(ValueError, match="was not issued by the validated constructor"): + with pytest.raises(ValueError, match="requested_fields must be a frozenset"): evaluate_purpose_bound_access(request=request, policy=_policy()) -def test_rejected_policy_reinitialization_preserves_issued_value() -> None: - """A rejected second constructor call cannot rewrite observable policy state.""" +def test_same_process_policy_value_change_is_not_misrepresented_as_provenance_security() -> None: + """Inside the TCB, valid current policy data—not a Python object history—drives evaluation.""" policy = _policy() - before = ( - policy.tenant_record_id, - policy.policy_version_code, - policy.resource_kind, - policy.purpose_code, - policy.operation_code, - policy.required_scope_code, - policy.permitted_fields, - repr(policy), - hash(policy), - ) + object.__setattr__(policy, "permitted_fields", frozenset({"compensation_amount"})) - with pytest.raises(TypeError, match="PurposeBoundAccessPolicy is already initialized"): - PurposeBoundAccessPolicy.__init__( - policy, - tenant_record_id=TENANT, - policy_version_code="people_pii_v2", - resource_kind="person_record", - purpose_code="compensation_review", - operation_code="read_person_pii", - required_scope_code="orgmetra.people.read", - permitted_fields=frozenset({"compensation_amount"}), - ) - - after = ( - policy.tenant_record_id, - policy.policy_version_code, - policy.resource_kind, - policy.purpose_code, - policy.operation_code, - policy.required_scope_code, - policy.permitted_fields, - repr(policy), - hash(policy), + decision = evaluate_purpose_bound_access( + request=_request(field="compensation_amount"), + policy=policy, ) - assert after == before + assert decision.allowed is True + assert decision.authorized_fields == frozenset({"compensation_amount"}) -def test_invalid_policy_reinitialization_preserves_issued_value() -> None: - """The issuance guard precedes validation and leaves an issued policy unchanged.""" - policy = _policy() - before = repr(policy) - - with pytest.raises(TypeError, match="PurposeBoundAccessPolicy is already initialized"): - PurposeBoundAccessPolicy.__init__( - policy, - tenant_record_id=TENANT, - policy_version_code="contains whitespace", - resource_kind="person_record", - purpose_code="hr_operations", - operation_code="read_person_pii", - required_scope_code="orgmetra.people.read", - permitted_fields=frozenset({"work_email"}), - ) - - assert repr(policy) == before - assert policy.policy_version_code == "people_pii_v1" - - -def test_rejected_request_reinitialization_preserves_issued_value() -> None: - """A rejected second constructor call cannot rewrite authenticated request state.""" - request = _request() - before = ( - request.tenant_record_id, - request.actor_tenant_record_id, - request.resource_tenant_record_id, - request.actor_reference, - request.resource_reference, - request.purpose_code, - request.operation_code, - request.resource_kind, - request.requested_fields, - request.granted_scope_codes, - repr(request), - hash(request), - ) - with pytest.raises(TypeError, match="PurposeBoundAccessRequest is already initialized"): - PurposeBoundAccessRequest.__init__( - request, - tenant_record_id=TENANT, - actor_tenant_record_id=TENANT, - resource_tenant_record_id=TENANT, - actor_reference="keyverse_subject:sub_other_actor", - resource_reference="person_record:per_01J5EXACTTARGET", - purpose_code="compensation_review", - operation_code="read_person_pii", - resource_kind="person_record", - requested_fields=frozenset({"compensation_amount"}), - granted_scope_codes=frozenset({"orgmetra.people.read"}), - ) - - after = ( - request.tenant_record_id, - request.actor_tenant_record_id, - request.resource_tenant_record_id, - request.actor_reference, - request.resource_reference, - request.purpose_code, - request.operation_code, - request.resource_kind, - request.requested_fields, - request.granted_scope_codes, - repr(request), - hash(request), - ) - assert after == before +def test_policy_retains_deterministic_value_semantics() -> None: + """Validation hardening preserves stable equality, hashing, and diagnostics.""" + left = _policy() + right = _policy() + assert left == right + assert hash(left) == hash(right) + assert repr(left).startswith("PurposeBoundAccessPolicy(") -def test_invalid_request_reinitialization_preserves_issued_value() -> None: - """The issuance guard precedes validation and leaves an issued request unchanged.""" - request = _request() - before = repr(request) - - with pytest.raises(TypeError, match="PurposeBoundAccessRequest is already initialized"): - PurposeBoundAccessRequest.__init__( - request, - tenant_record_id=TENANT, - actor_tenant_record_id=TENANT, - resource_tenant_record_id=TENANT, - actor_reference="keyverse_subject:sub_jordan_hale", - resource_reference="wrong_namespace:per_01J5EXACTTARGET", - purpose_code="hr_operations", - operation_code="read_person_pii", - resource_kind="person_record", - requested_fields=frozenset({"work_email"}), - granted_scope_codes=frozenset({"orgmetra.people.read"}), - ) - - assert repr(request) == before - assert request.resource_reference == "person_record:per_01J5EXACTTARGET" + +def test_request_retains_deterministic_value_semantics() -> None: + """Request validation preserves stable equality, hashing, and diagnostics.""" + left = _request() + right = _request() + + assert left == right + assert hash(left) == hash(right) + assert repr(left).startswith("PurposeBoundAccessRequest(") From 0ad7b917a5a662e576b7b6fa6f5f5f9f71ac1343 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:44:21 +0900 Subject: [PATCH 054/241] test(authz): stop treating Python object provenance as authority --- ...horization_decision_issuance_provenance.py | 129 ++++++------------ 1 file changed, 39 insertions(+), 90 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py index a662c4d3f..7fc648626 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_issuance_provenance.py @@ -1,12 +1,9 @@ -"""Issuance-provenance regressions for purpose-bound authorization decisions.""" +"""Trust-boundary regressions for purpose-bound authorization decisions.""" from __future__ import annotations -import weakref from uuid import UUID -import pytest - import orgmetra_keyverse_adapter.authorization as authorization_module from orgmetra_keyverse_adapter import ( AuthorizationDecision, @@ -20,97 +17,49 @@ REQUESTED_FIELDS = frozenset({"assignment_category_code"}) -def test_direct_decision_constructor_cannot_mint_allow_authority() -> None: - """A caller must not mint an allow decision without governed policy evaluation.""" - with pytest.raises(TypeError, match="purpose-bound evaluation"): - AuthorizationDecision( - allowed=True, - tenant_record_id=TENANT, - actor_reference="keyverse_subject:operator-17", - resource_reference=RESOURCE_REFERENCE, - policy_version_code="assignment-correction-v1", - purpose_code="workforce_admin", - operation_code="correct_record", - resource_kind="assignment_record", - requested_fields=REQUESTED_FIELDS, - authorized_fields=REQUESTED_FIELDS, - reason_code="access_permitted", - next_action="Continue with only the authorized fields.", - ) - - -def test_module_decision_helper_cannot_mint_from_fabricated_snapshots() -> None: - """Module-callable helpers must not turn fabricated snapshots into authority.""" - request_snapshot = authorization_module._RequestSnapshot( - TENANT.int, - TENANT.int, - TENANT.int, - "keyverse_subject:operator-17", - RESOURCE_REFERENCE, - "workforce_admin", - "correct_record", - "assignment_record", - REQUESTED_FIELDS, - frozenset({"orgmetra.people.write"}), - ) - policy_snapshot = authorization_module._PolicySnapshot( - TENANT.int, - "assignment-correction-v1", - "assignment_record", - "workforce_admin", - "correct_record", - "orgmetra.people.write", - REQUESTED_FIELDS, - ) - - with pytest.raises(TypeError, match="internal to evaluate_purpose_bound_access"): - authorization_module._decision( - request=request_snapshot, - policy=policy_snapshot, - allowed=True, - reason_code="access_permitted", - ) - assert not hasattr(authorization_module, "_DECISION_ISSUANCE_IDS") +def _decision_values() -> dict[str, object]: + """Return one semantically coherent PII-minimized allow decision payload.""" + return { + "allowed": True, + "tenant_record_id": TENANT, + "actor_reference": "keyverse_subject:operator-17", + "resource_reference": RESOURCE_REFERENCE, + "policy_version_code": "assignment-correction-v1", + "purpose_code": "workforce_admin", + "operation_code": "correct_record", + "resource_kind": "assignment_record", + "requested_fields": REQUESTED_FIELDS, + "authorized_fields": REQUESTED_FIELDS, + "reason_code": "access_permitted", + "next_action": "Continue with only the authorized fields.", + } + + +def test_direct_decision_construction_validates_data_but_does_not_claim_provenance() -> None: + """A Python decision object is validated evidence data, not an unforgeable capability.""" + decision = AuthorizationDecision(**_decision_values()) # type: ignore[arg-type] + assert decision.allowed is True + assert decision.tenant_record_id == TENANT + assert decision.authorized_fields == REQUESTED_FIELDS -def test_module_registry_insertion_cannot_mint_forged_decision() -> None: - """Consumer-visible module state must not provide a writable authority registry.""" - forged = object.__new__(AuthorizationDecision) - snapshot = authorization_module._validated_decision_snapshot( - allowed=True, - tenant_record_id=TENANT, - actor_reference="keyverse_subject:operator-17", - resource_reference=RESOURCE_REFERENCE, - policy_version_code="assignment-correction-v1", - purpose_code="workforce_admin", - operation_code="correct_record", - resource_kind="assignment_record", - requested_fields=REQUESTED_FIELDS, - authorized_fields=REQUESTED_FIELDS, - reason_code="access_permitted", - next_action="Continue with only the authorized fields.", - ) - registry = getattr(authorization_module, "_DECISION_SNAPSHOT_REGISTRY", None) - inserted = False - try: - if registry is not None: - try: - registry[id(forged)] = (weakref.ref(forged), snapshot) - except TypeError: - pass - else: - inserted = True - with pytest.raises(ValueError, match="was not issued by purpose-bound evaluation"): - _ = forged.allowed - finally: - if inserted: - registry.pop(id(forged), None) - assert registry is None +def test_authorization_module_exposes_no_mutable_issuance_registry() -> None: + """No Python mapping or id-set may be represented as authorization issuance authority.""" + for attribute_name in ( + "_DECISION_SNAPSHOT_REGISTRY", + "_DECISION_ISSUANCE_IDS", + "_POLICY_SNAPSHOT_REGISTRY", + "_REQUEST_SNAPSHOT_REGISTRY", + "_POLICY_CONSTRUCTION_IDS", + "_REQUEST_CONSTRUCTION_IDS", + ): + assert not hasattr(authorization_module, attribute_name) + assert evaluate_purpose_bound_access.__closure__ is None -def test_governed_evaluator_remains_the_decision_issuance_path() -> None: - """Matching issued request and policy evidence still produce one allow decision.""" +def test_governed_evaluator_builds_decision_from_current_trusted_policy_and_request() -> None: + """The normal service path still evaluates every narrowing attribute before allow.""" policy = PurposeBoundAccessPolicy( tenant_record_id=TENANT, policy_version_code="assignment-correction-v1", From 5f809a10c530571030d0fb9088894b74f1cfbf43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:44:56 +0900 Subject: [PATCH 055/241] test(authz): validate decision data at consumer boundary --- ...uthorization_decision_runtime_integrity.py | 100 ++++++------------ 1 file changed, 34 insertions(+), 66 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 9066ff9dc..08592cc73 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -1,9 +1,7 @@ -"""Runtime-integrity regressions for evaluator-issued authorization decisions.""" +"""Runtime-integrity regressions for purpose-bound authorization decision data.""" from __future__ import annotations -import gc -import weakref from uuid import UUID import pytest @@ -34,7 +32,7 @@ class _ForgedFieldSet(frozenset[str]): def _policy(**overrides: object) -> PurposeBoundAccessPolicy: - """Build one deterministic issued policy for decision-integrity tests.""" + """Build one deterministic trusted policy for decision-integrity tests.""" values: dict[str, object] = { "tenant_record_id": TENANT, "policy_version_code": "assignment-correction-v1", @@ -49,7 +47,7 @@ def _policy(**overrides: object) -> PurposeBoundAccessPolicy: def _request(**overrides: object) -> PurposeBoundAccessRequest: - """Build one deterministic issued request for decision-integrity tests.""" + """Build one deterministic request value for decision-integrity tests.""" values: dict[str, object] = { "tenant_record_id": TENANT, "actor_tenant_record_id": TENANT, @@ -67,12 +65,12 @@ def _request(**overrides: object) -> PurposeBoundAccessRequest: def _decision() -> AuthorizationDecision: - """Return one allow decision issued only by the governed evaluator.""" + """Return one allow decision from the normal evaluator path.""" return evaluate_purpose_bound_access(request=_request(), policy=_policy()) def _validate_decision(**overrides: object) -> tuple[object, ...]: - """Exercise the internal pure evidence validator without minting authority.""" + """Exercise the pure evidence validator without asserting object provenance.""" values: dict[str, object] = { "allowed": True, "tenant_record_id": TENANT, @@ -91,77 +89,56 @@ def _validate_decision(**overrides: object) -> tuple[object, ...]: return authorization_module._validated_decision_snapshot(**values) -def test_decision_cannot_be_subclassed_to_bypass_validation() -> None: - """Caller-defined decision classes must not override issued evidence behavior.""" +def test_decision_cannot_be_subclassed_to_override_runtime_behavior() -> None: + """Caller-defined decision classes cannot override validated field semantics.""" with pytest.raises(TypeError, match="AuthorizationDecision must not be subclassed"): class _ForgedDecision(AuthorizationDecision): pass -def test_decision_resists_object_setattr_after_valid_evaluation() -> None: - """Low-level attribute writes cannot replace already-issued authorization evidence.""" +def test_consumer_revalidation_detects_low_level_decision_mutation() -> None: + """A durable consumer can fail closed if trusted-process code corrupts decision data.""" decision = _decision() - with pytest.raises((AttributeError, TypeError)): - object.__setattr__(decision, "allowed", False) - assert decision.allowed is True - assert decision.reason_code == "access_permitted" + object.__setattr__(decision, "allowed", False) + + with pytest.raises(ValueError, match="deny decision must not authorize fields"): + authorization_module.validate_authorization_decision(decision) def test_decision_detaches_caller_owned_exact_uuid() -> None: - """Later low-level UUID mutation must not rewrite an evaluator-issued decision.""" + """Later low-level UUID mutation cannot rewrite constructed decision data.""" tenant = UUID(str(TENANT)) - decision = evaluate_purpose_bound_access( - request=_request( - tenant_record_id=tenant, - actor_tenant_record_id=tenant, - resource_tenant_record_id=tenant, - ), - policy=_policy(tenant_record_id=tenant), + decision = AuthorizationDecision( + allowed=True, + tenant_record_id=tenant, + actor_reference="keyverse_subject:operator-17", + resource_reference=RESOURCE_REFERENCE, + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=REQUESTED_FIELDS, + authorized_fields=REQUESTED_FIELDS, + reason_code="access_permitted", + next_action="Continue with only the authorized fields.", ) object.__setattr__(tenant, "int", 0) + assert decision.tenant_record_id == TENANT @pytest.mark.parametrize("forged_int", [-1, 1 << 128, "invalid"]) def test_decision_validator_rejects_low_level_corrupted_exact_uuid(forged_int: object) -> None: - """The internal snapshot validator rejects an exact UUID with corrupted integer state.""" + """The snapshot validator rejects an exact UUID with corrupted integer state.""" tenant = UUID(str(TENANT)) object.__setattr__(tenant, "int", forged_int) with pytest.raises(ValueError, match="tenant_record_id must contain a valid UUID integer"): _validate_decision(tenant_record_id=tenant) -def test_decision_rejects_unissued_low_level_instance() -> None: - """Bypassing evaluation must not yield readable authorization evidence.""" - forged = object.__new__(AuthorizationDecision) - with pytest.raises(ValueError, match="was not issued by purpose-bound evaluation"): - _ = forged.allowed - - -def test_decision_cannot_be_reinitialized_with_new_evidence() -> None: - """A previously issued decision cannot be replaced through a second initializer call.""" - decision = _decision() - with pytest.raises(TypeError, match="already initialized"): - AuthorizationDecision.__init__( - decision, - allowed=False, - tenant_record_id=TENANT, - actor_reference="keyverse_subject:operator-17", - resource_reference=RESOURCE_REFERENCE, - policy_version_code="assignment-correction-v1", - purpose_code="workforce_admin", - operation_code="correct_record", - resource_kind="assignment_record", - requested_fields=REQUESTED_FIELDS, - authorized_fields=frozenset(), - reason_code="field_not_allowed", - next_action="Request only fields allowed for this purpose.", - ) - - def test_decision_preserves_value_semantics_and_deterministic_repr() -> None: - """Issuance hardening preserves equality, hashing, and diagnostic representation.""" + """Validation preserves equality, hashing, and diagnostic representation.""" left = _decision() right = _decision() assert left == right @@ -171,15 +148,6 @@ def test_decision_preserves_value_semantics_and_deterministic_repr() -> None: assert "assignment_category_code" in repr(left) -def test_decision_registry_does_not_retain_dead_evidence() -> None: - """Lifecycle bookkeeping must not keep evaluator-issued evidence alive.""" - decision = _decision() - reference = weakref.ref(decision) - del decision - gc.collect() - assert reference() is None - - def test_decision_validator_rejects_non_boolean_allowed_flag() -> None: """Truthy integers cannot masquerade as an authorization verdict.""" with pytest.raises(ValueError, match="allowed must be a boolean"): @@ -187,7 +155,7 @@ def test_decision_validator_rejects_non_boolean_allowed_flag() -> None: def test_decision_validator_rejects_uuid_subclass() -> None: - """Decision snapshots cannot retain caller-defined UUID runtime behavior.""" + """Decision data cannot retain caller-defined UUID runtime behavior.""" forged = _ForgedUUID(str(TENANT)) with pytest.raises(ValueError, match="tenant_record_id must be a UUID"): _validate_decision(tenant_record_id=forged) @@ -207,14 +175,14 @@ def test_decision_validator_rejects_uuid_subclass() -> None: ], ) def test_decision_validator_rejects_string_subclasses(field_name: str, forged_value: str) -> None: - """Decision snapshots cannot retain caller-defined text runtime behavior.""" + """Decision data cannot retain caller-defined text runtime behavior.""" with pytest.raises(ValueError): _validate_decision(**{field_name: forged_value}) @pytest.mark.parametrize("field_name", ["requested_fields", "authorized_fields"]) def test_decision_validator_rejects_frozenset_subclasses(field_name: str) -> None: - """Field evidence cannot override containment or equality after evaluation.""" + """Field evidence cannot override containment or equality behavior.""" forged = _ForgedFieldSet({"assignment_category_code"}) with pytest.raises(ValueError, match=f"{field_name} must be a frozenset"): _validate_decision(**{field_name: forged}) @@ -262,7 +230,7 @@ def test_deny_decision_rejects_success_reason() -> None: def test_decision_validator_accepts_bounded_internal_denial_reason() -> None: - """The pure validator preserves a bounded denial code without minting authority.""" + """The pure validator preserves a bounded denial code without conferring authority.""" snapshot = _validate_decision( allowed=False, authorized_fields=frozenset(), From a3a943c082a1d9ac562f9d404406651db10bb4ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:46:05 +0900 Subject: [PATCH 056/241] feat(authz): expose decision revalidation contract --- .../src/orgmetra_keyverse_adapter/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/__init__.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/__init__.py index fcfbd63ff..410f22620 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/__init__.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/__init__.py @@ -1,10 +1,9 @@ """Keyverse identity binding and purpose-bound authorization for Orgmetra. Orgmetra never stores passwords, passkeys, or raw credentials on a person -record. Use ``bind_identity_subject`` after Keyverse authenticates the actor, -then evaluate the authenticated subject, tenant, purpose, operation, scope, and -requested field set against an Orgmetra-owned purpose-bound policy before -returning protected HR data. +record. Keyverse authenticates identity and scopes; Orgmetra's trusted service +composition supplies HR policy. The exported value objects validate data but do +not pretend to be unforgeable capabilities against arbitrary same-process code. """ from orgmetra_keyverse_adapter.authorization import ( @@ -14,6 +13,7 @@ PurposeBoundAccessRequest, evaluate_purpose_bound_access, require_purpose_bound_access, + validate_authorization_decision, ) from orgmetra_keyverse_adapter.binding import ( CredentialRejectedError, @@ -31,4 +31,5 @@ "bind_identity_subject", "evaluate_purpose_bound_access", "require_purpose_bound_access", + "validate_authorization_decision", ] From 6ed5ec1b7d60b1ee240884cc63cce91fb81a5c0f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:46:40 +0900 Subject: [PATCH 057/241] test(authz): cover durable decision revalidation boundary --- ...horization_decision_consumer_validation.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py b/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py new file mode 100644 index 000000000..41f2987a7 --- /dev/null +++ b/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py @@ -0,0 +1,43 @@ +"""Consumer-boundary regressions for authorization decision revalidation.""" + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import AuthorizationDecision, validate_authorization_decision + +TENANT = UUID("10000000-0000-7000-8000-000000000501") +FIELDS = frozenset({"assignment_category_code"}) + + +def _decision() -> AuthorizationDecision: + """Build one coherent internal decision-data value.""" + return AuthorizationDecision( + allowed=True, + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference="assignment_record:0198a412800070008000000000000070", + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=FIELDS, + authorized_fields=FIELDS, + reason_code="access_permitted", + next_action="Continue with only the authorized fields.", + ) + + +def test_consumer_validator_returns_detached_coherent_snapshot() -> None: + """A durable consumer can revalidate exact current decision semantics.""" + snapshot = validate_authorization_decision(_decision()) + + assert snapshot.allowed is True + assert snapshot.tenant_record_id_int == TENANT.int + assert snapshot.authorized_fields == FIELDS + + +def test_consumer_validator_rejects_non_decision_runtime_type() -> None: + """Caller-defined unrelated objects cannot enter the durable evidence boundary.""" + with pytest.raises(TypeError, match="decision must be an AuthorizationDecision"): + validate_authorization_decision(object()) # type: ignore[arg-type] From 427a31fab5afcca81fe02a4e20346034613005e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:47:31 +0900 Subject: [PATCH 058/241] docs(authz): anchor policy authority at trusted composition --- docs/TRACEABILITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index c830867d6..d90709772 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -21,7 +21,7 @@ | Tenant-safe atomic outbox claiming and crash recovery | Integration Hub dispatcher boundary | `outbox_delivery_record` pending/expired-lease claim indexes plus `claim_outbox_delivery(...)` | PostgreSQL already-expired-new-lease rejection, due-order claim, live-lease exclusion, pre-exhaustion takeover with `lease_expired` evidence, retry-budget claim bound, tenant-context binding, opaque-worker validation, and bounded-lease contract | ADR-0006 | implemented_on_active_pr | | Owner-bound outbox completion, retry, and terminal dead-letter escalation | Integration Hub dispatcher boundary | immutable `outbox_delivery_record.maximum_attempt_count`, `complete_outbox_delivery(...)`, `retry_outbox_delivery(...)`, `dead_letter_outbox_delivery(...)`, `outbox_delivery_escalation_record` | PostgreSQL foreign/stale-owner denial, dispatcher-budget-signature rejection, direct-terminal-DML rejection, stored-budget exhaustion, retry-attempt-N+1 denial, exhausted expired-lease non-reclaimability, recorded-owner terminalization, nonterminal-escalation rejection, terminal non-reclaimability, and append-only escalation evidence | ADR-0006 | implemented_on_active_pr | | Predictive-validity case integrity | Workforce Validation | `validity_study`, normalized `validity_study_case_record`, exact `selection_decision`, sealed `decision_evidence_set`, governed `candidate_worker_conversion_record`, `criterion_observation` | `test_validity_study_case_postgres.sh`: legacy loose-link write rejection; exact evidence-set ID, Job, criterion and worker mismatch rejection; study/observation system-recorded visibility boundaries; governed upstream decision/evidence/conversion lineage from the evidence-sealing and candidate-worker conversion contracts; UPDATE/DELETE/TRUNCATE protection; missing/foreign-tenant RLS denial. Statistical estimation remains subsequent work. | ADR-0001, SIOP Principles 5th ed., 29 C.F.R. Part 1607 | implemented_on_protected_main | -| Purpose-bound PII access and authorization-evidence issuance | Security architecture / Keyverse adapter boundary | `PurposeBoundAccessPolicy`, `PurposeBoundAccessRequest.resource_reference`, evaluator-issued `AuthorizationDecision` | exact tenant/actor/resource binding; exact opaque target correlation; resource/purpose/operation/scope/field minimization; reserved-UUID and malformed-runtime rejection; detached creation-time policy/request/decision snapshots; policy/request constructor-provenance regressions; direct `AuthorizationDecision(allowed=True, ...)` minting rejection; evaluator-only decision issuance; weakref cleanup and exact 100% owned statement/branch coverage | ADR-0008 | implemented_on_active_pr | +| Purpose-bound PII access and authorization decision integrity | Security architecture / Keyverse adapter / trusted service composition | `PurposeBoundAccessPolicy`, `PurposeBoundAccessRequest.resource_reference`, `AuthorizationDecision` | exact tenant/actor/resource binding; exact opaque target correlation; resource/purpose/operation/scope/field minimization; exact built-in runtime types; UUID detachment; evaluation-time policy/request revalidation; verdict/reason/field coherence; consumer-side decision revalidation; regressions proving no mutable module/closure registry is represented as an issuance capability; exact 100% owned statement/branch coverage | ADR-0008 | implemented_on_active_pr | | Least-privilege API capability | Keyverse gateway boundary | operation scope conceptual | structural per-operation scope and confused-deputy contract tests | ADR-0002 | implemented_on_active_pr | | Client-safe failure correlation | API error boundary | `support_reference` conceptual | error disclosure and support-lookup tests | ADR-0002 | implemented_on_active_pr | | Foundation artifact integrity | Repository governance | deterministic `manifest.json` file inventory | SHA-256/byte/line validation plus Python/Node inventory-equivalence regression and explicit dispatcher/validity/criterion/job-analysis migration and execution-contract provenance regression | ADR-0001 | implemented_on_active_pr | @@ -30,7 +30,7 @@ | External contract | Orgmetra owner boundary | Integration style | Required evidence | ADR | Maturity | |---|---|---|---|---|---| -| Keyverse identity and authorization | API Gateway / purpose-bound authorization | Published OIDC/API identity and scope contract plus Orgmetra-owned `orgmetra_keyverse_adapter` policy evaluation | tenant/actor/resource agreement, exact opaque target-resource reference, purpose, operation-specific scope, requested-field minimization, opaque subject, no stored credentials or protected values in authorization evidence; authorization decisions are minted only by the Orgmetra evaluator, not by consumer construction | ADR-0002, ADR-0008 | implemented_on_active_pr | +| Keyverse identity and authorization | API Gateway / purpose-bound authorization | Published OIDC/API identity and scope contract plus Orgmetra-owned `orgmetra_keyverse_adapter` policy evaluation | tenant/actor/resource agreement, exact opaque target-resource reference, purpose, operation-specific scope, requested-field minimization, opaque subject, no stored credentials or protected values in authorization evidence; production policy values come from trusted Orgmetra service composition/policy sources rather than request-controlled data; Python decision objects are revalidated PII-minimized data, not unforgeable capabilities against arbitrary same-process code | ADR-0002, ADR-0008 | implemented_on_active_pr | | naruon communication and calendar | Integration Hub | Published API/event adapter | idempotency, delivery audit, no direct table access | ADR-0002 | planned | | Psychometrics Commons @ `cc5850a0d1eacbbf16d03075534fce460a8286e6` | Workforce Validation | Immutable response/result snapshot contract | pinned revision, model/version/provenance snapshot, immutable result linkage, no direct application-table access | ADR-0002 | accepted_architecture | | fast-mlsirm @ `fb67ced09d8ee00542c05d56374537a9a7239751` | Workforce Validation | Published `orgmetra.fast_mlsirm.v1` result contract; direct calls only from approved offline validation worker | pinned revision, contract identifier, backend/result provenance, CPU/GPU parity evidence where material, no duplicated kernel | ADR-0002 | accepted_architecture | From b141870aa6ad2b7fbe3e03421082ef728eff4577 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:49:38 +0900 Subject: [PATCH 059/241] chore(manifest): reseal authorization traceability --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 57b2fecf9..8072cd951 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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"cb957a525c77fc6ab7c8b174bda781b25ab3ef37761cba7e5678e2bde1d43220","bytes":11704,"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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30}]} \ No newline at end of file From acf54efd8e67452188b16954c34616d190655c20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:52:37 +0900 Subject: [PATCH 060/241] fix(manifest): restore full inventory after reseal error --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 8072cd951..57b2fecf9 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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30}]} \ No newline at end of file +{"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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"cb957a525c77fc6ab7c8b174bda781b25ab3ef37761cba7e5678e2bde1d43220","bytes":11704,"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}]} From bf66cf4176f7ab20c9c9472f6e149f6058942278 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:54:14 +0900 Subject: [PATCH 061/241] chore(manifest): reseal authorization traceability --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 57b2fecf9..f0b84a18c 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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"cb957a525c77fc6ab7c8b174bda781b25ab3ef37761cba7e5678e2bde1d43220","bytes":11704,"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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"d5b57dfa3b4b5c4a408062b6a47f947080aa98eb4b82ffaf130a85ac17c80892","bytes":11841,"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.sql","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}]} From f1abd661835f8554ddce5656712c62bc278184bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:55:59 +0900 Subject: [PATCH 062/241] fix(manifest): restore criterion ADR inventory path --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index f0b84a18c..5955ecdd3 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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"d5b57dfa3b4b5c4a408062b6a47f947080aa98eb4b82ffaf130a85ac17c80892","bytes":11841,"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.sql","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":"0e86a39d0dc8e631565a7be341ed69ed27af2a9a5843457ba78c889464d55315","bytes":17672,"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":"d5b57dfa3b4b5c4a408062b6a47f947080aa98eb4b82ffaf130a85ac17c80892","bytes":11841,"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}]} From 9e9a16a034b552061651204eda1ed13996645b01 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:04:01 +0900 Subject: [PATCH 063/241] test(people): require durable authorization revalidation --- ...orization_decision_durable_revalidation.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 services/people-api/tests/test_authorization_decision_durable_revalidation.py diff --git a/services/people-api/tests/test_authorization_decision_durable_revalidation.py b/services/people-api/tests/test_authorization_decision_durable_revalidation.py new file mode 100644 index 000000000..78ab4c2d3 --- /dev/null +++ b/services/people-api/tests/test_authorization_decision_durable_revalidation.py @@ -0,0 +1,57 @@ +"""Regressions for semantic revalidation at durable People authorization boundaries.""" + +from __future__ import annotations + +import unittest + +from orgmetra_people_api.hire import HireDecisionIntegrityError +from orgmetra_people_api.mutations import PeopleMutationIntegrityError, mutation_command_digest +from orgmetra_people_api.postgres_hire import _validate_authorization as validate_hire_authorization +from orgmetra_people_api.postgres_mutations import _require_authorization as require_people_authorization +from test_people_mutations import EMPLOYMENT, TENANT, employment_command +from test_postgres_hire_acceptance import allowed_authorization, command as hire_command +from test_postgres_people_mutations import employment_authorization + + +def _contradict_allowed_decision(decision: object) -> object: + """Simulate post-construction corruption that leaves the runtime class unchanged.""" + object.__setattr__(decision, "reason_code", "access_denied") + return decision + + +class AuthorizationDecisionDurableRevalidationTests(unittest.TestCase): + """Require durable consumers to reject contradictory exact decision objects.""" + + def test_semantic_digest_revalidates_decision_before_reading_evidence(self) -> None: + """Idempotency evidence must not hash a contradictory allow decision.""" + decision = _contradict_allowed_decision(employment_authorization()) + + with self.assertRaises(ValueError): + mutation_command_digest( + command=employment_command(), + authorization=decision, # type: ignore[arg-type] + ) + + def test_generic_postgres_boundary_revalidates_before_accepting_allow(self) -> None: + """Generic persistence must reject a contradictory exact decision before SQL.""" + decision = _contradict_allowed_decision(employment_authorization()) + + with self.assertRaises(PeopleMutationIntegrityError): + require_people_authorization( + authorization=decision, + tenant_record_id=TENANT, + resource_reference=f"employment_record:{EMPLOYMENT.hex}", + resource_kind="employment_record", + requested_fields=frozenset({"employment_record"}), + ) + + def test_hire_postgres_boundary_revalidates_before_accepting_allow(self) -> None: + """Hire persistence must reject a contradictory exact decision before SQL.""" + decision = _contradict_allowed_decision(allowed_authorization()) + + with self.assertRaises(HireDecisionIntegrityError): + validate_hire_authorization(hire_command(), decision) + + +if __name__ == "__main__": + unittest.main() From b3cec0ce22b9a7802c2b8b78e1232dfa1044cc53 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:07:13 +0900 Subject: [PATCH 064/241] fix(people): revalidate decisions before durable digests --- .../src/orgmetra_people_api/mutations.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/mutations.py b/services/people-api/src/orgmetra_people_api/mutations.py index 6baeac684..7016eb018 100644 --- a/services/people-api/src/orgmetra_people_api/mutations.py +++ b/services/people-api/src/orgmetra_people_api/mutations.py @@ -19,7 +19,11 @@ from typing import Protocol, runtime_checkable from uuid import UUID, uuid5 -from orgmetra_keyverse_adapter import AuthorizationDecision, PurposeBoundAccessPolicy +from orgmetra_keyverse_adapter import ( + AuthorizationDecision, + PurposeBoundAccessPolicy, + validate_authorization_decision, +) from orgmetra_people_api.auth import AuthenticatedPrincipal from orgmetra_people_api.authorization import authorize_resource_fields @@ -113,10 +117,10 @@ def mutation_command_digest( """Hash method, route, tenant, actor, purpose, and semantic command fields. Generated record identifiers are excluded so a retry that allocates fresh - UUIDs still matches the first committed command. + UUIDs still matches the first committed command. Authorization evidence is + semantically revalidated and detached before any durable digest reads it. """ - if not isinstance(authorization, AuthorizationDecision): - raise TypeError("authorization must be an AuthorizationDecision") + decision = validate_authorization_decision(authorization) if isinstance(command, EmploymentMutationCommand): route = "employment-records" semantic_command: dict[str, object] = { @@ -151,10 +155,10 @@ def mutation_command_digest( else: raise TypeError("command must be a governed People mutation command") payload = { - "actor_reference": authorization.actor_reference, + "actor_reference": decision.actor_reference, "command_route": route, "method": "POST", - "purpose_code": authorization.purpose_code, + "purpose_code": decision.purpose_code, "semantic_command": semantic_command, "tenant_record_id": str(command.tenant_record_id), } @@ -445,4 +449,4 @@ def parse_allocation_ratio(raw_value: object) -> Decimal: """Parse the OpenAPI allocation token into an exact four-decimal ratio.""" if not isinstance(raw_value, str) or re.fullmatch(r"^(0\.[0-9]{4}|1\.0000)$", raw_value) is None: raise ValueError("allocation_ratio must match 0.0001-1.0000 four-decimal form.") - return Decimal(raw_value) + return Decimal(raw_value) \ No newline at end of file From 79e06698611744cb39016f40c2a28bc006789656 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:08:18 +0900 Subject: [PATCH 065/241] fix(people): revalidate hire authorization before persistence --- .../src/orgmetra_people_api/postgres_hire.py | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/postgres_hire.py b/services/people-api/src/orgmetra_people_api/postgres_hire.py index 4c328e02f..a02625375 100644 --- a/services/people-api/src/orgmetra_people_api/postgres_hire.py +++ b/services/people-api/src/orgmetra_people_api/postgres_hire.py @@ -20,7 +20,7 @@ from uuid import UUID from orgmetra_hris_kernel.audit import AuditOutboxEvent -from orgmetra_keyverse_adapter import AuthorizationDecision +from orgmetra_keyverse_adapter import AuthorizationDecision, validate_authorization_decision from orgmetra_people_api.hire import ( HireAcceptanceCommand, @@ -174,30 +174,46 @@ def _is_aware_datetime(value: object) -> bool: def _validate_authorization(command: HireAcceptanceCommand, authorization: object) -> AuthorizationDecision: - """Require an exact allow decision for this immutable selection decision.""" + """Revalidate and detach the exact allow decision before opening a transaction.""" expected_reference = f"selection_decision:{command.selection_decision_id.hex}" - if not isinstance(authorization, AuthorizationDecision): - raise HireDecisionIntegrityError("hire mutation requires a typed authorization decision") + try: + snapshot = validate_authorization_decision(authorization) # type: ignore[arg-type] + except (TypeError, ValueError) as error: + raise HireDecisionIntegrityError("hire mutation requires coherent authorization evidence") from error if ( - not authorization.allowed - or authorization.tenant_record_id != command.tenant_record_id - or authorization.resource_reference != expected_reference - or authorization.resource_kind != "selection_decision" - or authorization.operation_code != "materialize_worker" - or authorization.requested_fields != _HIRE_MUTATION_FIELDS - or authorization.authorized_fields != _HIRE_MUTATION_FIELDS + not snapshot.allowed + or snapshot.tenant_record_id_int != command.tenant_record_id.int + or snapshot.resource_reference != expected_reference + or snapshot.resource_kind != "selection_decision" + or snapshot.operation_code != "materialize_worker" + or snapshot.requested_fields != _HIRE_MUTATION_FIELDS + or snapshot.authorized_fields != _HIRE_MUTATION_FIELDS ): raise HireDecisionIntegrityError("hire mutation authorization does not match the exact decision") - return authorization + return AuthorizationDecision( + allowed=snapshot.allowed, + tenant_record_id=UUID(int=snapshot.tenant_record_id_int), + actor_reference=snapshot.actor_reference, + resource_reference=snapshot.resource_reference, + policy_version_code=snapshot.policy_version_code, + purpose_code=snapshot.purpose_code, + operation_code=snapshot.operation_code, + resource_kind=snapshot.resource_kind, + requested_fields=snapshot.requested_fields, + authorized_fields=snapshot.authorized_fields, + reason_code=snapshot.reason_code, + next_action=snapshot.next_action, + ) def _hire_command_digest(command: HireAcceptanceCommand, authorization: AuthorizationDecision) -> str: - """Hash the exact confirmed-hire semantics without storing necessary PII in audit evidence.""" + """Hash confirmed-hire semantics from a freshly validated authorization snapshot.""" + decision = validate_authorization_decision(authorization) payload = { - "actor_reference": authorization.actor_reference, + "actor_reference": decision.actor_reference, "command_route": _HIRE_IDEMPOTENCY_ROUTE, "method": "POST", - "purpose_code": authorization.purpose_code, + "purpose_code": decision.purpose_code, "semantic_command": { "audit_event_record_id": str(command.audit_event_record_id), "candidate_profile_id": str(command.candidate_profile_id), @@ -453,4 +469,4 @@ def accept_hire( person_record_id=command.person_record_id, employment_record_id=command.employment_record_id, candidate_worker_conversion_record_id=command.candidate_worker_conversion_record_id, - ) + ) \ No newline at end of file From 7d2d31947924d71901e9e733595ae089917d9904 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:10:04 +0900 Subject: [PATCH 066/241] fix(people): revalidate mutation authorization before SQL --- .../orgmetra_people_api/postgres_mutations.py | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/postgres_mutations.py b/services/people-api/src/orgmetra_people_api/postgres_mutations.py index d94832cf8..7534674db 100644 --- a/services/people-api/src/orgmetra_people_api/postgres_mutations.py +++ b/services/people-api/src/orgmetra_people_api/postgres_mutations.py @@ -26,7 +26,7 @@ validate_assignment_write, validate_person_employment_exclusivity, ) -from orgmetra_keyverse_adapter import AuthorizationDecision +from orgmetra_keyverse_adapter import AuthorizationDecision, validate_authorization_decision from orgmetra_people_api.mutations import ( AssignmentMutationCommand, @@ -321,20 +321,35 @@ def _require_authorization( resource_kind: str, requested_fields: frozenset[str], ) -> AuthorizationDecision: - """Require an exact allow decision for the intended mutation target.""" - if not isinstance(authorization, AuthorizationDecision): - raise PeopleMutationIntegrityError("people mutation requires a typed authorization decision") + """Revalidate and detach the exact allow decision before opening a transaction.""" + try: + snapshot = validate_authorization_decision(authorization) # type: ignore[arg-type] + except (TypeError, ValueError) as error: + raise PeopleMutationIntegrityError("people mutation requires coherent authorization evidence") from error if ( - not authorization.allowed - or authorization.tenant_record_id != tenant_record_id - or authorization.resource_reference != resource_reference - or authorization.resource_kind != resource_kind - or authorization.operation_code != "create_record" - or authorization.requested_fields != requested_fields - or authorization.authorized_fields != requested_fields + not snapshot.allowed + or snapshot.tenant_record_id_int != tenant_record_id.int + or snapshot.resource_reference != resource_reference + or snapshot.resource_kind != resource_kind + or snapshot.operation_code != "create_record" + or snapshot.requested_fields != requested_fields + or snapshot.authorized_fields != requested_fields ): raise PeopleMutationIntegrityError("people mutation authorization does not match the exact record") - return authorization + return AuthorizationDecision( + allowed=snapshot.allowed, + tenant_record_id=UUID(int=snapshot.tenant_record_id_int), + actor_reference=snapshot.actor_reference, + resource_reference=snapshot.resource_reference, + policy_version_code=snapshot.policy_version_code, + purpose_code=snapshot.purpose_code, + operation_code=snapshot.operation_code, + resource_kind=snapshot.resource_kind, + requested_fields=snapshot.requested_fields, + authorized_fields=snapshot.authorized_fields, + reason_code=snapshot.reason_code, + next_action=snapshot.next_action, + ) def _record_audit( @@ -834,4 +849,4 @@ def create_assignment( authorization=decision, created_record_id=command.assignment_record_id, ) - return AssignmentMutationResult(assignment_record_id=command.assignment_record_id) + return AssignmentMutationResult(assignment_record_id=command.assignment_record_id) \ No newline at end of file From e2db55a9f38a008d5b905c6722942af0fabab816 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:16:10 +0900 Subject: [PATCH 067/241] test(people): require authorization revalidation before DB acquisition --- ...rization_revalidation_precedes_database.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 services/people-api/tests/test_authorization_revalidation_precedes_database.py diff --git a/services/people-api/tests/test_authorization_revalidation_precedes_database.py b/services/people-api/tests/test_authorization_revalidation_precedes_database.py new file mode 100644 index 000000000..b88c3021f --- /dev/null +++ b/services/people-api/tests/test_authorization_revalidation_precedes_database.py @@ -0,0 +1,110 @@ +"""Regressions proving corrupted authorization evidence cannot acquire a DB connection.""" + +from __future__ import annotations + +import unittest + +from orgmetra_people_api.hire import HireDecisionIntegrityError +from orgmetra_people_api.mutations import PeopleMutationIntegrityError, mutation_command_digest +from orgmetra_people_api.postgres_hire import PostgresHireAcceptancePort, _hire_command_digest +from orgmetra_people_api.postgres_mutations import PostgresPeopleMutationPort +from test_people_mutations import assignment_command, employment_command, position_command +from test_postgres_hire_acceptance import allowed_authorization, command as hire_command +from test_postgres_people_mutations import ( + assignment_authorization, + employment_authorization, + position_authorization, +) + + +def _contradict(decision: object) -> object: + """Corrupt a field not used by the old local allow/matching predicates.""" + object.__setattr__(decision, "reason_code", "access_denied") + return decision + + +class CountingConnectionFactory: + """Fail if a persistence path asks for a connection after invalid evidence.""" + + def __init__(self) -> None: + self.call_count = 0 + + def __call__(self) -> object: + self.call_count += 1 + raise AssertionError("database connection must not be acquired") + + +class AuthorizationRevalidationBeforeDatabaseTests(unittest.TestCase): + """Keep semantic authorization validation ahead of every People SQL boundary.""" + + def test_hire_port_rejects_corruption_before_connection_factory(self) -> None: + """Confirmed-hire persistence must reject invalid evidence before SQL setup.""" + factory = CountingConnectionFactory() + port = PostgresHireAcceptancePort(factory) + + with self.assertRaises(HireDecisionIntegrityError): + port.accept_hire( + command=hire_command(), + authorization=_contradict(allowed_authorization()), # type: ignore[arg-type] + ) + + self.assertEqual(factory.call_count, 0) + + def test_employment_port_rejects_corruption_before_connection_factory(self) -> None: + """Employment persistence must reject invalid evidence before SQL setup.""" + factory = CountingConnectionFactory() + port = PostgresPeopleMutationPort(factory) + + with self.assertRaises(PeopleMutationIntegrityError): + port.create_employment( + command=employment_command(), + authorization=_contradict(employment_authorization()), # type: ignore[arg-type] + ) + + self.assertEqual(factory.call_count, 0) + + def test_position_port_rejects_corruption_before_connection_factory(self) -> None: + """Position persistence must reject invalid evidence before SQL setup.""" + factory = CountingConnectionFactory() + port = PostgresPeopleMutationPort(factory) + + with self.assertRaises(PeopleMutationIntegrityError): + port.create_position( + command=position_command(), + authorization=_contradict(position_authorization()), # type: ignore[arg-type] + ) + + self.assertEqual(factory.call_count, 0) + + def test_assignment_port_rejects_corruption_before_connection_factory(self) -> None: + """Assignment persistence must reject invalid evidence before SQL setup.""" + factory = CountingConnectionFactory() + port = PostgresPeopleMutationPort(factory) + + with self.assertRaises(PeopleMutationIntegrityError): + port.create_assignment( + command=assignment_command(), + authorization=_contradict(assignment_authorization()), # type: ignore[arg-type] + ) + + self.assertEqual(factory.call_count, 0) + + def test_generic_digest_maps_corrupt_decision_to_people_integrity_error(self) -> None: + """Idempotency hashing must use the People integrity error vocabulary.""" + with self.assertRaises(PeopleMutationIntegrityError): + mutation_command_digest( + command=employment_command(), + authorization=_contradict(employment_authorization()), # type: ignore[arg-type] + ) + + def test_hire_digest_maps_corrupt_decision_to_hire_integrity_error(self) -> None: + """Hire replay hashing must use the hire integrity error vocabulary.""" + with self.assertRaises(HireDecisionIntegrityError): + _hire_command_digest( + hire_command(), + _contradict(allowed_authorization()), # type: ignore[arg-type] + ) + + +if __name__ == "__main__": + unittest.main() From 311b421c427b52ae5ec21f856d0d6ba7957a8072 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:17:05 +0900 Subject: [PATCH 068/241] test(people): align digest validation error contract --- ...t_authorization_revalidation_precedes_database.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/people-api/tests/test_authorization_revalidation_precedes_database.py b/services/people-api/tests/test_authorization_revalidation_precedes_database.py index b88c3021f..a646fe8e0 100644 --- a/services/people-api/tests/test_authorization_revalidation_precedes_database.py +++ b/services/people-api/tests/test_authorization_revalidation_precedes_database.py @@ -89,17 +89,17 @@ def test_assignment_port_rejects_corruption_before_connection_factory(self) -> N self.assertEqual(factory.call_count, 0) - def test_generic_digest_maps_corrupt_decision_to_people_integrity_error(self) -> None: - """Idempotency hashing must use the People integrity error vocabulary.""" - with self.assertRaises(PeopleMutationIntegrityError): + def test_generic_digest_revalidates_before_reading_corrupt_evidence(self) -> None: + """Idempotency hashing must reject contradictory decision data before hashing it.""" + with self.assertRaises(ValueError): mutation_command_digest( command=employment_command(), authorization=_contradict(employment_authorization()), # type: ignore[arg-type] ) - def test_hire_digest_maps_corrupt_decision_to_hire_integrity_error(self) -> None: - """Hire replay hashing must use the hire integrity error vocabulary.""" - with self.assertRaises(HireDecisionIntegrityError): + def test_hire_digest_revalidates_before_reading_corrupt_evidence(self) -> None: + """Hire replay hashing must reject contradictory decision data before hashing it.""" + with self.assertRaises(ValueError): _hire_command_digest( hire_command(), _contradict(allowed_authorization()), # type: ignore[arg-type] From 9d22d2e5ddf94124ae7df7b1900a165d75aaca9e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:34:46 +0900 Subject: [PATCH 069/241] test(authz): reproduce job-analysis audit authority drift --- ...st_postgres_audit_authorization_binding.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py diff --git a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py new file mode 100644 index 000000000..2c07ef13d --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py @@ -0,0 +1,78 @@ +"""Regression coverage for job-analysis authorization/audit binding at persistence.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import AuditOutboxEvent +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError, command_digest +from fixtures import ANALYSIS, IDEMPOTENCY_KEY, OTHER_TENANT, TENANT, clinical_psychologist_snapshot + +_ACTOR_REFERENCE = "keyverse:actor-ja-1" +_PURPOSE_CODE = "job_analysis_write" +_RESOURCE_REFERENCE = f"job_analysis_snapshot:{ANALYSIS.hex}" + + +def _never_connect() -> object: + """Prove invalid durable evidence is rejected before database acquisition.""" + raise AssertionError("database acquired before job-analysis audit binding validation") + + +def _audit_event(**overrides: object) -> AuditOutboxEvent: + """Build one shaped audit envelope whose authority fields may be adversarially drifted.""" + snapshot = clinical_psychologist_snapshot() + values: dict[str, object] = { + "event_id": UUID("0198a412-6000-7000-8000-000000000401"), + "tenant_record_id": TENANT, + "source_service": "job_analysis_api", + "event_type": "orgmetra.job_architecture.snapshot_recorded", + "resource_reference": _RESOURCE_REFERENCE, + "actor_reference": _ACTOR_REFERENCE, + "purpose_code": _PURPOSE_CODE, + "reason_code": "snapshot_persisted", + "evidence_version_code": snapshot.analysis_version_code, + "result_code": "recorded", + "occurred_at": datetime(2026, 8, 18, 5, 1, tzinfo=timezone.utc), + "high_impact": False, + } + values.update(overrides) + return AuditOutboxEvent(**values) # type: ignore[arg-type] + + +@pytest.mark.parametrize( + "audit_event", + [ + _audit_event(actor_reference="keyverse:actor-ja-other"), + _audit_event(purpose_code="job_analysis_read"), + _audit_event(tenant_record_id=OTHER_TENANT), + _audit_event(resource_reference="job_analysis_snapshot:0198a412600070008000000000000999"), + ], +) +def test_job_analysis_audit_authority_drift_fails_before_database( + audit_event: AuditOutboxEvent, +) -> None: + """Command authority and durable audit provenance must describe the same write.""" + snapshot = clinical_psychologist_snapshot() + port = PostgresJobAnalysisPort(_never_connect) + + with pytest.raises(JobAnalysisIntegrityError, match="audit event does not match the job-analysis write authority"): + port.persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + position_record_id=None, + criterion_blueprint_id=None, + audit_event=audit_event, + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000402"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000403"), + ) From 307725f0a51d05529455ec0605ea524f7ca602d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:35:45 +0900 Subject: [PATCH 070/241] fix(authz): bind job-analysis audit authority before persistence --- .../src/orgmetra_job_analysis_api/postgres.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 48fa57ce6..608271781 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -249,6 +249,16 @@ def persist_snapshot( validate_operational_uuid("position_record_id", position_record_id) if criterion_blueprint_id is not None: validate_operational_uuid("criterion_blueprint_id", criterion_blueprint_id) + expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" + if ( + audit_event.tenant_record_id != snapshot.tenant_record_id + or audit_event.resource_reference != expected_resource_reference + or audit_event.actor_reference != actor_reference + or audit_event.purpose_code != purpose_code + ): + raise JobAnalysisIntegrityError( + "audit event does not match the job-analysis write authority" + ) with self.connection_factory() as connection: with connection.cursor() as cursor: From 1149960029c9c03fc22a2f5abadf5f56a6edd45e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:41:25 +0900 Subject: [PATCH 071/241] test(authz): reject executable Job Analysis audit subtypes --- ...st_postgres_audit_authorization_binding.py | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py index 2c07ef13d..c95bb012e 100644 --- a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py +++ b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import datetime, timezone +from typing import TypeVar from uuid import UUID import pytest @@ -15,6 +16,11 @@ _ACTOR_REFERENCE = "keyverse:actor-ja-1" _PURPOSE_CODE = "job_analysis_write" _RESOURCE_REFERENCE = f"job_analysis_snapshot:{ANALYSIS.hex}" +_AuditEventT = TypeVar("_AuditEventT", bound=AuditOutboxEvent) + + +class _AuditEventSubtype(AuditOutboxEvent): + """Represent caller-defined executable behavior at the durable audit boundary.""" def _never_connect() -> object: @@ -22,7 +28,10 @@ def _never_connect() -> object: raise AssertionError("database acquired before job-analysis audit binding validation") -def _audit_event(**overrides: object) -> AuditOutboxEvent: +def _audit_event( + event_class: type[_AuditEventT] = AuditOutboxEvent, + **overrides: object, +) -> _AuditEventT: """Build one shaped audit envelope whose authority fields may be adversarially drifted.""" snapshot = clinical_psychologist_snapshot() values: dict[str, object] = { @@ -40,7 +49,29 @@ def _audit_event(**overrides: object) -> AuditOutboxEvent: "high_impact": False, } values.update(overrides) - return AuditOutboxEvent(**values) # type: ignore[arg-type] + return event_class(**values) # type: ignore[arg-type] + + +def _persist_with_audit(audit_event: AuditOutboxEvent) -> None: + """Invoke the durable write boundary with one otherwise-valid command.""" + snapshot = clinical_psychologist_snapshot() + port = PostgresJobAnalysisPort(_never_connect) + port.persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + position_record_id=None, + criterion_blueprint_id=None, + audit_event=audit_event, + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000402"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000403"), + ) @pytest.mark.parametrize( @@ -56,23 +87,11 @@ def test_job_analysis_audit_authority_drift_fails_before_database( audit_event: AuditOutboxEvent, ) -> None: """Command authority and durable audit provenance must describe the same write.""" - snapshot = clinical_psychologist_snapshot() - port = PostgresJobAnalysisPort(_never_connect) - with pytest.raises(JobAnalysisIntegrityError, match="audit event does not match the job-analysis write authority"): - port.persist_snapshot( - snapshot=snapshot, - idempotency_key=IDEMPOTENCY_KEY, - request_digest=command_digest( - snapshot=snapshot, - position_record_id=None, - criterion_blueprint_id=None, - ), - actor_reference=_ACTOR_REFERENCE, - purpose_code=_PURPOSE_CODE, - position_record_id=None, - criterion_blueprint_id=None, - audit_event=audit_event, - outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000402"), - write_command_id=UUID("0198a412-6000-7000-8000-000000000403"), - ) + _persist_with_audit(audit_event) + + +def test_job_analysis_audit_subtype_fails_before_database() -> None: + """Caller-defined audit behavior must not cross the durable provenance boundary.""" + with pytest.raises(TypeError, match="audit_event must be an exact AuditOutboxEvent"): + _persist_with_audit(_audit_event(_AuditEventSubtype)) From 33e14634db0364e1ac07ae50e9b7d1c10fbc5985 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:42:30 +0900 Subject: [PATCH 072/241] fix(authz): reject executable Job Analysis audit subtypes --- .../src/orgmetra_job_analysis_api/postgres.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 608271781..706517ca1 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -239,8 +239,8 @@ def persist_snapshot( """ if not isinstance(snapshot, JobAnalysisSnapshot): raise TypeError("snapshot must be a JobAnalysisSnapshot") - if not isinstance(audit_event, AuditOutboxEvent): - raise TypeError("audit_event must be an AuditOutboxEvent") + if type(audit_event) is not AuditOutboxEvent: + raise TypeError("audit_event must be an exact AuditOutboxEvent") if not isinstance(idempotency_key, str): raise ValueError("idempotency_key must reach the write port as a string.") validate_operational_uuid("write_command_id", write_command_id) From c4c96ae357742a56a251625458485bafefcac2be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:46:00 +0900 Subject: [PATCH 073/241] test(authz): prove audit exact-type check precedes subtype access --- ...st_postgres_audit_authorization_binding.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py index c95bb012e..7faeb36f2 100644 --- a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py +++ b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py @@ -20,7 +20,30 @@ class _AuditEventSubtype(AuditOutboxEvent): - """Represent caller-defined executable behavior at the durable audit boundary.""" + """Trip if persistence consumes caller-defined audit behavior before type rejection.""" + + _TRIPWIRE_FIELDS = frozenset( + {"tenant_record_id", "resource_reference", "actor_reference", "purpose_code"} + ) + + def __getattribute__(self, name: str) -> object: + """Reject authority-field reads once the adversarial fixture is armed.""" + if name in _AuditEventSubtype._TRIPWIRE_FIELDS: + try: + armed = object.__getattribute__(self, "_tripwire_armed") + except AttributeError: + armed = False + if armed: + raise AssertionError(f"audit subtype authority field consumed before exact-type rejection: {name}") + return super().__getattribute__(name) + + def canonical_json(self) -> str: + """Reject canonical serialization if exact-type validation is reordered.""" + raise AssertionError("audit subtype canonical_json consumed before exact-type rejection") + + def content_digest(self) -> str: + """Reject digest serialization if exact-type validation is reordered.""" + raise AssertionError("audit subtype content_digest consumed before exact-type rejection") def _never_connect() -> object: @@ -49,7 +72,10 @@ def _audit_event( "high_impact": False, } values.update(overrides) - return event_class(**values) # type: ignore[arg-type] + event = event_class(**values) # type: ignore[arg-type] + if type(event) is _AuditEventSubtype: + object.__setattr__(event, "_tripwire_armed", True) + return event def _persist_with_audit(audit_event: AuditOutboxEvent) -> None: @@ -91,7 +117,7 @@ def test_job_analysis_audit_authority_drift_fails_before_database( _persist_with_audit(audit_event) -def test_job_analysis_audit_subtype_fails_before_database() -> None: - """Caller-defined audit behavior must not cross the durable provenance boundary.""" +def test_job_analysis_audit_subtype_fails_before_any_audit_or_database_access() -> None: + """Exact-type rejection must precede subtype-controlled fields, serialization, and DB I/O.""" with pytest.raises(TypeError, match="audit_event must be an exact AuditOutboxEvent"): _persist_with_audit(_audit_event(_AuditEventSubtype)) From 92d87c898394dc6cf130e32ecc7c757839324d80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:48:44 +0900 Subject: [PATCH 074/241] test(job-analysis): reproduce executable snapshot subtype trust breach --- .../tests/test_snapshot_runtime_integrity.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 services/job-analysis-api/tests/test_snapshot_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py new file mode 100644 index 000000000..3b9baebf6 --- /dev/null +++ b/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py @@ -0,0 +1,112 @@ +"""Regression coverage for executable snapshot subtypes at Job Analysis durable boundaries.""" + +from __future__ import annotations + +from dataclasses import fields +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import AuditOutboxEvent, JobAnalysisSnapshot +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import command_digest +from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot + +_ACTOR_REFERENCE = "keyverse:actor-ja-1" +_PURPOSE_CODE = "job_analysis_write" + + +class _SnapshotSubtype(JobAnalysisSnapshot): + """Trip if command or persistence code consumes caller-defined snapshot behavior.""" + + _TRIPWIRE_FIELDS = frozenset({"analysis_record_id", "tenant_record_id", "job_record_id"}) + + def __getattribute__(self, name: str) -> object: + """Reject authority-field reads once the adversarial fixture is armed.""" + if name in _SnapshotSubtype._TRIPWIRE_FIELDS: + try: + armed = object.__getattribute__(self, "_tripwire_armed") + except AttributeError: + armed = False + if armed: + raise AssertionError(f"snapshot subtype field consumed before exact-type rejection: {name}") + return super().__getattribute__(name) + + def canonical_json(self) -> str: + """Reject canonical serialization if command validation is reordered.""" + raise AssertionError("snapshot subtype canonical_json consumed before exact-type rejection") + + def content_digest(self) -> str: + """Reject digest serialization if persistence validation is reordered.""" + raise AssertionError("snapshot subtype content_digest consumed before exact-type rejection") + + +def _snapshot_subtype() -> JobAnalysisSnapshot: + """Clone one valid kernel snapshot into a caller-defined runtime subtype and arm it.""" + snapshot = clinical_psychologist_snapshot() + values = { + field.name: getattr(snapshot, field.name) + for field in fields(JobAnalysisSnapshot) + if field.init + } + subtype = _SnapshotSubtype(**values) + object.__setattr__(subtype, "_tripwire_armed", True) + return subtype + + +def _audit_event(snapshot: JobAnalysisSnapshot) -> AuditOutboxEvent: + """Build the canonical audit envelope for the otherwise-valid durable write.""" + return AuditOutboxEvent( + event_id=UUID("0198a412-6000-7000-8000-000000000411"), + tenant_record_id=TENANT, + source_service="job_analysis_api", + event_type="orgmetra.job_architecture.snapshot_recorded", + resource_reference=f"job_analysis_snapshot:{ANALYSIS.hex}", + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + reason_code="snapshot_persisted", + evidence_version_code=snapshot.analysis_version_code, + result_code="recorded", + occurred_at=datetime(2026, 8, 18, 5, 1, tzinfo=timezone.utc), + high_impact=False, + ) + + +def _never_connect() -> object: + """Prove invalid snapshot runtime types fail before database acquisition.""" + raise AssertionError("database acquired before exact JobAnalysisSnapshot rejection") + + +def test_command_digest_rejects_snapshot_subtype_before_serialization() -> None: + """Semantic idempotency must not execute caller-defined snapshot serialization.""" + with pytest.raises(TypeError, match="snapshot must be an exact JobAnalysisSnapshot"): + command_digest( + snapshot=_snapshot_subtype(), + position_record_id=None, + criterion_blueprint_id=None, + ) + + +def test_postgres_rejects_snapshot_subtype_before_fields_or_database() -> None: + """Persistence must reject executable snapshot subtypes before field access or DB I/O.""" + base_snapshot = clinical_psychologist_snapshot() + port = PostgresJobAnalysisPort(_never_connect) + + with pytest.raises(TypeError, match="snapshot must be an exact JobAnalysisSnapshot"): + port.persist_snapshot( + snapshot=_snapshot_subtype(), + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=base_snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(base_snapshot), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), + ) From d88662370530718e9e2fd430b1b545c4df054926 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:51:18 +0900 Subject: [PATCH 075/241] fix(job-analysis): reject snapshot subtypes before command digest --- .../job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 28fcbe62a..77d870cab 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -322,6 +322,8 @@ def command_digest( criterion_blueprint_id: UUID | None, ) -> str: """Return SHA-256 over the exact snapshot bytes plus optional scope identities.""" + if type(snapshot) is not JobAnalysisSnapshot: + raise TypeError("snapshot must be an exact JobAnalysisSnapshot") payload = { "criterion_blueprint_id": None if criterion_blueprint_id is None else str(criterion_blueprint_id), "position_record_id": None if position_record_id is None else str(position_record_id), From fc986dd65233fabf965c7111e0203a4721e25391 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:01:23 +0900 Subject: [PATCH 076/241] fix(job-analysis): reject snapshot subtype at PostgreSQL authority --- .../src/orgmetra_job_analysis_api/postgres.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 706517ca1..8eda2821c 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -237,8 +237,8 @@ def persist_snapshot( ``record_audit_outbox_event`` runs only for a new write, inside the same transaction. """ - if not isinstance(snapshot, JobAnalysisSnapshot): - raise TypeError("snapshot must be a JobAnalysisSnapshot") + if type(snapshot) is not JobAnalysisSnapshot: + raise TypeError("snapshot must be an exact JobAnalysisSnapshot") if type(audit_event) is not AuditOutboxEvent: raise TypeError("audit_event must be an exact AuditOutboxEvent") if not isinstance(idempotency_key, str): From 43c6e6153dc8e436069c21c46a7229ef0f957347 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:02:10 +0900 Subject: [PATCH 077/241] test(job-analysis): reproduce executable snapshot result subtype breach --- .../tests/test_snapshot_runtime_integrity.py | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py index 3b9baebf6..f605c4660 100644 --- a/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py @@ -10,8 +10,23 @@ from orgmetra_hris_kernel import AuditOutboxEvent, JobAnalysisSnapshot from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort -from orgmetra_job_analysis_api.snapshot import command_digest -from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot +from orgmetra_job_analysis_api.snapshot import ( + JobAnalysisIntegrityError, + command_digest, + persist_job_analysis_snapshot, + read_job_analysis_snapshot, +) +from fixtures import ( + ANALYSIS, + IDEMPOTENCY_KEY, + TENANT, + clinical_psychologist_document, + clinical_psychologist_snapshot, + read_policy, + read_principal, + write_policy, + write_principal, +) _ACTOR_REFERENCE = "keyverse:actor-ja-1" _PURPOSE_CODE = "job_analysis_write" @@ -41,6 +56,32 @@ def content_digest(self) -> str: """Reject digest serialization if persistence validation is reordered.""" raise AssertionError("snapshot subtype content_digest consumed before exact-type rejection") + def to_snapshot(self) -> dict[str, object]: + """Reject document export if a service consumes subtype behavior before validation.""" + raise AssertionError("snapshot subtype to_snapshot consumed before exact-type rejection") + + +class _ReturningWritePort: + """Return a configured persistence result without consuming it.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + + def persist_snapshot(self, **_: object) -> JobAnalysisSnapshot: + """Return the adversarial value exactly as a compromised adapter could.""" + return self.result + + +class _ReturningReadPort: + """Return a configured read result without consuming it.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + + def read_snapshot(self, **_: object) -> JobAnalysisSnapshot: + """Return the adversarial value exactly as a compromised adapter could.""" + return self.result + def _snapshot_subtype() -> JobAnalysisSnapshot: """Clone one valid kernel snapshot into a caller-defined runtime subtype and arm it.""" @@ -110,3 +151,36 @@ def test_postgres_rejects_snapshot_subtype_before_fields_or_database() -> None: outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), ) + + +def test_persist_use_case_rejects_write_port_snapshot_subtype_before_export() -> None: + """The service must reject an executable persistence result before document export.""" + with pytest.raises( + JobAnalysisIntegrityError, + match="persisted snapshot has an invalid runtime type", + ): + persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=IDEMPOTENCY_KEY, + purpose_code=_PURPOSE_CODE, + policy=write_policy(), + write_port=_ReturningWritePort(_snapshot_subtype()), + ) + + +def test_read_use_case_rejects_read_port_snapshot_subtype_before_fields_or_export() -> None: + """The service must reject an executable repository result before authority-field access.""" + with pytest.raises( + JobAnalysisIntegrityError, + match="resolved snapshot has an invalid runtime type", + ): + read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=ANALYSIS, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=_ReturningReadPort(_snapshot_subtype()), + ) From c5b39adb242ac34440bc25e209eb75b682e3c138 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:03:56 +0900 Subject: [PATCH 078/241] fix(job-analysis): reject executable snapshot port results --- .../src/orgmetra_job_analysis_api/snapshot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 77d870cab..f73c67bdd 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -447,6 +447,8 @@ def persist_job_analysis_snapshot( outbox_delivery_record_id=uuid4(), write_command_id=uuid4(), ) + if type(persisted) is not JobAnalysisSnapshot: + raise JobAnalysisIntegrityError("persisted snapshot has an invalid runtime type") if persisted.to_snapshot() != snapshot.to_snapshot(): raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") return PersistedJobAnalysisView( @@ -485,6 +487,8 @@ def read_job_analysis_snapshot( ) if snapshot is None: raise JobAnalysisSnapshotNotFound("job-analysis snapshot is unavailable") + if type(snapshot) is not JobAnalysisSnapshot: + raise JobAnalysisIntegrityError("resolved snapshot has an invalid runtime type") if ( snapshot.tenant_record_id != tenant_record_id or snapshot.analysis_record_id != analysis_record_id From 1c0fa1ef6e51b47b76d8efdffd021fa40e46d468 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:11:01 +0900 Subject: [PATCH 079/241] test(job-analysis): reproduce write-port snapshot alias mutation --- .../tests/test_snapshot_runtime_integrity.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py index f605c4660..413ac7c51 100644 --- a/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_runtime_integrity.py @@ -83,6 +83,17 @@ def read_snapshot(self, **_: object) -> JobAnalysisSnapshot: return self.result +class _MutatingWritePort: + """Mutate the exact canonical snapshot supplied by the service and return the alias.""" + + def persist_snapshot(self, **kwargs: object) -> JobAnalysisSnapshot: + """Simulate a defective adapter that rewrites evidence through low-level mutation.""" + snapshot = kwargs["snapshot"] + assert type(snapshot) is JobAnalysisSnapshot + object.__setattr__(snapshot, "analysis_version_code", "clinical-psychologist:mutated") + return snapshot + + def _snapshot_subtype() -> JobAnalysisSnapshot: """Clone one valid kernel snapshot into a caller-defined runtime subtype and arm it.""" snapshot = clinical_psychologist_snapshot() @@ -170,6 +181,20 @@ def test_persist_use_case_rejects_write_port_snapshot_subtype_before_export() -> ) +def test_persist_use_case_detects_exact_snapshot_mutation_by_write_port() -> None: + """Compare persistence to evidence detached before the port can mutate the supplied object.""" + with pytest.raises(JobAnalysisIntegrityError, match="escaped posted payload"): + persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=IDEMPOTENCY_KEY, + purpose_code=_PURPOSE_CODE, + policy=write_policy(), + write_port=_MutatingWritePort(), + ) + + def test_read_use_case_rejects_read_port_snapshot_subtype_before_fields_or_export() -> None: """The service must reject an executable repository result before authority-field access.""" with pytest.raises( From 39f57fdece95f9bdd70b2105cc2567d2c241eedb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:12:53 +0900 Subject: [PATCH 080/241] fix(job-analysis): detach authorized snapshot before persistence --- .../src/orgmetra_job_analysis_api/snapshot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index f73c67bdd..ba005b05a 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -431,6 +431,7 @@ def persist_job_analysis_snapshot( occurred_at=datetime.now(timezone.utc), high_impact=False, ) + authorized_snapshot = snapshot.to_snapshot() persisted = write_port.persist_snapshot( snapshot=snapshot, idempotency_key=key, @@ -449,11 +450,11 @@ def persist_job_analysis_snapshot( ) if type(persisted) is not JobAnalysisSnapshot: raise JobAnalysisIntegrityError("persisted snapshot has an invalid runtime type") - if persisted.to_snapshot() != snapshot.to_snapshot(): + if persisted.to_snapshot() != authorized_snapshot: raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") return PersistedJobAnalysisView( resource_reference=decision.resource_reference, - snapshot=persisted.to_snapshot(), + snapshot=authorized_snapshot, ) From 1d44c7147995d7ac3922d1537ad501efff31d127 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:31:14 +0900 Subject: [PATCH 081/241] test(authz): bind decision reason and next action semantics --- ...horization_decision_consumer_validation.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py b/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py index 41f2987a7..a2e9246f3 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_consumer_validation.py @@ -41,3 +41,50 @@ def test_consumer_validator_rejects_non_decision_runtime_type() -> None: """Caller-defined unrelated objects cannot enter the durable evidence boundary.""" with pytest.raises(TypeError, match="decision must be an AuthorizationDecision"): validate_authorization_decision(object()) # type: ignore[arg-type] + + +def test_consumer_validator_rejects_noncanonical_allow_next_action() -> None: + """Post-construction mutation cannot change the canonical allow recovery contract.""" + decision = _decision() + object.__setattr__(decision, "next_action", "Continue with any fields.") + + with pytest.raises(ValueError, match="allow decision must use the canonical next action"): + validate_authorization_decision(decision) + + +def test_decision_rejects_unknown_denial_reason() -> None: + """Denial evidence must use a governed reason rather than an arbitrary valid code.""" + with pytest.raises(ValueError, match="deny decision must use a known denial reason"): + AuthorizationDecision( + allowed=False, + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference="assignment_record:0198a412800070008000000000000070", + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=FIELDS, + authorized_fields=frozenset(), + reason_code="policy_denied", + next_action="Request another policy decision.", + ) + + +def test_decision_binds_denial_next_action_to_reason() -> None: + """Known denial reasons cannot carry caller-selected recovery instructions.""" + with pytest.raises(ValueError, match="deny decision must use the canonical next action"): + AuthorizationDecision( + allowed=False, + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + resource_reference="assignment_record:0198a412800070008000000000000070", + policy_version_code="assignment-correction-v1", + purpose_code="workforce_admin", + operation_code="correct_record", + resource_kind="assignment_record", + requested_fields=FIELDS, + authorized_fields=frozenset(), + reason_code="purpose_not_allowed", + next_action="Request another policy decision.", + ) From 811360903bdbea27a62e4469db08e8ed95c9e6af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:33:06 +0900 Subject: [PATCH 082/241] fix(authz): bind decision recovery evidence to verdict --- .../src/orgmetra_keyverse_adapter/authorization.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 6c64d75af..44ae26d49 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -393,10 +393,16 @@ def _validated_decision_snapshot( raise ValueError("allow decision must authorize exactly the requested fields.") if not allowed and authorized_fields: raise ValueError("deny decision must not authorize fields.") - if allowed and reason_code != "access_permitted": - raise ValueError("allow decision must use access_permitted reason.") - if not allowed and reason_code == "access_permitted": - raise ValueError("deny decision must not use access_permitted reason.") + if allowed: + if reason_code != "access_permitted": + raise ValueError("allow decision must use access_permitted reason.") + if next_action != _ALLOW_NEXT_ACTION: + raise ValueError("allow decision must use the canonical next action.") + else: + if reason_code not in _DENIAL_NEXT_ACTION: + raise ValueError("deny decision must use a known denial reason.") + if next_action != _DENIAL_NEXT_ACTION[reason_code]: + raise ValueError("deny decision must use the canonical next action.") return _DecisionSnapshot( allowed, tenant_int, From 0a54a069703b32de2fed8d361af0b127c78ace90 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:38:05 +0900 Subject: [PATCH 083/241] test(authz): align runtime oracle with governed recovery evidence --- ...uthorization_decision_runtime_integrity.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 08592cc73..59cc26f1d 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -209,7 +209,9 @@ def test_deny_decision_cannot_carry_authorized_fields() -> None: allowed=False, authorized_fields=REQUESTED_FIELDS, reason_code="field_not_allowed", - next_action="Request only fields allowed for this purpose.", + next_action=( + "Request only fields allowed for this purpose or obtain a separately reviewed field policy." + ), ) @@ -221,7 +223,7 @@ def test_allow_decision_rejects_denial_reason() -> None: def test_deny_decision_rejects_success_reason() -> None: """A deny verdict cannot masquerade as successful authorization evidence.""" - with pytest.raises(ValueError, match="deny decision must not use access_permitted reason"): + with pytest.raises(ValueError, match="deny decision must use a known denial reason"): _validate_decision( allowed=False, authorized_fields=frozenset(), @@ -229,21 +231,25 @@ def test_deny_decision_rejects_success_reason() -> None: ) -def test_decision_validator_accepts_bounded_internal_denial_reason() -> None: - """The pure validator preserves a bounded denial code without conferring authority.""" +def test_decision_validator_accepts_governed_denial_reason_and_action() -> None: + """A denial snapshot preserves the evaluator's exact reason-to-recovery contract.""" + next_action = ( + "Use an approved purpose for this policy or obtain a separately governed policy decision." + ) snapshot = _validate_decision( allowed=False, authorized_fields=frozenset(), - reason_code="access_denied", - next_action="stop", + reason_code="purpose_not_allowed", + next_action=next_action, ) - assert snapshot[10] == "access_denied" + assert snapshot[10] == "purpose_not_allowed" + assert snapshot[11] == next_action -def test_decision_validator_preserves_bounded_actionable_text() -> None: - """Recovery guidance validation remains independent of the governed verdict.""" - snapshot = _validate_decision(next_action="Continue after logging the reviewed evidence.") - assert snapshot[11] == "Continue after logging the reviewed evidence." +def test_decision_validator_rejects_noncanonical_allow_action() -> None: + """Allow evidence cannot replace the evaluator's governed recovery instruction.""" + with pytest.raises(ValueError, match="allow decision must use the canonical next action"): + _validate_decision(next_action="Continue after logging the reviewed evidence.") def test_decision_validator_rejects_resource_reference_namespace_mismatch() -> None: From 6127146de53b55283fc7b0cf8e50d0fb07fb100d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:05:08 +0900 Subject: [PATCH 084/241] test(auth): reject People principal runtime subtypes --- ...t_authenticated_principal_runtime_types.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 services/people-api/tests/test_authenticated_principal_runtime_types.py diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py new file mode 100644 index 000000000..d5c39abba --- /dev/null +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -0,0 +1,58 @@ +"""Regression contracts for authenticated principal runtime-type integrity.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_people_api import AuthenticatedPrincipal + +TENANT = UUID("0198a412-6000-7000-8000-000000000001") +SCOPE = "orgmetra.people.read" + + +class _UUIDSubtype(UUID): + """Caller-defined UUID subtype that must not cross the authentication boundary.""" + + +class _TextSubtype(str): + """Caller-defined text subtype that must not carry identity or scope evidence.""" + + +class _ScopeSetSubtype(frozenset[str]): + """Caller-defined immutable-set subtype that must not carry scope evidence.""" + + +class AuthenticatedPrincipalRuntimeTypeTests(unittest.TestCase): + """Require exact built-in trust-bearing values at principal construction.""" + + def test_rejects_trust_bearing_runtime_subtypes(self) -> None: + cases = ( + { + "tenant_record_id": _UUIDSubtype(TENANT.hex), + "actor_reference": "keyverse:actor-1", + "granted_scope_codes": frozenset({SCOPE}), + }, + { + "tenant_record_id": TENANT, + "actor_reference": _TextSubtype("keyverse:actor-1"), + "granted_scope_codes": frozenset({SCOPE}), + }, + { + "tenant_record_id": TENANT, + "actor_reference": "keyverse:actor-1", + "granted_scope_codes": _ScopeSetSubtype({SCOPE}), + }, + { + "tenant_record_id": TENANT, + "actor_reference": "keyverse:actor-1", + "granted_scope_codes": frozenset({_TextSubtype(SCOPE)}), + }, + ) + for values in cases: + with self.subTest(values=values), self.assertRaises(ValueError): + AuthenticatedPrincipal(**values) + + +if __name__ == "__main__": + unittest.main() From 92d481f3fd82abb669d922a6c75bc487cef79b81 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:05:18 +0900 Subject: [PATCH 085/241] test(auth): reject Job Analysis principal runtime subtypes --- ...t_authenticated_principal_runtime_types.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py new file mode 100644 index 000000000..d4ec729cd --- /dev/null +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -0,0 +1,58 @@ +"""Regression contracts for Job Analysis principal runtime-type integrity.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_job_analysis_api import AuthenticatedPrincipal + +TENANT = UUID("0198a412-6000-7000-8000-000000000001") +SCOPE = "orgmetra.job_architecture.write" + + +class _UUIDSubtype(UUID): + """Caller-defined UUID subtype that must not cross the authentication boundary.""" + + +class _TextSubtype(str): + """Caller-defined text subtype that must not carry identity or scope evidence.""" + + +class _ScopeSetSubtype(frozenset[str]): + """Caller-defined immutable-set subtype that must not carry scope evidence.""" + + +class AuthenticatedPrincipalRuntimeTypeTests(unittest.TestCase): + """Require exact built-in trust-bearing values at principal construction.""" + + def test_rejects_trust_bearing_runtime_subtypes(self) -> None: + cases = ( + { + "tenant_record_id": _UUIDSubtype(TENANT.hex), + "actor_reference": "keyverse:actor-ja-1", + "granted_scope_codes": frozenset({SCOPE}), + }, + { + "tenant_record_id": TENANT, + "actor_reference": _TextSubtype("keyverse:actor-ja-1"), + "granted_scope_codes": frozenset({SCOPE}), + }, + { + "tenant_record_id": TENANT, + "actor_reference": "keyverse:actor-ja-1", + "granted_scope_codes": _ScopeSetSubtype({SCOPE}), + }, + { + "tenant_record_id": TENANT, + "actor_reference": "keyverse:actor-ja-1", + "granted_scope_codes": frozenset({_TextSubtype(SCOPE)}), + }, + ) + for values in cases: + with self.subTest(values=values), self.assertRaises(ValueError): + AuthenticatedPrincipal(**values) + + +if __name__ == "__main__": + unittest.main() From bf62ebb3be02927cb488431139862ff27252cea2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:05:35 +0900 Subject: [PATCH 086/241] fix(auth): reject People principal runtime subtypes --- services/people-api/src/orgmetra_people_api/auth.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index 1bd1cf073..04e10e0bd 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -37,16 +37,16 @@ class AuthenticatedPrincipal: granted_scope_codes: frozenset[str] def __post_init__(self) -> None: - """Reject sentinel identities, mutable grants, wildcards, and bad references.""" - if not isinstance(self.tenant_record_id, UUID): + """Reject sentinel identities, runtime subtypes, wildcards, and bad references.""" + if type(self.tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") if self.tenant_record_id.int in (0, _MAX_UUID_INT): raise ValueError("tenant_record_id must not use a reserved UUID sentinel.") - if not isinstance(self.actor_reference, str) or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: + if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: raise ValueError("actor_reference must be a namespaced opaque reference.") - if not isinstance(self.granted_scope_codes, frozenset) or not self.granted_scope_codes: + if type(self.granted_scope_codes) is not frozenset or not self.granted_scope_codes: raise ValueError("granted_scope_codes must be a non-empty frozenset.") - if any(not isinstance(scope, str) or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): + if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") From 9d9ebd3e966f1a0b5df5b635add38f26dd696556 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:05:50 +0900 Subject: [PATCH 087/241] fix(auth): reject Job Analysis principal runtime subtypes --- .../src/orgmetra_job_analysis_api/auth.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index dc3ef9274..4fd38b231 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -35,16 +35,16 @@ class AuthenticatedPrincipal: granted_scope_codes: frozenset[str] def __post_init__(self) -> None: - """Reject sentinel identities, mutable grants, wildcards, and bad references.""" - if not isinstance(self.tenant_record_id, UUID): + """Reject sentinel identities, runtime subtypes, wildcards, and bad references.""" + if type(self.tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") if self.tenant_record_id.int in (0, _MAX_UUID_INT): raise ValueError("tenant_record_id must not use a reserved UUID sentinel.") - if not isinstance(self.actor_reference, str) or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: + if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: raise ValueError("actor_reference must be a namespaced opaque reference.") - if not isinstance(self.granted_scope_codes, frozenset) or not self.granted_scope_codes: + if type(self.granted_scope_codes) is not frozenset or not self.granted_scope_codes: raise ValueError("granted_scope_codes must be a non-empty frozenset.") - if any(not isinstance(scope, str) or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): + if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") From f764104ad3e5a1f770d99c30646352d41739ef84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:10:06 +0900 Subject: [PATCH 088/241] test(auth): reject executable People principal subclasses --- .../tests/test_authenticated_principal_runtime_types.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py index d5c39abba..3fe9e97a5 100644 --- a/services/people-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -24,7 +24,7 @@ class _ScopeSetSubtype(frozenset[str]): class AuthenticatedPrincipalRuntimeTypeTests(unittest.TestCase): - """Require exact built-in trust-bearing values at principal construction.""" + """Require exact canonical authentication evidence at principal construction.""" def test_rejects_trust_bearing_runtime_subtypes(self) -> None: cases = ( @@ -53,6 +53,13 @@ def test_rejects_trust_bearing_runtime_subtypes(self) -> None: with self.subTest(values=values), self.assertRaises(ValueError): AuthenticatedPrincipal(**values) + def test_principal_runtime_class_cannot_be_subclassed(self) -> None: + """Executable principal subclasses cannot override authenticated evidence access.""" + with self.assertRaisesRegex(TypeError, "AuthenticatedPrincipal must not be subclassed"): + + class _PrincipalSubtype(AuthenticatedPrincipal): + pass + if __name__ == "__main__": unittest.main() From c671c12e7e83f51419c0ed496481b89437f9ac98 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:10:17 +0900 Subject: [PATCH 089/241] test(auth): reject executable Job Analysis principal subclasses --- .../tests/test_authenticated_principal_runtime_types.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py index d4ec729cd..fb9c7137d 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -24,7 +24,7 @@ class _ScopeSetSubtype(frozenset[str]): class AuthenticatedPrincipalRuntimeTypeTests(unittest.TestCase): - """Require exact built-in trust-bearing values at principal construction.""" + """Require exact canonical authentication evidence at principal construction.""" def test_rejects_trust_bearing_runtime_subtypes(self) -> None: cases = ( @@ -53,6 +53,13 @@ def test_rejects_trust_bearing_runtime_subtypes(self) -> None: with self.subTest(values=values), self.assertRaises(ValueError): AuthenticatedPrincipal(**values) + def test_principal_runtime_class_cannot_be_subclassed(self) -> None: + """Executable principal subclasses cannot override authenticated evidence access.""" + with self.assertRaisesRegex(TypeError, "AuthenticatedPrincipal must not be subclassed"): + + class _PrincipalSubtype(AuthenticatedPrincipal): + pass + if __name__ == "__main__": unittest.main() From 88312c45e59fe0ca6ae823e39b1a223c7efd91a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:10:47 +0900 Subject: [PATCH 090/241] fix(auth): seal People authenticated principal runtime class --- services/people-api/src/orgmetra_people_api/auth.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index 04e10e0bd..fca24bded 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -49,6 +49,11 @@ def __post_init__(self) -> None: if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") + def __init_subclass__(cls, **kwargs: object) -> None: + """Prevent executable principal subclasses from overriding authenticated evidence.""" + del kwargs + raise TypeError("AuthenticatedPrincipal must not be subclassed") + @runtime_checkable class TokenAuthenticator(Protocol): From 2b6b6176276d668d049dee48b12539aaa68b892f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:11:04 +0900 Subject: [PATCH 091/241] fix(auth): seal Job Analysis authenticated principal runtime class --- .../job-analysis-api/src/orgmetra_job_analysis_api/auth.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 4fd38b231..95527361d 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -47,6 +47,11 @@ def __post_init__(self) -> None: if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") + def __init_subclass__(cls, **kwargs: object) -> None: + """Prevent executable principal subclasses from overriding authenticated evidence.""" + del kwargs + raise TypeError("AuthenticatedPrincipal must not be subclassed") + @runtime_checkable class TokenAuthenticator(Protocol): From d1fba05edaed849e963e9724a77d26ec246aaab4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:16:37 +0900 Subject: [PATCH 092/241] test(auth): detach People principal tenant UUID evidence --- ...t_authenticated_principal_runtime_types.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py index 3fe9e97a5..6f6c519cc 100644 --- a/services/people-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -8,6 +8,7 @@ from orgmetra_people_api import AuthenticatedPrincipal TENANT = UUID("0198a412-6000-7000-8000-000000000001") +OTHER_TENANT = UUID("0198a412-6000-7000-8000-000000000002") SCOPE = "orgmetra.people.read" @@ -60,6 +61,32 @@ def test_principal_runtime_class_cannot_be_subclassed(self) -> None: class _PrincipalSubtype(AuthenticatedPrincipal): pass + def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: + """Post-construction mutation of the caller UUID cannot retarget the principal.""" + tenant_record_id = UUID(TENANT.hex) + principal = AuthenticatedPrincipal( + tenant_record_id=tenant_record_id, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + + object.__setattr__(tenant_record_id, "int", OTHER_TENANT.int) + + self.assertEqual(principal.tenant_record_id, TENANT) + self.assertIsNot(principal.tenant_record_id, tenant_record_id) + + def test_rejects_corrupted_exact_uuid_state(self) -> None: + """An exact UUID with an invalid internal integer cannot become identity evidence.""" + tenant_record_id = UUID(TENANT.hex) + object.__setattr__(tenant_record_id, "int", "not-an-integer") + + with self.assertRaisesRegex(ValueError, "tenant_record_id must contain a valid UUID integer"): + AuthenticatedPrincipal( + tenant_record_id=tenant_record_id, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + if __name__ == "__main__": unittest.main() From b6fa64c29c56330909670775e01855c3f5a0cc2f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:16:53 +0900 Subject: [PATCH 093/241] test(auth): detach Job Analysis principal tenant UUID evidence --- ...t_authenticated_principal_runtime_types.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py index fb9c7137d..cfdf31dc7 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -8,6 +8,7 @@ from orgmetra_job_analysis_api import AuthenticatedPrincipal TENANT = UUID("0198a412-6000-7000-8000-000000000001") +OTHER_TENANT = UUID("0198a412-6000-7000-8000-000000000002") SCOPE = "orgmetra.job_architecture.write" @@ -60,6 +61,32 @@ def test_principal_runtime_class_cannot_be_subclassed(self) -> None: class _PrincipalSubtype(AuthenticatedPrincipal): pass + def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: + """Post-construction mutation of the caller UUID cannot retarget the principal.""" + tenant_record_id = UUID(TENANT.hex) + principal = AuthenticatedPrincipal( + tenant_record_id=tenant_record_id, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + + object.__setattr__(tenant_record_id, "int", OTHER_TENANT.int) + + self.assertEqual(principal.tenant_record_id, TENANT) + self.assertIsNot(principal.tenant_record_id, tenant_record_id) + + def test_rejects_corrupted_exact_uuid_state(self) -> None: + """An exact UUID with an invalid internal integer cannot become identity evidence.""" + tenant_record_id = UUID(TENANT.hex) + object.__setattr__(tenant_record_id, "int", "not-an-integer") + + with self.assertRaisesRegex(ValueError, "tenant_record_id must contain a valid UUID integer"): + AuthenticatedPrincipal( + tenant_record_id=tenant_record_id, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + if __name__ == "__main__": unittest.main() From d3fbc97f9f971b5c49dbb93e606c213d796743cf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:17:18 +0900 Subject: [PATCH 094/241] fix(auth): detach People principal tenant UUID evidence --- services/people-api/src/orgmetra_people_api/auth.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index fca24bded..a0f2c8ac8 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -37,10 +37,13 @@ class AuthenticatedPrincipal: granted_scope_codes: frozenset[str] def __post_init__(self) -> None: - """Reject sentinel identities, runtime subtypes, wildcards, and bad references.""" + """Validate and detach exact identity/scope evidence before service use.""" if type(self.tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") - if self.tenant_record_id.int in (0, _MAX_UUID_INT): + tenant_record_id_int = self.tenant_record_id.int + if type(tenant_record_id_int) is not int or not 0 <= tenant_record_id_int <= _MAX_UUID_INT: + raise ValueError("tenant_record_id must contain a valid UUID integer.") + if tenant_record_id_int in (0, _MAX_UUID_INT): raise ValueError("tenant_record_id must not use a reserved UUID sentinel.") if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: raise ValueError("actor_reference must be a namespaced opaque reference.") @@ -48,6 +51,7 @@ def __post_init__(self) -> None: raise ValueError("granted_scope_codes must be a non-empty frozenset.") if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") + object.__setattr__(self, "tenant_record_id", UUID(int=tenant_record_id_int)) def __init_subclass__(cls, **kwargs: object) -> None: """Prevent executable principal subclasses from overriding authenticated evidence.""" From b5f4a1da2871b75d8c5ebf46969b041d63d531ec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:17:34 +0900 Subject: [PATCH 095/241] fix(auth): detach Job Analysis principal tenant UUID evidence --- .../src/orgmetra_job_analysis_api/auth.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 95527361d..fc2596bfb 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -35,10 +35,13 @@ class AuthenticatedPrincipal: granted_scope_codes: frozenset[str] def __post_init__(self) -> None: - """Reject sentinel identities, runtime subtypes, wildcards, and bad references.""" + """Validate and detach exact identity/scope evidence before service use.""" if type(self.tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") - if self.tenant_record_id.int in (0, _MAX_UUID_INT): + tenant_record_id_int = self.tenant_record_id.int + if type(tenant_record_id_int) is not int or not 0 <= tenant_record_id_int <= _MAX_UUID_INT: + raise ValueError("tenant_record_id must contain a valid UUID integer.") + if tenant_record_id_int in (0, _MAX_UUID_INT): raise ValueError("tenant_record_id must not use a reserved UUID sentinel.") if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: raise ValueError("actor_reference must be a namespaced opaque reference.") @@ -46,6 +49,7 @@ def __post_init__(self) -> None: raise ValueError("granted_scope_codes must be a non-empty frozenset.") if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") + object.__setattr__(self, "tenant_record_id", UUID(int=tenant_record_id_int)) def __init_subclass__(cls, **kwargs: object) -> None: """Prevent executable principal subclasses from overriding authenticated evidence.""" From 73e5b5c5b44c2ab338dacc8f33930d377a18df93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:33:15 +0900 Subject: [PATCH 096/241] test(auth): reproduce principal evidence rewrite --- ...t_authenticated_principal_runtime_types.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py index 6f6c519cc..a956cae9d 100644 --- a/services/people-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -75,6 +75,27 @@ def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: self.assertEqual(principal.tenant_record_id, TENANT) self.assertIsNot(principal.tenant_record_id, tenant_record_id) + def test_principal_evidence_cannot_be_rewritten_after_authentication(self) -> None: + """Low-level writes must not replace authenticated evidence on a live principal.""" + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + cases = ( + ("tenant_record_id", OTHER_TENANT), + ("actor_reference", "keyverse:actor-2"), + ("granted_scope_codes", frozenset({"orgmetra.people.write"})), + ) + + for field_name, replacement in cases: + with self.subTest(field_name=field_name), self.assertRaises((AttributeError, TypeError)): + object.__setattr__(principal, field_name, replacement) + + self.assertEqual(principal.tenant_record_id, TENANT) + self.assertEqual(principal.actor_reference, "keyverse:actor-1") + self.assertEqual(principal.granted_scope_codes, frozenset({SCOPE})) + def test_rejects_corrupted_exact_uuid_state(self) -> None: """An exact UUID with an invalid internal integer cannot become identity evidence.""" tenant_record_id = UUID(TENANT.hex) From 59c37bcec175e13ea385122f9765dedc8a0d551b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:33:54 +0900 Subject: [PATCH 097/241] test(auth): reproduce job-analysis principal rewrite --- ...t_authenticated_principal_runtime_types.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py index cfdf31dc7..2fc95dd90 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -75,6 +75,27 @@ def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: self.assertEqual(principal.tenant_record_id, TENANT) self.assertIsNot(principal.tenant_record_id, tenant_record_id) + def test_principal_evidence_cannot_be_rewritten_after_authentication(self) -> None: + """Low-level writes must not replace authenticated evidence on a live principal.""" + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + cases = ( + ("tenant_record_id", OTHER_TENANT), + ("actor_reference", "keyverse:actor-ja-2"), + ("granted_scope_codes", frozenset({"orgmetra.job_architecture.read"})), + ) + + for field_name, replacement in cases: + with self.subTest(field_name=field_name), self.assertRaises((AttributeError, TypeError)): + object.__setattr__(principal, field_name, replacement) + + self.assertEqual(principal.tenant_record_id, TENANT) + self.assertEqual(principal.actor_reference, "keyverse:actor-ja-1") + self.assertEqual(principal.granted_scope_codes, frozenset({SCOPE})) + def test_rejects_corrupted_exact_uuid_state(self) -> None: """An exact UUID with an invalid internal integer cannot become identity evidence.""" tenant_record_id = UUID(TENANT.hex) From 2225c3d26f8131ba8f66b8c66658754b4cca781d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:34:42 +0900 Subject: [PATCH 098/241] fix(auth): make People principal structurally immutable --- .../src/orgmetra_people_api/auth.py | 74 +++++++++++++++---- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index a0f2c8ac8..cba1cbc4d 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -8,7 +8,6 @@ from __future__ import annotations -from dataclasses import dataclass import re from typing import Protocol, runtime_checkable from uuid import UUID @@ -22,36 +21,79 @@ class AuthenticationFailed(RuntimeError): """Indicate that bearer authentication evidence is absent or malformed.""" -@dataclass(frozen=True, slots=True) -class AuthenticatedPrincipal: - """Identity attributes that may be trusted only after token authentication. +class AuthenticatedPrincipal(tuple[UUID, str, frozenset[str]]): + """Structurally immutable identity evidence returned by token authentication. ``tenant_record_id`` binds the authenticated actor to one Orgmetra tenant. ``actor_reference`` is opaque audit correlation rather than a person record identifier. ``granted_scope_codes`` carries explicit operation capabilities; it never carries an HR purpose decision. - """ - tenant_record_id: UUID - actor_reference: str - granted_scope_codes: frozenset[str] + Tuple-backed storage deliberately leaves no writable instance slots. This + prevents low-level field replacement after authentication while preserving + value semantics for trusted service code. + """ - def __post_init__(self) -> None: - """Validate and detach exact identity/scope evidence before service use.""" - if type(self.tenant_record_id) is not UUID: + __slots__ = () + __match_args__ = ("tenant_record_id", "actor_reference", "granted_scope_codes") + + def __new__( + cls, + tenant_record_id: UUID, + actor_reference: str, + granted_scope_codes: frozenset[str], + ) -> AuthenticatedPrincipal: + """Validate, detach, and store exact authentication evidence once.""" + if type(tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") - tenant_record_id_int = self.tenant_record_id.int + tenant_record_id_int = tenant_record_id.int if type(tenant_record_id_int) is not int or not 0 <= tenant_record_id_int <= _MAX_UUID_INT: raise ValueError("tenant_record_id must contain a valid UUID integer.") if tenant_record_id_int in (0, _MAX_UUID_INT): raise ValueError("tenant_record_id must not use a reserved UUID sentinel.") - if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: + if type(actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(actor_reference) is None: raise ValueError("actor_reference must be a namespaced opaque reference.") - if type(self.granted_scope_codes) is not frozenset or not self.granted_scope_codes: + if type(granted_scope_codes) is not frozenset or not granted_scope_codes: raise ValueError("granted_scope_codes must be a non-empty frozenset.") - if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): + if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") - object.__setattr__(self, "tenant_record_id", UUID(int=tenant_record_id_int)) + return tuple.__new__(cls, (UUID(int=tenant_record_id_int), actor_reference, granted_scope_codes)) + + @property + def tenant_record_id(self) -> UUID: + """Return the detached authenticated tenant identifier.""" + return self[0] + + @property + def actor_reference(self) -> str: + """Return the opaque authenticated actor correlation reference.""" + return self[1] + + @property + def granted_scope_codes(self) -> frozenset[str]: + """Return the exact operation scopes issued at authentication.""" + return self[2] + + def __repr__(self) -> str: + """Render the same field-oriented diagnostic shape as the prior value object.""" + return ( + "AuthenticatedPrincipal(" + f"tenant_record_id={self.tenant_record_id!r}, " + f"actor_reference={self.actor_reference!r}, " + f"granted_scope_codes={self.granted_scope_codes!r})" + ) + + def __eq__(self, other: object) -> bool: + """Compare only another exact authenticated-principal value.""" + if type(other) is not AuthenticatedPrincipal: + return NotImplemented + return tuple.__eq__(self, other) + + __hash__ = tuple.__hash__ + + def __getnewargs__(self) -> tuple[UUID, str, frozenset[str]]: + """Preserve validated constructor arguments for standard value reconstruction.""" + return (self.tenant_record_id, self.actor_reference, self.granted_scope_codes) def __init_subclass__(cls, **kwargs: object) -> None: """Prevent executable principal subclasses from overriding authenticated evidence.""" From a4e84c4c5c5b18d1c134bb3491eee757357ffdc8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:35:10 +0900 Subject: [PATCH 099/241] fix(auth): make Job Analysis principal structurally immutable --- .../src/orgmetra_job_analysis_api/auth.py | 74 +++++++++++++++---- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index fc2596bfb..9c50dcc4c 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -7,7 +7,6 @@ from __future__ import annotations -from dataclasses import dataclass import re from typing import Protocol, runtime_checkable from uuid import UUID @@ -21,35 +20,78 @@ class AuthenticationFailed(RuntimeError): """Indicate that bearer authentication evidence is absent or malformed.""" -@dataclass(frozen=True, slots=True) -class AuthenticatedPrincipal: - """Identity attributes that may be trusted only after token authentication. +class AuthenticatedPrincipal(tuple[UUID, str, frozenset[str]]): + """Structurally immutable identity evidence returned by token authentication. ``tenant_record_id`` binds the authenticated actor to one Orgmetra tenant. ``actor_reference`` is opaque audit correlation. ``granted_scope_codes`` carries explicit operation capabilities and never an HR purpose decision. - """ - tenant_record_id: UUID - actor_reference: str - granted_scope_codes: frozenset[str] + Tuple-backed storage deliberately leaves no writable instance slots. This + prevents low-level field replacement after authentication while preserving + value semantics for trusted service code. + """ - def __post_init__(self) -> None: - """Validate and detach exact identity/scope evidence before service use.""" - if type(self.tenant_record_id) is not UUID: + __slots__ = () + __match_args__ = ("tenant_record_id", "actor_reference", "granted_scope_codes") + + def __new__( + cls, + tenant_record_id: UUID, + actor_reference: str, + granted_scope_codes: frozenset[str], + ) -> AuthenticatedPrincipal: + """Validate, detach, and store exact authentication evidence once.""" + if type(tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") - tenant_record_id_int = self.tenant_record_id.int + tenant_record_id_int = tenant_record_id.int if type(tenant_record_id_int) is not int or not 0 <= tenant_record_id_int <= _MAX_UUID_INT: raise ValueError("tenant_record_id must contain a valid UUID integer.") if tenant_record_id_int in (0, _MAX_UUID_INT): raise ValueError("tenant_record_id must not use a reserved UUID sentinel.") - if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: + if type(actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(actor_reference) is None: raise ValueError("actor_reference must be a namespaced opaque reference.") - if type(self.granted_scope_codes) is not frozenset or not self.granted_scope_codes: + if type(granted_scope_codes) is not frozenset or not granted_scope_codes: raise ValueError("granted_scope_codes must be a non-empty frozenset.") - if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in self.granted_scope_codes): + if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") - object.__setattr__(self, "tenant_record_id", UUID(int=tenant_record_id_int)) + return tuple.__new__(cls, (UUID(int=tenant_record_id_int), actor_reference, granted_scope_codes)) + + @property + def tenant_record_id(self) -> UUID: + """Return the detached authenticated tenant identifier.""" + return self[0] + + @property + def actor_reference(self) -> str: + """Return the opaque authenticated actor correlation reference.""" + return self[1] + + @property + def granted_scope_codes(self) -> frozenset[str]: + """Return the exact operation scopes issued at authentication.""" + return self[2] + + def __repr__(self) -> str: + """Render the same field-oriented diagnostic shape as the prior value object.""" + return ( + "AuthenticatedPrincipal(" + f"tenant_record_id={self.tenant_record_id!r}, " + f"actor_reference={self.actor_reference!r}, " + f"granted_scope_codes={self.granted_scope_codes!r})" + ) + + def __eq__(self, other: object) -> bool: + """Compare only another exact authenticated-principal value.""" + if type(other) is not AuthenticatedPrincipal: + return NotImplemented + return tuple.__eq__(self, other) + + __hash__ = tuple.__hash__ + + def __getnewargs__(self) -> tuple[UUID, str, frozenset[str]]: + """Preserve validated constructor arguments for standard value reconstruction.""" + return (self.tenant_record_id, self.actor_reference, self.granted_scope_codes) def __init_subclass__(cls, **kwargs: object) -> None: """Prevent executable principal subclasses from overriding authenticated evidence.""" From eb4a177370d2337ec0e3d60c6d26bd68eefbaa64 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:36:20 +0900 Subject: [PATCH 100/241] test(auth): preserve principal value semantics --- ...t_authenticated_principal_runtime_types.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py index a956cae9d..1fb1a5c43 100644 --- a/services/people-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -2,6 +2,7 @@ from __future__ import annotations +import pickle import unittest from uuid import UUID @@ -96,6 +97,32 @@ def test_principal_evidence_cannot_be_rewritten_after_authentication(self) -> No self.assertEqual(principal.actor_reference, "keyverse:actor-1") self.assertEqual(principal.granted_scope_codes, frozenset({SCOPE})) + def test_structural_storage_preserves_value_object_semantics(self) -> None: + """Structural immutability must not collapse the principal into a raw tuple value.""" + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + equivalent = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + raw_tuple = (TENANT, "keyverse:actor-1", frozenset({SCOPE})) + + self.assertEqual(principal, equivalent) + self.assertEqual(hash(principal), hash(equivalent)) + self.assertNotEqual(principal, raw_tuple) + self.assertNotEqual(raw_tuple, principal) + self.assertEqual(pickle.loads(pickle.dumps(principal)), principal) + self.assertEqual( + repr(principal), + "AuthenticatedPrincipal(" + f"tenant_record_id={TENANT!r}, actor_reference='keyverse:actor-1', " + f"granted_scope_codes={frozenset({SCOPE})!r})", + ) + def test_rejects_corrupted_exact_uuid_state(self) -> None: """An exact UUID with an invalid internal integer cannot become identity evidence.""" tenant_record_id = UUID(TENANT.hex) From 1ceca3884a2872fd3de6fd812f4f5e3bfdde66ae Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:36:54 +0900 Subject: [PATCH 101/241] test(auth): preserve job-analysis principal value semantics --- ...t_authenticated_principal_runtime_types.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py index 2fc95dd90..36bb94ba4 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -2,6 +2,7 @@ from __future__ import annotations +import pickle import unittest from uuid import UUID @@ -96,6 +97,32 @@ def test_principal_evidence_cannot_be_rewritten_after_authentication(self) -> No self.assertEqual(principal.actor_reference, "keyverse:actor-ja-1") self.assertEqual(principal.granted_scope_codes, frozenset({SCOPE})) + def test_structural_storage_preserves_value_object_semantics(self) -> None: + """Structural immutability must not collapse the principal into a raw tuple value.""" + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + equivalent = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + raw_tuple = (TENANT, "keyverse:actor-ja-1", frozenset({SCOPE})) + + self.assertEqual(principal, equivalent) + self.assertEqual(hash(principal), hash(equivalent)) + self.assertNotEqual(principal, raw_tuple) + self.assertNotEqual(raw_tuple, principal) + self.assertEqual(pickle.loads(pickle.dumps(principal)), principal) + self.assertEqual( + repr(principal), + "AuthenticatedPrincipal(" + f"tenant_record_id={TENANT!r}, actor_reference='keyverse:actor-ja-1', " + f"granted_scope_codes={frozenset({SCOPE})!r})", + ) + def test_rejects_corrupted_exact_uuid_state(self) -> None: """An exact UUID with an invalid internal integer cannot become identity evidence.""" tenant_record_id = UUID(TENANT.hex) From f35ee42d8fcb7948f9a28656bd428a01b1886062 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:37:48 +0900 Subject: [PATCH 102/241] fix(auth): preserve strict People principal equality --- services/people-api/src/orgmetra_people_api/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index cba1cbc4d..4f8746f95 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -86,7 +86,7 @@ def __repr__(self) -> str: def __eq__(self, other: object) -> bool: """Compare only another exact authenticated-principal value.""" if type(other) is not AuthenticatedPrincipal: - return NotImplemented + return False return tuple.__eq__(self, other) __hash__ = tuple.__hash__ From e588c9165f2683a7d3399cc4f5e299b3207a6b95 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 22:38:15 +0900 Subject: [PATCH 103/241] fix(auth): preserve strict Job Analysis principal equality --- services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 9c50dcc4c..112757a39 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -84,7 +84,7 @@ def __repr__(self) -> str: def __eq__(self, other: object) -> bool: """Compare only another exact authenticated-principal value.""" if type(other) is not AuthenticatedPrincipal: - return NotImplemented + return False return tuple.__eq__(self, other) __hash__ = tuple.__hash__ From 5e1eb4a5146ca1fb29e93a66f7475c469c14a9ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 23:03:03 +0900 Subject: [PATCH 104/241] fix(auth): keep principal inequality strict The tuple-backed authentication principals intentionally reject equality with raw tuples, but inherited tuple.__ne__ still reported equal-content raw tuples as not-unequal. The existing compatibility regressions already require strict principal-only inequality in both operand orders. Define __ne__ consistently for People and Job Analysis principals without changing validated storage or authentication semantics. --- .../job-analysis-api/src/orgmetra_job_analysis_api/auth.py | 6 ++++++ services/people-api/src/orgmetra_people_api/auth.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 112757a39..ecb44564d 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -87,6 +87,12 @@ def __eq__(self, other: object) -> bool: return False return tuple.__eq__(self, other) + def __ne__(self, other: object) -> bool: + """Keep inequality consistent with strict principal-only equality.""" + if type(other) is not AuthenticatedPrincipal: + return True + return tuple.__ne__(self, other) + __hash__ = tuple.__hash__ def __getnewargs__(self) -> tuple[UUID, str, frozenset[str]]: diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index 4f8746f95..64f70b9e7 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -89,6 +89,12 @@ def __eq__(self, other: object) -> bool: return False return tuple.__eq__(self, other) + def __ne__(self, other: object) -> bool: + """Keep inequality consistent with strict principal-only equality.""" + if type(other) is not AuthenticatedPrincipal: + return True + return tuple.__ne__(self, other) + __hash__ = tuple.__hash__ def __getnewargs__(self) -> tuple[UUID, str, frozenset[str]]: From 92f7eb41ab37247a65c1da5950ae6965e78abe2a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 23:10:16 +0900 Subject: [PATCH 105/241] test(auth): protect returned tenant UUID evidence The tuple-backed principals detach the caller-owned UUID at construction, but their tenant_record_id property currently returns the stored UUID object itself. A caller can mutate that returned UUID through low-level object.__setattr__ and retarget live authentication evidence inside the tuple. Add People and Job Analysis regressions requiring returned UUID values to be detached from stored authority. --- .../test_authenticated_principal_runtime_types.py | 14 ++++++++++++++ .../test_authenticated_principal_runtime_types.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py index 36bb94ba4..7417ddea4 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -76,6 +76,20 @@ def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: self.assertEqual(principal.tenant_record_id, TENANT) self.assertIsNot(principal.tenant_record_id, tenant_record_id) + def test_returned_tenant_uuid_cannot_retarget_principal(self) -> None: + """Mutating a returned UUID value cannot rewrite stored tenant evidence.""" + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + returned_tenant_record_id = principal.tenant_record_id + + object.__setattr__(returned_tenant_record_id, "int", OTHER_TENANT.int) + + self.assertEqual(principal.tenant_record_id, TENANT) + self.assertIsNot(principal.tenant_record_id, returned_tenant_record_id) + def test_principal_evidence_cannot_be_rewritten_after_authentication(self) -> None: """Low-level writes must not replace authenticated evidence on a live principal.""" principal = AuthenticatedPrincipal( diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py index 1fb1a5c43..b1ee99ca7 100644 --- a/services/people-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -76,6 +76,20 @@ def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: self.assertEqual(principal.tenant_record_id, TENANT) self.assertIsNot(principal.tenant_record_id, tenant_record_id) + def test_returned_tenant_uuid_cannot_retarget_principal(self) -> None: + """Mutating a returned UUID value cannot rewrite stored tenant evidence.""" + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + returned_tenant_record_id = principal.tenant_record_id + + object.__setattr__(returned_tenant_record_id, "int", OTHER_TENANT.int) + + self.assertEqual(principal.tenant_record_id, TENANT) + self.assertIsNot(principal.tenant_record_id, returned_tenant_record_id) + def test_principal_evidence_cannot_be_rewritten_after_authentication(self) -> None: """Low-level writes must not replace authenticated evidence on a live principal.""" principal = AuthenticatedPrincipal( From 64deb92f65c6645ba104f93dca26dc93a48e4b36 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 23:11:51 +0900 Subject: [PATCH 106/241] fix(auth): detach returned People tenant UUID --- .../people-api/src/orgmetra_people_api/auth.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index 64f70b9e7..e4b5cf39e 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -21,7 +21,7 @@ class AuthenticationFailed(RuntimeError): """Indicate that bearer authentication evidence is absent or malformed.""" -class AuthenticatedPrincipal(tuple[UUID, str, frozenset[str]]): +class AuthenticatedPrincipal(tuple[int, str, frozenset[str]]): """Structurally immutable identity evidence returned by token authentication. ``tenant_record_id`` binds the authenticated actor to one Orgmetra tenant. @@ -29,9 +29,9 @@ class AuthenticatedPrincipal(tuple[UUID, str, frozenset[str]]): identifier. ``granted_scope_codes`` carries explicit operation capabilities; it never carries an HR purpose decision. - Tuple-backed storage deliberately leaves no writable instance slots. This - prevents low-level field replacement after authentication while preserving - value semantics for trusted service code. + Tuple-backed storage deliberately leaves no writable instance slots. The + tenant UUID is stored as its validated integer and reconstructed on access, + so neither the caller's UUID nor a returned UUID aliases stored authority. """ __slots__ = () @@ -57,12 +57,12 @@ def __new__( raise ValueError("granted_scope_codes must be a non-empty frozenset.") if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") - return tuple.__new__(cls, (UUID(int=tenant_record_id_int), actor_reference, granted_scope_codes)) + return tuple.__new__(cls, (tenant_record_id_int, actor_reference, granted_scope_codes)) @property def tenant_record_id(self) -> UUID: - """Return the detached authenticated tenant identifier.""" - return self[0] + """Return a detached authenticated tenant identifier value.""" + return UUID(int=self[0]) @property def actor_reference(self) -> str: From b468accb8a1db0f268b0c44f65e8f4bbb16e95e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 23:12:15 +0900 Subject: [PATCH 107/241] fix(auth): detach returned Job Analysis tenant UUID --- .../src/orgmetra_job_analysis_api/auth.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index ecb44564d..971832539 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -20,16 +20,16 @@ class AuthenticationFailed(RuntimeError): """Indicate that bearer authentication evidence is absent or malformed.""" -class AuthenticatedPrincipal(tuple[UUID, str, frozenset[str]]): +class AuthenticatedPrincipal(tuple[int, str, frozenset[str]]): """Structurally immutable identity evidence returned by token authentication. ``tenant_record_id`` binds the authenticated actor to one Orgmetra tenant. ``actor_reference`` is opaque audit correlation. ``granted_scope_codes`` carries explicit operation capabilities and never an HR purpose decision. - Tuple-backed storage deliberately leaves no writable instance slots. This - prevents low-level field replacement after authentication while preserving - value semantics for trusted service code. + Tuple-backed storage deliberately leaves no writable instance slots. The + tenant UUID is stored as its validated integer and reconstructed on access, + so neither the caller's UUID nor a returned UUID aliases stored authority. """ __slots__ = () @@ -55,12 +55,12 @@ def __new__( raise ValueError("granted_scope_codes must be a non-empty frozenset.") if any(type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None for scope in granted_scope_codes): raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") - return tuple.__new__(cls, (UUID(int=tenant_record_id_int), actor_reference, granted_scope_codes)) + return tuple.__new__(cls, (tenant_record_id_int, actor_reference, granted_scope_codes)) @property def tenant_record_id(self) -> UUID: - """Return the detached authenticated tenant identifier.""" - return self[0] + """Return a detached authenticated tenant identifier value.""" + return UUID(int=self[0]) @property def actor_reference(self) -> str: From 1d13d6d3a90c05ad9d924aac0b1219f1cdfaf4c1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:07:50 +0900 Subject: [PATCH 108/241] test(people): expose tuple principal validation bypass --- ...nticated_principal_storage_revalidation.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 services/people-api/tests/test_authenticated_principal_storage_revalidation.py diff --git a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py new file mode 100644 index 000000000..1bfcecb76 --- /dev/null +++ b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py @@ -0,0 +1,87 @@ +"""Regression contracts for tuple-level principal storage revalidation.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_people_api import AuthenticatedPrincipal + +TENANT = UUID("0198a412-6200-7000-8000-000000000001") +SCOPE = "orgmetra.people.read" + + +class _TextSubtype(str): + """Caller-controlled text runtime behavior that cannot become identity evidence.""" + + +class AuthenticatedPrincipalStorageRevalidationTests(unittest.TestCase): + """Require every public principal view to revalidate tuple-backed evidence.""" + + def test_tuple_constructor_bypass_cannot_publish_unvalidated_actor_evidence(self) -> None: + """A direct base-tuple constructor must not bypass actor runtime validation.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, _TextSubtype("keyverse:actor-1"), frozenset({SCOPE})), + ) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged.actor_reference + + def test_tuple_constructor_bypass_cannot_publish_unvalidated_scope_evidence(self) -> None: + """A direct base-tuple constructor must not bypass scope runtime validation.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, "keyverse:actor-1", frozenset({_TextSubtype(SCOPE)})), + ) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged.granted_scope_codes + + def test_tuple_constructor_bypass_cannot_publish_malformed_storage_shape(self) -> None: + """Malformed tuple arity must fail through the stable principal integrity contract.""" + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, "keyverse:actor-1")) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged.tenant_record_id + + def test_malformed_storage_cannot_participate_in_value_semantics(self) -> None: + """Hash, comparison, and repr must not legitimize malformed authentication evidence.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, object(), frozenset({SCOPE})), + ) + canonical = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + hash(forged) + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged == canonical + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + repr(forged) + + def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenance(self) -> None: + """Valid structural evidence stays readable without treating construction history as authority.""" + structurally_valid = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, "keyverse:actor-1", frozenset({SCOPE})), + ) + canonical = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-1", + granted_scope_codes=frozenset({SCOPE}), + ) + + self.assertEqual(structurally_valid.tenant_record_id, TENANT) + self.assertEqual(structurally_valid.actor_reference, "keyverse:actor-1") + self.assertEqual(structurally_valid.granted_scope_codes, frozenset({SCOPE})) + self.assertEqual(structurally_valid, canonical) + self.assertEqual(hash(structurally_valid), hash(canonical)) + + +if __name__ == "__main__": + unittest.main() From 1d2eec5a7ba774c4ebe9081f2b2fdc0827e8d74b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:08:05 +0900 Subject: [PATCH 109/241] test(job-analysis): expose tuple principal validation bypass --- ...nticated_principal_storage_revalidation.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py diff --git a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py new file mode 100644 index 000000000..76f477ec4 --- /dev/null +++ b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py @@ -0,0 +1,87 @@ +"""Regression contracts for tuple-level principal storage revalidation.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_job_analysis_api import AuthenticatedPrincipal + +TENANT = UUID("0198a412-6200-7000-8000-000000000101") +SCOPE = "orgmetra.job_architecture.read" + + +class _TextSubtype(str): + """Caller-controlled text runtime behavior that cannot become identity evidence.""" + + +class AuthenticatedPrincipalStorageRevalidationTests(unittest.TestCase): + """Require every public principal view to revalidate tuple-backed evidence.""" + + def test_tuple_constructor_bypass_cannot_publish_unvalidated_actor_evidence(self) -> None: + """A direct base-tuple constructor must not bypass actor runtime validation.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, _TextSubtype("keyverse:actor-ja-1"), frozenset({SCOPE})), + ) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged.actor_reference + + def test_tuple_constructor_bypass_cannot_publish_unvalidated_scope_evidence(self) -> None: + """A direct base-tuple constructor must not bypass scope runtime validation.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, "keyverse:actor-ja-1", frozenset({_TextSubtype(SCOPE)})), + ) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged.granted_scope_codes + + def test_tuple_constructor_bypass_cannot_publish_malformed_storage_shape(self) -> None: + """Malformed tuple arity must fail through the stable principal integrity contract.""" + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, "keyverse:actor-ja-1")) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged.tenant_record_id + + def test_malformed_storage_cannot_participate_in_value_semantics(self) -> None: + """Hash, comparison, and repr must not legitimize malformed authentication evidence.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, object(), frozenset({SCOPE})), + ) + canonical = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + hash(forged) + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + _ = forged == canonical + with self.assertRaisesRegex(ValueError, "stored authentication evidence"): + repr(forged) + + def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenance(self) -> None: + """Valid structural evidence stays readable without treating construction history as authority.""" + structurally_valid = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, "keyverse:actor-ja-1", frozenset({SCOPE})), + ) + canonical = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse:actor-ja-1", + granted_scope_codes=frozenset({SCOPE}), + ) + + self.assertEqual(structurally_valid.tenant_record_id, TENANT) + self.assertEqual(structurally_valid.actor_reference, "keyverse:actor-ja-1") + self.assertEqual(structurally_valid.granted_scope_codes, frozenset({SCOPE})) + self.assertEqual(structurally_valid, canonical) + self.assertEqual(hash(structurally_valid), hash(canonical)) + + +if __name__ == "__main__": + unittest.main() From 3fcbe7634d9af359334b2b8ed0a8d384b7fd0e1b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:08:29 +0900 Subject: [PATCH 110/241] fix(people): revalidate tuple-backed principal storage --- .../src/orgmetra_people_api/auth.py | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index e4b5cf39e..408cbc8ed 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -17,6 +17,40 @@ _SCOPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") +def _validated_principal_storage( + value: tuple[int, str, frozenset[str]], +) -> tuple[int, str, frozenset[str]]: + """Return exact tuple-backed identity evidence or reject malformed storage. + + ``tuple.__new__`` can instantiate a tuple subclass without invoking that + subclass's public constructor. Public principal behavior therefore cannot + assume that tuple storage was validated merely because the runtime class is + exact. Revalidating the raw built-in tuple slots keeps request-edge consumers + fail-closed without treating Python construction history as policy authority. + """ + if tuple.__len__(value) != 3: + raise ValueError("stored authentication evidence is malformed.") + tenant_record_id_int = tuple.__getitem__(value, 0) + actor_reference = tuple.__getitem__(value, 1) + granted_scope_codes = tuple.__getitem__(value, 2) + if ( + type(tenant_record_id_int) is not int + or not 0 <= tenant_record_id_int <= _MAX_UUID_INT + or tenant_record_id_int in (0, _MAX_UUID_INT) + ): + raise ValueError("stored authentication evidence is malformed.") + if type(actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(actor_reference) is None: + raise ValueError("stored authentication evidence is malformed.") + if type(granted_scope_codes) is not frozenset or not granted_scope_codes: + raise ValueError("stored authentication evidence is malformed.") + if any( + type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None + for scope in granted_scope_codes + ): + raise ValueError("stored authentication evidence is malformed.") + return tenant_record_id_int, actor_reference, granted_scope_codes + + class AuthenticationFailed(RuntimeError): """Indicate that bearer authentication evidence is absent or malformed.""" @@ -32,6 +66,8 @@ class AuthenticatedPrincipal(tuple[int, str, frozenset[str]]): Tuple-backed storage deliberately leaves no writable instance slots. The tenant UUID is stored as its validated integer and reconstructed on access, so neither the caller's UUID nor a returned UUID aliases stored authority. + Public access also revalidates all raw tuple slots because callers inside the + service TCB can invoke ``tuple.__new__`` without this class's constructor. """ __slots__ = () @@ -62,44 +98,51 @@ def __new__( @property def tenant_record_id(self) -> UUID: """Return a detached authenticated tenant identifier value.""" - return UUID(int=self[0]) + tenant_record_id_int, _, _ = _validated_principal_storage(self) + return UUID(int=tenant_record_id_int) @property def actor_reference(self) -> str: """Return the opaque authenticated actor correlation reference.""" - return self[1] + _, actor_reference, _ = _validated_principal_storage(self) + return actor_reference @property def granted_scope_codes(self) -> frozenset[str]: """Return the exact operation scopes issued at authentication.""" - return self[2] + _, _, granted_scope_codes = _validated_principal_storage(self) + return granted_scope_codes def __repr__(self) -> str: """Render the same field-oriented diagnostic shape as the prior value object.""" + tenant_record_id_int, actor_reference, granted_scope_codes = _validated_principal_storage(self) return ( "AuthenticatedPrincipal(" - f"tenant_record_id={self.tenant_record_id!r}, " - f"actor_reference={self.actor_reference!r}, " - f"granted_scope_codes={self.granted_scope_codes!r})" + f"tenant_record_id={UUID(int=tenant_record_id_int)!r}, " + f"actor_reference={actor_reference!r}, " + f"granted_scope_codes={granted_scope_codes!r})" ) def __eq__(self, other: object) -> bool: """Compare only another exact authenticated-principal value.""" if type(other) is not AuthenticatedPrincipal: return False - return tuple.__eq__(self, other) + return _validated_principal_storage(self) == _validated_principal_storage(other) def __ne__(self, other: object) -> bool: """Keep inequality consistent with strict principal-only equality.""" if type(other) is not AuthenticatedPrincipal: return True - return tuple.__ne__(self, other) + return _validated_principal_storage(self) != _validated_principal_storage(other) - __hash__ = tuple.__hash__ + def __hash__(self) -> int: + """Hash only revalidated immutable authentication evidence.""" + return hash(_validated_principal_storage(self)) def __getnewargs__(self) -> tuple[UUID, str, frozenset[str]]: """Preserve validated constructor arguments for standard value reconstruction.""" - return (self.tenant_record_id, self.actor_reference, self.granted_scope_codes) + tenant_record_id_int, actor_reference, granted_scope_codes = _validated_principal_storage(self) + return (UUID(int=tenant_record_id_int), actor_reference, granted_scope_codes) def __init_subclass__(cls, **kwargs: object) -> None: """Prevent executable principal subclasses from overriding authenticated evidence.""" From 7e5fc43806f6fe94a77dfcbd8bffd1bf61ab0439 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:08:48 +0900 Subject: [PATCH 111/241] fix(job-analysis): revalidate tuple-backed principal storage --- .../src/orgmetra_job_analysis_api/auth.py | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 971832539..ecb2f4f9f 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -16,6 +16,40 @@ _SCOPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") +def _validated_principal_storage( + value: tuple[int, str, frozenset[str]], +) -> tuple[int, str, frozenset[str]]: + """Return exact tuple-backed identity evidence or reject malformed storage. + + ``tuple.__new__`` can instantiate a tuple subclass without invoking that + subclass's public constructor. Public principal behavior therefore cannot + assume that tuple storage was validated merely because the runtime class is + exact. Revalidating the raw built-in tuple slots keeps request-edge consumers + fail-closed without treating Python construction history as policy authority. + """ + if tuple.__len__(value) != 3: + raise ValueError("stored authentication evidence is malformed.") + tenant_record_id_int = tuple.__getitem__(value, 0) + actor_reference = tuple.__getitem__(value, 1) + granted_scope_codes = tuple.__getitem__(value, 2) + if ( + type(tenant_record_id_int) is not int + or not 0 <= tenant_record_id_int <= _MAX_UUID_INT + or tenant_record_id_int in (0, _MAX_UUID_INT) + ): + raise ValueError("stored authentication evidence is malformed.") + if type(actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(actor_reference) is None: + raise ValueError("stored authentication evidence is malformed.") + if type(granted_scope_codes) is not frozenset or not granted_scope_codes: + raise ValueError("stored authentication evidence is malformed.") + if any( + type(scope) is not str or _SCOPE_PATTERN.fullmatch(scope) is None + for scope in granted_scope_codes + ): + raise ValueError("stored authentication evidence is malformed.") + return tenant_record_id_int, actor_reference, granted_scope_codes + + class AuthenticationFailed(RuntimeError): """Indicate that bearer authentication evidence is absent or malformed.""" @@ -30,6 +64,8 @@ class AuthenticatedPrincipal(tuple[int, str, frozenset[str]]): Tuple-backed storage deliberately leaves no writable instance slots. The tenant UUID is stored as its validated integer and reconstructed on access, so neither the caller's UUID nor a returned UUID aliases stored authority. + Public access also revalidates all raw tuple slots because callers inside the + service TCB can invoke ``tuple.__new__`` without this class's constructor. """ __slots__ = () @@ -60,44 +96,51 @@ def __new__( @property def tenant_record_id(self) -> UUID: """Return a detached authenticated tenant identifier value.""" - return UUID(int=self[0]) + tenant_record_id_int, _, _ = _validated_principal_storage(self) + return UUID(int=tenant_record_id_int) @property def actor_reference(self) -> str: """Return the opaque authenticated actor correlation reference.""" - return self[1] + _, actor_reference, _ = _validated_principal_storage(self) + return actor_reference @property def granted_scope_codes(self) -> frozenset[str]: """Return the exact operation scopes issued at authentication.""" - return self[2] + _, _, granted_scope_codes = _validated_principal_storage(self) + return granted_scope_codes def __repr__(self) -> str: """Render the same field-oriented diagnostic shape as the prior value object.""" + tenant_record_id_int, actor_reference, granted_scope_codes = _validated_principal_storage(self) return ( "AuthenticatedPrincipal(" - f"tenant_record_id={self.tenant_record_id!r}, " - f"actor_reference={self.actor_reference!r}, " - f"granted_scope_codes={self.granted_scope_codes!r})" + f"tenant_record_id={UUID(int=tenant_record_id_int)!r}, " + f"actor_reference={actor_reference!r}, " + f"granted_scope_codes={granted_scope_codes!r})" ) def __eq__(self, other: object) -> bool: """Compare only another exact authenticated-principal value.""" if type(other) is not AuthenticatedPrincipal: return False - return tuple.__eq__(self, other) + return _validated_principal_storage(self) == _validated_principal_storage(other) def __ne__(self, other: object) -> bool: """Keep inequality consistent with strict principal-only equality.""" if type(other) is not AuthenticatedPrincipal: return True - return tuple.__ne__(self, other) + return _validated_principal_storage(self) != _validated_principal_storage(other) - __hash__ = tuple.__hash__ + def __hash__(self) -> int: + """Hash only revalidated immutable authentication evidence.""" + return hash(_validated_principal_storage(self)) def __getnewargs__(self) -> tuple[UUID, str, frozenset[str]]: """Preserve validated constructor arguments for standard value reconstruction.""" - return (self.tenant_record_id, self.actor_reference, self.granted_scope_codes) + tenant_record_id_int, actor_reference, granted_scope_codes = _validated_principal_storage(self) + return (UUID(int=tenant_record_id_int), actor_reference, granted_scope_codes) def __init_subclass__(cls, **kwargs: object) -> None: """Prevent executable principal subclasses from overriding authenticated evidence.""" From e505b922226ebf2defc053f97ca43fa85d865cdb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:05:05 +0900 Subject: [PATCH 112/241] test(people): reject malformed principal sequence access --- ...nticated_principal_storage_revalidation.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py index 1bfcecb76..72bda6671 100644 --- a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py +++ b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py @@ -64,6 +64,25 @@ def test_malformed_storage_cannot_participate_in_value_semantics(self) -> None: with self.assertRaisesRegex(ValueError, "stored authentication evidence"): repr(forged) + def test_malformed_storage_cannot_escape_through_sequence_protocol(self) -> None: + """Ordinary tuple-like access must not expose raw unvalidated identity evidence.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, _TextSubtype("keyverse:actor-1"), frozenset({SCOPE})), + ) + + for access in ( + lambda: forged[1], + lambda: forged[:], + lambda: list(forged), + lambda: tuple(forged), + ): + with self.subTest(access=access), self.assertRaisesRegex( + ValueError, + "stored authentication evidence", + ): + access() + def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenance(self) -> None: """Valid structural evidence stays readable without treating construction history as authority.""" structurally_valid = tuple.__new__( From 8780c1acb86154becdabe3b532436ac3d1e34f84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:05:20 +0900 Subject: [PATCH 113/241] test(job-analysis): reject malformed principal sequence access --- ...nticated_principal_storage_revalidation.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py index 76f477ec4..5a35ab262 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py @@ -64,6 +64,25 @@ def test_malformed_storage_cannot_participate_in_value_semantics(self) -> None: with self.assertRaisesRegex(ValueError, "stored authentication evidence"): repr(forged) + def test_malformed_storage_cannot_escape_through_sequence_protocol(self) -> None: + """Ordinary tuple-like access must not expose raw unvalidated identity evidence.""" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, _TextSubtype("keyverse:actor-ja-1"), frozenset({SCOPE})), + ) + + for access in ( + lambda: forged[1], + lambda: forged[:], + lambda: list(forged), + lambda: tuple(forged), + ): + with self.subTest(access=access), self.assertRaisesRegex( + ValueError, + "stored authentication evidence", + ): + access() + def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenance(self) -> None: """Valid structural evidence stays readable without treating construction history as authority.""" structurally_valid = tuple.__new__( From 6d61dbea3f26a51cec7273a90e70e9770c91b6aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:06:11 +0900 Subject: [PATCH 114/241] fix(people): validate principal sequence views --- services/people-api/src/orgmetra_people_api/auth.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index 408cbc8ed..65a01a991 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -9,6 +9,7 @@ from __future__ import annotations import re +from collections.abc import Iterator from typing import Protocol, runtime_checkable from uuid import UUID @@ -95,6 +96,17 @@ def __new__( raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") return tuple.__new__(cls, (tenant_record_id_int, actor_reference, granted_scope_codes)) + def __getitem__( + self, + key: int | slice, + ) -> int | str | frozenset[str] | tuple[int, str, frozenset[str]]: + """Expose sequence items only after all stored authentication evidence is valid.""" + return _validated_principal_storage(self)[key] + + def __iter__(self) -> Iterator[int | str | frozenset[str]]: + """Iterate only after all stored authentication evidence is revalidated.""" + return iter(_validated_principal_storage(self)) + @property def tenant_record_id(self) -> UUID: """Return a detached authenticated tenant identifier value.""" From 02568d048eb460bbe169ee36ca9b1b320113aaf3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:06:39 +0900 Subject: [PATCH 115/241] fix(job-analysis): validate principal sequence views --- .../src/orgmetra_job_analysis_api/auth.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index ecb2f4f9f..4e5e37668 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -8,6 +8,7 @@ from __future__ import annotations import re +from collections.abc import Iterator from typing import Protocol, runtime_checkable from uuid import UUID @@ -93,6 +94,17 @@ def __new__( raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") return tuple.__new__(cls, (tenant_record_id_int, actor_reference, granted_scope_codes)) + def __getitem__( + self, + key: int | slice, + ) -> int | str | frozenset[str] | tuple[int, str, frozenset[str]]: + """Expose sequence items only after all stored authentication evidence is valid.""" + return _validated_principal_storage(self)[key] + + def __iter__(self) -> Iterator[int | str | frozenset[str]]: + """Iterate only after all stored authentication evidence is revalidated.""" + return iter(_validated_principal_storage(self)) + @property def tenant_record_id(self) -> UUID: """Return a detached authenticated tenant identifier value.""" From 93c0f168110c826d67894f3d54c44193f9e42ffd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:06:57 +0900 Subject: [PATCH 116/241] test(people): preserve validated principal sequence semantics --- .../test_authenticated_principal_storage_revalidation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py index 72bda6671..21daa8a76 100644 --- a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py +++ b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py @@ -94,10 +94,15 @@ def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenanc actor_reference="keyverse:actor-1", granted_scope_codes=frozenset({SCOPE}), ) + expected_storage = (TENANT.int, "keyverse:actor-1", frozenset({SCOPE})) self.assertEqual(structurally_valid.tenant_record_id, TENANT) self.assertEqual(structurally_valid.actor_reference, "keyverse:actor-1") self.assertEqual(structurally_valid.granted_scope_codes, frozenset({SCOPE})) + self.assertEqual(structurally_valid[0], TENANT.int) + self.assertEqual(structurally_valid[1:], expected_storage[1:]) + self.assertEqual(list(structurally_valid), list(expected_storage)) + self.assertEqual(tuple(structurally_valid), expected_storage) self.assertEqual(structurally_valid, canonical) self.assertEqual(hash(structurally_valid), hash(canonical)) From 934bb4d46bc4a774b53158e47058b475d30d2924 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:07:14 +0900 Subject: [PATCH 117/241] test(job-analysis): preserve validated principal sequence semantics --- .../test_authenticated_principal_storage_revalidation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py index 5a35ab262..b62e84c33 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py @@ -94,10 +94,15 @@ def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenanc actor_reference="keyverse:actor-ja-1", granted_scope_codes=frozenset({SCOPE}), ) + expected_storage = (TENANT.int, "keyverse:actor-ja-1", frozenset({SCOPE})) self.assertEqual(structurally_valid.tenant_record_id, TENANT) self.assertEqual(structurally_valid.actor_reference, "keyverse:actor-ja-1") self.assertEqual(structurally_valid.granted_scope_codes, frozenset({SCOPE})) + self.assertEqual(structurally_valid[0], TENANT.int) + self.assertEqual(structurally_valid[1:], expected_storage[1:]) + self.assertEqual(list(structurally_valid), list(expected_storage)) + self.assertEqual(tuple(structurally_valid), expected_storage) self.assertEqual(structurally_valid, canonical) self.assertEqual(hash(structurally_valid), hash(canonical)) From 5755c4dc5b81b7fc2153922242b637642743f402 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:02:36 +0900 Subject: [PATCH 118/241] test(people): cover remaining principal tuple operations --- ...nticated_principal_storage_revalidation.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py index 21daa8a76..df6b53357 100644 --- a/services/people-api/tests/test_authenticated_principal_storage_revalidation.py +++ b/services/people-api/tests/test_authenticated_principal_storage_revalidation.py @@ -83,6 +83,35 @@ def test_malformed_storage_cannot_escape_through_sequence_protocol(self) -> None ): access() + def test_malformed_storage_cannot_escape_through_remaining_tuple_operations(self) -> None: + """Tuple helpers and operators must validate stored evidence before using it.""" + actor_reference = "keyverse:actor-1" + forged = tuple.__new__( + AuthenticatedPrincipal, + (TENANT.int, _TextSubtype(actor_reference), frozenset({SCOPE})), + ) + comparison = (TENANT.int, "keyverse:actor-2", frozenset({SCOPE})) + + for access in ( + lambda: len(forged), + lambda: actor_reference in forged, + lambda: forged.count(actor_reference), + lambda: forged.index(actor_reference), + lambda: forged + (), + lambda: () + forged, + lambda: forged * 1, + lambda: 1 * forged, + lambda: forged < comparison, + lambda: forged <= comparison, + lambda: forged > comparison, + lambda: forged >= comparison, + ): + with self.subTest(access=access), self.assertRaisesRegex( + ValueError, + "stored authentication evidence", + ): + access() + def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenance(self) -> None: """Valid structural evidence stays readable without treating construction history as authority.""" structurally_valid = tuple.__new__( @@ -95,6 +124,7 @@ def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenanc granted_scope_codes=frozenset({SCOPE}), ) expected_storage = (TENANT.int, "keyverse:actor-1", frozenset({SCOPE})) + comparison = (TENANT.int, "keyverse:actor-2", frozenset({SCOPE})) self.assertEqual(structurally_valid.tenant_record_id, TENANT) self.assertEqual(structurally_valid.actor_reference, "keyverse:actor-1") @@ -103,6 +133,18 @@ def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenanc self.assertEqual(structurally_valid[1:], expected_storage[1:]) self.assertEqual(list(structurally_valid), list(expected_storage)) self.assertEqual(tuple(structurally_valid), expected_storage) + self.assertEqual(len(structurally_valid), 3) + self.assertIn("keyverse:actor-1", structurally_valid) + self.assertEqual(structurally_valid.count("keyverse:actor-1"), 1) + self.assertEqual(structurally_valid.index("keyverse:actor-1"), 1) + self.assertEqual(structurally_valid + (), expected_storage) + self.assertEqual(() + structurally_valid, expected_storage) + self.assertEqual(structurally_valid * 1, expected_storage) + self.assertEqual(1 * structurally_valid, expected_storage) + self.assertLess(structurally_valid, comparison) + self.assertLessEqual(structurally_valid, comparison) + self.assertFalse(structurally_valid > comparison) + self.assertFalse(structurally_valid >= comparison) self.assertEqual(structurally_valid, canonical) self.assertEqual(hash(structurally_valid), hash(canonical)) From 39208dfb108253e21e29bd444dbf44f5da7f8291 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:03:25 +0900 Subject: [PATCH 119/241] test(job-analysis): cover remaining principal tuple operations --- ...nticated_principal_storage_revalidation.py | 89 +++++++++---------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py index b62e84c33..450085f98 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_storage_revalidation.py @@ -19,44 +19,23 @@ class AuthenticatedPrincipalStorageRevalidationTests(unittest.TestCase): """Require every public principal view to revalidate tuple-backed evidence.""" def test_tuple_constructor_bypass_cannot_publish_unvalidated_actor_evidence(self) -> None: - """A direct base-tuple constructor must not bypass actor runtime validation.""" - forged = tuple.__new__( - AuthenticatedPrincipal, - (TENANT.int, _TextSubtype("keyverse:actor-ja-1"), frozenset({SCOPE})), - ) - + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, _TextSubtype("keyverse:actor-ja-1"), frozenset({SCOPE}))) with self.assertRaisesRegex(ValueError, "stored authentication evidence"): _ = forged.actor_reference def test_tuple_constructor_bypass_cannot_publish_unvalidated_scope_evidence(self) -> None: - """A direct base-tuple constructor must not bypass scope runtime validation.""" - forged = tuple.__new__( - AuthenticatedPrincipal, - (TENANT.int, "keyverse:actor-ja-1", frozenset({_TextSubtype(SCOPE)})), - ) - + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, "keyverse:actor-ja-1", frozenset({_TextSubtype(SCOPE)}))) with self.assertRaisesRegex(ValueError, "stored authentication evidence"): _ = forged.granted_scope_codes def test_tuple_constructor_bypass_cannot_publish_malformed_storage_shape(self) -> None: - """Malformed tuple arity must fail through the stable principal integrity contract.""" forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, "keyverse:actor-ja-1")) - with self.assertRaisesRegex(ValueError, "stored authentication evidence"): _ = forged.tenant_record_id def test_malformed_storage_cannot_participate_in_value_semantics(self) -> None: - """Hash, comparison, and repr must not legitimize malformed authentication evidence.""" - forged = tuple.__new__( - AuthenticatedPrincipal, - (TENANT.int, object(), frozenset({SCOPE})), - ) - canonical = AuthenticatedPrincipal( - tenant_record_id=TENANT, - actor_reference="keyverse:actor-ja-1", - granted_scope_codes=frozenset({SCOPE}), - ) - + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, object(), frozenset({SCOPE}))) + canonical = AuthenticatedPrincipal(TENANT, "keyverse:actor-ja-1", frozenset({SCOPE})) with self.assertRaisesRegex(ValueError, "stored authentication evidence"): hash(forged) with self.assertRaisesRegex(ValueError, "stored authentication evidence"): @@ -65,37 +44,37 @@ def test_malformed_storage_cannot_participate_in_value_semantics(self) -> None: repr(forged) def test_malformed_storage_cannot_escape_through_sequence_protocol(self) -> None: - """Ordinary tuple-like access must not expose raw unvalidated identity evidence.""" - forged = tuple.__new__( - AuthenticatedPrincipal, - (TENANT.int, _TextSubtype("keyverse:actor-ja-1"), frozenset({SCOPE})), - ) + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, _TextSubtype("keyverse:actor-ja-1"), frozenset({SCOPE}))) + for access in (lambda: forged[1], lambda: forged[:], lambda: list(forged), lambda: tuple(forged)): + with self.subTest(access=access), self.assertRaisesRegex(ValueError, "stored authentication evidence"): + access() + def test_malformed_storage_cannot_escape_through_remaining_tuple_operations(self) -> None: + actor_reference = "keyverse:actor-ja-1" + forged = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, _TextSubtype(actor_reference), frozenset({SCOPE}))) + comparison = (TENANT.int, "keyverse:actor-ja-2", frozenset({SCOPE})) for access in ( - lambda: forged[1], - lambda: forged[:], - lambda: list(forged), - lambda: tuple(forged), + lambda: len(forged), + lambda: actor_reference in forged, + lambda: forged.count(actor_reference), + lambda: forged.index(actor_reference), + lambda: forged + (), + lambda: () + forged, + lambda: forged * 1, + lambda: 1 * forged, + lambda: forged < comparison, + lambda: forged <= comparison, + lambda: forged > comparison, + lambda: forged >= comparison, ): - with self.subTest(access=access), self.assertRaisesRegex( - ValueError, - "stored authentication evidence", - ): + with self.subTest(access=access), self.assertRaisesRegex(ValueError, "stored authentication evidence"): access() def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenance(self) -> None: - """Valid structural evidence stays readable without treating construction history as authority.""" - structurally_valid = tuple.__new__( - AuthenticatedPrincipal, - (TENANT.int, "keyverse:actor-ja-1", frozenset({SCOPE})), - ) - canonical = AuthenticatedPrincipal( - tenant_record_id=TENANT, - actor_reference="keyverse:actor-ja-1", - granted_scope_codes=frozenset({SCOPE}), - ) + structurally_valid = tuple.__new__(AuthenticatedPrincipal, (TENANT.int, "keyverse:actor-ja-1", frozenset({SCOPE}))) + canonical = AuthenticatedPrincipal(TENANT, "keyverse:actor-ja-1", frozenset({SCOPE})) expected_storage = (TENANT.int, "keyverse:actor-ja-1", frozenset({SCOPE})) - + comparison = (TENANT.int, "keyverse:actor-ja-2", frozenset({SCOPE})) self.assertEqual(structurally_valid.tenant_record_id, TENANT) self.assertEqual(structurally_valid.actor_reference, "keyverse:actor-ja-1") self.assertEqual(structurally_valid.granted_scope_codes, frozenset({SCOPE})) @@ -103,6 +82,18 @@ def test_valid_tuple_storage_remains_value_compatible_without_claiming_provenanc self.assertEqual(structurally_valid[1:], expected_storage[1:]) self.assertEqual(list(structurally_valid), list(expected_storage)) self.assertEqual(tuple(structurally_valid), expected_storage) + self.assertEqual(len(structurally_valid), 3) + self.assertIn("keyverse:actor-ja-1", structurally_valid) + self.assertEqual(structurally_valid.count("keyverse:actor-ja-1"), 1) + self.assertEqual(structurally_valid.index("keyverse:actor-ja-1"), 1) + self.assertEqual(structurally_valid + (), expected_storage) + self.assertEqual(() + structurally_valid, expected_storage) + self.assertEqual(structurally_valid * 1, expected_storage) + self.assertEqual(1 * structurally_valid, expected_storage) + self.assertLess(structurally_valid, comparison) + self.assertLessEqual(structurally_valid, comparison) + self.assertFalse(structurally_valid > comparison) + self.assertFalse(structurally_valid >= comparison) self.assertEqual(structurally_valid, canonical) self.assertEqual(hash(structurally_valid), hash(canonical)) From e7aff6af7c89ba37ce7960e673a5ba8a1df0643e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:04:34 +0900 Subject: [PATCH 120/241] fix(people): revalidate all ordinary tuple operations --- .../src/orgmetra_people_api/auth.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index 65a01a991..ce3f1c71c 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -9,6 +9,7 @@ from __future__ import annotations import re +import sys from collections.abc import Iterator from typing import Protocol, runtime_checkable from uuid import UUID @@ -96,6 +97,11 @@ def __new__( raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") return tuple.__new__(cls, (tenant_record_id_int, actor_reference, granted_scope_codes)) + def __len__(self) -> int: + """Report sequence length only after stored authentication evidence is valid.""" + _validated_principal_storage(self) + return 3 + def __getitem__( self, key: int | slice, @@ -107,6 +113,50 @@ def __iter__(self) -> Iterator[int | str | frozenset[str]]: """Iterate only after all stored authentication evidence is revalidated.""" return iter(_validated_principal_storage(self)) + def __contains__(self, value: object) -> bool: + """Search only revalidated authentication evidence.""" + return value in _validated_principal_storage(self) + + def count(self, value: object) -> int: + """Count matches only in revalidated authentication evidence.""" + return _validated_principal_storage(self).count(value) + + def index(self, value: object, start: int = 0, stop: int = sys.maxsize) -> int: + """Locate a value only in revalidated authentication evidence.""" + return _validated_principal_storage(self).index(value, start, stop) + + def __add__(self, other: tuple[object, ...]) -> tuple[object, ...]: + """Concatenate only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) + other + + def __radd__(self, other: tuple[object, ...]) -> tuple[object, ...]: + """Right-concatenate only after this principal's stored evidence is revalidated.""" + return other + _validated_principal_storage(self) + + def __mul__(self, count: int) -> tuple[object, ...]: + """Repeat only revalidated authentication evidence.""" + return _validated_principal_storage(self) * count + + def __rmul__(self, count: int) -> tuple[object, ...]: + """Right-repeat only revalidated authentication evidence.""" + return count * _validated_principal_storage(self) + + def __lt__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) < other + + def __le__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) <= other + + def __gt__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) > other + + def __ge__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) >= other + @property def tenant_record_id(self) -> UUID: """Return a detached authenticated tenant identifier value.""" From 43821d812089fa4bc3c228a2f759ce7abe849471 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:06:39 +0900 Subject: [PATCH 121/241] fix(job-analysis): revalidate all ordinary tuple operations --- .../src/orgmetra_job_analysis_api/auth.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 4e5e37668..2aa547cc4 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -8,6 +8,7 @@ from __future__ import annotations import re +import sys from collections.abc import Iterator from typing import Protocol, runtime_checkable from uuid import UUID @@ -94,6 +95,11 @@ def __new__( raise ValueError("granted_scope_codes must contain explicit Orgmetra scopes.") return tuple.__new__(cls, (tenant_record_id_int, actor_reference, granted_scope_codes)) + def __len__(self) -> int: + """Report sequence length only after stored authentication evidence is valid.""" + _validated_principal_storage(self) + return 3 + def __getitem__( self, key: int | slice, @@ -105,6 +111,50 @@ def __iter__(self) -> Iterator[int | str | frozenset[str]]: """Iterate only after all stored authentication evidence is revalidated.""" return iter(_validated_principal_storage(self)) + def __contains__(self, value: object) -> bool: + """Search only revalidated authentication evidence.""" + return value in _validated_principal_storage(self) + + def count(self, value: object) -> int: + """Count matches only in revalidated authentication evidence.""" + return _validated_principal_storage(self).count(value) + + def index(self, value: object, start: int = 0, stop: int = sys.maxsize) -> int: + """Locate a value only in revalidated authentication evidence.""" + return _validated_principal_storage(self).index(value, start, stop) + + def __add__(self, other: tuple[object, ...]) -> tuple[object, ...]: + """Concatenate only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) + other + + def __radd__(self, other: tuple[object, ...]) -> tuple[object, ...]: + """Right-concatenate only after this principal's stored evidence is revalidated.""" + return other + _validated_principal_storage(self) + + def __mul__(self, count: int) -> tuple[object, ...]: + """Repeat only revalidated authentication evidence.""" + return _validated_principal_storage(self) * count + + def __rmul__(self, count: int) -> tuple[object, ...]: + """Right-repeat only revalidated authentication evidence.""" + return count * _validated_principal_storage(self) + + def __lt__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) < other + + def __le__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) <= other + + def __gt__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) > other + + def __ge__(self, other: tuple[object, ...]) -> bool: + """Order only after this principal's stored evidence is revalidated.""" + return _validated_principal_storage(self) >= other + @property def tenant_record_id(self) -> UUID: """Return a detached authenticated tenant identifier value.""" From 58e4447d10155e1e416fdc93d5314c03b465bc28 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:34:16 +0900 Subject: [PATCH 122/241] test(job-analysis): reject malformed durable command scalars before DB --- ...test_postgres_command_scalar_validation.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_command_scalar_validation.py diff --git a/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py b/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py new file mode 100644 index 000000000..615fcab28 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py @@ -0,0 +1,99 @@ +"""Regression coverage for durable Job Analysis command scalars at persistence.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import AuditOutboxEvent +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import command_digest +from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot + +_ACTOR_REFERENCE = "keyverse:actor-ja-1" +_PURPOSE_CODE = "job_analysis_write" +_RESOURCE_REFERENCE = f"job_analysis_snapshot:{ANALYSIS.hex}" + + +class _TextSubtype(str): + """Represent caller-defined executable text at the durable write boundary.""" + + +def _never_connect() -> object: + """Prove malformed command evidence is rejected before database acquisition.""" + raise AssertionError("database acquired before durable command scalar validation") + + +def _audit_event() -> AuditOutboxEvent: + """Build one valid audit event matching the Job Analysis write authority.""" + snapshot = clinical_psychologist_snapshot() + return AuditOutboxEvent( + event_id=UUID("0198a412-6000-7000-8000-000000000411"), + tenant_record_id=TENANT, + source_service="job_analysis_api", + event_type="orgmetra.job_architecture.snapshot_recorded", + resource_reference=_RESOURCE_REFERENCE, + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + reason_code="snapshot_persisted", + evidence_version_code=snapshot.analysis_version_code, + result_code="recorded", + occurred_at=datetime(2026, 8, 18, 5, 1, tzinfo=timezone.utc), + high_impact=False, + ) + + +def _persist( + *, + idempotency_key: str = IDEMPOTENCY_KEY, + request_digest: str | None = None, +) -> None: + """Invoke the PostgreSQL port with otherwise-valid durable command evidence.""" + snapshot = clinical_psychologist_snapshot() + digest = request_digest + if digest is None: + digest = command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ) + PostgresJobAnalysisPort(_never_connect).persist_snapshot( + snapshot=snapshot, + idempotency_key=idempotency_key, + request_digest=digest, + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), + ) + + +@pytest.mark.parametrize( + "idempotency_key", + [ + _TextSubtype(IDEMPOTENCY_KEY), + f"{IDEMPOTENCY_KEY}\n", + ], +) +def test_durable_idempotency_key_fails_closed_before_database(idempotency_key: str) -> None: + """Persistence must reject executable or control-character Idempotency-Key text.""" + with pytest.raises(ValueError, match="idempotency_key"): + _persist(idempotency_key=idempotency_key) + + +@pytest.mark.parametrize( + "request_digest", + [ + _TextSubtype("a" * 64), + "not-a-lowercase-sha256-digest", + ], +) +def test_durable_request_digest_fails_closed_before_database(request_digest: str) -> None: + """Persistence must accept only exact lowercase SHA-256 command digests.""" + with pytest.raises(ValueError, match="request_digest"): + _persist(request_digest=request_digest) From 2c8a1149fc234fc7d02a8418bf638205a7e0bead Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:36:43 +0900 Subject: [PATCH 123/241] fix(job-analysis): validate durable command scalars before DB --- .../src/orgmetra_job_analysis_api/postgres.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 8eda2821c..65e3a80c7 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -12,6 +12,7 @@ from contextlib import AbstractContextManager from dataclasses import dataclass from datetime import datetime, timezone +import re from typing import Any, Callable from uuid import UUID @@ -29,11 +30,13 @@ JobAnalysisIdempotencyConflict, JobAnalysisIntegrityError, JobAnalysisScopeMissing, + _validate_idempotency_key, validate_operational_uuid, ) PostgresConnectionFactory = Callable[[], AbstractContextManager[Any]] +_REQUEST_DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") _TENANT_CONTEXT_SQL = "SELECT pg_catalog.set_config('orgmetra.tenant_record_id', %s, true)" _READ_ONLY_SQL = "SET TRANSACTION READ ONLY" _IDEMPOTENCY_LOOKUP_SQL = """ @@ -189,6 +192,15 @@ def _source_params(source: EvidenceSource) -> tuple[object, ...]: ) +def _validate_durable_command_scalars(*, idempotency_key: object, request_digest: object) -> None: + """Reject malformed durable command text before acquiring PostgreSQL resources.""" + if type(idempotency_key) is not str: + raise ValueError("idempotency_key must be exact built-in text.") + _validate_idempotency_key(idempotency_key) + if type(request_digest) is not str or _REQUEST_DIGEST_PATTERN.fullmatch(request_digest) is None: + raise ValueError("request_digest must be an exact lowercase SHA-256 digest.") + + def _is_unique_violation(error: Exception) -> bool: """Return whether a PostgreSQL DB-API error reports SQLSTATE 23505.""" return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" @@ -241,8 +253,10 @@ def persist_snapshot( raise TypeError("snapshot must be an exact JobAnalysisSnapshot") if type(audit_event) is not AuditOutboxEvent: raise TypeError("audit_event must be an exact AuditOutboxEvent") - if not isinstance(idempotency_key, str): - raise ValueError("idempotency_key must reach the write port as a string.") + _validate_durable_command_scalars( + idempotency_key=idempotency_key, + request_digest=request_digest, + ) validate_operational_uuid("write_command_id", write_command_id) validate_operational_uuid("outbox_delivery_record_id", outbox_delivery_record_id) if position_record_id is not None: @@ -538,4 +552,4 @@ def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[objec importance_level=row[3], proficiency_level=row[4], source=_source_from_row(row[5:11]), - ) + ) \ No newline at end of file From d8b2260b85112f398d3950640b8eabe2c38d3371 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 02:58:13 +0900 Subject: [PATCH 124/241] test(authz): reject mutable or spoofed operational UUID evidence --- ...test_operational_uuid_runtime_integrity.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 services/job-analysis-api/tests/test_operational_uuid_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_operational_uuid_runtime_integrity.py b/services/job-analysis-api/tests/test_operational_uuid_runtime_integrity.py new file mode 100644 index 000000000..ca7afecd3 --- /dev/null +++ b/services/job-analysis-api/tests/test_operational_uuid_runtime_integrity.py @@ -0,0 +1,84 @@ +"""Runtime-integrity contracts for operational UUID evidence boundaries.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_hris_kernel import JobAnalysisSnapshot + +from orgmetra_job_analysis_api.snapshot import ( + read_job_analysis_snapshot, + validate_operational_uuid, +) +from fixtures import ( + ANALYSIS, + JOB, + TENANT, + clinical_psychologist_snapshot, + read_policy, + read_principal, +) + + +class _SpoofedHexUUID(UUID): + """Expose a reviewed resource hex while retaining another UUID value.""" + + @property + def hex(self) -> str: + """Return the authorized analysis identifier instead of stored identity.""" + return ANALYSIS.hex + + +class _RecordingReadPort: + """Record protected-read calls so validation ordering is observable.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + self.calls: list[tuple[UUID, UUID]] = [] + + def read_snapshot( + self, + *, + tenant_record_id: UUID, + analysis_record_id: UUID, + ) -> JobAnalysisSnapshot: + """Return deterministic evidence after recording the requested identity.""" + self.calls.append((tenant_record_id, analysis_record_id)) + return self.result + + +class OperationalUUIDRuntimeIntegrityTests(unittest.TestCase): + """Require one detached exact UUID snapshot before authority or persistence use.""" + + def test_uuid_subclass_cannot_spoof_authorized_resource_before_read_port(self) -> None: + """Reject a UUID subtype whose public hex disagrees with its stored identity.""" + spoofed = _SpoofedHexUUID(str(JOB)) + port = _RecordingReadPort(clinical_psychologist_snapshot()) + + with self.assertRaises(ValueError): + read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=spoofed, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=port, + ) + + self.assertEqual(port.calls, []) + + def test_validated_uuid_is_detached_from_caller_owned_alias(self) -> None: + """Mutation of the caller UUID after validation cannot rewrite accepted evidence.""" + caller_owned = UUID(str(ANALYSIS)) + + accepted = validate_operational_uuid("analysis_record_id", caller_owned) + object.__setattr__(caller_owned, "int", JOB.int) + + self.assertIs(type(accepted), UUID) + self.assertIsNot(accepted, caller_owned) + self.assertEqual(accepted, ANALYSIS) + + +if __name__ == "__main__": + unittest.main() From 51a4fa5949216f74cddd96d1bfc3dba892d44ceb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 03:00:18 +0900 Subject: [PATCH 125/241] fix(authz): detach exact operational UUID evidence --- .../src/orgmetra_job_analysis_api/snapshot.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index ba005b05a..7ef24c9af 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -130,10 +130,13 @@ def _reject_unknown_fields( def validate_operational_uuid(field_name: str, value: object) -> UUID: - """Require a UUID that is not one of Orgmetra's reserved protocol sentinels.""" - if not isinstance(value, UUID) or value.int in (0, _MAX_UUID_INT): + """Return a detached exact UUID after validating operational identity evidence.""" + if type(value) is not UUID: raise ValueError(f"{field_name} must be an operational UUID.") - return value + value_int = value.int + if type(value_int) is not int or not 0 < value_int < _MAX_UUID_INT: + raise ValueError(f"{field_name} must be an operational UUID.") + return UUID(int=value_int) def _validate_idempotency_key(value: object) -> str: From ae0c8c8c0d7878cb32d5a2669dfc50642dbc19fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 03:06:31 +0900 Subject: [PATCH 126/241] test(authz): detach validated UUIDs at PostgreSQL boundary --- ...st_postgres_operational_uuid_detachment.py | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_operational_uuid_detachment.py diff --git a/services/job-analysis-api/tests/test_postgres_operational_uuid_detachment.py b/services/job-analysis-api/tests/test_postgres_operational_uuid_detachment.py new file mode 100644 index 000000000..7f1adf285 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_operational_uuid_detachment.py @@ -0,0 +1,121 @@ +"""Regression coverage for detached operational UUIDs at the PostgreSQL port.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import command_digest +from fixtures import ( + ANALYSIS, + IDEMPOTENCY_KEY, + JOB, + OTHER_TENANT, + TENANT, + clinical_psychologist_snapshot, +) +from test_postgres import ( + FakeConnection, + FakeCursor, + _audit_event, + _header_row, + _ksao_rows, + _link_rows, + _task_rows, +) + + +class PostgresOperationalUUIDDetachmentTests(unittest.TestCase): + """Require validated UUID values to be snapshotted before DB acquisition hooks run.""" + + def test_read_port_uses_detached_uuid_values_after_connection_factory_mutates_aliases(self) -> None: + """A caller alias cannot retarget tenant or analysis identity after validation.""" + tenant_record_id = UUID(str(TENANT)) + analysis_record_id = UUID(str(ANALYSIS)) + cursor = FakeCursor( + [None, None, [_header_row()], _task_rows(), _ksao_rows(), _link_rows()] + ) + + def connection_factory() -> FakeConnection: + object.__setattr__(tenant_record_id, "int", OTHER_TENANT.int) + object.__setattr__(analysis_record_id, "int", JOB.int) + return FakeConnection(cursor) + + resolved = PostgresJobAnalysisPort(connection_factory).read_snapshot( + tenant_record_id=tenant_record_id, + analysis_record_id=analysis_record_id, + ) + + self.assertIsNotNone(resolved) + self.assertEqual(resolved.analysis_record_id, ANALYSIS) + self.assertEqual(cursor.executions[1][1], (str(TENANT),)) + self.assertEqual(cursor.executions[2][1], (TENANT, ANALYSIS)) + + def test_write_port_uses_detached_command_ids_after_connection_factory_mutates_aliases(self) -> None: + """Validated write/outbox identities cannot be rewritten before SQL insertion.""" + snapshot = clinical_psychologist_snapshot() + write_command_id = UUID("0198a412-6000-7000-8000-000000000413") + outbox_delivery_record_id = UUID("0198a412-6000-7000-8000-000000000412") + expected_write_command_id = UUID(str(write_command_id)) + expected_outbox_delivery_record_id = UUID(str(outbox_delivery_record_id)) + mutated_write_command_id = UUID("0198a412-6000-7000-8000-000000000499") + mutated_outbox_delivery_record_id = UUID("0198a412-6000-7000-8000-000000000498") + write_statement_count = ( + 1 + + len(snapshot.tasks) + + len(snapshot.ksao_requirements) + + len(snapshot.task_ksao_links) + + 2 + ) + cursor = FakeCursor([None, None, (JOB,)] + [None] * write_statement_count) + + def connection_factory() -> FakeConnection: + object.__setattr__(write_command_id, "int", mutated_write_command_id.int) + object.__setattr__( + outbox_delivery_record_id, + "int", + mutated_outbox_delivery_record_id.int, + ) + return FakeConnection(cursor) + + PostgresJobAnalysisPort(connection_factory).persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=outbox_delivery_record_id, + write_command_id=write_command_id, + ) + + parameter_sets = [parameters for _, parameters in cursor.executions if parameters] + self.assertTrue( + any(expected_write_command_id in parameters for parameters in parameter_sets) + ) + self.assertTrue( + any( + expected_outbox_delivery_record_id in parameters + for parameters in parameter_sets + ) + ) + self.assertFalse( + any(mutated_write_command_id in parameters for parameters in parameter_sets) + ) + self.assertFalse( + any( + mutated_outbox_delivery_record_id in parameters + for parameters in parameter_sets + ) + ) + + +if __name__ == "__main__": + unittest.main() From 9058e0c5b29a8d4493b39d8c88c789ea2288b77b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 03:09:07 +0900 Subject: [PATCH 127/241] fix(authz): retain detached UUIDs at PostgreSQL boundary --- .../src/orgmetra_job_analysis_api/postgres.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 65e3a80c7..99700108e 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -257,12 +257,18 @@ def persist_snapshot( idempotency_key=idempotency_key, request_digest=request_digest, ) - validate_operational_uuid("write_command_id", write_command_id) - validate_operational_uuid("outbox_delivery_record_id", outbox_delivery_record_id) + write_command_id = validate_operational_uuid("write_command_id", write_command_id) + outbox_delivery_record_id = validate_operational_uuid( + "outbox_delivery_record_id", + outbox_delivery_record_id, + ) if position_record_id is not None: - validate_operational_uuid("position_record_id", position_record_id) + position_record_id = validate_operational_uuid("position_record_id", position_record_id) if criterion_blueprint_id is not None: - validate_operational_uuid("criterion_blueprint_id", criterion_blueprint_id) + criterion_blueprint_id = validate_operational_uuid( + "criterion_blueprint_id", + criterion_blueprint_id, + ) expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" if ( audit_event.tenant_record_id != snapshot.tenant_record_id @@ -446,8 +452,8 @@ def read_snapshot( analysis_record_id: UUID, ) -> JobAnalysisSnapshot | None: """Read one snapshot under forced tenant RLS and reconstruct the kernel document.""" - validate_operational_uuid("tenant_record_id", tenant_record_id) - validate_operational_uuid("analysis_record_id", analysis_record_id) + tenant_record_id = validate_operational_uuid("tenant_record_id", tenant_record_id) + analysis_record_id = validate_operational_uuid("analysis_record_id", analysis_record_id) with self.connection_factory() as connection: with connection.cursor() as cursor: cursor.execute(_READ_ONLY_SQL) From 2d174f6ffd166701dabe3e45b4db783af803bba0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 03:31:13 +0900 Subject: [PATCH 128/241] test(authz): reject durable authority text subtypes before PostgreSQL --- ...test_postgres_command_scalar_validation.py | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py b/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py index 615fcab28..36c3c0588 100644 --- a/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py +++ b/services/job-analysis-api/tests/test_postgres_command_scalar_validation.py @@ -26,7 +26,11 @@ def _never_connect() -> object: raise AssertionError("database acquired before durable command scalar validation") -def _audit_event() -> AuditOutboxEvent: +def _audit_event( + *, + actor_reference: str = _ACTOR_REFERENCE, + purpose_code: str = _PURPOSE_CODE, +) -> AuditOutboxEvent: """Build one valid audit event matching the Job Analysis write authority.""" snapshot = clinical_psychologist_snapshot() return AuditOutboxEvent( @@ -35,8 +39,8 @@ def _audit_event() -> AuditOutboxEvent: source_service="job_analysis_api", event_type="orgmetra.job_architecture.snapshot_recorded", resource_reference=_RESOURCE_REFERENCE, - actor_reference=_ACTOR_REFERENCE, - purpose_code=_PURPOSE_CODE, + actor_reference=actor_reference, + purpose_code=purpose_code, reason_code="snapshot_persisted", evidence_version_code=snapshot.analysis_version_code, result_code="recorded", @@ -49,6 +53,8 @@ def _persist( *, idempotency_key: str = IDEMPOTENCY_KEY, request_digest: str | None = None, + actor_reference: str = _ACTOR_REFERENCE, + purpose_code: str = _PURPOSE_CODE, ) -> None: """Invoke the PostgreSQL port with otherwise-valid durable command evidence.""" snapshot = clinical_psychologist_snapshot() @@ -63,11 +69,14 @@ def _persist( snapshot=snapshot, idempotency_key=idempotency_key, request_digest=digest, - actor_reference=_ACTOR_REFERENCE, - purpose_code=_PURPOSE_CODE, + actor_reference=actor_reference, + purpose_code=purpose_code, position_record_id=None, criterion_blueprint_id=None, - audit_event=_audit_event(), + audit_event=_audit_event( + actor_reference=actor_reference, + purpose_code=purpose_code, + ), outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), ) @@ -97,3 +106,23 @@ def test_durable_request_digest_fails_closed_before_database(request_digest: str """Persistence must accept only exact lowercase SHA-256 command digests.""" with pytest.raises(ValueError, match="request_digest"): _persist(request_digest=request_digest) + + +@pytest.mark.parametrize( + ("field_name", "actor_reference", "purpose_code"), + [ + ("actor_reference", _TextSubtype(_ACTOR_REFERENCE), _PURPOSE_CODE), + ("purpose_code", _ACTOR_REFERENCE, _TextSubtype(_PURPOSE_CODE)), + ], +) +def test_durable_authority_text_fails_closed_before_database( + field_name: str, + actor_reference: str, + purpose_code: str, +) -> None: + """Actor and purpose authority must be exact immutable text before DB acquisition.""" + with pytest.raises(ValueError, match=field_name): + _persist( + actor_reference=actor_reference, + purpose_code=purpose_code, + ) From cf14ca8e06f788d905be59f76a987282c1c24395 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 03:33:34 +0900 Subject: [PATCH 129/241] fix(authz): exact-gate durable actor and purpose text --- .../src/orgmetra_job_analysis_api/postgres.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 99700108e..5a1a8986a 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -192,13 +192,23 @@ def _source_params(source: EvidenceSource) -> tuple[object, ...]: ) -def _validate_durable_command_scalars(*, idempotency_key: object, request_digest: object) -> None: +def _validate_durable_command_scalars( + *, + idempotency_key: object, + request_digest: object, + actor_reference: object, + purpose_code: object, +) -> None: """Reject malformed durable command text before acquiring PostgreSQL resources.""" if type(idempotency_key) is not str: raise ValueError("idempotency_key must be exact built-in text.") _validate_idempotency_key(idempotency_key) if type(request_digest) is not str or _REQUEST_DIGEST_PATTERN.fullmatch(request_digest) is None: raise ValueError("request_digest must be an exact lowercase SHA-256 digest.") + if type(actor_reference) is not str: + raise ValueError("actor_reference must be exact built-in text.") + if type(purpose_code) is not str: + raise ValueError("purpose_code must be exact built-in text.") def _is_unique_violation(error: Exception) -> bool: @@ -256,6 +266,8 @@ def persist_snapshot( _validate_durable_command_scalars( idempotency_key=idempotency_key, request_digest=request_digest, + actor_reference=actor_reference, + purpose_code=purpose_code, ) write_command_id = validate_operational_uuid("write_command_id", write_command_id) outbox_delivery_record_id = validate_operational_uuid( From f6b0d2c692c6e9b820faff386f0da136e2338ce2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:06:02 +0900 Subject: [PATCH 130/241] test(security): reject forged durable audit authority text --- ...st_postgres_audit_authorization_binding.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py index 7faeb36f2..0997f61f1 100644 --- a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py +++ b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py @@ -46,6 +46,26 @@ def content_digest(self) -> str: raise AssertionError("audit subtype content_digest consumed before exact-type rejection") +class _ForgedAuthorityText(str): + """Retain hostile audit text while reporting equality with reviewed authority.""" + + def __new__(cls, value: str, equal_to: str) -> _ForgedAuthorityText: + """Build valid-looking text whose equality does not describe its stored bytes.""" + instance = super().__new__(cls, value) + instance._equal_to = equal_to + return instance + + def __eq__(self, other: object) -> bool: + """Spoof equality only for the reviewed authority value.""" + return other == self._equal_to + + def __ne__(self, other: object) -> bool: + """Keep inequality logically inverse to the forged equality result.""" + return not self.__eq__(other) + + __hash__ = str.__hash__ + + def _never_connect() -> object: """Prove invalid durable evidence is rejected before database acquisition.""" raise AssertionError("database acquired before job-analysis audit binding validation") @@ -117,6 +137,35 @@ def test_job_analysis_audit_authority_drift_fails_before_database( _persist_with_audit(audit_event) +@pytest.mark.parametrize( + ("field_name", "forged_value", "reviewed_value"), + [ + ( + "resource_reference", + "job_analysis_snapshot:0198a412600070008000000000000999", + _RESOURCE_REFERENCE, + ), + ("actor_reference", "keyverse:actor-ja-other", _ACTOR_REFERENCE), + ("purpose_code", "job_analysis_read", _PURPOSE_CODE), + ], +) +def test_exact_audit_event_rejects_forged_authority_text_before_database( + field_name: str, + forged_value: str, + reviewed_value: str, +) -> None: + """Exact envelope type must not let subtype-controlled equality authorize durable audit bytes.""" + audit_event = _audit_event( + **{field_name: _ForgedAuthorityText(forged_value, reviewed_value)} + ) + + with pytest.raises( + ValueError, + match=rf"audit_event\.{field_name} must be exact built-in text", + ): + _persist_with_audit(audit_event) + + def test_job_analysis_audit_subtype_fails_before_any_audit_or_database_access() -> None: """Exact-type rejection must precede subtype-controlled fields, serialization, and DB I/O.""" with pytest.raises(TypeError, match="audit_event must be an exact AuditOutboxEvent"): From bd32e250c9b454dc52d9cb1312750300bbf458db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:08:37 +0900 Subject: [PATCH 131/241] fix(security): snapshot durable audit authority before PostgreSQL --- .../src/orgmetra_job_analysis_api/postgres.py | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 5a1a8986a..4a9c07fea 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -12,6 +12,7 @@ from contextlib import AbstractContextManager from dataclasses import dataclass from datetime import datetime, timezone +from hashlib import sha256 import re from typing import Any, Callable from uuid import UUID @@ -211,6 +212,35 @@ def _validate_durable_command_scalars( raise ValueError("purpose_code must be exact built-in text.") +def _snapshot_durable_audit_authority( + audit_event: AuditOutboxEvent, +) -> tuple[UUID, UUID, str, str, str, str, str]: + """Freeze exact audit authority and canonical bytes before executable DB acquisition.""" + event_id = validate_operational_uuid("audit_event.event_id", audit_event.event_id) + tenant_record_id = validate_operational_uuid( + "audit_event.tenant_record_id", + audit_event.tenant_record_id, + ) + authority_text: list[str] = [] + for field_name in ("resource_reference", "actor_reference", "purpose_code"): + value = getattr(audit_event, field_name) + if type(value) is not str: + raise ValueError(f"audit_event.{field_name} must be exact built-in text.") + authority_text.append(value) + resource_reference, actor_reference, purpose_code = authority_text + canonical_json = audit_event.canonical_json() + content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() + return ( + event_id, + tenant_record_id, + resource_reference, + actor_reference, + purpose_code, + canonical_json, + content_digest, + ) + + def _is_unique_violation(error: Exception) -> bool: """Return whether a PostgreSQL DB-API error reports SQLSTATE 23505.""" return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" @@ -269,6 +299,15 @@ def persist_snapshot( actor_reference=actor_reference, purpose_code=purpose_code, ) + ( + audit_event_id, + audit_tenant_record_id, + audit_resource_reference, + audit_actor_reference, + audit_purpose_code, + audit_canonical_json, + audit_content_digest, + ) = _snapshot_durable_audit_authority(audit_event) write_command_id = validate_operational_uuid("write_command_id", write_command_id) outbox_delivery_record_id = validate_operational_uuid( "outbox_delivery_record_id", @@ -283,10 +322,10 @@ def persist_snapshot( ) expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" if ( - audit_event.tenant_record_id != snapshot.tenant_record_id - or audit_event.resource_reference != expected_resource_reference - or audit_event.actor_reference != actor_reference - or audit_event.purpose_code != purpose_code + audit_tenant_record_id != snapshot.tenant_record_id + or audit_resource_reference != expected_resource_reference + or audit_actor_reference != actor_reference + or audit_purpose_code != purpose_code ): raise JobAnalysisIntegrityError( "audit event does not match the job-analysis write authority" @@ -448,10 +487,10 @@ def persist_snapshot( _AUDIT_OUTBOX_SQL, ( snapshot.tenant_record_id, - audit_event.event_id, + audit_event_id, outbox_delivery_record_id, - audit_event.canonical_json(), - audit_event.content_digest(), + audit_canonical_json, + audit_content_digest, "integration_hub", ), ) @@ -570,4 +609,4 @@ def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[objec importance_level=row[3], proficiency_level=row[4], source=_source_from_row(row[5:11]), - ) \ No newline at end of file + ) From abb92868b8485627ef0890c8c8a59e5e0832c23f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:14:05 +0900 Subject: [PATCH 132/241] test(security): detect audit authority drift during canonicalization --- ...st_postgres_audit_authorization_binding.py | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py index 0997f61f1..925f9d506 100644 --- a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py +++ b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone, tzinfo from typing import TypeVar from uuid import UUID @@ -66,6 +66,41 @@ def __ne__(self, other: object) -> bool: __hash__ = str.__hash__ +class _AuthorityMutatingTimezone(tzinfo): + """Mutate an exact audit envelope from the canonicalization callback surface.""" + + def __init__(self) -> None: + """Start inert so AuditOutboxEvent construction itself remains valid.""" + self._audit_event: AuditOutboxEvent | None = None + self._armed = False + + def arm(self, audit_event: AuditOutboxEvent) -> None: + """Enable the mutation only after the exact event constructor has returned.""" + self._audit_event = audit_event + self._armed = True + + def utcoffset(self, value: datetime | None) -> timedelta: + """Drift authority when canonicalization asks the timezone for its UTC offset.""" + del value + if self._armed and self._audit_event is not None: + object.__setattr__( + self._audit_event, + "actor_reference", + "keyverse:actor-ja-canonicalization-drift", + ) + return timedelta(0) + + def dst(self, value: datetime | None) -> timedelta: + """Expose a stable zero daylight-saving offset.""" + del value + return timedelta(0) + + def tzname(self, value: datetime | None) -> str: + """Return a deterministic test-only timezone name.""" + del value + return "UTC_TEST" + + def _never_connect() -> object: """Prove invalid durable evidence is rejected before database acquisition.""" raise AssertionError("database acquired before job-analysis audit binding validation") @@ -166,6 +201,21 @@ def test_exact_audit_event_rejects_forged_authority_text_before_database( _persist_with_audit(audit_event) +def test_canonicalization_callback_cannot_drift_validated_audit_authority() -> None: + """Canonical bytes must be rechecked after any executable timezone callback.""" + callback_timezone = _AuthorityMutatingTimezone() + audit_event = _audit_event( + occurred_at=datetime(2026, 8, 18, 5, 1, tzinfo=callback_timezone) + ) + callback_timezone.arm(audit_event) + + with pytest.raises( + JobAnalysisIntegrityError, + match="canonical audit evidence does not match validated authority", + ): + _persist_with_audit(audit_event) + + def test_job_analysis_audit_subtype_fails_before_any_audit_or_database_access() -> None: """Exact-type rejection must precede subtype-controlled fields, serialization, and DB I/O.""" with pytest.raises(TypeError, match="audit_event must be an exact AuditOutboxEvent"): From 4cfa2bffd28361ee8372f4e34c61613ba68c0735 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:15:17 +0900 Subject: [PATCH 133/241] fix(security): verify frozen audit bytes match detached authority --- .../src/orgmetra_job_analysis_api/postgres.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 4a9c07fea..2d973061f 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -13,6 +13,7 @@ from dataclasses import dataclass from datetime import datetime, timezone from hashlib import sha256 +import json import re from typing import Any, Callable from uuid import UUID @@ -229,6 +230,17 @@ def _snapshot_durable_audit_authority( authority_text.append(value) resource_reference, actor_reference, purpose_code = authority_text canonical_json = audit_event.canonical_json() + canonical_event = json.loads(canonical_json) + if ( + canonical_event.get("id") != str(event_id) + or canonical_event.get("orgmetratenant") != str(tenant_record_id) + or canonical_event.get("subject") != resource_reference + or canonical_event.get("orgmetraactor") != actor_reference + or canonical_event.get("orgmetrapurpose") != purpose_code + ): + raise JobAnalysisIntegrityError( + "canonical audit evidence does not match validated authority" + ) content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() return ( event_id, From c78871091bc17f882073fb06ab5dfcff841bbe06 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:24:33 +0900 Subject: [PATCH 134/241] test(security): bind durable job-analysis audit semantics --- ...st_postgres_audit_authorization_binding.py | 95 +++++++++++++++++-- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py index 925f9d506..a46387c22 100644 --- a/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py +++ b/services/job-analysis-api/tests/test_postgres_audit_authorization_binding.py @@ -47,7 +47,7 @@ def content_digest(self) -> str: class _ForgedAuthorityText(str): - """Retain hostile audit text while reporting equality with reviewed authority.""" + """Retain hostile audit text while reporting equality with reviewed evidence.""" def __new__(cls, value: str, equal_to: str) -> _ForgedAuthorityText: """Build valid-looking text whose equality does not describe its stored bytes.""" @@ -56,7 +56,7 @@ def __new__(cls, value: str, equal_to: str) -> _ForgedAuthorityText: return instance def __eq__(self, other: object) -> bool: - """Spoof equality only for the reviewed authority value.""" + """Spoof equality only for the reviewed evidence value.""" return other == self._equal_to def __ne__(self, other: object) -> bool: @@ -66,12 +66,18 @@ def __ne__(self, other: object) -> bool: __hash__ = str.__hash__ -class _AuthorityMutatingTimezone(tzinfo): - """Mutate an exact audit envelope from the canonicalization callback surface.""" +class _AuditFieldMutatingTimezone(tzinfo): + """Mutate one exact audit-envelope field from the canonicalization callback surface.""" - def __init__(self) -> None: + def __init__( + self, + field_name: str = "actor_reference", + field_value: object = "keyverse:actor-ja-canonicalization-drift", + ) -> None: """Start inert so AuditOutboxEvent construction itself remains valid.""" self._audit_event: AuditOutboxEvent | None = None + self._field_name = field_name + self._field_value = field_value self._armed = False def arm(self, audit_event: AuditOutboxEvent) -> None: @@ -80,13 +86,13 @@ def arm(self, audit_event: AuditOutboxEvent) -> None: self._armed = True def utcoffset(self, value: datetime | None) -> timedelta: - """Drift authority when canonicalization asks the timezone for its UTC offset.""" + """Drift one field when canonicalization asks the timezone for its UTC offset.""" del value if self._armed and self._audit_event is not None: object.__setattr__( self._audit_event, - "actor_reference", - "keyverse:actor-ja-canonicalization-drift", + self._field_name, + self._field_value, ) return timedelta(0) @@ -110,7 +116,7 @@ def _audit_event( event_class: type[_AuditEventT] = AuditOutboxEvent, **overrides: object, ) -> _AuditEventT: - """Build one shaped audit envelope whose authority fields may be adversarially drifted.""" + """Build one shaped audit envelope whose evidence may be adversarially drifted.""" snapshot = clinical_psychologist_snapshot() values: dict[str, object] = { "event_id": UUID("0198a412-6000-7000-8000-000000000401"), @@ -201,9 +207,63 @@ def test_exact_audit_event_rejects_forged_authority_text_before_database( _persist_with_audit(audit_event) +@pytest.mark.parametrize( + "audit_event", + [ + _audit_event(source_service="people_api"), + _audit_event(event_type="orgmetra.job_architecture.snapshot_superseded"), + _audit_event(reason_code="snapshot_corrected"), + _audit_event(evidence_version_code="unexpected:v1"), + _audit_event(result_code="updated"), + _audit_event(confirmation_reference="review:job-analysis-1"), + _audit_event(high_impact=True, confirmation_reference="review:job-analysis-1"), + ], +) +def test_job_analysis_audit_semantic_drift_fails_before_database( + audit_event: AuditOutboxEvent, +) -> None: + """Durable audit semantics must identify this exact successful snapshot-recording write.""" + with pytest.raises( + JobAnalysisIntegrityError, + match="audit event does not match the job-analysis snapshot semantics", + ): + _persist_with_audit(audit_event) + + +@pytest.mark.parametrize( + ("field_name", "forged_value", "reviewed_value"), + [ + ("source_service", "people_api", "job_analysis_api"), + ( + "event_type", + "orgmetra.job_architecture.snapshot_superseded", + "orgmetra.job_architecture.snapshot_recorded", + ), + ("reason_code", "snapshot_corrected", "snapshot_persisted"), + ("evidence_version_code", "unexpected:v1", clinical_psychologist_snapshot().analysis_version_code), + ("result_code", "updated", "recorded"), + ], +) +def test_exact_audit_event_rejects_forged_semantic_text_before_database( + field_name: str, + forged_value: str, + reviewed_value: str, +) -> None: + """Runtime text equality must not substitute for durable audit semantic bytes.""" + audit_event = _audit_event( + **{field_name: _ForgedAuthorityText(forged_value, reviewed_value)} + ) + + with pytest.raises( + ValueError, + match=rf"audit_event\.{field_name} must be exact built-in text", + ): + _persist_with_audit(audit_event) + + def test_canonicalization_callback_cannot_drift_validated_audit_authority() -> None: """Canonical bytes must be rechecked after any executable timezone callback.""" - callback_timezone = _AuthorityMutatingTimezone() + callback_timezone = _AuditFieldMutatingTimezone() audit_event = _audit_event( occurred_at=datetime(2026, 8, 18, 5, 1, tzinfo=callback_timezone) ) @@ -216,6 +276,21 @@ def test_canonicalization_callback_cannot_drift_validated_audit_authority() -> N _persist_with_audit(audit_event) +def test_canonicalization_callback_cannot_drift_validated_audit_semantics() -> None: + """Frozen canonical bytes must still represent the pre-canonical semantic snapshot.""" + callback_timezone = _AuditFieldMutatingTimezone("result_code", "updated") + audit_event = _audit_event( + occurred_at=datetime(2026, 8, 18, 5, 1, tzinfo=callback_timezone) + ) + callback_timezone.arm(audit_event) + + with pytest.raises( + JobAnalysisIntegrityError, + match="canonical audit evidence does not match validated semantics", + ): + _persist_with_audit(audit_event) + + def test_job_analysis_audit_subtype_fails_before_any_audit_or_database_access() -> None: """Exact-type rejection must precede subtype-controlled fields, serialization, and DB I/O.""" with pytest.raises(TypeError, match="audit_event must be an exact AuditOutboxEvent"): From 0ae78907b25f03d5adf1595479b3f82997935f1a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:34:56 +0900 Subject: [PATCH 135/241] fix(security): bind durable job-analysis audit semantics --- .../src/orgmetra_job_analysis_api/postgres.py | 126 +++++++++++++----- 1 file changed, 93 insertions(+), 33 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 2d973061f..74c66def5 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -39,6 +39,10 @@ PostgresConnectionFactory = Callable[[], AbstractContextManager[Any]] _REQUEST_DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") +_EXPECTED_AUDIT_SOURCE_SERVICE = "job_analysis_api" +_EXPECTED_AUDIT_EVENT_TYPE = "orgmetra.job_architecture.snapshot_recorded" +_EXPECTED_AUDIT_REASON_CODE = "snapshot_persisted" +_EXPECTED_AUDIT_RESULT_CODE = "recorded" _TENANT_CONTEXT_SQL = "SELECT pg_catalog.set_config('orgmetra.tenant_record_id', %s, true)" _READ_ONLY_SQL = "SET TRANSACTION READ ONLY" _IDEMPOTENCY_LOOKUP_SQL = """ @@ -213,43 +217,95 @@ def _validate_durable_command_scalars( raise ValueError("purpose_code must be exact built-in text.") +@dataclass(frozen=True, slots=True) +class _DurableAuditEvidence: + """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition.""" + + event_id: UUID + tenant_record_id: UUID + source_service: str + event_type: str + resource_reference: str + actor_reference: str + purpose_code: str + reason_code: str + evidence_version_code: str + result_code: str + high_impact: bool + confirmation_reference: str | None + canonical_json: str + content_digest: str + + def _snapshot_durable_audit_authority( audit_event: AuditOutboxEvent, -) -> tuple[UUID, UUID, str, str, str, str, str]: - """Freeze exact audit authority and canonical bytes before executable DB acquisition.""" +) -> _DurableAuditEvidence: + """Freeze exact audit authority, semantics, and canonical bytes before DB acquisition.""" event_id = validate_operational_uuid("audit_event.event_id", audit_event.event_id) tenant_record_id = validate_operational_uuid( "audit_event.tenant_record_id", audit_event.tenant_record_id, ) - authority_text: list[str] = [] - for field_name in ("resource_reference", "actor_reference", "purpose_code"): + audit_text: dict[str, str] = {} + for field_name in ( + "source_service", + "event_type", + "resource_reference", + "actor_reference", + "purpose_code", + "reason_code", + "evidence_version_code", + "result_code", + ): value = getattr(audit_event, field_name) if type(value) is not str: raise ValueError(f"audit_event.{field_name} must be exact built-in text.") - authority_text.append(value) - resource_reference, actor_reference, purpose_code = authority_text + audit_text[field_name] = value + high_impact = audit_event.high_impact + confirmation_reference = audit_event.confirmation_reference canonical_json = audit_event.canonical_json() canonical_event = json.loads(canonical_json) if ( canonical_event.get("id") != str(event_id) or canonical_event.get("orgmetratenant") != str(tenant_record_id) - or canonical_event.get("subject") != resource_reference - or canonical_event.get("orgmetraactor") != actor_reference - or canonical_event.get("orgmetrapurpose") != purpose_code + or canonical_event.get("subject") != audit_text["resource_reference"] + or canonical_event.get("orgmetraactor") != audit_text["actor_reference"] + or canonical_event.get("orgmetrapurpose") != audit_text["purpose_code"] ): raise JobAnalysisIntegrityError( "canonical audit evidence does not match validated authority" ) + if ( + canonical_event.get("source") != f"urn:orgmetra:{audit_text['source_service']}" + or canonical_event.get("type") != audit_text["event_type"] + or canonical_event.get("orgmetrareason") != audit_text["reason_code"] + or canonical_event.get("orgmetraevidence") != audit_text["evidence_version_code"] + or canonical_event.get("data") + != { + "result_code": audit_text["result_code"], + "high_impact": high_impact, + } + or canonical_event.get("orgmetraconfirmation") != confirmation_reference + ): + raise JobAnalysisIntegrityError( + "canonical audit evidence does not match validated semantics" + ) content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() - return ( - event_id, - tenant_record_id, - resource_reference, - actor_reference, - purpose_code, - canonical_json, - content_digest, + return _DurableAuditEvidence( + event_id=event_id, + tenant_record_id=tenant_record_id, + source_service=audit_text["source_service"], + event_type=audit_text["event_type"], + resource_reference=audit_text["resource_reference"], + actor_reference=audit_text["actor_reference"], + purpose_code=audit_text["purpose_code"], + reason_code=audit_text["reason_code"], + evidence_version_code=audit_text["evidence_version_code"], + result_code=audit_text["result_code"], + high_impact=high_impact, + confirmation_reference=confirmation_reference, + canonical_json=canonical_json, + content_digest=content_digest, ) @@ -311,15 +367,7 @@ def persist_snapshot( actor_reference=actor_reference, purpose_code=purpose_code, ) - ( - audit_event_id, - audit_tenant_record_id, - audit_resource_reference, - audit_actor_reference, - audit_purpose_code, - audit_canonical_json, - audit_content_digest, - ) = _snapshot_durable_audit_authority(audit_event) + audit_evidence = _snapshot_durable_audit_authority(audit_event) write_command_id = validate_operational_uuid("write_command_id", write_command_id) outbox_delivery_record_id = validate_operational_uuid( "outbox_delivery_record_id", @@ -334,14 +382,26 @@ def persist_snapshot( ) expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" if ( - audit_tenant_record_id != snapshot.tenant_record_id - or audit_resource_reference != expected_resource_reference - or audit_actor_reference != actor_reference - or audit_purpose_code != purpose_code + audit_evidence.tenant_record_id != snapshot.tenant_record_id + or audit_evidence.resource_reference != expected_resource_reference + or audit_evidence.actor_reference != actor_reference + or audit_evidence.purpose_code != purpose_code ): raise JobAnalysisIntegrityError( "audit event does not match the job-analysis write authority" ) + if ( + audit_evidence.source_service != _EXPECTED_AUDIT_SOURCE_SERVICE + or audit_evidence.event_type != _EXPECTED_AUDIT_EVENT_TYPE + or audit_evidence.reason_code != _EXPECTED_AUDIT_REASON_CODE + or audit_evidence.evidence_version_code != snapshot.analysis_version_code + or audit_evidence.result_code != _EXPECTED_AUDIT_RESULT_CODE + or audit_evidence.high_impact is not False + or audit_evidence.confirmation_reference is not None + ): + raise JobAnalysisIntegrityError( + "audit event does not match the job-analysis snapshot semantics" + ) with self.connection_factory() as connection: with connection.cursor() as cursor: @@ -499,10 +559,10 @@ def persist_snapshot( _AUDIT_OUTBOX_SQL, ( snapshot.tenant_record_id, - audit_event_id, + audit_evidence.event_id, outbox_delivery_record_id, - audit_canonical_json, - audit_content_digest, + audit_evidence.canonical_json, + audit_evidence.content_digest, "integration_hub", ), ) From 1665f743dea993489c92285c7d450b37a6edee82 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:06:45 +0900 Subject: [PATCH 136/241] test(job-analysis): reproduce snapshot mutation across DB acquisition --- .../test_postgres_snapshot_detachment.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_snapshot_detachment.py diff --git a/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py b/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py new file mode 100644 index 000000000..fc961f90d --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py @@ -0,0 +1,75 @@ +"""Regression coverage for detached Job Analysis snapshots at the PostgreSQL boundary.""" + +from __future__ import annotations + +from uuid import UUID + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import command_digest +from fixtures import IDEMPOTENCY_KEY, JOB, clinical_psychologist_snapshot +from test_postgres import FakeConnection, FakeCursor, _audit_event + +_MUTATED_ANALYSIS = UUID("0198a412-6000-7000-8000-000000000499") + + +def test_write_port_detaches_snapshot_before_executable_database_acquisition() -> None: + """Database hooks cannot retarget or rewrite already-authorized snapshot evidence.""" + snapshot = clinical_psychologist_snapshot() + expected_snapshot = snapshot.to_snapshot() + expected_analysis_id = snapshot.analysis_record_id + expected_version = snapshot.analysis_version_code + expected_task_statement = snapshot.tasks[0].task_statement + write_statement_count = ( + 1 + + len(snapshot.tasks) + + len(snapshot.ksao_requirements) + + len(snapshot.task_ksao_links) + + 2 + ) + cursor = FakeCursor([None, None, (JOB,)] + [None] * write_statement_count) + + def connection_factory() -> FakeConnection: + object.__setattr__(snapshot, "analysis_record_id", _MUTATED_ANALYSIS) + object.__setattr__(snapshot, "analysis_version_code", "clinical-psychologist:mutated") + object.__setattr__(snapshot.tasks[0], "task_statement", "Mutated task statement after authorization") + return FakeConnection(cursor) + + persisted = PostgresJobAnalysisPort(connection_factory).persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), + ) + + snapshot_insert = next( + parameters + for statement, parameters in cursor.executions + if statement.startswith("INSERT INTO public.job_analysis_snapshot") + ) + task_insert = next( + parameters + for statement, parameters in cursor.executions + if statement.startswith("INSERT INTO public.job_analysis_task_item") + ) + command_insert = next( + parameters + for statement, parameters in cursor.executions + if statement.startswith("INSERT INTO public.job_analysis_write_command") + ) + + assert snapshot_insert[1] == expected_analysis_id + assert snapshot_insert[5] == expected_version + assert task_insert[1] == expected_analysis_id + assert task_insert[3] == expected_task_statement + assert command_insert[2] == expected_analysis_id + assert persisted.to_snapshot() == expected_snapshot From f8543a72bb4243f7f0bb5e7ee23d6e1b9122355b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:08:06 +0900 Subject: [PATCH 137/241] fix(job-analysis): detach snapshot before database hooks --- .../src/orgmetra_job_analysis_api/postgres.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 74c66def5..eaca8f560 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -33,6 +33,7 @@ JobAnalysisIntegrityError, JobAnalysisScopeMissing, _validate_idempotency_key, + snapshot_from_document, validate_operational_uuid, ) @@ -217,6 +218,25 @@ def _validate_durable_command_scalars( raise ValueError("purpose_code must be exact built-in text.") +def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapshot: + """Rebuild exact snapshot evidence before any executable database boundary runs.""" + tenant_record_id = validate_operational_uuid( + "snapshot.tenant_record_id", + snapshot.tenant_record_id, + ) + canonical_json = snapshot.canonical_json() + try: + document = json.loads(canonical_json) + except (TypeError, ValueError) as error: + raise JobAnalysisIntegrityError("snapshot canonical evidence is not valid JSON") from error + if not isinstance(document, dict): + raise JobAnalysisIntegrityError("snapshot canonical evidence must be an object") + detached = snapshot_from_document(document, tenant_record_id=tenant_record_id) + if detached.canonical_json() != canonical_json: + raise JobAnalysisIntegrityError("detached snapshot does not match canonical evidence") + return detached + + @dataclass(frozen=True, slots=True) class _DurableAuditEvidence: """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition.""" @@ -367,6 +387,7 @@ def persist_snapshot( actor_reference=actor_reference, purpose_code=purpose_code, ) + snapshot = _detach_durable_snapshot(snapshot) audit_evidence = _snapshot_durable_audit_authority(audit_event) write_command_id = validate_operational_uuid("write_command_id", write_command_id) outbox_delivery_record_id = validate_operational_uuid( From 522559babbec7b85a1cde201bdd6e42878c03387 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:13:27 +0900 Subject: [PATCH 138/241] test(job-analysis): cover noncanonical snapshot rejection --- .../test_postgres_snapshot_detachment.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py b/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py index fc961f90d..2b7bbf3df 100644 --- a/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py +++ b/services/job-analysis-api/tests/test_postgres_snapshot_detachment.py @@ -4,8 +4,10 @@ from uuid import UUID +import pytest + from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort -from orgmetra_job_analysis_api.snapshot import command_digest +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError, command_digest from fixtures import IDEMPOTENCY_KEY, JOB, clinical_psychologist_snapshot from test_postgres import FakeConnection, FakeCursor, _audit_event @@ -73,3 +75,34 @@ def connection_factory() -> FakeConnection: assert task_insert[3] == expected_task_statement assert command_insert[2] == expected_analysis_id assert persisted.to_snapshot() == expected_snapshot + + +def test_write_port_rejects_noncanonical_snapshot_before_database_acquisition() -> None: + """A low-level-normalizable mutation must not silently change durable evidence.""" + snapshot = clinical_psychologist_snapshot() + original_statement = snapshot.tasks[0].task_statement + object.__setattr__(snapshot.tasks[0], "task_statement", f" {original_statement} ") + + def never_connect() -> object: + raise AssertionError("database acquired before noncanonical snapshot rejection") + + with pytest.raises( + JobAnalysisIntegrityError, + match="detached snapshot does not match canonical evidence", + ): + PostgresJobAnalysisPort(never_connect).persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), + ) From abb0916a455404d1bcf0abe96776e6777e81ae0b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:14:25 +0900 Subject: [PATCH 139/241] refactor(job-analysis): keep snapshot detachment coverage exact --- .../src/orgmetra_job_analysis_api/postgres.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index eaca8f560..613f29c1f 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -225,12 +225,7 @@ def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapsh snapshot.tenant_record_id, ) canonical_json = snapshot.canonical_json() - try: - document = json.loads(canonical_json) - except (TypeError, ValueError) as error: - raise JobAnalysisIntegrityError("snapshot canonical evidence is not valid JSON") from error - if not isinstance(document, dict): - raise JobAnalysisIntegrityError("snapshot canonical evidence must be an object") + document = json.loads(canonical_json) detached = snapshot_from_document(document, tenant_record_id=tenant_record_id) if detached.canonical_json() != canonical_json: raise JobAnalysisIntegrityError("detached snapshot does not match canonical evidence") From 8005a44a63fd6319ec4d738c0daa5e856f2e0b5c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:23:02 +0900 Subject: [PATCH 140/241] test(job-analysis): reproduce unbound durable request digest --- .../test_postgres_request_digest_binding.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_request_digest_binding.py diff --git a/services/job-analysis-api/tests/test_postgres_request_digest_binding.py b/services/job-analysis-api/tests/test_postgres_request_digest_binding.py new file mode 100644 index 000000000..d5e66618e --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_request_digest_binding.py @@ -0,0 +1,37 @@ +"""Regression coverage for semantic request-digest binding at the PostgreSQL boundary.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError +from fixtures import IDEMPOTENCY_KEY, clinical_psychologist_snapshot +from test_postgres import _audit_event + + +def test_write_port_rejects_well_formed_digest_for_different_command_before_database() -> None: + """A syntactically valid digest cannot redefine idempotency semantics at the durable port.""" + snapshot = clinical_psychologist_snapshot() + + def never_connect() -> object: + raise AssertionError("database acquired before request-digest binding validation") + + with pytest.raises( + JobAnalysisIntegrityError, + match="request_digest does not match detached snapshot command", + ): + PostgresJobAnalysisPort(never_connect).persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest="0" * 64, + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), + ) From 2780bca872779b3f074a1243cc7c54feb96e4a02 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:24:50 +0900 Subject: [PATCH 141/241] fix(job-analysis): bind durable request digest to detached command --- .../src/orgmetra_job_analysis_api/postgres.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 613f29c1f..486b1dd5c 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -33,6 +33,7 @@ JobAnalysisIntegrityError, JobAnalysisScopeMissing, _validate_idempotency_key, + command_digest, snapshot_from_document, validate_operational_uuid, ) @@ -396,6 +397,15 @@ def persist_snapshot( "criterion_blueprint_id", criterion_blueprint_id, ) + expected_request_digest = command_digest( + snapshot=snapshot, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + ) + if request_digest != expected_request_digest: + raise JobAnalysisIntegrityError( + "request_digest does not match detached snapshot command" + ) expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" if ( audit_evidence.tenant_record_id != snapshot.tenant_record_id From f7e42ab82fa1276e4162918a6f4a632c822a1a1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:36:48 +0900 Subject: [PATCH 142/241] test(job-analysis): reject replay bound to another snapshot --- .../test_postgres_idempotency_authority.py | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py index ccf9bbf2b..55d6fae6a 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py @@ -3,9 +3,14 @@ from __future__ import annotations import unittest +from uuid import UUID from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort, _IDEMPOTENCY_LOOKUP_SQL -from orgmetra_job_analysis_api.snapshot import JobAnalysisIdempotencyConflict, command_digest +from orgmetra_job_analysis_api.snapshot import ( + JobAnalysisIdempotencyConflict, + JobAnalysisIntegrityError, + command_digest, +) from fixtures import ANALYSIS, IDEMPOTENCY_KEY, clinical_psychologist_snapshot from test_postgres import ( @@ -20,13 +25,14 @@ class PostgresIdempotencyAuthorityTests(unittest.TestCase): - """Prove a durable idempotency key cannot cross actor or purpose authority.""" + """Prove a durable idempotency key cannot cross command or authority identity.""" def _persist_replay( self, *, stored_actor_reference: str, stored_purpose_code: str, + stored_analysis_record_id: UUID = ANALYSIS, actor_reference: str = "keyverse:actor-ja-1", purpose_code: str = "job_analysis_write", include_snapshot: bool = False, @@ -41,7 +47,7 @@ def _persist_replay( None, ( digest, - ANALYSIS, + stored_analysis_record_id, stored_actor_reference, stored_purpose_code, ), @@ -59,12 +65,8 @@ def _persist_replay( position_record_id=None, criterion_blueprint_id=None, audit_event=_audit_event(), - outbox_delivery_record_id=__import__("uuid").UUID( - "0198a412-6000-7000-8000-000000000302" - ), - write_command_id=__import__("uuid").UUID( - "0198a412-6000-7000-8000-000000000303" - ), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000302"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000303"), ) def test_lookup_reads_the_immutable_actor_and_purpose_binding(self) -> None: @@ -83,8 +85,18 @@ def test_same_key_and_digest_cannot_replay_under_a_different_purpose(self) -> No with self.assertRaisesRegex(JobAnalysisIdempotencyConflict, "purpose"): self._persist_replay(stored_actor_reference="keyverse:actor-ja-1", stored_purpose_code="job_analysis_read") - def test_exact_actor_and_purpose_replay_returns_the_stored_snapshot(self) -> None: - """Preserve the successful retry contract for the exact original authority.""" + def test_same_digest_cannot_replay_a_different_snapshot_identity(self) -> None: + """A durable digest cannot authorize replay of another persisted snapshot identity.""" + foreign_analysis_record_id = UUID("0198a412-6000-7000-8000-000000000399") + with self.assertRaisesRegex(JobAnalysisIntegrityError, "analysis_record_id"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code="job_analysis_write", + stored_analysis_record_id=foreign_analysis_record_id, + ) + + def test_exact_actor_purpose_and_snapshot_replay_returns_the_stored_snapshot(self) -> None: + """Preserve the successful retry contract for the exact original command authority.""" replayed = self._persist_replay( stored_actor_reference="keyverse:actor-ja-1", stored_purpose_code="job_analysis_write", From 14c2b8145823b39c3a88c41c6598de1b1db3273d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:39:10 +0900 Subject: [PATCH 143/241] fix(job-analysis): bind idempotent replay to snapshot identity --- .../src/orgmetra_job_analysis_api/postgres.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 486b1dd5c..4f59f9ae2 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -448,6 +448,19 @@ def persist_snapshot( raise JobAnalysisIdempotencyConflict( "idempotency key is bound to a different snapshot digest" ) + try: + stored_analysis_id = validate_operational_uuid( + "stored analysis_record_id", + stored_analysis_id, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent command has invalid analysis_record_id" + ) from error + if stored_analysis_id != snapshot.analysis_record_id: + raise JobAnalysisIntegrityError( + "idempotent command analysis_record_id does not match detached snapshot" + ) if stored_authority: stored_actor_reference, stored_purpose_code = stored_authority if stored_actor_reference != actor_reference: From 85beec4283a7ff935ed90667b66bc0a9ea2ca23b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 05:39:48 +0900 Subject: [PATCH 144/241] test(job-analysis): cover malformed durable replay identity --- .../tests/test_postgres_idempotency_authority.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py index 55d6fae6a..cda98c458 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py @@ -32,7 +32,7 @@ def _persist_replay( *, stored_actor_reference: str, stored_purpose_code: str, - stored_analysis_record_id: UUID = ANALYSIS, + stored_analysis_record_id: object = ANALYSIS, actor_reference: str = "keyverse:actor-ja-1", purpose_code: str = "job_analysis_write", include_snapshot: bool = False, @@ -95,6 +95,15 @@ def test_same_digest_cannot_replay_a_different_snapshot_identity(self) -> None: stored_analysis_record_id=foreign_analysis_record_id, ) + def test_malformed_stored_snapshot_identity_fails_closed(self) -> None: + """Corrupt durable replay identity is an integrity failure, not a load target.""" + with self.assertRaisesRegex(JobAnalysisIntegrityError, "invalid analysis_record_id"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code="job_analysis_write", + stored_analysis_record_id="0198a412-6000-7000-8000-000000000399", + ) + def test_exact_actor_purpose_and_snapshot_replay_returns_the_stored_snapshot(self) -> None: """Preserve the successful retry contract for the exact original command authority.""" replayed = self._persist_replay( From 6ae246bcf2ef2d6f312707a070351c0d80ea2f48 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 06:06:13 +0900 Subject: [PATCH 145/241] test(job-analysis): bind replayed snapshot to command digest --- .../test_postgres_idempotency_authority.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py index cda98c458..6c415e6df 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py @@ -2,6 +2,7 @@ from __future__ import annotations +from dataclasses import replace import unittest from uuid import UUID @@ -36,6 +37,7 @@ def _persist_replay( actor_reference: str = "keyverse:actor-ja-1", purpose_code: str = "job_analysis_write", include_snapshot: bool = False, + stored_snapshot_header: tuple[object, ...] | None = None, ) -> object: snapshot = clinical_psychologist_snapshot() digest = command_digest( @@ -53,7 +55,14 @@ def _persist_replay( ), ] if include_snapshot: - script.extend([[_header_row()], _task_rows(), _ksao_rows(), _link_rows()]) + script.extend( + [ + [stored_snapshot_header if stored_snapshot_header is not None else _header_row()], + _task_rows(), + _ksao_rows(), + _link_rows(), + ] + ) cursor = FakeCursor(script) port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) return port.persist_snapshot( @@ -104,6 +113,24 @@ def test_malformed_stored_snapshot_identity_fails_closed(self) -> None: stored_analysis_record_id="0198a412-6000-7000-8000-000000000399", ) + def test_same_command_row_cannot_replay_different_snapshot_content(self) -> None: + """Bind returned durable snapshot semantics back to the idempotency command digest.""" + requested_snapshot = clinical_psychologist_snapshot() + altered_snapshot = replace( + requested_snapshot, + analysis_version_code="clinical-psychologist:v2", + ) + altered_header = list(_header_row(digest=altered_snapshot.content_digest())) + altered_header[3] = altered_snapshot.analysis_version_code + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "recorded command digest"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code="job_analysis_write", + include_snapshot=True, + stored_snapshot_header=tuple(altered_header), + ) + def test_exact_actor_purpose_and_snapshot_replay_returns_the_stored_snapshot(self) -> None: """Preserve the successful retry contract for the exact original command authority.""" replayed = self._persist_replay( From 462953a7c8b011bcd10293d08e47a5e652da8c0a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 06:08:12 +0900 Subject: [PATCH 146/241] fix(job-analysis): verify replay content against command digest --- .../src/orgmetra_job_analysis_api/postgres.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 4f59f9ae2..e0eaba7d8 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -478,6 +478,15 @@ def persist_snapshot( ) if replayed is None: raise JobAnalysisIntegrityError("idempotent command lost its snapshot") + replayed_digest = command_digest( + snapshot=replayed, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + ) + if replayed_digest != request_digest: + raise JobAnalysisIntegrityError( + "idempotent replay snapshot does not match recorded command digest" + ) return replayed try: @@ -720,4 +729,4 @@ def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[objec importance_level=row[3], proficiency_level=row[4], source=_source_from_row(row[5:11]), - ) + ) \ No newline at end of file From def403c920262a3f782ac39e731e8db059823c5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 07:08:08 +0900 Subject: [PATCH 147/241] test(job-analysis): reject malformed durable replay command rows --- .../test_postgres_idempotency_authority.py | 69 ++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py index 6c415e6df..e7886b347 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py @@ -25,15 +25,27 @@ ) +class _AlwaysEqualText(str): + """Model a non-canonical DB-returned text value that lies during comparison.""" + + def __eq__(self, other: object) -> bool: + return True + + def __ne__(self, other: object) -> bool: + return False + + class PostgresIdempotencyAuthorityTests(unittest.TestCase): """Prove a durable idempotency key cannot cross command or authority identity.""" def _persist_replay( self, *, - stored_actor_reference: str, - stored_purpose_code: str, + stored_actor_reference: object, + stored_purpose_code: object, stored_analysis_record_id: object = ANALYSIS, + stored_request_digest: object | None = None, + include_stored_authority: bool = True, actor_reference: str = "keyverse:actor-ja-1", purpose_code: str = "job_analysis_write", include_snapshot: bool = False, @@ -45,15 +57,18 @@ def _persist_replay( position_record_id=None, criterion_blueprint_id=None, ) - script: list[object] = [ - None, - ( - digest, + durable_digest = digest if stored_request_digest is None else stored_request_digest + durable_row: tuple[object, ...] + if include_stored_authority: + durable_row = ( + durable_digest, stored_analysis_record_id, stored_actor_reference, stored_purpose_code, - ), - ] + ) + else: + durable_row = (durable_digest, stored_analysis_record_id) + script: list[object] = [None, durable_row] if include_snapshot: script.extend( [ @@ -84,6 +99,44 @@ def test_lookup_reads_the_immutable_actor_and_purpose_binding(self) -> None: self.assertIn("actor_reference", normalized) self.assertIn("purpose_code", normalized) + def test_authorityless_durable_row_fails_closed(self) -> None: + """Reject a replay row that does not have the four columns selected by the SQL contract.""" + with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command row"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code="job_analysis_write", + include_stored_authority=False, + include_snapshot=True, + ) + + def test_noncanonical_stored_digest_cannot_bypass_digest_binding(self) -> None: + """Revalidate database-returned digest text before using equality for authority.""" + with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code="job_analysis_write", + stored_request_digest=_AlwaysEqualText("f" * 64), + include_snapshot=True, + ) + + def test_noncanonical_stored_actor_cannot_bypass_actor_binding(self) -> None: + """Reject a driver-returned actor text subtype before comparing authority identity.""" + with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command"): + self._persist_replay( + stored_actor_reference=_AlwaysEqualText("keyverse:actor-ja-other"), + stored_purpose_code="job_analysis_write", + include_snapshot=True, + ) + + def test_noncanonical_stored_purpose_cannot_bypass_purpose_binding(self) -> None: + """Reject a driver-returned purpose text subtype before comparing purpose authority.""" + with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code=_AlwaysEqualText("job_analysis_read"), + include_snapshot=True, + ) + def test_same_key_and_digest_cannot_replay_under_a_different_actor(self) -> None: """Prevent one authorized principal from inheriting another actor's command.""" with self.assertRaisesRegex(JobAnalysisIdempotencyConflict, "actor"): From cf5c9a622107dcefdd72af82d3a0207e68182b88 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 07:09:35 +0900 Subject: [PATCH 148/241] fix(job-analysis): revalidate durable replay command evidence --- .../src/orgmetra_job_analysis_api/postgres.py | 88 ++++++++++++------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index e0eaba7d8..4dacd0cdf 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -442,27 +442,47 @@ def persist_snapshot( ), ) existing = cursor.fetchone() - if existing is not None and existing[0] is not None: - stored_digest, stored_analysis_id, *stored_authority = existing - if stored_digest != request_digest: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different snapshot digest" - ) + if existing is not None: try: - stored_analysis_id = validate_operational_uuid( - "stored analysis_record_id", + ( + stored_digest, stored_analysis_id, - ) - except ValueError as error: + stored_actor_reference, + stored_purpose_code, + ) = existing + except (TypeError, ValueError) as error: raise JobAnalysisIntegrityError( - "idempotent command has invalid analysis_record_id" + "idempotent durable command row has invalid shape" ) from error - if stored_analysis_id != snapshot.analysis_record_id: - raise JobAnalysisIntegrityError( - "idempotent command analysis_record_id does not match detached snapshot" - ) - if stored_authority: - stored_actor_reference, stored_purpose_code = stored_authority + if stored_digest is not None: + try: + _validate_durable_command_scalars( + idempotency_key=idempotency_key, + request_digest=stored_digest, + actor_reference=stored_actor_reference, + purpose_code=stored_purpose_code, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent durable command has invalid scalar evidence" + ) from error + if stored_digest != request_digest: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different snapshot digest" + ) + try: + stored_analysis_id = validate_operational_uuid( + "stored analysis_record_id", + stored_analysis_id, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent command has invalid analysis_record_id" + ) from error + if stored_analysis_id != snapshot.analysis_record_id: + raise JobAnalysisIntegrityError( + "idempotent command analysis_record_id does not match detached snapshot" + ) if stored_actor_reference != actor_reference: raise JobAnalysisIdempotencyConflict( "idempotency key is bound to a different actor" @@ -471,23 +491,23 @@ def persist_snapshot( raise JobAnalysisIdempotencyConflict( "idempotency key is bound to a different purpose" ) - replayed = self._load_snapshot( - cursor, - tenant_record_id=snapshot.tenant_record_id, - analysis_record_id=stored_analysis_id, - ) - if replayed is None: - raise JobAnalysisIntegrityError("idempotent command lost its snapshot") - replayed_digest = command_digest( - snapshot=replayed, - position_record_id=position_record_id, - criterion_blueprint_id=criterion_blueprint_id, - ) - if replayed_digest != request_digest: - raise JobAnalysisIntegrityError( - "idempotent replay snapshot does not match recorded command digest" + replayed = self._load_snapshot( + cursor, + tenant_record_id=snapshot.tenant_record_id, + analysis_record_id=stored_analysis_id, + ) + if replayed is None: + raise JobAnalysisIntegrityError("idempotent command lost its snapshot") + replayed_digest = command_digest( + snapshot=replayed, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, ) - return replayed + if replayed_digest != request_digest: + raise JobAnalysisIntegrityError( + "idempotent replay snapshot does not match recorded command digest" + ) + return replayed try: cursor.execute( @@ -729,4 +749,4 @@ def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[objec importance_level=row[3], proficiency_level=row[4], source=_source_from_row(row[5:11]), - ) \ No newline at end of file + ) From d355bbe6c58eca1e5abc9805dd814e24cb6dbe6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 07:10:06 +0900 Subject: [PATCH 149/241] test(job-analysis): align replay fixtures with durable SQL row --- services/job-analysis-api/tests/test_postgres.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres.py b/services/job-analysis-api/tests/test_postgres.py index 61c6c8627..2315cbec6 100644 --- a/services/job-analysis-api/tests/test_postgres.py +++ b/services/job-analysis-api/tests/test_postgres.py @@ -296,7 +296,7 @@ def test_idempotent_replay_returns_stored_snapshot_without_new_write(self) -> No digest = command_digest(snapshot=snapshot, position_record_id=None, criterion_blueprint_id=None) script = [ None, - (digest, ANALYSIS), + (digest, ANALYSIS, "keyverse:actor-ja-1", "job_analysis_write"), [_header_row()], _task_rows(), _ksao_rows(), @@ -310,10 +310,14 @@ def test_idempotent_replay_returns_stored_snapshot_without_new_write(self) -> No def test_idempotency_conflict_and_lost_snapshot_fail_closed(self) -> None: snapshot = clinical_psychologist_snapshot() digest = command_digest(snapshot=snapshot, position_record_id=None, criterion_blueprint_id=None) - port, _ = self._port([None, ("other" * 16, ANALYSIS)]) + port, _ = self._port( + [None, ("f" * 64, ANALYSIS, "keyverse:actor-ja-1", "job_analysis_write")] + ) with self.assertRaises(JobAnalysisIdempotencyConflict): self._persist(port, request_digest=digest) - port, _ = self._port([None, (digest, ANALYSIS), []]) + port, _ = self._port( + [None, (digest, ANALYSIS, "keyverse:actor-ja-1", "job_analysis_write"), []] + ) with self.assertRaisesRegex(JobAnalysisIntegrityError, "lost its snapshot"): self._persist(port, request_digest=digest) From 84e9c75fa6fe0a1b04ccb3c0eb578c25e1078a20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 07:18:24 +0900 Subject: [PATCH 150/241] test(job-analysis): align concurrency replay sentinel --- services/job-analysis-api/tests/test_postgres_concurrency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_concurrency.py b/services/job-analysis-api/tests/test_postgres_concurrency.py index 67f2abdcf..dcd71e574 100644 --- a/services/job-analysis-api/tests/test_postgres_concurrency.py +++ b/services/job-analysis-api/tests/test_postgres_concurrency.py @@ -67,7 +67,7 @@ def execute(self, sql: str, parameters: tuple[object, ...] | None = None) -> Non self._last = None return if "FROM public.job_analysis_write_command" in sql: - self._last = (None, None) + self._last = (None, None, None, None) elif "FROM public.job_profile" in sql: self._last = (JOB,) else: From a16b46f734ff59c959747391f15abffca35678a3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 08:05:22 +0900 Subject: [PATCH 151/241] test(job-analysis): reject partial-null replay command evidence --- .../tests/test_postgres_idempotency_authority.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py index e7886b347..96ebf97ba 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_authority.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_authority.py @@ -25,6 +25,9 @@ ) +_UNSET = object() + + class _AlwaysEqualText(str): """Model a non-canonical DB-returned text value that lies during comparison.""" @@ -44,7 +47,7 @@ def _persist_replay( stored_actor_reference: object, stored_purpose_code: object, stored_analysis_record_id: object = ANALYSIS, - stored_request_digest: object | None = None, + stored_request_digest: object = _UNSET, include_stored_authority: bool = True, actor_reference: str = "keyverse:actor-ja-1", purpose_code: str = "job_analysis_write", @@ -57,7 +60,7 @@ def _persist_replay( position_record_id=None, criterion_blueprint_id=None, ) - durable_digest = digest if stored_request_digest is None else stored_request_digest + durable_digest = digest if stored_request_digest is _UNSET else stored_request_digest durable_row: tuple[object, ...] if include_stored_authority: durable_row = ( @@ -109,6 +112,15 @@ def test_authorityless_durable_row_fails_closed(self) -> None: include_snapshot=True, ) + def test_partial_null_durable_row_fails_closed(self) -> None: + """Only the all-NULL LEFT JOIN projection can mean that no durable command exists.""" + with self.assertRaisesRegex(JobAnalysisIntegrityError, "partial-null"): + self._persist_replay( + stored_actor_reference="keyverse:actor-ja-1", + stored_purpose_code="job_analysis_write", + stored_request_digest=None, + ) + def test_noncanonical_stored_digest_cannot_bypass_digest_binding(self) -> None: """Revalidate database-returned digest text before using equality for authority.""" with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command"): From 1bb57bda1497580fdb9bbbe43e26e84aa787ced1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 08:07:40 +0900 Subject: [PATCH 152/241] fix(job-analysis): fail closed on partial-null replay evidence --- .../src/orgmetra_job_analysis_api/postgres.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 4dacd0cdf..9fd27a1e7 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -454,7 +454,19 @@ def persist_snapshot( raise JobAnalysisIntegrityError( "idempotent durable command row has invalid shape" ) from error - if stored_digest is not None: + if stored_digest is None: + if any( + value is not None + for value in ( + stored_analysis_id, + stored_actor_reference, + stored_purpose_code, + ) + ): + raise JobAnalysisIntegrityError( + "idempotent durable command row is partial-null" + ) + else: try: _validate_durable_command_scalars( idempotency_key=idempotency_key, From 3598b464e9dee1616226981aba549e15432e60a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:05:00 +0900 Subject: [PATCH 153/241] test(job-analysis): fail closed on missing idempotency projection --- ..._postgres_idempotency_lookup_projection.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py new file mode 100644 index 000000000..09b4410b5 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py @@ -0,0 +1,48 @@ +"""Fail-closed regression for the idempotency lookup projection contract.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError, command_digest + +from fixtures import IDEMPOTENCY_KEY, clinical_psychologist_snapshot +from test_postgres import FakeConnection, FakeCursor, _audit_event + + +class PostgresIdempotencyLookupProjectionTests(unittest.TestCase): + """Require the advisory-lock LEFT JOIN to return its one-row projection.""" + + def test_missing_lookup_projection_fails_before_scope_reads(self) -> None: + """Treat DB-API ``None`` as impossible evidence, not as command absence.""" + snapshot = clinical_psychologist_snapshot() + cursor = FakeCursor([None, None]) + port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "lookup.*projection"): + port.persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000302"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000303"), + ) + + self.assertFalse( + any("FROM public.job_profile" in statement for statement, _ in cursor.executions) + ) + + +if __name__ == "__main__": + unittest.main() From 521011ffb81b37ff4063615d026c4640a535942c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:10:31 +0900 Subject: [PATCH 154/241] fix(job-analysis): require idempotency lookup projection --- .../src/orgmetra_job_analysis_api/postgres.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 9fd27a1e7..06b2850ed 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -442,6 +442,10 @@ def persist_snapshot( ), ) existing = cursor.fetchone() + if existing is None: + raise JobAnalysisIntegrityError( + "idempotent durable command lookup returned no projection" + ) if existing is not None: try: ( From 5b95eb4b12e4a5aaf3ffe83282a6c98f041bcea1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:11:39 +0900 Subject: [PATCH 155/241] test(job-analysis): model idempotency LEFT JOIN absence projection --- services/job-analysis-api/tests/test_postgres.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres.py b/services/job-analysis-api/tests/test_postgres.py index 2315cbec6..fcd0978ba 100644 --- a/services/job-analysis-api/tests/test_postgres.py +++ b/services/job-analysis-api/tests/test_postgres.py @@ -47,6 +47,8 @@ def execute(self, sql: str, parameters: tuple[object, ...] | None = None) -> Non """Record each SQL statement and advance the scripted response.""" self.executions.append((sql, parameters)) self._last = self.script.pop(0) if self.script else None + if self._last is None and "FROM idempotency_lock" in sql: + self._last = (None, None, None, None) def fetchone(self) -> object: """Return the row prepared by the previous execute.""" From 59e9a46bebd761382588661c20af6ac8e7d42e8a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:12:05 +0900 Subject: [PATCH 156/241] test(job-analysis): preserve missing-projection regression --- .../test_postgres_idempotency_lookup_projection.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py index 09b4410b5..6cf8602b5 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py @@ -12,13 +12,23 @@ from test_postgres import FakeConnection, FakeCursor, _audit_event +class _MissingProjectionCursor(FakeCursor): + """Model a DB-API cursor violating the one-row LEFT JOIN projection contract.""" + + def fetchone(self) -> object: + """Return no row only for the idempotency projection under test.""" + if self.executions and "FROM idempotency_lock" in self.executions[-1][0]: + return None + return super().fetchone() + + class PostgresIdempotencyLookupProjectionTests(unittest.TestCase): """Require the advisory-lock LEFT JOIN to return its one-row projection.""" def test_missing_lookup_projection_fails_before_scope_reads(self) -> None: """Treat DB-API ``None`` as impossible evidence, not as command absence.""" snapshot = clinical_psychologist_snapshot() - cursor = FakeCursor([None, None]) + cursor = _MissingProjectionCursor([None, None]) port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) with self.assertRaisesRegex(JobAnalysisIntegrityError, "lookup.*projection"): From 7002ff3412d0a5a240d11e642a989a06c65c518d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:33:49 +0900 Subject: [PATCH 157/241] test(job-analysis): bind scope projections to requested identities --- ...est_postgres_scope_projection_integrity.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py new file mode 100644 index 000000000..0f4543587 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py @@ -0,0 +1,82 @@ +"""Regression coverage for scope-query target identity at the durable port.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError, command_digest +from fixtures import CRITERION, IDEMPOTENCY_KEY, JOB, POSITION, clinical_psychologist_snapshot +from test_postgres import FakeConnection, FakeCursor, _audit_event + + +_WRONG_JOB = UUID("0198a412-6000-7000-8000-000000000491") +_WRONG_POSITION = UUID("0198a412-6000-7000-8000-000000000492") +_WRONG_CRITERION = UUID("0198a412-6000-7000-8000-000000000493") + + +class PostgresScopeProjectionIntegrityTests(unittest.TestCase): + """Require scope rows to prove the exact Job, Position, and Criterion queried.""" + + def _persist( + self, + cursor: FakeCursor, + *, + position_record_id: UUID | None = None, + criterion_blueprint_id: UUID | None = None, + ) -> None: + snapshot = clinical_psychologist_snapshot() + PostgresJobAnalysisPort(lambda: FakeConnection(cursor)).persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + ), + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000494"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000495"), + ) + + def _assert_no_write(self, cursor: FakeCursor) -> None: + self.assertFalse( + any( + statement.startswith("INSERT INTO") + or "record_audit_outbox_event" in statement + for statement, _ in cursor.executions + ) + ) + + def test_job_scope_projection_must_name_requested_job(self) -> None: + cursor = FakeCursor([None, None, (_WRONG_JOB,)]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "job_profile scope row"): + self._persist(cursor) + + self._assert_no_write(cursor) + + def test_position_scope_projection_must_name_requested_position(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (_WRONG_POSITION, JOB)]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "position_record scope row"): + self._persist(cursor, position_record_id=POSITION) + + self._assert_no_write(cursor) + + def test_criterion_scope_projection_must_name_requested_criterion(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (_WRONG_CRITERION, JOB)]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "criterion_blueprint scope row"): + self._persist(cursor, criterion_blueprint_id=CRITERION) + + self._assert_no_write(cursor) + + +if __name__ == "__main__": + unittest.main() From 013664c929ec63fa9e17ecc0c57109113a49f196 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:35:42 +0900 Subject: [PATCH 158/241] fix(job-analysis): verify scope projection identities --- .../src/orgmetra_job_analysis_api/postgres.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 06b2850ed..308856d2a 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -530,8 +530,13 @@ def persist_snapshot( _JOB_SCOPE_SQL, (snapshot.tenant_record_id, snapshot.job_record_id), ) - if cursor.fetchone() is None: + job_row = cursor.fetchone() + if job_row is None: raise JobAnalysisScopeMissing("job_profile does not exist in the tenant") + if job_row[0] != snapshot.job_record_id: + raise JobAnalysisIntegrityError( + "job_profile scope row escaped requested target" + ) if position_record_id is not None: cursor.execute( _POSITION_SCOPE_SQL, @@ -540,6 +545,10 @@ def persist_snapshot( position_row = cursor.fetchone() if position_row is None or position_row[1] != snapshot.job_record_id: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") + if position_row[0] != position_record_id: + raise JobAnalysisIntegrityError( + "position_record scope row escaped requested target" + ) if criterion_blueprint_id is not None: cursor.execute( _CRITERION_SCOPE_SQL, @@ -550,6 +559,10 @@ def persist_snapshot( raise JobAnalysisScopeMissing( "criterion_blueprint is missing or not bound to the job" ) + if criterion_row[0] != criterion_blueprint_id: + raise JobAnalysisIntegrityError( + "criterion_blueprint scope row escaped requested target" + ) cursor.execute( _INSERT_SNAPSHOT_SQL, From aed5edac46077f47c595290430a0c45d7f014da7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:38:53 +0900 Subject: [PATCH 159/241] test(job-analysis): reject forged scope projection UUIDs --- ...est_postgres_scope_projection_integrity.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py index 0f4543587..64b91a8d6 100644 --- a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py @@ -16,6 +16,21 @@ _WRONG_CRITERION = UUID("0198a412-6000-7000-8000-000000000493") +class _EqualityForgedUUID(UUID): + """Model a DB-API UUID subtype that can forge equality at a trust boundary.""" + + def __eq__(self, other: object) -> bool: + return True + + def __ne__(self, other: object) -> bool: + return False + + +_FORGED_JOB = _EqualityForgedUUID(str(_WRONG_JOB)) +_FORGED_POSITION = _EqualityForgedUUID(str(_WRONG_POSITION)) +_FORGED_CRITERION = _EqualityForgedUUID(str(_WRONG_CRITERION)) + + class PostgresScopeProjectionIntegrityTests(unittest.TestCase): """Require scope rows to prove the exact Job, Position, and Criterion queried.""" @@ -77,6 +92,30 @@ def test_criterion_scope_projection_must_name_requested_criterion(self) -> None: self._assert_no_write(cursor) + def test_job_scope_projection_rejects_equality_forging_uuid_subtype(self) -> None: + cursor = FakeCursor([None, None, (_FORGED_JOB,)]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "job_profile scope row"): + self._persist(cursor) + + self._assert_no_write(cursor) + + def test_position_scope_projection_rejects_equality_forging_uuid_subtype(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (_FORGED_POSITION, JOB)]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "position_record scope row"): + self._persist(cursor, position_record_id=POSITION) + + self._assert_no_write(cursor) + + def test_criterion_scope_projection_rejects_equality_forging_uuid_subtype(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (_FORGED_CRITERION, JOB)]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "criterion_blueprint scope row"): + self._persist(cursor, criterion_blueprint_id=CRITERION) + + self._assert_no_write(cursor) + if __name__ == "__main__": unittest.main() From 94d9cf7fd2e169306b49e39abc04d4d0d5272fd7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:41:06 +0900 Subject: [PATCH 160/241] fix(job-analysis): validate scope projection UUID evidence --- .../src/orgmetra_job_analysis_api/postgres.py | 169 ++++++++++-------- 1 file changed, 95 insertions(+), 74 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 308856d2a..6cf3bf6cb 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -219,6 +219,16 @@ def _validate_durable_command_scalars( raise ValueError("purpose_code must be exact built-in text.") +def _validate_scope_projection_uuid(field_name: str, value: object) -> UUID: + """Normalize malformed durable scope identity evidence to an integrity failure.""" + try: + return validate_operational_uuid(field_name, value) + except ValueError as error: + raise JobAnalysisIntegrityError( + f"{field_name} scope row has invalid identity" + ) from error + + def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapshot: """Rebuild exact snapshot evidence before any executable database boundary runs.""" tenant_record_id = validate_operational_uuid( @@ -446,84 +456,83 @@ def persist_snapshot( raise JobAnalysisIntegrityError( "idempotent durable command lookup returned no projection" ) - if existing is not None: - try: - ( - stored_digest, + try: + ( + stored_digest, + stored_analysis_id, + stored_actor_reference, + stored_purpose_code, + ) = existing + except (TypeError, ValueError) as error: + raise JobAnalysisIntegrityError( + "idempotent durable command row has invalid shape" + ) from error + if stored_digest is None: + if any( + value is not None + for value in ( stored_analysis_id, stored_actor_reference, stored_purpose_code, - ) = existing - except (TypeError, ValueError) as error: + ) + ): + raise JobAnalysisIntegrityError( + "idempotent durable command row is partial-null" + ) + else: + try: + _validate_durable_command_scalars( + idempotency_key=idempotency_key, + request_digest=stored_digest, + actor_reference=stored_actor_reference, + purpose_code=stored_purpose_code, + ) + except ValueError as error: raise JobAnalysisIntegrityError( - "idempotent durable command row has invalid shape" + "idempotent durable command has invalid scalar evidence" ) from error - if stored_digest is None: - if any( - value is not None - for value in ( - stored_analysis_id, - stored_actor_reference, - stored_purpose_code, - ) - ): - raise JobAnalysisIntegrityError( - "idempotent durable command row is partial-null" - ) - else: - try: - _validate_durable_command_scalars( - idempotency_key=idempotency_key, - request_digest=stored_digest, - actor_reference=stored_actor_reference, - purpose_code=stored_purpose_code, - ) - except ValueError as error: - raise JobAnalysisIntegrityError( - "idempotent durable command has invalid scalar evidence" - ) from error - if stored_digest != request_digest: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different snapshot digest" - ) - try: - stored_analysis_id = validate_operational_uuid( - "stored analysis_record_id", - stored_analysis_id, - ) - except ValueError as error: - raise JobAnalysisIntegrityError( - "idempotent command has invalid analysis_record_id" - ) from error - if stored_analysis_id != snapshot.analysis_record_id: - raise JobAnalysisIntegrityError( - "idempotent command analysis_record_id does not match detached snapshot" - ) - if stored_actor_reference != actor_reference: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different actor" - ) - if stored_purpose_code != purpose_code: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different purpose" - ) - replayed = self._load_snapshot( - cursor, - tenant_record_id=snapshot.tenant_record_id, - analysis_record_id=stored_analysis_id, + if stored_digest != request_digest: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different snapshot digest" ) - if replayed is None: - raise JobAnalysisIntegrityError("idempotent command lost its snapshot") - replayed_digest = command_digest( - snapshot=replayed, - position_record_id=position_record_id, - criterion_blueprint_id=criterion_blueprint_id, + try: + stored_analysis_id = validate_operational_uuid( + "stored analysis_record_id", + stored_analysis_id, ) - if replayed_digest != request_digest: - raise JobAnalysisIntegrityError( - "idempotent replay snapshot does not match recorded command digest" - ) - return replayed + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent command has invalid analysis_record_id" + ) from error + if stored_analysis_id != snapshot.analysis_record_id: + raise JobAnalysisIntegrityError( + "idempotent command analysis_record_id does not match detached snapshot" + ) + if stored_actor_reference != actor_reference: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different actor" + ) + if stored_purpose_code != purpose_code: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different purpose" + ) + replayed = self._load_snapshot( + cursor, + tenant_record_id=snapshot.tenant_record_id, + analysis_record_id=stored_analysis_id, + ) + if replayed is None: + raise JobAnalysisIntegrityError("idempotent command lost its snapshot") + replayed_digest = command_digest( + snapshot=replayed, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + ) + if replayed_digest != request_digest: + raise JobAnalysisIntegrityError( + "idempotent replay snapshot does not match recorded command digest" + ) + return replayed try: cursor.execute( @@ -533,7 +542,11 @@ def persist_snapshot( job_row = cursor.fetchone() if job_row is None: raise JobAnalysisScopeMissing("job_profile does not exist in the tenant") - if job_row[0] != snapshot.job_record_id: + job_projection_id = _validate_scope_projection_uuid( + "job_profile", + job_row[0], + ) + if job_projection_id != snapshot.job_record_id: raise JobAnalysisIntegrityError( "job_profile scope row escaped requested target" ) @@ -545,7 +558,11 @@ def persist_snapshot( position_row = cursor.fetchone() if position_row is None or position_row[1] != snapshot.job_record_id: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") - if position_row[0] != position_record_id: + position_projection_id = _validate_scope_projection_uuid( + "position_record", + position_row[0], + ) + if position_projection_id != position_record_id: raise JobAnalysisIntegrityError( "position_record scope row escaped requested target" ) @@ -559,7 +576,11 @@ def persist_snapshot( raise JobAnalysisScopeMissing( "criterion_blueprint is missing or not bound to the job" ) - if criterion_row[0] != criterion_blueprint_id: + criterion_projection_id = _validate_scope_projection_uuid( + "criterion_blueprint", + criterion_row[0], + ) + if criterion_projection_id != criterion_blueprint_id: raise JobAnalysisIntegrityError( "criterion_blueprint scope row escaped requested target" ) From 9a972940378eec3bd3ef4c4fc222b8b5fddd8d23 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:42:22 +0900 Subject: [PATCH 161/241] repair(job-analysis): keep scope fix causally minimal --- .../src/orgmetra_job_analysis_api/postgres.py | 141 +++++++++--------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 6cf3bf6cb..bc2363f74 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -456,83 +456,84 @@ def persist_snapshot( raise JobAnalysisIntegrityError( "idempotent durable command lookup returned no projection" ) - try: - ( - stored_digest, - stored_analysis_id, - stored_actor_reference, - stored_purpose_code, - ) = existing - except (TypeError, ValueError) as error: - raise JobAnalysisIntegrityError( - "idempotent durable command row has invalid shape" - ) from error - if stored_digest is None: - if any( - value is not None - for value in ( + if existing is not None: + try: + ( + stored_digest, stored_analysis_id, stored_actor_reference, stored_purpose_code, - ) - ): + ) = existing + except (TypeError, ValueError) as error: raise JobAnalysisIntegrityError( - "idempotent durable command row is partial-null" - ) - else: - try: - _validate_durable_command_scalars( - idempotency_key=idempotency_key, - request_digest=stored_digest, - actor_reference=stored_actor_reference, - purpose_code=stored_purpose_code, - ) - except ValueError as error: - raise JobAnalysisIntegrityError( - "idempotent durable command has invalid scalar evidence" + "idempotent durable command row has invalid shape" ) from error - if stored_digest != request_digest: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different snapshot digest" - ) - try: - stored_analysis_id = validate_operational_uuid( - "stored analysis_record_id", - stored_analysis_id, - ) - except ValueError as error: - raise JobAnalysisIntegrityError( - "idempotent command has invalid analysis_record_id" - ) from error - if stored_analysis_id != snapshot.analysis_record_id: - raise JobAnalysisIntegrityError( - "idempotent command analysis_record_id does not match detached snapshot" - ) - if stored_actor_reference != actor_reference: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different actor" - ) - if stored_purpose_code != purpose_code: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different purpose" + if stored_digest is None: + if any( + value is not None + for value in ( + stored_analysis_id, + stored_actor_reference, + stored_purpose_code, + ) + ): + raise JobAnalysisIntegrityError( + "idempotent durable command row is partial-null" + ) + else: + try: + _validate_durable_command_scalars( + idempotency_key=idempotency_key, + request_digest=stored_digest, + actor_reference=stored_actor_reference, + purpose_code=stored_purpose_code, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent durable command has invalid scalar evidence" + ) from error + if stored_digest != request_digest: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different snapshot digest" + ) + try: + stored_analysis_id = validate_operational_uuid( + "stored analysis_record_id", + stored_analysis_id, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent command has invalid analysis_record_id" + ) from error + if stored_analysis_id != snapshot.analysis_record_id: + raise JobAnalysisIntegrityError( + "idempotent command analysis_record_id does not match detached snapshot" + ) + if stored_actor_reference != actor_reference: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different actor" + ) + if stored_purpose_code != purpose_code: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different purpose" + ) + replayed = self._load_snapshot( + cursor, + tenant_record_id=snapshot.tenant_record_id, + analysis_record_id=stored_analysis_id, ) - replayed = self._load_snapshot( - cursor, - tenant_record_id=snapshot.tenant_record_id, - analysis_record_id=stored_analysis_id, - ) - if replayed is None: - raise JobAnalysisIntegrityError("idempotent command lost its snapshot") - replayed_digest = command_digest( - snapshot=replayed, - position_record_id=position_record_id, - criterion_blueprint_id=criterion_blueprint_id, - ) - if replayed_digest != request_digest: - raise JobAnalysisIntegrityError( - "idempotent replay snapshot does not match recorded command digest" + if replayed is None: + raise JobAnalysisIntegrityError("idempotent command lost its snapshot") + replayed_digest = command_digest( + snapshot=replayed, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, ) - return replayed + if replayed_digest != request_digest: + raise JobAnalysisIntegrityError( + "idempotent replay snapshot does not match recorded command digest" + ) + return replayed try: cursor.execute( From 8d324b826b4d3dc4138fa7d1519a17ad56283835 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:47:05 +0900 Subject: [PATCH 162/241] test(job-analysis): reject forged scope relationship UUIDs --- ...est_postgres_scope_projection_integrity.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py index 64b91a8d6..336b37da8 100644 --- a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py @@ -116,6 +116,28 @@ def test_criterion_scope_projection_rejects_equality_forging_uuid_subtype(self) self._assert_no_write(cursor) + def test_position_scope_projection_rejects_forged_job_relationship_uuid(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (POSITION, _FORGED_JOB)]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "position_record.job_profile_id scope row", + ): + self._persist(cursor, position_record_id=POSITION) + + self._assert_no_write(cursor) + + def test_criterion_scope_projection_rejects_forged_job_relationship_uuid(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (CRITERION, _FORGED_JOB)]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "criterion_blueprint.job_profile_id scope row", + ): + self._persist(cursor, criterion_blueprint_id=CRITERION) + + self._assert_no_write(cursor) + if __name__ == "__main__": unittest.main() From 47074cad0062c9aa721cae538f84fe543f66b8ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 09:48:24 +0900 Subject: [PATCH 163/241] fix(job-analysis): validate scope relationship UUID evidence --- .../src/orgmetra_job_analysis_api/postgres.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index bc2363f74..bb12c3b43 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -557,7 +557,13 @@ def persist_snapshot( (snapshot.tenant_record_id, position_record_id), ) position_row = cursor.fetchone() - if position_row is None or position_row[1] != snapshot.job_record_id: + if position_row is None: + raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") + position_job_id = _validate_scope_projection_uuid( + "position_record.job_profile_id", + position_row[1], + ) + if position_job_id != snapshot.job_record_id: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") position_projection_id = _validate_scope_projection_uuid( "position_record", @@ -573,7 +579,15 @@ def persist_snapshot( (snapshot.tenant_record_id, criterion_blueprint_id), ) criterion_row = cursor.fetchone() - if criterion_row is None or criterion_row[1] != snapshot.job_record_id: + if criterion_row is None: + raise JobAnalysisScopeMissing( + "criterion_blueprint is missing or not bound to the job" + ) + criterion_job_id = _validate_scope_projection_uuid( + "criterion_blueprint.job_profile_id", + criterion_row[1], + ) + if criterion_job_id != snapshot.job_record_id: raise JobAnalysisScopeMissing( "criterion_blueprint is missing or not bound to the job" ) From a6ef26a11778ad4ebd53f46d2b195a24e54efde7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:00:34 +0900 Subject: [PATCH 164/241] test(job-analysis): reject malformed scope projection shapes --- ...est_postgres_scope_projection_integrity.py | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py index 336b37da8..b35e26060 100644 --- a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py @@ -116,6 +116,45 @@ def test_criterion_scope_projection_rejects_equality_forging_uuid_subtype(self) self._assert_no_write(cursor) + def test_job_scope_projection_rejects_invalid_shape(self) -> None: + for malformed_row in ((), (JOB, JOB)): + with self.subTest(malformed_row=malformed_row): + cursor = FakeCursor([None, None, malformed_row]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_profile scope row has invalid shape", + ): + self._persist(cursor) + + self._assert_no_write(cursor) + + def test_position_scope_projection_rejects_invalid_shape(self) -> None: + for malformed_row in ((POSITION,), (POSITION, JOB, JOB)): + with self.subTest(malformed_row=malformed_row): + cursor = FakeCursor([None, None, (JOB,), malformed_row]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "position_record scope row has invalid shape", + ): + self._persist(cursor, position_record_id=POSITION) + + self._assert_no_write(cursor) + + def test_criterion_scope_projection_rejects_invalid_shape(self) -> None: + for malformed_row in ((CRITERION,), (CRITERION, JOB, JOB)): + with self.subTest(malformed_row=malformed_row): + cursor = FakeCursor([None, None, (JOB,), malformed_row]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "criterion_blueprint scope row has invalid shape", + ): + self._persist(cursor, criterion_blueprint_id=CRITERION) + + self._assert_no_write(cursor) + def test_position_scope_projection_rejects_forged_job_relationship_uuid(self) -> None: cursor = FakeCursor([None, None, (JOB,), (POSITION, _FORGED_JOB)]) @@ -140,4 +179,4 @@ def test_criterion_scope_projection_rejects_forged_job_relationship_uuid(self) - if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file From 2a14b10e6e1eac95641e22f5995fdcdd39475e83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:01:40 +0900 Subject: [PATCH 165/241] test(job-analysis): cover non-sequence scope projection rows --- .../tests/test_postgres_scope_projection_integrity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py index b35e26060..aa5582f14 100644 --- a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py @@ -117,7 +117,7 @@ def test_criterion_scope_projection_rejects_equality_forging_uuid_subtype(self) self._assert_no_write(cursor) def test_job_scope_projection_rejects_invalid_shape(self) -> None: - for malformed_row in ((), (JOB, JOB)): + for malformed_row in (object(), (), (JOB, JOB)): with self.subTest(malformed_row=malformed_row): cursor = FakeCursor([None, None, malformed_row]) @@ -130,7 +130,7 @@ def test_job_scope_projection_rejects_invalid_shape(self) -> None: self._assert_no_write(cursor) def test_position_scope_projection_rejects_invalid_shape(self) -> None: - for malformed_row in ((POSITION,), (POSITION, JOB, JOB)): + for malformed_row in (object(), (POSITION,), (POSITION, JOB, JOB)): with self.subTest(malformed_row=malformed_row): cursor = FakeCursor([None, None, (JOB,), malformed_row]) @@ -143,7 +143,7 @@ def test_position_scope_projection_rejects_invalid_shape(self) -> None: self._assert_no_write(cursor) def test_criterion_scope_projection_rejects_invalid_shape(self) -> None: - for malformed_row in ((CRITERION,), (CRITERION, JOB, JOB)): + for malformed_row in (object(), (CRITERION,), (CRITERION, JOB, JOB)): with self.subTest(malformed_row=malformed_row): cursor = FakeCursor([None, None, (JOB,), malformed_row]) From 0220e5a1aedd953ff5a449c08c61b8f79b2196e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:02:52 +0900 Subject: [PATCH 166/241] fix(job-analysis): validate scope projection cardinality --- .../src/orgmetra_job_analysis_api/postgres.py | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index bb12c3b43..2b2a51c8f 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -229,6 +229,23 @@ def _validate_scope_projection_uuid(field_name: str, value: object) -> UUID: ) from error +def _unpack_scope_projection( + field_name: str, + row: Any, + expected_columns: int, +) -> tuple[object, ...]: + """Reject scope rows whose cardinality disagrees with the fixed SQL projection.""" + try: + values = tuple(row) + except TypeError as error: + raise JobAnalysisIntegrityError( + f"{field_name} scope row has invalid shape" + ) from error + if len(values) != expected_columns: + raise JobAnalysisIntegrityError(f"{field_name} scope row has invalid shape") + return values + + def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapshot: """Rebuild exact snapshot evidence before any executable database boundary runs.""" tenant_record_id = validate_operational_uuid( @@ -543,9 +560,14 @@ def persist_snapshot( job_row = cursor.fetchone() if job_row is None: raise JobAnalysisScopeMissing("job_profile does not exist in the tenant") + (job_projection_value,) = _unpack_scope_projection( + "job_profile", + job_row, + 1, + ) job_projection_id = _validate_scope_projection_uuid( "job_profile", - job_row[0], + job_projection_value, ) if job_projection_id != snapshot.job_record_id: raise JobAnalysisIntegrityError( @@ -559,15 +581,20 @@ def persist_snapshot( position_row = cursor.fetchone() if position_row is None: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") + position_projection_value, position_job_value = _unpack_scope_projection( + "position_record", + position_row, + 2, + ) position_job_id = _validate_scope_projection_uuid( "position_record.job_profile_id", - position_row[1], + position_job_value, ) if position_job_id != snapshot.job_record_id: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") position_projection_id = _validate_scope_projection_uuid( "position_record", - position_row[0], + position_projection_value, ) if position_projection_id != position_record_id: raise JobAnalysisIntegrityError( @@ -583,9 +610,14 @@ def persist_snapshot( raise JobAnalysisScopeMissing( "criterion_blueprint is missing or not bound to the job" ) + criterion_projection_value, criterion_job_value = _unpack_scope_projection( + "criterion_blueprint", + criterion_row, + 2, + ) criterion_job_id = _validate_scope_projection_uuid( "criterion_blueprint.job_profile_id", - criterion_row[1], + criterion_job_value, ) if criterion_job_id != snapshot.job_record_id: raise JobAnalysisScopeMissing( @@ -593,7 +625,7 @@ def persist_snapshot( ) criterion_projection_id = _validate_scope_projection_uuid( "criterion_blueprint", - criterion_row[0], + criterion_projection_value, ) if criterion_projection_id != criterion_blueprint_id: raise JobAnalysisIntegrityError( From e022fe59e95303c799d61080df3e99cf175c1fb0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:07:28 +0900 Subject: [PATCH 167/241] test(job-analysis): reject iterable non-row projections --- ...est_postgres_scope_projection_integrity.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py index aa5582f14..9269ac30c 100644 --- a/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_scope_projection_integrity.py @@ -155,6 +155,39 @@ def test_criterion_scope_projection_rejects_invalid_shape(self) -> None: self._assert_no_write(cursor) + def test_job_scope_projection_rejects_non_sequence_iterable(self) -> None: + cursor = FakeCursor([None, None, (value for value in (JOB,))]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_profile scope row has invalid shape", + ): + self._persist(cursor) + + self._assert_no_write(cursor) + + def test_position_scope_projection_rejects_non_sequence_iterable(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (value for value in (POSITION, JOB))]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "position_record scope row has invalid shape", + ): + self._persist(cursor, position_record_id=POSITION) + + self._assert_no_write(cursor) + + def test_criterion_scope_projection_rejects_non_sequence_iterable(self) -> None: + cursor = FakeCursor([None, None, (JOB,), (value for value in (CRITERION, JOB))]) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "criterion_blueprint scope row has invalid shape", + ): + self._persist(cursor, criterion_blueprint_id=CRITERION) + + self._assert_no_write(cursor) + def test_position_scope_projection_rejects_forged_job_relationship_uuid(self) -> None: cursor = FakeCursor([None, None, (JOB,), (POSITION, _FORGED_JOB)]) From 278a0326ff3f3cd5f62f488924e0acbce113de43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:09:23 +0900 Subject: [PATCH 168/241] fix(job-analysis): require sequence scope projections --- .../src/orgmetra_job_analysis_api/postgres.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 2b2a51c8f..d519935cd 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -9,6 +9,7 @@ from __future__ import annotations +from collections.abc import Sequence from contextlib import AbstractContextManager from dataclasses import dataclass from datetime import datetime, timezone @@ -234,7 +235,9 @@ def _unpack_scope_projection( row: Any, expected_columns: int, ) -> tuple[object, ...]: - """Reject scope rows whose cardinality disagrees with the fixed SQL projection.""" + """Reject scope rows whose sequence shape disagrees with the fixed SQL projection.""" + if not isinstance(row, Sequence) or isinstance(row, (str, bytes, bytearray, memoryview)): + raise JobAnalysisIntegrityError(f"{field_name} scope row has invalid shape") try: values = tuple(row) except TypeError as error: From f31f19898719ebf58ab77f059e7e0d11b62c195f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:12:54 +0900 Subject: [PATCH 169/241] test(job-analysis): reject malformed durable read projections --- ...test_postgres_read_projection_integrity.py | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_read_projection_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py new file mode 100644 index 000000000..1fdded3c4 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -0,0 +1,146 @@ +"""Regression coverage for fixed PostgreSQL read projections and target identity.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError +from fixtures import ANALYSIS, OTHER_TENANT, TENANT +from test_postgres import ( + FakeConnection, + FakeCursor, + _header_row, + _ksao_rows, + _link_rows, + _task_rows, +) + + +class _EqualityForgedUUID(UUID): + """Model DB-returned UUID evidence that forges equality before validation.""" + + def __eq__(self, other: object) -> bool: + return True + + def __ne__(self, other: object) -> bool: + return False + + +class PostgresReadProjectionIntegrityTests(unittest.TestCase): + """Require every fixed read projection to match its SQL row contract.""" + + def _read(self, script: list[object]) -> None: + cursor = FakeCursor(script) + port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) + port.read_snapshot(tenant_record_id=TENANT, analysis_record_id=ANALYSIS) + + def _valid_script( + self, + *, + headers: object | None = None, + tasks: object | None = None, + ksaos: object | None = None, + links: object | None = None, + ) -> list[object]: + return [ + None, + None, + [_header_row()] if headers is None else headers, + _task_rows() if tasks is None else tasks, + _ksao_rows() if ksaos is None else ksaos, + _link_rows() if links is None else links, + ] + + def test_read_rejects_invalid_snapshot_header_shape(self) -> None: + header = _header_row() + malformed_rows = ( + object(), + header[:-1], + header + ("surplus",), + (value for value in header), + ) + for row in malformed_rows: + with self.subTest(row_type=type(row).__name__): + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_snapshot row has invalid shape", + ): + self._read(self._valid_script(headers=[row])) + + def test_read_rejects_invalid_task_projection_shape(self) -> None: + canonical = _task_rows() + malformed_rows = ( + object(), + canonical[0][:-1], + canonical[0] + ("surplus",), + (value for value in canonical[0]), + ) + for row in malformed_rows: + with self.subTest(row_type=type(row).__name__): + rows = list(canonical) + rows[0] = row # type: ignore[assignment] + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_task_item row has invalid shape", + ): + self._read(self._valid_script(tasks=rows)) + + def test_read_rejects_invalid_ksao_projection_shape(self) -> None: + canonical = _ksao_rows() + malformed_rows = ( + object(), + canonical[0][:-1], + canonical[0] + ("surplus",), + (value for value in canonical[0]), + ) + for row in malformed_rows: + with self.subTest(row_type=type(row).__name__): + rows = list(canonical) + rows[0] = row # type: ignore[assignment] + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_ksao_item row has invalid shape", + ): + self._read(self._valid_script(ksaos=rows)) + + def test_read_rejects_invalid_link_projection_shape(self) -> None: + canonical = _link_rows() + malformed_rows = ( + object(), + canonical[0][:-1], + canonical[0] + ("surplus",), + (value for value in canonical[0]), + ) + for row in malformed_rows: + with self.subTest(row_type=type(row).__name__): + rows = list(canonical) + rows[0] = row # type: ignore[assignment] + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_task_ksao_link row has invalid shape", + ): + self._read(self._valid_script(links=rows)) + + def test_read_exact_validates_returned_target_identity_before_equality(self) -> None: + forged_tenant = _EqualityForgedUUID(str(OTHER_TENANT)) + forged_analysis = _EqualityForgedUUID("0198a412-6000-7000-8000-000000000499") + cases = ( + ( + _header_row(tenant_record_id=forged_tenant), + "job_analysis_snapshot.tenant_record_id row has invalid identity", + ), + ( + _header_row(analysis_record_id=forged_analysis), + "job_analysis_snapshot.analysis_record_id row has invalid identity", + ), + ) + for header, expected in cases: + with self.subTest(expected=expected): + with self.assertRaisesRegex(JobAnalysisIntegrityError, expected): + self._read(self._valid_script(headers=[header])) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From df92a02c18e7d19df2c2024e331770661217ecf7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:14:12 +0900 Subject: [PATCH 170/241] fix(job-analysis): validate durable read projections --- .../src/orgmetra_job_analysis_api/postgres.py | 87 ++++++++++++------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index d519935cd..4348a4f07 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -220,32 +220,33 @@ def _validate_durable_command_scalars( raise ValueError("purpose_code must be exact built-in text.") -def _validate_scope_projection_uuid(field_name: str, value: object) -> UUID: - """Normalize malformed durable scope identity evidence to an integrity failure.""" +def _validate_projection_uuid( + field_name: str, + value: object, + *, + row_label: str, +) -> UUID: + """Normalize malformed durable projection identity to an integrity failure.""" try: return validate_operational_uuid(field_name, value) except ValueError as error: - raise JobAnalysisIntegrityError( - f"{field_name} scope row has invalid identity" - ) from error + raise JobAnalysisIntegrityError(f"{row_label} has invalid identity") from error -def _unpack_scope_projection( - field_name: str, +def _unpack_fixed_projection( + row_label: str, row: Any, expected_columns: int, ) -> tuple[object, ...]: - """Reject scope rows whose sequence shape disagrees with the fixed SQL projection.""" + """Reject durable rows whose sequence shape disagrees with a fixed SQL projection.""" if not isinstance(row, Sequence) or isinstance(row, (str, bytes, bytearray, memoryview)): - raise JobAnalysisIntegrityError(f"{field_name} scope row has invalid shape") + raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") try: values = tuple(row) except TypeError as error: - raise JobAnalysisIntegrityError( - f"{field_name} scope row has invalid shape" - ) from error + raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") from error if len(values) != expected_columns: - raise JobAnalysisIntegrityError(f"{field_name} scope row has invalid shape") + raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") return values @@ -563,14 +564,15 @@ def persist_snapshot( job_row = cursor.fetchone() if job_row is None: raise JobAnalysisScopeMissing("job_profile does not exist in the tenant") - (job_projection_value,) = _unpack_scope_projection( - "job_profile", + (job_projection_value,) = _unpack_fixed_projection( + "job_profile scope", job_row, 1, ) - job_projection_id = _validate_scope_projection_uuid( + job_projection_id = _validate_projection_uuid( "job_profile", job_projection_value, + row_label="job_profile scope row", ) if job_projection_id != snapshot.job_record_id: raise JobAnalysisIntegrityError( @@ -584,20 +586,22 @@ def persist_snapshot( position_row = cursor.fetchone() if position_row is None: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") - position_projection_value, position_job_value = _unpack_scope_projection( - "position_record", + position_projection_value, position_job_value = _unpack_fixed_projection( + "position_record scope", position_row, 2, ) - position_job_id = _validate_scope_projection_uuid( + position_job_id = _validate_projection_uuid( "position_record.job_profile_id", position_job_value, + row_label="position_record.job_profile_id scope row", ) if position_job_id != snapshot.job_record_id: raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") - position_projection_id = _validate_scope_projection_uuid( + position_projection_id = _validate_projection_uuid( "position_record", position_projection_value, + row_label="position_record scope row", ) if position_projection_id != position_record_id: raise JobAnalysisIntegrityError( @@ -613,22 +617,24 @@ def persist_snapshot( raise JobAnalysisScopeMissing( "criterion_blueprint is missing or not bound to the job" ) - criterion_projection_value, criterion_job_value = _unpack_scope_projection( - "criterion_blueprint", + criterion_projection_value, criterion_job_value = _unpack_fixed_projection( + "criterion_blueprint scope", criterion_row, 2, ) - criterion_job_id = _validate_scope_projection_uuid( + criterion_job_id = _validate_projection_uuid( "criterion_blueprint.job_profile_id", criterion_job_value, + row_label="criterion_blueprint.job_profile_id scope row", ) if criterion_job_id != snapshot.job_record_id: raise JobAnalysisScopeMissing( "criterion_blueprint is missing or not bound to the job" ) - criterion_projection_id = _validate_scope_projection_uuid( + criterion_projection_id = _validate_projection_uuid( "criterion_blueprint", criterion_projection_value, + row_label="criterion_blueprint scope row", ) if criterion_projection_id != criterion_blueprint_id: raise JobAnalysisIntegrityError( @@ -769,18 +775,37 @@ def _load_snapshot( return None if len(headers) != 1: raise JobAnalysisIntegrityError("multiple snapshot headers match the requested target") - header = headers[0] - if header[0] != tenant_record_id or header[1] != analysis_record_id: + header = _unpack_fixed_projection("job_analysis_snapshot", headers[0], 19) + header_tenant_id = _validate_projection_uuid( + "job_analysis_snapshot.tenant_record_id", + header[0], + row_label="job_analysis_snapshot.tenant_record_id row", + ) + header_analysis_id = _validate_projection_uuid( + "job_analysis_snapshot.analysis_record_id", + header[1], + row_label="job_analysis_snapshot.analysis_record_id row", + ) + if header_tenant_id != tenant_record_id or header_analysis_id != analysis_record_id: raise JobAnalysisIntegrityError("database row escaped requested target") cursor.execute(_READ_TASKS_SQL, (tenant_record_id, analysis_record_id)) - task_rows = cursor.fetchall() + task_rows = tuple( + _unpack_fixed_projection("job_analysis_task_item", row, 10) + for row in cursor.fetchall() + ) cursor.execute(_READ_KSAOS_SQL, (tenant_record_id, analysis_record_id)) - ksao_rows = cursor.fetchall() + ksao_rows = tuple( + _unpack_fixed_projection("job_analysis_ksao_item", row, 11) + for row in cursor.fetchall() + ) cursor.execute(_READ_LINKS_SQL, (tenant_record_id, analysis_record_id)) - link_rows = cursor.fetchall() + link_rows = tuple( + _unpack_fixed_projection("job_analysis_task_ksao_link", row, 4) + for row in cursor.fetchall() + ) snapshot = JobAnalysisSnapshot( - analysis_record_id=header[1], - tenant_record_id=header[0], + analysis_record_id=header_analysis_id, + tenant_record_id=header_tenant_id, job_record_id=header[2], analysis_version_code=header[3], status_code=header[4], From 84df3b7738e5ff74f7c9d8fda9325d6a49e9601c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 10:15:18 +0900 Subject: [PATCH 171/241] test(job-analysis): cover failing sequence materialization --- .../tests/test_postgres_read_projection_integrity.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py index 1fdded3c4..89963a8b5 100644 --- a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Sequence import unittest from uuid import UUID @@ -28,6 +29,16 @@ def __ne__(self, other: object) -> bool: return False +class _BrokenSequence(Sequence[object]): + """Model a DB-API row sequence that fails while values are detached.""" + + def __len__(self) -> int: + return 1 + + def __getitem__(self, index: int) -> object: + raise TypeError("broken row sequence") + + class PostgresReadProjectionIntegrityTests(unittest.TestCase): """Require every fixed read projection to match its SQL row contract.""" @@ -57,6 +68,7 @@ def test_read_rejects_invalid_snapshot_header_shape(self) -> None: header = _header_row() malformed_rows = ( object(), + _BrokenSequence(), header[:-1], header + ("surplus",), (value for value in header), From bf447c799f15db829bcedbd792d0d48b9e80db23 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 11:02:48 +0900 Subject: [PATCH 172/241] test(auth): reject executable bearer header subtype --- services/job-analysis-api/tests/test_auth.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/job-analysis-api/tests/test_auth.py b/services/job-analysis-api/tests/test_auth.py index fa5923fec..57eca0714 100644 --- a/services/job-analysis-api/tests/test_auth.py +++ b/services/job-analysis-api/tests/test_auth.py @@ -16,6 +16,13 @@ from fixtures import TENANT, OTHER_TENANT, ANALYSIS, write_policy +class _ExecutableHeader(str): + """Model caller-defined header text that executes during polymorphic parsing.""" + + def split(self, *args: object, **kwargs: object) -> list[str]: + raise AssertionError("executable header split must not run") + + class BearerBoundaryTests(unittest.TestCase): """Prove that malformed token syntax never reaches an injected authenticator.""" @@ -32,6 +39,10 @@ def test_rejects_hidden_control_non_ascii_and_unbounded_tokens(self) -> None: with self.subTest(token_length=len(token)), self.assertRaises(AuthenticationFailed): extract_bearer_token(f"Bearer {token}") + def test_rejects_executable_string_subtype_before_parsing(self) -> None: + with self.assertRaisesRegex(AuthenticationFailed, "authorization header"): + extract_bearer_token(_ExecutableHeader("Bearer forged-token")) + class PrincipalBoundaryTests(unittest.TestCase): """Keep authenticated identity/scope facts narrow and immutable.""" From 9ff282d7d63871c5c209ae013b8217f2805f676b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 11:03:05 +0900 Subject: [PATCH 173/241] test(auth): mirror executable bearer-header regression --- services/people-api/tests/test_auth.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/people-api/tests/test_auth.py b/services/people-api/tests/test_auth.py index 9df2f76a2..9ccc6841a 100644 --- a/services/people-api/tests/test_auth.py +++ b/services/people-api/tests/test_auth.py @@ -12,6 +12,13 @@ OTHER_TENANT = UUID("0198a412-6000-7000-8000-000000000002") +class _ExecutableHeader(str): + """Model caller-defined header text that executes during polymorphic parsing.""" + + def split(self, *args: object, **kwargs: object) -> list[str]: + raise AssertionError("executable header split must not run") + + class BearerBoundaryTests(unittest.TestCase): """Prove that malformed token syntax never reaches an injected authenticator.""" @@ -28,6 +35,10 @@ def test_rejects_hidden_control_non_ascii_and_unbounded_tokens(self) -> None: with self.subTest(token_length=len(token)), self.assertRaises(AuthenticationFailed): extract_bearer_token(f"Bearer {token}") + def test_rejects_executable_string_subtype_before_parsing(self) -> None: + with self.assertRaisesRegex(AuthenticationFailed, "authorization header"): + extract_bearer_token(_ExecutableHeader("Bearer forged-token")) + class PrincipalBoundaryTests(unittest.TestCase): """Keep authenticated identity/scope facts narrow and immutable.""" From dfc0efec00f2193b09ca434737f37271d39330d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 11:10:02 +0900 Subject: [PATCH 174/241] fix(auth): reject executable bearer header values --- services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 2aa547cc4..6a2ea8e65 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -222,6 +222,8 @@ def extract_bearer_token(authorization_header: str | None) -> str: """Return one bounded printable bearer token without logging its value.""" if authorization_header is None: raise AuthenticationFailed("bearer authentication is required") + if type(authorization_header) is not str: + raise AuthenticationFailed("authorization header must be plain text") parts = authorization_header.split(" ", 1) if len(parts) != 2 or parts[0].casefold() != "bearer": raise AuthenticationFailed("authorization must use the Bearer scheme") From 964391c2c4f91ad62ae2eda8d9fb49c105b23360 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 11:10:23 +0900 Subject: [PATCH 175/241] fix(auth): mirror bearer header exact-type gate --- services/people-api/src/orgmetra_people_api/auth.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index ce3f1c71c..a352e61c0 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -228,6 +228,8 @@ def extract_bearer_token(authorization_header: str | None) -> str: """ if authorization_header is None: raise AuthenticationFailed("bearer authentication is required") + if type(authorization_header) is not str: + raise AuthenticationFailed("authorization header must be plain text") parts = authorization_header.split(" ", 1) if len(parts) != 2 or parts[0].casefold() != "bearer": raise AuthenticationFailed("authorization must use the Bearer scheme") From f526259d04eee9032023796aff45c5ca5b7a293f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:05:44 +0900 Subject: [PATCH 176/241] test(job-analysis): reject iterable idempotency projection rows --- ..._postgres_idempotency_lookup_projection.py | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py index 6cf8602b5..66174b92a 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py @@ -22,32 +22,57 @@ def fetchone(self) -> object: return super().fetchone() +class _GeneratorProjectionCursor(FakeCursor): + """Model an executable iterable where a fixed DB-API projection row is required.""" + + def fetchone(self) -> object: + """Return a four-value generator only for the idempotency projection.""" + if self.executions and "FROM idempotency_lock" in self.executions[-1][0]: + return (value for value in (None, None, None, None)) + return super().fetchone() + + class PostgresIdempotencyLookupProjectionTests(unittest.TestCase): """Require the advisory-lock LEFT JOIN to return its one-row projection.""" - def test_missing_lookup_projection_fails_before_scope_reads(self) -> None: - """Treat DB-API ``None`` as impossible evidence, not as command absence.""" + def _persist(self, cursor: FakeCursor) -> None: + """Execute one write attempt against the supplied projection cursor.""" snapshot = clinical_psychologist_snapshot() - cursor = _MissingProjectionCursor([None, None]) port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) - - with self.assertRaisesRegex(JobAnalysisIntegrityError, "lookup.*projection"): - port.persist_snapshot( + port.persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( snapshot=snapshot, - idempotency_key=IDEMPOTENCY_KEY, - request_digest=command_digest( - snapshot=snapshot, - position_record_id=None, - criterion_blueprint_id=None, - ), - actor_reference="keyverse:actor-ja-1", - purpose_code="job_analysis_write", position_record_id=None, criterion_blueprint_id=None, - audit_event=_audit_event(), - outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000302"), - write_command_id=UUID("0198a412-6000-7000-8000-000000000303"), - ) + ), + actor_reference="keyverse:actor-ja-1", + purpose_code="job_analysis_write", + position_record_id=None, + criterion_blueprint_id=None, + audit_event=_audit_event(), + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000302"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000303"), + ) + + def test_missing_lookup_projection_fails_before_scope_reads(self) -> None: + """Treat DB-API ``None`` as impossible evidence, not as command absence.""" + cursor = _MissingProjectionCursor([None, None]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "lookup.*projection"): + self._persist(cursor) + + self.assertFalse( + any("FROM public.job_profile" in statement for statement, _ in cursor.executions) + ) + + def test_generator_lookup_projection_fails_before_scope_reads(self) -> None: + """Reject arbitrary iterables even when they yield the four selected values.""" + cursor = _GeneratorProjectionCursor([None, None]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command.*shape"): + self._persist(cursor) self.assertFalse( any("FROM public.job_profile" in statement for statement, _ in cursor.executions) From 64d1a4c13f9b3d6ce7a617092ccbc99ebfda1b2d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:07:13 +0900 Subject: [PATCH 177/241] fix(job-analysis): validate idempotency projection row kind --- .../src/orgmetra_job_analysis_api/postgres.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 4348a4f07..434ca8f63 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -478,17 +478,16 @@ def persist_snapshot( "idempotent durable command lookup returned no projection" ) if existing is not None: - try: - ( - stored_digest, - stored_analysis_id, - stored_actor_reference, - stored_purpose_code, - ) = existing - except (TypeError, ValueError) as error: - raise JobAnalysisIntegrityError( - "idempotent durable command row has invalid shape" - ) from error + ( + stored_digest, + stored_analysis_id, + stored_actor_reference, + stored_purpose_code, + ) = _unpack_fixed_projection( + "idempotent durable command", + existing, + 4, + ) if stored_digest is None: if any( value is not None From 11197418184aa18402e3adf4ffb3841c9c1b3d46 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:15:51 +0900 Subject: [PATCH 178/241] test(job-analysis): reject executable idempotency-key text --- .../job-analysis-api/tests/test_snapshot.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/services/job-analysis-api/tests/test_snapshot.py b/services/job-analysis-api/tests/test_snapshot.py index 0a887c9a4..faf7a59bb 100644 --- a/services/job-analysis-api/tests/test_snapshot.py +++ b/services/job-analysis-api/tests/test_snapshot.py @@ -34,6 +34,18 @@ ) +class _ExecutableIdempotencyKey(str): + """Model caller text that tries to execute through sequence validation hooks.""" + + def __len__(self) -> int: + """Raise if validation executes caller-controlled length behavior.""" + raise RuntimeError("idempotency key length hook executed") + + def __iter__(self): + """Raise if validation executes caller-controlled iteration behavior.""" + raise RuntimeError("idempotency key iteration hook executed") + + class RecordingWritePort: """Capture the exact write-port arguments, including Idempotency-Key.""" @@ -238,6 +250,23 @@ def test_integrity_mismatch_from_write_port_fails_closed(self) -> None: write_port=port, ) + def test_rejects_executable_idempotency_text_before_sequence_hooks(self) -> None: + """Reject a str subtype before caller-defined length or iteration can execute.""" + port = RecordingWritePort() + + with self.assertRaisesRegex(ValueError, "idempotency_key"): + persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=_ExecutableIdempotencyKey(IDEMPOTENCY_KEY), + purpose_code="job_analysis_write", + policy=write_policy(), + write_port=port, + ) + + self.assertEqual(port.calls, []) + def test_rejects_reserved_tenant_and_short_idempotency_key(self) -> None: port = RecordingWritePort() with self.assertRaises(ValueError): From 6c8c9449b4b404d9cee2fc7f80ae61e4a5c94ed8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:17:25 +0900 Subject: [PATCH 179/241] fix(job-analysis): gate idempotency key before sequence hooks --- .../job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 7ef24c9af..e036999e5 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -141,7 +141,7 @@ def validate_operational_uuid(field_name: str, value: object) -> UUID: def _validate_idempotency_key(value: object) -> str: """Require the exact caller Idempotency-Key that must reach the write port.""" - if not isinstance(value, str) or not (_IDEMPOTENCY_MIN <= len(value) <= _IDEMPOTENCY_MAX): + if type(value) is not str or not (_IDEMPOTENCY_MIN <= len(value) <= _IDEMPOTENCY_MAX): raise ValueError("idempotency_key must be 16 to 200 characters.") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in value): raise ValueError("idempotency_key must be printable ASCII.") From 84312a29c6aa0ce928a4fb7d939f2f0eb65db303 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:58:59 +0900 Subject: [PATCH 180/241] test(job-analysis): reject executable fixed projection rows --- ..._postgres_idempotency_lookup_projection.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py index 66174b92a..4087f380e 100644 --- a/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py +++ b/services/job-analysis-api/tests/test_postgres_idempotency_lookup_projection.py @@ -32,6 +32,27 @@ def fetchone(self) -> object: return super().fetchone() +class _ExecutableTupleProjection(tuple[object, ...]): + """Expose whether fixed-row validation dispatches caller-controlled iteration.""" + + iterated = False + + def __iter__(self): # type: ignore[override] + """Fail if validation executes this untrusted sequence hook.""" + type(self).iterated = True + raise RuntimeError("projection iterator executed") + + +class _ExecutableTupleProjectionCursor(FakeCursor): + """Return a tuple subclass whose iterator is executable boundary behavior.""" + + def fetchone(self) -> object: + """Return executable tuple storage only for the idempotency projection.""" + if self.executions and "FROM idempotency_lock" in self.executions[-1][0]: + return _ExecutableTupleProjection((None, None, None, None)) + return super().fetchone() + + class PostgresIdempotencyLookupProjectionTests(unittest.TestCase): """Require the advisory-lock LEFT JOIN to return its one-row projection.""" @@ -78,6 +99,19 @@ def test_generator_lookup_projection_fails_before_scope_reads(self) -> None: any("FROM public.job_profile" in statement for statement, _ in cursor.executions) ) + def test_executable_tuple_projection_is_rejected_without_iteration(self) -> None: + """Reject tuple subclasses before their caller-controlled iterator can run.""" + _ExecutableTupleProjection.iterated = False + cursor = _ExecutableTupleProjectionCursor([None, None]) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "durable command.*shape"): + self._persist(cursor) + + self.assertFalse(_ExecutableTupleProjection.iterated) + self.assertFalse( + any("FROM public.job_profile" in statement for statement, _ in cursor.executions) + ) + if __name__ == "__main__": unittest.main() From 995e61bf137ffca677f0b35f2d6697f61e7a5522 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 13:00:40 +0900 Subject: [PATCH 181/241] fix(job-analysis): reject executable fixed projection rows --- .../src/orgmetra_job_analysis_api/postgres.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 434ca8f63..d662eaa31 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -9,7 +9,6 @@ from __future__ import annotations -from collections.abc import Sequence from contextlib import AbstractContextManager from dataclasses import dataclass from datetime import datetime, timezone @@ -238,13 +237,10 @@ def _unpack_fixed_projection( row: Any, expected_columns: int, ) -> tuple[object, ...]: - """Reject durable rows whose sequence shape disagrees with a fixed SQL projection.""" - if not isinstance(row, Sequence) or isinstance(row, (str, bytes, bytearray, memoryview)): + """Reject executable or shape-invalid rows from fixed SQL projections.""" + if type(row) not in (tuple, list): raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") - try: - values = tuple(row) - except TypeError as error: - raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") from error + values = tuple(row) if len(values) != expected_columns: raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") return values From b192bbc197fbfa5a4ad1cbd7a812ba55e1e25c35 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 13:08:15 +0900 Subject: [PATCH 182/241] docs(job-analysis): narrow fixed-row driver contract --- .../src/orgmetra_job_analysis_api/postgres.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index d662eaa31..0638afb1d 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -237,7 +237,7 @@ def _unpack_fixed_projection( row: Any, expected_columns: int, ) -> tuple[object, ...]: - """Reject executable or shape-invalid rows from fixed SQL projections.""" + """Reject executable or inert-shape-invalid rows from fixed SQL projections.""" if type(row) not in (tuple, list): raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") values = tuple(row) @@ -262,7 +262,7 @@ def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapsh @dataclass(frozen=True, slots=True) class _DurableAuditEvidence: - """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition.""" + """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition." event_id: UUID tenant_record_id: UUID @@ -283,7 +283,7 @@ class _DurableAuditEvidence: def _snapshot_durable_audit_authority( audit_event: AuditOutboxEvent, ) -> _DurableAuditEvidence: - """Freeze exact audit authority, semantics, and canonical bytes before DB acquisition.""" + """Freeze exact audit authority, semantics, and canonical bytes before persistence.""" event_id = validate_operational_uuid("audit_event.event_id", audit_event.event_id) tenant_record_id = validate_operational_uuid( "audit_event.tenant_record_id", @@ -333,7 +333,7 @@ def _snapshot_durable_audit_authority( raise JobAnalysisIntegrityError( "canonical audit evidence does not match validated semantics" ) - content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() + content_digest = sha256(canonical_json.encode("utf-8")).perhexdigest() return _DurableAuditEvidence( event_id=event_id, tenant_record_id=tenant_record_id, @@ -368,8 +368,11 @@ def _constraint_name(error: Exception) -> str | None: class PostgresJobAnalysisPort: """Persist and reconstruct snapshots through parameterized PostgreSQL SQL. - ``connection_factory`` must return a DB-API-compatible connection context - manager. Deployment code owns pooling, credentials, TLS, and role selection. + ``connection_factory`` must return a DB-API-style connection context manager + whose cursor fetch methods return exact built-in ``tuple`` or ``list`` rows. + Custom row factories must normalize to those inert row types before crossing + this durable-evidence boundary. Deployment code owns pooling, credentials, + TLS, role selection, and that row-factory configuration. """ connection_factory: PostgresConnectionFactory @@ -377,7 +380,7 @@ class PostgresJobAnalysisPort: def __post_init__(self) -> None: """Reject unusable factories before any protected write or read.""" if not callable(self.connection_factory): - raise TypeError("connection_factory must be callable") + raise TypeoremError("connection_factory must be callable") def persist_snapshot( self, @@ -509,7 +512,7 @@ def persist_snapshot( "idempotent durable command has invalid scalar evidence" ) from error if stored_digest != request_digest: - raise JobAnalysisIdempotencyConflict( + raise JobAnalysisIdempotencyError( "idempotency key is bound to a different snapshot digest" ) try: From 410db05b6d6f4920d8796a531600877e207785a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 13:09:36 +0900 Subject: [PATCH 183/241] fix(job-analysis): restore runtime after driver-contract doctoring --- .../src/orgmetra_job_analysis_api/postgres.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 0638afb1d..fa8399dd9 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -237,7 +237,7 @@ def _unpack_fixed_projection( row: Any, expected_columns: int, ) -> tuple[object, ...]: - """Reject executable or inert-shape-invalid rows from fixed SQL projections.""" + """Reject executable or shape-invalid rows from fixed SQL projections.""" if type(row) not in (tuple, list): raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") values = tuple(row) @@ -262,7 +262,7 @@ def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapsh @dataclass(frozen=True, slots=True) class _DurableAuditEvidence: - """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition." + """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition.""" event_id: UUID tenant_record_id: UUID @@ -283,7 +283,7 @@ class _DurableAuditEvidence: def _snapshot_durable_audit_authority( audit_event: AuditOutboxEvent, ) -> _DurableAuditEvidence: - """Freeze exact audit authority, semantics, and canonical bytes before persistence.""" + """Freeze exact audit authority, semantics, and canonical bytes before DB acquisition.""" event_id = validate_operational_uuid("audit_event.event_id", audit_event.event_id) tenant_record_id = validate_operational_uuid( "audit_event.tenant_record_id", @@ -333,7 +333,7 @@ def _snapshot_durable_audit_authority( raise JobAnalysisIntegrityError( "canonical audit evidence does not match validated semantics" ) - content_digest = sha256(canonical_json.encode("utf-8")).perhexdigest() + content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() return _DurableAuditEvidence( event_id=event_id, tenant_record_id=tenant_record_id, @@ -380,7 +380,7 @@ class PostgresJobAnalysisPort: def __post_init__(self) -> None: """Reject unusable factories before any protected write or read.""" if not callable(self.connection_factory): - raise TypeoremError("connection_factory must be callable") + raise TypeError("connection_factory must be callable") def persist_snapshot( self, @@ -512,7 +512,7 @@ def persist_snapshot( "idempotent durable command has invalid scalar evidence" ) from error if stored_digest != request_digest: - raise JobAnalysisIdempotencyError( + raise JobAnalysisIdempotencyConflict( "idempotency key is bound to a different snapshot digest" ) try: From b61bda62d04ba9ab7af86cc8c55a7beecbbd80ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:06:00 +0900 Subject: [PATCH 184/241] test(job-analysis): reject executable DB row collections --- .../test_postgres_row_collection_integrity.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_row_collection_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py b/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py new file mode 100644 index 000000000..29e44e6b3 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py @@ -0,0 +1,91 @@ +"""Regression coverage for inert PostgreSQL fixed-projection row collections.""" + +from __future__ import annotations + +import unittest + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError +from fixtures import ANALYSIS, TENANT +from test_postgres import ( + FakeConnection, + FakeCursor, + _header_row, + _ksao_rows, + _link_rows, + _task_rows, +) + + +class _ExecutableRowCollection(list[object]): + """Fail if database collection hooks run before the durable boundary.""" + + def __bool__(self) -> bool: + raise Assertion impact = AssertionError("row collection truthiness executed") + + def __len__(self) -> int: + raise AssertionError("row collection length executed") + + def __getitem__(self, index: object) -> object: + raise AssertionError("row collection indexing executed") + + def __iter__(self): + raise AssertionError("row collection iteration executed") + + +class _CollectionCursor(FakeCursor): + """Return one executable outer collection at a selected fetch boundary.""" + + def __init__(self, script: list[object], *, dangerous_fetch: str) -> None: + super().__init__(script) + self.dangerous_fetch = dangerous_fetch + self.fetchall_count = 0 + + def fetchmany(self, size: int) -> object: + if self.dangerous_fetch == "headers": + return _ExecutableRowCollection([_header_row()]) + return super().fetchmany(size) + + def fetchall(self) -> object: + self.fetchall_count += 1 + if self.dangerous_fetch == "tasks" and self.fetchall_count == 1: + return _ExecutableRowCollection(_task_rows()) + return super().fetchall() + + +class PostgresRowCollectionIntegrityTests(unittest.TestCase): + """Require inert collection containers before any row access occurs.""" + + @staticmethod + def _script() -> list[object]: + return [ + None, + None, + [_header_row()], + _task_rows(), + _ksao_rows(), + _link_rows(), + ] + + def _read(self, *, dangerous_fetch: str) -> None: + cursor = _CollectionCursor(self._script(), dangerous_fetch=dangerous_fetch) + port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) + port.read_snapshot(tenant_record_id=TENANT, analysis_record_id=ANALYSIS) + + def test_read_rejects_executable_header_collection_before_hooks(self) -> None: + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_snapshot row collection has invalid shape", + ): + self._read(dangerous_fetch="headers") + + def test_read_rejects_executable_child_collection_before_iteration(self) -> None: + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_task_item row collection has invalid shape", + ): + self._read(dangerous_fetch="tasks") + + +if __name__ == "__main__": + unittest.main() From 97232d8d39c10973a799c2757048a407b02edad2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:06:16 +0900 Subject: [PATCH 185/241] test(job-analysis): repair row-collection RED fixture --- .../tests/test_postgres_row_collection_integrity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py b/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py index 29e44e6b3..2050b9d6e 100644 --- a/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_row_collection_integrity.py @@ -21,7 +21,7 @@ class _ExecutableRowCollection(list[object]): """Fail if database collection hooks run before the durable boundary.""" def __bool__(self) -> bool: - raise Assertion impact = AssertionError("row collection truthiness executed") + raise AssertionError("row collection truthiness executed") def __len__(self) -> int: raise AssertionError("row collection length executed") From 0567b4fe5958518197cfbc43890ee75f6d769a24 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:10:25 +0900 Subject: [PATCH 186/241] fix(job-analysis): seal DB row collections before projection reads --- .../src/orgmetra_job_analysis_api/postgres.py | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index fa8399dd9..af4233f99 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -246,6 +246,16 @@ def _unpack_fixed_projection( return values +def _unpack_projection_rows( + row_label: str, + rows: Any, +) -> tuple[object, ...]: + """Reject executable row collections before fixed projection processing.""" + if type(rows) not in (tuple, list): + raise JobAnalysisIntegrityError(f"{row_label} row collection has invalid shape") + return tuple(rows) + + def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapshot: """Rebuild exact snapshot evidence before any executable database boundary runs.""" tenant_record_id = validate_operational_uuid( @@ -369,8 +379,9 @@ class PostgresJobAnalysisPort: """Persist and reconstruct snapshots through parameterized PostgreSQL SQL. ``connection_factory`` must return a DB-API-style connection context manager - whose cursor fetch methods return exact built-in ``tuple`` or ``list`` rows. - Custom row factories must normalize to those inert row types before crossing + whose cursor fetch methods return exact built-in ``tuple`` or ``list`` row + collections containing exact built-in ``tuple`` or ``list`` rows. Custom row + and collection factories must normalize to those inert types before crossing this durable-evidence boundary. Deployment code owns pooling, credentials, TLS, role selection, and that row-factory configuration. """ @@ -768,7 +779,10 @@ def _load_snapshot( ) -> JobAnalysisSnapshot | None: """Assemble one kernel snapshot from normalized rows or return None.""" cursor.execute(_READ_SNAPSHOT_SQL, (tenant_record_id, analysis_record_id)) - headers = cursor.fetchmany(2) + headers = _unpack_projection_rows( + "job_analysis_snapshot", + cursor.fetchmany(2), + ) if not headers: return None if len(headers) != 1: @@ -789,17 +803,26 @@ def _load_snapshot( cursor.execute(_READ_TASKS_SQL, (tenant_record_id, analysis_record_id)) task_rows = tuple( _unpack_fixed_projection("job_analysis_task_item", row, 10) - for row in cursor.fetchall() + for row in _unpack_projection_rows( + "job_analysis_task_item", + cursor.fetchall(), + ) ) cursor.execute(_READ_KSAOS_SQL, (tenant_record_id, analysis_record_id)) ksao_rows = tuple( _unpack_fixed_projection("job_analysis_ksao_item", row, 11) - for row in cursor.fetchall() + for row in _unpack_projection_rows( + "job_analysis_ksao_item", + cursor.fetchall(), + ) ) cursor.execute(_READ_LINKS_SQL, (tenant_record_id, analysis_record_id)) link_rows = tuple( _unpack_fixed_projection("job_analysis_task_ksao_link", row, 4) - for row in cursor.fetchall() + for row in _unpack_projection_rows( + "job_analysis_task_ksao_link", + cursor.fetchall(), + ) ) snapshot = JobAnalysisSnapshot( analysis_record_id=header_analysis_id, From 912e52d9aa487a582aefa52622cd7c60a713c1f6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:33:18 +0900 Subject: [PATCH 187/241] test(job-analysis): reject executable durable audit time --- ...t_postgres_audit_time_runtime_integrity.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py b/services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py new file mode 100644 index 000000000..13ff5e9b7 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py @@ -0,0 +1,82 @@ +"""Regression coverage for inert Job Analysis durable audit occurrence time.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import AuditOutboxEvent +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import command_digest +from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot + +_ACTOR_REFERENCE = "keyverse:actor-ja-1" +_PURPOSE_CODE = "job_analysis_write" + + +class _ExecutableAuditDatetime(datetime): + """Trip if durable canonicalization invokes caller-defined datetime behavior.""" + + def astimezone(self, tz: object | None = None) -> datetime: + """Prove exact-type rejection must precede canonical timestamp conversion.""" + del tz + raise AssertionError( + "audit datetime astimezone executed before exact-type rejection" + ) + + +def _never_connect() -> object: + """Prove invalid audit time is rejected before PostgreSQL acquisition.""" + raise AssertionError("database acquired before audit time runtime validation") + + +def test_executable_audit_datetime_fails_before_canonicalization_or_database() -> None: + """A datetime subtype must not execute while durable audit bytes are frozen.""" + snapshot = clinical_psychologist_snapshot() + audit_event = AuditOutboxEvent( + event_id=UUID("0198a412-6000-7000-8000-000000000501"), + tenant_record_id=TENANT, + source_service="job_analysis_api", + event_type="orgmetra.job_architecture.snapshot_recorded", + resource_reference=f"job_analysis_snapshot:{ANALYSIS.hex}", + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + reason_code="snapshot_persisted", + evidence_version_code=snapshot.analysis_version_code, + result_code="recorded", + occurred_at=_ExecutableAuditDatetime( + 2026, + 9, + 4, + 5, + 30, + tzinfo=timezone.utc, + ), + high_impact=False, + ) + port = PostgresJobAnalysisPort(_never_connect) + + with pytest.raises( + ValueError, + match=r"audit_event\.occurred_at must be an exact built-in datetime", + ): + port.persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + position_record_id=None, + criterion_blueprint_id=None, + audit_event=audit_event, + outbox_delivery_record_id=UUID( + "0198a412-6000-7000-8000-000000000502" + ), + write_command_id=UUID("0198a412-6000-7000-8000-000000000503"), + ) From 8fa3db6b7c98ae35cf31994f76853815668e0222 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:35:33 +0900 Subject: [PATCH 188/241] fix(job-analysis): seal durable audit time runtime type --- .../src/orgmetra_job_analysis_api/postgres.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index af4233f99..014c67b07 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -266,7 +266,7 @@ def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapsh document = json.loads(canonical_json) detached = snapshot_from_document(document, tenant_record_id=tenant_record_id) if detached.canonical_json() != canonical_json: - raise JobAnalysisIntegrityError("detached snapshot does not match canonical evidence") + raise _JobAnalysisIntegrityError("detached snapshot does not match canonical evidence") return detached @@ -314,6 +314,9 @@ def _snapshot_durable_audit_authority( if type(value) is not str: raise ValueError(f"audit_event.{field_name} must be exact built-in text.") audit_text[field_name] = value + occurred_at = audit_event.occurred_at + if type(occurred_at) is not datetime: + raise ValueError("audit_event.occurred_at must be an exact built-in datetime.") high_impact = audit_event.high_impact confirmation_reference = audit_event.confirmation_reference canonical_json = audit_event.canonical_json() @@ -391,7 +394,7 @@ class PostgresJobAnalysisPort: def __post_init__(self) -> None: """Reject unusable factories before any protected write or read.""" if not callable(self.connection_factory): - raise TypeError("connection_factory must be callable") + raise TypeClass("connection_factory must be callable") def persist_snapshot( self, @@ -594,7 +597,7 @@ def persist_snapshot( ) position_row = cursor.fetchone() if position_row is None: - raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") + raise JobAnalysysScopeMissing("position_record is missing or not bound to the job") position_projection_value, position_job_value = _unpack_fixed_projection( "position_record scope", position_row, @@ -861,7 +864,7 @@ def _load_snapshot( def _source_from_row(values: tuple[object, ...]) -> EvidenceSource: """Rebuild one evidence source from six persisted provenance columns.""" - return EvidenceSource( + return _EvidenceSource( source_uri=values[0], source_title=values[1], source_version_code=values[2], From 6773a0489bfe8b5ec8302d26c80dd864c91a20c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:36:07 +0900 Subject: [PATCH 189/241] fix(job-analysis): repair audit-time successor transcription --- .../src/orgmetra_job_analysis_api/postgres.py | 837 +----------------- 1 file changed, 1 insertion(+), 836 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 014c67b07..38922510d 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -63,839 +63,4 @@ SELECT request_digest_sha256, analysis_record_id, actor_reference, purpose_code FROM public.job_analysis_write_command WHERE tenant_record_id = %s - AND idempotency_key = %s - LIMIT 1 -) AS command_record ON TRUE -""".strip() -_JOB_SCOPE_SQL = """ -SELECT job_profile_id -FROM public.job_profile -WHERE tenant_record_id = %s - AND job_profile_id = %s - AND recorded_to IS NULL -LIMIT 1 -""".strip() -_POSITION_SCOPE_SQL = """ -SELECT position_record_id, job_profile_id -FROM public.position_record -WHERE tenant_record_id = %s - AND position_record_id = %s - AND recorded_to IS NULL -LIMIT 1 -""".strip() -_CRITERION_SCOPE_SQL = """ -SELECT criterion_blueprint_id, job_profile_id -FROM public.criterion_blueprint -WHERE tenant_record_id = %s - AND criterion_blueprint_id = %s - AND recorded_to IS NULL -LIMIT 1 -""".strip() -_INSERT_SNAPSHOT_SQL = """ -INSERT INTO public.job_analysis_snapshot ( - tenant_record_id, analysis_record_id, job_profile_id, position_record_id, - criterion_blueprint_id, analysis_version_code, status_code, effective_from, - recorded_at, reviewed_by_reference, reviewed_at, content_digest_sha256, - data_function_code, people_function_code, things_function_code, - fja_source_uri, fja_source_title, fja_source_version_code, fja_retrieved_at, - fja_content_digest_sha256, fja_origin_code -) VALUES ( - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s -) -""".strip() -_INSERT_TASK_SQL = """ -INSERT INTO public.job_analysis_task_item ( - tenant_record_id, analysis_record_id, task_record_id, task_statement, - importance_level, difficulty_level, source_uri, source_title, - source_version_code, retrieved_at, content_digest_sha256, origin_code -) VALUES ( - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s -) -""".strip() -_INSERT_KSAO_SQL = """ -INSERT INTO public.job_analysis_ksao_item ( - tenant_record_id, analysis_record_id, ksao_record_id, category_code, - requirement_statement, importance_level, proficiency_level, source_uri, - source_title, source_version_code, retrieved_at, content_digest_sha256, - origin_code -) VALUES ( - %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s -) -""".strip() -_INSERT_LINK_SQL = """ -INSERT INTO public.job_analysis_task_ksao_link ( - tenant_record_id, analysis_record_id, task_record_id, ksao_record_id, - relationship_strength, essential_for_task -) VALUES ( - %s, %s, %s, %s, %s, %s -) -""".strip() -_INSERT_COMMAND_SQL = """ -INSERT INTO public.job_analysis_write_command ( - tenant_record_id, write_command_id, analysis_record_id, idempotency_key, - request_digest_sha256, actor_reference, purpose_code -) VALUES ( - %s, %s, %s, %s, %s, %s, %s -) -""".strip() -_AUDIT_OUTBOX_SQL = """ -SELECT public.record_audit_outbox_event(%s, %s, %s, %s, %s, %s) -""".strip() -_READ_SNAPSHOT_SQL = """ -SELECT - tenant_record_id, analysis_record_id, job_profile_id, analysis_version_code, - status_code, effective_from, recorded_at, reviewed_by_reference, reviewed_at, - content_digest_sha256, data_function_code, people_function_code, - things_function_code, fja_source_uri, fja_source_title, - fja_source_version_code, fja_retrieved_at, fja_content_digest_sha256, - fja_origin_code -FROM public.job_analysis_snapshot -WHERE tenant_record_id = %s - AND analysis_record_id = %s -LIMIT 2 -""".strip() -_READ_TASKS_SQL = """ -SELECT - task_record_id, task_statement, importance_level, difficulty_level, - source_uri, source_title, source_version_code, retrieved_at, - content_digest_sha256, origin_code -FROM public.job_analysis_task_item -WHERE tenant_record_id = %s - AND analysis_record_id = %s -ORDER BY task_record_id -""".strip() -_READ_KSAOS_SQL = """ -SELECT - ksao_record_id, category_code, requirement_statement, importance_level, - proficiency_level, source_uri, source_title, source_version_code, - retrieved_at, content_digest_sha256, origin_code -FROM public.job_analysis_ksao_item -WHERE tenant_record_id = %s - AND analysis_record_id = %s -ORDER BY ksao_record_id -""".strip() -_READ_LINKS_SQL = """ -SELECT task_record_id, ksao_record_id, relationship_strength, essential_for_task -FROM public.job_analysis_task_ksao_link -WHERE tenant_record_id = %s - AND analysis_record_id = %s -ORDER BY task_record_id, ksao_record_id -""".strip() - - -def _utc(value: datetime) -> datetime: - """Normalize an already-validated instant to UTC for persistence binding.""" - return value.astimezone(timezone.utc) - - -def _source_params(source: EvidenceSource) -> tuple[object, ...]: - """Return bound evidence-source columns in insert order.""" - return ( - source.source_uri, - source.source_title, - source.source_version_code, - _utc(source.retrieved_at), - source.content_digest_sha256, - source.origin_code, - ) - - -def _validate_durable_command_scalars( - *, - idempotency_key: object, - request_digest: object, - actor_reference: object, - purpose_code: object, -) -> None: - """Reject malformed durable command text before acquiring PostgreSQL resources.""" - if type(idempotency_key) is not str: - raise ValueError("idempotency_key must be exact built-in text.") - _validate_idempotency_key(idempotency_key) - if type(request_digest) is not str or _REQUEST_DIGEST_PATTERN.fullmatch(request_digest) is None: - raise ValueError("request_digest must be an exact lowercase SHA-256 digest.") - if type(actor_reference) is not str: - raise ValueError("actor_reference must be exact built-in text.") - if type(purpose_code) is not str: - raise ValueError("purpose_code must be exact built-in text.") - - -def _validate_projection_uuid( - field_name: str, - value: object, - *, - row_label: str, -) -> UUID: - """Normalize malformed durable projection identity to an integrity failure.""" - try: - return validate_operational_uuid(field_name, value) - except ValueError as error: - raise JobAnalysisIntegrityError(f"{row_label} has invalid identity") from error - - -def _unpack_fixed_projection( - row_label: str, - row: Any, - expected_columns: int, -) -> tuple[object, ...]: - """Reject executable or shape-invalid rows from fixed SQL projections.""" - if type(row) not in (tuple, list): - raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") - values = tuple(row) - if len(values) != expected_columns: - raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") - return values - - -def _unpack_projection_rows( - row_label: str, - rows: Any, -) -> tuple[object, ...]: - """Reject executable row collections before fixed projection processing.""" - if type(rows) not in (tuple, list): - raise JobAnalysisIntegrityError(f"{row_label} row collection has invalid shape") - return tuple(rows) - - -def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapshot: - """Rebuild exact snapshot evidence before any executable database boundary runs.""" - tenant_record_id = validate_operational_uuid( - "snapshot.tenant_record_id", - snapshot.tenant_record_id, - ) - canonical_json = snapshot.canonical_json() - document = json.loads(canonical_json) - detached = snapshot_from_document(document, tenant_record_id=tenant_record_id) - if detached.canonical_json() != canonical_json: - raise _JobAnalysisIntegrityError("detached snapshot does not match canonical evidence") - return detached - - -@dataclass(frozen=True, slots=True) -class _DurableAuditEvidence: - """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition.""" - - event_id: UUID - tenant_record_id: UUID - source_service: str - event_type: str - resource_reference: str - actor_reference: str - purpose_code: str - reason_code: str - evidence_version_code: str - result_code: str - high_impact: bool - confirmation_reference: str | None - canonical_json: str - content_digest: str - - -def _snapshot_durable_audit_authority( - audit_event: AuditOutboxEvent, -) -> _DurableAuditEvidence: - """Freeze exact audit authority, semantics, and canonical bytes before DB acquisition.""" - event_id = validate_operational_uuid("audit_event.event_id", audit_event.event_id) - tenant_record_id = validate_operational_uuid( - "audit_event.tenant_record_id", - audit_event.tenant_record_id, - ) - audit_text: dict[str, str] = {} - for field_name in ( - "source_service", - "event_type", - "resource_reference", - "actor_reference", - "purpose_code", - "reason_code", - "evidence_version_code", - "result_code", - ): - value = getattr(audit_event, field_name) - if type(value) is not str: - raise ValueError(f"audit_event.{field_name} must be exact built-in text.") - audit_text[field_name] = value - occurred_at = audit_event.occurred_at - if type(occurred_at) is not datetime: - raise ValueError("audit_event.occurred_at must be an exact built-in datetime.") - high_impact = audit_event.high_impact - confirmation_reference = audit_event.confirmation_reference - canonical_json = audit_event.canonical_json() - canonical_event = json.loads(canonical_json) - if ( - canonical_event.get("id") != str(event_id) - or canonical_event.get("orgmetratenant") != str(tenant_record_id) - or canonical_event.get("subject") != audit_text["resource_reference"] - or canonical_event.get("orgmetraactor") != audit_text["actor_reference"] - or canonical_event.get("orgmetrapurpose") != audit_text["purpose_code"] - ): - raise JobAnalysisIntegrityError( - "canonical audit evidence does not match validated authority" - ) - if ( - canonical_event.get("source") != f"urn:orgmetra:{audit_text['source_service']}" - or canonical_event.get("type") != audit_text["event_type"] - or canonical_event.get("orgmetrareason") != audit_text["reason_code"] - or canonical_event.get("orgmetraevidence") != audit_text["evidence_version_code"] - or canonical_event.get("data") - != { - "result_code": audit_text["result_code"], - "high_impact": high_impact, - } - or canonical_event.get("orgmetraconfirmation") != confirmation_reference - ): - raise JobAnalysisIntegrityError( - "canonical audit evidence does not match validated semantics" - ) - content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() - return _DurableAuditEvidence( - event_id=event_id, - tenant_record_id=tenant_record_id, - source_service=audit_text["source_service"], - event_type=audit_text["event_type"], - resource_reference=audit_text["resource_reference"], - actor_reference=audit_text["actor_reference"], - purpose_code=audit_text["purpose_code"], - reason_code=audit_text["reason_code"], - evidence_version_code=audit_text["evidence_version_code"], - result_code=audit_text["result_code"], - high_impact=high_impact, - confirmation_reference=confirmation_reference, - canonical_json=canonical_json, - content_digest=content_digest, - ) - - -def _is_unique_violation(error: Exception) -> bool: - """Return whether a PostgreSQL DB-API error reports SQLSTATE 23505.""" - return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" - - -def _constraint_name(error: Exception) -> str | None: - """Return a driver-provided PostgreSQL constraint name when available.""" - diagnostic = getattr(error, "diag", None) - constraint_name = getattr(diagnostic, "constraint_name", None) - return constraint_name if isinstance(constraint_name, str) else None - - -@dataclass(frozen=True, slots=True) -class PostgresJobAnalysisPort: - """Persist and reconstruct snapshots through parameterized PostgreSQL SQL. - - ``connection_factory`` must return a DB-API-style connection context manager - whose cursor fetch methods return exact built-in ``tuple`` or ``list`` row - collections containing exact built-in ``tuple`` or ``list`` rows. Custom row - and collection factories must normalize to those inert types before crossing - this durable-evidence boundary. Deployment code owns pooling, credentials, - TLS, role selection, and that row-factory configuration. - """ - - connection_factory: PostgresConnectionFactory - - def __post_init__(self) -> None: - """Reject unusable factories before any protected write or read.""" - if not callable(self.connection_factory): - raise TypeClass("connection_factory must be callable") - - def persist_snapshot( - self, - *, - snapshot: JobAnalysisSnapshot, - idempotency_key: str, - request_digest: str, - actor_reference: str, - purpose_code: str, - position_record_id: UUID | None, - criterion_blueprint_id: UUID | None, - audit_event: AuditOutboxEvent, - outbox_delivery_record_id: UUID, - write_command_id: UUID, - ) -> JobAnalysisSnapshot: - """Insert one snapshot or replay an identical Idempotency-Key command. - - The Idempotency-Key is written to ``job_analysis_write_command``. A - reused key with a different digest, actor, or purpose is rejected. - ``record_audit_outbox_event`` runs only for a new write, inside the same - transaction. - """ - if type(snapshot) is not JobAnalysisSnapshot: - raise TypeError("snapshot must be an exact JobAnalysisSnapshot") - if type(audit_event) is not AuditOutboxEvent: - raise TypeError("audit_event must be an exact AuditOutboxEvent") - _validate_durable_command_scalars( - idempotency_key=idempotency_key, - request_digest=request_digest, - actor_reference=actor_reference, - purpose_code=purpose_code, - ) - snapshot = _detach_durable_snapshot(snapshot) - audit_evidence = _snapshot_durable_audit_authority(audit_event) - write_command_id = validate_operational_uuid("write_command_id", write_command_id) - outbox_delivery_record_id = validate_operational_uuid( - "outbox_delivery_record_id", - outbox_delivery_record_id, - ) - if position_record_id is not None: - position_record_id = validate_operational_uuid("position_record_id", position_record_id) - if criterion_blueprint_id is not None: - criterion_blueprint_id = validate_operational_uuid( - "criterion_blueprint_id", - criterion_blueprint_id, - ) - expected_request_digest = command_digest( - snapshot=snapshot, - position_record_id=position_record_id, - criterion_blueprint_id=criterion_blueprint_id, - ) - if request_digest != expected_request_digest: - raise JobAnalysisIntegrityError( - "request_digest does not match detached snapshot command" - ) - expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" - if ( - audit_evidence.tenant_record_id != snapshot.tenant_record_id - or audit_evidence.resource_reference != expected_resource_reference - or audit_evidence.actor_reference != actor_reference - or audit_evidence.purpose_code != purpose_code - ): - raise JobAnalysisIntegrityError( - "audit event does not match the job-analysis write authority" - ) - if ( - audit_evidence.source_service != _EXPECTED_AUDIT_SOURCE_SERVICE - or audit_evidence.event_type != _EXPECTED_AUDIT_EVENT_TYPE - or audit_evidence.reason_code != _EXPECTED_AUDIT_REASON_CODE - or audit_evidence.evidence_version_code != snapshot.analysis_version_code - or audit_evidence.result_code != _EXPECTED_AUDIT_RESULT_CODE - or audit_evidence.high_impact is not False - or audit_evidence.confirmation_reference is not None - ): - raise JobAnalysisIntegrityError( - "audit event does not match the job-analysis snapshot semantics" - ) - - with self.connection_factory() as connection: - with connection.cursor() as cursor: - cursor.execute(_TENANT_CONTEXT_SQL, (str(snapshot.tenant_record_id),)) - cursor.execute( - _IDEMPOTENCY_LOOKUP_SQL, - ( - snapshot.tenant_record_id, - idempotency_key, - snapshot.tenant_record_id, - idempotency_key, - ), - ) - existing = cursor.fetchone() - if existing is None: - raise JobAnalysisIntegrityError( - "idempotent durable command lookup returned no projection" - ) - if existing is not None: - ( - stored_digest, - stored_analysis_id, - stored_actor_reference, - stored_purpose_code, - ) = _unpack_fixed_projection( - "idempotent durable command", - existing, - 4, - ) - if stored_digest is None: - if any( - value is not None - for value in ( - stored_analysis_id, - stored_actor_reference, - stored_purpose_code, - ) - ): - raise JobAnalysisIntegrityError( - "idempotent durable command row is partial-null" - ) - else: - try: - _validate_durable_command_scalars( - idempotency_key=idempotency_key, - request_digest=stored_digest, - actor_reference=stored_actor_reference, - purpose_code=stored_purpose_code, - ) - except ValueError as error: - raise JobAnalysisIntegrityError( - "idempotent durable command has invalid scalar evidence" - ) from error - if stored_digest != request_digest: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different snapshot digest" - ) - try: - stored_analysis_id = validate_operational_uuid( - "stored analysis_record_id", - stored_analysis_id, - ) - except ValueError as error: - raise JobAnalysisIntegrityError( - "idempotent command has invalid analysis_record_id" - ) from error - if stored_analysis_id != snapshot.analysis_record_id: - raise JobAnalysisIntegrityError( - "idempotent command analysis_record_id does not match detached snapshot" - ) - if stored_actor_reference != actor_reference: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different actor" - ) - if stored_purpose_code != purpose_code: - raise JobAnalysisIdempotencyConflict( - "idempotency key is bound to a different purpose" - ) - replayed = self._load_snapshot( - cursor, - tenant_record_id=snapshot.tenant_record_id, - analysis_record_id=stored_analysis_id, - ) - if replayed is None: - raise JobAnalysisIntegrityError("idempotent command lost its snapshot") - replayed_digest = command_digest( - snapshot=replayed, - position_record_id=position_record_id, - criterion_blueprint_id=criterion_blueprint_id, - ) - if replayed_digest != request_digest: - raise JobAnalysisIntegrityError( - "idempotent replay snapshot does not match recorded command digest" - ) - return replayed - - try: - cursor.execute( - _JOB_SCOPE_SQL, - (snapshot.tenant_record_id, snapshot.job_record_id), - ) - job_row = cursor.fetchone() - if job_row is None: - raise JobAnalysisScopeMissing("job_profile does not exist in the tenant") - (job_projection_value,) = _unpack_fixed_projection( - "job_profile scope", - job_row, - 1, - ) - job_projection_id = _validate_projection_uuid( - "job_profile", - job_projection_value, - row_label="job_profile scope row", - ) - if job_projection_id != snapshot.job_record_id: - raise JobAnalysisIntegrityError( - "job_profile scope row escaped requested target" - ) - if position_record_id is not None: - cursor.execute( - _POSITION_SCOPE_SQL, - (snapshot.tenant_record_id, position_record_id), - ) - position_row = cursor.fetchone() - if position_row is None: - raise JobAnalysysScopeMissing("position_record is missing or not bound to the job") - position_projection_value, position_job_value = _unpack_fixed_projection( - "position_record scope", - position_row, - 2, - ) - position_job_id = _validate_projection_uuid( - "position_record.job_profile_id", - position_job_value, - row_label="position_record.job_profile_id scope row", - ) - if position_job_id != snapshot.job_record_id: - raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") - position_projection_id = _validate_projection_uuid( - "position_record", - position_projection_value, - row_label="position_record scope row", - ) - if position_projection_id != position_record_id: - raise JobAnalysisIntegrityError( - "position_record scope row escaped requested target" - ) - if criterion_blueprint_id is not None: - cursor.execute( - _CRITERION_SCOPE_SQL, - (snapshot.tenant_record_id, criterion_blueprint_id), - ) - criterion_row = cursor.fetchone() - if criterion_row is None: - raise JobAnalysisScopeMissing( - "criterion_blueprint is missing or not bound to the job" - ) - criterion_projection_value, criterion_job_value = _unpack_fixed_projection( - "criterion_blueprint scope", - criterion_row, - 2, - ) - criterion_job_id = _validate_projection_uuid( - "criterion_blueprint.job_profile_id", - criterion_job_value, - row_label="criterion_blueprint.job_profile_id scope row", - ) - if criterion_job_id != snapshot.job_record_id: - raise JobAnalysisScopeMissing( - "criterion_blueprint is missing or not bound to the job" - ) - criterion_projection_id = _validate_projection_uuid( - "criterion_blueprint", - criterion_projection_value, - row_label="criterion_blueprint scope row", - ) - if criterion_projection_id != criterion_blueprint_id: - raise JobAnalysisIntegrityError( - "criterion_blueprint scope row escaped requested target" - ) - - cursor.execute( - _INSERT_SNAPSHOT_SQL, - ( - snapshot.tenant_record_id, - snapshot.analysis_record_id, - snapshot.job_record_id, - position_record_id, - criterion_blueprint_id, - snapshot.analysis_version_code, - snapshot.status_code, - snapshot.effective_from, - _utc(snapshot.recorded_at), - snapshot.reviewed_by_reference, - None if snapshot.reviewed_at is None else _utc(snapshot.reviewed_at), - snapshot.content_digest(), - snapshot.fja_profile.data_function_code, - snapshot.fja_profile.people_function_code, - snapshot.fja_profile.things_function_code, - *_source_params(snapshot.fja_profile.source), - ), - ) - except Exception as error: # noqa: BLE001 - DB-API errors are normalized below. - if not _is_unique_violation(error): - raise - constraint_name = _constraint_name(error) - raise JobAnalysisIntegrityError( - f"job-analysis snapshot identity or version already exists ({constraint_name!r})" - ) from error - - for task in snapshot.tasks: - cursor.execute( - _INSERT_TASK_SQL, - ( - snapshot.tenant_record_id, - snapshot.analysis_record_id, - task.task_record_id, - task.task_statement, - task.importance_level, - task.difficulty_level, - *_source_params(task.source), - ), - ) - for item in snapshot.ksao_requirements: - cursor.execute( - _INSERT_KSAO_SQL, - ( - snapshot.tenant_record_id, - snapshot.analysis_record_id, - item.ksao_record_id, - item.category_code, - item.requirement_statement, - item.importance_level, - item.proficiency_level, - *_source_params(item.source), - ), - ) - for link in snapshot.task_ksao_links: - cursor.execute( - _INSERT_LINK_SQL, - ( - snapshot.tenant_record_id, - snapshot.analysis_record_id, - link.task_record_id, - link.ksao_record_id, - link.relationship_strength, - link.essential_for_task, - ), - ) - try: - cursor.execute( - _INSERT_COMMAND_SQL, - ( - snapshot.tenant_record_id, - write_command_id, - snapshot.analysis_record_id, - idempotency_key, - request_digest, - actor_reference, - purpose_code, - ), - ) - except Exception as error: # noqa: BLE001 - DB-API errors are normalized below. - if not _is_unique_violation(error): - raise - constraint_name = _constraint_name(error) - raise JobAnalysisIdempotencyConflict( - f"idempotency or command identity was recorded concurrently ({constraint_name!r})" - ) from error - cursor.execute( - _AUDIT_OUTBOX_SQL, - ( - snapshot.tenant_record_id, - audit_evidence.event_id, - outbox_delivery_record_id, - audit_evidence.canonical_json, - audit_evidence.content_digest, - "integration_hub", - ), - ) - return snapshot - - def read_snapshot( - self, - *, - tenant_record_id: UUID, - analysis_record_id: UUID, - ) -> JobAnalysisSnapshot | None: - """Read one snapshot under forced tenant RLS and reconstruct the kernel document.""" - tenant_record_id = validate_operational_uuid("tenant_record_id", tenant_record_id) - analysis_record_id = validate_operational_uuid("analysis_record_id", analysis_record_id) - with self.connection_factory() as connection: - with connection.cursor() as cursor: - cursor.execute(_READ_ONLY_SQL) - cursor.execute(_TENANT_CONTEXT_SQL, (str(tenant_record_id),)) - return self._load_snapshot( - cursor, - tenant_record_id=tenant_record_id, - analysis_record_id=analysis_record_id, - ) - - def _load_snapshot( - self, - cursor: Any, - *, - tenant_record_id: UUID, - analysis_record_id: UUID, - ) -> JobAnalysisSnapshot | None: - """Assemble one kernel snapshot from normalized rows or return None.""" - cursor.execute(_READ_SNAPSHOT_SQL, (tenant_record_id, analysis_record_id)) - headers = _unpack_projection_rows( - "job_analysis_snapshot", - cursor.fetchmany(2), - ) - if not headers: - return None - if len(headers) != 1: - raise JobAnalysisIntegrityError("multiple snapshot headers match the requested target") - header = _unpack_fixed_projection("job_analysis_snapshot", headers[0], 19) - header_tenant_id = _validate_projection_uuid( - "job_analysis_snapshot.tenant_record_id", - header[0], - row_label="job_analysis_snapshot.tenant_record_id row", - ) - header_analysis_id = _validate_projection_uuid( - "job_analysis_snapshot.analysis_record_id", - header[1], - row_label="job_analysis_snapshot.analysis_record_id row", - ) - if header_tenant_id != tenant_record_id or header_analysis_id != analysis_record_id: - raise JobAnalysisIntegrityError("database row escaped requested target") - cursor.execute(_READ_TASKS_SQL, (tenant_record_id, analysis_record_id)) - task_rows = tuple( - _unpack_fixed_projection("job_analysis_task_item", row, 10) - for row in _unpack_projection_rows( - "job_analysis_task_item", - cursor.fetchall(), - ) - ) - cursor.execute(_READ_KSAOS_SQL, (tenant_record_id, analysis_record_id)) - ksao_rows = tuple( - _unpack_fixed_projection("job_analysis_ksao_item", row, 11) - for row in _unpack_projection_rows( - "job_analysis_ksao_item", - cursor.fetchall(), - ) - ) - cursor.execute(_READ_LINKS_SQL, (tenant_record_id, analysis_record_id)) - link_rows = tuple( - _unpack_fixed_projection("job_analysis_task_ksao_link", row, 4) - for row in _unpack_projection_rows( - "job_analysis_task_ksao_link", - cursor.fetchall(), - ) - ) - snapshot = JobAnalysisSnapshot( - analysis_record_id=header_analysis_id, - tenant_record_id=header_tenant_id, - job_record_id=header[2], - analysis_version_code=header[3], - status_code=header[4], - effective_from=header[5], - recorded_at=header[6], - tasks=tuple(_task_from_row(tenant_record_id, header[2], row) for row in task_rows), - ksao_requirements=tuple(_ksao_from_row(tenant_record_id, header[2], row) for row in ksao_rows), - task_ksao_links=tuple( - TaskKSAOLink( - task_record_id=row[0], - ksao_record_id=row[1], - relationship_strength=row[2], - essential_for_task=row[3], - ) - for row in link_rows - ), - fja_profile=FunctionalJobAnalysisProfile( - tenant_record_id=tenant_record_id, - job_record_id=header[2], - data_function_code=header[10], - people_function_code=header[11], - things_function_code=header[12], - source=_source_from_row(header[13:19]), - ), - reviewed_by_reference=header[7], - reviewed_at=header[8], - ) - if snapshot.content_digest() != header[9]: - raise JobAnalysisIntegrityError("stored snapshot digest does not match reconstructed evidence") - return snapshot - - -def _source_from_row(values: tuple[object, ...]) -> EvidenceSource: - """Rebuild one evidence source from six persisted provenance columns.""" - return _EvidenceSource( - source_uri=values[0], - source_title=values[1], - source_version_code=values[2], - retrieved_at=values[3], - content_digest_sha256=values[4], - origin_code=values[5], - ) - - -def _task_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[object, ...]) -> TaskEvidence: - """Rebuild one task item from its persisted 3NF row.""" - return TaskEvidence( - tenant_record_id=tenant_record_id, - job_record_id=job_record_id, - task_record_id=row[0], - task_statement=row[1], - importance_level=row[2], - difficulty_level=row[3], - source=_source_from_row(row[4:10]), - ) - - -def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[object, ...]) -> KSAORequirement: - """Rebuild one KSAO item from its persisted 3NF row.""" - return KSAORequirement( - tenant_record_id=tenant_record_id, - job_record_id=job_record_id, - ksao_record_id=row[0], - category_code=row[1], - requirement_statement=row[2], - importance_level=row[3], - proficiency_level=row[4], - source=_source_from_row(row[5:11]), - ) + <> From 348c6e50e502fd11edfad82f99d0fe5cb82f04e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:36:28 +0900 Subject: [PATCH 190/241] fix(job-analysis): restore canonical postgres source after failed edit --- .../src/orgmetra_job_analysis_api/postgres.py | 834 +++++++++++++++++- 1 file changed, 833 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 38922510d..af4233f99 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -63,4 +63,836 @@ SELECT request_digest_sha256, analysis_record_id, actor_reference, purpose_code FROM public.job_analysis_write_command WHERE tenant_record_id = %s - <> + AND idempotency_key = %s + LIMIT 1 +) AS command_record ON TRUE +""".strip() +_JOB_SCOPE_SQL = """ +SELECT job_profile_id +FROM public.job_profile +WHERE tenant_record_id = %s + AND job_profile_id = %s + AND recorded_to IS NULL +LIMIT 1 +""".strip() +_POSITION_SCOPE_SQL = """ +SELECT position_record_id, job_profile_id +FROM public.position_record +WHERE tenant_record_id = %s + AND position_record_id = %s + AND recorded_to IS NULL +LIMIT 1 +""".strip() +_CRITERION_SCOPE_SQL = """ +SELECT criterion_blueprint_id, job_profile_id +FROM public.criterion_blueprint +WHERE tenant_record_id = %s + AND criterion_blueprint_id = %s + AND recorded_to IS NULL +LIMIT 1 +""".strip() +_INSERT_SNAPSHOT_SQL = """ +INSERT INTO public.job_analysis_snapshot ( + tenant_record_id, analysis_record_id, job_profile_id, position_record_id, + criterion_blueprint_id, analysis_version_code, status_code, effective_from, + recorded_at, reviewed_by_reference, reviewed_at, content_digest_sha256, + data_function_code, people_function_code, things_function_code, + fja_source_uri, fja_source_title, fja_source_version_code, fja_retrieved_at, + fja_content_digest_sha256, fja_origin_code +) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s +) +""".strip() +_INSERT_TASK_SQL = """ +INSERT INTO public.job_analysis_task_item ( + tenant_record_id, analysis_record_id, task_record_id, task_statement, + importance_level, difficulty_level, source_uri, source_title, + source_version_code, retrieved_at, content_digest_sha256, origin_code +) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s +) +""".strip() +_INSERT_KSAO_SQL = """ +INSERT INTO public.job_analysis_ksao_item ( + tenant_record_id, analysis_record_id, ksao_record_id, category_code, + requirement_statement, importance_level, proficiency_level, source_uri, + source_title, source_version_code, retrieved_at, content_digest_sha256, + origin_code +) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s +) +""".strip() +_INSERT_LINK_SQL = """ +INSERT INTO public.job_analysis_task_ksao_link ( + tenant_record_id, analysis_record_id, task_record_id, ksao_record_id, + relationship_strength, essential_for_task +) VALUES ( + %s, %s, %s, %s, %s, %s +) +""".strip() +_INSERT_COMMAND_SQL = """ +INSERT INTO public.job_analysis_write_command ( + tenant_record_id, write_command_id, analysis_record_id, idempotency_key, + request_digest_sha256, actor_reference, purpose_code +) VALUES ( + %s, %s, %s, %s, %s, %s, %s +) +""".strip() +_AUDIT_OUTBOX_SQL = """ +SELECT public.record_audit_outbox_event(%s, %s, %s, %s, %s, %s) +""".strip() +_READ_SNAPSHOT_SQL = """ +SELECT + tenant_record_id, analysis_record_id, job_profile_id, analysis_version_code, + status_code, effective_from, recorded_at, reviewed_by_reference, reviewed_at, + content_digest_sha256, data_function_code, people_function_code, + things_function_code, fja_source_uri, fja_source_title, + fja_source_version_code, fja_retrieved_at, fja_content_digest_sha256, + fja_origin_code +FROM public.job_analysis_snapshot +WHERE tenant_record_id = %s + AND analysis_record_id = %s +LIMIT 2 +""".strip() +_READ_TASKS_SQL = """ +SELECT + task_record_id, task_statement, importance_level, difficulty_level, + source_uri, source_title, source_version_code, retrieved_at, + content_digest_sha256, origin_code +FROM public.job_analysis_task_item +WHERE tenant_record_id = %s + AND analysis_record_id = %s +ORDER BY task_record_id +""".strip() +_READ_KSAOS_SQL = """ +SELECT + ksao_record_id, category_code, requirement_statement, importance_level, + proficiency_level, source_uri, source_title, source_version_code, + retrieved_at, content_digest_sha256, origin_code +FROM public.job_analysis_ksao_item +WHERE tenant_record_id = %s + AND analysis_record_id = %s +ORDER BY ksao_record_id +""".strip() +_READ_LINKS_SQL = """ +SELECT task_record_id, ksao_record_id, relationship_strength, essential_for_task +FROM public.job_analysis_task_ksao_link +WHERE tenant_record_id = %s + AND analysis_record_id = %s +ORDER BY task_record_id, ksao_record_id +""".strip() + + +def _utc(value: datetime) -> datetime: + """Normalize an already-validated instant to UTC for persistence binding.""" + return value.astimezone(timezone.utc) + + +def _source_params(source: EvidenceSource) -> tuple[object, ...]: + """Return bound evidence-source columns in insert order.""" + return ( + source.source_uri, + source.source_title, + source.source_version_code, + _utc(source.retrieved_at), + source.content_digest_sha256, + source.origin_code, + ) + + +def _validate_durable_command_scalars( + *, + idempotency_key: object, + request_digest: object, + actor_reference: object, + purpose_code: object, +) -> None: + """Reject malformed durable command text before acquiring PostgreSQL resources.""" + if type(idempotency_key) is not str: + raise ValueError("idempotency_key must be exact built-in text.") + _validate_idempotency_key(idempotency_key) + if type(request_digest) is not str or _REQUEST_DIGEST_PATTERN.fullmatch(request_digest) is None: + raise ValueError("request_digest must be an exact lowercase SHA-256 digest.") + if type(actor_reference) is not str: + raise ValueError("actor_reference must be exact built-in text.") + if type(purpose_code) is not str: + raise ValueError("purpose_code must be exact built-in text.") + + +def _validate_projection_uuid( + field_name: str, + value: object, + *, + row_label: str, +) -> UUID: + """Normalize malformed durable projection identity to an integrity failure.""" + try: + return validate_operational_uuid(field_name, value) + except ValueError as error: + raise JobAnalysisIntegrityError(f"{row_label} has invalid identity") from error + + +def _unpack_fixed_projection( + row_label: str, + row: Any, + expected_columns: int, +) -> tuple[object, ...]: + """Reject executable or shape-invalid rows from fixed SQL projections.""" + if type(row) not in (tuple, list): + raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") + values = tuple(row) + if len(values) != expected_columns: + raise JobAnalysisIntegrityError(f"{row_label} row has invalid shape") + return values + + +def _unpack_projection_rows( + row_label: str, + rows: Any, +) -> tuple[object, ...]: + """Reject executable row collections before fixed projection processing.""" + if type(rows) not in (tuple, list): + raise JobAnalysisIntegrityError(f"{row_label} row collection has invalid shape") + return tuple(rows) + + +def _detach_durable_snapshot(snapshot: JobAnalysisSnapshot) -> JobAnalysisSnapshot: + """Rebuild exact snapshot evidence before any executable database boundary runs.""" + tenant_record_id = validate_operational_uuid( + "snapshot.tenant_record_id", + snapshot.tenant_record_id, + ) + canonical_json = snapshot.canonical_json() + document = json.loads(canonical_json) + detached = snapshot_from_document(document, tenant_record_id=tenant_record_id) + if detached.canonical_json() != canonical_json: + raise JobAnalysisIntegrityError("detached snapshot does not match canonical evidence") + return detached + + +@dataclass(frozen=True, slots=True) +class _DurableAuditEvidence: + """Detached Job Analysis audit evidence frozen before PostgreSQL acquisition.""" + + event_id: UUID + tenant_record_id: UUID + source_service: str + event_type: str + resource_reference: str + actor_reference: str + purpose_code: str + reason_code: str + evidence_version_code: str + result_code: str + high_impact: bool + confirmation_reference: str | None + canonical_json: str + content_digest: str + + +def _snapshot_durable_audit_authority( + audit_event: AuditOutboxEvent, +) -> _DurableAuditEvidence: + """Freeze exact audit authority, semantics, and canonical bytes before DB acquisition.""" + event_id = validate_operational_uuid("audit_event.event_id", audit_event.event_id) + tenant_record_id = validate_operational_uuid( + "audit_event.tenant_record_id", + audit_event.tenant_record_id, + ) + audit_text: dict[str, str] = {} + for field_name in ( + "source_service", + "event_type", + "resource_reference", + "actor_reference", + "purpose_code", + "reason_code", + "evidence_version_code", + "result_code", + ): + value = getattr(audit_event, field_name) + if type(value) is not str: + raise ValueError(f"audit_event.{field_name} must be exact built-in text.") + audit_text[field_name] = value + high_impact = audit_event.high_impact + confirmation_reference = audit_event.confirmation_reference + canonical_json = audit_event.canonical_json() + canonical_event = json.loads(canonical_json) + if ( + canonical_event.get("id") != str(event_id) + or canonical_event.get("orgmetratenant") != str(tenant_record_id) + or canonical_event.get("subject") != audit_text["resource_reference"] + or canonical_event.get("orgmetraactor") != audit_text["actor_reference"] + or canonical_event.get("orgmetrapurpose") != audit_text["purpose_code"] + ): + raise JobAnalysisIntegrityError( + "canonical audit evidence does not match validated authority" + ) + if ( + canonical_event.get("source") != f"urn:orgmetra:{audit_text['source_service']}" + or canonical_event.get("type") != audit_text["event_type"] + or canonical_event.get("orgmetrareason") != audit_text["reason_code"] + or canonical_event.get("orgmetraevidence") != audit_text["evidence_version_code"] + or canonical_event.get("data") + != { + "result_code": audit_text["result_code"], + "high_impact": high_impact, + } + or canonical_event.get("orgmetraconfirmation") != confirmation_reference + ): + raise JobAnalysisIntegrityError( + "canonical audit evidence does not match validated semantics" + ) + content_digest = sha256(canonical_json.encode("utf-8")).hexdigest() + return _DurableAuditEvidence( + event_id=event_id, + tenant_record_id=tenant_record_id, + source_service=audit_text["source_service"], + event_type=audit_text["event_type"], + resource_reference=audit_text["resource_reference"], + actor_reference=audit_text["actor_reference"], + purpose_code=audit_text["purpose_code"], + reason_code=audit_text["reason_code"], + evidence_version_code=audit_text["evidence_version_code"], + result_code=audit_text["result_code"], + high_impact=high_impact, + confirmation_reference=confirmation_reference, + canonical_json=canonical_json, + content_digest=content_digest, + ) + + +def _is_unique_violation(error: Exception) -> bool: + """Return whether a PostgreSQL DB-API error reports SQLSTATE 23505.""" + return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" + + +def _constraint_name(error: Exception) -> str | None: + """Return a driver-provided PostgreSQL constraint name when available.""" + diagnostic = getattr(error, "diag", None) + constraint_name = getattr(diagnostic, "constraint_name", None) + return constraint_name if isinstance(constraint_name, str) else None + + +@dataclass(frozen=True, slots=True) +class PostgresJobAnalysisPort: + """Persist and reconstruct snapshots through parameterized PostgreSQL SQL. + + ``connection_factory`` must return a DB-API-style connection context manager + whose cursor fetch methods return exact built-in ``tuple`` or ``list`` row + collections containing exact built-in ``tuple`` or ``list`` rows. Custom row + and collection factories must normalize to those inert types before crossing + this durable-evidence boundary. Deployment code owns pooling, credentials, + TLS, role selection, and that row-factory configuration. + """ + + connection_factory: PostgresConnectionFactory + + def __post_init__(self) -> None: + """Reject unusable factories before any protected write or read.""" + if not callable(self.connection_factory): + raise TypeError("connection_factory must be callable") + + def persist_snapshot( + self, + *, + snapshot: JobAnalysisSnapshot, + idempotency_key: str, + request_digest: str, + actor_reference: str, + purpose_code: str, + position_record_id: UUID | None, + criterion_blueprint_id: UUID | None, + audit_event: AuditOutboxEvent, + outbox_delivery_record_id: UUID, + write_command_id: UUID, + ) -> JobAnalysisSnapshot: + """Insert one snapshot or replay an identical Idempotency-Key command. + + The Idempotency-Key is written to ``job_analysis_write_command``. A + reused key with a different digest, actor, or purpose is rejected. + ``record_audit_outbox_event`` runs only for a new write, inside the same + transaction. + """ + if type(snapshot) is not JobAnalysisSnapshot: + raise TypeError("snapshot must be an exact JobAnalysisSnapshot") + if type(audit_event) is not AuditOutboxEvent: + raise TypeError("audit_event must be an exact AuditOutboxEvent") + _validate_durable_command_scalars( + idempotency_key=idempotency_key, + request_digest=request_digest, + actor_reference=actor_reference, + purpose_code=purpose_code, + ) + snapshot = _detach_durable_snapshot(snapshot) + audit_evidence = _snapshot_durable_audit_authority(audit_event) + write_command_id = validate_operational_uuid("write_command_id", write_command_id) + outbox_delivery_record_id = validate_operational_uuid( + "outbox_delivery_record_id", + outbox_delivery_record_id, + ) + if position_record_id is not None: + position_record_id = validate_operational_uuid("position_record_id", position_record_id) + if criterion_blueprint_id is not None: + criterion_blueprint_id = validate_operational_uuid( + "criterion_blueprint_id", + criterion_blueprint_id, + ) + expected_request_digest = command_digest( + snapshot=snapshot, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + ) + if request_digest != expected_request_digest: + raise JobAnalysisIntegrityError( + "request_digest does not match detached snapshot command" + ) + expected_resource_reference = f"job_analysis_snapshot:{snapshot.analysis_record_id.hex}" + if ( + audit_evidence.tenant_record_id != snapshot.tenant_record_id + or audit_evidence.resource_reference != expected_resource_reference + or audit_evidence.actor_reference != actor_reference + or audit_evidence.purpose_code != purpose_code + ): + raise JobAnalysisIntegrityError( + "audit event does not match the job-analysis write authority" + ) + if ( + audit_evidence.source_service != _EXPECTED_AUDIT_SOURCE_SERVICE + or audit_evidence.event_type != _EXPECTED_AUDIT_EVENT_TYPE + or audit_evidence.reason_code != _EXPECTED_AUDIT_REASON_CODE + or audit_evidence.evidence_version_code != snapshot.analysis_version_code + or audit_evidence.result_code != _EXPECTED_AUDIT_RESULT_CODE + or audit_evidence.high_impact is not False + or audit_evidence.confirmation_reference is not None + ): + raise JobAnalysisIntegrityError( + "audit event does not match the job-analysis snapshot semantics" + ) + + with self.connection_factory() as connection: + with connection.cursor() as cursor: + cursor.execute(_TENANT_CONTEXT_SQL, (str(snapshot.tenant_record_id),)) + cursor.execute( + _IDEMPOTENCY_LOOKUP_SQL, + ( + snapshot.tenant_record_id, + idempotency_key, + snapshot.tenant_record_id, + idempotency_key, + ), + ) + existing = cursor.fetchone() + if existing is None: + raise JobAnalysisIntegrityError( + "idempotent durable command lookup returned no projection" + ) + if existing is not None: + ( + stored_digest, + stored_analysis_id, + stored_actor_reference, + stored_purpose_code, + ) = _unpack_fixed_projection( + "idempotent durable command", + existing, + 4, + ) + if stored_digest is None: + if any( + value is not None + for value in ( + stored_analysis_id, + stored_actor_reference, + stored_purpose_code, + ) + ): + raise JobAnalysisIntegrityError( + "idempotent durable command row is partial-null" + ) + else: + try: + _validate_durable_command_scalars( + idempotency_key=idempotency_key, + request_digest=stored_digest, + actor_reference=stored_actor_reference, + purpose_code=stored_purpose_code, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent durable command has invalid scalar evidence" + ) from error + if stored_digest != request_digest: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different snapshot digest" + ) + try: + stored_analysis_id = validate_operational_uuid( + "stored analysis_record_id", + stored_analysis_id, + ) + except ValueError as error: + raise JobAnalysisIntegrityError( + "idempotent command has invalid analysis_record_id" + ) from error + if stored_analysis_id != snapshot.analysis_record_id: + raise JobAnalysisIntegrityError( + "idempotent command analysis_record_id does not match detached snapshot" + ) + if stored_actor_reference != actor_reference: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different actor" + ) + if stored_purpose_code != purpose_code: + raise JobAnalysisIdempotencyConflict( + "idempotency key is bound to a different purpose" + ) + replayed = self._load_snapshot( + cursor, + tenant_record_id=snapshot.tenant_record_id, + analysis_record_id=stored_analysis_id, + ) + if replayed is None: + raise JobAnalysisIntegrityError("idempotent command lost its snapshot") + replayed_digest = command_digest( + snapshot=replayed, + position_record_id=position_record_id, + criterion_blueprint_id=criterion_blueprint_id, + ) + if replayed_digest != request_digest: + raise JobAnalysisIntegrityError( + "idempotent replay snapshot does not match recorded command digest" + ) + return replayed + + try: + cursor.execute( + _JOB_SCOPE_SQL, + (snapshot.tenant_record_id, snapshot.job_record_id), + ) + job_row = cursor.fetchone() + if job_row is None: + raise JobAnalysisScopeMissing("job_profile does not exist in the tenant") + (job_projection_value,) = _unpack_fixed_projection( + "job_profile scope", + job_row, + 1, + ) + job_projection_id = _validate_projection_uuid( + "job_profile", + job_projection_value, + row_label="job_profile scope row", + ) + if job_projection_id != snapshot.job_record_id: + raise JobAnalysisIntegrityError( + "job_profile scope row escaped requested target" + ) + if position_record_id is not None: + cursor.execute( + _POSITION_SCOPE_SQL, + (snapshot.tenant_record_id, position_record_id), + ) + position_row = cursor.fetchone() + if position_row is None: + raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") + position_projection_value, position_job_value = _unpack_fixed_projection( + "position_record scope", + position_row, + 2, + ) + position_job_id = _validate_projection_uuid( + "position_record.job_profile_id", + position_job_value, + row_label="position_record.job_profile_id scope row", + ) + if position_job_id != snapshot.job_record_id: + raise JobAnalysisScopeMissing("position_record is missing or not bound to the job") + position_projection_id = _validate_projection_uuid( + "position_record", + position_projection_value, + row_label="position_record scope row", + ) + if position_projection_id != position_record_id: + raise JobAnalysisIntegrityError( + "position_record scope row escaped requested target" + ) + if criterion_blueprint_id is not None: + cursor.execute( + _CRITERION_SCOPE_SQL, + (snapshot.tenant_record_id, criterion_blueprint_id), + ) + criterion_row = cursor.fetchone() + if criterion_row is None: + raise JobAnalysisScopeMissing( + "criterion_blueprint is missing or not bound to the job" + ) + criterion_projection_value, criterion_job_value = _unpack_fixed_projection( + "criterion_blueprint scope", + criterion_row, + 2, + ) + criterion_job_id = _validate_projection_uuid( + "criterion_blueprint.job_profile_id", + criterion_job_value, + row_label="criterion_blueprint.job_profile_id scope row", + ) + if criterion_job_id != snapshot.job_record_id: + raise JobAnalysisScopeMissing( + "criterion_blueprint is missing or not bound to the job" + ) + criterion_projection_id = _validate_projection_uuid( + "criterion_blueprint", + criterion_projection_value, + row_label="criterion_blueprint scope row", + ) + if criterion_projection_id != criterion_blueprint_id: + raise JobAnalysisIntegrityError( + "criterion_blueprint scope row escaped requested target" + ) + + cursor.execute( + _INSERT_SNAPSHOT_SQL, + ( + snapshot.tenant_record_id, + snapshot.analysis_record_id, + snapshot.job_record_id, + position_record_id, + criterion_blueprint_id, + snapshot.analysis_version_code, + snapshot.status_code, + snapshot.effective_from, + _utc(snapshot.recorded_at), + snapshot.reviewed_by_reference, + None if snapshot.reviewed_at is None else _utc(snapshot.reviewed_at), + snapshot.content_digest(), + snapshot.fja_profile.data_function_code, + snapshot.fja_profile.people_function_code, + snapshot.fja_profile.things_function_code, + *_source_params(snapshot.fja_profile.source), + ), + ) + except Exception as error: # noqa: BLE001 - DB-API errors are normalized below. + if not _is_unique_violation(error): + raise + constraint_name = _constraint_name(error) + raise JobAnalysisIntegrityError( + f"job-analysis snapshot identity or version already exists ({constraint_name!r})" + ) from error + + for task in snapshot.tasks: + cursor.execute( + _INSERT_TASK_SQL, + ( + snapshot.tenant_record_id, + snapshot.analysis_record_id, + task.task_record_id, + task.task_statement, + task.importance_level, + task.difficulty_level, + *_source_params(task.source), + ), + ) + for item in snapshot.ksao_requirements: + cursor.execute( + _INSERT_KSAO_SQL, + ( + snapshot.tenant_record_id, + snapshot.analysis_record_id, + item.ksao_record_id, + item.category_code, + item.requirement_statement, + item.importance_level, + item.proficiency_level, + *_source_params(item.source), + ), + ) + for link in snapshot.task_ksao_links: + cursor.execute( + _INSERT_LINK_SQL, + ( + snapshot.tenant_record_id, + snapshot.analysis_record_id, + link.task_record_id, + link.ksao_record_id, + link.relationship_strength, + link.essential_for_task, + ), + ) + try: + cursor.execute( + _INSERT_COMMAND_SQL, + ( + snapshot.tenant_record_id, + write_command_id, + snapshot.analysis_record_id, + idempotency_key, + request_digest, + actor_reference, + purpose_code, + ), + ) + except Exception as error: # noqa: BLE001 - DB-API errors are normalized below. + if not _is_unique_violation(error): + raise + constraint_name = _constraint_name(error) + raise JobAnalysisIdempotencyConflict( + f"idempotency or command identity was recorded concurrently ({constraint_name!r})" + ) from error + cursor.execute( + _AUDIT_OUTBOX_SQL, + ( + snapshot.tenant_record_id, + audit_evidence.event_id, + outbox_delivery_record_id, + audit_evidence.canonical_json, + audit_evidence.content_digest, + "integration_hub", + ), + ) + return snapshot + + def read_snapshot( + self, + *, + tenant_record_id: UUID, + analysis_record_id: UUID, + ) -> JobAnalysisSnapshot | None: + """Read one snapshot under forced tenant RLS and reconstruct the kernel document.""" + tenant_record_id = validate_operational_uuid("tenant_record_id", tenant_record_id) + analysis_record_id = validate_operational_uuid("analysis_record_id", analysis_record_id) + with self.connection_factory() as connection: + with connection.cursor() as cursor: + cursor.execute(_READ_ONLY_SQL) + cursor.execute(_TENANT_CONTEXT_SQL, (str(tenant_record_id),)) + return self._load_snapshot( + cursor, + tenant_record_id=tenant_record_id, + analysis_record_id=analysis_record_id, + ) + + def _load_snapshot( + self, + cursor: Any, + *, + tenant_record_id: UUID, + analysis_record_id: UUID, + ) -> JobAnalysisSnapshot | None: + """Assemble one kernel snapshot from normalized rows or return None.""" + cursor.execute(_READ_SNAPSHOT_SQL, (tenant_record_id, analysis_record_id)) + headers = _unpack_projection_rows( + "job_analysis_snapshot", + cursor.fetchmany(2), + ) + if not headers: + return None + if len(headers) != 1: + raise JobAnalysisIntegrityError("multiple snapshot headers match the requested target") + header = _unpack_fixed_projection("job_analysis_snapshot", headers[0], 19) + header_tenant_id = _validate_projection_uuid( + "job_analysis_snapshot.tenant_record_id", + header[0], + row_label="job_analysis_snapshot.tenant_record_id row", + ) + header_analysis_id = _validate_projection_uuid( + "job_analysis_snapshot.analysis_record_id", + header[1], + row_label="job_analysis_snapshot.analysis_record_id row", + ) + if header_tenant_id != tenant_record_id or header_analysis_id != analysis_record_id: + raise JobAnalysisIntegrityError("database row escaped requested target") + cursor.execute(_READ_TASKS_SQL, (tenant_record_id, analysis_record_id)) + task_rows = tuple( + _unpack_fixed_projection("job_analysis_task_item", row, 10) + for row in _unpack_projection_rows( + "job_analysis_task_item", + cursor.fetchall(), + ) + ) + cursor.execute(_READ_KSAOS_SQL, (tenant_record_id, analysis_record_id)) + ksao_rows = tuple( + _unpack_fixed_projection("job_analysis_ksao_item", row, 11) + for row in _unpack_projection_rows( + "job_analysis_ksao_item", + cursor.fetchall(), + ) + ) + cursor.execute(_READ_LINKS_SQL, (tenant_record_id, analysis_record_id)) + link_rows = tuple( + _unpack_fixed_projection("job_analysis_task_ksao_link", row, 4) + for row in _unpack_projection_rows( + "job_analysis_task_ksao_link", + cursor.fetchall(), + ) + ) + snapshot = JobAnalysisSnapshot( + analysis_record_id=header_analysis_id, + tenant_record_id=header_tenant_id, + job_record_id=header[2], + analysis_version_code=header[3], + status_code=header[4], + effective_from=header[5], + recorded_at=header[6], + tasks=tuple(_task_from_row(tenant_record_id, header[2], row) for row in task_rows), + ksao_requirements=tuple(_ksao_from_row(tenant_record_id, header[2], row) for row in ksao_rows), + task_ksao_links=tuple( + TaskKSAOLink( + task_record_id=row[0], + ksao_record_id=row[1], + relationship_strength=row[2], + essential_for_task=row[3], + ) + for row in link_rows + ), + fja_profile=FunctionalJobAnalysisProfile( + tenant_record_id=tenant_record_id, + job_record_id=header[2], + data_function_code=header[10], + people_function_code=header[11], + things_function_code=header[12], + source=_source_from_row(header[13:19]), + ), + reviewed_by_reference=header[7], + reviewed_at=header[8], + ) + if snapshot.content_digest() != header[9]: + raise JobAnalysisIntegrityError("stored snapshot digest does not match reconstructed evidence") + return snapshot + + +def _source_from_row(values: tuple[object, ...]) -> EvidenceSource: + """Rebuild one evidence source from six persisted provenance columns.""" + return EvidenceSource( + source_uri=values[0], + source_title=values[1], + source_version_code=values[2], + retrieved_at=values[3], + content_digest_sha256=values[4], + origin_code=values[5], + ) + + +def _task_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[object, ...]) -> TaskEvidence: + """Rebuild one task item from its persisted 3NF row.""" + return TaskEvidence( + tenant_record_id=tenant_record_id, + job_record_id=job_record_id, + task_record_id=row[0], + task_statement=row[1], + importance_level=row[2], + difficulty_level=row[3], + source=_source_from_row(row[4:10]), + ) + + +def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[object, ...]) -> KSAORequirement: + """Rebuild one KSAO item from its persisted 3NF row.""" + return KSAORequirement( + tenant_record_id=tenant_record_id, + job_record_id=job_record_id, + ksao_record_id=row[0], + category_code=row[1], + requirement_statement=row[2], + importance_level=row[3], + proficiency_level=row[4], + source=_source_from_row(row[5:11]), + ) From cec9eb8c372de14e5b221ebe10aaaef0f0ef9df0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:40:15 +0900 Subject: [PATCH 191/241] fix(job-analysis): seal durable audit time before canonicalization --- .../src/orgmetra_job_analysis_api/postgres.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index af4233f99..8ba236a4b 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -314,6 +314,9 @@ def _snapshot_durable_audit_authority( if type(value) is not str: raise ValueError(f"audit_event.{field_name} must be exact built-in text.") audit_text[field_name] = value + occurred_at = audit_event.occurred_at + if type(occurred_at) is not datetime: + raise ValueError("audit_event.occurred_at must be an exact built-in datetime.") high_impact = audit_event.high_impact confirmation_reference = audit_event.confirmation_reference canonical_json = audit_event.canonical_json() @@ -367,7 +370,7 @@ def _is_unique_violation(error: Exception) -> bool: return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" -def _constraint_name(error: Exception) -> str | None: +def _constraint_name(error: Exception) -> str | str: """Return a driver-provided PostgreSQL constraint name when available.""" diagnostic = getattr(error, "diag", None) constraint_name = getattr(diagnostic, "constraint_name", None) @@ -829,7 +832,7 @@ def _load_snapshot( tenant_record_id=header_tenant_id, job_record_id=header[2], analysis_version_code=header[3], - status_code=header[4], + job_status_code=header[4], effective_from=header[5], recorded_at=header[6], tasks=tuple(_task_from_row(tenant_record_id, header[2], row) for row in task_rows), From a57d5840b3893ec7c56799d4c55e9a3f35f196e1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:41:07 +0900 Subject: [PATCH 192/241] fix(job-analysis): restore canonical postgres source after rejected edit --- .../src/orgmetra_job_analysis_api/postgres.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 8ba236a4b..af4233f99 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -314,9 +314,6 @@ def _snapshot_durable_audit_authority( if type(value) is not str: raise ValueError(f"audit_event.{field_name} must be exact built-in text.") audit_text[field_name] = value - occurred_at = audit_event.occurred_at - if type(occurred_at) is not datetime: - raise ValueError("audit_event.occurred_at must be an exact built-in datetime.") high_impact = audit_event.high_impact confirmation_reference = audit_event.confirmation_reference canonical_json = audit_event.canonical_json() @@ -370,7 +367,7 @@ def _is_unique_violation(error: Exception) -> bool: return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" -def _constraint_name(error: Exception) -> str | str: +def _constraint_name(error: Exception) -> str | None: """Return a driver-provided PostgreSQL constraint name when available.""" diagnostic = getattr(error, "diag", None) constraint_name = getattr(diagnostic, "constraint_name", None) @@ -832,7 +829,7 @@ def _load_snapshot( tenant_record_id=header_tenant_id, job_record_id=header[2], analysis_version_code=header[3], - job_status_code=header[4], + status_code=header[4], effective_from=header[5], recorded_at=header[6], tasks=tuple(_task_from_row(tenant_record_id, header[2], row) for row in task_rows), From 2c8ab24c9858dd076f52e2ea4bd48ead43068993 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:44:34 +0900 Subject: [PATCH 193/241] chore(job-analysis): defer audit time type gate to canonical kernel owner --- ...t_postgres_audit_time_runtime_integrity.py | 82 ------------------- 1 file changed, 82 deletions(-) delete mode 100644 services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py b/services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py deleted file mode 100644 index 13ff5e9b7..000000000 --- a/services/job-analysis-api/tests/test_postgres_audit_time_runtime_integrity.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Regression coverage for inert Job Analysis durable audit occurrence time.""" - -from __future__ import annotations - -from datetime import datetime, timezone -from uuid import UUID - -import pytest - -from orgmetra_hris_kernel import AuditOutboxEvent -from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort -from orgmetra_job_analysis_api.snapshot import command_digest -from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot - -_ACTOR_REFERENCE = "keyverse:actor-ja-1" -_PURPOSE_CODE = "job_analysis_write" - - -class _ExecutableAuditDatetime(datetime): - """Trip if durable canonicalization invokes caller-defined datetime behavior.""" - - def astimezone(self, tz: object | None = None) -> datetime: - """Prove exact-type rejection must precede canonical timestamp conversion.""" - del tz - raise AssertionError( - "audit datetime astimezone executed before exact-type rejection" - ) - - -def _never_connect() -> object: - """Prove invalid audit time is rejected before PostgreSQL acquisition.""" - raise AssertionError("database acquired before audit time runtime validation") - - -def test_executable_audit_datetime_fails_before_canonicalization_or_database() -> None: - """A datetime subtype must not execute while durable audit bytes are frozen.""" - snapshot = clinical_psychologist_snapshot() - audit_event = AuditOutboxEvent( - event_id=UUID("0198a412-6000-7000-8000-000000000501"), - tenant_record_id=TENANT, - source_service="job_analysis_api", - event_type="orgmetra.job_architecture.snapshot_recorded", - resource_reference=f"job_analysis_snapshot:{ANALYSIS.hex}", - actor_reference=_ACTOR_REFERENCE, - purpose_code=_PURPOSE_CODE, - reason_code="snapshot_persisted", - evidence_version_code=snapshot.analysis_version_code, - result_code="recorded", - occurred_at=_ExecutableAuditDatetime( - 2026, - 9, - 4, - 5, - 30, - tzinfo=timezone.utc, - ), - high_impact=False, - ) - port = PostgresJobAnalysisPort(_never_connect) - - with pytest.raises( - ValueError, - match=r"audit_event\.occurred_at must be an exact built-in datetime", - ): - port.persist_snapshot( - snapshot=snapshot, - idempotency_key=IDEMPOTENCY_KEY, - request_digest=command_digest( - snapshot=snapshot, - position_record_id=None, - criterion_blueprint_id=None, - ), - actor_reference=_ACTOR_REFERENCE, - purpose_code=_PURPOSE_CODE, - position_record_id=None, - criterion_blueprint_id=None, - audit_event=audit_event, - outbox_delivery_record_id=UUID( - "0198a412-6000-7000-8000-000000000502" - ), - write_command_id=UUID("0198a412-6000-7000-8000-000000000503"), - ) From 854444f12488eb44852f7854c2cac64874e612e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:02:06 +0900 Subject: [PATCH 194/241] test(job-analysis): reject executable driver error metadata --- ...ostgres_driver_error_metadata_integrity.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py b/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py new file mode 100644 index 000000000..301fda6f7 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py @@ -0,0 +1,77 @@ +"""Runtime-integrity regressions for PostgreSQL driver error metadata.""" + +from __future__ import annotations + +import unittest + +from orgmetra_job_analysis_api.postgres import _constraint_name, _is_unique_violation + + +class _ExecutableText(str): + """Fail if driver-provided text participates in caller-defined equality.""" + + def __eq__(self, other: object) -> bool: + raise AssertionError("driver text equality must not execute") + + +class _FallbackProbe(Exception): + """Expose modern SQLSTATE while making legacy fallback access observable.""" + + sqlstate = "23505" + + @property + def pgcode(self) -> str: + raise AssertionError("legacy pgcode must not be read when sqlstate is present") + + +class _ExecutableStateError(Exception): + """Carry a string subtype that must not be trusted as SQLSTATE evidence.""" + + sqlstate = _ExecutableText("23505") + + +class _Diagnostic: + """Carry one driver-provided constraint diagnostic."" + + def __init__(self, constraint_name: str) -> None: + self.constraint_name = constraint_name + + +class _ExecutableConstraintError(Exception): + """Carry a constraint-name subtype that must not escape normalization.""" + + sqlstate = "23505" + + def __init__(self) -> None: + super().__init__("unique violation") + self.diag = _Diagnostic(_ExecutableText("job_analysis_snapshot_job_version_unique")) + + +class _LegacyUniqueViolation(Exception): + """Mimic a legacy PostgreSQL DB-API error exposing only pgcode.""" + + pgcode = "23505" + + +class PostgresDriverErrorMetadataIntegrityTests(unittest.TestCase): + """Require inert built-in diagnostics before classification or interpolation.""" + + def test_modern_sqlstate_does_not_touch_legacy_fallback(self) -> None: + """Do not execute a legacy fallback accessor after modern SQLSTATE exists.""" + self.assertTrue(_is_unique_violation(_FallbackProbe("unique"))) + + def test_sqlstate_subtype_is_not_compared_as_unique_violation_evidence(self) -> None: + """Reject executable text before equality-based SQLSTATE classification.""" + self.assertFalse(_is_unique_violation(_ExecutableStateError("unique"))) + + def test_constraint_name_requires_exact_builtin_text(self) -> None: + """Keep executable diagnostic text out of normalized conflict messages.""" + self.assertIsNone(_constraint_name(_ExecutableConstraintError())) + + def test_legacy_builtin_pgcode_remains_supported(self) -> None: + """Retain DB-API compatibility when only inert legacy pgcode is available.""" + self.assertTrue(_is_unique_violation(_LegacyUniqueViolation("unique"))) + + +if __name__ == "__main__": + unittest.main() From 9a5f1dee17958fe968ac8772f79e3b97e7b84c17 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:03:25 +0900 Subject: [PATCH 195/241] fix(job-analysis): normalize inert driver error metadata --- .../src/orgmetra_job_analysis_api/postgres.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index af4233f99..ac3fa2270 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -363,15 +363,18 @@ def _snapshot_durable_audit_authority( def _is_unique_violation(error: Exception) -> bool: - """Return whether a PostgreSQL DB-API error reports SQLSTATE 23505.""" - return getattr(error, "sqlstate", getattr(error, "pgcode", None)) == "23505" + """Return whether inert PostgreSQL DB-API metadata reports SQLSTATE 23505.""" + sqlstate = getattr(error, "sqlstate", None) + if sqlstate is None: + sqlstate = getattr(error, "pgcode", None) + return type(sqlstate) is str and sqlstate == "23505" def _constraint_name(error: Exception) -> str | None: - """Return a driver-provided PostgreSQL constraint name when available.""" + """Return an exact built-in PostgreSQL constraint diagnostic when available.""" diagnostic = getattr(error, "diag", None) constraint_name = getattr(diagnostic, "constraint_name", None) - return constraint_name if isinstance(constraint_name, str) else None + return constraint_name if type(constraint_name) is str else None @dataclass(frozen=True, slots=True) From b0055c42627f1e0763ec4be7bad01a2f542f6d0e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:09:42 +0900 Subject: [PATCH 196/241] test(job-analysis): reject polymorphic error metadata lookup --- ...ostgres_driver_error_metadata_integrity.py | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py b/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py index 301fda6f7..92713282a 100644 --- a/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py @@ -30,8 +30,27 @@ class _ExecutableStateError(Exception): sqlstate = _ExecutableText("23505") +class _AttributeTrapStateError(Exception): + """Expose inert class metadata behind executable dynamic attribute access.""" + + sqlstate = "23505" + + def __getattribute__(self, name: str) -> object: + if name in {"sqlstate", "pgcode"}: + raise AssertionError("driver metadata lookup must not execute __getattribute__") + return super().__getattribute__(name) + + +class _ExecutableStatePropertyError(Exception): + """Expose SQLSTATE only through a descriptor that must not execute.""" + + @property + def sqlstate(self) -> str: + raise AssertionError("driver SQLSTATE descriptor must not execute") + + class _Diagnostic: - """Carry one driver-provided constraint diagnostic."" + """Carry one driver-provided constraint diagnostic.""" def __init__(self, constraint_name: str) -> None: self.constraint_name = constraint_name @@ -47,6 +66,43 @@ def __init__(self) -> None: self.diag = _Diagnostic(_ExecutableText("job_analysis_snapshot_job_version_unique")) +class _AttributeTrapDiagnostic: + """Store inert diagnostic text behind executable dynamic attribute access.""" + + def __init__(self) -> None: + self.constraint_name = "job_analysis_snapshot_job_version_unique" + + def __getattribute__(self, name: str) -> object: + if name == "constraint_name": + raise AssertionError("constraint metadata lookup must not execute __getattribute__") + return super().__getattribute__(name) + + +class _AttributeTrapConstraintError(Exception): + """Store an inert diagnostic object behind executable dynamic access.""" + + sqlstate = "23505" + + def __init__(self) -> None: + super().__init__("unique violation") + self.diag = _AttributeTrapDiagnostic() + + def __getattribute__(self, name: str) -> object: + if name == "diag": + raise AssertionError("driver diagnostic lookup must not execute __getattribute__") + return super().__getattribute__(name) + + +class _ExecutableDiagPropertyError(Exception): + """Expose diagnostics only through a descriptor that must not execute.""" + + sqlstate = "23505" + + @property + def diag(self) -> object: + raise AssertionError("driver diagnostic descriptor must not execute") + + class _LegacyUniqueViolation(Exception): """Mimic a legacy PostgreSQL DB-API error exposing only pgcode.""" @@ -64,10 +120,29 @@ def test_sqlstate_subtype_is_not_compared_as_unique_violation_evidence(self) -> """Reject executable text before equality-based SQLSTATE classification.""" self.assertFalse(_is_unique_violation(_ExecutableStateError("unique"))) + def test_sqlstate_lookup_does_not_execute_dynamic_attribute_access(self) -> None: + """Read inert stored SQLSTATE without invoking a driver override.""" + self.assertTrue(_is_unique_violation(_AttributeTrapStateError("unique"))) + + def test_sqlstate_descriptor_is_not_executed_for_classification(self) -> None: + """Treat executable SQLSTATE descriptors as unavailable evidence.""" + self.assertFalse(_is_unique_violation(_ExecutableStatePropertyError("unique"))) + def test_constraint_name_requires_exact_builtin_text(self) -> None: """Keep executable diagnostic text out of normalized conflict messages.""" self.assertIsNone(_constraint_name(_ExecutableConstraintError())) + def test_constraint_lookup_does_not_execute_dynamic_attribute_access(self) -> None: + """Recover inert stored diagnostics without invoking driver overrides.""" + self.assertEqual( + _constraint_name(_AttributeTrapConstraintError()), + "job_analysis_snapshot_job_version_unique", + ) + + def test_constraint_descriptor_is_not_executed(self) -> None: + """Omit a diagnostic that exists only behind an executable descriptor.""" + self.assertIsNone(_constraint_name(_ExecutableDiagPropertyError("unique"))) + def test_legacy_builtin_pgcode_remains_supported(self) -> None: """Retain DB-API compatibility when only inert legacy pgcode is available.""" self.assertTrue(_is_unique_violation(_LegacyUniqueViolation("unique"))) From c71fc7778dc13df4d5d2c4fb714ad1c26c41f6e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:11:27 +0900 Subject: [PATCH 197/241] fix(job-analysis): statically read driver error metadata --- .../src/orgmetra_job_analysis_api/postgres.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index ac3fa2270..e84c8f9a5 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -13,6 +13,7 @@ from dataclasses import dataclass from datetime import datetime, timezone from hashlib import sha256 +from inspect import getattr_static import json import re from typing import Any, Callable @@ -362,19 +363,30 @@ def _snapshot_durable_audit_authority( ) +def _static_builtin_text_attribute(owner: object, attribute_name: str) -> str | None: + """Read stored driver metadata without invoking dynamic attribute behavior.""" + try: + value = getattr_static(owner, attribute_name) + except AttributeError: + return None + return value if type(value) is str else None + + def _is_unique_violation(error: Exception) -> bool: """Return whether inert PostgreSQL DB-API metadata reports SQLSTATE 23505.""" - sqlstate = getattr(error, "sqlstate", None) + sqlstate = _static_builtin_text_attribute(error, "sqlstate") if sqlstate is None: - sqlstate = getattr(error, "pgcode", None) - return type(sqlstate) is str and sqlstate == "23505" + sqlstate = _static_builtin_text_attribute(error, "pgcode") + return sqlstate == "23505" def _constraint_name(error: Exception) -> str | None: - """Return an exact built-in PostgreSQL constraint diagnostic when available.""" - diagnostic = getattr(error, "diag", None) - constraint_name = getattr(diagnostic, "constraint_name", None) - return constraint_name if type(constraint_name) is str else None + """Return an inert PostgreSQL constraint diagnostic when safely stored.""" + try: + diagnostic = getattr_static(error, "diag") + except AttributeError: + return None + return _static_builtin_text_attribute(diagnostic, "constraint_name") @dataclass(frozen=True, slots=True) From b60793a982f2dde09c1385a0ab500b327a000de7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:16:28 +0900 Subject: [PATCH 198/241] test(job-analysis): decouple conflicts from driver diagnostics --- .../tests/test_postgres_concurrency.py | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_concurrency.py b/services/job-analysis-api/tests/test_postgres_concurrency.py index dcd71e574..5484dd7b3 100644 --- a/services/job-analysis-api/tests/test_postgres_concurrency.py +++ b/services/job-analysis-api/tests/test_postgres_concurrency.py @@ -10,7 +10,6 @@ from orgmetra_job_analysis_api.postgres import ( PostgresJobAnalysisPort, _IDEMPOTENCY_LOOKUP_SQL, - _constraint_name, _is_unique_violation, ) from orgmetra_job_analysis_api.snapshot import ( @@ -22,22 +21,11 @@ from fixtures import IDEMPOTENCY_KEY, JOB, clinical_psychologist_snapshot -class _Diagnostic: - """Expose the PostgreSQL constraint name carried by a driver error.""" - - def __init__(self, constraint_name: str) -> None: - self.constraint_name = constraint_name - - class _UniqueViolation(Exception): """Mimic a DB-API unique violation from psycopg-style drivers.""" sqlstate = "23505" - def __init__(self, constraint_name: str) -> None: - super().__init__(constraint_name) - self.diag = _Diagnostic(constraint_name) - class _ConstraintCursor: """Return valid parent lookups and fail at one selected insert constraint.""" @@ -149,27 +137,28 @@ def test_idempotency_lookup_serializes_the_tenant_and_key_before_reading(self) - self.assertIn("idempotency_key", normalized) def test_unique_violation_metadata_is_read_without_driver_lock_in(self) -> None: - """Support modern and legacy PostgreSQL DB-API error attributes.""" + """Support modern and legacy PostgreSQL DB-API SQLSTATE attributes.""" error = _UniqueViolation("job_analysis_snapshot_job_version_unique") self.assertTrue(_is_unique_violation(error)) - self.assertEqual( - _constraint_name(error), - "job_analysis_snapshot_job_version_unique", - ) legacy = Exception("legacy") legacy.pgcode = "23505" # type: ignore[attr-defined] self.assertTrue(_is_unique_violation(legacy)) self.assertFalse(_is_unique_violation(RuntimeError("other"))) - self.assertIsNone(_constraint_name(RuntimeError("other"))) def test_snapshot_version_race_maps_to_integrity_error(self) -> None: - """Do not leak a raw driver exception for a concurrent job/version write.""" - with self.assertRaisesRegex(JobAnalysisIntegrityError, "already exists"): + """Normalize a unique race without depending on optional driver diagnostics.""" + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + r"^job-analysis snapshot identity or version already exists$", + ): _persist_with_constraint("job_analysis_snapshot_job_version_unique") def test_command_key_race_maps_to_idempotency_conflict(self) -> None: - """Do not expose a raw driver error if an uncoordinated writer wins the key.""" - with self.assertRaisesRegex(JobAnalysisIdempotencyConflict, "concurrently"): + """Normalize a command race without depending on optional driver diagnostics.""" + with self.assertRaisesRegex( + JobAnalysisIdempotencyConflict, + r"^idempotency or command identity was recorded concurrently$", + ): _persist_with_constraint("job_analysis_write_command_idempotency_unique") def test_non_unique_snapshot_failure_is_not_reclassified(self) -> None: From 3a403a59909930d57a2d5882cdfe36489989942f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:16:46 +0900 Subject: [PATCH 199/241] test(job-analysis): narrow driver contract to SQLSTATE evidence --- ...ostgres_driver_error_metadata_integrity.py | 75 +------------------ 1 file changed, 3 insertions(+), 72 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py b/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py index 92713282a..e82e3181c 100644 --- a/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_driver_error_metadata_integrity.py @@ -1,10 +1,10 @@ -"""Runtime-integrity regressions for PostgreSQL driver error metadata.""" +"""Runtime-integrity regressions for PostgreSQL driver SQLSTATE metadata.""" from __future__ import annotations import unittest -from orgmetra_job_analysis_api.postgres import _constraint_name, _is_unique_violation +from orgmetra_job_analysis_api.postgres import _is_unique_violation class _ExecutableText(str): @@ -49,60 +49,6 @@ def sqlstate(self) -> str: raise AssertionError("driver SQLSTATE descriptor must not execute") -class _Diagnostic: - """Carry one driver-provided constraint diagnostic.""" - - def __init__(self, constraint_name: str) -> None: - self.constraint_name = constraint_name - - -class _ExecutableConstraintError(Exception): - """Carry a constraint-name subtype that must not escape normalization.""" - - sqlstate = "23505" - - def __init__(self) -> None: - super().__init__("unique violation") - self.diag = _Diagnostic(_ExecutableText("job_analysis_snapshot_job_version_unique")) - - -class _AttributeTrapDiagnostic: - """Store inert diagnostic text behind executable dynamic attribute access.""" - - def __init__(self) -> None: - self.constraint_name = "job_analysis_snapshot_job_version_unique" - - def __getattribute__(self, name: str) -> object: - if name == "constraint_name": - raise AssertionError("constraint metadata lookup must not execute __getattribute__") - return super().__getattribute__(name) - - -class _AttributeTrapConstraintError(Exception): - """Store an inert diagnostic object behind executable dynamic access.""" - - sqlstate = "23505" - - def __init__(self) -> None: - super().__init__("unique violation") - self.diag = _AttributeTrapDiagnostic() - - def __getattribute__(self, name: str) -> object: - if name == "diag": - raise AssertionError("driver diagnostic lookup must not execute __getattribute__") - return super().__getattribute__(name) - - -class _ExecutableDiagPropertyError(Exception): - """Expose diagnostics only through a descriptor that must not execute.""" - - sqlstate = "23505" - - @property - def diag(self) -> object: - raise AssertionError("driver diagnostic descriptor must not execute") - - class _LegacyUniqueViolation(Exception): """Mimic a legacy PostgreSQL DB-API error exposing only pgcode.""" @@ -110,7 +56,7 @@ class _LegacyUniqueViolation(Exception): class PostgresDriverErrorMetadataIntegrityTests(unittest.TestCase): - """Require inert built-in diagnostics before classification or interpolation.""" + """Require inert built-in SQLSTATE evidence before unique classification.""" def test_modern_sqlstate_does_not_touch_legacy_fallback(self) -> None: """Do not execute a legacy fallback accessor after modern SQLSTATE exists.""" @@ -128,21 +74,6 @@ def test_sqlstate_descriptor_is_not_executed_for_classification(self) -> None: """Treat executable SQLSTATE descriptors as unavailable evidence.""" self.assertFalse(_is_unique_violation(_ExecutableStatePropertyError("unique"))) - def test_constraint_name_requires_exact_builtin_text(self) -> None: - """Keep executable diagnostic text out of normalized conflict messages.""" - self.assertIsNone(_constraint_name(_ExecutableConstraintError())) - - def test_constraint_lookup_does_not_execute_dynamic_attribute_access(self) -> None: - """Recover inert stored diagnostics without invoking driver overrides.""" - self.assertEqual( - _constraint_name(_AttributeTrapConstraintError()), - "job_analysis_snapshot_job_version_unique", - ) - - def test_constraint_descriptor_is_not_executed(self) -> None: - """Omit a diagnostic that exists only behind an executable descriptor.""" - self.assertIsNone(_constraint_name(_ExecutableDiagPropertyError("unique"))) - def test_legacy_builtin_pgcode_remains_supported(self) -> None: """Retain DB-API compatibility when only inert legacy pgcode is available.""" self.assertTrue(_is_unique_violation(_LegacyUniqueViolation("unique"))) From b051272ffe52ddfbf71ddf1f0f5dcce26eb9d946 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:18:05 +0900 Subject: [PATCH 200/241] fix(job-analysis): decouple conflicts from driver diagnostics --- .../src/orgmetra_job_analysis_api/postgres.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index e84c8f9a5..0a13c1416 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -380,15 +380,6 @@ def _is_unique_violation(error: Exception) -> bool: return sqlstate == "23505" -def _constraint_name(error: Exception) -> str | None: - """Return an inert PostgreSQL constraint diagnostic when safely stored.""" - try: - diagnostic = getattr_static(error, "diag") - except AttributeError: - return None - return _static_builtin_text_attribute(diagnostic, "constraint_name") - - @dataclass(frozen=True, slots=True) class PostgresJobAnalysisPort: """Persist and reconstruct snapshots through parameterized PostgreSQL SQL. @@ -689,9 +680,8 @@ def persist_snapshot( except Exception as error: # noqa: BLE001 - DB-API errors are normalized below. if not _is_unique_violation(error): raise - constraint_name = _constraint_name(error) raise JobAnalysisIntegrityError( - f"job-analysis snapshot identity or version already exists ({constraint_name!r})" + "job-analysis snapshot identity or version already exists" ) from error for task in snapshot.tasks: @@ -749,9 +739,8 @@ def persist_snapshot( except Exception as error: # noqa: BLE001 - DB-API errors are normalized below. if not _is_unique_violation(error): raise - constraint_name = _constraint_name(error) raise JobAnalysisIdempotencyConflict( - f"idempotency or command identity was recorded concurrently ({constraint_name!r})" + "idempotency or command identity was recorded concurrently" ) from error cursor.execute( _AUDIT_OUTBOX_SQL, From d47c9627e53b7fa85d4fb61352d38d9b008ad7f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:01:25 +0900 Subject: [PATCH 201/241] test(job-analysis): reject executable snapshot document inputs --- ...est_snapshot_document_runtime_integrity.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py new file mode 100644 index 000000000..062737a52 --- /dev/null +++ b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py @@ -0,0 +1,103 @@ +"""Regressions for executable posted snapshot values before authorization.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest + +from orgmetra_job_analysis_api.snapshot import snapshot_from_document +from fixtures import TENANT, clinical_psychologist_document + + +class _ExecutableMapping(dict): + """Trip if snapshot parsing iterates or reads a caller-defined mapping subtype.""" + + def __iter__(self): + """Reject parser iteration before exact container validation.""" + raise AssertionError("mapping subtype iteration executed") + + def get(self, key: object, default: object = None) -> object: + """Reject parser field reads before exact container validation.""" + raise AssertionError("mapping subtype get executed") + + +class _ExecutableList(list): + """Trip if snapshot parsing consumes a caller-defined list subtype.""" + + def __len__(self) -> int: + """Reject truthiness/size checks before exact container validation.""" + raise AssertionError("list subtype length executed") + + def __iter__(self): + """Reject item iteration before exact container validation.""" + raise AssertionError("list subtype iteration executed") + + +class _ExecutableText(str): + """Trip if text normalization executes caller-defined string behavior.""" + + def replace(self, old: str, new: str, count: int = -1) -> str: + """Reject ISO timestamp normalization before exact text validation.""" + raise AssertionError("text subtype replace executed") + + +class _ExecutableDateTime(datetime): + """Trip if timezone validation consumes a caller-defined datetime subtype.""" + + def utcoffset(self): + """Reject timezone behavior before exact datetime validation.""" + raise AssertionError("datetime subtype utcoffset executed") + + +def test_rejects_executable_top_level_mapping_before_iteration() -> None: + """The posted document must be an inert built-in mapping before any field scan.""" + posted = _ExecutableMapping(clinical_psychologist_document()) + + with pytest.raises(ValueError, match="snapshot document must be an object"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + +def test_rejects_executable_task_list_before_truthiness_or_iteration() -> None: + """Repeated snapshot members must be inert built-in lists before size checks.""" + posted = clinical_psychologist_document() + posted["tasks"] = _ExecutableList(posted["tasks"]) + + with pytest.raises(ValueError, match="tasks must be a non-empty list"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + +def test_rejects_executable_nested_source_mapping_before_field_reads() -> None: + """Nested evidence objects must be exact mappings before `.get` or iteration.""" + posted = clinical_psychologist_document() + first_task = dict(posted["tasks"][0]) + first_task["source"] = _ExecutableMapping(first_task["source"]) + posted["tasks"] = [first_task, *posted["tasks"][1:]] + + with pytest.raises(ValueError, match="source must be an object"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + +def test_rejects_executable_timestamp_text_before_replace() -> None: + """ISO timestamp parsing must exact-gate text before `.replace` can execute.""" + posted = clinical_psychologist_document() + posted["recorded_at"] = _ExecutableText(posted["recorded_at"]) + + with pytest.raises(ValueError, match="recorded_at must be an ISO-8601 datetime"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + +def test_rejects_executable_datetime_before_timezone_behavior() -> None: + """Direct Python datetime support must not admit executable datetime subtypes.""" + posted = clinical_psychologist_document() + posted["recorded_at"] = _ExecutableDateTime( + 2026, + 8, + 18, + 5, + 0, + tzinfo=timezone.utc, + ) + + with pytest.raises(ValueError, match="recorded_at must be an ISO-8601 datetime"): + snapshot_from_document(posted, tenant_record_id=TENANT) From 106cff6e13906e93757a603ec6fd83e69c1f1a3a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:03:33 +0900 Subject: [PATCH 202/241] test(job-analysis): reject executable snapshot field names --- ...est_snapshot_document_runtime_integrity.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py index 062737a52..b0a2f9083 100644 --- a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py @@ -42,6 +42,24 @@ def replace(self, old: str, new: str, count: int = -1) -> str: raise AssertionError("text subtype replace executed") +class _ExecutableFieldName(str): + """Trip if unknown-field validation hashes a caller-defined key before exact gating.""" + + armed: bool + + def __new__(cls, value: str): + """Create an initially inert key so the test mapping itself can be assembled.""" + instance = super().__new__(cls, value) + instance.armed = False + return instance + + def __hash__(self) -> int: + """Reject set membership after the fixture is armed.""" + if self.armed: + raise AssertionError("field-name subtype hash executed") + return str.__hash__(self) + + class _ExecutableDateTime(datetime): """Trip if timezone validation consumes a caller-defined datetime subtype.""" @@ -58,6 +76,18 @@ def test_rejects_executable_top_level_mapping_before_iteration() -> None: snapshot_from_document(posted, tenant_record_id=TENANT) +def test_rejects_executable_field_name_before_hash_or_membership() -> None: + """Exact built-in field names must be established before schema membership checks.""" + posted = clinical_psychologist_document() + value = posted.pop("analysis_record_id") + field_name = _ExecutableFieldName("analysis_record_id") + posted[field_name] = value + field_name.armed = True + + with pytest.raises(ValueError, match="field names must be exact built-in text"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + def test_rejects_executable_task_list_before_truthiness_or_iteration() -> None: """Repeated snapshot members must be inert built-in lists before size checks.""" posted = clinical_psychologist_document() From 48cb282060c9868dfc8f039d3d999feff920766d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:04:09 +0900 Subject: [PATCH 203/241] test(job-analysis): reject executable timezone providers --- ...est_snapshot_document_runtime_integrity.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py index b0a2f9083..63edef775 100644 --- a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone, tzinfo import pytest @@ -68,6 +68,18 @@ def utcoffset(self): raise AssertionError("datetime subtype utcoffset executed") +class _ExecutableTimezone(tzinfo): + """Trip if an exact datetime can delegate validation to caller-defined tzinfo.""" + + def utcoffset(self, value: datetime | None) -> timedelta: + """Reject caller-controlled UTC offset computation.""" + raise AssertionError("timezone provider utcoffset executed") + + def dst(self, value: datetime | None) -> timedelta: + """Provide the abstract method without making it usable by the parser.""" + return timedelta(0) + + def test_rejects_executable_top_level_mapping_before_iteration() -> None: """The posted document must be an inert built-in mapping before any field scan.""" posted = _ExecutableMapping(clinical_psychologist_document()) @@ -131,3 +143,19 @@ def test_rejects_executable_datetime_before_timezone_behavior() -> None: with pytest.raises(ValueError, match="recorded_at must be an ISO-8601 datetime"): snapshot_from_document(posted, tenant_record_id=TENANT) + + +def test_rejects_executable_timezone_provider_before_utcoffset() -> None: + """An exact datetime must not delegate trust validation to caller-defined tzinfo.""" + posted = clinical_psychologist_document() + posted["recorded_at"] = datetime( + 2026, + 8, + 18, + 5, + 0, + tzinfo=_ExecutableTimezone(), + ) + + with pytest.raises(ValueError, match="recorded_at must use a fixed UTC offset"): + snapshot_from_document(posted, tenant_record_id=TENANT) From aa72ca14eb6069570de4ab62083e373a9d99b244 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:08:24 +0900 Subject: [PATCH 204/241] fix(job-analysis): exact-gate posted snapshot runtime types --- .../src/orgmetra_job_analysis_api/snapshot.py | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index e036999e5..4976067f1 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -122,7 +122,9 @@ def _reject_unknown_fields( allowed_fields: frozenset[str], ) -> None: """Reject object members that the published evidence contract does not own.""" - unknown_fields = sorted(str(key) for key in value if key not in allowed_fields) + if any(type(key) is not str for key in value): + raise ValueError(f"{boundary_name} field names must be exact built-in text.") + unknown_fields = sorted(key for key in value if key not in allowed_fields) if unknown_fields: raise ValueError( f"{boundary_name} contains unsupported fields: {', '.join(unknown_fields)}." @@ -150,9 +152,9 @@ def _validate_idempotency_key(value: object) -> str: def _parse_uuid(field_name: str, value: object) -> UUID: """Parse one posted UUID string or reject a non-operational identity.""" - if isinstance(value, UUID): + if type(value) is UUID: return validate_operational_uuid(field_name, value) - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be a UUID string.") try: parsed = UUID(value) @@ -163,11 +165,15 @@ def _parse_uuid(field_name: str, value: object) -> UUID: def _parse_aware_datetime(field_name: str, value: object) -> datetime: """Parse one posted UTC instant used as evidence time.""" - if isinstance(value, datetime): - if value.tzinfo is None or value.utcoffset() is None: + if type(value) is datetime: + if value.tzinfo is None: + raise ValueError(f"{field_name} must be timezone-aware.") + if type(value.tzinfo) is not timezone: + raise ValueError(f"{field_name} must use a fixed UTC offset.") + if value.utcoffset() is None: raise ValueError(f"{field_name} must be timezone-aware.") return value - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be an ISO-8601 datetime.") normalized = value.replace("Z", "+00:00") try: @@ -181,11 +187,11 @@ def _parse_aware_datetime(field_name: str, value: object) -> datetime: def _parse_business_date(field_name: str, value: object) -> date: """Parse one posted business date without accepting a datetime.""" - if isinstance(value, datetime): + if type(value) is datetime: raise ValueError(f"{field_name} must be a date.") - if isinstance(value, date): + if type(value) is date: return value - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be an ISO business date.") try: return date.fromisoformat(value) @@ -195,7 +201,7 @@ def _parse_business_date(field_name: str, value: object) -> date: def _parse_source(value: object) -> EvidenceSource: """Rebuild one evidence source from posted provenance fields.""" - if not isinstance(value, dict): + if type(value) is not dict: raise ValueError("source must be an object.") _reject_unknown_fields("source", value, _SOURCE_FIELDS) return EvidenceSource( @@ -218,7 +224,7 @@ def snapshot_from_document( The posted tenant must match the authorized route tenant. Kernel constructors then enforce linkage completeness, provenance, and review governance. """ - if not isinstance(document, dict): + if type(document) is not dict: raise ValueError("snapshot document must be an object.") _reject_unknown_fields("snapshot document", document, _SNAPSHOT_FIELDS) posted_tenant = _parse_uuid("tenant_record_id", document.get("tenant_record_id")) @@ -229,26 +235,26 @@ def snapshot_from_document( raw_ksaos = document.get("ksao_requirements") raw_links = document.get("task_ksao_links") raw_fja = document.get("fja_profile") - if not isinstance(raw_tasks, list) or not raw_tasks: + if type(raw_tasks) is not list or not raw_tasks: raise ValueError("tasks must be a non-empty list.") if len(raw_tasks) > _MAX_TASKS: raise ValueError(f"tasks must contain at most {_MAX_TASKS} items.") - if not isinstance(raw_ksaos, list) or not raw_ksaos: + if type(raw_ksaos) is not list or not raw_ksaos: raise ValueError("ksao_requirements must be a non-empty list.") if len(raw_ksaos) > _MAX_KSAOS: raise ValueError(f"ksao_requirements must contain at most {_MAX_KSAOS} items.") - if not isinstance(raw_links, list) or not raw_links: + if type(raw_links) is not list or not raw_links: raise ValueError("task_ksao_links must be a non-empty list.") if len(raw_links) > _MAX_TASK_KSAO_LINKS: raise ValueError( f"task_ksao_links must contain at most {_MAX_TASK_KSAO_LINKS} items." ) - if not isinstance(raw_fja, dict): + if type(raw_fja) is not dict: raise ValueError("fja_profile must be an object.") _reject_unknown_fields("fja_profile", raw_fja, _FJA_FIELDS) tasks = [] for item in raw_tasks: - if not isinstance(item, dict): + if type(item) is not dict: raise ValueError("tasks must contain objects.") _reject_unknown_fields("task", item, _TASK_FIELDS) tasks.append( @@ -264,7 +270,7 @@ def snapshot_from_document( ) ksaos = [] for item in raw_ksaos: - if not isinstance(item, dict): + if type(item) is not dict: raise ValueError("ksao_requirements must contain objects.") _reject_unknown_fields("ksao_requirement", item, _KSAO_FIELDS) ksaos.append( @@ -281,7 +287,7 @@ def snapshot_from_document( ) links = [] for item in raw_links: - if not isinstance(item, dict): + if type(item) is not dict: raise ValueError("task_ksao_links must contain objects.") _reject_unknown_fields("task_ksao_link", item, _TASK_KSAO_LINK_FIELDS) links.append( From 8b8ee60bd157f1de3a68a4d4c6d49c29566c2815 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:10:00 +0900 Subject: [PATCH 205/241] test(job-analysis): reject executable snapshot scalar leaves --- ...est_snapshot_document_runtime_integrity.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py index 63edef775..bb4225c5e 100644 --- a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py @@ -35,12 +35,16 @@ def __iter__(self): class _ExecutableText(str): - """Trip if text normalization executes caller-defined string behavior.""" + """Trip if validation executes caller-defined string behavior.""" def replace(self, old: str, new: str, count: int = -1) -> str: """Reject ISO timestamp normalization before exact text validation.""" raise AssertionError("text subtype replace executed") + def split(self, sep: str | None = None, maxsplit: int = -1) -> list[str]: + """Reject kernel text normalization before exact leaf validation.""" + raise AssertionError("text subtype split executed") + class _ExecutableFieldName(str): """Trip if unknown-field validation hashes a caller-defined key before exact gating.""" @@ -60,6 +64,18 @@ def __hash__(self) -> int: return str.__hash__(self) +class _ExecutableInteger(int): + """Trip if kernel ordinal validation compares a caller-defined integer subtype.""" + + def __ge__(self, other: object) -> bool: + """Reject lower-bound comparison before exact integer validation.""" + raise AssertionError("integer subtype comparison executed") + + def __le__(self, other: object) -> bool: + """Reject upper-bound comparison before exact integer validation.""" + raise AssertionError("integer subtype comparison executed") + + class _ExecutableDateTime(datetime): """Trip if timezone validation consumes a caller-defined datetime subtype.""" @@ -120,6 +136,28 @@ def test_rejects_executable_nested_source_mapping_before_field_reads() -> None: snapshot_from_document(posted, tenant_record_id=TENANT) +def test_rejects_executable_task_text_before_kernel_normalization() -> None: + """Leaf text must be exact built-in text before kernel `.split` normalization.""" + posted = clinical_psychologist_document() + first_task = dict(posted["tasks"][0]) + first_task["task_statement"] = _ExecutableText(first_task["task_statement"]) + posted["tasks"] = [first_task, *posted["tasks"][1:]] + + with pytest.raises(ValueError, match="task_statement must be exact built-in text"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + +def test_rejects_executable_rating_before_kernel_comparison() -> None: + """Ordinal ratings must be exact built-in integers before range comparison.""" + posted = clinical_psychologist_document() + first_task = dict(posted["tasks"][0]) + first_task["importance_level"] = _ExecutableInteger(first_task["importance_level"]) + posted["tasks"] = [first_task, *posted["tasks"][1:]] + + with pytest.raises(ValueError, match="importance_level must be an exact built-in integer"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + def test_rejects_executable_timestamp_text_before_replace() -> None: """ISO timestamp parsing must exact-gate text before `.replace` can execute.""" posted = clinical_psychologist_document() From e0814b8b461bdc91828bc59580a8dc96d46bfdca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:11:05 +0900 Subject: [PATCH 206/241] fix(job-analysis): exact-gate snapshot scalar evidence --- .../src/orgmetra_job_analysis_api/snapshot.py | 104 ++++++++++++++---- 1 file changed, 84 insertions(+), 20 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 4976067f1..12487d2ec 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -131,6 +131,27 @@ def _reject_unknown_fields( ) +def _exact_text(field_name: str, value: object) -> str: + """Return inert caller text before domain validators may normalize or match it.""" + if type(value) is not str: + raise ValueError(f"{field_name} must be exact built-in text.") + return value + + +def _exact_integer(field_name: str, value: object) -> int: + """Return an inert integer before domain validators perform ordinal comparisons.""" + if type(value) is not int: + raise ValueError(f"{field_name} must be an exact built-in integer.") + return value + + +def _exact_boolean(field_name: str, value: object) -> bool: + """Return an exact boolean for a trust-bearing posted flag.""" + if type(value) is not bool: + raise ValueError(f"{field_name} must be an exact built-in boolean.") + return value + + def validate_operational_uuid(field_name: str, value: object) -> UUID: """Return a detached exact UUID after validating operational identity evidence.""" if type(value) is not UUID: @@ -205,12 +226,18 @@ def _parse_source(value: object) -> EvidenceSource: raise ValueError("source must be an object.") _reject_unknown_fields("source", value, _SOURCE_FIELDS) return EvidenceSource( - source_uri=value.get("source_uri"), - source_title=value.get("source_title"), - source_version_code=value.get("source_version_code"), + source_uri=_exact_text("source_uri", value.get("source_uri")), + source_title=_exact_text("source_title", value.get("source_title")), + source_version_code=_exact_text( + "source_version_code", + value.get("source_version_code"), + ), retrieved_at=_parse_aware_datetime("retrieved_at", value.get("retrieved_at")), - content_digest_sha256=value.get("content_digest_sha256"), - origin_code=value.get("origin_code"), + content_digest_sha256=_exact_text( + "content_digest_sha256", + value.get("content_digest_sha256"), + ), + origin_code=_exact_text("origin_code", value.get("origin_code")), ) @@ -262,9 +289,15 @@ def snapshot_from_document( tenant_record_id=tenant_record_id, job_record_id=job_record_id, task_record_id=_parse_uuid("task_record_id", item.get("task_record_id")), - task_statement=item.get("task_statement"), - importance_level=item.get("importance_level"), - difficulty_level=item.get("difficulty_level"), + task_statement=_exact_text("task_statement", item.get("task_statement")), + importance_level=_exact_integer( + "importance_level", + item.get("importance_level"), + ), + difficulty_level=_exact_integer( + "difficulty_level", + item.get("difficulty_level"), + ), source=_parse_source(item.get("source")), ) ) @@ -278,10 +311,19 @@ def snapshot_from_document( tenant_record_id=tenant_record_id, job_record_id=job_record_id, ksao_record_id=_parse_uuid("ksao_record_id", item.get("ksao_record_id")), - category_code=item.get("category_code"), - requirement_statement=item.get("requirement_statement"), - importance_level=item.get("importance_level"), - proficiency_level=item.get("proficiency_level"), + category_code=_exact_text("category_code", item.get("category_code")), + requirement_statement=_exact_text( + "requirement_statement", + item.get("requirement_statement"), + ), + importance_level=_exact_integer( + "importance_level", + item.get("importance_level"), + ), + proficiency_level=_exact_integer( + "proficiency_level", + item.get("proficiency_level"), + ), source=_parse_source(item.get("source")), ) ) @@ -294,8 +336,14 @@ def snapshot_from_document( TaskKSAOLink( task_record_id=_parse_uuid("task_record_id", item.get("task_record_id")), ksao_record_id=_parse_uuid("ksao_record_id", item.get("ksao_record_id")), - relationship_strength=item.get("relationship_strength"), - essential_for_task=item.get("essential_for_task"), + relationship_strength=_exact_integer( + "relationship_strength", + item.get("relationship_strength"), + ), + essential_for_task=_exact_boolean( + "essential_for_task", + item.get("essential_for_task"), + ), ) ) reviewed_by_reference = document.get("reviewed_by_reference") @@ -304,8 +352,11 @@ def snapshot_from_document( analysis_record_id=_parse_uuid("analysis_record_id", document.get("analysis_record_id")), tenant_record_id=tenant_record_id, job_record_id=job_record_id, - analysis_version_code=document.get("analysis_version_code"), - status_code=document.get("status_code"), + analysis_version_code=_exact_text( + "analysis_version_code", + document.get("analysis_version_code"), + ), + status_code=_exact_text("status_code", document.get("status_code")), effective_from=_parse_business_date("effective_from", document.get("effective_from")), recorded_at=_parse_aware_datetime("recorded_at", document.get("recorded_at")), tasks=tuple(tasks), @@ -314,12 +365,25 @@ def snapshot_from_document( fja_profile=FunctionalJobAnalysisProfile( tenant_record_id=tenant_record_id, job_record_id=job_record_id, - data_function_code=raw_fja.get("data_function_code"), - people_function_code=raw_fja.get("people_function_code"), - things_function_code=raw_fja.get("things_function_code"), + data_function_code=_exact_integer( + "data_function_code", + raw_fja.get("data_function_code"), + ), + people_function_code=_exact_integer( + "people_function_code", + raw_fja.get("people_function_code"), + ), + things_function_code=_exact_integer( + "things_function_code", + raw_fja.get("things_function_code"), + ), source=_parse_source(raw_fja.get("source")), ), - reviewed_by_reference=reviewed_by_reference, + reviewed_by_reference=( + None + if reviewed_by_reference is None + else _exact_text("reviewed_by_reference", reviewed_by_reference) + ), reviewed_at=None if reviewed_at is None else _parse_aware_datetime("reviewed_at", reviewed_at), ) From e2cb31e6ec9ea5986511a5e69076c34a09faf131 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:12:24 +0900 Subject: [PATCH 207/241] test(job-analysis): cover exact boolean snapshot flag --- .../tests/test_snapshot_document_runtime_integrity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py index bb4225c5e..87b6409cf 100644 --- a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py @@ -158,6 +158,17 @@ def test_rejects_executable_rating_before_kernel_comparison() -> None: snapshot_from_document(posted, tenant_record_id=TENANT) +def test_rejects_non_boolean_essential_flag_at_request_boundary() -> None: + """Boolean relationship flags must not reuse the integer acceptance surface.""" + posted = clinical_psychologist_document() + first_link = dict(posted["task_ksao_links"][0]) + first_link["essential_for_task"] = 1 + posted["task_ksao_links"] = [first_link, *posted["task_ksao_links"][1:]] + + with pytest.raises(ValueError, match="essential_for_task must be an exact built-in boolean"): + snapshot_from_document(posted, tenant_record_id=TENANT) + + def test_rejects_executable_timestamp_text_before_replace() -> None: """ISO timestamp parsing must exact-gate text before `.replace` can execute.""" posted = clinical_psychologist_document() From 49732436df7cd192bfbf19432d38d41a153bbb2d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:05:07 +0900 Subject: [PATCH 208/241] test(job-analysis): reject executable read target identities --- ..._read_snapshot_target_runtime_integrity.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 services/job-analysis-api/tests/test_read_snapshot_target_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_read_snapshot_target_runtime_integrity.py b/services/job-analysis-api/tests/test_read_snapshot_target_runtime_integrity.py new file mode 100644 index 000000000..ffbd180f1 --- /dev/null +++ b/services/job-analysis-api/tests/test_read_snapshot_target_runtime_integrity.py @@ -0,0 +1,88 @@ +"""Regression contract for inert Job Analysis read-target evidence.""" + +from __future__ import annotations + +import unittest +from uuid import UUID + +from orgmetra_hris_kernel import JobAnalysisSnapshot +from orgmetra_job_analysis_api.snapshot import ( + JobAnalysisIntegrityError, + read_job_analysis_snapshot, +) + +from fixtures import ( + ANALYSIS, + TENANT, + clinical_psychologist_snapshot, + read_policy, + read_principal, +) + + +class _ExecutableUUID(UUID): + """Raise if a returned durable identity executes before exact validation.""" + + def __eq__(self, other: object) -> bool: + """Fail if target matching invokes caller-controlled equality.""" + raise AssertionError("returned UUID equality executed") + + def __ne__(self, other: object) -> bool: + """Fail if target matching invokes caller-controlled inequality.""" + raise AssertionError("returned UUID inequality executed") + + def __str__(self) -> str: + """Fail if export stringifies caller-controlled identity evidence.""" + raise AssertionError("returned UUID stringification executed") + + +class _ReadPort: + """Return one configured exact snapshot from the application read boundary.""" + + def __init__(self, snapshot: JobAnalysisSnapshot) -> None: + self.snapshot = snapshot + + def read_snapshot( + self, + *, + tenant_record_id: UUID, + analysis_record_id: UUID, + ) -> JobAnalysisSnapshot: + """Return the configured snapshot without normalizing its live fields.""" + return self.snapshot + + +class ReadSnapshotTargetRuntimeIntegrityTests(unittest.TestCase): + """Reject executable returned target identity before equality or export.""" + + def test_returned_target_uuid_subtype_fails_before_runtime_hooks(self) -> None: + """Treat low-level rewritten tenant/analysis identities as corrupt evidence.""" + cases = ( + ("tenant_record_id", TENANT), + ("analysis_record_id", ANALYSIS), + ) + for field_name, authorized_value in cases: + with self.subTest(field_name=field_name): + snapshot = clinical_psychologist_snapshot() + object.__setattr__( + snapshot, + field_name, + _ExecutableUUID(str(authorized_value)), + ) + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "resolved snapshot target identity", + ): + read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=ANALYSIS, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=_ReadPort(snapshot), + ) + + +if __name__ == "__main__": + unittest.main() From e1f483cf9a73eca7afd81abfe1351d00901ce0de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:06:43 +0900 Subject: [PATCH 209/241] fix(job-analysis): validate returned snapshot target identities --- .../src/orgmetra_job_analysis_api/snapshot.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 12487d2ec..35c7a71c4 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -563,12 +563,23 @@ def read_job_analysis_snapshot( raise JobAnalysisSnapshotNotFound("job-analysis snapshot is unavailable") if type(snapshot) is not JobAnalysisSnapshot: raise JobAnalysisIntegrityError("resolved snapshot has an invalid runtime type") + try: + resolved_tenant_record_id = validate_operational_uuid( + "resolved snapshot tenant_record_id", + snapshot.tenant_record_id, + ) + resolved_analysis_record_id = validate_operational_uuid( + "resolved snapshot analysis_record_id", + snapshot.analysis_record_id, + ) + except ValueError as error: + raise JobAnalysisIntegrityError("resolved snapshot target identity is invalid") from error if ( - snapshot.tenant_record_id != tenant_record_id - or snapshot.analysis_record_id != analysis_record_id + resolved_tenant_record_id != tenant_record_id + or resolved_analysis_record_id != analysis_record_id ): raise JobAnalysisIntegrityError("resolved snapshot does not match authorized target") return PersistedJobAnalysisView( resource_reference=decision.resource_reference, snapshot=snapshot.to_snapshot(), - ) + ) \ No newline at end of file From 554ed9de9ebbd36d0443bf0317bbe657e39f242e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:06:20 +0900 Subject: [PATCH 210/241] test(job-analysis): reject executable returned snapshot graph --- ...t_read_snapshot_graph_runtime_integrity.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 services/job-analysis-api/tests/test_read_snapshot_graph_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_read_snapshot_graph_runtime_integrity.py b/services/job-analysis-api/tests/test_read_snapshot_graph_runtime_integrity.py new file mode 100644 index 000000000..c5bef87dc --- /dev/null +++ b/services/job-analysis-api/tests/test_read_snapshot_graph_runtime_integrity.py @@ -0,0 +1,158 @@ +"""Regression contract for inert Job Analysis read-result graphs.""" + +from __future__ import annotations + +from datetime import datetime, timedelta, tzinfo +import unittest +from uuid import UUID + +from orgmetra_hris_kernel import JobAnalysisSnapshot +from orgmetra_job_analysis_api.snapshot import ( + JobAnalysisIntegrityError, + read_job_analysis_snapshot, +) + +from fixtures import ( + ANALYSIS, + JOB, + TENANT, + clinical_psychologist_snapshot, + read_policy, + read_principal, +) + + +class _ExecutableUUID(UUID): + """Raise if response export stringifies caller-controlled identity evidence.""" + + def __str__(self) -> str: + """Fail if a low-level rewritten UUID reaches snapshot export.""" + raise AssertionError("returned snapshot UUID stringification executed") + + +class _ExecutableTuple(tuple[object, ...]): + """Raise if response export iterates a caller-controlled collection.""" + + def __iter__(self): # type: ignore[override] + """Fail if a low-level rewritten collection reaches sorting/export.""" + raise AssertionError("returned snapshot collection iteration executed") + + +class _ExecutableText(str): + """Represent non-inert text that must not cross the read-result boundary.""" + + +class _ExecutableTimezone(tzinfo): + """Raise if datetime canonicalization delegates to caller-controlled timezone code.""" + + def utcoffset(self, dt: datetime | None) -> timedelta: + """Fail if response export asks the hostile timezone for an offset.""" + raise AssertionError("returned snapshot timezone offset executed") + + def dst(self, dt: datetime | None) -> timedelta: + """Return a nominal DST value; export must not call this implementation.""" + return timedelta(0) + + def tzname(self, dt: datetime | None) -> str: + """Return a nominal name; export must reject the timezone before use.""" + return "EXECUTABLE" + + +class _ReadPort: + """Return one configured exact snapshot without normalizing live fields.""" + + def __init__(self, snapshot: JobAnalysisSnapshot) -> None: + self.snapshot = snapshot + + def read_snapshot( + self, + *, + tenant_record_id: UUID, + analysis_record_id: UUID, + ) -> JobAnalysisSnapshot: + """Return the configured snapshot exactly as supplied by the test.""" + return self.snapshot + + +def _read(snapshot: JobAnalysisSnapshot) -> None: + """Execute the governed read path for one adversarial returned snapshot.""" + read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=ANALYSIS, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=_ReadPort(snapshot), + ) + + +class ReadSnapshotGraphRuntimeIntegrityTests(unittest.TestCase): + """Reject executable non-target graph evidence before customer export.""" + + def test_root_job_identity_is_exact_gated_before_stringification(self) -> None: + """Reject an executable Job UUID even when route target identities are valid.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__(snapshot, "job_record_id", _ExecutableUUID(str(JOB))) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "resolved snapshot graph"): + _read(snapshot) + + def test_task_collection_is_exact_gated_before_iteration(self) -> None: + """Reject an executable tuple before sorting the returned Task evidence.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__(snapshot, "tasks", _ExecutableTuple(snapshot.tasks)) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "resolved snapshot graph"): + _read(snapshot) + + def test_nested_task_identity_is_exact_gated_before_sort_key_stringification(self) -> None: + """Reject executable nested UUID evidence before deterministic ordering.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__( + snapshot.tasks[0], + "task_record_id", + _ExecutableUUID(str(snapshot.tasks[0].task_record_id)), + ) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "resolved snapshot graph"): + _read(snapshot) + + def test_root_text_is_exact_gated_before_response_export(self) -> None: + """Reject a text subtype even if exporting it would not call an override yet.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__( + snapshot, + "analysis_version_code", + _ExecutableText(snapshot.analysis_version_code), + ) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "resolved snapshot graph"): + _read(snapshot) + + def test_datetime_timezone_is_exact_gated_before_canonicalization(self) -> None: + """Reject caller-controlled timezone behavior before `_utc_text` can execute it.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__( + snapshot, + "recorded_at", + datetime(2026, 8, 18, 5, 0, tzinfo=_ExecutableTimezone()), + ) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "resolved snapshot graph"): + _read(snapshot) + + def test_nested_source_timezone_is_exact_gated_before_canonicalization(self) -> None: + """Reject executable provenance time below a Task before source export.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__( + snapshot.tasks[0].source, + "retrieved_at", + datetime(2026, 8, 18, 3, 0, tzinfo=_ExecutableTimezone()), + ) + + with self.assertRaisesRegex(JobAnalysisIntegrityError, "resolved snapshot graph"): + _read(snapshot) + + +if __name__ == "__main__": + unittest.main() From 3d8cca28463948c28dcef8aa9457a55366be9868 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:08:31 +0900 Subject: [PATCH 211/241] fix(job-analysis): validate returned snapshot graph before export --- .../src/orgmetra_job_analysis_api/snapshot.py | 177 +++++++++++++++++- 1 file changed, 175 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 35c7a71c4..87723c0e8 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -413,6 +413,162 @@ def _optional_scope_id(field_name: str, value: object) -> UUID | None: return _parse_uuid(field_name, value) +def _resolved_exact(field_name: str, value: object, expected_type: type[object]) -> object: + """Require one inert exact runtime value returned by a snapshot read adapter.""" + if type(value) is not expected_type: + raise JobAnalysisIntegrityError( + f"resolved snapshot graph has invalid runtime evidence at {field_name}" + ) + return value + + +def _resolved_uuid(field_name: str, value: object) -> UUID: + """Require one operational exact UUID before snapshot export can stringify it.""" + try: + return validate_operational_uuid(field_name, value) + except ValueError as error: + raise JobAnalysisIntegrityError( + f"resolved snapshot graph has invalid runtime evidence at {field_name}" + ) from error + + +def _resolved_datetime(field_name: str, value: object) -> datetime: + """Require an exact fixed-offset datetime before `_utc_text` can execute it.""" + resolved = _resolved_exact(field_name, value, datetime) + assert type(resolved) is datetime + if type(resolved.tzinfo) is not timezone: + raise JobAnalysisIntegrityError( + f"resolved snapshot graph has invalid runtime evidence at {field_name}" + ) + return resolved + + +def _validate_resolved_source_runtime(field_name: str, value: object) -> None: + """Validate one returned provenance object before source-document export.""" + source = _resolved_exact(field_name, value, EvidenceSource) + assert type(source) is EvidenceSource + for attribute in ( + "source_uri", + "source_title", + "source_version_code", + "content_digest_sha256", + "origin_code", + ): + _resolved_exact(f"{field_name}.{attribute}", getattr(source, attribute), str) + _resolved_datetime(f"{field_name}.retrieved_at", source.retrieved_at) + + +def _validate_resolved_snapshot_graph_runtime(snapshot: JobAnalysisSnapshot) -> None: + """Prove every live value consumed by ``to_snapshot`` is inert before export.""" + _resolved_uuid("job_record_id", snapshot.job_record_id) + _resolved_exact("analysis_version_code", snapshot.analysis_version_code, str) + _resolved_exact("status_code", snapshot.status_code, str) + _resolved_exact("effective_from", snapshot.effective_from, date) + _resolved_datetime("recorded_at", snapshot.recorded_at) + + tasks = _resolved_exact("tasks", snapshot.tasks, tuple) + ksaos = _resolved_exact("ksao_requirements", snapshot.ksao_requirements, tuple) + links = _resolved_exact("task_ksao_links", snapshot.task_ksao_links, tuple) + assert type(tasks) is tuple + assert type(ksaos) is tuple + assert type(links) is tuple + + for index, task_value in enumerate(tasks): + task = _resolved_exact(f"tasks[{index}]", task_value, TaskEvidence) + assert type(task) is TaskEvidence + _resolved_uuid(f"tasks[{index}].tenant_record_id", task.tenant_record_id) + _resolved_uuid(f"tasks[{index}].job_record_id", task.job_record_id) + _resolved_uuid(f"tasks[{index}].task_record_id", task.task_record_id) + _resolved_exact(f"tasks[{index}].task_statement", task.task_statement, str) + _resolved_exact(f"tasks[{index}].importance_level", task.importance_level, int) + _resolved_exact(f"tasks[{index}].difficulty_level", task.difficulty_level, int) + _validate_resolved_source_runtime(f"tasks[{index}].source", task.source) + + for index, ksao_value in enumerate(ksaos): + ksao = _resolved_exact( + f"ksao_requirements[{index}]", + ksao_value, + KSAORequirement, + ) + assert type(ksao) is KSAORequirement + _resolved_uuid( + f"ksao_requirements[{index}].tenant_record_id", + ksao.tenant_record_id, + ) + _resolved_uuid( + f"ksao_requirements[{index}].job_record_id", + ksao.job_record_id, + ) + _resolved_uuid( + f"ksao_requirements[{index}].ksao_record_id", + ksao.ksao_record_id, + ) + _resolved_exact( + f"ksao_requirements[{index}].category_code", + ksao.category_code, + str, + ) + _resolved_exact( + f"ksao_requirements[{index}].requirement_statement", + ksao.requirement_statement, + str, + ) + _resolved_exact( + f"ksao_requirements[{index}].importance_level", + ksao.importance_level, + int, + ) + _resolved_exact( + f"ksao_requirements[{index}].proficiency_level", + ksao.proficiency_level, + int, + ) + _validate_resolved_source_runtime( + f"ksao_requirements[{index}].source", + ksao.source, + ) + + for index, link_value in enumerate(links): + link = _resolved_exact( + f"task_ksao_links[{index}]", + link_value, + TaskKSAOLink, + ) + assert type(link) is TaskKSAOLink + _resolved_uuid( + f"task_ksao_links[{index}].task_record_id", + link.task_record_id, + ) + _resolved_uuid( + f"task_ksao_links[{index}].ksao_record_id", + link.ksao_record_id, + ) + _resolved_exact( + f"task_ksao_links[{index}].relationship_strength", + link.relationship_strength, + int, + ) + _resolved_exact( + f"task_ksao_links[{index}].essential_for_task", + link.essential_for_task, + bool, + ) + + fja = _resolved_exact("fja_profile", snapshot.fja_profile, FunctionalJobAnalysisProfile) + assert type(fja) is FunctionalJobAnalysisProfile + _resolved_uuid("fja_profile.tenant_record_id", fja.tenant_record_id) + _resolved_uuid("fja_profile.job_record_id", fja.job_record_id) + _resolved_exact("fja_profile.data_function_code", fja.data_function_code, int) + _resolved_exact("fja_profile.people_function_code", fja.people_function_code, int) + _resolved_exact("fja_profile.things_function_code", fja.things_function_code, int) + _validate_resolved_source_runtime("fja_profile.source", fja.source) + + if snapshot.reviewed_by_reference is not None: + _resolved_exact("reviewed_by_reference", snapshot.reviewed_by_reference, str) + if snapshot.reviewed_at is not None: + _resolved_datetime("reviewed_at", snapshot.reviewed_at) + + @runtime_checkable class JobAnalysisWritePort(Protocol): """Persist one authorized snapshot and its Idempotency-Key in one transaction.""" @@ -579,7 +735,24 @@ def read_job_analysis_snapshot( or resolved_analysis_record_id != analysis_record_id ): raise JobAnalysisIntegrityError("resolved snapshot does not match authorized target") + + _validate_resolved_snapshot_graph_runtime(snapshot) + resolved_document = snapshot.to_snapshot() + try: + governed_snapshot = snapshot_from_document( + resolved_document, + tenant_record_id=tenant_record_id, + ) + except (TypeError, ValueError) as error: + raise JobAnalysisIntegrityError( + "resolved snapshot graph violates governed evidence contract" + ) from error + governed_document = governed_snapshot.to_snapshot() + if governed_document != resolved_document: + raise JobAnalysisIntegrityError( + "resolved snapshot graph changes under governed reconstruction" + ) return PersistedJobAnalysisView( resource_reference=decision.resource_reference, - snapshot=snapshot.to_snapshot(), - ) \ No newline at end of file + snapshot=governed_document, + ) From 9cf0ff3a41be9cdf70ac6051f6ad25bb2a62110e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:19:08 +0900 Subject: [PATCH 212/241] test(job-analysis): reject executable write-port result graph --- ...ersist_snapshot_graph_runtime_integrity.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 services/job-analysis-api/tests/test_persist_snapshot_graph_runtime_integrity.py diff --git a/services/job-analysis-api/tests/test_persist_snapshot_graph_runtime_integrity.py b/services/job-analysis-api/tests/test_persist_snapshot_graph_runtime_integrity.py new file mode 100644 index 000000000..396ae298a --- /dev/null +++ b/services/job-analysis-api/tests/test_persist_snapshot_graph_runtime_integrity.py @@ -0,0 +1,59 @@ +"""Regression contract for inert Job Analysis write-port result graphs.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import JobAnalysisSnapshot +from orgmetra_job_analysis_api.snapshot import ( + JobAnalysisIntegrityError, + persist_job_analysis_snapshot, +) + +from fixtures import ( + IDEMPOTENCY_KEY, + JOB, + TENANT, + clinical_psychologist_document, + clinical_psychologist_snapshot, + write_policy, + write_principal, +) + + +class _ExecutableUUID(UUID): + """Raise if comparison export stringifies unvalidated persistence evidence.""" + + def __str__(self) -> str: + """Fail if the write-port result reaches `to_snapshot()` before validation.""" + raise AssertionError("write-port result UUID stringification executed") + + +class _ReturningWritePort: + """Return one configured exact persistence result without normalizing it.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + + def persist_snapshot(self, **_: object) -> JobAnalysisSnapshot: + """Return the configured result exactly as a defective adapter could.""" + return self.result + + +def test_persist_result_graph_is_validated_before_comparison_export() -> None: + """Reject executable nested evidence before serializing a write-port result.""" + persisted = clinical_psychologist_snapshot() + object.__setattr__(persisted, "job_record_id", _ExecutableUUID(str(JOB))) + + with pytest.raises(JobAnalysisIntegrityError, match="persisted snapshot graph"): + persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=IDEMPOTENCY_KEY, + purpose_code="job_analysis_write", + policy=write_policy(), + write_port=_ReturningWritePort(persisted), + ) From d378a93d4153971c25fda048ae358eff8f12a4cf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:33:21 +0900 Subject: [PATCH 213/241] fix(job-analysis): validate persisted snapshot graph before export --- .../src/orgmetra_job_analysis_api/snapshot.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 87723c0e8..9e5fea67b 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -679,7 +679,41 @@ def persist_job_analysis_snapshot( ) if type(persisted) is not JobAnalysisSnapshot: raise JobAnalysisIntegrityError("persisted snapshot has an invalid runtime type") - if persisted.to_snapshot() != authorized_snapshot: + try: + persisted_tenant_record_id = validate_operational_uuid( + "persisted snapshot tenant_record_id", + persisted.tenant_record_id, + ) + persisted_analysis_record_id = validate_operational_uuid( + "persisted snapshot analysis_record_id", + persisted.analysis_record_id, + ) + _validate_resolved_snapshot_graph_runtime(persisted) + except (ValueError, JobAnalysisIntegrityError) as error: + raise JobAnalysisIntegrityError( + "persisted snapshot graph has invalid runtime evidence" + ) from error + if ( + persisted_tenant_record_id != tenant_record_id + or persisted_analysis_record_id != snapshot.analysis_record_id + ): + raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") + persisted_document = persisted.to_snapshot() + try: + governed_persisted = snapshot_from_document( + persisted_document, + tenant_record_id=tenant_record_id, + ) + except (TypeError, ValueError) as error: + raise JobAnalysisIntegrityError( + "persisted snapshot graph violates governed evidence contract" + ) from error + governed_document = governed_persisted.to_snapshot() + if governed_document != persisted_document: + raise JobAnalysisIntegrityError( + "persisted snapshot graph changes under governed reconstruction" + ) + if governed_document != authorized_snapshot: raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") return PersistedJobAnalysisView( resource_reference=decision.resource_reference, From 9109f191fbacc4b82573acbea2dc90b5eb67ebbe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:01:04 +0900 Subject: [PATCH 214/241] test(job-analysis): reproduce returned graph validation export gap --- ...apshot_returned_graph_capture_integrity.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 services/job-analysis-api/tests/test_snapshot_returned_graph_capture_integrity.py diff --git a/services/job-analysis-api/tests/test_snapshot_returned_graph_capture_integrity.py b/services/job-analysis-api/tests/test_snapshot_returned_graph_capture_integrity.py new file mode 100644 index 000000000..bb9245840 --- /dev/null +++ b/services/job-analysis-api/tests/test_snapshot_returned_graph_capture_integrity.py @@ -0,0 +1,123 @@ +"""Regression contract for binding returned snapshot validation to emitted evidence.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +import orgmetra_job_analysis_api.snapshot as snapshot_module +from orgmetra_hris_kernel import JobAnalysisSnapshot +from orgmetra_job_analysis_api.snapshot import ( + persist_job_analysis_snapshot, + read_job_analysis_snapshot, +) + +from fixtures import ( + ANALYSIS, + IDEMPOTENCY_KEY, + JOB, + TENANT, + clinical_psychologist_document, + clinical_psychologist_snapshot, + read_policy, + read_principal, + write_policy, + write_principal, +) + + +class _ExecutableUUID(UUID): + """Raise if evidence installed after validation reaches snapshot export.""" + + def __str__(self) -> str: + """Fail if a checked-versus-emitted gap rereads the mutated live graph.""" + raise AssertionError("post-validation UUID stringification executed") + + +class _ReturningWritePort: + """Return one retained exact snapshot from the persistence boundary.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + + def persist_snapshot(self, **_: object) -> JobAnalysisSnapshot: + """Return the exact retained result without normalizing it.""" + return self.result + + +class _ReadPort: + """Return one retained exact snapshot from the read boundary.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + + def read_snapshot( + self, + *, + tenant_record_id: UUID, + analysis_record_id: UUID, + ) -> JobAnalysisSnapshot: + """Return the exact retained result without normalizing it.""" + return self.result + + +def _mutate_after_runtime_validation( + monkeypatch: pytest.MonkeyPatch, + returned: JobAnalysisSnapshot, +) -> None: + """Inject a deterministic retained-adapter mutation after the validation pass.""" + original = snapshot_module._validate_resolved_snapshot_graph_runtime + + def validate_then_mutate(snapshot: JobAnalysisSnapshot): + captured = original(snapshot) + if snapshot is returned: + object.__setattr__(snapshot, "job_record_id", _ExecutableUUID(str(JOB))) + return captured + + monkeypatch.setattr( + snapshot_module, + "_validate_resolved_snapshot_graph_runtime", + validate_then_mutate, + ) + + +def test_read_uses_the_exact_graph_captured_by_runtime_validation( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Do not reread a retained read-port graph after it passed validation.""" + returned = clinical_psychologist_snapshot() + expected = returned.to_snapshot() + _mutate_after_runtime_validation(monkeypatch, returned) + + view = read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=ANALYSIS, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=_ReadPort(returned), + ) + + assert view.snapshot == expected + + +def test_write_uses_the_exact_graph_captured_by_runtime_validation( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Do not reread a retained write-port graph after it passed validation.""" + returned = clinical_psychologist_snapshot() + expected = returned.to_snapshot() + _mutate_after_runtime_validation(monkeypatch, returned) + + view = persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=IDEMPOTENCY_KEY, + purpose_code="job_analysis_write", + policy=write_policy(), + write_port=_ReturningWritePort(returned), + ) + + assert view.snapshot == expected From ea0ec9ebbce25b2ca43db7e0a52601f950fb1403 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:05:27 +0900 Subject: [PATCH 215/241] fix(job-analysis): emit captured returned snapshot evidence --- .../src/orgmetra_job_analysis_api/snapshot.py | 248 ++++++++++++++---- 1 file changed, 198 insertions(+), 50 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index 9e5fea67b..e1ae9c39e 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -433,7 +433,7 @@ def _resolved_uuid(field_name: str, value: object) -> UUID: def _resolved_datetime(field_name: str, value: object) -> datetime: - """Require an exact fixed-offset datetime before `_utc_text` can execute it.""" + """Require an exact fixed-offset datetime before canonicalization can use it.""" resolved = _resolved_exact(field_name, value, datetime) assert type(resolved) is datetime if type(resolved.tzinfo) is not timezone: @@ -443,28 +443,60 @@ def _resolved_datetime(field_name: str, value: object) -> datetime: return resolved -def _validate_resolved_source_runtime(field_name: str, value: object) -> None: - """Validate one returned provenance object before source-document export.""" +def _resolved_datetime_text(field_name: str, value: object) -> str: + """Capture one inert returned instant as the kernel's canonical UTC text.""" + resolved = _resolved_datetime(field_name, value) + return resolved.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def _validate_resolved_source_runtime( + field_name: str, + value: object, +) -> dict[str, object]: + """Capture one returned provenance object into validated inert evidence.""" source = _resolved_exact(field_name, value, EvidenceSource) assert type(source) is EvidenceSource - for attribute in ( - "source_uri", - "source_title", - "source_version_code", - "content_digest_sha256", - "origin_code", - ): - _resolved_exact(f"{field_name}.{attribute}", getattr(source, attribute), str) - _resolved_datetime(f"{field_name}.retrieved_at", source.retrieved_at) + source_uri = _resolved_exact(f"{field_name}.source_uri", source.source_uri, str) + source_title = _resolved_exact(f"{field_name}.source_title", source.source_title, str) + source_version_code = _resolved_exact( + f"{field_name}.source_version_code", + source.source_version_code, + str, + ) + retrieved_at = _resolved_datetime_text( + f"{field_name}.retrieved_at", + source.retrieved_at, + ) + content_digest_sha256 = _resolved_exact( + f"{field_name}.content_digest_sha256", + source.content_digest_sha256, + str, + ) + origin_code = _resolved_exact(f"{field_name}.origin_code", source.origin_code, str) + return { + "source_uri": source_uri, + "source_title": source_title, + "source_version_code": source_version_code, + "retrieved_at": retrieved_at, + "content_digest_sha256": content_digest_sha256, + "origin_code": origin_code, + } -def _validate_resolved_snapshot_graph_runtime(snapshot: JobAnalysisSnapshot) -> None: - """Prove every live value consumed by ``to_snapshot`` is inert before export.""" - _resolved_uuid("job_record_id", snapshot.job_record_id) - _resolved_exact("analysis_version_code", snapshot.analysis_version_code, str) - _resolved_exact("status_code", snapshot.status_code, str) - _resolved_exact("effective_from", snapshot.effective_from, date) - _resolved_datetime("recorded_at", snapshot.recorded_at) +def _validate_resolved_snapshot_graph_runtime( + snapshot: JobAnalysisSnapshot, +) -> dict[str, object]: + """Capture every validated non-target field into the exact document to emit.""" + job_record_id = _resolved_uuid("job_record_id", snapshot.job_record_id) + analysis_version_code = _resolved_exact( + "analysis_version_code", + snapshot.analysis_version_code, + str, + ) + status_code = _resolved_exact("status_code", snapshot.status_code, str) + effective_from = _resolved_exact("effective_from", snapshot.effective_from, date) + assert type(effective_from) is date + recorded_at = _resolved_datetime_text("recorded_at", snapshot.recorded_at) tasks = _resolved_exact("tasks", snapshot.tasks, tuple) ksaos = _resolved_exact("ksao_requirements", snapshot.ksao_requirements, tuple) @@ -473,17 +505,49 @@ def _validate_resolved_snapshot_graph_runtime(snapshot: JobAnalysisSnapshot) -> assert type(ksaos) is tuple assert type(links) is tuple + task_entries: list[tuple[str, dict[str, object]]] = [] for index, task_value in enumerate(tasks): task = _resolved_exact(f"tasks[{index}]", task_value, TaskEvidence) assert type(task) is TaskEvidence _resolved_uuid(f"tasks[{index}].tenant_record_id", task.tenant_record_id) _resolved_uuid(f"tasks[{index}].job_record_id", task.job_record_id) - _resolved_uuid(f"tasks[{index}].task_record_id", task.task_record_id) - _resolved_exact(f"tasks[{index}].task_statement", task.task_statement, str) - _resolved_exact(f"tasks[{index}].importance_level", task.importance_level, int) - _resolved_exact(f"tasks[{index}].difficulty_level", task.difficulty_level, int) - _validate_resolved_source_runtime(f"tasks[{index}].source", task.source) + task_record_id = _resolved_uuid( + f"tasks[{index}].task_record_id", + task.task_record_id, + ) + task_statement = _resolved_exact( + f"tasks[{index}].task_statement", + task.task_statement, + str, + ) + importance_level = _resolved_exact( + f"tasks[{index}].importance_level", + task.importance_level, + int, + ) + difficulty_level = _resolved_exact( + f"tasks[{index}].difficulty_level", + task.difficulty_level, + int, + ) + task_record_id_text = str(task_record_id) + task_entries.append( + ( + task_record_id_text, + { + "task_record_id": task_record_id_text, + "task_statement": task_statement, + "importance_level": importance_level, + "difficulty_level": difficulty_level, + "source": _validate_resolved_source_runtime( + f"tasks[{index}].source", + task.source, + ), + }, + ) + ) + ksao_entries: list[tuple[str, dict[str, object]]] = [] for index, ksao_value in enumerate(ksaos): ksao = _resolved_exact( f"ksao_requirements[{index}]", @@ -499,35 +563,49 @@ def _validate_resolved_snapshot_graph_runtime(snapshot: JobAnalysisSnapshot) -> f"ksao_requirements[{index}].job_record_id", ksao.job_record_id, ) - _resolved_uuid( + ksao_record_id = _resolved_uuid( f"ksao_requirements[{index}].ksao_record_id", ksao.ksao_record_id, ) - _resolved_exact( + category_code = _resolved_exact( f"ksao_requirements[{index}].category_code", ksao.category_code, str, ) - _resolved_exact( + requirement_statement = _resolved_exact( f"ksao_requirements[{index}].requirement_statement", ksao.requirement_statement, str, ) - _resolved_exact( + importance_level = _resolved_exact( f"ksao_requirements[{index}].importance_level", ksao.importance_level, int, ) - _resolved_exact( + proficiency_level = _resolved_exact( f"ksao_requirements[{index}].proficiency_level", ksao.proficiency_level, int, ) - _validate_resolved_source_runtime( - f"ksao_requirements[{index}].source", - ksao.source, + ksao_record_id_text = str(ksao_record_id) + ksao_entries.append( + ( + ksao_record_id_text, + { + "ksao_record_id": ksao_record_id_text, + "category_code": category_code, + "requirement_statement": requirement_statement, + "importance_level": importance_level, + "proficiency_level": proficiency_level, + "source": _validate_resolved_source_runtime( + f"ksao_requirements[{index}].source", + ksao.source, + ), + }, + ) ) + link_entries: list[tuple[tuple[str, str], dict[str, object]]] = [] for index, link_value in enumerate(links): link = _resolved_exact( f"task_ksao_links[{index}]", @@ -535,38 +613,95 @@ def _validate_resolved_snapshot_graph_runtime(snapshot: JobAnalysisSnapshot) -> TaskKSAOLink, ) assert type(link) is TaskKSAOLink - _resolved_uuid( + task_record_id = _resolved_uuid( f"task_ksao_links[{index}].task_record_id", link.task_record_id, ) - _resolved_uuid( + ksao_record_id = _resolved_uuid( f"task_ksao_links[{index}].ksao_record_id", link.ksao_record_id, ) - _resolved_exact( + relationship_strength = _resolved_exact( f"task_ksao_links[{index}].relationship_strength", link.relationship_strength, int, ) - _resolved_exact( + essential_for_task = _resolved_exact( f"task_ksao_links[{index}].essential_for_task", link.essential_for_task, bool, ) + task_record_id_text = str(task_record_id) + ksao_record_id_text = str(ksao_record_id) + link_entries.append( + ( + (task_record_id_text, ksao_record_id_text), + { + "task_record_id": task_record_id_text, + "ksao_record_id": ksao_record_id_text, + "relationship_strength": relationship_strength, + "essential_for_task": essential_for_task, + }, + ) + ) fja = _resolved_exact("fja_profile", snapshot.fja_profile, FunctionalJobAnalysisProfile) assert type(fja) is FunctionalJobAnalysisProfile _resolved_uuid("fja_profile.tenant_record_id", fja.tenant_record_id) _resolved_uuid("fja_profile.job_record_id", fja.job_record_id) - _resolved_exact("fja_profile.data_function_code", fja.data_function_code, int) - _resolved_exact("fja_profile.people_function_code", fja.people_function_code, int) - _resolved_exact("fja_profile.things_function_code", fja.things_function_code, int) - _validate_resolved_source_runtime("fja_profile.source", fja.source) - - if snapshot.reviewed_by_reference is not None: - _resolved_exact("reviewed_by_reference", snapshot.reviewed_by_reference, str) - if snapshot.reviewed_at is not None: - _resolved_datetime("reviewed_at", snapshot.reviewed_at) + data_function_code = _resolved_exact( + "fja_profile.data_function_code", + fja.data_function_code, + int, + ) + people_function_code = _resolved_exact( + "fja_profile.people_function_code", + fja.people_function_code, + int, + ) + things_function_code = _resolved_exact( + "fja_profile.things_function_code", + fja.things_function_code, + int, + ) + fja_source = _validate_resolved_source_runtime("fja_profile.source", fja.source) + + reviewed_by_reference = snapshot.reviewed_by_reference + reviewed_at = snapshot.reviewed_at + document: dict[str, object] = { + "job_record_id": str(job_record_id), + "analysis_version_code": analysis_version_code, + "status_code": status_code, + "effective_from": effective_from.isoformat(), + "recorded_at": recorded_at, + "tasks": [ + item_document + for _, item_document in sorted(task_entries, key=lambda item: item[0]) + ], + "ksao_requirements": [ + item_document + for _, item_document in sorted(ksao_entries, key=lambda item: item[0]) + ], + "task_ksao_links": [ + item_document + for _, item_document in sorted(link_entries, key=lambda item: item[0]) + ], + "fja_profile": { + "data_function_code": data_function_code, + "people_function_code": people_function_code, + "things_function_code": things_function_code, + "source": fja_source, + }, + } + if reviewed_by_reference is not None: + document["reviewed_by_reference"] = _resolved_exact( + "reviewed_by_reference", + reviewed_by_reference, + str, + ) + if reviewed_at is not None: + document["reviewed_at"] = _resolved_datetime_text("reviewed_at", reviewed_at) + return document @runtime_checkable @@ -688,8 +823,7 @@ def persist_job_analysis_snapshot( "persisted snapshot analysis_record_id", persisted.analysis_record_id, ) - _validate_resolved_snapshot_graph_runtime(persisted) - except (ValueError, JobAnalysisIntegrityError) as error: + except ValueError as error: raise JobAnalysisIntegrityError( "persisted snapshot graph has invalid runtime evidence" ) from error @@ -698,7 +832,17 @@ def persist_job_analysis_snapshot( or persisted_analysis_record_id != snapshot.analysis_record_id ): raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") - persisted_document = persisted.to_snapshot() + try: + persisted_graph = _validate_resolved_snapshot_graph_runtime(persisted) + except JobAnalysisIntegrityError as error: + raise JobAnalysisIntegrityError( + "persisted snapshot graph has invalid runtime evidence" + ) from error + persisted_document = { + "analysis_record_id": str(persisted_analysis_record_id), + "tenant_record_id": str(persisted_tenant_record_id), + **persisted_graph, + } try: governed_persisted = snapshot_from_document( persisted_document, @@ -770,8 +914,12 @@ def read_job_analysis_snapshot( ): raise JobAnalysisIntegrityError("resolved snapshot does not match authorized target") - _validate_resolved_snapshot_graph_runtime(snapshot) - resolved_document = snapshot.to_snapshot() + resolved_graph = _validate_resolved_snapshot_graph_runtime(snapshot) + resolved_document = { + "analysis_record_id": str(resolved_analysis_record_id), + "tenant_record_id": str(resolved_tenant_record_id), + **resolved_graph, + } try: governed_snapshot = snapshot_from_document( resolved_document, From 6aab0c90c0c31b7e0df87dba5e21ae24898f04fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:07:57 +0900 Subject: [PATCH 216/241] test(job-analysis): reproduce post-port input target alias execution --- .../test_persist_input_target_detachment.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 services/job-analysis-api/tests/test_persist_input_target_detachment.py diff --git a/services/job-analysis-api/tests/test_persist_input_target_detachment.py b/services/job-analysis-api/tests/test_persist_input_target_detachment.py new file mode 100644 index 000000000..7d55041aa --- /dev/null +++ b/services/job-analysis-api/tests/test_persist_input_target_detachment.py @@ -0,0 +1,61 @@ +"""Regression contract for detaching the posted write target before persistence.""" + +from __future__ import annotations + +from uuid import UUID + +from orgmetra_hris_kernel import JobAnalysisSnapshot +from orgmetra_job_analysis_api.snapshot import persist_job_analysis_snapshot + +from fixtures import ( + ANALYSIS, + IDEMPOTENCY_KEY, + TENANT, + clinical_psychologist_document, + clinical_psychologist_snapshot, + write_policy, + write_principal, +) + + +class _ExecutableUUID(UUID): + """Raise if post-port target equality consults adapter-mutated input evidence.""" + + def __getattribute__(self, name: str) -> object: + """Fail when exact UUID comparison asks the mutated subtype for integer state.""" + if name == "int": + raise AssertionError("post-port input target UUID state executed") + return super().__getattribute__(name) + + +class _MutatingInputWritePort: + """Mutate the supplied input alias but return separate pristine persistence evidence.""" + + def __init__(self, result: JobAnalysisSnapshot) -> None: + self.result = result + + def persist_snapshot(self, **kwargs: object) -> JobAnalysisSnapshot: + """Rewrite only the port-owned input target after all pre-port checks completed.""" + supplied = kwargs["snapshot"] + assert type(supplied) is JobAnalysisSnapshot + object.__setattr__( + supplied, + "analysis_record_id", + _ExecutableUUID(str(ANALYSIS)), + ) + return self.result + + +def test_persist_target_comparison_uses_pre_port_detached_identity() -> None: + """Never reread an input snapshot target after handing its alias to the write port.""" + view = persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=IDEMPOTENCY_KEY, + purpose_code="job_analysis_write", + policy=write_policy(), + write_port=_MutatingInputWritePort(clinical_psychologist_snapshot()), + ) + + assert view.snapshot["analysis_record_id"] == str(ANALYSIS) From 5497a9bc74f497d9b2457ffbf15a691abac3f690 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:09:25 +0900 Subject: [PATCH 217/241] fix(job-analysis): detach posted target before write port --- .../src/orgmetra_job_analysis_api/snapshot.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index e1ae9c39e..a89ba9a62 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -796,6 +796,10 @@ def persist_job_analysis_snapshot( high_impact=False, ) authorized_snapshot = snapshot.to_snapshot() + authorized_analysis_record_id = validate_operational_uuid( + "authorized snapshot analysis_record_id", + snapshot.analysis_record_id, + ) persisted = write_port.persist_snapshot( snapshot=snapshot, idempotency_key=key, @@ -829,7 +833,7 @@ def persist_job_analysis_snapshot( ) from error if ( persisted_tenant_record_id != tenant_record_id - or persisted_analysis_record_id != snapshot.analysis_record_id + or persisted_analysis_record_id != authorized_analysis_record_id ): raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") try: From f8fdc39dd0126b6a6bf8c0a6f39d91686d47e7f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:12:42 +0900 Subject: [PATCH 218/241] test(job-analysis): reproduce nested returned owner drift --- .../test_snapshot_returned_owner_coherence.py | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 services/job-analysis-api/tests/test_snapshot_returned_owner_coherence.py diff --git a/services/job-analysis-api/tests/test_snapshot_returned_owner_coherence.py b/services/job-analysis-api/tests/test_snapshot_returned_owner_coherence.py new file mode 100644 index 000000000..8d960e3f1 --- /dev/null +++ b/services/job-analysis-api/tests/test_snapshot_returned_owner_coherence.py @@ -0,0 +1,107 @@ +"""Regression contract for returned Job Analysis ownership coherence.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import JobAnalysisSnapshot +from orgmetra_job_analysis_api.snapshot import ( + JobAnalysisIntegrityError, + persist_job_analysis_snapshot, + read_job_analysis_snapshot, +) + +from fixtures import ( + ANALYSIS, + IDEMPOTENCY_KEY, + TENANT, + clinical_psychologist_document, + clinical_psychologist_snapshot, + read_policy, + read_principal, + write_policy, + write_principal, +) + +_OTHER_TENANT = UUID("0198a412-6000-7000-8000-000000000491") +_OTHER_JOB = UUID("0198a412-6000-7000-8000-000000000492") + + +class _ReadPort: + """Return one exact snapshot without repairing low-level ownership drift.""" + + def __init__(self, snapshot: JobAnalysisSnapshot) -> None: + self.snapshot = snapshot + + def read_snapshot(self, **_: object) -> JobAnalysisSnapshot: + """Return the configured snapshot exactly as durable evidence supplied it.""" + return self.snapshot + + +class _WritePort: + """Return one exact snapshot without repairing low-level ownership drift.""" + + def __init__(self, snapshot: JobAnalysisSnapshot) -> None: + self.snapshot = snapshot + + def persist_snapshot(self, **_: object) -> JobAnalysisSnapshot: + """Return the configured snapshot exactly as durable evidence supplied it.""" + return self.snapshot + + +def _read(snapshot: JobAnalysisSnapshot) -> None: + """Execute the governed read path for one contradictory returned graph.""" + read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=ANALYSIS, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=_ReadPort(snapshot), + ) + + +def test_read_rejects_task_tenant_drift_before_canonicalization() -> None: + """A Task cannot be silently re-parented to the snapshot tenant during export.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__(snapshot.tasks[0], "tenant_record_id", _OTHER_TENANT) + + with pytest.raises(JobAnalysisIntegrityError, match="ownership"): + _read(snapshot) + + +def test_read_rejects_ksao_job_drift_before_canonicalization() -> None: + """A KSAO cannot be silently re-parented to the snapshot Job during export.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__(snapshot.ksao_requirements[0], "job_record_id", _OTHER_JOB) + + with pytest.raises(JobAnalysisIntegrityError, match="ownership"): + _read(snapshot) + + +def test_read_rejects_fja_tenant_drift_before_canonicalization() -> None: + """The FJA profile must retain the exact returned snapshot tenant ownership.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__(snapshot.fja_profile, "tenant_record_id", _OTHER_TENANT) + + with pytest.raises(JobAnalysisIntegrityError, match="ownership"): + _read(snapshot) + + +def test_write_rejects_fja_job_drift_before_posted_document_comparison() -> None: + """Write-result capture must reject FJA ownership drift instead of normalizing it.""" + snapshot = clinical_psychologist_snapshot() + object.__setattr__(snapshot.fja_profile, "job_record_id", _OTHER_JOB) + + with pytest.raises(JobAnalysisIntegrityError, match="persisted snapshot graph"): + persist_job_analysis_snapshot( + principal=write_principal(), + tenant_record_id=TENANT, + document=clinical_psychologist_document(), + idempotency_key=IDEMPOTENCY_KEY, + purpose_code="job_analysis_write", + policy=write_policy(), + write_port=_WritePort(snapshot), + ) From b2541fda461cedc4508f9ce3f71797307ad4bb42 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:16:54 +0900 Subject: [PATCH 219/241] fix(job-analysis): reject returned ownership drift --- .../src/orgmetra_job_analysis_api/snapshot.py | 89 +++++++++++++++---- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index a89ba9a62..fead93068 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -483,10 +483,38 @@ def _validate_resolved_source_runtime( } +def _validate_resolved_owner_runtime( + field_name: str, + *, + tenant_record_id: object, + job_record_id: object, + expected_tenant_record_id: UUID, + expected_job_record_id: UUID, +) -> None: + """Require returned nested evidence to retain the detached root ownership pair.""" + resolved_tenant_record_id = _resolved_uuid( + f"{field_name}.tenant_record_id", + tenant_record_id, + ) + resolved_job_record_id = _resolved_uuid( + f"{field_name}.job_record_id", + job_record_id, + ) + if ( + resolved_tenant_record_id != expected_tenant_record_id + or resolved_job_record_id != expected_job_record_id + ): + raise JobAnalysisIntegrityError( + f"resolved snapshot graph ownership mismatch at {field_name}" + ) + + def _validate_resolved_snapshot_graph_runtime( snapshot: JobAnalysisSnapshot, -) -> dict[str, object]: - """Capture every validated non-target field into the exact document to emit.""" +) -> tuple[UUID, UUID, dict[str, object]]: + """Capture target/root ownership and validated evidence into the document to emit.""" + tenant_record_id = _resolved_uuid("tenant_record_id", snapshot.tenant_record_id) + analysis_record_id = _resolved_uuid("analysis_record_id", snapshot.analysis_record_id) job_record_id = _resolved_uuid("job_record_id", snapshot.job_record_id) analysis_version_code = _resolved_exact( "analysis_version_code", @@ -509,8 +537,13 @@ def _validate_resolved_snapshot_graph_runtime( for index, task_value in enumerate(tasks): task = _resolved_exact(f"tasks[{index}]", task_value, TaskEvidence) assert type(task) is TaskEvidence - _resolved_uuid(f"tasks[{index}].tenant_record_id", task.tenant_record_id) - _resolved_uuid(f"tasks[{index}].job_record_id", task.job_record_id) + _validate_resolved_owner_runtime( + f"tasks[{index}]", + tenant_record_id=task.tenant_record_id, + job_record_id=task.job_record_id, + expected_tenant_record_id=tenant_record_id, + expected_job_record_id=job_record_id, + ) task_record_id = _resolved_uuid( f"tasks[{index}].task_record_id", task.task_record_id, @@ -555,13 +588,12 @@ def _validate_resolved_snapshot_graph_runtime( KSAORequirement, ) assert type(ksao) is KSAORequirement - _resolved_uuid( - f"ksao_requirements[{index}].tenant_record_id", - ksao.tenant_record_id, - ) - _resolved_uuid( - f"ksao_requirements[{index}].job_record_id", - ksao.job_record_id, + _validate_resolved_owner_runtime( + f"ksao_requirements[{index}]", + tenant_record_id=ksao.tenant_record_id, + job_record_id=ksao.job_record_id, + expected_tenant_record_id=tenant_record_id, + expected_job_record_id=job_record_id, ) ksao_record_id = _resolved_uuid( f"ksao_requirements[{index}].ksao_record_id", @@ -647,8 +679,13 @@ def _validate_resolved_snapshot_graph_runtime( fja = _resolved_exact("fja_profile", snapshot.fja_profile, FunctionalJobAnalysisProfile) assert type(fja) is FunctionalJobAnalysisProfile - _resolved_uuid("fja_profile.tenant_record_id", fja.tenant_record_id) - _resolved_uuid("fja_profile.job_record_id", fja.job_record_id) + _validate_resolved_owner_runtime( + "fja_profile", + tenant_record_id=fja.tenant_record_id, + job_record_id=fja.job_record_id, + expected_tenant_record_id=tenant_record_id, + expected_job_record_id=job_record_id, + ) data_function_code = _resolved_exact( "fja_profile.data_function_code", fja.data_function_code, @@ -701,7 +738,7 @@ def _validate_resolved_snapshot_graph_runtime( ) if reviewed_at is not None: document["reviewed_at"] = _resolved_datetime_text("reviewed_at", reviewed_at) - return document + return tenant_record_id, analysis_record_id, document @runtime_checkable @@ -837,7 +874,18 @@ def persist_job_analysis_snapshot( ): raise JobAnalysisIntegrityError("persisted snapshot escaped posted payload") try: - persisted_graph = _validate_resolved_snapshot_graph_runtime(persisted) + ( + captured_tenant_record_id, + captured_analysis_record_id, + persisted_graph, + ) = _validate_resolved_snapshot_graph_runtime(persisted) + if ( + captured_tenant_record_id != persisted_tenant_record_id + or captured_analysis_record_id != persisted_analysis_record_id + ): + raise JobAnalysisIntegrityError( + "persisted snapshot target changed during graph capture" + ) except JobAnalysisIntegrityError as error: raise JobAnalysisIntegrityError( "persisted snapshot graph has invalid runtime evidence" @@ -918,7 +966,16 @@ def read_job_analysis_snapshot( ): raise JobAnalysisIntegrityError("resolved snapshot does not match authorized target") - resolved_graph = _validate_resolved_snapshot_graph_runtime(snapshot) + ( + captured_tenant_record_id, + captured_analysis_record_id, + resolved_graph, + ) = _validate_resolved_snapshot_graph_runtime(snapshot) + if ( + captured_tenant_record_id != resolved_tenant_record_id + or captured_analysis_record_id != resolved_analysis_record_id + ): + raise JobAnalysisIntegrityError("resolved snapshot target changed during graph capture") resolved_document = { "analysis_record_id": str(resolved_analysis_record_id), "tenant_record_id": str(resolved_tenant_record_id), From 09b321e657460914113d07df18ececc4591b368f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:34:21 +0900 Subject: [PATCH 220/241] test(job-analysis): reject executable stored snapshot digest --- ...test_postgres_read_projection_integrity.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py index 89963a8b5..4bb2c8cbc 100644 --- a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -29,6 +29,20 @@ def __ne__(self, other: object) -> bool: return False +class _ExecutableDigest(str): + """Model durable digest text whose comparison would execute adapter-owned code.""" + + calls = 0 + + def __eq__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable digest equality executed before exact validation") + + def __ne__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable digest inequality executed before exact validation") + + class _BrokenSequence(Sequence[object]): """Model a DB-API row sequence that fails while values are detached.""" @@ -153,6 +167,20 @@ def test_read_exact_validates_returned_target_identity_before_equality(self) -> with self.assertRaisesRegex(JobAnalysisIntegrityError, expected): self._read(self._valid_script(headers=[header])) + def test_read_rejects_executable_stored_digest_before_comparison(self) -> None: + canonical = _header_row() + digest = _ExecutableDigest(canonical[9]) + header = canonical[:9] + (digest,) + canonical[10:] + _ExecutableDigest.calls = 0 + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_snapshot.content_digest_sha256 row has invalid scalar evidence", + ): + self._read(self._valid_script(headers=[header])) + + self.assertEqual(_ExecutableDigest.calls, 0) + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From 5e6b6312afa381fe376854c5b62df713e6274486 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:36:36 +0900 Subject: [PATCH 221/241] fix(job-analysis): exact-validate stored snapshot digest --- .../src/orgmetra_job_analysis_api/postgres.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 0a13c1416..1142589db 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -233,6 +233,13 @@ def _validate_projection_uuid( raise JobAnalysisIntegrityError(f"{row_label} has invalid identity") from error +def _validate_projection_digest(field_name: str, value: object) -> str: + """Require inert lowercase SHA-256 text before durable-evidence comparison.""" + if type(value) is not str or _REQUEST_DIGEST_PATTERN.fullmatch(value) is None: + raise JobAnalysisIntegrityError(f"{field_name} row has invalid scalar evidence") + return value + + def _unpack_fixed_projection( row_label: str, row: Any, @@ -802,6 +809,10 @@ def _load_snapshot( header[1], row_label="job_analysis_snapshot.analysis_record_id row", ) + header_content_digest = _validate_projection_digest( + "job_analysis_snapshot.content_digest_sha256", + header[9], + ) if header_tenant_id != tenant_record_id or header_analysis_id != analysis_record_id: raise JobAnalysisIntegrityError("database row escaped requested target") cursor.execute(_READ_TASKS_SQL, (tenant_record_id, analysis_record_id)) @@ -858,7 +869,7 @@ def _load_snapshot( reviewed_by_reference=header[7], reviewed_at=header[8], ) - if snapshot.content_digest() != header[9]: + if snapshot.content_digest() != header_content_digest: raise JobAnalysisIntegrityError("stored snapshot digest does not match reconstructed evidence") return snapshot From 9aee97df40009d07956ab8c70c0e1557ec482fd9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:37:25 +0900 Subject: [PATCH 222/241] test(job-analysis): reject executable stored job identity --- ...test_postgres_read_projection_integrity.py | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py index 4bb2c8cbc..40422c7e3 100644 --- a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -8,7 +8,7 @@ from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError -from fixtures import ANALYSIS, OTHER_TENANT, TENANT +from fixtures import ANALYSIS, JOB, OTHER_TENANT, TENANT from test_postgres import ( FakeConnection, FakeCursor, @@ -29,6 +29,24 @@ def __ne__(self, other: object) -> bool: return False +class _ExecutableUUID(UUID): + """Model a durable UUID scalar that executes when kernel ownership is compared.""" + + calls = 0 + + def __eq__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable UUID equality executed before exact validation") + + def __ne__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable UUID inequality executed before exact validation") + + def __hash__(self) -> int: + type(self).calls += 1 + raise AssertionError("durable UUID hashing executed before exact validation") + + class _ExecutableDigest(str): """Model durable digest text whose comparison would execute adapter-owned code.""" @@ -167,6 +185,20 @@ def test_read_exact_validates_returned_target_identity_before_equality(self) -> with self.assertRaisesRegex(JobAnalysisIntegrityError, expected): self._read(self._valid_script(headers=[header])) + def test_read_rejects_executable_stored_job_identity_before_kernel_use(self) -> None: + canonical = _header_row() + executable_job = _ExecutableUUID(str(JOB)) + header = canonical[:2] + (executable_job,) + canonical[3:] + _ExecutableUUID.calls = 0 + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_snapshot.job_profile_id row has invalid identity", + ): + self._read(self._valid_script(headers=[header])) + + self.assertEqual(_ExecutableUUID.calls, 0) + def test_read_rejects_executable_stored_digest_before_comparison(self) -> None: canonical = _header_row() digest = _ExecutableDigest(canonical[9]) From 58e0c6ba9aacde7fe706443e2c2f8ad44cea4d2e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:38:32 +0900 Subject: [PATCH 223/241] test(job-analysis): reject executable durable snapshot scalars --- ...test_postgres_read_projection_integrity.py | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py index 40422c7e3..7ed0fd0ba 100644 --- a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -3,12 +3,13 @@ from __future__ import annotations from collections.abc import Sequence +from datetime import datetime import unittest from uuid import UUID from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError -from fixtures import ANALYSIS, JOB, OTHER_TENANT, TENANT +from fixtures import ANALYSIS, JOB, OTHER_TENANT, RECORDED_AT, TENANT from test_postgres import ( FakeConnection, FakeCursor, @@ -61,6 +62,48 @@ def __ne__(self, other: object) -> bool: raise AssertionError("durable digest inequality executed before exact validation") +class _ExecutableText(str): + """Model durable text whose normalization would dispatch a custom method.""" + + calls = 0 + + def split(self, *args: object, **kwargs: object) -> list[str]: + type(self).calls += 1 + raise AssertionError("durable text split executed before exact validation") + + +class _ExecutableInt(int): + """Model a durable ordinal whose range comparison would execute custom code.""" + + calls = 0 + + def __ge__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable integer comparison executed before exact validation") + + def __le__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable integer comparison executed before exact validation") + + def __lt__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable integer comparison executed before exact validation") + + def __gt__(self, other: object) -> bool: + type(self).calls += 1 + raise AssertionError("durable integer comparison executed before exact validation") + + +class _ExecutableDatetime(datetime): + """Model a durable instant whose offset lookup would execute custom code.""" + + calls = 0 + + def utcoffset(self) -> object: + type(self).calls += 1 + raise AssertionError("durable datetime offset executed before exact validation") + + class _BrokenSequence(Sequence[object]): """Model a DB-API row sequence that fails while values are detached.""" @@ -213,6 +256,57 @@ def test_read_rejects_executable_stored_digest_before_comparison(self) -> None: self.assertEqual(_ExecutableDigest.calls, 0) + def test_read_rejects_executable_task_text_before_kernel_normalization(self) -> None: + rows = list(_task_rows()) + row = rows[0] + rows[0] = row[:1] + (_ExecutableText(row[1]),) + row[2:] + _ExecutableText.calls = 0 + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_task_item.task_statement row has invalid scalar evidence", + ): + self._read(self._valid_script(tasks=rows)) + + self.assertEqual(_ExecutableText.calls, 0) + + def test_read_rejects_executable_task_level_before_kernel_comparison(self) -> None: + rows = list(_task_rows()) + row = rows[0] + rows[0] = row[:2] + (_ExecutableInt(row[2]),) + row[3:] + _ExecutableInt.calls = 0 + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_task_item.importance_level row has invalid scalar evidence", + ): + self._read(self._valid_script(tasks=rows)) + + self.assertEqual(_ExecutableInt.calls, 0) + + def test_read_rejects_executable_recorded_at_before_offset_lookup(self) -> None: + executable = _ExecutableDatetime( + RECORDED_AT.year, + RECORDED_AT.month, + RECORDED_AT.day, + RECORDED_AT.hour, + RECORDED_AT.minute, + RECORDED_AT.second, + RECORDED_AT.microsecond, + tzinfo=RECORDED_AT.tzinfo, + ) + canonical = _header_row() + header = canonical[:6] + (executable,) + canonical[7:] + _ExecutableDatetime.calls = 0 + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_snapshot.recorded_at row has invalid scalar evidence", + ): + self._read(self._valid_script(headers=[header])) + + self.assertEqual(_ExecutableDatetime.calls, 0) + if __name__ == "__main__": unittest.main() From 42670927d263ebe78ddc363b7ecba5370b0a52fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:40:26 +0900 Subject: [PATCH 224/241] fix(job-analysis): detach durable snapshot scalars --- .../src/orgmetra_job_analysis_api/postgres.py | 244 ++++++++++++++---- 1 file changed, 197 insertions(+), 47 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index 1142589db..e5a384503 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -11,7 +11,7 @@ from contextlib import AbstractContextManager from dataclasses import dataclass -from datetime import datetime, timezone +from datetime import date, datetime, timezone from hashlib import sha256 from inspect import getattr_static import json @@ -233,6 +233,60 @@ def _validate_projection_uuid( raise JobAnalysisIntegrityError(f"{row_label} has invalid identity") from error +def _validate_projection_scalar(field_name: str, value: object, expected_type: type[object]) -> object: + """Require one inert exact built-in scalar before kernel reconstruction.""" + if type(value) is not expected_type: + raise JobAnalysisIntegrityError(f"{field_name} row has invalid scalar evidence") + return value + + +def _validate_projection_text(field_name: str, value: object) -> str: + """Require inert exact text before durable evidence reaches kernel validators.""" + resolved = _validate_projection_scalar(field_name, value, str) + assert type(resolved) is str + return resolved + + +def _validate_projection_integer(field_name: str, value: object) -> int: + """Require an exact integer before ordinal or worker-function comparisons.""" + resolved = _validate_projection_scalar(field_name, value, int) + assert type(resolved) is int + return resolved + + +def _validate_projection_boolean(field_name: str, value: object) -> bool: + """Require an exact boolean before link evidence enters the kernel.""" + resolved = _validate_projection_scalar(field_name, value, bool) + assert type(resolved) is bool + return resolved + + +def _validate_projection_date(field_name: str, value: object) -> date: + """Require an exact business date before kernel serialization.""" + resolved = _validate_projection_scalar(field_name, value, date) + assert type(resolved) is date + return resolved + + +def _validate_projection_datetime(field_name: str, value: object) -> datetime: + """Require an exact fixed-offset instant before offset-aware kernel operations.""" + resolved = _validate_projection_scalar(field_name, value, datetime) + assert type(resolved) is datetime + if type(resolved.tzinfo) is not timezone: + raise JobAnalysisIntegrityError(f"{field_name} row has invalid scalar evidence") + return resolved + + +def _validate_projection_optional_text(field_name: str, value: object) -> str | None: + """Require optional durable text to be absent or an inert exact string.""" + return None if value is None else _validate_projection_text(field_name, value) + + +def _validate_projection_optional_datetime(field_name: str, value: object) -> datetime | None: + """Require optional durable time to be absent or an inert fixed-offset datetime.""" + return None if value is None else _validate_projection_datetime(field_name, value) + + def _validate_projection_digest(field_name: str, value: object) -> str: """Require inert lowercase SHA-256 text before durable-evidence comparison.""" if type(value) is not str or _REQUEST_DIGEST_PATTERN.fullmatch(value) is None: @@ -788,7 +842,7 @@ def _load_snapshot( tenant_record_id: UUID, analysis_record_id: UUID, ) -> JobAnalysisSnapshot | None: - """Assemble one kernel snapshot from normalized rows or return None.""" + """Assemble one kernel snapshot from exact-validated durable rows or return None.""" cursor.execute(_READ_SNAPSHOT_SQL, (tenant_record_id, analysis_record_id)) headers = _unpack_projection_rows( "job_analysis_snapshot", @@ -809,12 +863,58 @@ def _load_snapshot( header[1], row_label="job_analysis_snapshot.analysis_record_id row", ) + if header_tenant_id != tenant_record_id or header_analysis_id != analysis_record_id: + raise JobAnalysisIntegrityError("database row escaped requested target") + header_job_id = _validate_projection_uuid( + "job_analysis_snapshot.job_profile_id", + header[2], + row_label="job_analysis_snapshot.job_profile_id row", + ) + header_analysis_version_code = _validate_projection_text( + "job_analysis_snapshot.analysis_version_code", + header[3], + ) + header_status_code = _validate_projection_text( + "job_analysis_snapshot.status_code", + header[4], + ) + header_effective_from = _validate_projection_date( + "job_analysis_snapshot.effective_from", + header[5], + ) + header_recorded_at = _validate_projection_datetime( + "job_analysis_snapshot.recorded_at", + header[6], + ) + header_reviewed_by_reference = _validate_projection_optional_text( + "job_analysis_snapshot.reviewed_by_reference", + header[7], + ) + header_reviewed_at = _validate_projection_optional_datetime( + "job_analysis_snapshot.reviewed_at", + header[8], + ) header_content_digest = _validate_projection_digest( "job_analysis_snapshot.content_digest_sha256", header[9], ) - if header_tenant_id != tenant_record_id or header_analysis_id != analysis_record_id: - raise JobAnalysisIntegrityError("database row escaped requested target") + fja_data_function_code = _validate_projection_integer( + "job_analysis_snapshot.data_function_code", + header[10], + ) + fja_people_function_code = _validate_projection_integer( + "job_analysis_snapshot.people_function_code", + header[11], + ) + fja_things_function_code = _validate_projection_integer( + "job_analysis_snapshot.things_function_code", + header[12], + ) + fja_source = _source_from_row( + header[13:19], + row_label="job_analysis_snapshot.fja_source", + ) + cursor.execute(_READ_TASKS_SQL, (tenant_record_id, analysis_record_id)) task_rows = tuple( _unpack_fixed_projection("job_analysis_task_item", row, 10) @@ -842,72 +942,122 @@ def _load_snapshot( snapshot = JobAnalysisSnapshot( analysis_record_id=header_analysis_id, tenant_record_id=header_tenant_id, - job_record_id=header[2], - analysis_version_code=header[3], - status_code=header[4], - effective_from=header[5], - recorded_at=header[6], - tasks=tuple(_task_from_row(tenant_record_id, header[2], row) for row in task_rows), - ksao_requirements=tuple(_ksao_from_row(tenant_record_id, header[2], row) for row in ksao_rows), - task_ksao_links=tuple( - TaskKSAOLink( - task_record_id=row[0], - ksao_record_id=row[1], - relationship_strength=row[2], - essential_for_task=row[3], - ) - for row in link_rows + job_record_id=header_job_id, + analysis_version_code=header_analysis_version_code, + status_code=header_status_code, + effective_from=header_effective_from, + recorded_at=header_recorded_at, + tasks=tuple(_task_from_row(tenant_record_id, header_job_id, row) for row in task_rows), + ksao_requirements=tuple( + _ksao_from_row(tenant_record_id, header_job_id, row) for row in ksao_rows ), + task_ksao_links=tuple(_link_from_row(row) for row in link_rows), fja_profile=FunctionalJobAnalysisProfile( tenant_record_id=tenant_record_id, - job_record_id=header[2], - data_function_code=header[10], - people_function_code=header[11], - things_function_code=header[12], - source=_source_from_row(header[13:19]), + job_record_id=header_job_id, + data_function_code=fja_data_function_code, + people_function_code=fja_people_function_code, + things_function_code=fja_things_function_code, + source=fja_source, ), - reviewed_by_reference=header[7], - reviewed_at=header[8], + reviewed_by_reference=header_reviewed_by_reference, + reviewed_at=header_reviewed_at, ) if snapshot.content_digest() != header_content_digest: raise JobAnalysisIntegrityError("stored snapshot digest does not match reconstructed evidence") return snapshot -def _source_from_row(values: tuple[object, ...]) -> EvidenceSource: - """Rebuild one evidence source from six persisted provenance columns.""" +def _source_from_row(values: tuple[object, ...], *, row_label: str) -> EvidenceSource: + """Rebuild one evidence source only from exact-validated persisted scalars.""" return EvidenceSource( - source_uri=values[0], - source_title=values[1], - source_version_code=values[2], - retrieved_at=values[3], - content_digest_sha256=values[4], - origin_code=values[5], + source_uri=_validate_projection_text(f"{row_label}.source_uri", values[0]), + source_title=_validate_projection_text(f"{row_label}.source_title", values[1]), + source_version_code=_validate_projection_text( + f"{row_label}.source_version_code", + values[2], + ), + retrieved_at=_validate_projection_datetime(f"{row_label}.retrieved_at", values[3]), + content_digest_sha256=_validate_projection_digest( + f"{row_label}.content_digest_sha256", + values[4], + ), + origin_code=_validate_projection_text(f"{row_label}.origin_code", values[5]), ) def _task_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[object, ...]) -> TaskEvidence: - """Rebuild one task item from its persisted 3NF row.""" + """Rebuild one task item only after exact-validating its persisted scalars.""" return TaskEvidence( tenant_record_id=tenant_record_id, job_record_id=job_record_id, - task_record_id=row[0], - task_statement=row[1], - importance_level=row[2], - difficulty_level=row[3], - source=_source_from_row(row[4:10]), + task_record_id=_validate_projection_uuid( + "job_analysis_task_item.task_record_id", + row[0], + row_label="job_analysis_task_item.task_record_id row", + ), + task_statement=_validate_projection_text( + "job_analysis_task_item.task_statement", + row[1], + ), + importance_level=_validate_projection_integer( + "job_analysis_task_item.importance_level", + row[2], + ), + difficulty_level=_validate_projection_integer( + "job_analysis_task_item.difficulty_level", + row[3], + ), + source=_source_from_row(row[4:10], row_label="job_analysis_task_item.source"), ) def _ksao_from_row(tenant_record_id: UUID, job_record_id: UUID, row: tuple[object, ...]) -> KSAORequirement: - """Rebuild one KSAO item from its persisted 3NF row.""" + """Rebuild one KSAO item only after exact-validating its persisted scalars.""" return KSAORequirement( tenant_record_id=tenant_record_id, job_record_id=job_record_id, - ksao_record_id=row[0], - category_code=row[1], - requirement_statement=row[2], - importance_level=row[3], - proficiency_level=row[4], - source=_source_from_row(row[5:11]), + ksao_record_id=_validate_projection_uuid( + "job_analysis_ksao_item.ksao_record_id", + row[0], + row_label="job_analysis_ksao_item.ksao_record_id row", + ), + category_code=_validate_projection_text("job_analysis_ksao_item.category_code", row[1]), + requirement_statement=_validate_projection_text( + "job_analysis_ksao_item.requirement_statement", + row[2], + ), + importance_level=_validate_projection_integer( + "job_analysis_ksao_item.importance_level", + row[3], + ), + proficiency_level=_validate_projection_integer( + "job_analysis_ksao_item.proficiency_level", + row[4], + ), + source=_source_from_row(row[5:11], row_label="job_analysis_ksao_item.source"), + ) + + +def _link_from_row(row: tuple[object, ...]) -> TaskKSAOLink: + """Rebuild one task-KSAO link only from exact-validated persisted scalars.""" + return TaskKSAOLink( + task_record_id=_validate_projection_uuid( + "job_analysis_task_ksao_link.task_record_id", + row[0], + row_label="job_analysis_task_ksao_link.task_record_id row", + ), + ksao_record_id=_validate_projection_uuid( + "job_analysis_task_ksao_link.ksao_record_id", + row[1], + row_label="job_analysis_task_ksao_link.ksao_record_id row", + ), + relationship_strength=_validate_projection_integer( + "job_analysis_task_ksao_link.relationship_strength", + row[2], + ), + essential_for_task=_validate_projection_boolean( + "job_analysis_task_ksao_link.essential_for_task", + row[3], + ), ) From 201c67580a4e1e37b628ef0374cdb850b4555cc9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:41:35 +0900 Subject: [PATCH 225/241] test(job-analysis): preserve psycopg3 ZoneInfo timestamptz --- .../tests/test_postgres_read_projection_integrity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py index 7ed0fd0ba..385973c57 100644 --- a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -6,6 +6,7 @@ from datetime import datetime import unittest from uuid import UUID +from zoneinfo import ZoneInfo from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError @@ -307,6 +308,13 @@ def test_read_rejects_executable_recorded_at_before_offset_lookup(self) -> None: self.assertEqual(_ExecutableDatetime.calls, 0) + def test_read_accepts_psycopg3_zoneinfo_timestamptz_projection(self) -> None: + canonical = _header_row() + zoneinfo_recorded_at = RECORDED_AT.astimezone(ZoneInfo("UTC")) + header = canonical[:6] + (zoneinfo_recorded_at,) + canonical[7:] + + self._read(self._valid_script(headers=[header])) + if __name__ == "__main__": unittest.main() From 79ad46bf4913b6313aba2daa8340cf4678e7400b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:45:35 +0900 Subject: [PATCH 226/241] fix(job-analysis): preserve standard timestamptz adapters --- .../src/orgmetra_job_analysis_api/postgres.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py index e5a384503..cf32a6871 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/postgres.py @@ -18,6 +18,7 @@ import re from typing import Any, Callable from uuid import UUID +from zoneinfo import ZoneInfo from orgmetra_hris_kernel import ( AuditOutboxEvent, @@ -269,10 +270,10 @@ def _validate_projection_date(field_name: str, value: object) -> date: def _validate_projection_datetime(field_name: str, value: object) -> datetime: - """Require an exact fixed-offset instant before offset-aware kernel operations.""" + """Require an exact standard-library instant before offset-aware kernel operations.""" resolved = _validate_projection_scalar(field_name, value, datetime) assert type(resolved) is datetime - if type(resolved.tzinfo) is not timezone: + if type(resolved.tzinfo) not in (timezone, ZoneInfo): raise JobAnalysisIntegrityError(f"{field_name} row has invalid scalar evidence") return resolved @@ -283,7 +284,7 @@ def _validate_projection_optional_text(field_name: str, value: object) -> str | def _validate_projection_optional_datetime(field_name: str, value: object) -> datetime | None: - """Require optional durable time to be absent or an inert fixed-offset datetime.""" + """Require optional durable time to be absent or an inert standard-library datetime.""" return None if value is None else _validate_projection_datetime(field_name, value) From 5b49fb13e44a9483ebd411b0495c80039ff96d50 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:47:09 +0900 Subject: [PATCH 227/241] test(job-analysis): reject executable timezone evidence --- ...test_postgres_read_projection_integrity.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py index 385973c57..854c0fa2f 100644 --- a/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_read_projection_integrity.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Sequence -from datetime import datetime +from datetime import datetime, tzinfo import unittest from uuid import UUID from zoneinfo import ZoneInfo @@ -105,6 +105,22 @@ def utcoffset(self) -> object: raise AssertionError("durable datetime offset executed before exact validation") +class _ExecutableTzinfo(tzinfo): + """Model an exact datetime carrying executable non-standard timezone evidence.""" + + calls = 0 + + def utcoffset(self, dt: datetime | None) -> object: + type(self).calls += 1 + raise AssertionError("durable timezone offset executed before exact validation") + + def dst(self, dt: datetime | None) -> None: + return None + + def tzname(self, dt: datetime | None) -> str: + return "executable" + + class _BrokenSequence(Sequence[object]): """Model a DB-API row sequence that fails while values are detached.""" @@ -308,6 +324,30 @@ def test_read_rejects_executable_recorded_at_before_offset_lookup(self) -> None: self.assertEqual(_ExecutableDatetime.calls, 0) + def test_read_rejects_exact_datetime_with_executable_timezone_before_offset_lookup(self) -> None: + executable_timezone = _ExecutableTzinfo() + executable = datetime( + RECORDED_AT.year, + RECORDED_AT.month, + RECORDED_AT.day, + RECORDED_AT.hour, + RECORDED_AT.minute, + RECORDED_AT.second, + RECORDED_AT.microsecond, + tzinfo=executable_timezone, + ) + canonical = _header_row() + header = canonical[:6] + (executable,) + canonical[7:] + _ExecutableTzinfo.calls = 0 + + with self.assertRaisesRegex( + JobAnalysisIntegrityError, + "job_analysis_snapshot.recorded_at row has invalid scalar evidence", + ): + self._read(self._valid_script(headers=[header])) + + self.assertEqual(_ExecutableTzinfo.calls, 0) + def test_read_accepts_psycopg3_zoneinfo_timestamptz_projection(self) -> None: canonical = _header_row() zoneinfo_recorded_at = RECORDED_AT.astimezone(ZoneInfo("UTC")) From f953dda96c8d3d27ccb20b46358501a4024a745e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:07:19 +0900 Subject: [PATCH 228/241] test(job-analysis): preserve ZoneInfo through governed read --- ...res_read_service_zoneinfo_compatibility.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_read_service_zoneinfo_compatibility.py diff --git a/services/job-analysis-api/tests/test_postgres_read_service_zoneinfo_compatibility.py b/services/job-analysis-api/tests/test_postgres_read_service_zoneinfo_compatibility.py new file mode 100644 index 000000000..189c75802 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_read_service_zoneinfo_compatibility.py @@ -0,0 +1,58 @@ +"""Regression for Psycopg ZoneInfo timestamps crossing the governed read boundary.""" + +from __future__ import annotations + +import unittest +from zoneinfo import ZoneInfo + +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import read_job_analysis_snapshot + +from fixtures import ANALYSIS, RECORDED_AT, TENANT, read_policy, read_principal +from test_postgres import ( + FakeConnection, + FakeCursor, + _header_row, + _ksao_rows, + _link_rows, + _task_rows, +) + + +class PostgresReadServiceZoneInfoCompatibilityTests(unittest.TestCase): + """Keep accepted Psycopg timestamptz evidence valid through customer export.""" + + def test_read_accepts_psycopg_zoneinfo_timestamp_through_governed_export(self) -> None: + """A standard-library ZoneInfo row accepted by the port must remain readable.""" + header = _header_row() + zoneinfo_recorded_at = RECORDED_AT.astimezone(ZoneInfo("Asia/Seoul")) + header = header[:6] + (zoneinfo_recorded_at,) + header[7:] + cursor = FakeCursor( + [ + None, + None, + [header], + _task_rows(), + _ksao_rows(), + _link_rows(), + ] + ) + port = PostgresJobAnalysisPort(lambda: FakeConnection(cursor)) + + view = read_job_analysis_snapshot( + principal=read_principal(), + tenant_record_id=TENANT, + analysis_record_id=ANALYSIS, + purpose_code="job_analysis_read", + policy=read_policy(), + read_port=port, + ) + + self.assertEqual( + view.snapshot["recorded_at"], + RECORDED_AT.isoformat().replace("+00:00", "Z"), + ) + + +if __name__ == "__main__": + unittest.main() From b1e6017948a9ac86f72bab1935f1a0f111423d20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:09:49 +0900 Subject: [PATCH 229/241] fix(job-analysis): preserve ZoneInfo through read export --- .../src/orgmetra_job_analysis_api/snapshot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py index fead93068..bcb602577 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py @@ -14,6 +14,7 @@ import json from typing import Protocol, runtime_checkable from uuid import UUID, uuid4 +from zoneinfo import ZoneInfo from orgmetra_hris_kernel import ( AuditOutboxEvent, @@ -433,10 +434,10 @@ def _resolved_uuid(field_name: str, value: object) -> UUID: def _resolved_datetime(field_name: str, value: object) -> datetime: - """Require an exact fixed-offset datetime before canonicalization can use it.""" + """Require an exact standard-library datetime before canonicalization can use it.""" resolved = _resolved_exact(field_name, value, datetime) assert type(resolved) is datetime - if type(resolved.tzinfo) is not timezone: + if type(resolved.tzinfo) not in (timezone, ZoneInfo): raise JobAnalysisIntegrityError( f"resolved snapshot graph has invalid runtime evidence at {field_name}" ) From a0260aeaad44b8b12a6e11b7007ea338593eb985 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 22:06:15 +0900 Subject: [PATCH 230/241] test(auth): bound job-analysis authorization header before parsing --- .../tests/test_bearer_header_budget.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 services/job-analysis-api/tests/test_bearer_header_budget.py diff --git a/services/job-analysis-api/tests/test_bearer_header_budget.py b/services/job-analysis-api/tests/test_bearer_header_budget.py new file mode 100644 index 000000000..778f49435 --- /dev/null +++ b/services/job-analysis-api/tests/test_bearer_header_budget.py @@ -0,0 +1,26 @@ +"""Regression contract for pre-parse Job Analysis bearer-header budgeting.""" + +from __future__ import annotations + +import unittest + +from orgmetra_job_analysis_api.auth import AuthenticationFailed, extract_bearer_token + + +class BearerHeaderBudgetTests(unittest.TestCase): + """Bound exact authorization-header text before scheme parsing allocates work.""" + + def test_rejects_oversized_header_before_scheme_semantics(self) -> None: + oversized_header = "X" * 8200 + + with self.assertRaisesRegex(AuthenticationFailed, "authorization header length"): + extract_bearer_token(oversized_header) + + def test_accepts_maximum_valid_bearer_header(self) -> None: + token = "x" * 8192 + + self.assertEqual(extract_bearer_token(f"Bearer {token}"), token) + + +if __name__ == "__main__": + unittest.main() From cb0b999efb002fe8677a93d2e674ba192e2dcc05 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 22:06:27 +0900 Subject: [PATCH 231/241] test(auth): mirror People authorization header budget --- .../tests/test_bearer_header_budget.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 services/people-api/tests/test_bearer_header_budget.py diff --git a/services/people-api/tests/test_bearer_header_budget.py b/services/people-api/tests/test_bearer_header_budget.py new file mode 100644 index 000000000..81761afcf --- /dev/null +++ b/services/people-api/tests/test_bearer_header_budget.py @@ -0,0 +1,26 @@ +"""Regression contract for pre-parse People bearer-header budgeting.""" + +from __future__ import annotations + +import unittest + +from orgmetra_people_api.auth import AuthenticationFailed, extract_bearer_token + + +class BearerHeaderBudgetTests(unittest.TestCase): + """Bound exact authorization-header text before scheme parsing allocates work.""" + + def test_rejects_oversized_header_before_scheme_semantics(self) -> None: + oversized_header = "X" * 8200 + + with self.assertRaisesRegex(AuthenticationFailed, "authorization header length"): + extract_bearer_token(oversized_header) + + def test_accepts_maximum_valid_bearer_header(self) -> None: + token = "x" * 8192 + + self.assertEqual(extract_bearer_token(f"Bearer {token}"), token) + + +if __name__ == "__main__": + unittest.main() From 5660fc6e00d38510ac5538bcef9da2e89b52a1a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 22:07:07 +0900 Subject: [PATCH 232/241] fix(auth): bound job-analysis header before bearer parsing --- .../job-analysis-api/src/orgmetra_job_analysis_api/auth.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py index 6a2ea8e65..74ce2d70e 100644 --- a/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py +++ b/services/job-analysis-api/src/orgmetra_job_analysis_api/auth.py @@ -16,6 +16,8 @@ _MAX_UUID_INT = (1 << 128) - 1 _REFERENCE_PATTERN = re.compile(r"^[a-z][a-z0-9_]*:[A-Za-z0-9][A-Za-z0-9._~-]*$") _SCOPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") +_MAX_BEARER_TOKEN_LENGTH = 8192 +_MAX_AUTHORIZATION_HEADER_LENGTH = 8199 def _validated_principal_storage( @@ -224,11 +226,13 @@ def extract_bearer_token(authorization_header: str | None) -> str: raise AuthenticationFailed("bearer authentication is required") if type(authorization_header) is not str: raise AuthenticationFailed("authorization header must be plain text") + if len(authorization_header) > _MAX_AUTHORIZATION_HEADER_LENGTH: + raise AuthenticationFailed("authorization header length is invalid") parts = authorization_header.split(" ", 1) if len(parts) != 2 or parts[0].casefold() != "bearer": raise AuthenticationFailed("authorization must use the Bearer scheme") token = parts[1] - if not token or len(token) > 8192: + if not token or len(token) > _MAX_BEARER_TOKEN_LENGTH: raise AuthenticationFailed("bearer token length is invalid") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in token): raise AuthenticationFailed("bearer token contains invalid characters") From 39b6a3f96534dfecf1d2accbbf852f305a908027 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 22:07:40 +0900 Subject: [PATCH 233/241] fix(auth): mirror People header budget before parsing --- services/people-api/src/orgmetra_people_api/auth.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/people-api/src/orgmetra_people_api/auth.py b/services/people-api/src/orgmetra_people_api/auth.py index a352e61c0..e06614da1 100644 --- a/services/people-api/src/orgmetra_people_api/auth.py +++ b/services/people-api/src/orgmetra_people_api/auth.py @@ -17,6 +17,8 @@ _MAX_UUID_INT = (1 << 128) - 1 _REFERENCE_PATTERN = re.compile(r"^[a-z][a-z0-9_]*:[A-Za-z0-9][A-Za-z0-9._~-]*$") _SCOPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") +_MAX_BEARER_TOKEN_LENGTH = 8192 +_MAX_AUTHORIZATION_HEADER_LENGTH = 8199 def _validated_principal_storage( @@ -230,11 +232,13 @@ def extract_bearer_token(authorization_header: str | None) -> str: raise AuthenticationFailed("bearer authentication is required") if type(authorization_header) is not str: raise AuthenticationFailed("authorization header must be plain text") + if len(authorization_header) > _MAX_AUTHORIZATION_HEADER_LENGTH: + raise AuthenticationFailed("authorization header length is invalid") parts = authorization_header.split(" ", 1) if len(parts) != 2 or parts[0].casefold() != "bearer": raise AuthenticationFailed("authorization must use the Bearer scheme") token = parts[1] - if not token or len(token) > 8192: + if not token or len(token) > _MAX_BEARER_TOKEN_LENGTH: raise AuthenticationFailed("bearer token length is invalid") if any(ord(character) < 0x21 or ord(character) > 0x7E for character in token): raise AuthenticationFailed("bearer token contains invalid characters") From 375b91fc92b2ddf453d9cec15454e4d0f76542ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 23:06:17 +0900 Subject: [PATCH 234/241] merge(authz): preserve #161 changelog delta after restack --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d11acc37..39cc99a06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ All notable changes to Orgmetra will be documented in this file. - `employment_record_version.employment_concurrency_code` constrained to `exclusive` or `concurrent`. - ADR 0005 for exclusive employment and staffable seats. - `orgmetra_hris_kernel` 0.3.0 with identity-scoped bitemporal resolution, assignment-employment coverage, allocation-portfolio checks, and a Memorial Hospital RN correction case at 100% statement and branch coverage. -- `employment_record_version` and `position_record_version` so employment and position identity stay stable across retroactive corrections. +- `employment_record_version` and `position_record_version` so corrections no longer mint a new employment or position identifier. - `assignment_record.employment_record_id` bound to the same person as the covering employment. - `orgmetra_keyverse_adapter` that binds an opaque Keyverse subject to a person and rejects passwords, passkeys, and tokens. - Design tokens for the repeating HR actions: approve, review, correct, request evidence, compare, export, and escalate. @@ -37,6 +37,7 @@ All notable changes to Orgmetra will be documented in this file. ### Changed +- Consolidated repository-owned PR validation from twelve workflows into one Foundation CI job, while keeping the dual-cluster recovery rehearsal separately path-scoped. Central required review and security workflows remain organization-owned. - New predictive-validity membership must use one normalized worker-level case; the three independent validity-study decision/evidence/outcome link relations are historical read surfaces only and can no longer accept new rows. A case insert also rejects a criterion observation whose recorded interval is already closed at `linked_at`. - Canonicalized service identifiers as two-or-more-word `snake_case` across architecture, deployment, ACL, metrics, and client contracts. - Separated fast-mlsirm, TEPP, and Psychometrics Commons into immutable external scientific contracts. From 50fd31581ccc5b0310cfb1c7b5a2f878f30c1183 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 23:10:18 +0900 Subject: [PATCH 235/241] fix(ci): reseal foundation manifest after protected restack --- manifest.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index f7b6cf55e..02926e103 100644 --- a/manifest.json +++ b/manifest.json @@ -29,9 +29,9 @@ }, { "path": "CHANGELOG.md", - "sha256": "f2d2e0b488c0440533effa821808f2f17e37d92f8fb586174c2fdb594f760ca5", - "bytes": 17539, - "lines": 77 + "sha256": "8712d0ec7442acb52fcb5776988799da1d8550a90bcced6630273af0be6468ba", + "bytes": 17907, + "lines": 78 }, { "path": "CLAUDE.md", @@ -197,8 +197,8 @@ }, { "path": "docs/TRACEABILITY.md", - "sha256": "dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e", - "bytes": 11462, + "sha256": "d5b57dfa3b4b5c4a408062b6a47f947080aa98eb4b82ffaf130a85ac17c80892", + "bytes": 11841, "lines": 40 }, { From 200cc2a42e005bb2bcf131f64d60553fbc3c46d0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:03:25 +0900 Subject: [PATCH 236/241] test(job-analysis): reject executable durable audit scalar evidence --- ...st_postgres_audit_scalar_time_integrity.py | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py diff --git a/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py b/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py new file mode 100644 index 000000000..5061bef88 --- /dev/null +++ b/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py @@ -0,0 +1,159 @@ +"""Regression coverage for inert Job Analysis durable-audit scalar/time evidence.""" + +from __future__ import annotations + +from datetime import datetime, timedelta, timezone, tzinfo +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel import AuditOutboxEvent +from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort +from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError, command_digest +from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot + +_ACTOR_REFERENCE = "keyverse:actor-ja-1" +_PURPOSE_CODE = "job_analysis_write" +_RESOURCE_REFERENCE = f"job_analysis_snapshot:{ANALYSIS.hex}" + + +class _TripwireTimezone(tzinfo): + """Fail if durable validation executes caller-defined timezone behavior.""" + + def __init__(self) -> None: + self.calls = 0 + + def utcoffset(self, value: datetime | None) -> timedelta: + del value + self.calls += 1 + raise AssertionError("audit occurred_at timezone callback executed before rejection") + + def dst(self, value: datetime | None) -> timedelta: + del value + return timedelta(0) + + def tzname(self, value: datetime | None) -> str: + del value + return "TRIPWIRE" + + +class _ExecutableConfirmationReference(str): + """Trip if optional confirmation text reaches equality before exact-type rejection.""" + + def __eq__(self, other: object) -> bool: + del other + raise AssertionError("confirmation_reference equality executed before rejection") + + def __ne__(self, other: object) -> bool: + del other + raise AssertionError("confirmation_reference inequality executed before rejection") + + __hash__ = str.__hash__ + + +def _audit_event() -> AuditOutboxEvent: + """Build one valid exact audit envelope before low-level adversarial rewriting.""" + snapshot = clinical_psychologist_snapshot() + return AuditOutboxEvent( + event_id=UUID("0198a412-6000-7000-8000-000000000411"), + tenant_record_id=TENANT, + source_service="job_analysis_api", + event_type="orgmetra.job_architecture.snapshot_recorded", + resource_reference=_RESOURCE_REFERENCE, + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + reason_code="snapshot_persisted", + evidence_version_code=snapshot.analysis_version_code, + result_code="recorded", + occurred_at=datetime(2026, 8, 18, 5, 1, tzinfo=timezone.utc), + high_impact=False, + ) + + +def _never_connect() -> object: + """Prove malformed audit evidence is rejected before database acquisition.""" + raise AssertionError("database acquired before durable audit scalar/time validation") + + +def _persist_with_audit(audit_event: AuditOutboxEvent) -> None: + """Invoke the durable write boundary with one otherwise-valid command.""" + snapshot = clinical_psychologist_snapshot() + PostgresJobAnalysisPort(_never_connect).persist_snapshot( + snapshot=snapshot, + idempotency_key=IDEMPOTENCY_KEY, + request_digest=command_digest( + snapshot=snapshot, + position_record_id=None, + criterion_blueprint_id=None, + ), + actor_reference=_ACTOR_REFERENCE, + purpose_code=_PURPOSE_CODE, + position_record_id=None, + criterion_blueprint_id=None, + audit_event=audit_event, + outbox_delivery_record_id=UUID("0198a412-6000-7000-8000-000000000412"), + write_command_id=UUID("0198a412-6000-7000-8000-000000000413"), + ) + + +def test_durable_audit_rejects_executable_occurred_at_timezone_before_callback() -> None: + """Canonicalization must never execute a caller-defined durable-audit timezone.""" + audit_event = _audit_event() + tripwire = _TripwireTimezone() + object.__setattr__( + audit_event, + "occurred_at", + datetime(2026, 8, 18, 5, 1, tzinfo=tripwire), + ) + + with pytest.raises( + JobAnalysisIntegrityError, + match="audit event has invalid occurred_at evidence", + ): + _persist_with_audit(audit_event) + + assert tripwire.calls == 0 + + +def test_durable_audit_rejects_non_boolean_high_impact_before_canonicalization() -> None: + """Low-level rewrites cannot make non-boolean audit semantics reach serialization.""" + audit_event = _audit_event() + tripwire = _TripwireTimezone() + object.__setattr__(audit_event, "high_impact", 0) + object.__setattr__( + audit_event, + "occurred_at", + datetime(2026, 8, 18, 5, 1, tzinfo=tripwire), + ) + + with pytest.raises( + JobAnalysisIntegrityError, + match="audit event has invalid high_impact evidence", + ): + _persist_with_audit(audit_event) + + assert tripwire.calls == 0 + + +def test_durable_audit_rejects_executable_confirmation_before_canonicalization() -> None: + """Optional confirmation evidence must be inert exact text before serialization.""" + audit_event = _audit_event() + tripwire = _TripwireTimezone() + object.__setattr__( + audit_event, + "confirmation_reference", + _ExecutableConfirmationReference("review:job-analysis-1"), + ) + object.__setattr__( + audit_event, + "occurred_at", + datetime(2026, 8, 18, 5, 1, tzinfo=tripwire), + ) + + with pytest.raises( + JobAnalysisIntegrityError, + match="audit event has invalid confirmation_reference evidence", + ): + _persist_with_audit(audit_event) + + assert tripwire.calls == 0 From 47059d82011973855d7a540788500533859cc96a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:16:54 +0900 Subject: [PATCH 237/241] test(job-analysis): align durable audit acceptance with shared-kernel owner --- .../test_postgres_audit_scalar_time_integrity.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py b/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py index 5061bef88..772f62617 100644 --- a/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py +++ b/services/job-analysis-api/tests/test_postgres_audit_scalar_time_integrity.py @@ -9,7 +9,7 @@ from orgmetra_hris_kernel import AuditOutboxEvent from orgmetra_job_analysis_api.postgres import PostgresJobAnalysisPort -from orgmetra_job_analysis_api.snapshot import JobAnalysisIntegrityError, command_digest +from orgmetra_job_analysis_api.snapshot import command_digest from fixtures import ANALYSIS, IDEMPOTENCY_KEY, TENANT, clinical_psychologist_snapshot _ACTOR_REFERENCE = "keyverse:actor-ja-1" @@ -107,8 +107,8 @@ def test_durable_audit_rejects_executable_occurred_at_timezone_before_callback() ) with pytest.raises( - JobAnalysisIntegrityError, - match="audit event has invalid occurred_at evidence", + ValueError, + match="occurred_at must be an exact timezone-aware datetime", ): _persist_with_audit(audit_event) @@ -126,10 +126,7 @@ def test_durable_audit_rejects_non_boolean_high_impact_before_canonicalization() datetime(2026, 8, 18, 5, 1, tzinfo=tripwire), ) - with pytest.raises( - JobAnalysisIntegrityError, - match="audit event has invalid high_impact evidence", - ): + with pytest.raises(ValueError, match="high_impact must be a boolean"): _persist_with_audit(audit_event) assert tripwire.calls == 0 @@ -150,10 +147,7 @@ def test_durable_audit_rejects_executable_confirmation_before_canonicalization() datetime(2026, 8, 18, 5, 1, tzinfo=tripwire), ) - with pytest.raises( - JobAnalysisIntegrityError, - match="audit event has invalid confirmation_reference evidence", - ): + with pytest.raises(ValueError, match="confirmation_reference must be a string when supplied"): _persist_with_audit(audit_event) assert tripwire.calls == 0 From fee1989f9b30979845c389623cca80a072c76bb6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 09:08:52 +0900 Subject: [PATCH 238/241] test(job-analysis): satisfy runtime tripwire protocols --- .../test_snapshot_document_runtime_integrity.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py index 87b6409cf..3a9d7bc75 100644 --- a/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py +++ b/services/job-analysis-api/tests/test_snapshot_document_runtime_integrity.py @@ -63,6 +63,10 @@ def __hash__(self) -> int: raise AssertionError("field-name subtype hash executed") return str.__hash__(self) + def __eq__(self, other: object) -> bool: + """Preserve normal string equality while hash remains the execution tripwire.""" + return bool(str.__eq__(self, other)) + class _ExecutableInteger(int): """Trip if kernel ordinal validation compares a caller-defined integer subtype.""" @@ -75,6 +79,14 @@ def __le__(self, other: object) -> bool: """Reject upper-bound comparison before exact integer validation.""" raise AssertionError("integer subtype comparison executed") + def __lt__(self, other: object) -> bool: + """Reject strict lower-bound comparison before exact integer validation.""" + raise AssertionError("integer subtype comparison executed") + + def __gt__(self, other: object) -> bool: + """Reject strict upper-bound comparison before exact integer validation.""" + raise AssertionError("integer subtype comparison executed") + class _ExecutableDateTime(datetime): """Trip if timezone validation consumes a caller-defined datetime subtype.""" From e1471be7c9e58dbd4b452d76cbe6738f88765c10 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 09:10:09 +0900 Subject: [PATCH 239/241] test(job-analysis): avoid unused subclass binding --- .../tests/test_authenticated_principal_runtime_types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py index 7417ddea4..f3034f2cd 100644 --- a/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/job-analysis-api/tests/test_authenticated_principal_runtime_types.py @@ -58,9 +58,7 @@ def test_rejects_trust_bearing_runtime_subtypes(self) -> None: def test_principal_runtime_class_cannot_be_subclassed(self) -> None: """Executable principal subclasses cannot override authenticated evidence access.""" with self.assertRaisesRegex(TypeError, "AuthenticatedPrincipal must not be subclassed"): - - class _PrincipalSubtype(AuthenticatedPrincipal): - pass + type("_PrincipalSubtype", (AuthenticatedPrincipal,), {}) def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: """Post-construction mutation of the caller UUID cannot retarget the principal.""" From 8f6a5fecdb7380b83eb4b6af0e656e0d4b5a06c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 09:11:08 +0900 Subject: [PATCH 240/241] test(people): avoid unused subclass binding --- .../tests/test_authenticated_principal_runtime_types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/people-api/tests/test_authenticated_principal_runtime_types.py b/services/people-api/tests/test_authenticated_principal_runtime_types.py index b1ee99ca7..6d93555bd 100644 --- a/services/people-api/tests/test_authenticated_principal_runtime_types.py +++ b/services/people-api/tests/test_authenticated_principal_runtime_types.py @@ -58,9 +58,7 @@ def test_rejects_trust_bearing_runtime_subtypes(self) -> None: def test_principal_runtime_class_cannot_be_subclassed(self) -> None: """Executable principal subclasses cannot override authenticated evidence access.""" with self.assertRaisesRegex(TypeError, "AuthenticatedPrincipal must not be subclassed"): - - class _PrincipalSubtype(AuthenticatedPrincipal): - pass + type("_PrincipalSubtype", (AuthenticatedPrincipal,), {}) def test_tenant_uuid_is_detached_from_caller_owned_instance(self) -> None: """Post-construction mutation of the caller UUID cannot retarget the principal.""" From 1caf8f760e81f1cb6954fdf1d0d13a46dbb6c0b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 09:14:11 +0900 Subject: [PATCH 241/241] test(authz): avoid unused subclass binding --- .../tests/test_authorization_decision_runtime_integrity.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py index 59cc26f1d..5bdde6459 100644 --- a/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py +++ b/packages/keyverse-adapter/tests/test_authorization_decision_runtime_integrity.py @@ -92,9 +92,7 @@ def _validate_decision(**overrides: object) -> tuple[object, ...]: def test_decision_cannot_be_subclassed_to_override_runtime_behavior() -> None: """Caller-defined decision classes cannot override validated field semantics.""" with pytest.raises(TypeError, match="AuthorizationDecision must not be subclassed"): - - class _ForgedDecision(AuthorizationDecision): - pass + type("_ForgedDecision", (AuthorizationDecision,), {}) def test_consumer_revalidation_detects_low_level_decision_mutation() -> None: