From 0acf072240712cdc6ea2ee22daba761df3455de1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:02:32 +0900 Subject: [PATCH 01/60] test(workforce-validation): define governed registry read contract --- .../tests/test_registry.py | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_registry.py diff --git a/services/workforce-validation-api/tests/test_registry.py b/services/workforce-validation-api/tests/test_registry.py new file mode 100644 index 00000000..3a80cebd --- /dev/null +++ b/services/workforce-validation-api/tests/test_registry.py @@ -0,0 +1,242 @@ +"""Regression contract for the workforce-validation study registry boundary.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import AuthorizationDeniedError, PurposeBoundAccessPolicy +from orgmetra_workforce_validation_api.registry import ( + ValidationPrincipal, + ValidityStudyIntegrityError, + ValidityStudyNotFound, + ValidityStudyReadPort, + ValidityStudyRecord, + read_validity_study, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000001") +OTHER_TENANT = UUID("10000000-0000-7000-8000-000000000002") +STUDY = UUID("00000000-0000-7000-8000-0000000000c1") +OTHER_STUDY = UUID("00000000-0000-7000-8000-0000000000c2") +CRITERION = UUID("00000000-0000-7000-8000-0000000000a1") +RECORDED_FROM = datetime(2026, 11, 3, tzinfo=timezone.utc) + + +class _ReadPort: + """Return one configured registry record and capture the authorized target.""" + + def __init__(self, result: object) -> None: + self.result = result + self.calls: list[tuple[UUID, UUID]] = [] + + def read_validity_study(self, *, tenant_record_id: UUID, validity_study_id: UUID) -> object: + """Capture the target and return the configured persistence result.""" + self.calls.append((tenant_record_id, validity_study_id)) + return self.result + + +class _NoReadMethod: + """Deliberately fail the runtime repository protocol.""" + + +def _record(*, tenant_record_id: UUID = TENANT, validity_study_id: UUID = STUDY) -> ValidityStudyRecord: + return ValidityStudyRecord( + tenant_record_id=tenant_record_id, + validity_study_id=validity_study_id, + criterion_blueprint_id=CRITERION, + study_status_code="study_draft", + recorded_from=RECORDED_FROM, + recorded_to=None, + ) + + +def _principal(*, tenant_record_id: UUID = TENANT) -> ValidationPrincipal: + return ValidationPrincipal( + tenant_record_id=tenant_record_id, + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ) + + +def _policy(*, tenant_record_id: UUID = TENANT) -> PurposeBoundAccessPolicy: + return PurposeBoundAccessPolicy( + tenant_record_id=tenant_record_id, + policy_version_code="validation-read-v1", + resource_kind="validity_study_record", + purpose_code="validation_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset( + { + "criterion_blueprint_id", + "study_status_code", + "recorded_from", + "recorded_to", + } + ), + ) + + +def test_read_returns_only_authorized_requested_fields() -> None: + port = _ReadPort(_record()) + + view = read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code", "criterion_blueprint_id"}), + policy=_policy(), + read_port=port, + ) + + assert isinstance(port, ValidityStudyReadPort) + assert port.calls == [(TENANT, STUDY)] + assert view.tenant_record_id == TENANT + assert view.validity_study_id == STUDY + assert view.fields == ( + ("criterion_blueprint_id", CRITERION), + ("study_status_code", "study_draft"), + ) + + +def test_authorization_denial_happens_before_persistence() -> None: + port = _ReadPort(_record()) + + with pytest.raises(AuthorizationDeniedError): + read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"recorded_from"}), + policy=PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="validation-read-v1", + resource_kind="validity_study_record", + purpose_code="audit_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset({"recorded_from"}), + ), + read_port=port, + ) + + assert port.calls == [] + + +def test_missing_study_is_not_found() -> None: + with pytest.raises(ValidityStudyNotFound): + read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(), + read_port=_ReadPort(None), + ) + + +def test_foreign_or_noncanonical_persistence_result_fails_closed() -> None: + for result in (_record(tenant_record_id=OTHER_TENANT), _record(validity_study_id=OTHER_STUDY), object()): + with pytest.raises(ValidityStudyIntegrityError): + read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(), + read_port=_ReadPort(result), + ) + + +def test_dependency_and_request_types_fail_before_repository_use() -> None: + port = _ReadPort(_record()) + common = dict( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(), + read_port=port, + ) + + for key, value, error in ( + ("principal", object(), TypeError), + ("policy", object(), TypeError), + ("read_port", _NoReadMethod(), TypeError), + ("tenant_record_id", "not-a-uuid", ValueError), + ("validity_study_id", UUID(int=0), ValueError), + ("purpose_code", "Validation Review", ValueError), + ("requested_fields", set({"study_status_code"}), ValueError), + ("requested_fields", frozenset(), ValueError), + ("requested_fields", frozenset({"unknown_field"}), ValueError), + ): + arguments = dict(common) + arguments[key] = value + with pytest.raises(error): + read_validity_study(**arguments) + + assert port.calls == [] + + +def test_principal_rejects_invalid_identity_and_scope_shapes() -> None: + invalid_values = ( + dict(tenant_record_id=UUID(int=0), actor_reference="person:analyst-1", granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"})), + dict(tenant_record_id=TENANT, actor_reference="not namespaced", granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"})), + dict(tenant_record_id=TENANT, actor_reference="person:analyst-1", granted_scope_codes=frozenset()), + dict(tenant_record_id=TENANT, actor_reference="person:analyst-1", granted_scope_codes=frozenset({"bad-scope"})), + ) + for values in invalid_values: + with pytest.raises(ValueError): + ValidationPrincipal(**values) + + +def test_record_rejects_noncanonical_or_invalid_durable_scalars() -> None: + valid = dict( + tenant_record_id=TENANT, + validity_study_id=STUDY, + criterion_blueprint_id=CRITERION, + study_status_code="study_draft", + recorded_from=RECORDED_FROM, + recorded_to=None, + ) + cases = ( + ("tenant_record_id", UUID(int=0)), + ("validity_study_id", "not-a-uuid"), + ("criterion_blueprint_id", UUID(int=(1 << 128) - 1)), + ("study_status_code", "Study Draft"), + ("recorded_from", datetime(2026, 11, 3)), + ("recorded_to", "not-a-datetime"), + ) + for field_name, value in cases: + arguments = dict(valid) + arguments[field_name] = value + with pytest.raises(ValueError): + ValidityStudyRecord(**arguments) + + with pytest.raises(ValueError): + ValidityStudyRecord(**{**valid, "recorded_to": RECORDED_FROM}) + + +def test_valid_record_detaches_times_to_utc() -> None: + offset = timezone.utc + record = ValidityStudyRecord( + tenant_record_id=TENANT, + validity_study_id=STUDY, + criterion_blueprint_id=CRITERION, + study_status_code="study_draft", + recorded_from=datetime(2026, 11, 3, 9, tzinfo=offset), + recorded_to=datetime(2026, 11, 4, 9, tzinfo=offset), + ) + + assert type(record.recorded_from) is datetime + assert record.recorded_from.tzinfo is timezone.utc + assert record.recorded_to is not None + assert record.recorded_to.tzinfo is timezone.utc From 49662679f5d5e670f18f8bd64253187848532904 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:03:28 +0900 Subject: [PATCH 02/60] feat(workforce-validation): add governed study registry read boundary --- .../registry.py | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py new file mode 100644 index 00000000..5d38116f --- /dev/null +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -0,0 +1,246 @@ +"""Purpose-bound application boundary for the workforce-validation study registry. + +This module deliberately stops before PostgreSQL. The protected foundation still +stores validity-study tables in the legacy foundation schema, while +``ARCHITECTURE.md`` assigns persistence ownership to ``workforce_validation``. +The application contract therefore depends on an owner repository port instead +of normalizing direct cross-context SQL into a long-lived service contract. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime, timezone +import re +from typing import Protocol, runtime_checkable +from uuid import UUID +from zoneinfo import ZoneInfo + +from orgmetra_keyverse_adapter import ( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + require_purpose_bound_access, +) + +_MAX_UUID_INT = (1 << 128) - 1 +_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$") +_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,}$") +_RESOURCE_KIND = "validity_study_record" +_OPERATION = "read" +_READ_FIELDS = frozenset( + { + "criterion_blueprint_id", + "study_status_code", + "recorded_from", + "recorded_to", + } +) + + +class ValidityStudyNotFound(LookupError): + """Indicate that an authorized study identity has no visible registry record.""" + + +class ValidityStudyIntegrityError(RuntimeError): + """Indicate that persistence returned a record outside the authorized target.""" + + +def _require_operational_uuid(field_name: str, value: object) -> UUID: + """Return one exact operational UUID and reject protocol sentinels or subtypes.""" + if type(value) is not UUID or value.int in (0, _MAX_UUID_INT): + raise ValueError(f"{field_name} must be an exact operational UUID.") + return value + + +def _require_code(field_name: str, value: object) -> str: + """Return one exact lower-snake-case code used in an auditable policy request.""" + if type(value) is not str or _CODE_PATTERN.fullmatch(value) is None: + raise ValueError(f"{field_name} must be an exact lower snake_case code.") + return value + + +def _require_aware_datetime(field_name: str, value: object) -> datetime: + """Detach one durable timestamp to exact UTC without arbitrary timezone callbacks.""" + if type(value) is not datetime: + raise ValueError(f"{field_name} must be an exact datetime.") + provider = value.tzinfo + if type(provider) is not timezone and type(provider) is not ZoneInfo: + raise ValueError(f"{field_name} must use a standard-library timezone provider.") + if value.utcoffset() is None: + raise ValueError(f"{field_name} must be timezone-aware.") + return value.astimezone(timezone.utc) + + +def _validate_scope_set(values: object) -> frozenset[str]: + """Require immutable explicit Keyverse scopes before constructing an access request.""" + if type(values) is not frozenset or not values: + raise ValueError("granted_scope_codes must be a non-empty exact frozenset.") + 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 exact Orgmetra scopes.") + return values + + +def _validate_requested_fields(values: object) -> frozenset[str]: + """Require a non-empty immutable subset of the published registry read fields.""" + if type(values) is not frozenset or not values: + raise ValueError("requested_fields must be a non-empty exact frozenset.") + if any(type(value) is not str for value in values) or not values.issubset(_READ_FIELDS): + raise ValueError("requested_fields contains a field outside the validity-study registry contract.") + return values + + +@dataclass(frozen=True, slots=True) +class ValidationPrincipal: + """Authenticated Keyverse identity attributes needed by the validation context. + + The bearer credential itself never enters this value. ``actor_reference`` is + an opaque namespaced reference and scopes are the already-authenticated token + scopes supplied by the product authentication boundary. + """ + + tenant_record_id: UUID + actor_reference: str + granted_scope_codes: frozenset[str] + + def __post_init__(self) -> None: + """Reject malformed or mutable identity attributes before authorization.""" + _require_operational_uuid("tenant_record_id", self.tenant_record_id) + if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: + raise ValueError("actor_reference must be an exact namespaced opaque reference.") + _validate_scope_set(self.granted_scope_codes) + + +@dataclass(frozen=True, slots=True) +class ValidityStudyRecord: + """Canonical owner-side projection of one recorded validity-study header. + + This value intentionally contains only fields already represented by the + protected foundation schema. Predictor, sample, decision-policy and analysis + protocol versions are not invented here; Issue #234 owns that later scientific + model increment. + """ + + tenant_record_id: UUID + validity_study_id: UUID + criterion_blueprint_id: UUID + study_status_code: str + recorded_from: datetime + recorded_to: datetime | None + + def __post_init__(self) -> None: + """Detach durable scalar evidence before the application layer exposes it.""" + _require_operational_uuid("tenant_record_id", self.tenant_record_id) + _require_operational_uuid("validity_study_id", self.validity_study_id) + _require_operational_uuid("criterion_blueprint_id", self.criterion_blueprint_id) + _require_code("study_status_code", self.study_status_code) + recorded_from = _require_aware_datetime("recorded_from", self.recorded_from) + recorded_to = ( + None + if self.recorded_to is None + else _require_aware_datetime("recorded_to", self.recorded_to) + ) + if recorded_to is not None and recorded_to <= recorded_from: + raise ValueError("recorded_to must be later than recorded_from.") + object.__setattr__(self, "recorded_from", recorded_from) + object.__setattr__(self, "recorded_to", recorded_to) + + +@dataclass(frozen=True, slots=True) +class ValidityStudyView: + """Field-minimized authorized view returned to the gateway or role workspace.""" + + tenant_record_id: UUID + validity_study_id: UUID + fields: tuple[tuple[str, object], ...] + + +@runtime_checkable +class ValidityStudyReadPort(Protocol): + """Owner repository contract for one tenant-local validity-study header.""" + + def read_validity_study( + self, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + ) -> ValidityStudyRecord | None: + """Return one visible owner record or ``None`` without crossing service tables.""" + ... + + +def read_validity_study( + *, + principal: ValidationPrincipal, + tenant_record_id: UUID, + validity_study_id: UUID, + purpose_code: str, + requested_fields: frozenset[str], + policy: PurposeBoundAccessPolicy, + read_port: ValidityStudyReadPort, +) -> ValidityStudyView: + """Authorize and read one validity-study header through the canonical owner port. + + Authorization is completed before persistence. The persistence result is then + reconstructed into an exact immutable value and must match the authorized + tenant/study identity before any field is returned. + """ + if type(principal) is not ValidationPrincipal: + raise TypeError("principal must be an exact ValidationPrincipal.") + if type(policy) is not PurposeBoundAccessPolicy: + raise TypeError("policy must be an exact PurposeBoundAccessPolicy.") + if not isinstance(read_port, ValidityStudyReadPort): + raise TypeError("read_port must implement ValidityStudyReadPort.") + + tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) + study_id = _require_operational_uuid("validity_study_id", validity_study_id) + purpose = _require_code("purpose_code", purpose_code) + fields = _validate_requested_fields(requested_fields) + + require_purpose_bound_access( + request=PurposeBoundAccessRequest( + tenant_record_id=tenant_id, + actor_tenant_record_id=principal.tenant_record_id, + resource_tenant_record_id=tenant_id, + actor_reference=principal.actor_reference, + resource_reference=f"{_RESOURCE_KIND}:{study_id}", + purpose_code=purpose, + operation_code=_OPERATION, + resource_kind=_RESOURCE_KIND, + requested_fields=fields, + granted_scope_codes=principal.granted_scope_codes, + ), + policy=policy, + ) + + persisted = read_port.read_validity_study( + tenant_record_id=tenant_id, + validity_study_id=study_id, + ) + if persisted is None: + raise ValidityStudyNotFound(str(study_id)) + if type(persisted) is not ValidityStudyRecord: + raise ValidityStudyIntegrityError("repository returned a non-canonical validity-study record") + + record = ValidityStudyRecord( + tenant_record_id=persisted.tenant_record_id, + validity_study_id=persisted.validity_study_id, + criterion_blueprint_id=persisted.criterion_blueprint_id, + study_status_code=persisted.study_status_code, + recorded_from=persisted.recorded_from, + recorded_to=persisted.recorded_to, + ) + if record.tenant_record_id != tenant_id or record.validity_study_id != study_id: + raise ValidityStudyIntegrityError("repository returned a validity-study record for another target") + + values = { + "criterion_blueprint_id": record.criterion_blueprint_id, + "study_status_code": record.study_status_code, + "recorded_from": record.recorded_from, + "recorded_to": record.recorded_to, + } + return ValidityStudyView( + tenant_record_id=tenant_id, + validity_study_id=study_id, + fields=tuple((field_name, values[field_name]) for field_name in sorted(fields)), + ) From 3de387e0f2a82cc122f70d2c28343913bb9e55b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:03:48 +0900 Subject: [PATCH 03/60] feat(workforce-validation): export registry owner contract --- .../__init__.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 services/workforce-validation-api/src/orgmetra_workforce_validation_api/__init__.py diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/__init__.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/__init__.py new file mode 100644 index 00000000..48570a97 --- /dev/null +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/__init__.py @@ -0,0 +1,21 @@ +"""Canonical workforce-validation application contracts for Orgmetra.""" + +from orgmetra_workforce_validation_api.registry import ( + ValidationPrincipal, + ValidityStudyIntegrityError, + ValidityStudyNotFound, + ValidityStudyReadPort, + ValidityStudyRecord, + ValidityStudyView, + read_validity_study, +) + +__all__ = [ + "ValidationPrincipal", + "ValidityStudyIntegrityError", + "ValidityStudyNotFound", + "ValidityStudyReadPort", + "ValidityStudyRecord", + "ValidityStudyView", + "read_validity_study", +] From 844c3bcf66968e583846a1d08bbea35fee6bbe6b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:03:57 +0900 Subject: [PATCH 04/60] build(workforce-validation): define covered service package --- .../workforce-validation-api/pyproject.toml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 services/workforce-validation-api/pyproject.toml diff --git a/services/workforce-validation-api/pyproject.toml b/services/workforce-validation-api/pyproject.toml new file mode 100644 index 00000000..9a84581c --- /dev/null +++ b/services/workforce-validation-api/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["setuptools==82.0.1"] +build-backend = "setuptools.build_meta" + +[project] +name = "orgmetra-workforce-validation-api" +version = "0.1.0" +description = "Purpose-bound owner boundary for Orgmetra workforce-validation studies." +readme = "README.md" +requires-python = ">=3.11" +license = { text = "Apache-2.0" } +authors = [{ name = "ContextualWisdomLab" }] +dependencies = [ + "orgmetra-keyverse-adapter==0.1.0", +] + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +orgmetra_workforce_validation_api = ["py.typed"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = [ + "--cov=orgmetra_workforce_validation_api", + "--cov-branch", + "--cov-report=term-missing", + "--cov-fail-under=100", +] + +[tool.coverage.run] +branch = true +source = ["orgmetra_workforce_validation_api"] + +[tool.coverage.report] +fail_under = 100 +show_missing = true From fbd4d9dd339d794b5736d2a12b453953560af936 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:04:11 +0900 Subject: [PATCH 05/60] docs(workforce-validation): document registry owner slice --- services/workforce-validation-api/README.md | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 services/workforce-validation-api/README.md diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md new file mode 100644 index 00000000..338f6edc --- /dev/null +++ b/services/workforce-validation-api/README.md @@ -0,0 +1,31 @@ +# Orgmetra Workforce Validation API + +This package is the application boundary for the `workforce_validation` bounded context. The first slice exposes one purpose-bound read use case for the existing validity-study registry header. + +It does **not** query People, Talent Acquisition, Performance Management, Job Architecture, Psychometrics Commons, fast-mlsirm, or TEPP tables. Those contexts remain separate owners. Exact foreign identifiers and immutable specialist result references cross the boundary only through published contracts. + +## Current slice + +`read_validity_study(...)`: + +- accepts authenticated Keyverse identity attributes, not credentials; +- evaluates tenant, purpose, operation, scope, resource, and requested fields before persistence; +- calls only a `ValidityStudyReadPort` owned by this context; +- reconstructs and validates durable registry scalars before returning them; +- returns only the fields authorized for the exact study record. + +The repository port is intentionally abstract in this increment. Protected foundation migrations still create the validity-study tables in the legacy foundation schema while `ARCHITECTURE.md` assigns them to the `workforce_validation` schema and database role. A direct `public.validity_study` adapter here would turn that implementation drift into a new long-lived service contract. + +Issue #234 owns the next order: service-owned schema/role, durable PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. + +## Test + +Once this service is admitted to Foundation CI, its contract is: + +```bash +PYTHONPATH=services/workforce-validation-api/src:packages/keyverse-adapter/src \ + python -m pytest -c services/workforce-validation-api/pyproject.toml \ + services/workforce-validation-api/tests +``` + +The package declares 100% owned statement and branch coverage. Until the repository-wide Foundation writer includes this command and the exact head is GREEN, this slice remains Draft evidence rather than shipped product truth. From fbbbbe9a0284c87a99fdc15f70a4cf24f9e0190f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:04:22 +0900 Subject: [PATCH 06/60] build(workforce-validation): mark typed package --- .../src/orgmetra_workforce_validation_api/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 services/workforce-validation-api/src/orgmetra_workforce_validation_api/py.typed diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/py.typed b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/py.typed new file mode 100644 index 00000000..e69de29b From 3b2fa294b48feaed75240dfae4a9fc16ac87e103 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:05:13 +0900 Subject: [PATCH 07/60] test(workforce-validation): remove unreachable timezone branch --- .../src/orgmetra_workforce_validation_api/registry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 5d38116f..ad7fcd3e 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -67,8 +67,6 @@ def _require_aware_datetime(field_name: str, value: object) -> datetime: provider = value.tzinfo if type(provider) is not timezone and type(provider) is not ZoneInfo: raise ValueError(f"{field_name} must use a standard-library timezone provider.") - if value.utcoffset() is None: - raise ValueError(f"{field_name} must be timezone-aware.") return value.astimezone(timezone.utc) From b59f9cadfab1d4571efd62d805b9d319f9cb9741 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:06:11 +0900 Subject: [PATCH 08/60] test(workforce-validation): cover exact scalar and timezone branches --- .../tests/test_registry.py | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/services/workforce-validation-api/tests/test_registry.py b/services/workforce-validation-api/tests/test_registry.py index 3a80cebd..e0da59bf 100644 --- a/services/workforce-validation-api/tests/test_registry.py +++ b/services/workforce-validation-api/tests/test_registry.py @@ -2,8 +2,9 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from uuid import UUID +from zoneinfo import ZoneInfo import pytest @@ -174,9 +175,11 @@ def test_dependency_and_request_types_fail_before_repository_use() -> None: ("tenant_record_id", "not-a-uuid", ValueError), ("validity_study_id", UUID(int=0), ValueError), ("purpose_code", "Validation Review", ValueError), + ("purpose_code", 7, ValueError), ("requested_fields", set({"study_status_code"}), ValueError), ("requested_fields", frozenset(), ValueError), ("requested_fields", frozenset({"unknown_field"}), ValueError), + ("requested_fields", frozenset({7}), ValueError), ): arguments = dict(common) arguments[key] = value @@ -190,8 +193,10 @@ def test_principal_rejects_invalid_identity_and_scope_shapes() -> None: invalid_values = ( dict(tenant_record_id=UUID(int=0), actor_reference="person:analyst-1", granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"})), dict(tenant_record_id=TENANT, actor_reference="not namespaced", granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"})), + dict(tenant_record_id=TENANT, actor_reference=7, granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"})), dict(tenant_record_id=TENANT, actor_reference="person:analyst-1", granted_scope_codes=frozenset()), dict(tenant_record_id=TENANT, actor_reference="person:analyst-1", granted_scope_codes=frozenset({"bad-scope"})), + dict(tenant_record_id=TENANT, actor_reference="person:analyst-1", granted_scope_codes=frozenset({7})), ) for values in invalid_values: with pytest.raises(ValueError): @@ -212,6 +217,7 @@ def test_record_rejects_noncanonical_or_invalid_durable_scalars() -> None: ("validity_study_id", "not-a-uuid"), ("criterion_blueprint_id", UUID(int=(1 << 128) - 1)), ("study_status_code", "Study Draft"), + ("study_status_code", 7), ("recorded_from", datetime(2026, 11, 3)), ("recorded_to", "not-a-datetime"), ) @@ -225,18 +231,19 @@ def test_record_rejects_noncanonical_or_invalid_durable_scalars() -> None: ValidityStudyRecord(**{**valid, "recorded_to": RECORDED_FROM}) -def test_valid_record_detaches_times_to_utc() -> None: - offset = timezone.utc - record = ValidityStudyRecord( - tenant_record_id=TENANT, - validity_study_id=STUDY, - criterion_blueprint_id=CRITERION, - study_status_code="study_draft", - recorded_from=datetime(2026, 11, 3, 9, tzinfo=offset), - recorded_to=datetime(2026, 11, 4, 9, tzinfo=offset), - ) +def test_valid_record_detaches_supported_timezones_to_utc() -> None: + for provider in (timezone(timedelta(hours=9)), ZoneInfo("Asia/Seoul")): + record = ValidityStudyRecord( + tenant_record_id=TENANT, + validity_study_id=STUDY, + criterion_blueprint_id=CRITERION, + study_status_code="study_draft", + recorded_from=datetime(2026, 11, 3, 9, tzinfo=provider), + recorded_to=datetime(2026, 11, 4, 9, tzinfo=provider), + ) - assert type(record.recorded_from) is datetime - assert record.recorded_from.tzinfo is timezone.utc - assert record.recorded_to is not None - assert record.recorded_to.tzinfo is timezone.utc + assert type(record.recorded_from) is datetime + assert record.recorded_from.tzinfo is timezone.utc + assert record.recorded_from.hour == 0 + assert record.recorded_to is not None + assert record.recorded_to.tzinfo is timezone.utc From b22383c27ec3da6c8111e78fc862363e84399822 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:09:02 +0900 Subject: [PATCH 09/60] test(workforce-validation): pin policy scalar runtime integrity --- .../tests/test_policy_runtime_integrity.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_policy_runtime_integrity.py diff --git a/services/workforce-validation-api/tests/test_policy_runtime_integrity.py b/services/workforce-validation-api/tests/test_policy_runtime_integrity.py new file mode 100644 index 00000000..b3071641 --- /dev/null +++ b/services/workforce-validation-api/tests/test_policy_runtime_integrity.py @@ -0,0 +1,74 @@ +"""Regression for executable policy scalar values at the validation boundary.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy +from orgmetra_workforce_validation_api.registry import ( + ValidationPrincipal, + ValidityStudyReadPort, + read_validity_study, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000001") +STUDY = UUID("00000000-0000-7000-8000-0000000000c1") + + +class _ExecutableText(str): + """Trip if authorization compares this caller-defined string subtype.""" + + calls = 0 + + def __ne__(self, other: object) -> bool: + """Expose any comparison before the boundary rejects the subtype.""" + type(self).calls += 1 + raise AssertionError("caller-defined policy comparison executed") + + +class _ReadPort: + """Record whether persistence was reached.""" + + def __init__(self) -> None: + self.calls = 0 + + def read_validity_study(self, *, tenant_record_id: UUID, validity_study_id: UUID) -> None: + """Fail the test if a rejected policy reaches persistence.""" + del tenant_record_id, validity_study_id + self.calls += 1 + return None + + +def test_policy_text_subtype_is_rejected_before_comparison_or_persistence() -> None: + _ExecutableText.calls = 0 + port = _ReadPort() + assert isinstance(port, ValidityStudyReadPort) + policy = PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="validation-read-v1", + resource_kind=_ExecutableText("validity_study_record"), + purpose_code="validation_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset({"study_status_code"}), + ) + + with pytest.raises(ValueError, match="policy resource_kind"): + read_validity_study( + principal=ValidationPrincipal( + tenant_record_id=TENANT, + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=policy, + read_port=port, + ) + + assert _ExecutableText.calls == 0 + assert port.calls == 0 From 3fe809250c86b328dedf3cb46c3d5953966cfc72 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:09:44 +0900 Subject: [PATCH 10/60] fix(workforce-validation): detach exact policy evidence before evaluation --- .../registry.py | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index ad7fcd3e..dcdf7386 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -88,6 +88,50 @@ def _validate_requested_fields(values: object) -> frozenset[str]: return values +def _detach_policy(policy: PurposeBoundAccessPolicy) -> PurposeBoundAccessPolicy: + """Copy policy evidence into exact inert values before any authorization comparison. + + The protected Keyverse adapter accepts subclass-compatible scalar inputs for + backward compatibility. This owner boundary is stricter because a caller- + defined ``str``/``UUID`` subtype could otherwise execute Python behavior when + the evaluator compares or hashes policy attributes. Locals snapshot each + immutable value first; the reconstructed exact policy is the only one used by + authorization. + """ + 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 + + _require_operational_uuid("policy tenant_record_id", tenant_record_id) + for field_name, value in ( + ("policy_version_code", policy_version_code), + ("resource_kind", resource_kind), + ("purpose_code", purpose_code), + ("operation_code", operation_code), + ("required_scope_code", required_scope_code), + ): + if type(value) is not str: + raise ValueError(f"policy {field_name} must be an exact string.") + if type(permitted_fields) is not frozenset or any( + type(value) is not str for value in permitted_fields + ): + raise ValueError("policy permitted_fields must contain exact strings in an exact frozenset.") + + return PurposeBoundAccessPolicy( + tenant_record_id=tenant_record_id, + policy_version_code=policy_version_code, + resource_kind=resource_kind, + purpose_code=purpose_code, + operation_code=operation_code, + required_scope_code=required_scope_code, + permitted_fields=permitted_fields, + ) + + @dataclass(frozen=True, slots=True) class ValidationPrincipal: """Authenticated Keyverse identity attributes needed by the validation context. @@ -194,6 +238,7 @@ def read_validity_study( study_id = _require_operational_uuid("validity_study_id", validity_study_id) purpose = _require_code("purpose_code", purpose_code) fields = _validate_requested_fields(requested_fields) + detached_policy = _detach_policy(policy) require_purpose_bound_access( request=PurposeBoundAccessRequest( @@ -208,7 +253,7 @@ def read_validity_study( requested_fields=fields, granted_scope_codes=principal.granted_scope_codes, ), - policy=policy, + policy=detached_policy, ) persisted = read_port.read_validity_study( From 2b7cfe47c85138d82737de1a0059b99836d182b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:23:06 +0900 Subject: [PATCH 11/60] test(ci): require workforce validation coverage in foundation --- tests/test_foundation_ci_dependency_hygiene.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_foundation_ci_dependency_hygiene.sh b/tests/test_foundation_ci_dependency_hygiene.sh index 2c0f5087..d214695f 100644 --- a/tests/test_foundation_ci_dependency_hygiene.sh +++ b/tests/test_foundation_ci_dependency_hygiene.sh @@ -18,6 +18,7 @@ expected_pythonpaths=( "packages/selection-review/src" "services/job-analysis-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src" "services/people-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src" + "services/workforce-validation-api/src:packages/keyverse-adapter/src" ) if ! grep -Fq -- "${expected_install}" "${workflow_path}"; then From b7e23cb071d678c26b79763ea821e406ab54f2bb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:23:29 +0900 Subject: [PATCH 12/60] ci(validation): run workforce validation service coverage --- .github/workflows/foundation-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/foundation-ci.yml b/.github/workflows/foundation-ci.yml index 6b475d6f..51c98cf3 100644 --- a/.github/workflows/foundation-ci.yml +++ b/.github/workflows/foundation-ci.yml @@ -68,6 +68,7 @@ jobs: PYTHONPATH=packages/selection-review/src COVERAGE_FILE=/tmp/orgmetra-selection-review.coverage python -m pytest -c packages/selection-review/pyproject.toml packages/selection-review/tests PYTHONPATH=services/job-analysis-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src COVERAGE_FILE=/tmp/orgmetra-job-analysis-api.coverage python -m pytest -c services/job-analysis-api/pyproject.toml services/job-analysis-api/tests PYTHONPATH=services/people-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src COVERAGE_FILE=/tmp/orgmetra-people-api.coverage python -m pytest -c services/people-api/pyproject.toml services/people-api/tests + PYTHONPATH=services/workforce-validation-api/src:packages/keyverse-adapter/src COVERAGE_FILE=/tmp/orgmetra-workforce-validation-api.coverage python -m pytest -c services/workforce-validation-api/pyproject.toml services/workforce-validation-api/tests - name: Run PostgreSQL contracts in isolated containers env: PGPASSWORD: orgmetra From 0daaf12a0785d94f243b36c0c99c1d5358594242 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:25:38 +0900 Subject: [PATCH 13/60] test(validation): require structurally immutable study records --- services/workforce-validation-api/tests/test_registry.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/workforce-validation-api/tests/test_registry.py b/services/workforce-validation-api/tests/test_registry.py index e0da59bf..aef2e34d 100644 --- a/services/workforce-validation-api/tests/test_registry.py +++ b/services/workforce-validation-api/tests/test_registry.py @@ -247,3 +247,12 @@ def test_valid_record_detaches_supported_timezones_to_utc() -> None: assert record.recorded_from.hour == 0 assert record.recorded_to is not None assert record.recorded_to.tzinfo is timezone.utc + + +def test_record_is_structurally_immutable_against_object_setattr() -> None: + record = _record() + + with pytest.raises(AttributeError): + object.__setattr__(record, "study_status_code", "study_closed") + + assert record.study_status_code == "study_draft" From b609b0a46b835e4ba4c7f46f51088c5673dcbdb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:27:17 +0900 Subject: [PATCH 14/60] fix(validation): make study records structurally immutable --- .../registry.py | 91 +++++++++++++------ 1 file changed, 64 insertions(+), 27 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index dcdf7386..70d7f73d 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -153,39 +153,76 @@ def __post_init__(self) -> None: _validate_scope_set(self.granted_scope_codes) -@dataclass(frozen=True, slots=True) -class ValidityStudyRecord: - """Canonical owner-side projection of one recorded validity-study header. - - This value intentionally contains only fields already represented by the - protected foundation schema. Predictor, sample, decision-policy and analysis - protocol versions are not invented here; Issue #234 owns that later scientific - model increment. +class ValidityStudyRecord(tuple): + """Structurally immutable owner projection of one recorded validity-study header. + + The tuple-backed representation prevents a repository adapter that retains an + accepted record from rewriting durable study evidence through + ``object.__setattr__`` after construction. Only fields already represented by + the protected foundation schema are carried here. Predictor, sample, + decision-policy and analysis-protocol versions remain a later scientific-model + increment owned by Issue #234. """ - tenant_record_id: UUID - validity_study_id: UUID - criterion_blueprint_id: UUID - study_status_code: str - recorded_from: datetime - recorded_to: datetime | None + __slots__ = () - def __post_init__(self) -> None: - """Detach durable scalar evidence before the application layer exposes it.""" - _require_operational_uuid("tenant_record_id", self.tenant_record_id) - _require_operational_uuid("validity_study_id", self.validity_study_id) - _require_operational_uuid("criterion_blueprint_id", self.criterion_blueprint_id) - _require_code("study_status_code", self.study_status_code) - recorded_from = _require_aware_datetime("recorded_from", self.recorded_from) - recorded_to = ( + def __new__( + cls, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + criterion_blueprint_id: UUID, + study_status_code: str, + recorded_from: datetime, + recorded_to: datetime | None, + ) -> ValidityStudyRecord: + """Validate and detach durable scalars before creating the immutable tuple.""" + tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) + study_id = _require_operational_uuid("validity_study_id", validity_study_id) + criterion_id = _require_operational_uuid("criterion_blueprint_id", criterion_blueprint_id) + status_code = _require_code("study_status_code", study_status_code) + recorded_start = _require_aware_datetime("recorded_from", recorded_from) + recorded_end = ( None - if self.recorded_to is None - else _require_aware_datetime("recorded_to", self.recorded_to) + if recorded_to is None + else _require_aware_datetime("recorded_to", recorded_to) ) - if recorded_to is not None and recorded_to <= recorded_from: + if recorded_end is not None and recorded_end <= recorded_start: raise ValueError("recorded_to must be later than recorded_from.") - object.__setattr__(self, "recorded_from", recorded_from) - object.__setattr__(self, "recorded_to", recorded_to) + return tuple.__new__( + cls, + (tenant_id, study_id, criterion_id, status_code, recorded_start, recorded_end), + ) + + @property + def tenant_record_id(self) -> UUID: + """Return the tenant that owns this validity study.""" + return self[0] + + @property + def validity_study_id(self) -> UUID: + """Return the stable validity-study identity.""" + return self[1] + + @property + def criterion_blueprint_id(self) -> UUID: + """Return the criterion blueprint linked to the study header.""" + return self[2] + + @property + def study_status_code(self) -> str: + """Return the governed study lifecycle status code.""" + return self[3] + + @property + def recorded_from(self) -> datetime: + """Return the exact UTC instant when this version became recorded truth.""" + return self[4] + + @property + def recorded_to(self) -> datetime | None: + """Return the exact UTC close instant when present.""" + return self[5] @dataclass(frozen=True, slots=True) From 8c50d7d4517781c5a3801bc2c11b3ee077068341 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:32:10 +0900 Subject: [PATCH 15/60] fix(ci): seal workforce validation workflow manifest --- manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index f7b6cf55..7fbc02be 100644 --- a/manifest.json +++ b/manifest.json @@ -5,9 +5,9 @@ "files": [ { "path": ".github/workflows/foundation-ci.yml", - "sha256": "b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7", - "bytes": 6651, - "lines": 125 + "sha256": "80b9e4c1b2c6c04983f195cd3b4ec3760d30fe15317cbcfcd5c6ba57089024cc", + "bytes": 6911, + "lines": 126 }, { "path": ".gitignore", From cf498b96c0308e5037ebe11441e77c3677b0bdac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:33:08 +0900 Subject: [PATCH 16/60] docs(validation): keep Foundation acceptance state current --- services/workforce-validation-api/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index 338f6edc..593c2f8d 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -11,7 +11,7 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar - accepts authenticated Keyverse identity attributes, not credentials; - evaluates tenant, purpose, operation, scope, resource, and requested fields before persistence; - calls only a `ValidityStudyReadPort` owned by this context; -- reconstructs and validates durable registry scalars before returning them; +- reconstructs persisted registry scalars into structurally immutable owner evidence before target validation and output; - returns only the fields authorized for the exact study record. The repository port is intentionally abstract in this increment. Protected foundation migrations still create the validity-study tables in the legacy foundation schema while `ARCHITECTURE.md` assigns them to the `workforce_validation` schema and database role. A direct `public.validity_study` adapter here would turn that implementation drift into a new long-lived service contract. @@ -20,12 +20,13 @@ Issue #234 owns the next order: service-owned schema/role, durable PostgreSQL ad ## Test -Once this service is admitted to Foundation CI, its contract is: +The Draft branch is admitted to the canonical Foundation quality workflow with the same hash-locked test toolchain and direct source-tree dependency policy used by the existing owner services: ```bash PYTHONPATH=services/workforce-validation-api/src:packages/keyverse-adapter/src \ + COVERAGE_FILE=/tmp/orgmetra-workforce-validation-api.coverage \ python -m pytest -c services/workforce-validation-api/pyproject.toml \ services/workforce-validation-api/tests ``` -The package declares 100% owned statement and branch coverage. Until the repository-wide Foundation writer includes this command and the exact head is GREEN, this slice remains Draft evidence rather than shipped product truth. +The package declares 100% owned statement and branch coverage. Source-level workflow admission is not acceptance evidence by itself: this slice remains Draft until that command and the repository gates are terminal GREEN on the exact current head and qualifying independent review is satisfied. From 38536298b0da4969262c232305af19628328005c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:08:40 +0900 Subject: [PATCH 17/60] test(workforce-validation): reject retained principal rewrites --- .../tests/test_registry.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/services/workforce-validation-api/tests/test_registry.py b/services/workforce-validation-api/tests/test_registry.py index aef2e34d..02b8661f 100644 --- a/services/workforce-validation-api/tests/test_registry.py +++ b/services/workforce-validation-api/tests/test_registry.py @@ -203,6 +203,22 @@ def test_principal_rejects_invalid_identity_and_scope_shapes() -> None: ValidationPrincipal(**values) +def test_principal_is_structurally_immutable_after_identity_validation() -> None: + principal = _principal() + + for field_name, replacement in ( + ("tenant_record_id", OTHER_TENANT), + ("actor_reference", "person:attacker-2"), + ("granted_scope_codes", frozenset({"orgmetra.audit.read"})), + ): + with pytest.raises(AttributeError): + object.__setattr__(principal, field_name, replacement) + + assert principal.tenant_record_id == TENANT + assert principal.actor_reference == "person:analyst-1" + assert principal.granted_scope_codes == frozenset({"orgmetra.workforce_validation.read"}) + + def test_record_rejects_noncanonical_or_invalid_durable_scalars() -> None: valid = dict( tenant_record_id=TENANT, From e0ff34701967d7b4c29c13f42b3989e05f68dbab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:09:31 +0900 Subject: [PATCH 18/60] fix(workforce-validation): make principal evidence immutable --- .../registry.py | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 70d7f73d..523aef54 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -132,25 +132,44 @@ def _detach_policy(policy: PurposeBoundAccessPolicy) -> PurposeBoundAccessPolicy ) -@dataclass(frozen=True, slots=True) -class ValidationPrincipal: - """Authenticated Keyverse identity attributes needed by the validation context. +class ValidationPrincipal(tuple): + """Structurally immutable authenticated Keyverse attributes for validation reads. - The bearer credential itself never enters this value. ``actor_reference`` is - an opaque namespaced reference and scopes are the already-authenticated token - scopes supplied by the product authentication boundary. + The bearer credential itself never enters this value. Tuple-backed storage + prevents a retained caller reference from rewriting tenant, actor, or scope + evidence through ``object.__setattr__`` after constructor validation. """ - tenant_record_id: UUID - actor_reference: str - granted_scope_codes: frozenset[str] + __slots__ = () - def __post_init__(self) -> None: - """Reject malformed or mutable identity attributes before authorization.""" - _require_operational_uuid("tenant_record_id", self.tenant_record_id) - if type(self.actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(self.actor_reference) is None: + def __new__( + cls, + *, + tenant_record_id: UUID, + actor_reference: str, + granted_scope_codes: frozenset[str], + ) -> ValidationPrincipal: + """Validate exact identity evidence before creating the immutable principal.""" + tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) + if type(actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(actor_reference) is None: raise ValueError("actor_reference must be an exact namespaced opaque reference.") - _validate_scope_set(self.granted_scope_codes) + scope_codes = _validate_scope_set(granted_scope_codes) + return tuple.__new__(cls, (tenant_id, actor_reference, scope_codes)) + + @property + def tenant_record_id(self) -> UUID: + """Return the authenticated tenant identity.""" + return self[0] + + @property + def actor_reference(self) -> str: + """Return the opaque authenticated actor reference.""" + return self[1] + + @property + def granted_scope_codes(self) -> frozenset[str]: + """Return the immutable authenticated scope set.""" + return self[2] class ValidityStudyRecord(tuple): From d42eb025c96630f81fc0ae69ea12335110b43691 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:10:11 +0900 Subject: [PATCH 19/60] test(workforce-validation): require owner persistence boundary --- .../tests/test_persistence_layout.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_persistence_layout.py diff --git a/services/workforce-validation-api/tests/test_persistence_layout.py b/services/workforce-validation-api/tests/test_persistence_layout.py new file mode 100644 index 00000000..aba6931f --- /dev/null +++ b/services/workforce-validation-api/tests/test_persistence_layout.py @@ -0,0 +1,32 @@ +"""Architecture contract for workforce-validation-owned PostgreSQL persistence.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +MIGRATION = ROOT / "services/workforce-validation-api/database/migrations/0001_owner_schema.sql" + + +def test_owner_schema_migration_establishes_deny_default_role_boundary() -> None: + """Require a service-owned schema and least-privilege database role before adapters.""" + sql = MIGRATION.read_text(encoding="utf-8") + + required = ( + "CREATE ROLE workforce_validation_role NOLOGIN", + "CREATE SCHEMA workforce_validation AUTHORIZATION workforce_validation_role", + "REVOKE ALL ON SCHEMA workforce_validation FROM PUBLIC", + "ALTER ROLE workforce_validation_role SET search_path = workforce_validation, pg_catalog", + ) + for contract in required: + assert contract in sql + + assert "CREATE TABLE" not in sql + assert "public.validity_study" not in sql + assert "GRANT ALL" not in sql + + +def test_owner_migration_history_is_bounded_context_local() -> None: + """Prevent a new global migration number from colliding with other active lanes.""" + relative_path = MIGRATION.relative_to(ROOT).as_posix() + + assert relative_path == "services/workforce-validation-api/database/migrations/0001_owner_schema.sql" From 9dfeca7a7eb77d9df1f900ac28c30663630cc13d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:10:30 +0900 Subject: [PATCH 20/60] feat(workforce-validation): establish owner schema and role --- .../database/migrations/0001_owner_schema.sql | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 services/workforce-validation-api/database/migrations/0001_owner_schema.sql diff --git a/services/workforce-validation-api/database/migrations/0001_owner_schema.sql b/services/workforce-validation-api/database/migrations/0001_owner_schema.sql new file mode 100644 index 00000000..3d24b984 --- /dev/null +++ b/services/workforce-validation-api/database/migrations/0001_owner_schema.sql @@ -0,0 +1,24 @@ +-- Establish the logical PostgreSQL ownership boundary for workforce_validation. +-- This migration intentionally creates no application table. Legacy foundation +-- validity-study tables stay untouched until an explicit forward-only adoption +-- migration can preserve existing foreign-key and acceptance contracts. + +BEGIN; + +CREATE ROLE workforce_validation_role NOLOGIN + NOSUPERUSER + NOCREATEDB + NOCREATEROLE + NOINHERIT + NOREPLICATION + NOBYPASSRLS; + +CREATE SCHEMA workforce_validation AUTHORIZATION workforce_validation_role; +REVOKE ALL ON SCHEMA workforce_validation FROM PUBLIC; + +-- Any login role granted this owner role resolves only owner objects and the +-- PostgreSQL catalog by default. Cross-context application tables are never put +-- on the implicit search path. +ALTER ROLE workforce_validation_role SET search_path = workforce_validation, pg_catalog; + +COMMIT; From d264b89dad290887d265a1010ff2622b96f9909a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:11:00 +0900 Subject: [PATCH 21/60] docs(workforce-validation): record owner persistence bootstrap --- services/workforce-validation-api/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index 593c2f8d..945f2dea 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -1,6 +1,6 @@ # Orgmetra Workforce Validation API -This package is the application boundary for the `workforce_validation` bounded context. The first slice exposes one purpose-bound read use case for the existing validity-study registry header. +This package is the application boundary for the `workforce_validation` bounded context. The current slice exposes one purpose-bound read use case for the existing validity-study registry header and establishes the context-local PostgreSQL ownership bootstrap. It does **not** query People, Talent Acquisition, Performance Management, Job Architecture, Psychometrics Commons, fast-mlsirm, or TEPP tables. Those contexts remain separate owners. Exact foreign identifiers and immutable specialist result references cross the boundary only through published contracts. @@ -8,15 +8,15 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar `read_validity_study(...)`: -- accepts authenticated Keyverse identity attributes, not credentials; +- accepts structurally immutable authenticated Keyverse identity attributes, not credentials; - evaluates tenant, purpose, operation, scope, resource, and requested fields before persistence; - calls only a `ValidityStudyReadPort` owned by this context; - reconstructs persisted registry scalars into structurally immutable owner evidence before target validation and output; - returns only the fields authorized for the exact study record. -The repository port is intentionally abstract in this increment. Protected foundation migrations still create the validity-study tables in the legacy foundation schema while `ARCHITECTURE.md` assigns them to the `workforce_validation` schema and database role. A direct `public.validity_study` adapter here would turn that implementation drift into a new long-lived service contract. +`services/workforce-validation-api/database/migrations/0001_owner_schema.sql` starts this bounded context's own migration history. It creates the `workforce_validation` schema and `workforce_validation_role`, revokes public schema access, and limits the role's default search path to the owner schema plus `pg_catalog`. It intentionally creates or moves no application table yet. Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. -Issue #234 owns the next order: service-owned schema/role, durable PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. +Issue #234 owns the remaining order: PostgreSQL-backed owner-schema acceptance and durable adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issue #237 separately tracks the authenticated-principal structural-immutability repair until exact-head acceptance and protected integration. ## Test @@ -29,4 +29,4 @@ PYTHONPATH=services/workforce-validation-api/src:packages/keyverse-adapter/src \ services/workforce-validation-api/tests ``` -The package declares 100% owned statement and branch coverage. Source-level workflow admission is not acceptance evidence by itself: this slice remains Draft until that command and the repository gates are terminal GREEN on the exact current head and qualifying independent review is satisfied. +The package declares 100% owned statement and branch coverage. The current test suite also seals the location and deny-default shape of the bounded-context-local owner-schema migration. Source-level workflow admission and static migration contract are not PostgreSQL acceptance evidence by themselves: this slice remains Draft until the exact current head has terminal owner coverage, required security/review evidence, and a PostgreSQL-backed owner-schema contract before any durable adapter is treated as production-ready. From 5d4da09cd1fd899987299443501837674f4565d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:18:06 +0900 Subject: [PATCH 22/60] test(workforce-validation): require PostgreSQL owner-schema acceptance --- .../tests/test_persistence_layout.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/workforce-validation-api/tests/test_persistence_layout.py b/services/workforce-validation-api/tests/test_persistence_layout.py index aba6931f..70001cde 100644 --- a/services/workforce-validation-api/tests/test_persistence_layout.py +++ b/services/workforce-validation-api/tests/test_persistence_layout.py @@ -5,6 +5,8 @@ ROOT = Path(__file__).resolve().parents[3] MIGRATION = ROOT / "services/workforce-validation-api/database/migrations/0001_owner_schema.sql" +FOUNDATION_WORKFLOW = ROOT / ".github/workflows/foundation-ci.yml" +OWNER_SCHEMA_POSTGRES_CONTRACT = "test_workforce_validation_owner_schema_postgres.sh" def test_owner_schema_migration_establishes_deny_default_role_boundary() -> None: @@ -30,3 +32,10 @@ def test_owner_migration_history_is_bounded_context_local() -> None: relative_path = MIGRATION.relative_to(ROOT).as_posix() assert relative_path == "services/workforce-validation-api/database/migrations/0001_owner_schema.sql" + + +def test_owner_schema_postgres_contract_is_admitted_to_foundation() -> None: + """Require the owner-schema bootstrap to execute in the canonical PostgreSQL matrix.""" + workflow = FOUNDATION_WORKFLOW.read_text(encoding="utf-8") + + assert OWNER_SCHEMA_POSTGRES_CONTRACT in workflow From d67abe75028a18a9703be1372ea2cc37864b5b5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:18:34 +0900 Subject: [PATCH 23/60] test(workforce-validation): execute owner schema boundary --- ...kforce_validation_owner_schema_postgres.sh | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/test_workforce_validation_owner_schema_postgres.sh diff --git a/tests/test_workforce_validation_owner_schema_postgres.sh b/tests/test_workforce_validation_owner_schema_postgres.sh new file mode 100644 index 00000000..019f201b --- /dev/null +++ b/tests/test_workforce_validation_owner_schema_postgres.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +migration="services/workforce-validation-api/database/migrations/0001_owner_schema.sql" +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${migration}" + +role_flags="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT rolcanlogin, rolsuper, rolcreatedb, rolcreaterole, rolinherit, rolreplication, rolbypassrls +FROM pg_roles +WHERE rolname = 'workforce_validation_role'; +")" +if [[ "${role_flags}" != "f|f|f|f|f|f|f" ]]; then + echo "workforce_validation_role flags are not deny-default: ${role_flags}" >&2 + exit 1 +fi + +schema_owner="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT pg_get_userbyid(nspowner) +FROM pg_namespace +WHERE nspname = 'workforce_validation'; +")" +if [[ "${schema_owner}" != "workforce_validation_role" ]]; then + echo "workforce_validation schema has unexpected owner: ${schema_owner}" >&2 + exit 1 +fi + +role_config="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT array_to_string(rolconfig, ',') +FROM pg_roles +WHERE rolname = 'workforce_validation_role'; +")" +if [[ "${role_config}" != "search_path=workforce_validation, pg_catalog" ]]; then + echo "workforce_validation_role search_path is not owner-local: ${role_config}" >&2 + exit 1 +fi + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -qc "CREATE ROLE workforce_validation_public_probe NOLOGIN;" +trap 'psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -qc "DROP ROLE IF EXISTS workforce_validation_public_probe;" >/dev/null 2>&1 || true' EXIT + +public_usage="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT has_schema_privilege('workforce_validation_public_probe', 'workforce_validation', 'USAGE'); +")" +public_create="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT has_schema_privilege('workforce_validation_public_probe', 'workforce_validation', 'CREATE'); +")" +if [[ "${public_usage}" != "f" || "${public_create}" != "f" ]]; then + echo "PUBLIC retains workforce_validation schema privileges: usage=${public_usage} create=${public_create}" >&2 + exit 1 +fi + +relation_count="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SELECT count(*) +FROM pg_class AS relation +JOIN pg_namespace AS namespace ON namespace.oid = relation.relnamespace +WHERE namespace.nspname = 'workforce_validation'; +")" +if [[ "${relation_count}" != "0" ]]; then + echo "owner-schema bootstrap created application relations prematurely: ${relation_count}" >&2 + exit 1 +fi + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -qc "DROP ROLE workforce_validation_public_probe;" +trap - EXIT From 6970b8c76b9487ccc617cd9e7ce076c0d401c706 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:19:11 +0900 Subject: [PATCH 24/60] ci(workforce-validation): execute owner schema contract --- .github/workflows/foundation-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/foundation-ci.yml b/.github/workflows/foundation-ci.yml index 51c98cf3..4f6d0536 100644 --- a/.github/workflows/foundation-ci.yml +++ b/.github/workflows/foundation-ci.yml @@ -84,6 +84,7 @@ jobs: test_audit_outbox_hardening_postgres.sh test_candidate_worker_conversion_postgres.sh test_validity_study_case_postgres.sh + test_workforce_validation_owner_schema_postgres.sh test_criterion_observation_scope_postgres.sh test_people_mutation_idempotency_postgres.sh test_job_analysis_snapshot_postgres.sh From a0ccaf0afc1fa26ed979a5181e05c08140f92aff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:21:42 +0900 Subject: [PATCH 25/60] fix(ci): reseal workforce validation owner-schema contract --- manifest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 7fbc02be..b46eb360 100644 --- a/manifest.json +++ b/manifest.json @@ -5,9 +5,9 @@ "files": [ { "path": ".github/workflows/foundation-ci.yml", - "sha256": "80b9e4c1b2c6c04983f195cd3b4ec3760d30fe15317cbcfcd5c6ba57089024cc", - "bytes": 6911, - "lines": 126 + "sha256": "31c6a46cb81513cdaa2a08eed5cb00a57d8b36f15e3c772d230452cdc3329aed", + "bytes": 6974, + "lines": 127 }, { "path": ".gitignore", @@ -83,7 +83,7 @@ }, { "path": "database/migrations/0005_outbox_delivery_finalization.sql", - "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961", + "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5ef9e8a92abba5c3cf182", "bytes": 6125, "lines": 170 }, From 83e5c9418f912878f0b829df24c7bf19a11e2b83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:23:17 +0900 Subject: [PATCH 26/60] fix(ci): restore unrelated manifest seal --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index b46eb360..fbae55b2 100644 --- a/manifest.json +++ b/manifest.json @@ -83,7 +83,7 @@ }, { "path": "database/migrations/0005_outbox_delivery_finalization.sql", - "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5ef9e8a92abba5c3cf182", + "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961", "bytes": 6125, "lines": 170 }, From 7ca30f3447169e678269124dbf1fd23180b997b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:23:56 +0900 Subject: [PATCH 27/60] docs(workforce-validation): record PostgreSQL owner-schema gate --- services/workforce-validation-api/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index 945f2dea..11885a8a 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -16,7 +16,7 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar `services/workforce-validation-api/database/migrations/0001_owner_schema.sql` starts this bounded context's own migration history. It creates the `workforce_validation` schema and `workforce_validation_role`, revokes public schema access, and limits the role's default search path to the owner schema plus `pg_catalog`. It intentionally creates or moves no application table yet. Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. -Issue #234 owns the remaining order: PostgreSQL-backed owner-schema acceptance and durable adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issue #237 separately tracks the authenticated-principal structural-immutability repair until exact-head acceptance and protected integration. +Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issue #237 separately tracks the authenticated-principal structural-immutability repair until exact-head acceptance and protected integration. ## Test @@ -29,4 +29,6 @@ PYTHONPATH=services/workforce-validation-api/src:packages/keyverse-adapter/src \ services/workforce-validation-api/tests ``` -The package declares 100% owned statement and branch coverage. The current test suite also seals the location and deny-default shape of the bounded-context-local owner-schema migration. Source-level workflow admission and static migration contract are not PostgreSQL acceptance evidence by themselves: this slice remains Draft until the exact current head has terminal owner coverage, required security/review evidence, and a PostgreSQL-backed owner-schema contract before any durable adapter is treated as production-ready. +The same Foundation job now also runs `tests/test_workforce_validation_owner_schema_postgres.sh` in its own pinned PostgreSQL 16.14 container. That contract executes the service-local owner migration and checks the exact role flags, schema owner, role search path, absence of inherited PUBLIC `USAGE`/`CREATE`, and absence of application relations in the bootstrap schema. The workflow manifest is resealed after admitting this contract. + +Those source contracts are not terminal acceptance by themselves. The slice remains Draft until the exact current head actually executes with 100% owned statement/branch coverage, the PostgreSQL owner-schema contract is GREEN, applicable security workflows are terminal, and the normal review/governance requirements are satisfied. Only then may the next forward-only owner-table adoption and durable adapter be treated as eligible for integration. From f63c6e164c5a2534538e83b87968d4d94228ebfa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:32:51 +0900 Subject: [PATCH 28/60] test(workforce-validation): reject NOLOGIN search-path default --- .../workforce-validation-api/tests/test_persistence_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/workforce-validation-api/tests/test_persistence_layout.py b/services/workforce-validation-api/tests/test_persistence_layout.py index 70001cde..cf77b6bd 100644 --- a/services/workforce-validation-api/tests/test_persistence_layout.py +++ b/services/workforce-validation-api/tests/test_persistence_layout.py @@ -17,11 +17,11 @@ def test_owner_schema_migration_establishes_deny_default_role_boundary() -> None "CREATE ROLE workforce_validation_role NOLOGIN", "CREATE SCHEMA workforce_validation AUTHORIZATION workforce_validation_role", "REVOKE ALL ON SCHEMA workforce_validation FROM PUBLIC", - "ALTER ROLE workforce_validation_role SET search_path = workforce_validation, pg_catalog", ) for contract in required: assert contract in sql + assert "ALTER ROLE workforce_validation_role SET search_path" not in sql assert "CREATE TABLE" not in sql assert "public.validity_study" not in sql assert "GRANT ALL" not in sql From 7dad37bf9f5ded39b2224597d0fa9c2617f52361 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:33:02 +0900 Subject: [PATCH 29/60] test(workforce-validation): exercise SET ROLE search-path semantics --- ...orkforce_validation_owner_schema_postgres.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/test_workforce_validation_owner_schema_postgres.sh b/tests/test_workforce_validation_owner_schema_postgres.sh index 019f201b..c79b659c 100644 --- a/tests/test_workforce_validation_owner_schema_postgres.sh +++ b/tests/test_workforce_validation_owner_schema_postgres.sh @@ -27,12 +27,23 @@ if [[ "${schema_owner}" != "workforce_validation_role" ]]; then fi role_config="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " -SELECT array_to_string(rolconfig, ',') +SELECT COALESCE(array_to_string(rolconfig, ','), '') FROM pg_roles WHERE rolname = 'workforce_validation_role'; ")" -if [[ "${role_config}" != "search_path=workforce_validation, pg_catalog" ]]; then - echo "workforce_validation_role search_path is not owner-local: ${role_config}" >&2 +if [[ -n "${role_config}" ]]; then + echo "NOLOGIN schema owner must not carry ineffective login-only runtime defaults: ${role_config}" >&2 + exit 1 +fi + +set_role_probe="$(psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -Atqc " +SET search_path = public; +SET ROLE workforce_validation_role; +SELECT current_user || '|' || current_setting('search_path'); +RESET ROLE; +")" +if [[ "${set_role_probe}" != "workforce_validation_role|public" ]]; then + echo "unexpected SET ROLE search_path behavior: ${set_role_probe}" >&2 exit 1 fi From fd87b719bd581a64ca84ca679530e264ed6f167f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:33:12 +0900 Subject: [PATCH 30/60] fix(workforce-validation): remove ineffective owner search-path default --- .../database/migrations/0001_owner_schema.sql | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/workforce-validation-api/database/migrations/0001_owner_schema.sql b/services/workforce-validation-api/database/migrations/0001_owner_schema.sql index 3d24b984..67a1e3c3 100644 --- a/services/workforce-validation-api/database/migrations/0001_owner_schema.sql +++ b/services/workforce-validation-api/database/migrations/0001_owner_schema.sql @@ -16,9 +16,12 @@ CREATE ROLE workforce_validation_role NOLOGIN CREATE SCHEMA workforce_validation AUTHORIZATION workforce_validation_role; REVOKE ALL ON SCHEMA workforce_validation FROM PUBLIC; --- Any login role granted this owner role resolves only owner objects and the --- PostgreSQL catalog by default. Cross-context application tables are never put --- on the implicit search path. -ALTER ROLE workforce_validation_role SET search_path = workforce_validation, pg_catalog; +-- workforce_validation_role is a migration/schema-owner identity only. Runtime +-- principals must not be granted this owner role. PostgreSQL role-level GUC +-- defaults apply at login and are not re-applied by SET ROLE; because this role +-- is NOLOGIN, an ALTER ROLE ... SET search_path entry would not provide runtime +-- isolation. Future runtime adapters must use a distinct least-privilege role, +-- schema-qualified owner relations, and explicit function-level search_path for +-- any SECURITY DEFINER code. COMMIT; From a04c8b4cd58145f9e74a085d3fdb037d90966012 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 16:33:28 +0900 Subject: [PATCH 31/60] docs(workforce-validation): correct owner-role search-path contract --- services/workforce-validation-api/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index 11885a8a..1e0ea265 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -14,9 +14,11 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar - reconstructs persisted registry scalars into structurally immutable owner evidence before target validation and output; - returns only the fields authorized for the exact study record. -`services/workforce-validation-api/database/migrations/0001_owner_schema.sql` starts this bounded context's own migration history. It creates the `workforce_validation` schema and `workforce_validation_role`, revokes public schema access, and limits the role's default search path to the owner schema plus `pg_catalog`. It intentionally creates or moves no application table yet. Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. +`services/workforce-validation-api/database/migrations/0001_owner_schema.sql` starts this bounded context's own migration history. It creates the `workforce_validation` schema and deny-default `workforce_validation_role`, revokes public schema access, and intentionally creates or moves no application table yet. The role is a **NOLOGIN migration/schema owner only**; runtime principals must not be granted that owner role. PostgreSQL applies role-level configuration defaults at login and does not re-apply them on `SET ROLE`, so an `ALTER ROLE ... SET search_path` entry on this NOLOGIN role is not treated as a runtime isolation control. The later durable adapter must use a distinct least-privilege runtime role, schema-qualified `workforce_validation` relations, and explicit function-level `search_path` where `SECURITY DEFINER` code is introduced. -Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issue #237 separately tracks the authenticated-principal structural-immutability repair until exact-head acceptance and protected integration. +Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. + +Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236/#237 track structural immutability of persisted study and authenticated principal evidence; #238 tracks the owner-role/runtime-search-path boundary until exact-head acceptance and protected integration. ## Test @@ -29,6 +31,6 @@ PYTHONPATH=services/workforce-validation-api/src:packages/keyverse-adapter/src \ services/workforce-validation-api/tests ``` -The same Foundation job now also runs `tests/test_workforce_validation_owner_schema_postgres.sh` in its own pinned PostgreSQL 16.14 container. That contract executes the service-local owner migration and checks the exact role flags, schema owner, role search path, absence of inherited PUBLIC `USAGE`/`CREATE`, and absence of application relations in the bootstrap schema. The workflow manifest is resealed after admitting this contract. +The same Foundation job also runs `tests/test_workforce_validation_owner_schema_postgres.sh` in its own pinned PostgreSQL 16.14 container. That contract executes the service-local owner migration and checks the exact deny-default role flags, schema owner, absence of ineffective login-only `rolconfig`, actual `SET ROLE` search-path behavior, absence of inherited PUBLIC `USAGE`/`CREATE`, and absence of application relations in the bootstrap schema. The test intentionally demonstrates that `SET ROLE` retains the caller's existing `search_path`; runtime isolation therefore cannot be inferred from owner-role metadata. Those source contracts are not terminal acceptance by themselves. The slice remains Draft until the exact current head actually executes with 100% owned statement/branch coverage, the PostgreSQL owner-schema contract is GREEN, applicable security workflows are terminal, and the normal review/governance requirements are satisfied. Only then may the next forward-only owner-table adoption and durable adapter be treated as eligible for integration. From 60f5ba9d1b43ba75fd2d7f042153f7b43eff902b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 17:02:03 +0900 Subject: [PATCH 32/60] test(workforce-validation): reject forged principal storage before auth --- .../tests/test_principal_storage_integrity.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_principal_storage_integrity.py diff --git a/services/workforce-validation-api/tests/test_principal_storage_integrity.py b/services/workforce-validation-api/tests/test_principal_storage_integrity.py new file mode 100644 index 00000000..9473a5b1 --- /dev/null +++ b/services/workforce-validation-api/tests/test_principal_storage_integrity.py @@ -0,0 +1,83 @@ +"""Regression contract for canonical validation-principal storage before authorization.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy +from orgmetra_workforce_validation_api.registry import ( + ValidationPrincipal, + ValidityStudyRecord, + read_validity_study, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000001") +STUDY = UUID("00000000-0000-7000-8000-0000000000c1") + + +class _ExecutableUUID(UUID): + """Expose executable behavior if a UUID subtype reaches downstream validation.""" + + def __getattribute__(self, name: str) -> object: + if name == "int": + raise AssertionError("UUID subtype behavior executed") + return super().__getattribute__(name) + + +class _ReadPort: + """Capture repository use; this regression must fail before persistence.""" + + def __init__(self) -> None: + self.calls: list[tuple[UUID, UUID]] = [] + + def read_validity_study( + self, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + ) -> ValidityStudyRecord | None: + """Record an unexpected persistence call.""" + self.calls.append((tenant_record_id, validity_study_id)) + return None + + +def _policy() -> PurposeBoundAccessPolicy: + """Return the canonical purpose-bound policy used by the read boundary.""" + return PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="validation-read-v1", + resource_kind="validity_study_record", + purpose_code="validation_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset({"study_status_code"}), + ) + + +def test_low_level_exact_principal_is_revalidated_before_keyverse_evaluation() -> None: + """Reject constructor-bypassed identity evidence before subtype behavior can execute.""" + forged_tenant = _ExecutableUUID(str(TENANT)) + principal = tuple.__new__( + ValidationPrincipal, + ( + forged_tenant, + "person:analyst-1", + frozenset({"orgmetra.workforce_validation.read"}), + ), + ) + port = _ReadPort() + + with pytest.raises(ValueError, match="tenant_record_id must be an exact operational UUID"): + read_validity_study( + principal=principal, + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(), + read_port=port, + ) + + assert port.calls == [] From 4ef7ad130e4d0aa314ea59de3dafaea79cc0630c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 17:03:06 +0900 Subject: [PATCH 33/60] fix(workforce-validation): revalidate principal storage before auth --- .../src/orgmetra_workforce_validation_api/registry.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 523aef54..4921b7b9 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -290,6 +290,11 @@ def read_validity_study( if not isinstance(read_port, ValidityStudyReadPort): raise TypeError("read_port must implement ValidityStudyReadPort.") + detached_principal = ValidationPrincipal( + tenant_record_id=principal.tenant_record_id, + actor_reference=principal.actor_reference, + granted_scope_codes=principal.granted_scope_codes, + ) tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) study_id = _require_operational_uuid("validity_study_id", validity_study_id) purpose = _require_code("purpose_code", purpose_code) @@ -299,15 +304,15 @@ def read_validity_study( require_purpose_bound_access( request=PurposeBoundAccessRequest( tenant_record_id=tenant_id, - actor_tenant_record_id=principal.tenant_record_id, + actor_tenant_record_id=detached_principal.tenant_record_id, resource_tenant_record_id=tenant_id, - actor_reference=principal.actor_reference, + actor_reference=detached_principal.actor_reference, resource_reference=f"{_RESOURCE_KIND}:{study_id}", purpose_code=purpose, operation_code=_OPERATION, resource_kind=_RESOURCE_KIND, requested_fields=fields, - granted_scope_codes=principal.granted_scope_codes, + granted_scope_codes=detached_principal.granted_scope_codes, ), policy=detached_policy, ) From 9794ff543c9190cdd6fa00dc37016db8391709a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 17:08:13 +0900 Subject: [PATCH 34/60] test(workforce-validation): reject noncallable read port before auth --- .../test_read_port_dependency_integrity.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_read_port_dependency_integrity.py diff --git a/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py b/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py new file mode 100644 index 00000000..a5e2cbda --- /dev/null +++ b/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py @@ -0,0 +1,54 @@ +"""Regression contract for inert repository capability validation before authorization.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy +from orgmetra_workforce_validation_api.registry import ( + ValidationPrincipal, + read_validity_study, +) + +TENANT = UUID("10000000-0000-7000-8000-000000000001") +STUDY = UUID("00000000-0000-7000-8000-0000000000c1") + + +class _DescriptorReadPort: + """Expose a non-callable static protocol member whose getter must never execute.""" + + @property + def read_validity_study(self) -> object: + """Trip if dependency validation or later code executes this descriptor.""" + raise AssertionError("repository descriptor executed before rejection") + + +def test_noncallable_repository_capability_fails_before_authorization() -> None: + """Reject an invalid port before a deliberately denying policy can be evaluated.""" + principal = ValidationPrincipal( + tenant_record_id=TENANT, + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ) + denying_policy = PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="validation-read-v1", + resource_kind="validity_study_record", + purpose_code="audit_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset({"study_status_code"}), + ) + + with pytest.raises(TypeError, match="read_port must expose a statically callable read_validity_study"): + read_validity_study( + principal=principal, + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=denying_policy, + read_port=_DescriptorReadPort(), # type: ignore[arg-type] + ) From ccb5c0c58dc74c1d7eee59431e6337c207fcac35 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 17:08:51 +0900 Subject: [PATCH 35/60] fix(workforce-validation): validate repository capability statically --- .../src/orgmetra_workforce_validation_api/registry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 4921b7b9..3006b727 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -11,6 +11,7 @@ from dataclasses import dataclass from datetime import datetime, timezone +from inspect import getattr_static import re from typing import Protocol, runtime_checkable from uuid import UUID @@ -287,8 +288,9 @@ def read_validity_study( raise TypeError("principal must be an exact ValidationPrincipal.") if type(policy) is not PurposeBoundAccessPolicy: raise TypeError("policy must be an exact PurposeBoundAccessPolicy.") - if not isinstance(read_port, ValidityStudyReadPort): - raise TypeError("read_port must implement ValidityStudyReadPort.") + read_capability = getattr_static(read_port, "read_validity_study", None) + if not callable(read_capability): + raise TypeError("read_port must expose a statically callable read_validity_study.") detached_principal = ValidationPrincipal( tenant_record_id=principal.tenant_record_id, From 1a344f8057755ae8b652c31963d787ff8abf2beb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 18:06:18 +0900 Subject: [PATCH 36/60] test(workforce-validation): lock authorized view evidence --- .../tests/test_registry.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/services/workforce-validation-api/tests/test_registry.py b/services/workforce-validation-api/tests/test_registry.py index 02b8661f..53d5a62f 100644 --- a/services/workforce-validation-api/tests/test_registry.py +++ b/services/workforce-validation-api/tests/test_registry.py @@ -272,3 +272,28 @@ def test_record_is_structurally_immutable_against_object_setattr() -> None: object.__setattr__(record, "study_status_code", "study_closed") assert record.study_status_code == "study_draft" + + +def test_authorized_view_is_structurally_immutable_after_field_minimization() -> None: + view = read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(), + read_port=_ReadPort(_record()), + ) + original_fields = view.fields + + for field_name, replacement in ( + ("tenant_record_id", OTHER_TENANT), + ("validity_study_id", OTHER_STUDY), + ("fields", (("study_status_code", "study_closed"),)), + ): + with pytest.raises(AttributeError): + object.__setattr__(view, field_name, replacement) + + assert view.tenant_record_id == TENANT + assert view.validity_study_id == STUDY + assert view.fields == original_fields From 17092c94d180d082d1e389982e0beea9872f53f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 18:07:04 +0900 Subject: [PATCH 37/60] fix(workforce-validation): make authorized views structurally immutable --- .../registry.py | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 3006b727..868fe281 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -9,7 +9,6 @@ from __future__ import annotations -from dataclasses import dataclass from datetime import datetime, timezone from inspect import getattr_static import re @@ -245,13 +244,40 @@ def recorded_to(self) -> datetime | None: return self[5] -@dataclass(frozen=True, slots=True) -class ValidityStudyView: - """Field-minimized authorized view returned to the gateway or role workspace.""" +class ValidityStudyView(tuple): + """Structurally immutable field-minimized view returned after authorization. - tenant_record_id: UUID - validity_study_id: UUID - fields: tuple[tuple[str, object], ...] + Tuple-backed storage prevents downstream gateway, audit, or workspace code + from rewriting the authorized target identity or minimized field evidence + through ``object.__setattr__`` after the access decision has completed. + """ + + __slots__ = () + + def __new__( + cls, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + fields: tuple[tuple[str, object], ...], + ) -> ValidityStudyView: + """Create one immutable authorized-output envelope from already validated values.""" + return tuple.__new__(cls, (tenant_record_id, validity_study_id, fields)) + + @property + def tenant_record_id(self) -> UUID: + """Return the tenant identity authorized for this view.""" + return self[0] + + @property + def validity_study_id(self) -> UUID: + """Return the validity-study identity authorized for this view.""" + return self[1] + + @property + def fields(self) -> tuple[tuple[str, object], ...]: + """Return the ordered field-minimized evidence authorized for release.""" + return self[2] @runtime_checkable From 1b1d2e5c7cd492f57c806408618429e83f1e09d4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 19:00:11 +0900 Subject: [PATCH 38/60] test(workforce-validation): require authorized view issuance --- .../tests/test_view_issuance_integrity.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_view_issuance_integrity.py diff --git a/services/workforce-validation-api/tests/test_view_issuance_integrity.py b/services/workforce-validation-api/tests/test_view_issuance_integrity.py new file mode 100644 index 00000000..1095659a --- /dev/null +++ b/services/workforce-validation-api/tests/test_view_issuance_integrity.py @@ -0,0 +1,21 @@ +"""Regression contract for workforce-validation authorized-view issuance.""" + +from uuid import UUID + +import pytest + +from orgmetra_workforce_validation_api.registry import ValidityStudyView + + +TENANT = UUID("10000000-0000-7000-8000-000000000001") +STUDY = UUID("00000000-0000-7000-8000-0000000000c1") + + +def test_direct_authorized_view_construction_fails_closed() -> None: + """Require purpose-bound reads, not public construction, to issue study views.""" + with pytest.raises(TypeError, match="issued only by read_validity_study"): + ValidityStudyView( + tenant_record_id=TENANT, + validity_study_id=STUDY, + fields=(("study_status_code", "study_draft"),), + ) From b655063cc7d9680de5743949b9b66e631530b904 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 19:01:03 +0900 Subject: [PATCH 39/60] fix(workforce-validation): make study views read-issued only --- .../registry.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 868fe281..34c3ac97 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -249,7 +249,10 @@ class ValidityStudyView(tuple): Tuple-backed storage prevents downstream gateway, audit, or workspace code from rewriting the authorized target identity or minimized field evidence - through ``object.__setattr__`` after the access decision has completed. + through ``object.__setattr__`` after the access decision has completed. The + public constructor is deliberately non-issuing: callers obtain this data-only + projection from ``read_validity_study`` and must re-authorize consequential + actions rather than treating the Python runtime type as a durable credential. """ __slots__ = () @@ -261,8 +264,8 @@ def __new__( validity_study_id: UUID, fields: tuple[tuple[str, object], ...], ) -> ValidityStudyView: - """Create one immutable authorized-output envelope from already validated values.""" - return tuple.__new__(cls, (tenant_record_id, validity_study_id, fields)) + """Reject public construction so only the authorized read path issues views.""" + raise TypeError("ValidityStudyView is issued only by read_validity_study.") @property def tenant_record_id(self) -> UUID: @@ -280,6 +283,16 @@ def fields(self) -> tuple[tuple[str, object], ...]: return self[2] +def _issue_validity_study_view( + *, + tenant_record_id: UUID, + validity_study_id: UUID, + fields: tuple[tuple[str, object], ...], +) -> ValidityStudyView: + """Issue one immutable view after authorization and target validation complete.""" + return tuple.__new__(ValidityStudyView, (tenant_record_id, validity_study_id, fields)) + + @runtime_checkable class ValidityStudyReadPort(Protocol): """Owner repository contract for one tenant-local validity-study header.""" @@ -371,7 +384,7 @@ def read_validity_study( "recorded_from": record.recorded_from, "recorded_to": record.recorded_to, } - return ValidityStudyView( + return _issue_validity_study_view( tenant_record_id=tenant_id, validity_study_id=study_id, fields=tuple((field_name, values[field_name]) for field_name in sorted(fields)), From 412c62b279a4d3b5448dd2817c835e0aa85b412d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 19:02:44 +0900 Subject: [PATCH 40/60] docs(workforce-validation): define authorized view issuance boundary --- services/workforce-validation-api/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index 1e0ea265..2e18dc72 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -9,16 +9,21 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar `read_validity_study(...)`: - accepts structurally immutable authenticated Keyverse identity attributes, not credentials; +- reconstructs and revalidates principal storage before building the access request, so exact tuple type alone is not treated as identity authority; +- inertly verifies that the owner repository exposes a statically callable `read_validity_study` capability before authorization, without executing caller-controlled descriptors; - evaluates tenant, purpose, operation, scope, resource, and requested fields before persistence; - calls only a `ValidityStudyReadPort` owned by this context; - reconstructs persisted registry scalars into structurally immutable owner evidence before target validation and output; -- returns only the fields authorized for the exact study record. +- returns only the fields authorized for the exact study record; +- issues `ValidityStudyView` only from the authorized read path. Its public constructor fails closed, and the returned tuple-backed projection cannot be rewritten through ordinary assignment or `object.__setattr__`. + +`ValidityStudyView` is a data projection, not a durable authorization credential or cryptographic capability. Downstream consequential actions must perform their own purpose-bound authorization and authoritative re-resolution rather than treating the Python runtime type as reusable authority. Low-level interpreter construction is outside the supported public API and is not accepted as proof that authorization occurred. `services/workforce-validation-api/database/migrations/0001_owner_schema.sql` starts this bounded context's own migration history. It creates the `workforce_validation` schema and deny-default `workforce_validation_role`, revokes public schema access, and intentionally creates or moves no application table yet. The role is a **NOLOGIN migration/schema owner only**; runtime principals must not be granted that owner role. PostgreSQL applies role-level configuration defaults at login and does not re-apply them on `SET ROLE`, so an `ALTER ROLE ... SET search_path` entry on this NOLOGIN role is not treated as a runtime isolation control. The later durable adapter must use a distinct least-privilege runtime role, schema-qualified `workforce_validation` relations, and explicit function-level `search_path` where `SECURITY DEFINER` code is introduced. Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. -Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236/#237 track structural immutability of persisted study and authenticated principal evidence; #238 tracks the owner-role/runtime-search-path boundary until exact-head acceptance and protected integration. +Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236–#242 retain the current bootstrap trust-boundary findings through exact-head acceptance and protected integration: persisted-record immutability, principal immutability and constructor revalidation, owner-role/runtime-role separation, inert repository-capability validation, immutable minimized output, and non-public issuance of that output. ## Test From dfd8038e83270c8530c5a5ef7d2bfb1af409490b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:07:46 +0900 Subject: [PATCH 41/60] test(workforce-validation): expose retained UUID alias mutation --- .../tests/test_uuid_storage_integrity.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_uuid_storage_integrity.py diff --git a/services/workforce-validation-api/tests/test_uuid_storage_integrity.py b/services/workforce-validation-api/tests/test_uuid_storage_integrity.py new file mode 100644 index 00000000..d8bd48aa --- /dev/null +++ b/services/workforce-validation-api/tests/test_uuid_storage_integrity.py @@ -0,0 +1,118 @@ +"""Regression contract for UUID storage behind immutable registry value objects.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from uuid import UUID + +from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy +from orgmetra_workforce_validation_api.registry import ( + ValidationPrincipal, + ValidityStudyRecord, + read_validity_study, +) + +TENANT_TEXT = "10000000-0000-7000-8000-000000000001" +OTHER_TENANT = UUID("10000000-0000-7000-8000-000000000002") +STUDY_TEXT = "00000000-0000-7000-8000-0000000000c1" +OTHER_STUDY = UUID("00000000-0000-7000-8000-0000000000c2") +CRITERION_TEXT = "00000000-0000-7000-8000-0000000000a1" +OTHER_CRITERION = UUID("00000000-0000-7000-8000-0000000000a2") +RECORDED_FROM = datetime(2026, 11, 3, tzinfo=timezone.utc) + + +class _ReadPort: + """Return one configured owner record for UUID-storage regression coverage.""" + + def __init__(self, result: ValidityStudyRecord) -> None: + self.result = result + + def read_validity_study( + self, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + ) -> ValidityStudyRecord: + """Return the configured record after the application boundary authorizes the read.""" + return self.result + + +def _policy() -> PurposeBoundAccessPolicy: + """Return the canonical purpose-bound policy for the regression read.""" + return PurposeBoundAccessPolicy( + tenant_record_id=UUID(TENANT_TEXT), + policy_version_code="validation-read-v1", + resource_kind="validity_study_record", + purpose_code="validation_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset({"criterion_blueprint_id"}), + ) + + +def test_principal_and_record_do_not_retain_mutable_uuid_inputs() -> None: + """Retained UUID aliases cannot rewrite identity evidence after constructor validation.""" + tenant = UUID(TENANT_TEXT) + study = UUID(STUDY_TEXT) + criterion = UUID(CRITERION_TEXT) + principal = ValidationPrincipal( + tenant_record_id=tenant, + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ) + record = ValidityStudyRecord( + tenant_record_id=tenant, + validity_study_id=study, + criterion_blueprint_id=criterion, + study_status_code="study_draft", + recorded_from=RECORDED_FROM, + recorded_to=None, + ) + + object.__setattr__(tenant, "int", OTHER_TENANT.int) + object.__setattr__(study, "int", OTHER_STUDY.int) + object.__setattr__(criterion, "int", OTHER_CRITERION.int) + + assert principal.tenant_record_id == UUID(TENANT_TEXT) + assert record.tenant_record_id == UUID(TENANT_TEXT) + assert record.validity_study_id == UUID(STUDY_TEXT) + assert record.criterion_blueprint_id == UUID(CRITERION_TEXT) + + +def test_authorized_view_does_not_retain_or_expose_mutable_uuid_storage() -> None: + """Target and projected UUID evidence remain stable across retained-reference rewrites.""" + tenant = UUID(TENANT_TEXT) + study = UUID(STUDY_TEXT) + principal = ValidationPrincipal( + tenant_record_id=UUID(TENANT_TEXT), + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ) + record = ValidityStudyRecord( + tenant_record_id=UUID(TENANT_TEXT), + validity_study_id=UUID(STUDY_TEXT), + criterion_blueprint_id=UUID(CRITERION_TEXT), + study_status_code="study_draft", + recorded_from=RECORDED_FROM, + recorded_to=None, + ) + + view = read_validity_study( + principal=principal, + tenant_record_id=tenant, + validity_study_id=study, + purpose_code="validation_review", + requested_fields=frozenset({"criterion_blueprint_id"}), + policy=_policy(), + read_port=_ReadPort(record), + ) + + object.__setattr__(tenant, "int", OTHER_TENANT.int) + object.__setattr__(study, "int", OTHER_STUDY.int) + projected_criterion = dict(view.fields)["criterion_blueprint_id"] + assert type(projected_criterion) is UUID + object.__setattr__(projected_criterion, "int", OTHER_CRITERION.int) + + assert view.tenant_record_id == UUID(TENANT_TEXT) + assert view.validity_study_id == UUID(STUDY_TEXT) + assert dict(view.fields)["criterion_blueprint_id"] == UUID(CRITERION_TEXT) From a78104c71e340039ca7f0c36aa2d4f7d22dff999 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:08:44 +0900 Subject: [PATCH 42/60] test(workforce-validation): expose post-authorization UUID target switch --- .../tests/test_uuid_storage_integrity.py | 55 +++++++++++++++++-- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/services/workforce-validation-api/tests/test_uuid_storage_integrity.py b/services/workforce-validation-api/tests/test_uuid_storage_integrity.py index d8bd48aa..52c23998 100644 --- a/services/workforce-validation-api/tests/test_uuid_storage_integrity.py +++ b/services/workforce-validation-api/tests/test_uuid_storage_integrity.py @@ -5,9 +5,12 @@ from datetime import datetime, timezone from uuid import UUID +import pytest + from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_workforce_validation_api.registry import ( ValidationPrincipal, + ValidityStudyIntegrityError, ValidityStudyRecord, read_validity_study, ) @@ -37,6 +40,28 @@ def read_validity_study( return self.result +class _TargetSwitchingReadPort: + """Attempt to rewrite the authorized UUID target during the executable port call.""" + + def read_validity_study( + self, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + ) -> ValidityStudyRecord: + """Mutate received UUID aliases and return a record matching the rewritten target.""" + object.__setattr__(tenant_record_id, "int", OTHER_TENANT.int) + object.__setattr__(validity_study_id, "int", OTHER_STUDY.int) + return ValidityStudyRecord( + tenant_record_id=OTHER_TENANT, + validity_study_id=OTHER_STUDY, + criterion_blueprint_id=UUID(CRITERION_TEXT), + study_status_code="study_draft", + recorded_from=RECORDED_FROM, + recorded_to=None, + ) + + def _policy() -> PurposeBoundAccessPolicy: """Return the canonical purpose-bound policy for the regression read.""" return PurposeBoundAccessPolicy( @@ -50,6 +75,15 @@ def _policy() -> PurposeBoundAccessPolicy: ) +def _principal() -> ValidationPrincipal: + """Return one canonical principal for UUID target-integrity tests.""" + return ValidationPrincipal( + tenant_record_id=UUID(TENANT_TEXT), + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ) + + def test_principal_and_record_do_not_retain_mutable_uuid_inputs() -> None: """Retained UUID aliases cannot rewrite identity evidence after constructor validation.""" tenant = UUID(TENANT_TEXT) @@ -79,15 +113,24 @@ def test_principal_and_record_do_not_retain_mutable_uuid_inputs() -> None: assert record.criterion_blueprint_id == UUID(CRITERION_TEXT) +def test_port_cannot_switch_the_authorized_target_by_mutating_received_uuid_objects() -> None: + """The target comparison uses pre-port immutable identity evidence, not mutable aliases.""" + with pytest.raises(ValidityStudyIntegrityError, match="another target"): + read_validity_study( + principal=_principal(), + tenant_record_id=UUID(TENANT_TEXT), + validity_study_id=UUID(STUDY_TEXT), + purpose_code="validation_review", + requested_fields=frozenset({"criterion_blueprint_id"}), + policy=_policy(), + read_port=_TargetSwitchingReadPort(), + ) + + def test_authorized_view_does_not_retain_or_expose_mutable_uuid_storage() -> None: """Target and projected UUID evidence remain stable across retained-reference rewrites.""" tenant = UUID(TENANT_TEXT) study = UUID(STUDY_TEXT) - principal = ValidationPrincipal( - tenant_record_id=UUID(TENANT_TEXT), - actor_reference="person:analyst-1", - granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), - ) record = ValidityStudyRecord( tenant_record_id=UUID(TENANT_TEXT), validity_study_id=UUID(STUDY_TEXT), @@ -98,7 +141,7 @@ def test_authorized_view_does_not_retain_or_expose_mutable_uuid_storage() -> Non ) view = read_validity_study( - principal=principal, + principal=_principal(), tenant_record_id=tenant, validity_study_id=study, purpose_code="validation_review", From b1c70855e6655817a4f40cc9dfb4787770f40c5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:09:40 +0900 Subject: [PATCH 43/60] fix(workforce-validation): detach UUID identity storage --- .../registry.py | 158 ++++++++++++------ 1 file changed, 110 insertions(+), 48 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 34c3ac97..e0f2cb49 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -53,6 +53,18 @@ def _require_operational_uuid(field_name: str, value: object) -> UUID: return value +def _store_operational_uuid(field_name: str, value: object) -> int: + """Reduce one validated UUID to immutable integer storage without retaining its object alias.""" + return _require_operational_uuid(field_name, value).int + + +def _restore_operational_uuid(field_name: str, value: object) -> UUID: + """Reconstruct one fresh UUID from immutable internal integer storage.""" + if type(value) is not int or value <= 0 or value >= _MAX_UUID_INT: + raise ValueError(f"{field_name} must be an exact operational UUID.") + return UUID(int=value) + + def _require_code(field_name: str, value: object) -> str: """Return one exact lower-snake-case code used in an auditable policy request.""" if type(value) is not str or _CODE_PATTERN.fullmatch(value) is None: @@ -94,9 +106,9 @@ def _detach_policy(policy: PurposeBoundAccessPolicy) -> PurposeBoundAccessPolicy The protected Keyverse adapter accepts subclass-compatible scalar inputs for backward compatibility. This owner boundary is stricter because a caller- defined ``str``/``UUID`` subtype could otherwise execute Python behavior when - the evaluator compares or hashes policy attributes. Locals snapshot each - immutable value first; the reconstructed exact policy is the only one used by - authorization. + the evaluator compares or hashes policy attributes. Immutable UUID integer + storage also prevents a retained policy UUID alias from switching the tenant + after this boundary has accepted it. """ tenant_record_id = policy.tenant_record_id policy_version_code = policy.policy_version_code @@ -106,7 +118,7 @@ def _detach_policy(policy: PurposeBoundAccessPolicy) -> PurposeBoundAccessPolicy required_scope_code = policy.required_scope_code permitted_fields = policy.permitted_fields - _require_operational_uuid("policy tenant_record_id", tenant_record_id) + tenant_identity = _store_operational_uuid("policy tenant_record_id", tenant_record_id) for field_name, value in ( ("policy_version_code", policy_version_code), ("resource_kind", resource_kind), @@ -122,7 +134,7 @@ def _detach_policy(policy: PurposeBoundAccessPolicy) -> PurposeBoundAccessPolicy raise ValueError("policy permitted_fields must contain exact strings in an exact frozenset.") return PurposeBoundAccessPolicy( - tenant_record_id=tenant_record_id, + tenant_record_id=_restore_operational_uuid("policy tenant_record_id", tenant_identity), policy_version_code=policy_version_code, resource_kind=resource_kind, purpose_code=purpose_code, @@ -136,8 +148,8 @@ class ValidationPrincipal(tuple): """Structurally immutable authenticated Keyverse attributes for validation reads. The bearer credential itself never enters this value. Tuple-backed storage - prevents a retained caller reference from rewriting tenant, actor, or scope - evidence through ``object.__setattr__`` after constructor validation. + keeps only immutable UUID integer evidence plus immutable actor/scope values, + so retained UUID references cannot rewrite tenant identity after validation. """ __slots__ = () @@ -150,16 +162,16 @@ def __new__( granted_scope_codes: frozenset[str], ) -> ValidationPrincipal: """Validate exact identity evidence before creating the immutable principal.""" - tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) + tenant_identity = _store_operational_uuid("tenant_record_id", tenant_record_id) if type(actor_reference) is not str or _REFERENCE_PATTERN.fullmatch(actor_reference) is None: raise ValueError("actor_reference must be an exact namespaced opaque reference.") scope_codes = _validate_scope_set(granted_scope_codes) - return tuple.__new__(cls, (tenant_id, actor_reference, scope_codes)) + return tuple.__new__(cls, (tenant_identity, actor_reference, scope_codes)) @property def tenant_record_id(self) -> UUID: - """Return the authenticated tenant identity.""" - return self[0] + """Return a fresh authenticated tenant identity.""" + return _restore_operational_uuid("tenant_record_id", self[0]) @property def actor_reference(self) -> str: @@ -175,12 +187,12 @@ def granted_scope_codes(self) -> frozenset[str]: class ValidityStudyRecord(tuple): """Structurally immutable owner projection of one recorded validity-study header. - The tuple-backed representation prevents a repository adapter that retains an - accepted record from rewriting durable study evidence through - ``object.__setattr__`` after construction. Only fields already represented by - the protected foundation schema are carried here. Predictor, sample, - decision-policy and analysis-protocol versions remain a later scientific-model - increment owned by Issue #234. + The tuple-backed representation stores UUIDs as immutable integers, preventing + a repository adapter that retains accepted UUID objects from rewriting durable + study identity through ``object.__setattr__`` after construction. Only fields + already represented by the protected foundation schema are carried here. + Predictor, sample, decision-policy and analysis-protocol versions remain a + later scientific-model increment owned by Issue #234. """ __slots__ = () @@ -196,9 +208,11 @@ def __new__( recorded_to: datetime | None, ) -> ValidityStudyRecord: """Validate and detach durable scalars before creating the immutable tuple.""" - tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) - study_id = _require_operational_uuid("validity_study_id", validity_study_id) - criterion_id = _require_operational_uuid("criterion_blueprint_id", criterion_blueprint_id) + tenant_identity = _store_operational_uuid("tenant_record_id", tenant_record_id) + study_identity = _store_operational_uuid("validity_study_id", validity_study_id) + criterion_identity = _store_operational_uuid( + "criterion_blueprint_id", criterion_blueprint_id + ) status_code = _require_code("study_status_code", study_status_code) recorded_start = _require_aware_datetime("recorded_from", recorded_from) recorded_end = ( @@ -210,23 +224,30 @@ def __new__( raise ValueError("recorded_to must be later than recorded_from.") return tuple.__new__( cls, - (tenant_id, study_id, criterion_id, status_code, recorded_start, recorded_end), + ( + tenant_identity, + study_identity, + criterion_identity, + status_code, + recorded_start, + recorded_end, + ), ) @property def tenant_record_id(self) -> UUID: - """Return the tenant that owns this validity study.""" - return self[0] + """Return a fresh tenant identity for this validity study.""" + return _restore_operational_uuid("tenant_record_id", self[0]) @property def validity_study_id(self) -> UUID: - """Return the stable validity-study identity.""" - return self[1] + """Return a fresh stable validity-study identity.""" + return _restore_operational_uuid("validity_study_id", self[1]) @property def criterion_blueprint_id(self) -> UUID: - """Return the criterion blueprint linked to the study header.""" - return self[2] + """Return a fresh criterion-blueprint identity linked to the study header.""" + return _restore_operational_uuid("criterion_blueprint_id", self[2]) @property def study_status_code(self) -> str: @@ -244,13 +265,39 @@ def recorded_to(self) -> datetime | None: return self[5] +def _store_view_fields(fields: tuple[tuple[str, object], ...]) -> tuple[tuple[str, object], ...]: + """Store UUID-valued projection fields without retaining mutable UUID object aliases.""" + return tuple( + ( + field_name, + _store_operational_uuid(field_name, value) + if field_name == "criterion_blueprint_id" + else value, + ) + for field_name, value in fields + ) + + +def _restore_view_fields(fields: tuple[tuple[str, object], ...]) -> tuple[tuple[str, object], ...]: + """Return a public projection with fresh UUID objects for UUID-valued fields.""" + return tuple( + ( + field_name, + _restore_operational_uuid(field_name, value) + if field_name == "criterion_blueprint_id" + else value, + ) + for field_name, value in fields + ) + + class ValidityStudyView(tuple): """Structurally immutable field-minimized view returned after authorization. - Tuple-backed storage prevents downstream gateway, audit, or workspace code - from rewriting the authorized target identity or minimized field evidence - through ``object.__setattr__`` after the access decision has completed. The - public constructor is deliberately non-issuing: callers obtain this data-only + Tuple-backed storage keeps target UUIDs and UUID-valued projected evidence as + immutable integers, so downstream gateway, audit, or workspace code cannot + rewrite authorized identity through retained UUID objects. The public + constructor is deliberately non-issuing: callers obtain this data-only projection from ``read_validity_study`` and must re-authorize consequential actions rather than treating the Python runtime type as a durable credential. """ @@ -269,18 +316,18 @@ def __new__( @property def tenant_record_id(self) -> UUID: - """Return the tenant identity authorized for this view.""" - return self[0] + """Return a fresh tenant identity authorized for this view.""" + return _restore_operational_uuid("tenant_record_id", self[0]) @property def validity_study_id(self) -> UUID: - """Return the validity-study identity authorized for this view.""" - return self[1] + """Return a fresh validity-study identity authorized for this view.""" + return _restore_operational_uuid("validity_study_id", self[1]) @property def fields(self) -> tuple[tuple[str, object], ...]: - """Return the ordered field-minimized evidence authorized for release.""" - return self[2] + """Return ordered field-minimized evidence with fresh UUID-valued projections.""" + return _restore_view_fields(self[2]) def _issue_validity_study_view( @@ -290,7 +337,14 @@ def _issue_validity_study_view( fields: tuple[tuple[str, object], ...], ) -> ValidityStudyView: """Issue one immutable view after authorization and target validation complete.""" - return tuple.__new__(ValidityStudyView, (tenant_record_id, validity_study_id, fields)) + return tuple.__new__( + ValidityStudyView, + ( + _store_operational_uuid("tenant_record_id", tenant_record_id), + _store_operational_uuid("validity_study_id", validity_study_id), + _store_view_fields(fields), + ), + ) @runtime_checkable @@ -319,9 +373,10 @@ def read_validity_study( ) -> ValidityStudyView: """Authorize and read one validity-study header through the canonical owner port. - Authorization is completed before persistence. The persistence result is then - reconstructed into an exact immutable value and must match the authorized - tenant/study identity before any field is returned. + Authorization is completed before persistence. Immutable integer snapshots + preserve the authorized target across the executable repository call. The + persistence result is reconstructed into an exact immutable value and must + match those snapshots before any field is returned. """ if type(principal) is not ValidationPrincipal: raise TypeError("principal must be an exact ValidationPrincipal.") @@ -336,8 +391,10 @@ def read_validity_study( actor_reference=principal.actor_reference, granted_scope_codes=principal.granted_scope_codes, ) - tenant_id = _require_operational_uuid("tenant_record_id", tenant_record_id) - study_id = _require_operational_uuid("validity_study_id", validity_study_id) + tenant_identity = _store_operational_uuid("tenant_record_id", tenant_record_id) + study_identity = _store_operational_uuid("validity_study_id", validity_study_id) + tenant_id = _restore_operational_uuid("tenant_record_id", tenant_identity) + study_id = _restore_operational_uuid("validity_study_id", study_identity) purpose = _require_code("purpose_code", purpose_code) fields = _validate_requested_fields(requested_fields) detached_policy = _detach_policy(policy) @@ -359,8 +416,8 @@ def read_validity_study( ) persisted = read_port.read_validity_study( - tenant_record_id=tenant_id, - validity_study_id=study_id, + tenant_record_id=_restore_operational_uuid("tenant_record_id", tenant_identity), + validity_study_id=_restore_operational_uuid("validity_study_id", study_identity), ) if persisted is None: raise ValidityStudyNotFound(str(study_id)) @@ -375,7 +432,12 @@ def read_validity_study( recorded_from=persisted.recorded_from, recorded_to=persisted.recorded_to, ) - if record.tenant_record_id != tenant_id or record.validity_study_id != study_id: + if ( + _store_operational_uuid("record tenant_record_id", record.tenant_record_id) + != tenant_identity + or _store_operational_uuid("record validity_study_id", record.validity_study_id) + != study_identity + ): raise ValidityStudyIntegrityError("repository returned a validity-study record for another target") values = { @@ -385,7 +447,7 @@ def read_validity_study( "recorded_to": record.recorded_to, } return _issue_validity_study_view( - tenant_record_id=tenant_id, - validity_study_id=study_id, + tenant_record_id=_restore_operational_uuid("tenant_record_id", tenant_identity), + validity_study_id=_restore_operational_uuid("validity_study_id", study_identity), fields=tuple((field_name, values[field_name]) for field_name in sorted(fields)), ) From 65052598187e8b7b177f58e65f83d004dbfa8f83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:10:03 +0900 Subject: [PATCH 44/60] docs(workforce-validation): record UUID storage boundary --- services/workforce-validation-api/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index 2e18dc72..ee87458a 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -10,11 +10,13 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar - accepts structurally immutable authenticated Keyverse identity attributes, not credentials; - reconstructs and revalidates principal storage before building the access request, so exact tuple type alone is not treated as identity authority; +- stores UUID identity evidence behind the tuple-backed principal/record/view as exact integer payloads and reconstructs fresh UUID objects at public boundaries, so a retained UUID reference cannot rewrite accepted tenant/study/criterion identity through `object.__setattr__`; +- preserves tenant/study authorization targets as immutable integer snapshots across the executable repository call, so a repository cannot make a foreign record self-consistent by mutating the UUID objects it receives; - inertly verifies that the owner repository exposes a statically callable `read_validity_study` capability before authorization, without executing caller-controlled descriptors; - evaluates tenant, purpose, operation, scope, resource, and requested fields before persistence; - calls only a `ValidityStudyReadPort` owned by this context; - reconstructs persisted registry scalars into structurally immutable owner evidence before target validation and output; -- returns only the fields authorized for the exact study record; +- returns only the fields authorized for the exact study record; UUID-valued projected fields are reconstituted fresh rather than exposing mutable internal UUID aliases; - issues `ValidityStudyView` only from the authorized read path. Its public constructor fails closed, and the returned tuple-backed projection cannot be rewritten through ordinary assignment or `object.__setattr__`. `ValidityStudyView` is a data projection, not a durable authorization credential or cryptographic capability. Downstream consequential actions must perform their own purpose-bound authorization and authoritative re-resolution rather than treating the Python runtime type as reusable authority. Low-level interpreter construction is outside the supported public API and is not accepted as proof that authorization occurred. @@ -23,7 +25,7 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. -Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236–#242 retain the current bootstrap trust-boundary findings through exact-head acceptance and protected integration: persisted-record immutability, principal immutability and constructor revalidation, owner-role/runtime-role separation, inert repository-capability validation, immutable minimized output, and non-public issuance of that output. +Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236–#243 retain the current bootstrap trust-boundary findings through exact-head acceptance and protected integration: persisted-record immutability, principal immutability and constructor revalidation, owner-role/runtime-role separation, inert repository-capability validation, immutable minimized output, non-public issuance of that output, and detached UUID storage/target snapshots. ## Test From 23bdae68d23b5d50673184ab0a2dccd0dad3d355 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:16:18 +0900 Subject: [PATCH 45/60] test(workforce-validation): expose executable UUID payload --- .../tests/test_uuid_payload_integrity.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 services/workforce-validation-api/tests/test_uuid_payload_integrity.py diff --git a/services/workforce-validation-api/tests/test_uuid_payload_integrity.py b/services/workforce-validation-api/tests/test_uuid_payload_integrity.py new file mode 100644 index 00000000..5b20e787 --- /dev/null +++ b/services/workforce-validation-api/tests/test_uuid_payload_integrity.py @@ -0,0 +1,30 @@ +"""Regression contract for exact UUID payload validation before sentinel comparison.""" + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from orgmetra_workforce_validation_api.registry import ValidationPrincipal + + +class _ExecutableUUIDPayload: + """Fail if validation compares a forged UUID payload before proving it is an int.""" + + def __eq__(self, other: object) -> bool: + """Expose equality execution as a trust-boundary violation.""" + raise AssertionError(f"forged UUID payload executed equality against {other!r}") + + +def test_exact_uuid_with_executable_internal_payload_fails_before_comparison() -> None: + """Exact UUID outer type cannot authorize executable non-integer internal storage.""" + tenant_record_id = UUID("10000000-0000-7000-8000-000000000001") + object.__setattr__(tenant_record_id, "int", _ExecutableUUIDPayload()) + + with pytest.raises(ValueError, match="tenant_record_id must be an exact operational UUID"): + ValidationPrincipal( + tenant_record_id=tenant_record_id, + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ) From 7f7617b4225d96aed0f6d522c25302228b8a6bde Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:17:16 +0900 Subject: [PATCH 46/60] fix(workforce-validation): validate UUID payload before comparison --- .../orgmetra_workforce_validation_api/registry.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index e0f2cb49..f696fc88 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -46,16 +46,19 @@ class ValidityStudyIntegrityError(RuntimeError): """Indicate that persistence returned a record outside the authorized target.""" -def _require_operational_uuid(field_name: str, value: object) -> UUID: - """Return one exact operational UUID and reject protocol sentinels or subtypes.""" - if type(value) is not UUID or value.int in (0, _MAX_UUID_INT): +def _require_operational_uuid(field_name: str, value: object) -> int: + """Return one inert UUID integer after exact outer and internal-type validation.""" + if type(value) is not UUID: raise ValueError(f"{field_name} must be an exact operational UUID.") - return value + identity = value.int + if type(identity) is not int or identity <= 0 or identity >= _MAX_UUID_INT: + raise ValueError(f"{field_name} must be an exact operational UUID.") + return identity def _store_operational_uuid(field_name: str, value: object) -> int: """Reduce one validated UUID to immutable integer storage without retaining its object alias.""" - return _require_operational_uuid(field_name, value).int + return _require_operational_uuid(field_name, value) def _restore_operational_uuid(field_name: str, value: object) -> UUID: From 7c71f81c63ff87d6524fd565b3b5ce444905ca2e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:17:40 +0900 Subject: [PATCH 47/60] docs(workforce-validation): record UUID payload validation --- services/workforce-validation-api/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/workforce-validation-api/README.md b/services/workforce-validation-api/README.md index ee87458a..d3d84736 100644 --- a/services/workforce-validation-api/README.md +++ b/services/workforce-validation-api/README.md @@ -10,6 +10,7 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar - accepts structurally immutable authenticated Keyverse identity attributes, not credentials; - reconstructs and revalidates principal storage before building the access request, so exact tuple type alone is not treated as identity authority; +- requires both exact `UUID` outer type and exact built-in integer UUID payload before any sentinel/range comparison, so a forged exact UUID with executable internal storage is rejected without invoking caller-defined equality behavior; - stores UUID identity evidence behind the tuple-backed principal/record/view as exact integer payloads and reconstructs fresh UUID objects at public boundaries, so a retained UUID reference cannot rewrite accepted tenant/study/criterion identity through `object.__setattr__`; - preserves tenant/study authorization targets as immutable integer snapshots across the executable repository call, so a repository cannot make a foreign record self-consistent by mutating the UUID objects it receives; - inertly verifies that the owner repository exposes a statically callable `read_validity_study` capability before authorization, without executing caller-controlled descriptors; @@ -25,7 +26,7 @@ It does **not** query People, Talent Acquisition, Performance Management, Job Ar Protected foundation migrations still create validity-study tables in the legacy foundation schema, so the next forward-only persistence increment must adopt those records without normalizing `public.validity_study` as a long-lived service contract or breaking existing linkage evidence. -Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236–#243 retain the current bootstrap trust-boundary findings through exact-head acceptance and protected integration: persisted-record immutability, principal immutability and constructor revalidation, owner-role/runtime-role separation, inert repository-capability validation, immutable minimized output, non-public issuance of that output, and detached UUID storage/target snapshots. +Issue #234 owns the remaining order: durable owner-schema adoption and PostgreSQL adapter, idempotent registration, explicit predictor/sample/decision-policy/analysis-protocol versions, scientific adapters, OpenAPI/gateway exposure, and realistic p95 measurement. Issues #236–#244 retain the current bootstrap trust-boundary findings through exact-head acceptance and protected integration: persisted-record immutability, principal immutability and constructor revalidation, owner-role/runtime-role separation, inert repository-capability validation, immutable minimized output, non-public issuance of that output, detached UUID storage/target snapshots, and exact validation of UUID internal payloads before comparison. ## Test From dbb0d251a5b2b2fa6bee558febe74a8e6bd33c59 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:26:37 +0900 Subject: [PATCH 48/60] test(workforce-validation): reject importable view issuer --- .../tests/test_view_issuance_integrity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/workforce-validation-api/tests/test_view_issuance_integrity.py b/services/workforce-validation-api/tests/test_view_issuance_integrity.py index 1095659a..b65e72b0 100644 --- a/services/workforce-validation-api/tests/test_view_issuance_integrity.py +++ b/services/workforce-validation-api/tests/test_view_issuance_integrity.py @@ -4,6 +4,7 @@ import pytest +import orgmetra_workforce_validation_api.registry as registry from orgmetra_workforce_validation_api.registry import ValidityStudyView @@ -19,3 +20,8 @@ def test_direct_authorized_view_construction_fails_closed() -> None: validity_study_id=STUDY, fields=(("study_status_code", "study_draft"),), ) + + +def test_registry_module_exposes_no_unconditional_view_issuer() -> None: + """Keep ordinary view issuance inside the authorized read application path.""" + assert not hasattr(registry, "_issue_validity_study_view") From 656a0c41c06bc517b2cf7c554e35a6fb4f8c4f4b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:28:24 +0900 Subject: [PATCH 49/60] fix(workforce-validation): keep view issuance inside authorized read path --- .../registry.py | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index f696fc88..c86c0531 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -333,23 +333,6 @@ def fields(self) -> tuple[tuple[str, object], ...]: return _restore_view_fields(self[2]) -def _issue_validity_study_view( - *, - tenant_record_id: UUID, - validity_study_id: UUID, - fields: tuple[tuple[str, object], ...], -) -> ValidityStudyView: - """Issue one immutable view after authorization and target validation complete.""" - return tuple.__new__( - ValidityStudyView, - ( - _store_operational_uuid("tenant_record_id", tenant_record_id), - _store_operational_uuid("validity_study_id", validity_study_id), - _store_view_fields(fields), - ), - ) - - @runtime_checkable class ValidityStudyReadPort(Protocol): """Owner repository contract for one tenant-local validity-study header.""" @@ -449,8 +432,12 @@ def read_validity_study( "recorded_from": record.recorded_from, "recorded_to": record.recorded_to, } - return _issue_validity_study_view( - tenant_record_id=_restore_operational_uuid("tenant_record_id", tenant_identity), - validity_study_id=_restore_operational_uuid("validity_study_id", study_identity), - fields=tuple((field_name, values[field_name]) for field_name in sorted(fields)), + projected_fields = tuple((field_name, values[field_name]) for field_name in sorted(fields)) + return tuple.__new__( + ValidityStudyView, + ( + tenant_identity, + study_identity, + _store_view_fields(projected_fields), + ), ) From 9a83ff0b373799297c0c1daede645da11296c388 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:32:22 +0900 Subject: [PATCH 50/60] test(workforce-validation): bind repository capability use --- .../test_read_port_dependency_integrity.py | 84 +++++++++++++++++-- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py b/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py index a5e2cbda..d91fe206 100644 --- a/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py +++ b/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py @@ -1,7 +1,8 @@ -"""Regression contract for inert repository capability validation before authorization.""" +"""Regression contracts for inert repository capability validation before authorization.""" from __future__ import annotations +from datetime import datetime, timezone from uuid import UUID import pytest @@ -9,11 +10,14 @@ from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_workforce_validation_api.registry import ( ValidationPrincipal, + ValidityStudyRecord, read_validity_study, ) TENANT = UUID("10000000-0000-7000-8000-000000000001") STUDY = UUID("00000000-0000-7000-8000-0000000000c1") +CRITERION = UUID("00000000-0000-7000-8000-0000000000a1") +RECORDED_FROM = datetime(2026, 11, 3, tzinfo=timezone.utc) class _DescriptorReadPort: @@ -25,30 +29,94 @@ def read_validity_study(self) -> object: raise AssertionError("repository descriptor executed before rejection") -def test_noncallable_repository_capability_fails_before_authorization() -> None: - """Reject an invalid port before a deliberately denying policy can be evaluated.""" - principal = ValidationPrincipal( +class _DynamicLookupReadPort: + """Expose one safe class method but a different callable through instance lookup.""" + + def __init__(self) -> None: + self.dynamic_lookups = 0 + self.static_calls = 0 + + def __getattribute__(self, name: str) -> object: + """Trip if the authorized path performs a second dynamic capability lookup.""" + if name == "read_validity_study": + dynamic_lookups = object.__getattribute__(self, "dynamic_lookups") + object.__setattr__(self, "dynamic_lookups", dynamic_lookups + 1) + + def switched_capability(*, tenant_record_id: UUID, validity_study_id: UUID) -> object: + del tenant_record_id, validity_study_id + raise AssertionError("dynamic repository capability lookup executed after validation") + + return switched_capability + return object.__getattribute__(self, name) + + def read_validity_study( + self, + *, + tenant_record_id: UUID, + validity_study_id: UUID, + ) -> ValidityStudyRecord: + """Return valid owner evidence when the statically validated method is invoked.""" + self.static_calls += 1 + return ValidityStudyRecord( + tenant_record_id=tenant_record_id, + validity_study_id=validity_study_id, + criterion_blueprint_id=CRITERION, + study_status_code="study_draft", + recorded_from=RECORDED_FROM, + recorded_to=None, + ) + + +def _principal() -> ValidationPrincipal: + """Return one exact authenticated validation principal.""" + return ValidationPrincipal( tenant_record_id=TENANT, actor_reference="person:analyst-1", granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), ) - denying_policy = PurposeBoundAccessPolicy( + + +def _policy(*, purpose_code: str = "validation_review") -> PurposeBoundAccessPolicy: + """Return one purpose-bound policy for the focused repository tests.""" + return PurposeBoundAccessPolicy( tenant_record_id=TENANT, policy_version_code="validation-read-v1", resource_kind="validity_study_record", - purpose_code="audit_review", + purpose_code=purpose_code, operation_code="read", required_scope_code="orgmetra.workforce_validation.read", permitted_fields=frozenset({"study_status_code"}), ) + +def test_noncallable_repository_capability_fails_before_authorization() -> None: + """Reject an invalid port before a deliberately denying policy can be evaluated.""" with pytest.raises(TypeError, match="read_port must expose a statically callable read_validity_study"): read_validity_study( - principal=principal, + principal=_principal(), tenant_record_id=TENANT, validity_study_id=STUDY, purpose_code="validation_review", requested_fields=frozenset({"study_status_code"}), - policy=denying_policy, + policy=_policy(purpose_code="audit_review"), read_port=_DescriptorReadPort(), # type: ignore[arg-type] ) + + +def test_validated_repository_capability_is_the_capability_invoked_after_authorization() -> None: + """Bind the inertly validated class method instead of re-resolving it dynamically.""" + port = _DynamicLookupReadPort() + + view = read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(), + read_port=port, + ) + + assert port.dynamic_lookups == 0 + assert port.static_calls == 1 + assert view.fields == (("study_status_code", "study_draft"),) From 0ac2255321eaf1d0068978b931990f4d9c9f1c85 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:33:49 +0900 Subject: [PATCH 51/60] fix(workforce-validation): bind validated repository capability --- .../registry.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index c86c0531..78c1628b 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -12,6 +12,7 @@ from datetime import datetime, timezone from inspect import getattr_static import re +from types import FunctionType from typing import Protocol, runtime_checkable from uuid import UUID from zoneinfo import ZoneInfo @@ -359,17 +360,20 @@ def read_validity_study( ) -> ValidityStudyView: """Authorize and read one validity-study header through the canonical owner port. - Authorization is completed before persistence. Immutable integer snapshots - preserve the authorized target across the executable repository call. The - persistence result is reconstructed into an exact immutable value and must - match those snapshots before any field is returned. + Authorization is completed before persistence. The exact ordinary repository + method is captured inertly before authorization and that same function is + invoked after authorization, so dynamic instance lookup cannot switch the + validated capability. Immutable integer snapshots preserve the authorized + target across the executable repository call. The persistence result is + reconstructed into an exact immutable value and must match those snapshots + before any field is returned. """ if type(principal) is not ValidationPrincipal: raise TypeError("principal must be an exact ValidationPrincipal.") if type(policy) is not PurposeBoundAccessPolicy: raise TypeError("policy must be an exact PurposeBoundAccessPolicy.") - read_capability = getattr_static(read_port, "read_validity_study", None) - if not callable(read_capability): + read_capability = getattr_static(type(read_port), "read_validity_study", None) + if type(read_capability) is not FunctionType: raise TypeError("read_port must expose a statically callable read_validity_study.") detached_principal = ValidationPrincipal( @@ -401,7 +405,8 @@ def read_validity_study( policy=detached_policy, ) - persisted = read_port.read_validity_study( + persisted = read_capability( + read_port, tenant_record_id=_restore_operational_uuid("tenant_record_id", tenant_identity), validity_study_id=_restore_operational_uuid("validity_study_id", study_identity), ) From d2196311dbf4aa572d8a99bedc27d4a178dacc80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 00:05:16 +0900 Subject: [PATCH 52/60] test(workforce-validation): cover policy equality tripwire --- .../tests/test_policy_runtime_integrity.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/workforce-validation-api/tests/test_policy_runtime_integrity.py b/services/workforce-validation-api/tests/test_policy_runtime_integrity.py index b3071641..f712aacf 100644 --- a/services/workforce-validation-api/tests/test_policy_runtime_integrity.py +++ b/services/workforce-validation-api/tests/test_policy_runtime_integrity.py @@ -22,8 +22,13 @@ class _ExecutableText(str): calls = 0 + def __eq__(self, other: object) -> bool: + """Expose any equality comparison before the boundary rejects the subtype.""" + type(self).calls += 1 + raise AssertionError("caller-defined policy comparison executed") + def __ne__(self, other: object) -> bool: - """Expose any comparison before the boundary rejects the subtype.""" + """Expose any inequality comparison before the boundary rejects the subtype.""" type(self).calls += 1 raise AssertionError("caller-defined policy comparison executed") From 47c3b19ce1e2f35c0e59f92c20b042391901f849 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 00:05:27 +0900 Subject: [PATCH 53/60] test(workforce-validation): keep one registry import style --- .../tests/test_view_issuance_integrity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/workforce-validation-api/tests/test_view_issuance_integrity.py b/services/workforce-validation-api/tests/test_view_issuance_integrity.py index b65e72b0..a44fce40 100644 --- a/services/workforce-validation-api/tests/test_view_issuance_integrity.py +++ b/services/workforce-validation-api/tests/test_view_issuance_integrity.py @@ -5,7 +5,6 @@ import pytest import orgmetra_workforce_validation_api.registry as registry -from orgmetra_workforce_validation_api.registry import ValidityStudyView TENANT = UUID("10000000-0000-7000-8000-000000000001") @@ -15,7 +14,7 @@ def test_direct_authorized_view_construction_fails_closed() -> None: """Require purpose-bound reads, not public construction, to issue study views.""" with pytest.raises(TypeError, match="issued only by read_validity_study"): - ValidityStudyView( + registry.ValidityStudyView( tenant_record_id=TENANT, validity_study_id=STUDY, fields=(("study_status_code", "study_draft"),), From db7ce6b117052ea4fba59e61f15fd22389abe194 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 00:05:55 +0900 Subject: [PATCH 54/60] test(workforce-validation): use protocol-standard attribute trap --- .../tests/test_principal_storage_integrity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/workforce-validation-api/tests/test_principal_storage_integrity.py b/services/workforce-validation-api/tests/test_principal_storage_integrity.py index 9473a5b1..a0b29985 100644 --- a/services/workforce-validation-api/tests/test_principal_storage_integrity.py +++ b/services/workforce-validation-api/tests/test_principal_storage_integrity.py @@ -22,7 +22,7 @@ class _ExecutableUUID(UUID): def __getattribute__(self, name: str) -> object: if name == "int": - raise AssertionError("UUID subtype behavior executed") + raise AttributeError("UUID subtype behavior executed") return super().__getattribute__(name) From 14c10e8fe4861299ab4808d2e25cf25df35c8549 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 00:11:06 +0900 Subject: [PATCH 55/60] test(workforce-validation): reject inherited Protocol repository stub --- .../test_read_port_dependency_integrity.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py b/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py index d91fe206..e49cefc9 100644 --- a/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py +++ b/services/workforce-validation-api/tests/test_read_port_dependency_integrity.py @@ -10,6 +10,7 @@ from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_workforce_validation_api.registry import ( ValidationPrincipal, + ValidityStudyReadPort, ValidityStudyRecord, read_validity_study, ) @@ -67,6 +68,10 @@ def read_validity_study( ) +class _InheritedProtocolReadPort(ValidityStudyReadPort): + """Intentionally inherit the Protocol declaration without implementing persistence.""" + + def _principal() -> ValidationPrincipal: """Return one exact authenticated validation principal.""" return ValidationPrincipal( @@ -103,6 +108,20 @@ def test_noncallable_repository_capability_fails_before_authorization() -> None: ) +def test_inherited_protocol_placeholder_fails_before_authorization() -> None: + """Require a concrete repository implementation before Keyverse policy evaluation.""" + with pytest.raises(TypeError, match="read_port must expose a statically callable read_validity_study"): + read_validity_study( + principal=_principal(), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=_policy(purpose_code="audit_review"), + read_port=_InheritedProtocolReadPort(), + ) + + def test_validated_repository_capability_is_the_capability_invoked_after_authorization() -> None: """Bind the inertly validated class method instead of re-resolving it dynamically.""" port = _DynamicLookupReadPort() From 72ec2296cbc6b2df94e9c4e7394a8990061d0c88 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 00:12:40 +0900 Subject: [PATCH 56/60] fix(workforce-validation): reject inherited Protocol repository stub --- .../src/orgmetra_workforce_validation_api/registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py index 78c1628b..10159e1d 100644 --- a/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py +++ b/services/workforce-validation-api/src/orgmetra_workforce_validation_api/registry.py @@ -348,6 +348,9 @@ def read_validity_study( ... +_PROTOCOL_READ_CAPABILITY = getattr_static(ValidityStudyReadPort, "read_validity_study") + + def read_validity_study( *, principal: ValidationPrincipal, @@ -373,7 +376,7 @@ def read_validity_study( if type(policy) is not PurposeBoundAccessPolicy: raise TypeError("policy must be an exact PurposeBoundAccessPolicy.") read_capability = getattr_static(type(read_port), "read_validity_study", None) - if type(read_capability) is not FunctionType: + if type(read_capability) is not FunctionType or read_capability is _PROTOCOL_READ_CAPABILITY: raise TypeError("read_port must expose a statically callable read_validity_study.") detached_principal = ValidationPrincipal( From 195ffef5026625d5e1d36b0dbd0175acb4f65108 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 02:05:37 +0900 Subject: [PATCH 57/60] fix(foundation): admit workforce validation postgres contract --- scripts/foundation-contract-core.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index 4aacefb3..ba2d8c00 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -85,6 +85,7 @@ export const REQUIRED_FILES = Object.freeze([ 'tests/test_audit_outbox_hardening_postgres.sh', 'tests/test_candidate_worker_conversion_postgres.sh', 'tests/test_validity_study_case_postgres.sh', + 'tests/test_workforce_validation_owner_schema_postgres.sh', 'tests/test_criterion_observation_scope_postgres.sh', 'tests/test_people_mutation_idempotency_postgres.sh', 'tests/test_job_analysis_snapshot_postgres.sh', @@ -685,4 +686,4 @@ export function runCli(rootPath, outputStream = process.stdout, errorStream = pr } errorStream.write(`${JSON.stringify({ status: 'failed', error_count: errors.length, errors }, null, 2)}\n`); return 1; -} +} \ No newline at end of file From c91df2374eaf65bb36a337854cd231c601e93cb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 02:07:00 +0900 Subject: [PATCH 58/60] fix(foundation): register workforce validation postgres provenance --- tests/validate_repository.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/validate_repository.py b/tests/validate_repository.py index d9d4c15a..1a05c8ef 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -88,6 +88,7 @@ "tests/test_audit_outbox_hardening_postgres.sh", "tests/test_candidate_worker_conversion_postgres.sh", "tests/test_validity_study_case_postgres.sh", + "tests/test_workforce_validation_owner_schema_postgres.sh", "tests/test_criterion_observation_scope_postgres.sh", "tests/test_people_mutation_idempotency_postgres.sh", "tests/test_job_analysis_snapshot_postgres.sh", @@ -634,4 +635,4 @@ def main() -> None: if __name__ == "__main__": - main() + main() \ No newline at end of file From e87d28a32683c6e6f115b3d13645b7d263451795 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 02:33:19 +0900 Subject: [PATCH 59/60] fix(foundation): reseal workforce validation postgres provenance --- manifest.json | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/manifest.json b/manifest.json index fbae55b2..f4085d0e 100644 --- a/manifest.json +++ b/manifest.json @@ -359,9 +359,9 @@ }, { "path": "scripts/foundation-contract-core.mjs", - "sha256": "9b03efbbdffa60a05f5924e8a61b1cbc3cd75c502df428a5920085e8d0bf3603", - "bytes": 28121, - "lines": 688 + "sha256": "5dfc54d40820dfc45962dcc91367c57baf6b011e68efc5f6e8d59e7e82145b2a", + "bytes": 28182, + "lines": 689 }, { "path": "scripts/foundation-contract.mjs", @@ -465,11 +465,17 @@ "bytes": 14708, "lines": 301 }, + { + "path": "tests/test_workforce_validation_owner_schema_postgres.sh", + "sha256": "29f0cd8a7d9040ff86095b68fa2f3d0ed54ea3777e5a816d4a79eb6ceafb9339", + "bytes": 2949, + "lines": 76 + }, { "path": "tests/validate_repository.py", - "sha256": "091836b2f68600a30b08f7da2cea8b3bef10201a123da720a7369bf10985eec2", - "bytes": 27237, - "lines": 637 + "sha256": "244627252e7392e4dbed98392c132cb86dc8e5ead839a1129298e8d022fcf8eb", + "bytes": 27300, + "lines": 638 } ] } From dd95dd7256f37aab2c4f26aa1fb43e8c867f4e4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 04:07:23 +0900 Subject: [PATCH 60/60] test(workforce-validation): cover policy field runtime guard --- .../tests/test_policy_runtime_integrity.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/services/workforce-validation-api/tests/test_policy_runtime_integrity.py b/services/workforce-validation-api/tests/test_policy_runtime_integrity.py index f712aacf..c72a6c22 100644 --- a/services/workforce-validation-api/tests/test_policy_runtime_integrity.py +++ b/services/workforce-validation-api/tests/test_policy_runtime_integrity.py @@ -21,6 +21,7 @@ class _ExecutableText(str): """Trip if authorization compares this caller-defined string subtype.""" calls = 0 + __hash__ = str.__hash__ def __eq__(self, other: object) -> bool: """Expose any equality comparison before the boundary rejects the subtype.""" @@ -77,3 +78,36 @@ def test_policy_text_subtype_is_rejected_before_comparison_or_persistence() -> N assert _ExecutableText.calls == 0 assert port.calls == 0 + + +def test_policy_field_subtype_is_rejected_before_comparison_or_persistence() -> None: + _ExecutableText.calls = 0 + port = _ReadPort() + assert isinstance(port, ValidityStudyReadPort) + policy = PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="validation-read-v1", + resource_kind="validity_study_record", + purpose_code="validation_review", + operation_code="read", + required_scope_code="orgmetra.workforce_validation.read", + permitted_fields=frozenset({_ExecutableText("study_status_code")}), + ) + + with pytest.raises(ValueError, match="policy permitted_fields"): + read_validity_study( + principal=ValidationPrincipal( + tenant_record_id=TENANT, + actor_reference="person:analyst-1", + granted_scope_codes=frozenset({"orgmetra.workforce_validation.read"}), + ), + tenant_record_id=TENANT, + validity_study_id=STUDY, + purpose_code="validation_review", + requested_fields=frozenset({"study_status_code"}), + policy=policy, + read_port=port, + ) + + assert _ExecutableText.calls == 0 + assert port.calls == 0