From 3d2891c2ee8ab2f518a62f2ee0e7d22b727d2ea3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:05:15 -0700 Subject: [PATCH 001/101] test: define performance review quality contract --- packages/performance-review/pyproject.toml | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 packages/performance-review/pyproject.toml diff --git a/packages/performance-review/pyproject.toml b/packages/performance-review/pyproject.toml new file mode 100644 index 000000000..2366bdde5 --- /dev/null +++ b/packages/performance-review/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + +[project] +name = "orgmetra-performance-review" +version = "0.1.0" +description = "Governed human performance-review evidence for Orgmetra." +requires-python = ">=3.12" + +[project.optional-dependencies] +test = ["pytest>=8.3", "pytest-cov>=5.0"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = [ + "--cov=orgmetra_performance_review", + "--cov-branch", + "--cov-report=term-missing", + "--cov-fail-under=100", +] From fe16e6b9ff5a5b744638f38f59a1fac3ec3c1778 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:05:46 -0700 Subject: [PATCH 002/101] test: add RED governed performance review contract --- .../performance-review/tests/test_packet.py | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 packages/performance-review/tests/test_packet.py diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py new file mode 100644 index 000000000..19f13e03b --- /dev/null +++ b/packages/performance-review/tests/test_packet.py @@ -0,0 +1,184 @@ +from dataclasses import replace +from datetime import date, datetime, timedelta, timezone +from hashlib import sha256 +import json + +import pytest + +from orgmetra_performance_review import ( + PerformanceReviewPacket, + build_performance_review_packet, +) + +TENANT = "11111111-1111-4111-8111-111111111111" +PERFORMANCE_REVIEW = "performance_review:22222222-2222-4222-8222-222222222222" +PERSON = "person_record:33333333-3333-4333-8333-333333333333" +EMPLOYMENT = "employment_record:44444444-4444-4444-8444-444444444444" +JOB = "job_profile:55555555-5555-4555-8555-555555555555" +CYCLE = "performance_cycle:66666666-6666-4666-8666-666666666666" +CRITERION_SET = "criterion_set:77777777-7777-4777-8777-777777777777" +GOAL_PLAN = "performance_goal_plan:88888888-8888-4888-8888-888888888888" +OBSERVATION_SNAPSHOT = "criterion_observation_snapshot:99999999-9999-4999-8999-999999999999" +DEVELOPMENT_PLAN = "development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +REVIEWER = "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +DIGEST_A = "a" * 64 +DIGEST_B = "b" * 64 +DIGEST_C = "c" * 64 +DIGEST_D = "d" * 64 +GENERATED_AT = datetime(2026, 8, 19, 5, 15, 30, 123456, tzinfo=timezone.utc) + + +def build_valid(**overrides: object) -> PerformanceReviewPacket: + values: dict[str, object] = { + "tenant_record_id": TENANT, + "performance_review_reference": PERFORMANCE_REVIEW, + "person_record_reference": PERSON, + "employment_record_reference": EMPLOYMENT, + "job_profile_reference": JOB, + "performance_cycle_reference": CYCLE, + "criterion_set_reference": CRITERION_SET, + "criterion_set_digest": DIGEST_A, + "goal_plan_reference": GOAL_PLAN, + "goal_plan_digest": DIGEST_B, + "criterion_observation_snapshot_reference": OBSERVATION_SNAPSHOT, + "criterion_observation_snapshot_digest": DIGEST_C, + "development_plan_reference": DEVELOPMENT_PLAN, + "development_plan_digest": DIGEST_D, + "reviewer_reference": REVIEWER, + "purpose_code": "performance_review", + "reason_code": "scheduled_cycle_review", + "review_period_start": date(2026, 1, 1), + "review_period_end": date(2026, 6, 30), + "generated_at": GENERATED_AT, + } + values.update(overrides) + return build_performance_review_packet(**values) + + +def test_builds_value_free_human_review_packet() -> None: + packet = build_valid() + assert packet.contains_person_pii is False + assert packet.contains_rating_value is False + assert packet.contains_free_form_model_output is False + assert packet.human_confirmation_required is True + assert packet.decision_authority == "human_review_only" + assert packet.review_state == "requires_human_review" + assert "record accountable human rating and feedback" in packet.next_action + + +def test_canonical_json_and_digest_are_deterministic() -> None: + packet = build_valid() + canonical = packet.canonical_json() + payload = json.loads(canonical) + assert payload["person_record_reference"] == PERSON + assert payload["generated_at"] == "2026-08-19T05:15:30.123456Z" + assert packet.sha256_digest() == sha256(canonical.encode("utf-8")).hexdigest() + assert canonical == build_valid().canonical_json() + + +def test_timestamp_normalizes_to_utc_without_losing_precision() -> None: + shifted = GENERATED_AT.astimezone(timezone(timedelta(hours=9))) + assert build_valid(generated_at=shifted).canonical_json() == build_valid().canonical_json() + later = build_valid(generated_at=GENERATED_AT.replace(microsecond=123457)) + assert later.canonical_json() != build_valid().canonical_json() + assert later.sha256_digest() != build_valid().sha256_digest() + + +def test_optional_development_plan_may_be_absent_as_a_pair() -> None: + packet = build_valid(development_plan_reference=None, development_plan_digest=None) + assert packet.development_plan_reference is None + assert packet.development_plan_digest is None + + +@pytest.mark.parametrize("tenant", ["not-a-uuid", "00000000-0000-0000-0000-000000000000", "11111111-1111-4111-8111-11111111111A", None]) +def test_rejects_noncanonical_tenant_identity(tenant: object) -> None: + with pytest.raises(ValueError, match="tenant_record_id"): + build_valid(tenant_record_id=tenant) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("performance_review_reference", "performance_review:Jane-Doe"), + ("person_record_reference", "candidate_profile:33333333-3333-4333-8333-333333333333"), + ("employment_record_reference", "employment_record:00000000-0000-0000-0000-000000000000"), + ("job_profile_reference", "job_profile:not-a-uuid"), + ("performance_cycle_reference", "performance_cycle:66666666-6666-4666-8666-66666666666A"), + ("criterion_set_reference", "criterion:77777777-7777-4777-8777-777777777777"), + ("goal_plan_reference", "performance_goal_plan:salary"), + ("criterion_observation_snapshot_reference", "criterion_observation_snapshot:score-4"), + ("reviewer_reference", "actor:reviewer-name"), + ], +) +def test_rejects_nonopaque_or_wrong_namespace_references(field: str, value: str) -> None: + with pytest.raises(ValueError, match=field): + build_valid(**{field: value}) + + +@pytest.mark.parametrize("field", ["criterion_set_digest", "goal_plan_digest", "criterion_observation_snapshot_digest", "development_plan_digest"]) +def test_rejects_malformed_evidence_digests(field: str) -> None: + with pytest.raises(ValueError, match=field): + build_valid(**{field: "ABC"}) + + +def test_requires_development_reference_and_digest_as_a_pair() -> None: + with pytest.raises(ValueError, match="development plan reference and digest"): + build_valid(development_plan_reference=None) + with pytest.raises(ValueError, match="development plan reference and digest"): + build_valid(development_plan_digest=None) + + +@pytest.mark.parametrize("purpose", ["selection_review", "performance", "Performance_Review"]) +def test_purpose_is_fixed(purpose: str) -> None: + with pytest.raises(ValueError, match="purpose_code must remain performance_review"): + build_valid(purpose_code=purpose) + + +@pytest.mark.parametrize("reason", ["singleword", "Upper_case", "a" * 65]) +def test_reason_code_is_bounded_descriptive_snake_case(reason: str) -> None: + with pytest.raises(ValueError, match="reason_code"): + build_valid(reason_code=reason) + + +def test_review_period_must_be_real_dates_in_order() -> None: + with pytest.raises(ValueError, match="review_period_start"): + build_valid(review_period_start="2026-01-01") + with pytest.raises(ValueError, match="review_period_end"): + build_valid(review_period_end="2026-06-30") + with pytest.raises(ValueError, match="review period"): + build_valid(review_period_start=date(2026, 7, 1), review_period_end=date(2026, 6, 30)) + + +@pytest.mark.parametrize("generated_at", [datetime(2026, 8, 19, 5, 15), "2026-08-19T05:15:00Z"]) +def test_generated_at_must_be_timezone_aware_datetime(generated_at: object) -> None: + with pytest.raises(ValueError, match="generated_at"): + build_valid(generated_at=generated_at) + + +@pytest.mark.parametrize( + ("field", "value", "message"), + [ + ("contains_person_pii", True, "must not contain person PII"), + ("contains_rating_value", True, "must not contain rating values"), + ("contains_free_form_model_output", True, "must not contain free-form model output"), + ("human_confirmation_required", 1, "human confirmation is mandatory"), + ("decision_authority", "model_decision", "decision_authority"), + ("review_state", "approved", "review_state"), + ("next_action", "Auto-rate the employee.", "next_action"), + ], +) +def test_direct_construction_cannot_weaken_governance(field: str, value: object, message: str) -> None: + with pytest.raises(ValueError, match=message): + replace(build_valid(), **{field: value}) + + +def test_direct_construction_revalidates_reference_and_digest() -> None: + with pytest.raises(ValueError, match="person_record_reference"): + replace(build_valid(), person_record_reference="person_record:Jane-Doe") + with pytest.raises(ValueError, match="criterion_set_digest"): + replace(build_valid(), criterion_set_digest="0" * 63) + + +def test_builder_returns_same_public_type_as_direct_contract() -> None: + packet = build_valid() + assert isinstance(packet, PerformanceReviewPacket) From 080b6dc3d0a60e61f6b0ee9e1912dcba1417ac89 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:05:56 -0700 Subject: [PATCH 003/101] test: wire RED performance review quality gate --- .../workflows/performance-review-quality.yml | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/performance-review-quality.yml diff --git a/.github/workflows/performance-review-quality.yml b/.github/workflows/performance-review-quality.yml new file mode 100644 index 000000000..5ea88b260 --- /dev/null +++ b/.github/workflows/performance-review-quality.yml @@ -0,0 +1,57 @@ +name: Performance Review Quality + +on: + pull_request: + branches: + - develop + paths: + - "packages/performance-review/**" + - ".github/requirements/foundation-test.txt" + - ".github/workflows/performance-review-quality.yml" + - "docs/adr/0018-governed-performance-review.md" + - "docs/doctoring/performance-review-references.md" + - "docs/traceability/performance-review.md" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: performance-review-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit: + name: Performance review contract and 100% coverage + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Prove exact candidate checkout + env: + ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + check-latest: false + - name: Install reviewed test toolchain + run: | + python -m pip install --require-hashes --no-deps --only-binary=:all: -r .github/requirements/foundation-test.txt + python -m pip check + - name: Compile performance review package + run: python -m compileall -q packages/performance-review/src packages/performance-review/tests + - name: Test performance review with exact statement and branch coverage + env: + PYTHONPATH: packages/performance-review/src + COVERAGE_FILE: /tmp/orgmetra-performance-review.coverage + run: python -m pytest -c packages/performance-review/pyproject.toml packages/performance-review/tests + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" From 44fa375a66b0cb8432baac085ac6bebe44b94d2f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:06:50 -0700 Subject: [PATCH 004/101] feat: implement governed performance review packet --- .../src/orgmetra_performance_review/packet.py | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 packages/performance-review/src/orgmetra_performance_review/packet.py diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py new file mode 100644 index 000000000..044611ddf --- /dev/null +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -0,0 +1,270 @@ +"""Governed, value-free human performance-review evidence. + +The packet binds one employee review to authoritative Employment and Job scope, +a performance cycle, predetermined criteria and goals, an exact criterion-observation +snapshot, an optional development plan, and an accountable human reviewer. The opaque +person reference remains sensitive correlating metadata. Person PII, rating values, +free-form feedback, and free-form model output remain outside this envelope. +""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import date, datetime, timezone +from hashlib import sha256 +import json +import re +from uuid import UUID + +_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") +_DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") +_REFERENCE_PATTERN = re.compile( + r"^[a-z][a-z0-9_]{1,31}:[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$" +) +_PURPOSE_CODE = "performance_review" +_DECISION_AUTHORITY = "human_review_only" +_REVIEW_STATE = "requires_human_review" +_NEXT_ACTION = ( + "Verify authoritative Employment/Job scope, performance-cycle dates, governed " + "criteria and goals, criterion-observation evidence, and any development-plan " + "provenance; then record accountable human rating and feedback through the " + "authoritative performance workflow." +) + + +def _validate_operational_uuid(value: str, field_name: str) -> None: + """Require canonical non-sentinel UUID text for a governance identity.""" + try: + parsed = UUID(value) + except (ValueError, AttributeError, TypeError) as exc: + raise ValueError(f"{field_name} must be canonical UUID text") from exc + if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUID") + + +def _validate_code(value: str, field_name: str) -> None: + """Require a bounded descriptive lower snake_case governance code.""" + if not isinstance(value, str) or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): + raise ValueError(f"{field_name} must be bounded two-or-more-word lower snake_case") + + +def _validate_reference(value: str, prefix: str, field_name: str) -> None: + """Require an expected namespace plus a canonical operational UUID suffix.""" + error_message = f"{field_name} must be an opaque {prefix}: reference" + if ( + not isinstance(value, str) + or len(value) > 160 + or not _REFERENCE_PATTERN.fullmatch(value) + or not value.startswith(f"{prefix}:") + ): + raise ValueError(error_message) + suffix = value.split(":", 1)[1] + try: + parsed = UUID(suffix) + except (ValueError, AttributeError, TypeError) as exc: + raise ValueError(error_message) from exc + if str(parsed) != suffix or parsed.int in (0, (1 << 128) - 1): + raise ValueError(error_message) + + +def _validate_digest(value: str, field_name: str) -> None: + """Require lowercase SHA-256 hexadecimal evidence.""" + if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + raise ValueError(f"{field_name} must be lowercase SHA-256 hex") + + +def _canonical_timestamp(value: datetime) -> str: + """Render an aware instant as precision-preserving UTC RFC 3339 text.""" + if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: + raise ValueError("generated_at must be timezone-aware") + return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def _validate_business_date(value: date, field_name: str) -> None: + """Require a business date rather than a datetime or textual date.""" + if type(value) is not date: + raise ValueError(f"{field_name} must be a date") + + +@dataclass(frozen=True, slots=True) +class PerformanceReviewPacket: + """Immutable value-free performance-review packet awaiting accountable human review.""" + + tenant_record_id: str + performance_review_reference: str + person_record_reference: str + employment_record_reference: str + job_profile_reference: str + performance_cycle_reference: str + criterion_set_reference: str + criterion_set_digest: str + goal_plan_reference: str + goal_plan_digest: str + criterion_observation_snapshot_reference: str + criterion_observation_snapshot_digest: str + development_plan_reference: str | None + development_plan_digest: str | None + reviewer_reference: str + purpose_code: str + reason_code: str + review_period_start: date + review_period_end: date + generated_at: datetime + contains_person_pii: bool = False + contains_rating_value: bool = False + contains_free_form_model_output: bool = False + human_confirmation_required: bool = True + decision_authority: str = _DECISION_AUTHORITY + review_state: str = _REVIEW_STATE + next_action: str = _NEXT_ACTION + + def __post_init__(self) -> None: + """Fail closed when direct construction drifts from the governed contract.""" + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + _validate_reference( + self.performance_review_reference, + "performance_review", + "performance_review_reference", + ) + _validate_reference(self.person_record_reference, "person_record", "person_record_reference") + _validate_reference( + self.employment_record_reference, + "employment_record", + "employment_record_reference", + ) + _validate_reference(self.job_profile_reference, "job_profile", "job_profile_reference") + _validate_reference( + self.performance_cycle_reference, + "performance_cycle", + "performance_cycle_reference", + ) + _validate_reference(self.criterion_set_reference, "criterion_set", "criterion_set_reference") + _validate_digest(self.criterion_set_digest, "criterion_set_digest") + _validate_reference(self.goal_plan_reference, "performance_goal_plan", "goal_plan_reference") + _validate_digest(self.goal_plan_digest, "goal_plan_digest") + _validate_reference( + self.criterion_observation_snapshot_reference, + "criterion_observation_snapshot", + "criterion_observation_snapshot_reference", + ) + _validate_digest( + self.criterion_observation_snapshot_digest, + "criterion_observation_snapshot_digest", + ) + if (self.development_plan_reference is None) != (self.development_plan_digest is None): + raise ValueError("development plan reference and digest must be supplied together") + if self.development_plan_reference is not None: + _validate_reference( + self.development_plan_reference, + "development_plan", + "development_plan_reference", + ) + _validate_digest(self.development_plan_digest, "development_plan_digest") + _validate_reference(self.reviewer_reference, "actor", "reviewer_reference") + _validate_code(self.purpose_code, "purpose_code") + if self.purpose_code != _PURPOSE_CODE: + raise ValueError("purpose_code must remain performance_review") + _validate_code(self.reason_code, "reason_code") + _validate_business_date(self.review_period_start, "review_period_start") + _validate_business_date(self.review_period_end, "review_period_end") + if self.review_period_start > self.review_period_end: + raise ValueError("review period start must not be after review period end") + _canonical_timestamp(self.generated_at) + if self.contains_person_pii is not False: + raise ValueError("performance review packet must not contain person PII") + if self.contains_rating_value is not False: + raise ValueError("performance review packet must not contain rating values") + if self.contains_free_form_model_output is not False: + raise ValueError("performance review packet must not contain free-form model output") + if self.human_confirmation_required is not True: + raise ValueError("human confirmation is mandatory before performance rating") + if self.decision_authority != _DECISION_AUTHORITY: + raise ValueError("decision_authority must remain human_review_only") + if self.review_state != _REVIEW_STATE: + raise ValueError("review_state must remain requires_human_review") + if self.next_action != _NEXT_ACTION: + raise ValueError("next_action must remain the governed performance-review instruction") + + def canonical_json(self) -> str: + """Return deterministic canonical JSON for immutable audit correlation.""" + payload = { + "contains_free_form_model_output": self.contains_free_form_model_output, + "contains_person_pii": self.contains_person_pii, + "contains_rating_value": self.contains_rating_value, + "criterion_observation_snapshot_digest": self.criterion_observation_snapshot_digest, + "criterion_observation_snapshot_reference": self.criterion_observation_snapshot_reference, + "criterion_set_digest": self.criterion_set_digest, + "criterion_set_reference": self.criterion_set_reference, + "decision_authority": self.decision_authority, + "development_plan_digest": self.development_plan_digest, + "development_plan_reference": self.development_plan_reference, + "employment_record_reference": self.employment_record_reference, + "generated_at": _canonical_timestamp(self.generated_at), + "goal_plan_digest": self.goal_plan_digest, + "goal_plan_reference": self.goal_plan_reference, + "human_confirmation_required": self.human_confirmation_required, + "job_profile_reference": self.job_profile_reference, + "next_action": self.next_action, + "performance_cycle_reference": self.performance_cycle_reference, + "performance_review_reference": self.performance_review_reference, + "person_record_reference": self.person_record_reference, + "purpose_code": self.purpose_code, + "reason_code": self.reason_code, + "review_period_end": self.review_period_end.isoformat(), + "review_period_start": self.review_period_start.isoformat(), + "review_state": self.review_state, + "reviewer_reference": self.reviewer_reference, + "tenant_record_id": self.tenant_record_id, + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def sha256_digest(self) -> str: + """Return SHA-256 over the exact canonical UTF-8 performance-review packet.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + +def build_performance_review_packet( + *, + tenant_record_id: str, + performance_review_reference: str, + person_record_reference: str, + employment_record_reference: str, + job_profile_reference: str, + performance_cycle_reference: str, + criterion_set_reference: str, + criterion_set_digest: str, + goal_plan_reference: str, + goal_plan_digest: str, + criterion_observation_snapshot_reference: str, + criterion_observation_snapshot_digest: str, + development_plan_reference: str | None, + development_plan_digest: str | None, + reviewer_reference: str, + purpose_code: str, + reason_code: str, + review_period_start: date, + review_period_end: date, + generated_at: datetime, +) -> PerformanceReviewPacket: + """Build value-free performance-review evidence pending accountable human review.""" + return PerformanceReviewPacket( + tenant_record_id=tenant_record_id, + performance_review_reference=performance_review_reference, + person_record_reference=person_record_reference, + employment_record_reference=employment_record_reference, + job_profile_reference=job_profile_reference, + performance_cycle_reference=performance_cycle_reference, + criterion_set_reference=criterion_set_reference, + criterion_set_digest=criterion_set_digest, + goal_plan_reference=goal_plan_reference, + goal_plan_digest=goal_plan_digest, + criterion_observation_snapshot_reference=criterion_observation_snapshot_reference, + criterion_observation_snapshot_digest=criterion_observation_snapshot_digest, + development_plan_reference=development_plan_reference, + development_plan_digest=development_plan_digest, + reviewer_reference=reviewer_reference, + purpose_code=purpose_code, + reason_code=reason_code, + review_period_start=review_period_start, + review_period_end=review_period_end, + generated_at=generated_at, + ) From b76358066be121d0e8fc64ae1b02409f5581a07f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:06:56 -0700 Subject: [PATCH 005/101] feat: export governed performance review contract --- .../src/orgmetra_performance_review/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/performance-review/src/orgmetra_performance_review/__init__.py diff --git a/packages/performance-review/src/orgmetra_performance_review/__init__.py b/packages/performance-review/src/orgmetra_performance_review/__init__.py new file mode 100644 index 000000000..249d74eb9 --- /dev/null +++ b/packages/performance-review/src/orgmetra_performance_review/__init__.py @@ -0,0 +1,5 @@ +"""Public governed performance-review contract.""" + +from .packet import PerformanceReviewPacket, build_performance_review_packet + +__all__ = ["PerformanceReviewPacket", "build_performance_review_packet"] From e230dcfcee0e36c8ca31488b5c14e929b360a391 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:07:12 -0700 Subject: [PATCH 006/101] docs: explain governed performance review boundary --- packages/performance-review/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/performance-review/README.md diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md new file mode 100644 index 000000000..d48d15355 --- /dev/null +++ b/packages/performance-review/README.md @@ -0,0 +1,15 @@ +# Orgmetra Performance Review + +`orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying person PII, rating values, free-form feedback, or model output into the governance envelope. + +The packet binds one opaque person and Employment reference to the authoritative Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque UUID-backed reference and, where integrity matters, an independent SHA-256 digest. + +The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. + +## What this packet does not do + +It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, or execute a development action. It does not replace the authoritative performance/criterion persistence boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, or that a human review actually occurred. + +## Required review state + +Every packet remains `requires_human_review`, with `decision_authority="human_review_only"` and `human_confirmation_required=True`. The fixed next action tells the reviewer to verify Employment/Job scope, review-period and performance-cycle alignment, governed criteria and goals, criterion-observation evidence, and any development-plan provenance before recording accountable human rating and feedback through the authoritative performance workflow. From a5f208690e840798c9ed8424b573532cfe435875 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:07:20 -0700 Subject: [PATCH 007/101] docs: record unreleased performance review slice --- packages/performance-review/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/performance-review/CHANGELOG.md diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md new file mode 100644 index 000000000..2ab02f229 --- /dev/null +++ b/packages/performance-review/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +- Add a PII-minimized, human-review-only performance-review evidence packet binding authoritative Employment/Job scope, performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and accountable reviewer. From 9b8d11979076a9938a6874e435bb828bc6d24c63 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:07:37 -0700 Subject: [PATCH 008/101] docs: record governed performance review decision --- docs/adr/0018-governed-performance-review.md | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docs/adr/0018-governed-performance-review.md diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md new file mode 100644 index 000000000..cb30c31fc --- /dev/null +++ b/docs/adr/0018-governed-performance-review.md @@ -0,0 +1,39 @@ +# ADR 0018: Governed performance-review evidence packet + +- Status: Proposed — active PR only +- Date: 2026-08-19 + +## Context + +Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that proves which employment scope, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. + +U.S. OPM performance-management guidance treats performance management as a continuous cycle of planning, monitoring, developing, rating, and rewarding, and describes rating as evaluation against established elements and standards. ISO 30414:2025 Edition 2 provides current human-capital reporting requirements and recommendations across areas including productivity, skills/capabilities, and related workforce governance. Orgmetra uses those sources as design evidence, not as a claim that this packet by itself satisfies any jurisdiction-specific appraisal rule or ISO certification requirement. + +## Decision + +Introduce a transport-neutral `PerformanceReviewPacket` that remains pre-rating, value-free governance evidence. + +The packet MUST bind: + +- canonical tenant identity; +- opaque UUID-backed Person, Employment, Job, performance-cycle and performance-review references; +- a governed criterion-set reference plus independent SHA-256 digest; +- a governed performance-goal-plan reference plus independent SHA-256 digest; +- an exact criterion-observation-snapshot reference plus independent SHA-256 digest; +- an optional development-plan reference/digest pair; +- explicit business review-period dates; +- one accountable reviewer, fixed `performance_review` purpose, bounded reason code, and precision-preserving evidence timestamp. + +The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, and `review_state="requires_human_review"` remain intact. + +Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, the substantive validity or fairness of a criterion, lawful use, human completion, or the final rating. + +## Consequences + +Buyers can present a review-ready evidence envelope that keeps authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. + +This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. Later authoritative rating persistence must independently preserve actor, purpose, reason, evidence version, human confirmation, audit/outbox, temporal scope, and any applicable policy requirements. + +## References + +See `docs/doctoring/performance-review-references.md`. From 13af61ceee6acdbee3bc5202a4dd2ceb97851644 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:07:43 -0700 Subject: [PATCH 009/101] docs: add APA 7 performance review sources --- docs/doctoring/performance-review-references.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/doctoring/performance-review-references.md diff --git a/docs/doctoring/performance-review-references.md b/docs/doctoring/performance-review-references.md new file mode 100644 index 000000000..0e40e4a15 --- /dev/null +++ b/docs/doctoring/performance-review-references.md @@ -0,0 +1,9 @@ +# Performance-review references + +Retrieved August 19, 2026. These references support the active-PR governance boundary in ADR 0018. Orgmetra does not reproduce proprietary ISO text or claim certification. + +International Organization for Standardization. (2025). *ISO 30414:2025 human resource management—Requirements and recommendations for human capital reporting and disclosure* (2nd ed.). https://www.iso.org/standard/30414 + +U.S. Office of Personnel Management. (n.d.). *Performance management cycle*. Retrieved August 19, 2026, from https://www.opm.gov/policy-data-oversight/performance-management/performance-management-cycle/ + +U.S. Office of Personnel Management. (n.d.). *Performance management roadmap: Best practices guide for supervisors*. Retrieved August 19, 2026, from https://www.opm.gov/policy-data-oversight/performance-management/performance-management-toolkit/best-practices/performance-management-roadmap-for-supervisors/ From 22d71c6f92821541a3c8044ac1802e14da8fa540 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:07:55 -0700 Subject: [PATCH 010/101] docs: trace governed performance review slice --- docs/traceability/performance-review.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/traceability/performance-review.md diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md new file mode 100644 index 000000000..dacfe44eb --- /dev/null +++ b/docs/traceability/performance-review.md @@ -0,0 +1,18 @@ +# Performance review traceability + +Status: **active PR / proposed capability**, not protected-main truth. + +| Requirement | Evidence | Status | +|---|---|---| +| Bind review to authoritative Employment and Job scope | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference` | Implemented on active PR | +| Bind exact performance-cycle and business review period | `performance_cycle_reference`, `review_period_start`, `review_period_end` | Implemented on active PR | +| Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | +| Bind exact outcome evidence without copying values | `criterion_observation_snapshot_reference`/digest | Implemented on active PR | +| Preserve optional development provenance | paired `development_plan_reference`/digest | Implemented on active PR | +| Keep person PII, rating values, free-form feedback/model output outside packet | immutable false flags plus absence of value-bearing fields | Implemented on active PR | +| Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | +| Preserve deterministic immutable correlation evidence | canonical JSON plus SHA-256 | Implemented on active PR | +| Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | +| Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | + +The packet does not persist or calculate a rating, decide compensation, infer performance, or prove scientific validity/fairness/compliance. Those claims require their own authoritative evidence and controls. From 7c3aafde48212c3d66233dd9d0f4c58056f0e0bb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:10:12 -0700 Subject: [PATCH 011/101] test: make performance scope resolution explicitly unverified --- packages/performance-review/tests/test_packet.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 19f13e03b..920a31e85 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -63,6 +63,7 @@ def test_builds_value_free_human_review_packet() -> None: assert packet.human_confirmation_required is True assert packet.decision_authority == "human_review_only" assert packet.review_state == "requires_human_review" + assert packet.scope_verification_state == "requires_authoritative_resolution" assert "record accountable human rating and feedback" in packet.next_action @@ -71,6 +72,7 @@ def test_canonical_json_and_digest_are_deterministic() -> None: canonical = packet.canonical_json() payload = json.loads(canonical) assert payload["person_record_reference"] == PERSON + assert payload["scope_verification_state"] == "requires_authoritative_resolution" assert payload["generated_at"] == "2026-08-19T05:15:30.123456Z" assert packet.sha256_digest() == sha256(canonical.encode("utf-8")).hexdigest() assert canonical == build_valid().canonical_json() @@ -164,6 +166,7 @@ def test_generated_at_must_be_timezone_aware_datetime(generated_at: object) -> N ("human_confirmation_required", 1, "human confirmation is mandatory"), ("decision_authority", "model_decision", "decision_authority"), ("review_state", "approved", "review_state"), + ("scope_verification_state", "verified", "scope_verification_state"), ("next_action", "Auto-rate the employee.", "next_action"), ], ) From ece093216297199e262c9188340d321a4d5081aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:10:41 -0700 Subject: [PATCH 012/101] fix: keep performance scope resolution fail closed --- .../src/orgmetra_performance_review/packet.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 044611ddf..173e8efbe 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -1,10 +1,12 @@ """Governed, value-free human performance-review evidence. -The packet binds one employee review to authoritative Employment and Job scope, +The packet correlates one proposed employee review to Employment and Job references, a performance cycle, predetermined criteria and goals, an exact criterion-observation -snapshot, an optional development plan, and an accountable human reviewer. The opaque -person reference remains sensitive correlating metadata. Person PII, rating values, -free-form feedback, and free-form model output remain outside this envelope. +snapshot, an optional development plan, and an accountable human reviewer. It does not +assert that those references resolve to one authoritative scope; that verification must +occur at the authoritative HRIS/performance boundary before rating. The opaque person +reference remains sensitive correlating metadata. Person PII, rating values, free-form +feedback, and free-form model output remain outside this envelope. """ from __future__ import annotations @@ -23,6 +25,7 @@ _PURPOSE_CODE = "performance_review" _DECISION_AUTHORITY = "human_review_only" _REVIEW_STATE = "requires_human_review" +_SCOPE_VERIFICATION_STATE = "requires_authoritative_resolution" _NEXT_ACTION = ( "Verify authoritative Employment/Job scope, performance-cycle dates, governed " "criteria and goals, criterion-observation evidence, and any development-plan " @@ -87,7 +90,7 @@ def _validate_business_date(value: date, field_name: str) -> None: @dataclass(frozen=True, slots=True) class PerformanceReviewPacket: - """Immutable value-free performance-review packet awaiting accountable human review.""" + """Immutable value-free performance-review packet awaiting authoritative resolution.""" tenant_record_id: str performance_review_reference: str @@ -115,6 +118,7 @@ class PerformanceReviewPacket: human_confirmation_required: bool = True decision_authority: str = _DECISION_AUTHORITY review_state: str = _REVIEW_STATE + scope_verification_state: str = _SCOPE_VERIFICATION_STATE next_action: str = _NEXT_ACTION def __post_init__(self) -> None: @@ -181,6 +185,10 @@ def __post_init__(self) -> None: raise ValueError("decision_authority must remain human_review_only") if self.review_state != _REVIEW_STATE: raise ValueError("review_state must remain requires_human_review") + if self.scope_verification_state != _SCOPE_VERIFICATION_STATE: + raise ValueError( + "scope_verification_state must remain requires_authoritative_resolution" + ) if self.next_action != _NEXT_ACTION: raise ValueError("next_action must remain the governed performance-review instruction") @@ -213,6 +221,7 @@ def canonical_json(self) -> str: "review_period_start": self.review_period_start.isoformat(), "review_state": self.review_state, "reviewer_reference": self.reviewer_reference, + "scope_verification_state": self.scope_verification_state, "tenant_record_id": self.tenant_record_id, } return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) @@ -245,7 +254,7 @@ def build_performance_review_packet( review_period_end: date, generated_at: datetime, ) -> PerformanceReviewPacket: - """Build value-free performance-review evidence pending accountable human review.""" + """Build value-free performance-review evidence pending authoritative resolution.""" return PerformanceReviewPacket( tenant_record_id=tenant_record_id, performance_review_reference=performance_review_reference, From aa58be743ebe5ede1df9b6009a68838868d96105 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:11:07 -0700 Subject: [PATCH 013/101] docs: make performance scope verification explicit --- packages/performance-review/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index d48d15355..d6af6066d 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -2,14 +2,14 @@ `orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying person PII, rating values, free-form feedback, or model output into the governance envelope. -The packet binds one opaque person and Employment reference to the authoritative Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque UUID-backed reference and, where integrity matters, an independent SHA-256 digest. +The packet correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque UUID-backed reference and, where integrity matters, an independent SHA-256 digest. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. ## What this packet does not do -It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, or execute a development action. It does not replace the authoritative performance/criterion persistence boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, or that a human review actually occurred. +It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. ## Required review state -Every packet remains `requires_human_review`, with `decision_authority="human_review_only"` and `human_confirmation_required=True`. The fixed next action tells the reviewer to verify Employment/Job scope, review-period and performance-cycle alignment, governed criteria and goals, criterion-observation evidence, and any development-plan provenance before recording accountable human rating and feedback through the authoritative performance workflow. +Every packet remains `requires_human_review`, with `decision_authority="human_review_only"`, `human_confirmation_required=True`, and `scope_verification_state="requires_authoritative_resolution"`. The fixed next action tells the reviewer to verify authoritative Employment/Job scope, review-period and performance-cycle alignment, governed criteria and goals, criterion-observation evidence, and any development-plan provenance before recording accountable human rating and feedback through the authoritative performance workflow. From 20e7c3422e70c8b8e311f809809892354dad0c96 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:11:24 -0700 Subject: [PATCH 014/101] docs: fail closed on unresolved performance scope --- docs/adr/0018-governed-performance-review.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index cb30c31fc..43648bbe2 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -5,7 +5,9 @@ ## Context -Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that proves which employment scope, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. +Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that identifies which employment references, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. + +A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. U.S. OPM performance-management guidance treats performance management as a continuous cycle of planning, monitoring, developing, rating, and rewarding, and describes rating as evaluation against established elements and standards. ISO 30414:2025 Edition 2 provides current human-capital reporting requirements and recommendations across areas including productivity, skills/capabilities, and related workforce governance. Orgmetra uses those sources as design evidence, not as a claim that this packet by itself satisfies any jurisdiction-specific appraisal rule or ISO certification requirement. @@ -24,15 +26,17 @@ The packet MUST bind: - explicit business review-period dates; - one accountable reviewer, fixed `performance_review` purpose, bounded reason code, and precision-preserving evidence timestamp. -The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, and `review_state="requires_human_review"` remain intact. +The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. + +`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. -Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, the substantive validity or fairness of a criterion, lawful use, human completion, or the final rating. +Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, or the final rating. ## Consequences -Buyers can present a review-ready evidence envelope that keeps authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. +Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. -This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. Later authoritative rating persistence must independently preserve actor, purpose, reason, evidence version, human confirmation, audit/outbox, temporal scope, and any applicable policy requirements. +This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. Later authoritative rating persistence must independently preserve actor, purpose, reason, evidence version, human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. ## References From 03bccab853eb0638dbf5bf6d793368a58285bf37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 14:11:33 -0700 Subject: [PATCH 015/101] docs: trace authoritative performance scope resolution --- docs/traceability/performance-review.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index dacfe44eb..44bc4eea9 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -4,7 +4,8 @@ Status: **active PR / proposed capability**, not protected-main truth. | Requirement | Evidence | Status | |---|---|---| -| Bind review to authoritative Employment and Job scope | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference` | Implemented on active PR | +| Correlate review with Employment and Job references without claiming relationship resolution | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference`, fixed `scope_verification_state=requires_authoritative_resolution` | Implemented on active PR | +| Require authoritative Person↔Employment↔Job/cycle/evidence resolution before rating | immutable scope-verification state plus governed `next_action` | Enforced as downstream prerequisite on active PR | | Bind exact performance-cycle and business review period | `performance_cycle_reference`, `review_period_start`, `review_period_end` | Implemented on active PR | | Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | | Bind exact outcome evidence without copying values | `criterion_observation_snapshot_reference`/digest | Implemented on active PR | @@ -15,4 +16,4 @@ Status: **active PR / proposed capability**, not protected-main truth. | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | -The packet does not persist or calculate a rating, decide compensation, infer performance, or prove scientific validity/fairness/compliance. Those claims require their own authoritative evidence and controls. +The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. Those claims require their own authoritative evidence and controls. From 780a65844a3dd2bfd94cfb8d619a3ca99461f5ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 15:00:51 -0700 Subject: [PATCH 016/101] fix: preserve fixed performance purpose invariant --- .../performance-review/src/orgmetra_performance_review/packet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 173e8efbe..110ea90c9 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -164,7 +164,6 @@ def __post_init__(self) -> None: ) _validate_digest(self.development_plan_digest, "development_plan_digest") _validate_reference(self.reviewer_reference, "actor", "reviewer_reference") - _validate_code(self.purpose_code, "purpose_code") if self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain performance_review") _validate_code(self.reason_code, "reason_code") From 467c55bdb5d4f81ca5a72db32df0b5de6f0363d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 20:22:19 -0700 Subject: [PATCH 017/101] test: require redacted performance review repr --- .../tests/test_repr_privacy.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 packages/performance-review/tests/test_repr_privacy.py diff --git a/packages/performance-review/tests/test_repr_privacy.py b/packages/performance-review/tests/test_repr_privacy.py new file mode 100644 index 000000000..df6860416 --- /dev/null +++ b/packages/performance-review/tests/test_repr_privacy.py @@ -0,0 +1,36 @@ +from datetime import date, datetime, timezone + +from orgmetra_performance_review import build_performance_review_packet + + +def test_repr_redacts_worker_rating_scope_and_evidence() -> None: + packet = build_performance_review_packet( + tenant_record_id="11111111-1111-4111-8111-111111111111", + performance_review_reference="performance_review:22222222-2222-4222-8222-222222222222", + person_record_reference="person_record:33333333-3333-4333-8333-333333333333", + employment_record_reference="employment_record:44444444-4444-4444-8444-444444444444", + job_profile_reference="job_profile:55555555-5555-4555-8555-555555555555", + performance_cycle_reference="performance_cycle:66666666-6666-4666-8666-666666666666", + criterion_set_reference="criterion_set:77777777-7777-4777-8777-777777777777", + criterion_set_digest="a" * 64, + goal_plan_reference="performance_goal_plan:88888888-8888-4888-8888-888888888888", + goal_plan_digest="b" * 64, + criterion_observation_snapshot_reference="criterion_observation_snapshot:99999999-9999-4999-8999-999999999999", + criterion_observation_snapshot_digest="c" * 64, + development_plan_reference="development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + development_plan_digest="d" * 64, + reviewer_reference="actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + purpose_code="performance_review", + reason_code="scheduled_cycle_review", + review_period_start=date(2026, 1, 1), + review_period_end=date(2026, 6, 30), + generated_at=datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), + ) + + rendered = repr(packet) + assert rendered == "PerformanceReviewPacket()" + assert packet.tenant_record_id not in rendered + assert packet.person_record_reference not in rendered + assert packet.employment_record_reference not in rendered + assert packet.reviewer_reference not in rendered + assert packet.criterion_observation_snapshot_digest not in rendered From a956a4b642566f399fe5ae0b6c23fab421e6d4ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 20:23:06 -0700 Subject: [PATCH 018/101] fix: redact performance review evidence repr --- .../src/orgmetra_performance_review/packet.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 110ea90c9..2f3adcd81 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -88,7 +88,7 @@ def _validate_business_date(value: date, field_name: str) -> None: raise ValueError(f"{field_name} must be a date") -@dataclass(frozen=True, slots=True) +@dataclass(frozen=True, slots=True, repr=False) class PerformanceReviewPacket: """Immutable value-free performance-review packet awaiting authoritative resolution.""" @@ -121,6 +121,10 @@ class PerformanceReviewPacket: scope_verification_state: str = _SCOPE_VERIFICATION_STATE next_action: str = _NEXT_ACTION + def __repr__(self) -> str: + """Return a representation that never emits worker/rating correlation evidence.""" + return "PerformanceReviewPacket()" + def __post_init__(self) -> None: """Fail closed when direct construction drifts from the governed contract.""" _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") From b3350d5d3ee4794678d4811f76ef26e136b8ecb4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 23:21:25 -0700 Subject: [PATCH 019/101] test: reject ungoverned performance review reasons --- packages/performance-review/tests/test_packet.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 920a31e85..2bf5021ec 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -142,6 +142,15 @@ def test_reason_code_is_bounded_descriptive_snake_case(reason: str) -> None: build_valid(reason_code=reason) +@pytest.mark.parametrize( + "reason", + ["employee_jane_doe", "ssn_123_45_6789", "manager_override"], +) +def test_reason_code_rejects_ungoverned_free_form_values(reason: str) -> None: + with pytest.raises(ValueError, match="reason_code must be an authorized"): + build_valid(reason_code=reason) + + def test_review_period_must_be_real_dates_in_order() -> None: with pytest.raises(ValueError, match="review_period_start"): build_valid(review_period_start="2026-01-01") From 3a4cba5c592c26f99d412f791ffd136cd67b0b5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 23:23:21 -0700 Subject: [PATCH 020/101] fix: close performance review reason vocabulary --- docs/adr/0018-governed-performance-review.md | 4 +++- packages/performance-review/CHANGELOG.md | 3 ++- packages/performance-review/README.md | 2 +- .../src/orgmetra_performance_review/packet.py | 16 ++++++++-------- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index 43648bbe2..91539781c 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -24,7 +24,9 @@ The packet MUST bind: - an exact criterion-observation-snapshot reference plus independent SHA-256 digest; - an optional development-plan reference/digest pair; - explicit business review-period dates; -- one accountable reviewer, fixed `performance_review` purpose, bounded reason code, and precision-preserving evidence timestamp. +- one accountable reviewer, fixed `performance_review` purpose, a reviewed closed reason code, and precision-preserving evidence timestamp. + +The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arbitrary lower-snake-case values are rejected even when syntactically well formed, because free-form reason text can encode a person name, identifier, or unreviewed decision context. Additional reasons require an explicit governed contract change and regression evidence before they can enter canonical review evidence. The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 2ab02f229..17f66ccbe 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -2,4 +2,5 @@ ## Unreleased -- Add a PII-minimized, human-review-only performance-review evidence packet binding authoritative Employment/Job scope, performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and accountable reviewer. +- Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. +- Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index d6af6066d..0352e515e 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -4,7 +4,7 @@ The packet correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque UUID-backed reference and, where integrity matters, an independent SHA-256 digest. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. -The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. +The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. ## What this packet does not do diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 2f3adcd81..94dd61867 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -17,12 +17,12 @@ import re from uuid import UUID -_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") _REFERENCE_PATTERN = re.compile( r"^[a-z][a-z0-9_]{1,31}:[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$" ) _PURPOSE_CODE = "performance_review" +_ALLOWED_REASON_CODES = frozenset({"scheduled_cycle_review"}) _DECISION_AUTHORITY = "human_review_only" _REVIEW_STATE = "requires_human_review" _SCOPE_VERIFICATION_STATE = "requires_authoritative_resolution" @@ -44,12 +44,6 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: raise ValueError(f"{field_name} must be a canonical operational UUID") -def _validate_code(value: str, field_name: str) -> None: - """Require a bounded descriptive lower snake_case governance code.""" - if not isinstance(value, str) or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): - raise ValueError(f"{field_name} must be bounded two-or-more-word lower snake_case") - - def _validate_reference(value: str, prefix: str, field_name: str) -> None: """Require an expected namespace plus a canonical operational UUID suffix.""" error_message = f"{field_name} must be an opaque {prefix}: reference" @@ -88,6 +82,12 @@ def _validate_business_date(value: date, field_name: str) -> None: raise ValueError(f"{field_name} must be a date") +def _validate_reason_code(value: str) -> None: + """Require a closed, reviewed reason code so free-form PII cannot enter evidence.""" + if not isinstance(value, str) or value not in _ALLOWED_REASON_CODES: + raise ValueError("reason_code must be an authorized performance-review reason code") + + @dataclass(frozen=True, slots=True, repr=False) class PerformanceReviewPacket: """Immutable value-free performance-review packet awaiting authoritative resolution.""" @@ -170,7 +170,7 @@ def __post_init__(self) -> None: _validate_reference(self.reviewer_reference, "actor", "reviewer_reference") if self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain performance_review") - _validate_code(self.reason_code, "reason_code") + _validate_reason_code(self.reason_code) _validate_business_date(self.review_period_start, "review_period_start") _validate_business_date(self.review_period_end, "review_period_end") if self.review_period_start > self.review_period_end: From 34a85e8372ff9e0a1162dc7dfa3faa71c3e4609f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:34:43 -0700 Subject: [PATCH 021/101] test: require performance review evidence versioning --- .../performance-review/tests/test_packet.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 2bf5021ec..170c7e69b 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -78,6 +78,25 @@ def test_canonical_json_and_digest_are_deterministic() -> None: assert canonical == build_valid().canonical_json() +def test_evidence_version_is_bound_to_canonical_evidence() -> None: + """Require review evidence versioning to change the immutable correlation digest.""" + first = build_valid() + assert first.evidence_version == 1 + assert json.loads(first.canonical_json())["evidence_version"] == 1 + + second = build_valid(evidence_version=2) + assert second.evidence_version == 2 + assert second.canonical_json() != first.canonical_json() + assert second.sha256_digest() != first.sha256_digest() + + +@pytest.mark.parametrize("evidence_version", [0, -1, True, "1", 2_147_483_648]) +def test_rejects_invalid_evidence_version(evidence_version: object) -> None: + """Reject unbounded or non-integer performance-review evidence versions.""" + with pytest.raises(ValueError, match="evidence_version"): + build_valid(evidence_version=evidence_version) + + def test_timestamp_normalizes_to_utc_without_losing_precision() -> None: shifted = GENERATED_AT.astimezone(timezone(timedelta(hours=9))) assert build_valid(generated_at=shifted).canonical_json() == build_valid().canonical_json() From f07779f2e334176976b5fa08f52bdf7a3748d037 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:35:34 -0700 Subject: [PATCH 022/101] fix: bind performance review evidence version --- .../src/orgmetra_performance_review/packet.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 94dd61867..e123ebca5 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -88,6 +88,12 @@ def _validate_reason_code(value: str) -> None: raise ValueError("reason_code must be an authorized performance-review reason code") +def _validate_evidence_version(value: int) -> None: + """Require a bounded positive integer version for high-impact review evidence.""" + if type(value) is not int or value < 1 or value > 2_147_483_647: + raise ValueError("evidence_version must be an integer from 1 through 2147483647") + + @dataclass(frozen=True, slots=True, repr=False) class PerformanceReviewPacket: """Immutable value-free performance-review packet awaiting authoritative resolution.""" @@ -112,6 +118,7 @@ class PerformanceReviewPacket: review_period_start: date review_period_end: date generated_at: datetime + evidence_version: int = 1 contains_person_pii: bool = False contains_rating_value: bool = False contains_free_form_model_output: bool = False @@ -176,6 +183,7 @@ def __post_init__(self) -> None: if self.review_period_start > self.review_period_end: raise ValueError("review period start must not be after review period end") _canonical_timestamp(self.generated_at) + _validate_evidence_version(self.evidence_version) if self.contains_person_pii is not False: raise ValueError("performance review packet must not contain person PII") if self.contains_rating_value is not False: @@ -209,6 +217,7 @@ def canonical_json(self) -> str: "development_plan_digest": self.development_plan_digest, "development_plan_reference": self.development_plan_reference, "employment_record_reference": self.employment_record_reference, + "evidence_version": self.evidence_version, "generated_at": _canonical_timestamp(self.generated_at), "goal_plan_digest": self.goal_plan_digest, "goal_plan_reference": self.goal_plan_reference, @@ -256,6 +265,7 @@ def build_performance_review_packet( review_period_start: date, review_period_end: date, generated_at: datetime, + evidence_version: int = 1, ) -> PerformanceReviewPacket: """Build value-free performance-review evidence pending authoritative resolution.""" return PerformanceReviewPacket( @@ -279,4 +289,5 @@ def build_performance_review_packet( review_period_start=review_period_start, review_period_end=review_period_end, generated_at=generated_at, + evidence_version=evidence_version, ) From 5da89264064fc8f71bdd830bc59f6266a998dd47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:36:05 -0700 Subject: [PATCH 023/101] docs: document performance evidence versioning --- packages/performance-review/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 0352e515e..2b09d9c93 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -6,6 +6,8 @@ The packet correlates one opaque Person and Employment reference with a Job, per The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. +Every packet also carries a bounded positive integer `evidence_version` (default `1`). The version is part of canonical JSON and therefore changes the SHA-256 correlation digest when the reviewed evidence contract/version changes. Zero, negative, boolean, textual, and values above `2147483647` fail closed. The version identifies the review evidence envelope; it is not a rating, approval, or substitute for authoritative source-version verification. + ## What this packet does not do It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. From 8206de7963b77aa8967ae3a4dfefe3913957f2de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:36:23 -0700 Subject: [PATCH 024/101] docs: bind evidence version in performance ADR --- docs/adr/0018-governed-performance-review.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index 91539781c..7263970e6 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -24,10 +24,13 @@ The packet MUST bind: - an exact criterion-observation-snapshot reference plus independent SHA-256 digest; - an optional development-plan reference/digest pair; - explicit business review-period dates; -- one accountable reviewer, fixed `performance_review` purpose, a reviewed closed reason code, and precision-preserving evidence timestamp. +- one accountable reviewer, fixed `performance_review` purpose, a reviewed closed reason code, and precision-preserving evidence timestamp; +- a bounded positive integer `evidence_version`, defaulting to `1`, that is included in canonical evidence and therefore changes the packet digest when the governed evidence version changes. The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arbitrary lower-snake-case values are rejected even when syntactically well formed, because free-form reason text can encode a person name, identifier, or unreviewed decision context. Additional reasons require an explicit governed contract change and regression evidence before they can enter canonical review evidence. +`evidence_version` accepts only real integers from `1` through `2147483647`; booleans, text, zero, negative values, and overflow values fail closed. The field versions the immutable review evidence envelope and does not itself prove source-version resolution, human approval, or rating completion. + The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. `scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. @@ -38,7 +41,7 @@ Canonical JSON and SHA-256 are immutable correlation evidence only. They do not Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. -This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. Later authoritative rating persistence must independently preserve actor, purpose, reason, evidence version, human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. +This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet now preserves actor, purpose, reviewed reason, and evidence version in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. ## References From d63449b1c724d130c77fe8ba0b61f6f1d6bb2723 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:36:33 -0700 Subject: [PATCH 025/101] docs: trace performance evidence version --- docs/traceability/performance-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index 44bc4eea9..36d1e18bc 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -12,6 +12,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Preserve optional development provenance | paired `development_plan_reference`/digest | Implemented on active PR | | Keep person PII, rating values, free-form feedback/model output outside packet | immutable false flags plus absence of value-bearing fields | Implemented on active PR | | Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | +| Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | | Preserve deterministic immutable correlation evidence | canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | From 556d4a9ef44d3d31a35c57221a77cc956fb5e949 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:36:41 -0700 Subject: [PATCH 026/101] docs: record performance evidence versioning --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 17f66ccbe..684360d8d 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -4,3 +4,4 @@ - Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. +- Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. From 4b639725ec1cd39b965798f616a3f78148534c48 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 03:53:59 -0700 Subject: [PATCH 027/101] test: classify opaque worker references as personal data --- packages/performance-review/tests/test_packet.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 170c7e69b..802e540bb 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -55,9 +55,10 @@ def build_valid(**overrides: object) -> PerformanceReviewPacket: return build_performance_review_packet(**values) -def test_builds_value_free_human_review_packet() -> None: +def test_builds_value_minimized_human_review_packet() -> None: packet = build_valid() - assert packet.contains_person_pii is False + assert packet.contains_personal_data is True + assert packet.contains_direct_person_identifiers is False assert packet.contains_rating_value is False assert packet.contains_free_form_model_output is False assert packet.human_confirmation_required is True @@ -72,6 +73,8 @@ def test_canonical_json_and_digest_are_deterministic() -> None: canonical = packet.canonical_json() payload = json.loads(canonical) assert payload["person_record_reference"] == PERSON + assert payload["contains_personal_data"] is True + assert payload["contains_direct_person_identifiers"] is False assert payload["scope_verification_state"] == "requires_authoritative_resolution" assert payload["generated_at"] == "2026-08-19T05:15:30.123456Z" assert packet.sha256_digest() == sha256(canonical.encode("utf-8")).hexdigest() @@ -188,7 +191,8 @@ def test_generated_at_must_be_timezone_aware_datetime(generated_at: object) -> N @pytest.mark.parametrize( ("field", "value", "message"), [ - ("contains_person_pii", True, "must not contain person PII"), + ("contains_personal_data", False, "personal data"), + ("contains_direct_person_identifiers", True, "direct person identifiers"), ("contains_rating_value", True, "must not contain rating values"), ("contains_free_form_model_output", True, "must not contain free-form model output"), ("human_confirmation_required", 1, "human confirmation is mandatory"), From 0774a7ffe92611a3afed330701cd790f77f062d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 03:54:53 -0700 Subject: [PATCH 028/101] fix: classify worker correlations as personal data --- .../src/orgmetra_performance_review/packet.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index e123ebca5..ab316fbe5 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -1,12 +1,13 @@ -"""Governed, value-free human performance-review evidence. +"""Governed, value-minimized human performance-review evidence. The packet correlates one proposed employee review to Employment and Job references, a performance cycle, predetermined criteria and goals, an exact criterion-observation snapshot, an optional development plan, and an accountable human reviewer. It does not assert that those references resolve to one authoritative scope; that verification must -occur at the authoritative HRIS/performance boundary before rating. The opaque person -reference remains sensitive correlating metadata. Person PII, rating values, free-form -feedback, and free-form model output remain outside this envelope. +occur at the authoritative HRIS/performance boundary before rating. Opaque worker +references remain personal data because they can be re-associated with an identifiable +person through the authoritative HRIS boundary. Direct identifiers, rating values, +free-form feedback, and free-form model output remain outside this envelope. """ from __future__ import annotations @@ -96,7 +97,7 @@ def _validate_evidence_version(value: int) -> None: @dataclass(frozen=True, slots=True, repr=False) class PerformanceReviewPacket: - """Immutable value-free performance-review packet awaiting authoritative resolution.""" + """Immutable value-minimized review packet awaiting authoritative resolution.""" tenant_record_id: str performance_review_reference: str @@ -119,7 +120,8 @@ class PerformanceReviewPacket: review_period_end: date generated_at: datetime evidence_version: int = 1 - contains_person_pii: bool = False + contains_personal_data: bool = True + contains_direct_person_identifiers: bool = False contains_rating_value: bool = False contains_free_form_model_output: bool = False human_confirmation_required: bool = True @@ -184,8 +186,10 @@ def __post_init__(self) -> None: raise ValueError("review period start must not be after review period end") _canonical_timestamp(self.generated_at) _validate_evidence_version(self.evidence_version) - if self.contains_person_pii is not False: - raise ValueError("performance review packet must not contain person PII") + if self.contains_personal_data is not True: + raise ValueError("performance review packet contains personal data through worker references") + if self.contains_direct_person_identifiers is not False: + raise ValueError("performance review packet must not contain direct person identifiers") if self.contains_rating_value is not False: raise ValueError("performance review packet must not contain rating values") if self.contains_free_form_model_output is not False: @@ -206,8 +210,9 @@ def __post_init__(self) -> None: def canonical_json(self) -> str: """Return deterministic canonical JSON for immutable audit correlation.""" payload = { + "contains_direct_person_identifiers": self.contains_direct_person_identifiers, "contains_free_form_model_output": self.contains_free_form_model_output, - "contains_person_pii": self.contains_person_pii, + "contains_personal_data": self.contains_personal_data, "contains_rating_value": self.contains_rating_value, "criterion_observation_snapshot_digest": self.criterion_observation_snapshot_digest, "criterion_observation_snapshot_reference": self.criterion_observation_snapshot_reference, @@ -267,7 +272,7 @@ def build_performance_review_packet( generated_at: datetime, evidence_version: int = 1, ) -> PerformanceReviewPacket: - """Build value-free performance-review evidence pending authoritative resolution.""" + """Build value-minimized performance-review evidence pending authoritative resolution.""" return PerformanceReviewPacket( tenant_record_id=tenant_record_id, performance_review_reference=performance_review_reference, From ce88fb9ffb2e08f6580f627e27a3f47e8e33f732 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 19:33:40 -0700 Subject: [PATCH 029/101] test: reject UUIDv1 performance review trust references --- .../performance-review/tests/test_packet.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 802e540bb..4cd33d6bf 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -21,6 +21,7 @@ OBSERVATION_SNAPSHOT = "criterion_observation_snapshot:99999999-9999-4999-8999-999999999999" DEVELOPMENT_PLAN = "development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" REVIEWER = "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +UUID1_ID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" DIGEST_A = "a" * 64 DIGEST_B = "b" * 64 DIGEST_C = "c" * 64 @@ -139,6 +140,33 @@ def test_rejects_nonopaque_or_wrong_namespace_references(field: str, value: str) build_valid(**{field: value}) +@pytest.mark.parametrize( + ("field", "prefix"), + [ + ("performance_review_reference", "performance_review"), + ("person_record_reference", "person_record"), + ("employment_record_reference", "employment_record"), + ("job_profile_reference", "job_profile"), + ("performance_cycle_reference", "performance_cycle"), + ("criterion_set_reference", "criterion_set"), + ("goal_plan_reference", "performance_goal_plan"), + ("criterion_observation_snapshot_reference", "criterion_observation_snapshot"), + ("development_plan_reference", "development_plan"), + ("reviewer_reference", "actor"), + ], +) +def test_rejects_uuid1_trust_references_through_builder_and_replace( + field: str, + prefix: str, +) -> None: + value = f"{prefix}:{UUID1_ID}" + with pytest.raises(ValueError, match=field): + build_valid(**{field: value}) + + with pytest.raises(ValueError, match=field): + replace(build_valid(), **{field: value}) + + @pytest.mark.parametrize("field", ["criterion_set_digest", "goal_plan_digest", "criterion_observation_snapshot_digest", "development_plan_digest"]) def test_rejects_malformed_evidence_digests(field: str) -> None: with pytest.raises(ValueError, match=field): From 75ab918373afaec44830c85f37b5448b9024692c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 19:34:14 -0700 Subject: [PATCH 030/101] fix: require UUIDv4 performance review trust references --- .../src/orgmetra_performance_review/packet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index ab316fbe5..994154470 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -46,7 +46,7 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: def _validate_reference(value: str, prefix: str, field_name: str) -> None: - """Require an expected namespace plus a canonical operational UUID suffix.""" + """Require an expected namespace plus a canonical opaque UUIDv4 suffix.""" error_message = f"{field_name} must be an opaque {prefix}: reference" if ( not isinstance(value, str) @@ -60,7 +60,7 @@ def _validate_reference(value: str, prefix: str, field_name: str) -> None: parsed = UUID(suffix) except (ValueError, AttributeError, TypeError) as exc: raise ValueError(error_message) from exc - if str(parsed) != suffix or parsed.int in (0, (1 << 128) - 1): + if str(parsed) != suffix or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): raise ValueError(error_message) From 963a4267777a9ffbea0ce8e5ef0fc0da86bb895a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 19:34:33 -0700 Subject: [PATCH 031/101] docs: define UUIDv4 performance review reference privacy --- packages/performance-review/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 2b09d9c93..04af97ddc 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -2,7 +2,7 @@ `orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying person PII, rating values, free-form feedback, or model output into the governance envelope. -The packet correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque UUID-backed reference and, where integrity matters, an independent SHA-256 digest. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. +The packet correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque canonical non-sentinel UUIDv4-backed reference and, where integrity matters, an independent SHA-256 digest. UUIDv1 and other UUID versions are rejected so timestamp/node correlation metadata cannot enter an otherwise opaque trust-reference field. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. From b17c47467cd6c0ba4649441e8cc5ed2bcc3f7bb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 19:34:45 -0700 Subject: [PATCH 032/101] docs: record UUIDv4 performance trust-reference decision --- docs/adr/0018-governed-performance-review.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index 7263970e6..bfcfc740b 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -7,7 +7,7 @@ Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that identifies which employment references, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. -A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. +A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. UUID syntax is also part of the privacy boundary: UUIDv1 can expose timestamp/node-derived correlation metadata despite looking opaque, so trust references must not accept arbitrary UUID versions. U.S. OPM performance-management guidance treats performance management as a continuous cycle of planning, monitoring, developing, rating, and rewarding, and describes rating as evaluation against established elements and standards. ISO 30414:2025 Edition 2 provides current human-capital reporting requirements and recommendations across areas including productivity, skills/capabilities, and related workforce governance. Orgmetra uses those sources as design evidence, not as a claim that this packet by itself satisfies any jurisdiction-specific appraisal rule or ISO certification requirement. @@ -18,13 +18,13 @@ Introduce a transport-neutral `PerformanceReviewPacket` that remains pre-rating, The packet MUST bind: - canonical tenant identity; -- opaque UUID-backed Person, Employment, Job, performance-cycle and performance-review references; -- a governed criterion-set reference plus independent SHA-256 digest; -- a governed performance-goal-plan reference plus independent SHA-256 digest; -- an exact criterion-observation-snapshot reference plus independent SHA-256 digest; -- an optional development-plan reference/digest pair; +- opaque canonical non-sentinel UUIDv4-backed Person, Employment, Job, performance-cycle and performance-review references, rejecting UUIDv1 and every other UUID version; +- a governed criterion-set UUIDv4 reference plus independent SHA-256 digest; +- a governed performance-goal-plan UUIDv4 reference plus independent SHA-256 digest; +- an exact criterion-observation-snapshot UUIDv4 reference plus independent SHA-256 digest; +- an optional development-plan UUIDv4 reference/digest pair; - explicit business review-period dates; -- one accountable reviewer, fixed `performance_review` purpose, a reviewed closed reason code, and precision-preserving evidence timestamp; +- one accountable UUIDv4-backed reviewer, fixed `performance_review` purpose, a reviewed closed reason code, and precision-preserving evidence timestamp; - a bounded positive integer `evidence_version`, defaulting to `1`, that is included in canonical evidence and therefore changes the packet digest when the governed evidence version changes. The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arbitrary lower-snake-case values are rejected even when syntactically well formed, because free-form reason text can encode a person name, identifier, or unreviewed decision context. Additional reasons require an explicit governed contract change and regression evidence before they can enter canonical review evidence. @@ -39,7 +39,7 @@ Canonical JSON and SHA-256 are immutable correlation evidence only. They do not ## Consequences -Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. +Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. Requiring UUIDv4 for namespaced trust references also closes UUIDv1 timestamp/node correlation leakage, while authoritative resolution remains mandatory because UUIDv4 syntax does not establish tenant or business scope. This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet now preserves actor, purpose, reviewed reason, and evidence version in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. From 6fd2377e267b6f0502daf321cc97f686d38abe89 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 19:34:51 -0700 Subject: [PATCH 033/101] docs: record performance reference privacy hardening --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 684360d8d..06afeb469 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -3,5 +3,6 @@ ## Unreleased - Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. +- Require canonical non-sentinel UUIDv4 suffixes for every namespaced trust reference so UUIDv1 timestamp/node correlation metadata and other UUID versions cannot enter the evidence envelope. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. From a59faf010d179b00b8fdc113ab896f629f312214 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 19:35:01 -0700 Subject: [PATCH 034/101] docs: trace UUIDv4 performance reference regression --- docs/traceability/performance-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index 36d1e18bc..26bccb2d2 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -5,6 +5,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Requirement | Evidence | Status | |---|---|---| | Correlate review with Employment and Job references without claiming relationship resolution | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference`, fixed `scope_verification_state=requires_authoritative_resolution` | Implemented on active PR | +| Keep trust references opaque and non-correlating by UUID version | `test_rejects_uuid1_trust_references_through_builder_and_replace` | Every namespaced packet reference requires a canonical non-sentinel UUIDv4 suffix; UUIDv1 timestamp/node correlation and other UUID versions fail closed through builder and replacement paths. | | Require authoritative Person↔Employment↔Job/cycle/evidence resolution before rating | immutable scope-verification state plus governed `next_action` | Enforced as downstream prerequisite on active PR | | Bind exact performance-cycle and business review period | `performance_cycle_reference`, `review_period_start`, `review_period_end` | Implemented on active PR | | Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | From 17024a3a477d4a5884bc399c9dbe4d55e9871ce7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:51:05 -0700 Subject: [PATCH 035/101] test: reject correlating tenant UUIDv1 in performance review --- .../tests/test_tenant_identity_privacy.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/performance-review/tests/test_tenant_identity_privacy.py diff --git a/packages/performance-review/tests/test_tenant_identity_privacy.py b/packages/performance-review/tests/test_tenant_identity_privacy.py new file mode 100644 index 000000000..379c257d6 --- /dev/null +++ b/packages/performance-review/tests/test_tenant_identity_privacy.py @@ -0,0 +1,51 @@ +"""Privacy regression for the public tenant identity in performance review.""" +from dataclasses import replace +from datetime import date, datetime, timezone + +import pytest + +from orgmetra_performance_review import build_performance_review_packet + +UUID1_ID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + +def _build(): + """Build one valid value-minimized performance-review packet.""" + return build_performance_review_packet( + tenant_record_id="11111111-1111-4111-8111-111111111111", + performance_review_reference="performance_review:22222222-2222-4222-8222-222222222222", + person_record_reference="person_record:33333333-3333-4333-8333-333333333333", + employment_record_reference="employment_record:44444444-4444-4444-8444-444444444444", + job_profile_reference="job_profile:55555555-5555-4555-8555-555555555555", + performance_cycle_reference="performance_cycle:66666666-6666-4666-8666-666666666666", + criterion_set_reference="criterion_set:77777777-7777-4777-8777-777777777777", + criterion_set_digest="a" * 64, + goal_plan_reference="performance_goal_plan:88888888-8888-4888-8888-888888888888", + goal_plan_digest="b" * 64, + criterion_observation_snapshot_reference="criterion_observation_snapshot:99999999-9999-4999-8999-999999999999", + criterion_observation_snapshot_digest="c" * 64, + development_plan_reference="development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + development_plan_digest="d" * 64, + reviewer_reference="actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + purpose_code="performance_review", + reason_code="scheduled_cycle_review", + review_period_start=date(2026, 1, 1), + review_period_end=date(2026, 6, 30), + generated_at=datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), + ) + + +def test_uuid1_tenant_identity_is_rejected_by_builder_and_replace() -> None: + """UUIDv1 timestamp/node metadata must not enter the public tenant identity.""" + packet = _build() + with pytest.raises(ValueError, match="tenant_record_id"): + replace(packet, tenant_record_id=UUID1_ID) + + kwargs = { + field: getattr(packet, field) + for field in packet.__dataclass_fields__ + if field not in {"contains_personal_data", "contains_direct_person_identifiers", "contains_rating_value", "contains_free_form_model_output", "human_confirmation_required", "decision_authority", "review_state", "scope_verification_state", "next_action"} + } + kwargs["tenant_record_id"] = UUID1_ID + with pytest.raises(ValueError, match="tenant_record_id"): + build_performance_review_packet(**kwargs) From 468839218d8545cf9de070f9073491122f2289f7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 01:19:01 -0700 Subject: [PATCH 036/101] fix: require opaque UUIDv4 tenant identity --- .../src/orgmetra_performance_review/packet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 994154470..c9215ac8a 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -36,13 +36,13 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: - """Require canonical non-sentinel UUID text for a governance identity.""" + """Require canonical UUIDv4 text so a public governance identity stays opaque.""" try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as exc: raise ValueError(f"{field_name} must be canonical UUID text") from exc - if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): - raise ValueError(f"{field_name} must be a canonical operational UUID") + if str(parsed) != value or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUIDv4") def _validate_reference(value: str, prefix: str, field_name: str) -> None: From cf6a91fa785b820fdc70f210fc9cc00c4e86701e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 01:19:41 -0700 Subject: [PATCH 037/101] docs: bind performance tenant identity to UUIDv4 --- packages/performance-review/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 04af97ddc..32ad7ef92 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -2,7 +2,7 @@ `orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying person PII, rating values, free-form feedback, or model output into the governance envelope. -The packet correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every evidence artifact is represented by an opaque canonical non-sentinel UUIDv4-backed reference and, where integrity matters, an independent SHA-256 digest. UUIDv1 and other UUID versions are rejected so timestamp/node correlation metadata cannot enter an otherwise opaque trust-reference field. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. +The packet binds one canonical non-sentinel UUIDv4 `tenant_record_id` and correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every trust-bearing identity is represented by canonical non-sentinel UUIDv4 text or a UUIDv4-backed namespaced reference and, where integrity matters, an independent SHA-256 digest. UUIDv1 and other UUID versions are rejected for the public tenant identity and trust references so timestamp/node correlation metadata cannot enter values presented as opaque governance identities. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. @@ -10,7 +10,7 @@ Every packet also carries a bounded positive integer `evidence_version` (default ## What this packet does not do -It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. +It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. UUIDv4 syntax constrains identifier opacity only; it does not prove tenant ownership, worker relationship, authorization, or temporal scope. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. ## Required review state From d2f7d2a84d39f070a3966d1859cd8b2a4086fda2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 01:19:48 -0700 Subject: [PATCH 038/101] docs: record performance tenant UUIDv4 privacy repair --- packages/performance-review/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 06afeb469..066be5035 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -3,6 +3,6 @@ ## Unreleased - Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. -- Require canonical non-sentinel UUIDv4 suffixes for every namespaced trust reference so UUIDv1 timestamp/node correlation metadata and other UUID versions cannot enter the evidence envelope. +- Require canonical non-sentinel UUIDv4 identity for `tenant_record_id` and every namespaced trust reference so UUIDv1 timestamp/node correlation metadata and other UUID versions cannot enter values presented as opaque governance identities. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. From 83036d9d6c7fb219137df925b5ab9bbd1c37a349 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 01:19:58 -0700 Subject: [PATCH 039/101] docs: trace performance tenant UUIDv4 regression --- docs/traceability/performance-review.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index 26bccb2d2..aced9f521 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -5,7 +5,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Requirement | Evidence | Status | |---|---|---| | Correlate review with Employment and Job references without claiming relationship resolution | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference`, fixed `scope_verification_state=requires_authoritative_resolution` | Implemented on active PR | -| Keep trust references opaque and non-correlating by UUID version | `test_rejects_uuid1_trust_references_through_builder_and_replace` | Every namespaced packet reference requires a canonical non-sentinel UUIDv4 suffix; UUIDv1 timestamp/node correlation and other UUID versions fail closed through builder and replacement paths. | +| Keep tenant and trust references opaque and non-correlating by UUID version | `test_uuid1_tenant_identity_is_rejected_by_builder_and_replace`; `test_rejects_uuid1_trust_references_through_builder_and_replace` | `tenant_record_id` and every namespaced packet reference require canonical non-sentinel UUIDv4 identity; UUIDv1 timestamp/node correlation and other UUID versions fail closed through construction/replacement paths. | | Require authoritative Person↔Employment↔Job/cycle/evidence resolution before rating | immutable scope-verification state plus governed `next_action` | Enforced as downstream prerequisite on active PR | | Bind exact performance-cycle and business review period | `performance_cycle_reference`, `review_period_start`, `review_period_end` | Implemented on active PR | | Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | @@ -18,4 +18,4 @@ Status: **active PR / proposed capability**, not protected-main truth. | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | -The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. Those claims require their own authoritative evidence and controls. +The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. UUIDv4 is an identifier-opacity constraint only and does not establish tenant ownership, authorization, or worker relationship truth. Those claims require their own authoritative evidence and controls. From 752b4d0a5a09ede2c601fac899065d373bd288ec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 01:20:15 -0700 Subject: [PATCH 040/101] docs: make performance tenant UUIDv4 part of privacy decision --- docs/adr/0018-governed-performance-review.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index bfcfc740b..36e82f811 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -7,7 +7,7 @@ Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that identifies which employment references, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. -A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. UUID syntax is also part of the privacy boundary: UUIDv1 can expose timestamp/node-derived correlation metadata despite looking opaque, so trust references must not accept arbitrary UUID versions. +A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. UUID syntax is also part of the privacy boundary: UUIDv1 can expose timestamp/node-derived correlation metadata despite looking opaque, so neither the public tenant identity nor trust references may accept arbitrary UUID versions. U.S. OPM performance-management guidance treats performance management as a continuous cycle of planning, monitoring, developing, rating, and rewarding, and describes rating as evaluation against established elements and standards. ISO 30414:2025 Edition 2 provides current human-capital reporting requirements and recommendations across areas including productivity, skills/capabilities, and related workforce governance. Orgmetra uses those sources as design evidence, not as a claim that this packet by itself satisfies any jurisdiction-specific appraisal rule or ISO certification requirement. @@ -17,7 +17,7 @@ Introduce a transport-neutral `PerformanceReviewPacket` that remains pre-rating, The packet MUST bind: -- canonical tenant identity; +- a canonical non-sentinel UUIDv4 tenant identity, rejecting UUIDv1 and every other UUID version; - opaque canonical non-sentinel UUIDv4-backed Person, Employment, Job, performance-cycle and performance-review references, rejecting UUIDv1 and every other UUID version; - a governed criterion-set UUIDv4 reference plus independent SHA-256 digest; - a governed performance-goal-plan UUIDv4 reference plus independent SHA-256 digest; @@ -33,13 +33,13 @@ The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arb The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. -`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. +`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. UUIDv4 syntax is only an opacity constraint and does not prove tenant ownership, worker scope, authorization, or temporal validity. Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, or the final rating. ## Consequences -Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. Requiring UUIDv4 for namespaced trust references also closes UUIDv1 timestamp/node correlation leakage, while authoritative resolution remains mandatory because UUIDv4 syntax does not establish tenant or business scope. +Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. Requiring UUIDv4 for both tenant identity and namespaced trust references closes UUIDv1 timestamp/node correlation leakage, while authoritative resolution remains mandatory because UUIDv4 syntax does not establish tenant or business scope. This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet now preserves actor, purpose, reviewed reason, and evidence version in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. From 26146eff41fb317ebf7f3ea909ac3d34ec94f676 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:25:34 -0700 Subject: [PATCH 041/101] test: require performance review to accept core tenant UUIDv7 --- .../tests/test_tenant_identity_privacy.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/performance-review/tests/test_tenant_identity_privacy.py b/packages/performance-review/tests/test_tenant_identity_privacy.py index 379c257d6..d9793d07d 100644 --- a/packages/performance-review/tests/test_tenant_identity_privacy.py +++ b/packages/performance-review/tests/test_tenant_identity_privacy.py @@ -1,12 +1,10 @@ -"""Privacy regression for the public tenant identity in performance review.""" +"""Privacy and interoperability regression for performance-review tenant identity.""" from dataclasses import replace from datetime import date, datetime, timezone -import pytest - from orgmetra_performance_review import build_performance_review_packet -UUID1_ID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" +UUID7_TENANT = "10000000-0000-7000-8000-000000000001" def _build(): @@ -35,17 +33,18 @@ def _build(): ) -def test_uuid1_tenant_identity_is_rejected_by_builder_and_replace() -> None: - """UUIDv1 timestamp/node metadata must not enter the public tenant identity.""" +def test_authoritative_uuid7_tenant_identity_is_accepted_by_builder_and_replace() -> None: + """Accept tenant UUIDs already valid at the authoritative Orgmetra core boundary.""" packet = _build() - with pytest.raises(ValueError, match="tenant_record_id"): - replace(packet, tenant_record_id=UUID1_ID) + replaced = replace(packet, tenant_record_id=UUID7_TENANT) kwargs = { field: getattr(packet, field) for field in packet.__dataclass_fields__ if field not in {"contains_personal_data", "contains_direct_person_identifiers", "contains_rating_value", "contains_free_form_model_output", "human_confirmation_required", "decision_authority", "review_state", "scope_verification_state", "next_action"} } - kwargs["tenant_record_id"] = UUID1_ID - with pytest.raises(ValueError, match="tenant_record_id"): - build_performance_review_packet(**kwargs) + kwargs["tenant_record_id"] = UUID7_TENANT + rebuilt = build_performance_review_packet(**kwargs) + + assert replaced.tenant_record_id == UUID7_TENANT + assert rebuilt.tenant_record_id == UUID7_TENANT From 255d42477f625458ed1f47ea90bfa45746955850 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:26:18 -0700 Subject: [PATCH 042/101] fix: honor authoritative tenant UUID contract in performance review --- .../src/orgmetra_performance_review/packet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index c9215ac8a..1fbdd167c 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -36,13 +36,13 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: - """Require canonical UUIDv4 text so a public governance identity stays opaque.""" + """Require canonical non-sentinel UUID text owned by the authoritative HRIS.""" try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as exc: raise ValueError(f"{field_name} must be canonical UUID text") from exc - if str(parsed) != value or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): - raise ValueError(f"{field_name} must be a canonical operational UUIDv4") + if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUID") def _validate_reference(value: str, prefix: str, field_name: str) -> None: From 27aaaef93cfef53a923648ef1689870a7295c1e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:26:39 -0700 Subject: [PATCH 043/101] docs: align performance-review tenant identity with core --- packages/performance-review/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 32ad7ef92..1c47b6174 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -2,7 +2,7 @@ `orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying person PII, rating values, free-form feedback, or model output into the governance envelope. -The packet binds one canonical non-sentinel UUIDv4 `tenant_record_id` and correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Every trust-bearing identity is represented by canonical non-sentinel UUIDv4 text or a UUIDv4-backed namespaced reference and, where integrity matters, an independent SHA-256 digest. UUIDv1 and other UUID versions are rejected for the public tenant identity and trust references so timestamp/node correlation metadata cannot enter values presented as opaque governance identities. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. +The packet follows Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id` and correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Packet-owned trust-bearing references remain canonical non-sentinel UUIDv4-backed namespaced values and, where integrity matters, carry an independent SHA-256 digest. UUIDv1 and other non-v4 suffixes are rejected for those packet-owned references so timestamp/node correlation metadata cannot enter values presented as this package's opaque governance references. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. @@ -10,7 +10,7 @@ Every packet also carries a bounded positive integer `evidence_version` (default ## What this packet does not do -It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. UUIDv4 syntax constrains identifier opacity only; it does not prove tenant ownership, worker relationship, authorization, or temporal scope. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. +It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. UUIDv4 constrains packet-owned trust-reference opacity only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. ## Required review state From 12ec07eb4c4d50532ad0677ea107966458b8f7be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:26:59 -0700 Subject: [PATCH 044/101] docs: separate review tenant and packet UUID ownership --- docs/adr/0018-governed-performance-review.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index 36e82f811..1a0de42dd 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -7,7 +7,7 @@ Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that identifies which employment references, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. -A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. UUID syntax is also part of the privacy boundary: UUIDv1 can expose timestamp/node-derived correlation metadata despite looking opaque, so neither the public tenant identity nor trust references may accept arbitrary UUID versions. +A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. Packet-owned UUID syntax is also part of the privacy boundary: UUIDv1 can expose timestamp/node-derived correlation metadata despite looking opaque. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. U.S. OPM performance-management guidance treats performance management as a continuous cycle of planning, monitoring, developing, rating, and rewarding, and describes rating as evaluation against established elements and standards. ISO 30414:2025 Edition 2 provides current human-capital reporting requirements and recommendations across areas including productivity, skills/capabilities, and related workforce governance. Orgmetra uses those sources as design evidence, not as a claim that this packet by itself satisfies any jurisdiction-specific appraisal rule or ISO certification requirement. @@ -17,8 +17,8 @@ Introduce a transport-neutral `PerformanceReviewPacket` that remains pre-rating, The packet MUST bind: -- a canonical non-sentinel UUIDv4 tenant identity, rejecting UUIDv1 and every other UUID version; -- opaque canonical non-sentinel UUIDv4-backed Person, Employment, Job, performance-cycle and performance-review references, rejecting UUIDv1 and every other UUID version; +- a canonical non-sentinel tenant identity under Orgmetra's authoritative operational UUID contract; +- opaque canonical non-sentinel UUIDv4-backed Person, Employment, Job, performance-cycle and performance-review references, rejecting UUIDv1 and other non-v4 suffixes; - a governed criterion-set UUIDv4 reference plus independent SHA-256 digest; - a governed performance-goal-plan UUIDv4 reference plus independent SHA-256 digest; - an exact criterion-observation-snapshot UUIDv4 reference plus independent SHA-256 digest; @@ -33,13 +33,13 @@ The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arb The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. -`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. UUIDv4 syntax is only an opacity constraint and does not prove tenant ownership, worker scope, authorization, or temporal validity. +`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. UUIDv4 syntax is only an opacity constraint for packet-owned references; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, or the final rating. ## Consequences -Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. Requiring UUIDv4 for both tenant identity and namespaced trust references closes UUIDv1 timestamp/node correlation leakage, while authoritative resolution remains mandatory because UUIDv4 syntax does not establish tenant or business scope. +Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. Requiring UUIDv4 for packet-owned namespaced trust references closes UUIDv1 timestamp/node correlation leakage without making this leaf package incompatible with authoritative Orgmetra tenant UUIDs; authoritative resolution remains mandatory because UUID syntax does not establish tenant or business scope. This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet now preserves actor, purpose, reviewed reason, and evidence version in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. From 921d873b5201df7dbfedc5c4e0897cdf23af5f75 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:27:14 -0700 Subject: [PATCH 045/101] docs: trace review tenant UUID interoperability --- docs/traceability/performance-review.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index aced9f521..da0ab5c89 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -5,7 +5,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Requirement | Evidence | Status | |---|---|---| | Correlate review with Employment and Job references without claiming relationship resolution | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference`, fixed `scope_verification_state=requires_authoritative_resolution` | Implemented on active PR | -| Keep tenant and trust references opaque and non-correlating by UUID version | `test_uuid1_tenant_identity_is_rejected_by_builder_and_replace`; `test_rejects_uuid1_trust_references_through_builder_and_replace` | `tenant_record_id` and every namespaced packet reference require canonical non-sentinel UUIDv4 identity; UUIDv1 timestamp/node correlation and other UUID versions fail closed through construction/replacement paths. | +| Preserve authoritative tenant interoperability and packet-reference privacy | `test_authoritative_uuid7_tenant_identity_is_accepted_by_builder_and_replace`; `test_rejects_uuid1_trust_references_through_builder_and_replace` | `tenant_record_id` follows the canonical non-sentinel Orgmetra core operational-UUID contract; namespaced packet references require canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes through construction/replacement paths. | | Require authoritative Person↔Employment↔Job/cycle/evidence resolution before rating | immutable scope-verification state plus governed `next_action` | Enforced as downstream prerequisite on active PR | | Bind exact performance-cycle and business review period | `performance_cycle_reference`, `review_period_start`, `review_period_end` | Implemented on active PR | | Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | @@ -18,4 +18,4 @@ Status: **active PR / proposed capability**, not protected-main truth. | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | -The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. UUIDv4 is an identifier-opacity constraint only and does not establish tenant ownership, authorization, or worker relationship truth. Those claims require their own authoritative evidence and controls. +The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. UUIDv4 is an identifier-opacity constraint for packet-owned trust references only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Those claims require their own authoritative evidence and controls. From deeb4c794ca6560eb06af0eec3559bfc825955b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:27:27 -0700 Subject: [PATCH 046/101] docs: record review tenant identity interoperability repair --- packages/performance-review/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 066be5035..50d8df02e 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -3,6 +3,6 @@ ## Unreleased - Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. -- Require canonical non-sentinel UUIDv4 identity for `tenant_record_id` and every namespaced trust reference so UUIDv1 timestamp/node correlation metadata and other UUID versions cannot enter values presented as opaque governance identities. +- Follow Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id`, while namespaced packet-owned trust references remain canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. From c936e0e5ee30c7628320e0a72f2c92523e42305b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:05:56 -0700 Subject: [PATCH 047/101] test(performance-review): reject recorded-time subclasses --- .../tests/test_temporal_evidence_integrity.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/performance-review/tests/test_temporal_evidence_integrity.py diff --git a/packages/performance-review/tests/test_temporal_evidence_integrity.py b/packages/performance-review/tests/test_temporal_evidence_integrity.py new file mode 100644 index 000000000..ffc6fafa6 --- /dev/null +++ b/packages/performance-review/tests/test_temporal_evidence_integrity.py @@ -0,0 +1,56 @@ +"""Regression coverage for recorded-time evidence integrity.""" + +from __future__ import annotations + +from datetime import date, datetime, timezone + +import pytest + +from orgmetra_performance_review import build_performance_review_packet + + +class ForgedDateTime(datetime): + """Datetime subclass able to forge canonical evidence rendering.""" + + def astimezone(self, tz=None): # type: ignore[no-untyped-def] + """Keep the subclass alive across UTC normalization.""" + return self + + def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] + """Return an instant different from the underlying evidence instant.""" + return "2099-12-31T23:59:59+00:00" + + +def valid_kwargs() -> dict[str, object]: + """Return one otherwise valid performance-review packet input.""" + return { + "tenant_record_id": "11111111-1111-4111-8111-111111111111", + "performance_review_reference": "performance_review:22222222-2222-4222-8222-222222222222", + "person_record_reference": "person_record:33333333-3333-4333-8333-333333333333", + "employment_record_reference": "employment_record:44444444-4444-4444-8444-444444444444", + "job_profile_reference": "job_profile:55555555-5555-4555-8555-555555555555", + "performance_cycle_reference": "performance_cycle:66666666-6666-4666-8666-666666666666", + "criterion_set_reference": "criterion_set:77777777-7777-4777-8777-777777777777", + "criterion_set_digest": "a" * 64, + "goal_plan_reference": "performance_goal_plan:88888888-8888-4888-8888-888888888888", + "goal_plan_digest": "b" * 64, + "criterion_observation_snapshot_reference": "criterion_observation_snapshot:99999999-9999-4999-8999-999999999999", + "criterion_observation_snapshot_digest": "c" * 64, + "development_plan_reference": "development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "development_plan_digest": "d" * 64, + "reviewer_reference": "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "purpose_code": "performance_review", + "reason_code": "scheduled_cycle_review", + "review_period_start": date(2026, 1, 1), + "review_period_end": date(2026, 6, 30), + "generated_at": datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), + } + + +def test_rejects_datetime_subclasses_that_can_forge_recorded_time_evidence() -> None: + """Canonical audit evidence must not invoke caller-overridable datetime methods.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = ForgedDateTime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc) + + with pytest.raises(ValueError, match="generated_at"): + build_performance_review_packet(**kwargs) From d488bd1f2db8238e373d72812f779b978ae56071 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:06:20 -0700 Subject: [PATCH 048/101] fix(performance-review): require exact recorded-time type --- .../src/orgmetra_performance_review/packet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 1fbdd167c..4b3614f11 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -72,8 +72,8 @@ def _validate_digest(value: str, field_name: str) -> None: def _canonical_timestamp(value: datetime) -> str: """Render an aware instant as precision-preserving UTC RFC 3339 text.""" - if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: - raise ValueError("generated_at must be timezone-aware") + if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: + raise ValueError("generated_at must be an exact timezone-aware datetime") return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") From 75aa24e191ec68cdb8672d2297581ee05634fb5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:06:19 -0700 Subject: [PATCH 049/101] test(performance-review): reject forged string evidence types --- .../test_string_runtime_evidence_integrity.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 packages/performance-review/tests/test_string_runtime_evidence_integrity.py diff --git a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py new file mode 100644 index 000000000..320139ca5 --- /dev/null +++ b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py @@ -0,0 +1,86 @@ +"""Regression coverage for string-subclass evidence-boundary integrity.""" + +from __future__ import annotations + +from datetime import date, datetime, timezone + +import pytest + +from orgmetra_performance_review import build_performance_review_packet + + +class ForgedReference(str): + """String subclass that forges namespace and UUID suffix validation.""" + + def startswith(self, prefix, *args): # type: ignore[no-untyped-def] + """Pretend the hostile value carries every requested namespace.""" + return True + + def split(self, sep=None, maxsplit=-1): # type: ignore[no-untyped-def] + """Feed validation a canonical UUIDv4 suffix instead of stored text.""" + return ["evil", "22222222-2222-4222-8222-222222222222"] + + +class ForgedTenantUUIDText(str): + """String subclass that forges UUID parsing and canonical-equality checks.""" + + def replace(self, old, new, *args): # type: ignore[no-untyped-def] + """Feed UUID() canonical text instead of the stored hostile tenant text.""" + canonical = "11111111-1111-4111-8111-111111111111" + return canonical.replace(old, new, *args) + + def __eq__(self, other): # type: ignore[no-untyped-def] + """Claim canonical equality while retaining the hostile underlying text.""" + if other is None: + return False + return True + + def __ne__(self, other): # type: ignore[no-untyped-def] + """Keep UUID constructor sentinel checks working while defeating canonicality.""" + if other is None: + return True + return False + + +def valid_kwargs() -> dict[str, object]: + """Return one otherwise valid performance-review packet input.""" + return { + "tenant_record_id": "11111111-1111-4111-8111-111111111111", + "performance_review_reference": "performance_review:22222222-2222-4222-8222-222222222222", + "person_record_reference": "person_record:33333333-3333-4333-8333-333333333333", + "employment_record_reference": "employment_record:44444444-4444-4444-8444-444444444444", + "job_profile_reference": "job_profile:55555555-5555-4555-8555-555555555555", + "performance_cycle_reference": "performance_cycle:66666666-6666-4666-8666-666666666666", + "criterion_set_reference": "criterion_set:77777777-7777-4777-8777-777777777777", + "criterion_set_digest": "a" * 64, + "goal_plan_reference": "performance_goal_plan:88888888-8888-4888-8888-888888888888", + "goal_plan_digest": "b" * 64, + "criterion_observation_snapshot_reference": "criterion_observation_snapshot:99999999-9999-4999-8999-999999999999", + "criterion_observation_snapshot_digest": "c" * 64, + "development_plan_reference": "development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "development_plan_digest": "d" * 64, + "reviewer_reference": "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "purpose_code": "performance_review", + "reason_code": "scheduled_cycle_review", + "review_period_start": date(2026, 1, 1), + "review_period_end": date(2026, 6, 30), + "generated_at": datetime(2026, 8, 19, 5, 15, 30, 123456, tzinfo=timezone.utc), + } + + +def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: + """Canonical evidence must not retain text that only pretended to match a namespace.""" + kwargs = valid_kwargs() + kwargs["performance_review_reference"] = ForgedReference("evil:payload") + + with pytest.raises(ValueError, match="performance_review_reference"): + build_performance_review_packet(**kwargs) + + +def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: + """Authoritative tenant identity must be exact built-in text before UUID parsing.""" + kwargs = valid_kwargs() + kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") + + with pytest.raises(ValueError, match="tenant_record_id"): + build_performance_review_packet(**kwargs) From 7a46910596183fb80f34848ed691689b2efa540b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:06:45 -0700 Subject: [PATCH 050/101] fix(performance-review): require exact string evidence types --- .../src/orgmetra_performance_review/packet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 4b3614f11..3f88a4c39 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -37,6 +37,8 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: """Require canonical non-sentinel UUID text owned by the authoritative HRIS.""" + if type(value) is not str: + raise ValueError(f"{field_name} must be canonical UUID text") try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as exc: @@ -49,7 +51,7 @@ def _validate_reference(value: str, prefix: str, field_name: str) -> None: """Require an expected namespace plus a canonical opaque UUIDv4 suffix.""" error_message = f"{field_name} must be an opaque {prefix}: reference" if ( - not isinstance(value, str) + type(value) is not str or len(value) > 160 or not _REFERENCE_PATTERN.fullmatch(value) or not value.startswith(f"{prefix}:") From 513ee3853874eda241f727300aa2947e17ac93d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:20:17 -0700 Subject: [PATCH 051/101] test(performance-review): reject forged governance text --- .../test_string_runtime_evidence_integrity.py | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py index 320139ca5..148552841 100644 --- a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py +++ b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py @@ -2,6 +2,7 @@ from __future__ import annotations +from dataclasses import replace from datetime import date, datetime, timezone import pytest @@ -13,11 +14,9 @@ class ForgedReference(str): """String subclass that forges namespace and UUID suffix validation.""" def startswith(self, prefix, *args): # type: ignore[no-untyped-def] - """Pretend the hostile value carries every requested namespace.""" return True def split(self, sep=None, maxsplit=-1): # type: ignore[no-untyped-def] - """Feed validation a canonical UUIDv4 suffix instead of stored text.""" return ["evil", "22222222-2222-4222-8222-222222222222"] @@ -25,22 +24,28 @@ class ForgedTenantUUIDText(str): """String subclass that forges UUID parsing and canonical-equality checks.""" def replace(self, old, new, *args): # type: ignore[no-untyped-def] - """Feed UUID() canonical text instead of the stored hostile tenant text.""" canonical = "11111111-1111-4111-8111-111111111111" return canonical.replace(old, new, *args) def __eq__(self, other): # type: ignore[no-untyped-def] - """Claim canonical equality while retaining the hostile underlying text.""" - if other is None: - return False + return other is not None + + def __ne__(self, other): # type: ignore[no-untyped-def] + return other is None + + +class ForgedGovernanceText(str): + """String subclass that forges equality and closed-vocabulary membership.""" + + def __eq__(self, other): # type: ignore[no-untyped-def] return True def __ne__(self, other): # type: ignore[no-untyped-def] - """Keep UUID constructor sentinel checks working while defeating canonicality.""" - if other is None: - return True return False + def __hash__(self) -> int: + return hash("scheduled_cycle_review") + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid performance-review packet input.""" @@ -69,18 +74,43 @@ def valid_kwargs() -> dict[str, object]: def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: - """Canonical evidence must not retain text that only pretended to match a namespace.""" kwargs = valid_kwargs() kwargs["performance_review_reference"] = ForgedReference("evil:payload") - with pytest.raises(ValueError, match="performance_review_reference"): build_performance_review_packet(**kwargs) def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: - """Authoritative tenant identity must be exact built-in text before UUID parsing.""" kwargs = valid_kwargs() kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") - with pytest.raises(ValueError, match="tenant_record_id"): build_performance_review_packet(**kwargs) + + +def test_rejects_reason_code_string_subclass_that_can_forge_allow_list_membership() -> None: + kwargs = valid_kwargs() + kwargs["reason_code"] = ForgedGovernanceText("attacker_controlled_reason") + with pytest.raises(ValueError, match="reason_code"): + build_performance_review_packet(**kwargs) + + +def test_rejects_purpose_code_string_subclass_that_can_forge_fixed_code_check() -> None: + kwargs = valid_kwargs() + kwargs["purpose_code"] = ForgedGovernanceText("attacker_controlled_purpose") + with pytest.raises(ValueError, match="purpose_code"): + build_performance_review_packet(**kwargs) + + +@pytest.mark.parametrize( + ("field_name", "message"), + ( + ("decision_authority", "decision_authority"), + ("review_state", "review_state"), + ("scope_verification_state", "scope_verification_state"), + ("next_action", "next_action"), + ), +) +def test_rejects_forged_direct_construction_constant_text(field_name: str, message: str) -> None: + packet = build_performance_review_packet(**valid_kwargs()) + with pytest.raises(ValueError, match=message): + replace(packet, **{field_name: ForgedGovernanceText("attacker_controlled_text")}) From e27d89572fc5b3c15d164018150ed583a188b136 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:20:53 -0700 Subject: [PATCH 052/101] fix(performance-review): require exact governance text --- .../src/orgmetra_performance_review/packet.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 3f88a4c39..d674d1636 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -86,8 +86,8 @@ def _validate_business_date(value: date, field_name: str) -> None: def _validate_reason_code(value: str) -> None: - """Require a closed, reviewed reason code so free-form PII cannot enter evidence.""" - if not isinstance(value, str) or value not in _ALLOWED_REASON_CODES: + """Require exact built-in text from the closed reviewed reason vocabulary.""" + if type(value) is not str or value not in _ALLOWED_REASON_CODES: raise ValueError("reason_code must be an authorized performance-review reason code") @@ -179,7 +179,7 @@ def __post_init__(self) -> None: ) _validate_digest(self.development_plan_digest, "development_plan_digest") _validate_reference(self.reviewer_reference, "actor", "reviewer_reference") - if self.purpose_code != _PURPOSE_CODE: + if type(self.purpose_code) is not str or self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain performance_review") _validate_reason_code(self.reason_code) _validate_business_date(self.review_period_start, "review_period_start") @@ -198,15 +198,15 @@ def __post_init__(self) -> None: raise ValueError("performance review packet must not contain free-form model output") if self.human_confirmation_required is not True: raise ValueError("human confirmation is mandatory before performance rating") - if self.decision_authority != _DECISION_AUTHORITY: + if type(self.decision_authority) is not str or self.decision_authority != _DECISION_AUTHORITY: raise ValueError("decision_authority must remain human_review_only") - if self.review_state != _REVIEW_STATE: + if type(self.review_state) is not str or self.review_state != _REVIEW_STATE: raise ValueError("review_state must remain requires_human_review") - if self.scope_verification_state != _SCOPE_VERIFICATION_STATE: + if type(self.scope_verification_state) is not str or self.scope_verification_state != _SCOPE_VERIFICATION_STATE: raise ValueError( "scope_verification_state must remain requires_authoritative_resolution" ) - if self.next_action != _NEXT_ACTION: + if type(self.next_action) is not str or self.next_action != _NEXT_ACTION: raise ValueError("next_action must remain the governed performance-review instruction") def canonical_json(self) -> str: From 1f99050d04a73d0a1137f8c5c801ba1fd3346d91 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 01:14:12 -0700 Subject: [PATCH 053/101] test(performance-review): pin recorded-time issuance integrity --- .../tests/test_temporal_evidence_integrity.py | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/performance-review/tests/test_temporal_evidence_integrity.py b/packages/performance-review/tests/test_temporal_evidence_integrity.py index ffc6fafa6..8fcfdb90b 100644 --- a/packages/performance-review/tests/test_temporal_evidence_integrity.py +++ b/packages/performance-review/tests/test_temporal_evidence_integrity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import date, datetime, timezone +from datetime import date, datetime, timedelta, timezone, tzinfo import pytest @@ -21,6 +21,34 @@ def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] return "2099-12-31T23:59:59+00:00" +class MutableTimezone(tzinfo): + """Timezone provider whose offset changes after evidence issuance.""" + + def __init__(self, offset: timedelta) -> None: + """Store one caller-controlled offset.""" + self.offset = offset + + def utcoffset(self, dt: datetime | None) -> timedelta: + """Return the current mutable offset.""" + return self.offset + + def dst(self, dt: datetime | None) -> timedelta: + """Expose no daylight-saving adjustment.""" + return timedelta(0) + + +class RaisingTimezone(tzinfo): + """Timezone provider that raises while resolving UTC offset.""" + + def utcoffset(self, dt: datetime | None) -> timedelta: + """Raise caller-controlled behavior at the trust boundary.""" + raise RuntimeError("provider failure") + + def dst(self, dt: datetime | None) -> timedelta: + """Expose no daylight-saving adjustment.""" + return timedelta(0) + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid performance-review packet input.""" return { @@ -54,3 +82,36 @@ def test_rejects_datetime_subclasses_that_can_forge_recorded_time_evidence() -> with pytest.raises(ValueError, match="generated_at"): build_performance_review_packet(**kwargs) + + +def test_detaches_mutable_timezone_from_recorded_time_evidence() -> None: + """A mutable timezone provider must not rewrite canonical evidence after issuance.""" + provider = MutableTimezone(timedelta(hours=9)) + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime(2026, 8, 19, 14, 15, 30, tzinfo=provider) + + packet = build_performance_review_packet(**kwargs) + before = packet.canonical_json() + provider.offset = timedelta(hours=-7) + + assert packet.canonical_json() == before + assert packet.generated_at == datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc) + assert packet.generated_at.tzinfo is timezone.utc + + +def test_rejects_future_recorded_time() -> None: + """Do not seal performance-review evidence for a system time that has not occurred.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime(2099, 1, 1, tzinfo=timezone.utc) + + with pytest.raises(ValueError, match="generated_at must not be in the future"): + build_performance_review_packet(**kwargs) + + +def test_normalizes_timezone_provider_failure() -> None: + """Do not leak caller timezone exceptions across the review-evidence boundary.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime(2026, 8, 19, 5, 15, 30, tzinfo=RaisingTimezone()) + + with pytest.raises(ValueError, match="generated_at"): + build_performance_review_packet(**kwargs) From f501dd46aa9db30d4a56d455db873bca581fed69 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 01:15:19 -0700 Subject: [PATCH 054/101] test(performance-review): cover frozen-time fail-closed paths --- .../tests/test_temporal_evidence_integrity.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/performance-review/tests/test_temporal_evidence_integrity.py b/packages/performance-review/tests/test_temporal_evidence_integrity.py index 8fcfdb90b..220490b11 100644 --- a/packages/performance-review/tests/test_temporal_evidence_integrity.py +++ b/packages/performance-review/tests/test_temporal_evidence_integrity.py @@ -37,6 +37,18 @@ def dst(self, dt: datetime | None) -> timedelta: return timedelta(0) +class NullOffsetTimezone(tzinfo): + """Timezone provider with no concrete UTC offset.""" + + def utcoffset(self, dt: datetime | None) -> None: + """Return no usable offset.""" + return None + + def dst(self, dt: datetime | None) -> None: + """Return no daylight-saving offset.""" + return None + + class RaisingTimezone(tzinfo): """Timezone provider that raises while resolving UTC offset.""" @@ -108,6 +120,15 @@ def test_rejects_future_recorded_time() -> None: build_performance_review_packet(**kwargs) +def test_rejects_timezone_without_concrete_offset() -> None: + """Reject tzinfo objects that cannot resolve a concrete UTC offset.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime(2026, 8, 19, 5, 15, 30, tzinfo=NullOffsetTimezone()) + + with pytest.raises(ValueError, match="generated_at"): + build_performance_review_packet(**kwargs) + + def test_normalizes_timezone_provider_failure() -> None: """Do not leak caller timezone exceptions across the review-evidence boundary.""" kwargs = valid_kwargs() @@ -115,3 +136,25 @@ def test_normalizes_timezone_provider_failure() -> None: with pytest.raises(ValueError, match="generated_at"): build_performance_review_packet(**kwargs) + + +def test_rejects_timezone_normalization_overflow() -> None: + """Fail closed when a valid offset cannot be represented as a UTC datetime.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime.min.replace(tzinfo=timezone(timedelta(hours=14))) + + with pytest.raises(ValueError, match="generated_at"): + build_performance_review_packet(**kwargs) + + +def test_rejects_post_construction_timezone_reinjection() -> None: + """Do not emit evidence after low-level replacement of the frozen UTC instant.""" + packet = build_performance_review_packet(**valid_kwargs()) + object.__setattr__( + packet, + "generated_at", + datetime(2026, 8, 19, 14, 15, 30, tzinfo=timezone(timedelta(hours=9))), + ) + + with pytest.raises(ValueError, match="generated_at"): + packet.canonical_json() From 9080af6ba936756924ea43b5002343b8fcf8850b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 01:15:49 -0700 Subject: [PATCH 055/101] fix(performance-review): freeze recorded-time evidence --- .../src/orgmetra_performance_review/packet.py | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index d674d1636..1462ebb4c 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -12,7 +12,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import date, datetime, timezone +from datetime import date, datetime, timedelta, timezone from hashlib import sha256 import json import re @@ -72,11 +72,30 @@ def _validate_digest(value: str, field_name: str) -> None: raise ValueError(f"{field_name} must be lowercase SHA-256 hex") +def _freeze_timestamp(value: datetime) -> datetime: + """Resolve caller timezone behavior once and store one immutable UTC instant.""" + if type(value) is not datetime or value.tzinfo is None: + raise ValueError("generated_at must be an exact timezone-aware datetime") + try: + offset = value.utcoffset() + except Exception as exc: + raise ValueError("generated_at must be an exact timezone-aware datetime") from exc + if type(offset) is not timedelta: + raise ValueError("generated_at must be an exact timezone-aware datetime") + try: + frozen = (value.replace(tzinfo=None) - offset).replace(tzinfo=timezone.utc) + except (OverflowError, ValueError) as exc: + raise ValueError("generated_at must be an exact timezone-aware datetime") from exc + if frozen > datetime.now(timezone.utc): + raise ValueError("generated_at must not be in the future") + return frozen + + def _canonical_timestamp(value: datetime) -> str: - """Render an aware instant as precision-preserving UTC RFC 3339 text.""" - if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: + """Render one already-frozen UTC instant as precision-preserving RFC 3339 text.""" + if type(value) is not datetime or value.tzinfo is not timezone.utc: raise ValueError("generated_at must be an exact timezone-aware datetime") - return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + return value.isoformat().replace("+00:00", "Z") def _validate_business_date(value: date, field_name: str) -> None: @@ -186,7 +205,7 @@ def __post_init__(self) -> None: _validate_business_date(self.review_period_end, "review_period_end") if self.review_period_start > self.review_period_end: raise ValueError("review period start must not be after review period end") - _canonical_timestamp(self.generated_at) + object.__setattr__(self, "generated_at", _freeze_timestamp(self.generated_at)) _validate_evidence_version(self.evidence_version) if self.contains_personal_data is not True: raise ValueError("performance review packet contains personal data through worker references") From 2da67e8999169d17268948d953c04750615c9388 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 01:16:50 -0700 Subject: [PATCH 056/101] docs(performance-review): record recorded-time integrity repair --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 50d8df02e..e7cb62a87 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -6,3 +6,4 @@ - Follow Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id`, while namespaced packet-owned trust references remain canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. +- Freeze `generated_at` to a detached built-in UTC instant at issuance, reject future instants, normalize mutable/raising/missing timezone providers to fail-closed validation, and prevent later caller timezone behavior from rewriting canonical performance-review evidence. From 4c8e5d1ab640583ac42a18b52fdbf210d576688b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 01:17:05 -0700 Subject: [PATCH 057/101] docs(performance-review): explain frozen recorded time --- packages/performance-review/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 1c47b6174..fd971c249 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -8,6 +8,8 @@ The person reference is still sensitive correlating metadata. Hosts must enforce Every packet also carries a bounded positive integer `evidence_version` (default `1`). The version is part of canonical JSON and therefore changes the SHA-256 correlation digest when the reviewed evidence contract/version changes. Zero, negative, boolean, textual, and values above `2147483647` fail closed. The version identifies the review evidence envelope; it is not a rating, approval, or substitute for authoritative source-version verification. +`generated_at` is system-recorded issuance evidence. Construction requires an exact built-in `datetime`, resolves a concrete caller timezone offset once, converts the instant to a built-in UTC `datetime`, rejects future instants, and stores only that detached UTC value. Canonical evidence export therefore does not re-enter a caller-owned or mutable `tzinfo`; changing a provider after issuance cannot rewrite evidence. Missing offsets, provider exceptions, UTC-normalization overflow, datetime subclasses, and post-construction reinjection of a non-UTC timestamp fail closed before evidence emission. + ## What this packet does not do It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. UUIDv4 constrains packet-owned trust-reference opacity only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. From 482d2970cf872cb6f0b4e15fb8805f8bbfd990ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 01:17:17 -0700 Subject: [PATCH 058/101] docs(performance-review): trace recorded-time integrity --- docs/traceability/performance-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index da0ab5c89..204a61f80 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -14,6 +14,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Keep person PII, rating values, free-form feedback/model output outside packet | immutable false flags plus absence of value-bearing fields | Implemented on active PR | | Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | +| Preserve deterministic system-recorded chronology without caller-owned timezone behavior | `generated_at` is resolved once to a built-in UTC instant at issuance; future instants, missing/raising offsets and normalization overflow fail closed; later canonical export accepts only that frozen UTC shape | `test_temporal_evidence_integrity.py` covers datetime subclasses, mutable/raising/missing timezone providers, future time, overflow, and post-construction non-UTC reinjection | | Preserve deterministic immutable correlation evidence | canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | From 26055e3f016ae351a1be7f2bd5b12ea697aca10a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 13:17:29 -0700 Subject: [PATCH 059/101] test(performance-review): reject post-issuance evidence rewrites --- .../tests/test_issuance_integrity.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/performance-review/tests/test_issuance_integrity.py diff --git a/packages/performance-review/tests/test_issuance_integrity.py b/packages/performance-review/tests/test_issuance_integrity.py new file mode 100644 index 000000000..e16d55c69 --- /dev/null +++ b/packages/performance-review/tests/test_issuance_integrity.py @@ -0,0 +1,59 @@ +"""Regression tests for immutable performance-review issuance evidence.""" + +from __future__ import annotations + +from datetime import date, datetime, timezone + +import pytest + +from orgmetra_performance_review import build_performance_review_packet +from orgmetra_performance_review import packet as packet_module + + +def _build_packet(): + """Build one valid issued performance-review packet for tamper regressions.""" + return build_performance_review_packet( + tenant_record_id="11111111-1111-4111-8111-111111111111", + performance_review_reference="performance_review:22222222-2222-4222-8222-222222222222", + person_record_reference="person_record:33333333-3333-4333-8333-333333333333", + employment_record_reference="employment_record:44444444-4444-4444-8444-444444444444", + job_profile_reference="job_profile:55555555-5555-4555-8555-555555555555", + performance_cycle_reference="performance_cycle:66666666-6666-4666-8666-666666666666", + criterion_set_reference="criterion_set:77777777-7777-4777-8777-777777777777", + criterion_set_digest="a" * 64, + goal_plan_reference="performance_goal_plan:88888888-8888-4888-8888-888888888888", + goal_plan_digest="b" * 64, + criterion_observation_snapshot_reference="criterion_observation_snapshot:99999999-9999-4999-8999-999999999999", + criterion_observation_snapshot_digest="c" * 64, + development_plan_reference="development_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + development_plan_digest="d" * 64, + reviewer_reference="actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + purpose_code="performance_review", + reason_code="scheduled_cycle_review", + review_period_start=date(2026, 1, 1), + review_period_end=date(2026, 6, 30), + generated_at=datetime(2026, 8, 19, 5, 15, 30, 123456, tzinfo=timezone.utc), + ) + + +def test_post_issuance_valid_value_rewrite_cannot_emit_new_canonical_truth() -> None: + """Reject a valid digest rewrite after the governed packet has been issued.""" + packet = _build_packet() + original = packet.canonical_json() + + object.__setattr__(packet, "goal_plan_digest", "f" * 64) + + with pytest.raises(ValueError, match="changed after issuance"): + packet.canonical_json() + with pytest.raises(ValueError, match="changed after issuance"): + packet.sha256_digest() + assert original != packet_module._canonical_packet_json_unchecked(packet) + + +def test_missing_process_local_issuance_evidence_fails_closed() -> None: + """Reject canonical export when process-local issuance evidence is unavailable.""" + packet = _build_packet() + packet_module._discard_packet_seal(id(packet)) + + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + packet.canonical_json() From c10be954a9fb9183fd9b07cd6c8be139c289498a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 13:18:20 -0700 Subject: [PATCH 060/101] fix(performance-review): seal issued canonical evidence --- .../src/orgmetra_performance_review/packet.py | 116 ++++++++++++------ 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 1462ebb4c..1fb1d2e5e 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -14,9 +14,13 @@ from dataclasses import dataclass from datetime import date, datetime, timedelta, timezone from hashlib import sha256 +import hmac import json import re +import secrets +from threading import RLock from uuid import UUID +from weakref import finalize _DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") _REFERENCE_PATTERN = re.compile( @@ -33,6 +37,34 @@ "provenance; then record accountable human rating and feedback through the " "authoritative performance workflow." ) +_PROCESS_PACKET_SEAL_KEY = secrets.token_bytes(32) +_PACKET_SEALS: dict[int, str] = {} +_PACKET_SEALS_LOCK = RLock() + + +def _discard_packet_seal(packet_id: int) -> None: + """Discard process-local issuance evidence after its review packet is collected.""" + with _PACKET_SEALS_LOCK: + _PACKET_SEALS.pop(packet_id, None) + + +def _register_packet_seal(packet: object, seal: str) -> None: + """Bind one live review-packet identity to evidence outside writable slots.""" + packet_id = id(packet) + with _PACKET_SEALS_LOCK: + _PACKET_SEALS[packet_id] = seal + finalize(packet, _discard_packet_seal, packet_id) + + +def _authoritative_packet_seal(packet: object) -> str | None: + """Return process-local issuance evidence without trusting packet-owned state.""" + with _PACKET_SEALS_LOCK: + return _PACKET_SEALS.get(id(packet)) + + +def _seal_packet(payload_json: str) -> str: + """Bind one process-local issuance to exact canonical performance-review bytes.""" + return hmac.new(_PROCESS_PACKET_SEAL_KEY, payload_json.encode("utf-8"), "sha256").hexdigest() def _validate_operational_uuid(value: str, field_name: str) -> None: @@ -116,7 +148,7 @@ def _validate_evidence_version(value: int) -> None: raise ValueError("evidence_version must be an integer from 1 through 2147483647") -@dataclass(frozen=True, slots=True, repr=False) +@dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) class PerformanceReviewPacket: """Immutable value-minimized review packet awaiting authoritative resolution.""" @@ -227,48 +259,60 @@ def __post_init__(self) -> None: ) if type(self.next_action) is not str or self.next_action != _NEXT_ACTION: raise ValueError("next_action must remain the governed performance-review instruction") + _register_packet_seal(self, _seal_packet(_canonical_packet_json_unchecked(self))) def canonical_json(self) -> str: - """Return deterministic canonical JSON for immutable audit correlation.""" - payload = { - "contains_direct_person_identifiers": self.contains_direct_person_identifiers, - "contains_free_form_model_output": self.contains_free_form_model_output, - "contains_personal_data": self.contains_personal_data, - "contains_rating_value": self.contains_rating_value, - "criterion_observation_snapshot_digest": self.criterion_observation_snapshot_digest, - "criterion_observation_snapshot_reference": self.criterion_observation_snapshot_reference, - "criterion_set_digest": self.criterion_set_digest, - "criterion_set_reference": self.criterion_set_reference, - "decision_authority": self.decision_authority, - "development_plan_digest": self.development_plan_digest, - "development_plan_reference": self.development_plan_reference, - "employment_record_reference": self.employment_record_reference, - "evidence_version": self.evidence_version, - "generated_at": _canonical_timestamp(self.generated_at), - "goal_plan_digest": self.goal_plan_digest, - "goal_plan_reference": self.goal_plan_reference, - "human_confirmation_required": self.human_confirmation_required, - "job_profile_reference": self.job_profile_reference, - "next_action": self.next_action, - "performance_cycle_reference": self.performance_cycle_reference, - "performance_review_reference": self.performance_review_reference, - "person_record_reference": self.person_record_reference, - "purpose_code": self.purpose_code, - "reason_code": self.reason_code, - "review_period_end": self.review_period_end.isoformat(), - "review_period_start": self.review_period_start.isoformat(), - "review_state": self.review_state, - "reviewer_reference": self.reviewer_reference, - "scope_verification_state": self.scope_verification_state, - "tenant_record_id": self.tenant_record_id, - } - return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + """Return issuance-verified deterministic JSON for immutable audit correlation.""" + payload_json = _canonical_packet_json_unchecked(self) + authoritative_seal = _authoritative_packet_seal(self) + if authoritative_seal is None: + raise ValueError("performance review issuance evidence is unavailable") + if not hmac.compare_digest(authoritative_seal, _seal_packet(payload_json)): + raise ValueError("performance review evidence changed after issuance") + return payload_json def sha256_digest(self) -> str: - """Return SHA-256 over the exact canonical UTF-8 performance-review packet.""" + """Return SHA-256 over the exact issuance-verified UTF-8 review packet.""" return sha256(self.canonical_json().encode("utf-8")).hexdigest() +def _canonical_packet_json_unchecked(packet: PerformanceReviewPacket) -> str: + """Render canonical bytes without consulting process-local issuance state.""" + payload = { + "contains_direct_person_identifiers": packet.contains_direct_person_identifiers, + "contains_free_form_model_output": packet.contains_free_form_model_output, + "contains_personal_data": packet.contains_personal_data, + "contains_rating_value": packet.contains_rating_value, + "criterion_observation_snapshot_digest": packet.criterion_observation_snapshot_digest, + "criterion_observation_snapshot_reference": packet.criterion_observation_snapshot_reference, + "criterion_set_digest": packet.criterion_set_digest, + "criterion_set_reference": packet.criterion_set_reference, + "decision_authority": packet.decision_authority, + "development_plan_digest": packet.development_plan_digest, + "development_plan_reference": packet.development_plan_reference, + "employment_record_reference": packet.employment_record_reference, + "evidence_version": packet.evidence_version, + "generated_at": _canonical_timestamp(packet.generated_at), + "goal_plan_digest": packet.goal_plan_digest, + "goal_plan_reference": packet.goal_plan_reference, + "human_confirmation_required": packet.human_confirmation_required, + "job_profile_reference": packet.job_profile_reference, + "next_action": packet.next_action, + "performance_cycle_reference": packet.performance_cycle_reference, + "performance_review_reference": packet.performance_review_reference, + "person_record_reference": packet.person_record_reference, + "purpose_code": packet.purpose_code, + "reason_code": packet.reason_code, + "review_period_end": packet.review_period_end.isoformat(), + "review_period_start": packet.review_period_start.isoformat(), + "review_state": packet.review_state, + "reviewer_reference": packet.reviewer_reference, + "scope_verification_state": packet.scope_verification_state, + "tenant_record_id": packet.tenant_record_id, + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def build_performance_review_packet( *, tenant_record_id: str, From 307cab25cad8d3406365f34d275a606d84c19003 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 13:19:54 -0700 Subject: [PATCH 061/101] docs(performance-review): record issuance integrity repair --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index e7cb62a87..d41d82ad5 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -7,3 +7,4 @@ - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. - Freeze `generated_at` to a detached built-in UTC instant at issuance, reject future instants, normalize mutable/raising/missing timezone providers to fail-closed validation, and prevent later caller timezone behavior from rewriting canonical performance-review evidence. +- Bind each live issued performance-review packet to its exact construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites or when process-local issuance evidence is unavailable; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. From a271a0606399c23e345fb769375596c7d27d07d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 13:20:13 -0700 Subject: [PATCH 062/101] docs(performance-review): explain process-local issuance seal --- packages/performance-review/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index fd971c249..a4685d170 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -10,6 +10,8 @@ Every packet also carries a bounded positive integer `evidence_version` (default `generated_at` is system-recorded issuance evidence. Construction requires an exact built-in `datetime`, resolves a concrete caller timezone offset once, converts the instant to a built-in UTC `datetime`, rejects future instants, and stores only that detached UTC value. Canonical evidence export therefore does not re-enter a caller-owned or mutable `tzinfo`; changing a provider after issuance cannot rewrite evidence. Missing offsets, provider exceptions, UTC-normalization overflow, datetime subclasses, and post-construction reinjection of a non-UTC timestamp fail closed before evidence emission. +A frozen dataclass alone is not an issuance seal because low-level Python mutation can still rewrite otherwise valid fields. Each live issued packet is therefore bound to its exact construction-time canonical JSON by a process-local HMAC seal stored outside packet-writable slots. `canonical_json()` snapshots the current canonical bytes once, verifies that exact snapshot against the external issuance seal, and returns the verified bytes without rereading packet fields. A valid-value rewrite after issuance, or missing process-local issuance evidence, fails closed. This is in-process defense-in-depth only; durable cross-process uniqueness, purpose authorization, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host or persistence boundaries. + ## What this packet does not do It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. UUIDv4 constrains packet-owned trust-reference opacity only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. From fc578738b0126a00640765f4054eb6bb528e1958 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 13:20:27 -0700 Subject: [PATCH 063/101] docs(performance-review): trace issuance tamper evidence --- docs/traceability/performance-review.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index 204a61f80..f3075fea9 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -15,8 +15,9 @@ Status: **active PR / proposed capability**, not protected-main truth. | Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | | Preserve deterministic system-recorded chronology without caller-owned timezone behavior | `generated_at` is resolved once to a built-in UTC instant at issuance; future instants, missing/raising offsets and normalization overflow fail closed; later canonical export accepts only that frozen UTC shape | `test_temporal_evidence_integrity.py` covers datetime subclasses, mutable/raising/missing timezone providers, future time, overflow, and post-construction non-UTC reinjection | -| Preserve deterministic immutable correlation evidence | canonical JSON plus SHA-256 | Implemented on active PR | +| Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; export verifies one snapshot and fails closed after a valid-value rewrite or missing issuance state | `test_issuance_integrity.py`; defense-in-depth only, not durable cross-process authorization/persistence | +| Preserve deterministic immutable correlation evidence | issuance-verified canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | -The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. UUIDv4 is an identifier-opacity constraint for packet-owned trust references only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Those claims require their own authoritative evidence and controls. +The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. UUIDv4 is an identifier-opacity constraint for packet-owned trust references only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. The process-local issuance registry is deliberately not a distributed attestation store: durable uniqueness, authorization, retention, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host/persistence boundaries. Those claims require their own authoritative evidence and controls. From a2211135e3ebd37114cf8ff7e5f735785ecb92d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 21:32:10 -0700 Subject: [PATCH 064/101] test(performance-review): reject digest string subclasses --- .../test_string_runtime_evidence_integrity.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py index 148552841..b80dd971d 100644 --- a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py +++ b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py @@ -47,6 +47,10 @@ def __hash__(self) -> int: return hash("scheduled_cycle_review") +class ForgedDigest(str): + """Valid-looking digest subclass that must not cross the evidence boundary.""" + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid performance-review packet input.""" return { @@ -101,6 +105,23 @@ def test_rejects_purpose_code_string_subclass_that_can_forge_fixed_code_check() build_performance_review_packet(**kwargs) +@pytest.mark.parametrize( + "field_name", + ( + "criterion_set_digest", + "goal_plan_digest", + "criterion_observation_snapshot_digest", + "development_plan_digest", + ), +) +def test_rejects_digest_string_subclass_at_all_digest_boundaries(field_name: str) -> None: + """Every trust-bearing SHA-256 field requires exact built-in text.""" + kwargs = valid_kwargs() + kwargs[field_name] = ForgedDigest(str(kwargs[field_name])) + with pytest.raises(ValueError, match=field_name): + build_performance_review_packet(**kwargs) + + @pytest.mark.parametrize( ("field_name", "message"), ( From e4e810e98359417e06cb6c416eae2f81739242a0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 21:33:16 -0700 Subject: [PATCH 065/101] fix(performance-review): require exact digest text --- .../src/orgmetra_performance_review/packet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 1fb1d2e5e..99442efca 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -99,8 +99,8 @@ def _validate_reference(value: str, prefix: str, field_name: str) -> None: def _validate_digest(value: str, field_name: str) -> None: - """Require lowercase SHA-256 hexadecimal evidence.""" - if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + """Require exact built-in lowercase SHA-256 hexadecimal evidence text.""" + if type(value) is not str or not _DIGEST_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be lowercase SHA-256 hex") From 95a3d7dca818ee13263d7be3eacdd7d8313bc1ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 21:33:30 -0700 Subject: [PATCH 066/101] docs(performance-review): record strict digest runtime contract --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index d41d82ad5..37051bd8b 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -5,6 +5,7 @@ - Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. - Follow Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id`, while namespaced packet-owned trust references remain canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. +- Require exact built-in `str` values for every SHA-256 evidence digest before pattern validation and canonical binding, matching the strict runtime contract used by the other trust-bearing text fields. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. - Freeze `generated_at` to a detached built-in UTC instant at issuance, reject future instants, normalize mutable/raising/missing timezone providers to fail-closed validation, and prevent later caller timezone behavior from rewriting canonical performance-review evidence. - Bind each live issued performance-review packet to its exact construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites or when process-local issuance evidence is unavailable; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. From f6856cc1826de5092e7ed5f188e0fe4e162cc1a2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 21:33:44 -0700 Subject: [PATCH 067/101] docs(performance-review): trace exact digest runtime evidence --- docs/traceability/performance-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index f3075fea9..b6abc8047 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -11,6 +11,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | | Bind exact outcome evidence without copying values | `criterion_observation_snapshot_reference`/digest | Implemented on active PR | | Preserve optional development provenance | paired `development_plan_reference`/digest | Implemented on active PR | +| Reject caller-defined digest string subclasses at every evidence boundary | `_validate_digest` requires exact built-in `str`; `test_rejects_digest_string_subclass_at_all_digest_boundaries` covers criteria, goal-plan, criterion-observation, and development-plan digests | Implemented on active PR | | Keep person PII, rating values, free-form feedback/model output outside packet | immutable false flags plus absence of value-bearing fields | Implemented on active PR | | Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | From 2da07d28f8d5eaeb825f670d6362b40e57316dcc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:08:12 -0700 Subject: [PATCH 068/101] test(performance-review): prove trusted issuance and reference-risk gaps --- packages/performance-review/tests/test_packet.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 4cd33d6bf..3f9da3958 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -1,6 +1,7 @@ from dataclasses import replace from datetime import date, datetime, timedelta, timezone from hashlib import sha256 +import inspect import json import pytest @@ -245,3 +246,16 @@ def test_direct_construction_revalidates_reference_and_digest() -> None: def test_builder_returns_same_public_type_as_direct_contract() -> None: packet = build_valid() assert isinstance(packet, PerformanceReviewPacket) + + +def test_system_recorded_issuance_time_is_not_caller_supplied() -> None: + """System-recorded audit time must not be accepted from the packet caller.""" + assert "generated_at" not in inspect.signature(build_performance_review_packet).parameters + assert "generated_at" not in inspect.signature(PerformanceReviewPacket).parameters + + +def test_unverified_uuidv4_reference_is_classified_as_potential_direct_identifier() -> None: + """UUIDv4 syntax alone must not justify a no-direct-identifier assertion.""" + encoded_person = "person_record:4a616e65-2d44-4f65-8065-2d53534e3132" + packet = build_valid(person_record_reference=encoded_person) + assert packet.contains_direct_person_identifiers is True From 353b77c679e1c261f3bad3f72e63d4a4f5b3b768 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:12:26 -0700 Subject: [PATCH 069/101] fix(performance-review): own recorded time and classify unverified references --- .../src/orgmetra_performance_review/packet.py | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 99442efca..65ae953cd 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -3,15 +3,16 @@ The packet correlates one proposed employee review to Employment and Job references, a performance cycle, predetermined criteria and goals, an exact criterion-observation snapshot, an optional development plan, and an accountable human reviewer. It does not -assert that those references resolve to one authoritative scope; that verification must -occur at the authoritative HRIS/performance boundary before rating. Opaque worker -references remain personal data because they can be re-associated with an identifiable -person through the authoritative HRIS boundary. Direct identifiers, rating values, -free-form feedback, and free-form model output remain outside this envelope. +assert that those references resolve to one authoritative scope or that caller-supplied +UUIDv4-shaped references have already been proven opaque; that verification must occur +at the authoritative HRIS/performance boundary before rating. References are therefore +personal data and are conservatively classified as potentially containing direct person +identifier content until trusted provenance resolves them. Rating values, free-form +feedback, and free-form model output remain outside this envelope. """ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import date, datetime, timedelta, timezone from hashlib import sha256 import hmac @@ -32,10 +33,10 @@ _REVIEW_STATE = "requires_human_review" _SCOPE_VERIFICATION_STATE = "requires_authoritative_resolution" _NEXT_ACTION = ( - "Verify authoritative Employment/Job scope, performance-cycle dates, governed " - "criteria and goals, criterion-observation evidence, and any development-plan " - "provenance; then record accountable human rating and feedback through the " - "authoritative performance workflow." + "Verify authoritative reference provenance and opacity, Employment/Job scope, " + "performance-cycle dates, governed criteria and goals, criterion-observation " + "evidence, and any development-plan provenance; then record accountable human " + "rating and feedback through the authoritative performance workflow." ) _PROCESS_PACKET_SEAL_KEY = secrets.token_bytes(32) _PACKET_SEALS: dict[int, str] = {} @@ -67,6 +68,11 @@ def _seal_packet(payload_json: str) -> str: return hmac.new(_PROCESS_PACKET_SEAL_KEY, payload_json.encode("utf-8"), "sha256").hexdigest() +def _system_recorded_at() -> datetime: + """Read the trusted host clock for one packet issuance.""" + return datetime.now(timezone.utc) + + def _validate_operational_uuid(value: str, field_name: str) -> None: """Require canonical non-sentinel UUID text owned by the authoritative HRIS.""" if type(value) is not str: @@ -80,8 +86,8 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: def _validate_reference(value: str, prefix: str, field_name: str) -> None: - """Require an expected namespace plus a canonical opaque UUIDv4 suffix.""" - error_message = f"{field_name} must be an opaque {prefix}: reference" + """Require an expected namespace plus a canonical UUIDv4-shaped suffix.""" + error_message = f"{field_name} must be a canonical {prefix}: UUIDv4 reference" if ( type(value) is not str or len(value) > 160 @@ -105,7 +111,7 @@ def _validate_digest(value: str, field_name: str) -> None: def _freeze_timestamp(value: datetime) -> datetime: - """Resolve caller timezone behavior once and store one immutable UTC instant.""" + """Resolve the trusted clock once and store one immutable UTC instant.""" if type(value) is not datetime or value.tzinfo is None: raise ValueError("generated_at must be an exact timezone-aware datetime") try: @@ -171,10 +177,10 @@ class PerformanceReviewPacket: reason_code: str review_period_start: date review_period_end: date - generated_at: datetime + generated_at: datetime = field(init=False) evidence_version: int = 1 contains_personal_data: bool = True - contains_direct_person_identifiers: bool = False + contains_direct_person_identifiers: bool = True contains_rating_value: bool = False contains_free_form_model_output: bool = False human_confirmation_required: bool = True @@ -237,12 +243,14 @@ def __post_init__(self) -> None: _validate_business_date(self.review_period_end, "review_period_end") if self.review_period_start > self.review_period_end: raise ValueError("review period start must not be after review period end") - object.__setattr__(self, "generated_at", _freeze_timestamp(self.generated_at)) + object.__setattr__(self, "generated_at", _freeze_timestamp(_system_recorded_at())) _validate_evidence_version(self.evidence_version) if self.contains_personal_data is not True: raise ValueError("performance review packet contains personal data through worker references") - if self.contains_direct_person_identifiers is not False: - raise ValueError("performance review packet must not contain direct person identifiers") + if self.contains_direct_person_identifiers is not True: + raise ValueError( + "unverified reference provenance must be treated as potentially containing direct person identifiers" + ) if self.contains_rating_value is not False: raise ValueError("performance review packet must not contain rating values") if self.contains_free_form_model_output is not False: @@ -334,10 +342,9 @@ def build_performance_review_packet( reason_code: str, review_period_start: date, review_period_end: date, - generated_at: datetime, evidence_version: int = 1, ) -> PerformanceReviewPacket: - """Build value-minimized performance-review evidence pending authoritative resolution.""" + """Build value-minimized performance-review evidence using system-recorded time.""" return PerformanceReviewPacket( tenant_record_id=tenant_record_id, performance_review_reference=performance_review_reference, @@ -358,6 +365,5 @@ def build_performance_review_packet( reason_code=reason_code, review_period_start=review_period_start, review_period_end=review_period_end, - generated_at=generated_at, evidence_version=evidence_version, ) From 62fc923b2b1052e82e1dad434e641e2c0f590b7a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:13:43 -0700 Subject: [PATCH 070/101] test(performance-review): align fixtures with trusted issuance clock --- .../performance-review/tests/test_packet.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/performance-review/tests/test_packet.py b/packages/performance-review/tests/test_packet.py index 3f9da3958..b0b9f6a70 100644 --- a/packages/performance-review/tests/test_packet.py +++ b/packages/performance-review/tests/test_packet.py @@ -1,11 +1,12 @@ from dataclasses import replace -from datetime import date, datetime, timedelta, timezone +from datetime import date, datetime, timezone from hashlib import sha256 import inspect import json import pytest +import orgmetra_performance_review.packet as packet_module from orgmetra_performance_review import ( PerformanceReviewPacket, build_performance_review_packet, @@ -30,6 +31,12 @@ GENERATED_AT = datetime(2026, 8, 19, 5, 15, 30, 123456, tzinfo=timezone.utc) +@pytest.fixture(autouse=True) +def fixed_system_recorded_at(monkeypatch: pytest.MonkeyPatch) -> None: + """Keep packet digests deterministic while production owns issuance time.""" + monkeypatch.setattr(packet_module, "_system_recorded_at", lambda: GENERATED_AT) + + def build_valid(**overrides: object) -> PerformanceReviewPacket: values: dict[str, object] = { "tenant_record_id": TENANT, @@ -51,7 +58,6 @@ def build_valid(**overrides: object) -> PerformanceReviewPacket: "reason_code": "scheduled_cycle_review", "review_period_start": date(2026, 1, 1), "review_period_end": date(2026, 6, 30), - "generated_at": GENERATED_AT, } values.update(overrides) return build_performance_review_packet(**values) @@ -60,13 +66,14 @@ def build_valid(**overrides: object) -> PerformanceReviewPacket: def test_builds_value_minimized_human_review_packet() -> None: packet = build_valid() assert packet.contains_personal_data is True - assert packet.contains_direct_person_identifiers is False + assert packet.contains_direct_person_identifiers is True assert packet.contains_rating_value is False assert packet.contains_free_form_model_output is False assert packet.human_confirmation_required is True assert packet.decision_authority == "human_review_only" assert packet.review_state == "requires_human_review" assert packet.scope_verification_state == "requires_authoritative_resolution" + assert "reference provenance and opacity" in packet.next_action assert "record accountable human rating and feedback" in packet.next_action @@ -76,7 +83,7 @@ def test_canonical_json_and_digest_are_deterministic() -> None: payload = json.loads(canonical) assert payload["person_record_reference"] == PERSON assert payload["contains_personal_data"] is True - assert payload["contains_direct_person_identifiers"] is False + assert payload["contains_direct_person_identifiers"] is True assert payload["scope_verification_state"] == "requires_authoritative_resolution" assert payload["generated_at"] == "2026-08-19T05:15:30.123456Z" assert packet.sha256_digest() == sha256(canonical.encode("utf-8")).hexdigest() @@ -102,12 +109,11 @@ def test_rejects_invalid_evidence_version(evidence_version: object) -> None: build_valid(evidence_version=evidence_version) -def test_timestamp_normalizes_to_utc_without_losing_precision() -> None: - shifted = GENERATED_AT.astimezone(timezone(timedelta(hours=9))) - assert build_valid(generated_at=shifted).canonical_json() == build_valid().canonical_json() - later = build_valid(generated_at=GENERATED_AT.replace(microsecond=123457)) - assert later.canonical_json() != build_valid().canonical_json() - assert later.sha256_digest() != build_valid().sha256_digest() +def test_system_recorded_timestamp_is_bound_to_canonical_evidence() -> None: + packet = build_valid() + assert packet.generated_at == GENERATED_AT + assert packet.generated_at.tzinfo is timezone.utc + assert json.loads(packet.canonical_json())["generated_at"] == "2026-08-19T05:15:30.123456Z" def test_optional_development_plan_may_be_absent_as_a_pair() -> None: @@ -136,7 +142,7 @@ def test_rejects_noncanonical_tenant_identity(tenant: object) -> None: ("reviewer_reference", "actor:reviewer-name"), ], ) -def test_rejects_nonopaque_or_wrong_namespace_references(field: str, value: str) -> None: +def test_rejects_noncanonical_or_wrong_namespace_references(field: str, value: str) -> None: with pytest.raises(ValueError, match=field): build_valid(**{field: value}) @@ -211,17 +217,11 @@ def test_review_period_must_be_real_dates_in_order() -> None: build_valid(review_period_start=date(2026, 7, 1), review_period_end=date(2026, 6, 30)) -@pytest.mark.parametrize("generated_at", [datetime(2026, 8, 19, 5, 15), "2026-08-19T05:15:00Z"]) -def test_generated_at_must_be_timezone_aware_datetime(generated_at: object) -> None: - with pytest.raises(ValueError, match="generated_at"): - build_valid(generated_at=generated_at) - - @pytest.mark.parametrize( ("field", "value", "message"), [ ("contains_personal_data", False, "personal data"), - ("contains_direct_person_identifiers", True, "direct person identifiers"), + ("contains_direct_person_identifiers", False, "unverified reference provenance"), ("contains_rating_value", True, "must not contain rating values"), ("contains_free_form_model_output", True, "must not contain free-form model output"), ("human_confirmation_required", 1, "human confirmation is mandatory"), From 41956f41b07384276bb5395a2ba612be5c4c90f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:14:39 -0700 Subject: [PATCH 071/101] test(performance-review): exercise trusted host clock boundary --- .../tests/test_temporal_evidence_integrity.py | 94 +++++++++++-------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/packages/performance-review/tests/test_temporal_evidence_integrity.py b/packages/performance-review/tests/test_temporal_evidence_integrity.py index 220490b11..d3e732cf2 100644 --- a/packages/performance-review/tests/test_temporal_evidence_integrity.py +++ b/packages/performance-review/tests/test_temporal_evidence_integrity.py @@ -1,4 +1,4 @@ -"""Regression coverage for recorded-time evidence integrity.""" +"""Regression coverage for system-recorded performance-review time integrity.""" from __future__ import annotations @@ -6,6 +6,7 @@ import pytest +import orgmetra_performance_review.packet as packet_module from orgmetra_performance_review import build_performance_review_packet @@ -22,10 +23,10 @@ def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] class MutableTimezone(tzinfo): - """Timezone provider whose offset changes after evidence issuance.""" + """Clock timezone provider whose offset changes after evidence issuance.""" def __init__(self, offset: timedelta) -> None: - """Store one caller-controlled offset.""" + """Store one mutable offset.""" self.offset = offset def utcoffset(self, dt: datetime | None) -> timedelta: @@ -38,7 +39,7 @@ def dst(self, dt: datetime | None) -> timedelta: class NullOffsetTimezone(tzinfo): - """Timezone provider with no concrete UTC offset.""" + """Clock timezone provider with no concrete UTC offset.""" def utcoffset(self, dt: datetime | None) -> None: """Return no usable offset.""" @@ -50,10 +51,10 @@ def dst(self, dt: datetime | None) -> None: class RaisingTimezone(tzinfo): - """Timezone provider that raises while resolving UTC offset.""" + """Clock timezone provider that raises while resolving UTC offset.""" def utcoffset(self, dt: datetime | None) -> timedelta: - """Raise caller-controlled behavior at the trust boundary.""" + """Raise at the recorded-time trust boundary.""" raise RuntimeError("provider failure") def dst(self, dt: datetime | None) -> timedelta: @@ -83,26 +84,29 @@ def valid_kwargs() -> dict[str, object]: "reason_code": "scheduled_cycle_review", "review_period_start": date(2026, 1, 1), "review_period_end": date(2026, 6, 30), - "generated_at": datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), } -def test_rejects_datetime_subclasses_that_can_forge_recorded_time_evidence() -> None: - """Canonical audit evidence must not invoke caller-overridable datetime methods.""" - kwargs = valid_kwargs() - kwargs["generated_at"] = ForgedDateTime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc) +def test_rejects_datetime_subclasses_from_trusted_clock_adapter( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Canonical audit evidence must not invoke overridable datetime methods.""" + forged = ForgedDateTime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc) + monkeypatch.setattr(packet_module, "_system_recorded_at", lambda: forged) with pytest.raises(ValueError, match="generated_at"): - build_performance_review_packet(**kwargs) + build_performance_review_packet(**valid_kwargs()) -def test_detaches_mutable_timezone_from_recorded_time_evidence() -> None: - """A mutable timezone provider must not rewrite canonical evidence after issuance.""" +def test_detaches_mutable_clock_timezone_from_recorded_time_evidence( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A mutable clock timezone provider must not rewrite issued evidence.""" provider = MutableTimezone(timedelta(hours=9)) - kwargs = valid_kwargs() - kwargs["generated_at"] = datetime(2026, 8, 19, 14, 15, 30, tzinfo=provider) + recorded_at = datetime(2026, 8, 19, 14, 15, 30, tzinfo=provider) + monkeypatch.setattr(packet_module, "_system_recorded_at", lambda: recorded_at) - packet = build_performance_review_packet(**kwargs) + packet = build_performance_review_packet(**valid_kwargs()) before = packet.canonical_json() provider.offset = timedelta(hours=-7) @@ -111,44 +115,58 @@ def test_detaches_mutable_timezone_from_recorded_time_evidence() -> None: assert packet.generated_at.tzinfo is timezone.utc -def test_rejects_future_recorded_time() -> None: - """Do not seal performance-review evidence for a system time that has not occurred.""" - kwargs = valid_kwargs() - kwargs["generated_at"] = datetime(2099, 1, 1, tzinfo=timezone.utc) +def test_rejects_future_system_recorded_time(monkeypatch: pytest.MonkeyPatch) -> None: + """Do not seal evidence when the trusted clock reports a future instant.""" + monkeypatch.setattr( + packet_module, + "_system_recorded_at", + lambda: datetime(2099, 1, 1, tzinfo=timezone.utc), + ) with pytest.raises(ValueError, match="generated_at must not be in the future"): - build_performance_review_packet(**kwargs) + build_performance_review_packet(**valid_kwargs()) -def test_rejects_timezone_without_concrete_offset() -> None: - """Reject tzinfo objects that cannot resolve a concrete UTC offset.""" - kwargs = valid_kwargs() - kwargs["generated_at"] = datetime(2026, 8, 19, 5, 15, 30, tzinfo=NullOffsetTimezone()) +def test_rejects_clock_timezone_without_concrete_offset( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Reject clock adapters that cannot resolve a concrete UTC offset.""" + recorded_at = datetime(2026, 8, 19, 5, 15, 30, tzinfo=NullOffsetTimezone()) + monkeypatch.setattr(packet_module, "_system_recorded_at", lambda: recorded_at) with pytest.raises(ValueError, match="generated_at"): - build_performance_review_packet(**kwargs) + build_performance_review_packet(**valid_kwargs()) -def test_normalizes_timezone_provider_failure() -> None: - """Do not leak caller timezone exceptions across the review-evidence boundary.""" - kwargs = valid_kwargs() - kwargs["generated_at"] = datetime(2026, 8, 19, 5, 15, 30, tzinfo=RaisingTimezone()) +def test_normalizes_clock_timezone_provider_failure(monkeypatch: pytest.MonkeyPatch) -> None: + """Do not leak clock timezone exceptions across the evidence boundary.""" + recorded_at = datetime(2026, 8, 19, 5, 15, 30, tzinfo=RaisingTimezone()) + monkeypatch.setattr(packet_module, "_system_recorded_at", lambda: recorded_at) with pytest.raises(ValueError, match="generated_at"): - build_performance_review_packet(**kwargs) + build_performance_review_packet(**valid_kwargs()) -def test_rejects_timezone_normalization_overflow() -> None: - """Fail closed when a valid offset cannot be represented as a UTC datetime.""" - kwargs = valid_kwargs() - kwargs["generated_at"] = datetime.min.replace(tzinfo=timezone(timedelta(hours=14))) +def test_rejects_clock_timezone_normalization_overflow( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Fail closed when a clock instant cannot be represented as UTC.""" + recorded_at = datetime.min.replace(tzinfo=timezone(timedelta(hours=14))) + monkeypatch.setattr(packet_module, "_system_recorded_at", lambda: recorded_at) with pytest.raises(ValueError, match="generated_at"): - build_performance_review_packet(**kwargs) + build_performance_review_packet(**valid_kwargs()) -def test_rejects_post_construction_timezone_reinjection() -> None: +def test_rejects_post_construction_timezone_reinjection( + monkeypatch: pytest.MonkeyPatch, +) -> None: """Do not emit evidence after low-level replacement of the frozen UTC instant.""" + monkeypatch.setattr( + packet_module, + "_system_recorded_at", + lambda: datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), + ) packet = build_performance_review_packet(**valid_kwargs()) object.__setattr__( packet, From 0888b1ea877a441b23282fbf17d9ac3ed628937a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:15:46 -0700 Subject: [PATCH 072/101] test(performance-review): stop supplying recorded time in issuance fixtures --- packages/performance-review/tests/test_issuance_integrity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/performance-review/tests/test_issuance_integrity.py b/packages/performance-review/tests/test_issuance_integrity.py index e16d55c69..41704788e 100644 --- a/packages/performance-review/tests/test_issuance_integrity.py +++ b/packages/performance-review/tests/test_issuance_integrity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import date, datetime, timezone +from datetime import date import pytest @@ -32,7 +32,6 @@ def _build_packet(): reason_code="scheduled_cycle_review", review_period_start=date(2026, 1, 1), review_period_end=date(2026, 6, 30), - generated_at=datetime(2026, 8, 19, 5, 15, 30, 123456, tzinfo=timezone.utc), ) From 2625cdded1918a25938e966b973c556df2bc258b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:15:58 -0700 Subject: [PATCH 073/101] test(performance-review): use system-owned time in repr fixture --- packages/performance-review/tests/test_repr_privacy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/performance-review/tests/test_repr_privacy.py b/packages/performance-review/tests/test_repr_privacy.py index df6860416..bcf78d1f5 100644 --- a/packages/performance-review/tests/test_repr_privacy.py +++ b/packages/performance-review/tests/test_repr_privacy.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, timezone +from datetime import date from orgmetra_performance_review import build_performance_review_packet @@ -24,7 +24,6 @@ def test_repr_redacts_worker_rating_scope_and_evidence() -> None: reason_code="scheduled_cycle_review", review_period_start=date(2026, 1, 1), review_period_end=date(2026, 6, 30), - generated_at=datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), ) rendered = repr(packet) From 294ad0fa668223686adb26cdb4c2c31c220a2d4b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:16:37 -0700 Subject: [PATCH 074/101] test(performance-review): use system-owned time in string-integrity fixtures --- .../tests/test_string_runtime_evidence_integrity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py index b80dd971d..9f0ac3683 100644 --- a/packages/performance-review/tests/test_string_runtime_evidence_integrity.py +++ b/packages/performance-review/tests/test_string_runtime_evidence_integrity.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import replace -from datetime import date, datetime, timezone +from datetime import date import pytest @@ -73,7 +73,6 @@ def valid_kwargs() -> dict[str, object]: "reason_code": "scheduled_cycle_review", "review_period_start": date(2026, 1, 1), "review_period_end": date(2026, 6, 30), - "generated_at": datetime(2026, 8, 19, 5, 15, 30, 123456, tzinfo=timezone.utc), } From d3b093e0beaae17c5d0bf1b051a26836d4b0f1f3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:17:00 -0700 Subject: [PATCH 075/101] test(performance-review): preserve system-owned time in tenant rebuilds --- .../tests/test_tenant_identity_privacy.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/performance-review/tests/test_tenant_identity_privacy.py b/packages/performance-review/tests/test_tenant_identity_privacy.py index d9793d07d..d4ca56d99 100644 --- a/packages/performance-review/tests/test_tenant_identity_privacy.py +++ b/packages/performance-review/tests/test_tenant_identity_privacy.py @@ -1,6 +1,6 @@ """Privacy and interoperability regression for performance-review tenant identity.""" from dataclasses import replace -from datetime import date, datetime, timezone +from datetime import date from orgmetra_performance_review import build_performance_review_packet @@ -29,7 +29,6 @@ def _build(): reason_code="scheduled_cycle_review", review_period_start=date(2026, 1, 1), review_period_end=date(2026, 6, 30), - generated_at=datetime(2026, 8, 19, 5, 15, 30, tzinfo=timezone.utc), ) @@ -41,7 +40,19 @@ def test_authoritative_uuid7_tenant_identity_is_accepted_by_builder_and_replace( kwargs = { field: getattr(packet, field) for field in packet.__dataclass_fields__ - if field not in {"contains_personal_data", "contains_direct_person_identifiers", "contains_rating_value", "contains_free_form_model_output", "human_confirmation_required", "decision_authority", "review_state", "scope_verification_state", "next_action"} + if field + not in { + "generated_at", + "contains_personal_data", + "contains_direct_person_identifiers", + "contains_rating_value", + "contains_free_form_model_output", + "human_confirmation_required", + "decision_authority", + "review_state", + "scope_verification_state", + "next_action", + } } kwargs["tenant_record_id"] = UUID7_TENANT rebuilt = build_performance_review_packet(**kwargs) From d618da0cb83f9d7f58bd5ae967fe275659c14b8f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:18:22 -0700 Subject: [PATCH 076/101] docs(performance-review): document trusted time and reference provenance risk --- packages/performance-review/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index a4685d170..82d0320d3 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -1,21 +1,21 @@ # Orgmetra Performance Review -`orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying person PII, rating values, free-form feedback, or model output into the governance envelope. +`orgmetra-performance-review` provides a small, transport-neutral evidence packet for preparing an accountable human performance review without copying rating values, free-form feedback, or model output into the governance envelope. -The packet follows Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id` and correlates one opaque Person and Employment reference with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Packet-owned trust-bearing references remain canonical non-sentinel UUIDv4-backed namespaced values and, where integrity matters, carry an independent SHA-256 digest. UUIDv1 and other non-v4 suffixes are rejected for those packet-owned references so timestamp/node correlation metadata cannot enter values presented as this package's opaque governance references. **The packet does not assert that those independently supplied references already resolve to one authoritative employment/performance scope.** `scope_verification_state` is fixed to `requires_authoritative_resolution`; the authoritative HRIS/performance boundary must resolve that relationship before a rating is recorded. +The packet follows Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id` and correlates Person and Employment references with a Job, performance cycle, governed criterion set, goal plan, exact criterion-observation snapshot, optional development plan, and reviewer. Packet-owned trust-bearing references require canonical non-sentinel UUIDv4-shaped namespaced values and reject UUIDv1/non-v4 suffixes so UUIDv1 timestamp/node metadata cannot enter these fields. **UUIDv4 syntax does not prove that independently supplied bytes are random, opaque, or free of encoded identifier content.** Until an authoritative issuer/resolver verifies reference provenance and opacity, the packet conservatively records `contains_direct_person_identifiers=True` and requires purpose-bound handling. The packet also does not assert that independently supplied references already resolve to one authoritative employment/performance scope: `scope_verification_state` remains `requires_authoritative_resolution` until the authoritative HRIS/performance boundary resolves that relationship before rating. -The person reference is still sensitive correlating metadata. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. +Worker references are sensitive correlating metadata and are treated as potentially containing direct identifier content until trusted provenance verifies otherwise. Hosts must enforce purpose-bound authorization, least privilege, retention/export controls, encryption where appropriate, and immutable audit evidence around packet access. `reason_code` is not free-form metadata: the current reviewed vocabulary accepts only `scheduled_cycle_review`. New business reasons must be introduced through an explicit governed contract change rather than encoded into arbitrary lower-snake-case strings, preventing names, identifiers, or other unreviewed context from entering canonical evidence. -Every packet also carries a bounded positive integer `evidence_version` (default `1`). The version is part of canonical JSON and therefore changes the SHA-256 correlation digest when the reviewed evidence contract/version changes. Zero, negative, boolean, textual, and values above `2147483647` fail closed. The version identifies the review evidence envelope; it is not a rating, approval, or substitute for authoritative source-version verification. +Every packet carries a bounded positive integer `evidence_version` (default `1`). The version is part of canonical JSON and therefore changes the SHA-256 correlation digest when the reviewed evidence contract/version changes. Zero, negative, boolean, textual, and values above `2147483647` fail closed. The version identifies the review evidence envelope; it is not a rating, approval, or substitute for authoritative source-version verification. -`generated_at` is system-recorded issuance evidence. Construction requires an exact built-in `datetime`, resolves a concrete caller timezone offset once, converts the instant to a built-in UTC `datetime`, rejects future instants, and stores only that detached UTC value. Canonical evidence export therefore does not re-enter a caller-owned or mutable `tzinfo`; changing a provider after issuance cannot rewrite evidence. Missing offsets, provider exceptions, UTC-normalization overflow, datetime subclasses, and post-construction reinjection of a non-UTC timestamp fail closed before evidence emission. +`generated_at` is system-recorded issuance evidence and is **not a caller argument**. Construction reads the host clock inside the packet boundary, resolves a concrete timezone offset once, converts the instant to a built-in UTC `datetime`, rejects future instants, and stores only that detached UTC value. Canonical evidence export therefore does not re-enter caller input or a mutable `tzinfo`; invalid clock adapters, missing offsets, provider exceptions, UTC-normalization overflow, datetime subclasses, and post-construction reinjection of a non-UTC timestamp fail closed before evidence emission. Tests replace the internal clock adapter only to make exact canonical bytes deterministic; production callers cannot supply `generated_at`. A frozen dataclass alone is not an issuance seal because low-level Python mutation can still rewrite otherwise valid fields. Each live issued packet is therefore bound to its exact construction-time canonical JSON by a process-local HMAC seal stored outside packet-writable slots. `canonical_json()` snapshots the current canonical bytes once, verifies that exact snapshot against the external issuance seal, and returns the verified bytes without rereading packet fields. A valid-value rewrite after issuance, or missing process-local issuance evidence, fails closed. This is in-process defense-in-depth only; durable cross-process uniqueness, purpose authorization, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host or persistence boundaries. ## What this packet does not do -It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, or prove cross-record scope consistency by syntax alone. It does not replace the authoritative performance/criterion persistence boundary. UUIDv4 constrains packet-owned trust-reference opacity only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, or that a human review actually occurred. +It does not calculate or persist a rating, write narrative feedback, infer performance, make an employment decision, modify compensation, execute a development action, prove reference opacity by UUID syntax, or prove cross-record scope consistency. It does not replace the authoritative performance/criterion persistence boundary. Tenant UUID generation/version/privacy policy and trust-bearing reference provenance remain owned by authoritative host boundaries. Canonical JSON and SHA-256 provide correlation integrity only; they do not prove fairness, scientific validity, legal compliance, authoritative scope resolution, reference provenance, or that a human review actually occurred. ## Required review state -Every packet remains `requires_human_review`, with `decision_authority="human_review_only"`, `human_confirmation_required=True`, and `scope_verification_state="requires_authoritative_resolution"`. The fixed next action tells the reviewer to verify authoritative Employment/Job scope, review-period and performance-cycle alignment, governed criteria and goals, criterion-observation evidence, and any development-plan provenance before recording accountable human rating and feedback through the authoritative performance workflow. +Every packet remains `requires_human_review`, with `decision_authority="human_review_only"`, `human_confirmation_required=True`, and `scope_verification_state="requires_authoritative_resolution"`. The fixed next action tells the reviewer to verify authoritative reference provenance and opacity, Employment/Job scope, review-period and performance-cycle alignment, governed criteria and goals, criterion-observation evidence, and any development-plan provenance before recording accountable human rating and feedback through the authoritative performance workflow. From efde52cd17171862ecbe1f5b06214e86ef328b84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:18:33 -0700 Subject: [PATCH 077/101] docs(performance-review): record trusted issuance and reference-risk repair --- packages/performance-review/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 37051bd8b..930e1a103 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -2,10 +2,10 @@ ## Unreleased -- Add a PII-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. -- Follow Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id`, while namespaced packet-owned trust references remain canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes. +- Add a value-minimized, human-review-only performance-review evidence packet binding Employment/Job references while requiring downstream authoritative scope resolution before rating, together with performance cycle, criteria, goals, outcome evidence, optional development-plan provenance, and an accountable reviewer. +- Follow Orgmetra's authoritative canonical non-sentinel operational UUID contract for `tenant_record_id`, while namespaced packet-owned trust references require canonical non-sentinel UUIDv4-shaped values and reject UUIDv1/non-v4 suffixes. UUIDv4 syntax is no longer described as proof of opacity: until trusted issuer/resolver provenance verifies otherwise, independently supplied references are conservatively classified as potentially containing direct person identifier content. - Restrict `reason_code` to the reviewed closed vocabulary (`scheduled_cycle_review`) so arbitrary lower-snake-case text cannot carry PII or ungoverned decision context into canonical evidence. - Require exact built-in `str` values for every SHA-256 evidence digest before pattern validation and canonical binding, matching the strict runtime contract used by the other trust-bearing text fields. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. -- Freeze `generated_at` to a detached built-in UTC instant at issuance, reject future instants, normalize mutable/raising/missing timezone providers to fail-closed validation, and prevent later caller timezone behavior from rewriting canonical performance-review evidence. +- Make `generated_at` system-owned rather than caller-supplied: packet construction reads the trusted host clock, freezes it to a detached built-in UTC instant, rejects future/invalid clock results, and prevents callers from falsifying audit chronology by submitting arbitrary historical issuance times. - Bind each live issued performance-review packet to its exact construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites or when process-local issuance evidence is unavailable; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. From 14aa5a0f2c80edea5c0e96f0a06542bd8cce532d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:19:14 -0700 Subject: [PATCH 078/101] docs(adr): harden performance-review issuance and reference provenance --- docs/adr/0018-governed-performance-review.md | 35 ++++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index 1a0de42dd..b94165c61 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -5,43 +5,50 @@ ## Context -Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that identifies which employment references, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying person values or prematurely materializing a rating. +Orgmetra already owns authoritative Employment/Job truth and performance/criterion evidence boundaries, but a buyer-facing review workflow also needs a small pre-rating object that identifies which employment references, review period, performance cycle, criteria, goals, outcome evidence, and reviewer are being considered without copying rating values, narrative feedback, or model output into the envelope. -A transport-neutral packet cannot prove merely from syntactically valid opaque references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Treating correlation as verified scope would create a misleading high-impact evidence boundary. Authoritative relationship and temporal resolution therefore remains a required downstream step before rating. Packet-owned UUID syntax is also part of the privacy boundary: UUIDv1 can expose timestamp/node-derived correlation metadata despite looking opaque. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. +A transport-neutral packet cannot prove merely from syntactically valid references that the Person, Employment, Job, cycle, goals, and observation snapshot all resolve to one authoritative temporal scope. Nor can UUIDv4 syntax prove that independently supplied bytes were randomly generated or contain no encoded identifier content. Treating either relationship resolution or reference opacity as established from syntax would create a misleading high-impact evidence boundary. Authoritative relationship/temporal resolution and trusted reference-provenance verification therefore remain required downstream steps before rating. UUIDv1 is still rejected for packet-owned namespaced references because its timestamp/node layout is unnecessary metadata for this boundary. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package accepts the canonical non-sentinel operational UUID contract owned by that boundary rather than imposing a second version policy. + +System-recorded time is also audit evidence. Accepting an arbitrary caller-supplied historical `generated_at` would let a caller backdate issuance while still passing a future-only validation gate. The packet therefore owns the issuance timestamp and reads it from the host clock during construction rather than accepting it as a public constructor or builder input. U.S. OPM performance-management guidance treats performance management as a continuous cycle of planning, monitoring, developing, rating, and rewarding, and describes rating as evaluation against established elements and standards. ISO 30414:2025 Edition 2 provides current human-capital reporting requirements and recommendations across areas including productivity, skills/capabilities, and related workforce governance. Orgmetra uses those sources as design evidence, not as a claim that this packet by itself satisfies any jurisdiction-specific appraisal rule or ISO certification requirement. ## Decision -Introduce a transport-neutral `PerformanceReviewPacket` that remains pre-rating, value-free governance evidence. +Introduce a transport-neutral `PerformanceReviewPacket` that remains pre-rating governance evidence. The packet MUST bind: - a canonical non-sentinel tenant identity under Orgmetra's authoritative operational UUID contract; -- opaque canonical non-sentinel UUIDv4-backed Person, Employment, Job, performance-cycle and performance-review references, rejecting UUIDv1 and other non-v4 suffixes; -- a governed criterion-set UUIDv4 reference plus independent SHA-256 digest; -- a governed performance-goal-plan UUIDv4 reference plus independent SHA-256 digest; -- an exact criterion-observation-snapshot UUIDv4 reference plus independent SHA-256 digest; -- an optional development-plan UUIDv4 reference/digest pair; +- canonical non-sentinel UUIDv4-shaped namespaced Person, Employment, Job, performance-cycle and performance-review references, rejecting UUIDv1 and other non-v4 suffixes without claiming that UUIDv4 syntax proves opacity; +- a governed criterion-set UUIDv4-shaped reference plus independent SHA-256 digest; +- a governed performance-goal-plan UUIDv4-shaped reference plus independent SHA-256 digest; +- an exact criterion-observation-snapshot UUIDv4-shaped reference plus independent SHA-256 digest; +- an optional development-plan UUIDv4-shaped reference/digest pair; - explicit business review-period dates; -- one accountable UUIDv4-backed reviewer, fixed `performance_review` purpose, a reviewed closed reason code, and precision-preserving evidence timestamp; +- one accountable UUIDv4-shaped reviewer, fixed `performance_review` purpose, and a reviewed closed reason code; +- a **system-owned** precision-preserving issuance timestamp read from the host clock inside the packet boundary, with no caller-supplied `generated_at` parameter; and - a bounded positive integer `evidence_version`, defaulting to `1`, that is included in canonical evidence and therefore changes the packet digest when the governed evidence version changes. The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arbitrary lower-snake-case values are rejected even when syntactically well formed, because free-form reason text can encode a person name, identifier, or unreviewed decision context. Additional reasons require an explicit governed contract change and regression evidence before they can enter canonical review evidence. `evidence_version` accepts only real integers from `1` through `2147483647`; booleans, text, zero, negative values, and overflow values fail closed. The field versions the immutable review evidence envelope and does not itself prove source-version resolution, human approval, or rating completion. -The packet MUST NOT carry person PII, a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. +Because this package cannot prove independently supplied reference provenance, `contains_personal_data` and `contains_direct_person_identifiers` are both fixed to `True`. The latter is deliberately conservative: it means the envelope must be handled as potentially containing direct identifier content until an authoritative issuer/resolver verifies opacity. The packet MUST NOT carry a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. + +`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must verify reference provenance and opacity, then resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and governed evidence scope using current temporal truth and purpose-bound authorization. UUIDv4 shape alone is not provenance evidence; tenant UUID generation/version/privacy policy likewise remains owned by the authoritative HRIS boundary. -`scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and the governed evidence scope using its current temporal truth and purpose-bound authorization. UUIDv4 syntax is only an opacity constraint for packet-owned references; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. +`generated_at` is constructed from a trusted internal clock adapter. The resulting exact built-in timezone-aware `datetime` is normalized once to UTC and then sealed; future instants, missing/raising offsets, normalization overflow, datetime subclasses, or post-construction non-UTC reinjection fail closed. Tests may replace the internal clock adapter to make canonical bytes deterministic, but production callers cannot provide the issuance timestamp. -Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, or the final rating. +Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, reference provenance, or the final rating. ## Consequences -Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle. Person correlation remains sensitive metadata and therefore still requires purpose-bound access, least privilege, retention/export controls, and immutable audit handling. Requiring UUIDv4 for packet-owned namespaced trust references closes UUIDv1 timestamp/node correlation leakage without making this leaf package incompatible with authoritative Orgmetra tenant UUIDs; authoritative resolution remains mandatory because UUID syntax does not establish tenant or business scope. +Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle or that UUIDv4-shaped values are opaque. Until authoritative provenance verification occurs, the packet receives the more restrictive identifier-risk classification rather than a false no-direct-identifier assertion. + +The system-recorded timestamp can no longer be backdated through public packet construction. Hosts still own purpose-bound authorization, durable immutable audit/outbox, retention/export controls, and the authoritative clock/runtime environment. -This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet now preserves actor, purpose, reviewed reason, and evidence version in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope-resolution evidence, and any applicable policy requirements. +This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet preserves actor, purpose, reviewed reason, evidence version, conservative identifier-risk classification, and system-recorded issuance time in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope/provenance resolution evidence, and any applicable policy requirements. ## References From 804d59681af2b75136b8e49b62d7f52ffe18fc2c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:19:58 -0700 Subject: [PATCH 079/101] docs(traceability): bind performance-review time and reference-risk controls --- docs/traceability/performance-review.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index b6abc8047..fcd0d6b91 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -5,20 +5,23 @@ Status: **active PR / proposed capability**, not protected-main truth. | Requirement | Evidence | Status | |---|---|---| | Correlate review with Employment and Job references without claiming relationship resolution | `PerformanceReviewPacket.employment_record_reference`, `job_profile_reference`, fixed `scope_verification_state=requires_authoritative_resolution` | Implemented on active PR | -| Preserve authoritative tenant interoperability and packet-reference privacy | `test_authoritative_uuid7_tenant_identity_is_accepted_by_builder_and_replace`; `test_rejects_uuid1_trust_references_through_builder_and_replace` | `tenant_record_id` follows the canonical non-sentinel Orgmetra core operational-UUID contract; namespaced packet references require canonical non-sentinel UUIDv4 and reject UUIDv1/non-v4 suffixes through construction/replacement paths. | -| Require authoritative Person↔Employment↔Job/cycle/evidence resolution before rating | immutable scope-verification state plus governed `next_action` | Enforced as downstream prerequisite on active PR | +| Preserve authoritative tenant interoperability without treating UUID syntax as opacity proof | `test_authoritative_uuid7_tenant_identity_is_accepted_by_builder_and_replace`; `test_rejects_uuid1_trust_references_through_builder_and_replace`; `test_unverified_uuidv4_reference_is_classified_as_potential_direct_identifier` | `tenant_record_id` follows the canonical non-sentinel Orgmetra core operational-UUID contract; namespaced packet references require canonical non-sentinel UUIDv4 shape and reject UUIDv1/non-v4 suffixes, while independently supplied references remain conservatively classified as potentially containing direct person identifier content until trusted provenance verifies opacity. | +| Require authoritative reference provenance plus Person↔Employment↔Job/cycle/evidence resolution before rating | immutable scope-verification state, conservative identifier-risk flag, and governed `next_action` | Enforced as downstream prerequisite on active PR | | Bind exact performance-cycle and business review period | `performance_cycle_reference`, `review_period_start`, `review_period_end` | Implemented on active PR | | Bind predetermined criteria and goals | `criterion_set_reference`/digest, `goal_plan_reference`/digest | Implemented on active PR | | Bind exact outcome evidence without copying values | `criterion_observation_snapshot_reference`/digest | Implemented on active PR | | Preserve optional development provenance | paired `development_plan_reference`/digest | Implemented on active PR | | Reject caller-defined digest string subclasses at every evidence boundary | `_validate_digest` requires exact built-in `str`; `test_rejects_digest_string_subclass_at_all_digest_boundaries` covers criteria, goal-plan, criterion-observation, and development-plan digests | Implemented on active PR | -| Keep person PII, rating values, free-form feedback/model output outside packet | immutable false flags plus absence of value-bearing fields | Implemented on active PR | +| Avoid unsupported no-direct-identifier claims for independently supplied references | `contains_personal_data=True`, `contains_direct_person_identifiers=True`; UUIDv4-shaped encoded-person regression | Implemented conservatively until authoritative issuer/resolver provenance is verified | +| Exclude rating values and free-form feedback/model output | immutable false value/model flags plus absence of rating/narrative fields | Implemented on active PR | | Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | -| Preserve deterministic system-recorded chronology without caller-owned timezone behavior | `generated_at` is resolved once to a built-in UTC instant at issuance; future instants, missing/raising offsets and normalization overflow fail closed; later canonical export accepts only that frozen UTC shape | `test_temporal_evidence_integrity.py` covers datetime subclasses, mutable/raising/missing timezone providers, future time, overflow, and post-construction non-UTC reinjection | +| Make system-recorded issuance chronology non-caller-controlled | public builder/dataclass signatures exclude `generated_at`; `__post_init__` reads `_system_recorded_at()` and freezes the result to UTC before sealing | `test_system_recorded_issuance_time_is_not_caller_supplied`; `test_system_recorded_timestamp_is_bound_to_canonical_evidence` | +| Preserve deterministic system-recorded chronology despite hostile clock/tz behavior | trusted clock result is resolved once to a built-in UTC instant; future instants, datetime subclasses, missing/raising offsets and normalization overflow fail closed; later canonical export accepts only that frozen UTC shape | `test_temporal_evidence_integrity.py` exercises the internal clock adapter, mutable/raising/missing timezone providers, future time, overflow, and post-construction non-UTC reinjection | | Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; export verifies one snapshot and fails closed after a valid-value rewrite or missing issuance state | `test_issuance_integrity.py`; defense-in-depth only, not durable cross-process authorization/persistence | | Preserve deterministic immutable correlation evidence | issuance-verified canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | +| Canonical documentation discoverability | ADR 0018 is indexed; root README/CHANGELOG identify the capability as active-PR truth | Required before review completion | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | -The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, or prove scientific validity/fairness/compliance. UUIDv4 is an identifier-opacity constraint for packet-owned trust references only; tenant UUID generation/version/privacy policy remains owned by the authoritative HRIS boundary. The process-local issuance registry is deliberately not a distributed attestation store: durable uniqueness, authorization, retention, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host/persistence boundaries. Those claims require their own authoritative evidence and controls. +The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, prove reference opacity, or prove scientific validity/fairness/compliance. UUIDv4 is only a constrained namespaced identifier shape for packet-owned trust references; reference provenance/opacity and tenant UUID generation/version/privacy policy remain owned by authoritative HRIS boundaries. The process-local issuance registry is deliberately not a distributed attestation store: durable uniqueness, authorization, retention, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host/persistence boundaries. Those claims require their own authoritative evidence and controls. From 93dda900570e9877c40934c416e99b74dba4b5d4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:20:32 -0700 Subject: [PATCH 080/101] docs(adr): index performance-review decision --- docs/adr/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/README.md b/docs/adr/README.md index 099a21139..6046af7cd 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -16,3 +16,4 @@ | [0012](0012-governed-migration-handoff.md) | Governed migration handoff | Accepted on active implementation branch | | [0013](0013-governed-requisition-review-packet.md) | Governed requisition review packet | Accepted on active implementation branch | | [0014](0014-job-analysis-snapshot-persistence.md) | Persist governed job-analysis snapshots | Accepted on active implementation branch | +| [0018](0018-governed-performance-review.md) | Governed performance-review evidence packet | Proposed — active PR only | From e43ab2d253acbda472df2a4af3fe76b0e68ef045 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 20:21:27 -0700 Subject: [PATCH 081/101] docs: register active performance-review capability --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47bb087a3..8c0140a02 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,6 @@ Job evidence ## Status -Protected `develop` includes the employment-truth kernel, governed candidate-to-worker conversion, purpose-bound PII authorization, normalized worker-bound validity studies, criterion-observation scope, bitemporal workforce-composition evidence, the governed Naruon intent adapter, and requisition review packets. This active PR adds durable purpose-bound People mutation and confirmed-hire materialization paths for Employment, Position, and Assignment with atomic audit/outbox evidence and tenant-scoped idempotency; treat those write paths as active-PR truth until this exact head passes all fresh protected-base gates and merges. +Protected `develop` remains the source of shipped truth and includes the employment-truth kernel, governed candidate-to-worker conversion, purpose-bound PII authorization, normalized worker-bound validity studies, criterion-observation scope, bitemporal workforce-composition evidence, the governed Naruon intent adapter, and requisition review packets. + +This **active PR only** adds the governed `orgmetra-performance-review` evidence packet described by ADR 0018. It prepares accountable pre-rating human-review evidence without calculating or persisting a rating or making an employment decision. Its issuance time is system-recorded inside the packet boundary rather than caller supplied, and independently supplied UUIDv4-shaped references are conservatively treated as potentially containing direct identifier content until authoritative reference provenance/opacity and Employment/Job scope are resolved. Do not describe this capability as shipped until this exact PR head satisfies fresh protected-base gates and merges. From f75dc8d46e26d0dba8adb3f184a744b540971928 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 14:02:16 +0900 Subject: [PATCH 082/101] docs(performance-review): register active capability --- CHANGELOG.md | 1 + docs/adr/README.md | 1 + manifest.json | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f4752d7..a37a982de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to Orgmetra will be documented in this file. - Accepted ADRs 0001–0003 now include buyer-facing Context, Decision, and Consequences grounded in verified ISO 30400:2022, ISO 30414:2025, Uniform Guidelines (29 C.F.R. Part 1607), SIOP (2018), OpenAPI Specification v3.2.0, OpenID Connect Core 1.0 errata set 2, CloudEvents v1.0.2, Jensen and Snodgrass (1999), Snodgrass (1999), and Allen (1983) records already listed in `docs/doctoring/REFERENCES.md`. ADRs 0004 and 0005 gained APA 7th References pointers to that same bibliography without changing their Decision bodies. - Active-PR governed Job Analysis persistence/API on the canonical `JobAnalysisSnapshot` model: migration `0013_job_analysis_snapshot.sql` stores immutable tenant-scoped snapshot, Task, KSAO, Task–KSAO, FJA and write-command evidence; `POST /v1/tenants/{tenant_record_id}/job-analysis-snapshots` and matching GET enforce purpose-bound Keyverse scope, authenticated-principal actor authority, bounded/strict JSON handling, transactional Idempotency-Key serialization, parent-scope fail-closed integrity, forced RLS, and atomic audit/outbox evidence. ADR 0014 records the persistence decision while ADR 0007 remains the domain/evidence authority; validated evidence still requires accountable human review and non-LLM provenance, and the service does not make a high-impact employment decision. - Active-PR `orgmetra_selection_review` packet for PII-minimized, evidence-bound human selection review: canonical operational tenant identity, UUID-backed opaque candidate/Job/sealed-evidence/reviewer references, explicit purpose/reason/evidence version, deterministic canonical JSON and SHA-256 correlation, mandatory human decision state, redacted packet repr, and provenance-paired model evidence that remains `untrusted_draft`, with exact 100% owned statement and branch coverage required by its quality gate. +- Active-PR `orgmetra-performance-review` packet for value-minimized, pre-rating human-review evidence: system-recorded issuance time, conservative person-reference handling until authoritative provenance and scope resolution, governed performance-cycle/criterion/goal evidence, optional development-plan provenance, issuance integrity sealing, and exact 100% owned statement and branch coverage required by its quality gate. This capability is not protected-branch truth until its current PR passes fresh gates and merges. - Active performance-criterion scope hardening: `criterion_observation_scope_guard` rejects criterion outcomes for a Job the worker did not effectively hold at the observation date, observations before the relevant assignment, and observations outside the referenced performance cycle while preserving valid multiple-assignment cases and existing bitemporal correction semantics. The guard evaluates current-recorded facts, derives the date coordinate from `observed_at` in UTC so session `TimeZone` cannot alter the result, uses a trusted function search path, and adds no PII or automated employment decision authority. The Foundation PostgreSQL contract also rejects a closed `recorded_to` on each time-coordinate lookup and proves UTC midnight plus non-UTC session `TimeZone` boundaries. - Bitemporal tenant-scoped organization hierarchy validation that rejects visible indirect parent cycles and reuses single-valued recorded-time reconstruction before graph traversal. - Stacked governed job-analysis evidence contract via `JobAnalysisSnapshot`, `TaskEvidence`, `KSAORequirement`, `TaskKSAOLink`, `FunctionalJobAnalysisProfile`, and `EvidenceSource`: tenant/Job-scoped observable tasks, explicit Task-to-KSAO linkage, importance/difficulty/proficiency ratings, source/version/retrieval/SHA-256 provenance, deterministic canonical snapshot bytes, current O*NET evidence support, and historical DOT Data/People/Things compatibility. Validated snapshots require accountable human review and complete non-LLM evidence; LLM-origin material remains `analysis_draft`, and the snapshot is evidence input rather than a hiring, promotion, termination, compensation, or other high-impact employment decision. diff --git a/docs/adr/README.md b/docs/adr/README.md index 6046af7cd..f4ee16eb0 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -17,3 +17,4 @@ | [0013](0013-governed-requisition-review-packet.md) | Governed requisition review packet | Accepted on active implementation branch | | [0014](0014-job-analysis-snapshot-persistence.md) | Persist governed job-analysis snapshots | Accepted on active implementation branch | | [0018](0018-governed-performance-review.md) | Governed performance-review evidence packet | Proposed — active PR only | +| [0018](0018-governed-performance-review.md) | Governed performance-review evidence packet | Proposed — active PR only | diff --git a/manifest.json b/manifest.json index 97f2bab14..bfe20ffe6 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"df558724edae8575b6cb0a026abfac857758460ea0b1a7370025b28e4d2c9cb5","bytes":17819,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"19582a342f13f44293581846d426ba6685618467bfdc2f43c993b95c08e86d61","bytes":4180,"lines":83},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"d267689ca8c9f04baf43235bf00b43a7a605657666cc2e8f19d87cb2ffc205f3","bytes":2086,"lines":20},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} From e011579f7191f41b500f017314c5ce6283e7d4e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 14:16:55 +0900 Subject: [PATCH 083/101] docs: remove duplicate performance ADR entry --- docs/adr/README.md | 1 - manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index f4ee16eb0..6046af7cd 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -17,4 +17,3 @@ | [0013](0013-governed-requisition-review-packet.md) | Governed requisition review packet | Accepted on active implementation branch | | [0014](0014-job-analysis-snapshot-persistence.md) | Persist governed job-analysis snapshots | Accepted on active implementation branch | | [0018](0018-governed-performance-review.md) | Governed performance-review evidence packet | Proposed — active PR only | -| [0018](0018-governed-performance-review.md) | Governed performance-review evidence packet | Proposed — active PR only | diff --git a/manifest.json b/manifest.json index bfe20ffe6..49a3e37fa 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"df558724edae8575b6cb0a026abfac857758460ea0b1a7370025b28e4d2c9cb5","bytes":17819,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"19582a342f13f44293581846d426ba6685618467bfdc2f43c993b95c08e86d61","bytes":4180,"lines":83},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"d267689ca8c9f04baf43235bf00b43a7a605657666cc2e8f19d87cb2ffc205f3","bytes":2086,"lines":20},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"df558724edae8575b6cb0a026abfac857758460ea0b1a7370025b28e4d2c9cb5","bytes":17819,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"19582a342f13f44293581846d426ba6685618467bfdc2f43c993b95c08e86d61","bytes":4180,"lines":83},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"445aa6d59495b0387e189c7e4ea992e8f871052130fff99e191bcf1db02520aa","bytes":1962,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} From 482bd185958f8328aa42b13a603a0235ada5dfb4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:31:55 -0700 Subject: [PATCH 084/101] test(performance-review): require free-form feedback exclusion --- .../tests/test_feedback_privacy.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/performance-review/tests/test_feedback_privacy.py diff --git a/packages/performance-review/tests/test_feedback_privacy.py b/packages/performance-review/tests/test_feedback_privacy.py new file mode 100644 index 000000000..c0156220b --- /dev/null +++ b/packages/performance-review/tests/test_feedback_privacy.py @@ -0,0 +1,19 @@ +"""Regression coverage for free-form feedback exclusion from review evidence.""" + +from dataclasses import replace +import json + +import pytest + +from test_packet import build_valid + + +def test_packet_explicitly_excludes_free_form_feedback() -> None: + """Keep free-form human feedback outside the immutable correlation envelope.""" + packet = build_valid() + + assert packet.contains_free_form_feedback is False + assert json.loads(packet.canonical_json())["contains_free_form_feedback"] is False + + with pytest.raises(ValueError, match="free-form feedback"): + replace(packet, contains_free_form_feedback=True) From 66178313ebee02a9ec850405a3c4133bd7347efd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:32:37 -0700 Subject: [PATCH 085/101] fix(performance-review): make feedback exclusion explicit --- .../src/orgmetra_performance_review/packet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 65ae953cd..83d496757 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -182,6 +182,7 @@ class PerformanceReviewPacket: contains_personal_data: bool = True contains_direct_person_identifiers: bool = True contains_rating_value: bool = False + contains_free_form_feedback: bool = False contains_free_form_model_output: bool = False human_confirmation_required: bool = True decision_authority: str = _DECISION_AUTHORITY @@ -253,6 +254,8 @@ def __post_init__(self) -> None: ) if self.contains_rating_value is not False: raise ValueError("performance review packet must not contain rating values") + if self.contains_free_form_feedback is not False: + raise ValueError("performance review packet must not contain free-form feedback") if self.contains_free_form_model_output is not False: raise ValueError("performance review packet must not contain free-form model output") if self.human_confirmation_required is not True: @@ -288,6 +291,7 @@ def _canonical_packet_json_unchecked(packet: PerformanceReviewPacket) -> str: """Render canonical bytes without consulting process-local issuance state.""" payload = { "contains_direct_person_identifiers": packet.contains_direct_person_identifiers, + "contains_free_form_feedback": packet.contains_free_form_feedback, "contains_free_form_model_output": packet.contains_free_form_model_output, "contains_personal_data": packet.contains_personal_data, "contains_rating_value": packet.contains_rating_value, From 58086e2451c1065a5de792362d7218b18b7783a4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:32:56 -0700 Subject: [PATCH 086/101] docs(traceability): bind feedback exclusion contract --- docs/traceability/performance-review.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index fcd0d6b91..9ceedcacb 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -13,7 +13,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Preserve optional development provenance | paired `development_plan_reference`/digest | Implemented on active PR | | Reject caller-defined digest string subclasses at every evidence boundary | `_validate_digest` requires exact built-in `str`; `test_rejects_digest_string_subclass_at_all_digest_boundaries` covers criteria, goal-plan, criterion-observation, and development-plan digests | Implemented on active PR | | Avoid unsupported no-direct-identifier claims for independently supplied references | `contains_personal_data=True`, `contains_direct_person_identifiers=True`; UUIDv4-shaped encoded-person regression | Implemented conservatively until authoritative issuer/resolver provenance is verified | -| Exclude rating values and free-form feedback/model output | immutable false value/model flags plus absence of rating/narrative fields | Implemented on active PR | +| Exclude rating values and free-form feedback/model output | immutable `contains_rating_value=False`, `contains_free_form_feedback=False`, and `contains_free_form_model_output=False`; no rating/narrative fields; `test_feedback_privacy.py` proves the feedback flag is canonicalized and cannot be weakened by dataclass replacement | Implemented on active PR | | Require accountable human review | fixed `human_confirmation_required=True`, `decision_authority=human_review_only`, `review_state=requires_human_review` | Implemented on active PR | | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | | Make system-recorded issuance chronology non-caller-controlled | public builder/dataclass signatures exclude `generated_at`; `__post_init__` reads `_system_recorded_at()` and freezes the result to UTC before sealing | `test_system_recorded_issuance_time_is_not_caller_supplied`; `test_system_recorded_timestamp_is_bound_to_canonical_evidence` | @@ -24,4 +24,4 @@ Status: **active PR / proposed capability**, not protected-main truth. | Canonical documentation discoverability | ADR 0018 is indexed; root README/CHANGELOG identify the capability as active-PR truth | Required before review completion | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | -The packet does not persist or calculate a rating, decide compensation, infer performance, prove cross-record scope consistency, prove reference opacity, or prove scientific validity/fairness/compliance. UUIDv4 is only a constrained namespaced identifier shape for packet-owned trust references; reference provenance/opacity and tenant UUID generation/version/privacy policy remain owned by authoritative HRIS boundaries. The process-local issuance registry is deliberately not a distributed attestation store: durable uniqueness, authorization, retention, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host/persistence boundaries. Those claims require their own authoritative evidence and controls. +The packet does not persist or calculate a rating, carry free-form feedback, decide compensation, infer performance, prove cross-record scope consistency, prove reference opacity, or prove scientific validity/fairness/compliance. UUIDv4 is only a constrained namespaced identifier shape for packet-owned trust references; reference provenance/opacity and tenant UUID generation/version/privacy policy remain owned by authoritative HRIS boundaries. The process-local issuance registry is deliberately not a distributed attestation store: durable uniqueness, authorization, retention, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host/persistence boundaries. Those claims require their own authoritative evidence and controls. From ed5ed70a8fbde76f6cefbefb2ebd3b5d9788e37d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:33:06 -0700 Subject: [PATCH 087/101] docs(performance-review): record feedback privacy invariant --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 930e1a103..c3c0d7774 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -8,4 +8,5 @@ - Require exact built-in `str` values for every SHA-256 evidence digest before pattern validation and canonical binding, matching the strict runtime contract used by the other trust-bearing text fields. - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. - Make `generated_at` system-owned rather than caller-supplied: packet construction reads the trusted host clock, freezes it to a detached built-in UTC instant, rejects future/invalid clock results, and prevents callers from falsifying audit chronology by submitting arbitrary historical issuance times. +- Make the existing free-form-feedback exclusion machine-verifiable with immutable `contains_free_form_feedback=False` canonical evidence and fail-closed replacement validation, alongside the existing no-rating-value and no-free-form-model-output controls. - Bind each live issued performance-review packet to its exact construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites or when process-local issuance evidence is unavailable; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. From f2f788f3fa15ba45806b2acd2a4f3850e67dbfa9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:33:33 -0700 Subject: [PATCH 088/101] docs(adr): make feedback exclusion machine-verifiable --- docs/adr/0018-governed-performance-review.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index b94165c61..a225a3c87 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -34,7 +34,7 @@ The initial closed reason vocabulary contains only `scheduled_cycle_review`. Arb `evidence_version` accepts only real integers from `1` through `2147483647`; booleans, text, zero, negative values, and overflow values fail closed. The field versions the immutable review evidence envelope and does not itself prove source-version resolution, human approval, or rating completion. -Because this package cannot prove independently supplied reference provenance, `contains_personal_data` and `contains_direct_person_identifiers` are both fixed to `True`. The latter is deliberately conservative: it means the envelope must be handled as potentially containing direct identifier content until an authoritative issuer/resolver verifies opacity. The packet MUST NOT carry a rating value, free-form feedback, or free-form model output. Direct construction and mutation-by-copy MUST fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. +Because this package cannot prove independently supplied reference provenance, `contains_personal_data` and `contains_direct_person_identifiers` are both fixed to `True`. The latter is deliberately conservative: it means the envelope must be handled as potentially containing direct identifier content until an authoritative issuer/resolver verifies opacity. The packet MUST NOT carry a rating value, free-form feedback, or free-form model output. Those exclusions are machine-verifiable canonical evidence through fixed `contains_rating_value=False`, `contains_free_form_feedback=False`, and `contains_free_form_model_output=False`; mutation-by-copy that attempts to weaken any exclusion fails closed. Direct construction and mutation-by-copy MUST also fail closed unless `human_confirmation_required=True`, `decision_authority="human_review_only"`, `review_state="requires_human_review"`, and `scope_verification_state="requires_authoritative_resolution"` remain intact. `scope_verification_state` deliberately cannot be changed to `verified` inside this package. Before rating, the authoritative HRIS/performance boundary must verify reference provenance and opacity, then resolve the Person↔Employment↔Job relation, performance-cycle/review-period alignment, and governed evidence scope using current temporal truth and purpose-bound authorization. UUIDv4 shape alone is not provenance evidence; tenant UUID generation/version/privacy policy likewise remains owned by the authoritative HRIS boundary. @@ -44,11 +44,11 @@ Canonical JSON and SHA-256 are immutable correlation evidence only. They do not ## Consequences -Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle or that UUIDv4-shaped values are opaque. Until authoritative provenance verification occurs, the packet receives the more restrictive identifier-risk classification rather than a false no-direct-identifier assertion. +Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. The envelope itself now carries explicit canonical proof that rating values, free-form feedback, and free-form model output are excluded from this pre-rating evidence boundary. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle or that UUIDv4-shaped values are opaque. Until authoritative provenance verification occurs, the packet receives the more restrictive identifier-risk classification rather than a false no-direct-identifier assertion. The system-recorded timestamp can no longer be backdated through public packet construction. Hosts still own purpose-bound authorization, durable immutable audit/outbox, retention/export controls, and the authoritative clock/runtime environment. -This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet preserves actor, purpose, reviewed reason, evidence version, conservative identifier-risk classification, and system-recorded issuance time in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope/provenance resolution evidence, and any applicable policy requirements. +This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet preserves actor, purpose, reviewed reason, evidence version, conservative identifier-risk classification, system-recorded issuance time, and explicit content-exclusion flags in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope/provenance resolution evidence, and any applicable policy requirements. ## References From 7a0e328929219dab59e696ce16389dd588067f1f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:36:10 -0700 Subject: [PATCH 089/101] test(performance-review): exclude fixed feedback invariant from builder fixture --- .../performance-review/tests/test_tenant_identity_privacy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/tests/test_tenant_identity_privacy.py b/packages/performance-review/tests/test_tenant_identity_privacy.py index d4ca56d99..96494d6a8 100644 --- a/packages/performance-review/tests/test_tenant_identity_privacy.py +++ b/packages/performance-review/tests/test_tenant_identity_privacy.py @@ -46,6 +46,7 @@ def test_authoritative_uuid7_tenant_identity_is_accepted_by_builder_and_replace( "contains_personal_data", "contains_direct_person_identifiers", "contains_rating_value", + "contains_free_form_feedback", "contains_free_form_model_output", "human_confirmation_required", "decision_authority", From 1aee9c2db062e1d63f020efef472d04d42c1f789 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:31:09 -0700 Subject: [PATCH 090/101] test(performance-review): require shared config quality triggers --- .../tests/test_quality_workflow_trigger.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/performance-review/tests/test_quality_workflow_trigger.py diff --git a/packages/performance-review/tests/test_quality_workflow_trigger.py b/packages/performance-review/tests/test_quality_workflow_trigger.py new file mode 100644 index 000000000..9df32f885 --- /dev/null +++ b/packages/performance-review/tests/test_quality_workflow_trigger.py @@ -0,0 +1,27 @@ +"""Regression tests for the performance-review quality-gate trigger surface.""" + +from pathlib import Path + + +_WORKFLOW_PATH = Path(".github/workflows/performance-review-quality.yml") +_SHARED_TEST_CONFIGURATION = ( + ".gitignore", + ".python-version", + "conftest.py", + "packages/conftest.py", + "pyproject.toml", + "pytest.ini", + "setup.cfg", + "tox.ini", +) + + +def test_quality_workflow_retriggers_on_shared_test_configuration() -> None: + """Require every shared test/runtime configuration input to retrigger this gate.""" + workflow = _WORKFLOW_PATH.read_text(encoding="utf-8") + + for path in _SHARED_TEST_CONFIGURATION: + assert f'- "{path}"' in workflow, ( + f"{path} can change package test or clean-checkout behavior and must retrigger " + "Performance Review Quality" + ) From 70123cae14d3b8750dbc1d17909dbbbbc102219f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:33:04 -0700 Subject: [PATCH 091/101] fix(performance-review): retrigger quality on shared config --- .github/workflows/performance-review-quality.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/performance-review-quality.yml b/.github/workflows/performance-review-quality.yml index 5ea88b260..7f1e4e4eb 100644 --- a/.github/workflows/performance-review-quality.yml +++ b/.github/workflows/performance-review-quality.yml @@ -8,6 +8,14 @@ on: - "packages/performance-review/**" - ".github/requirements/foundation-test.txt" - ".github/workflows/performance-review-quality.yml" + - ".gitignore" + - ".python-version" + - "conftest.py" + - "packages/conftest.py" + - "pyproject.toml" + - "pytest.ini" + - "setup.cfg" + - "tox.ini" - "docs/adr/0018-governed-performance-review.md" - "docs/doctoring/performance-review-references.md" - "docs/traceability/performance-review.md" From d3189419c4b336c09105aec9fb97292473fcb003 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:34:19 -0700 Subject: [PATCH 092/101] docs(performance-review): record shared-config gate integrity --- packages/performance-review/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index c3c0d7774..9bdb79bd7 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -10,3 +10,4 @@ - Make `generated_at` system-owned rather than caller-supplied: packet construction reads the trusted host clock, freezes it to a detached built-in UTC instant, rejects future/invalid clock results, and prevents callers from falsifying audit chronology by submitting arbitrary historical issuance times. - Make the existing free-form-feedback exclusion machine-verifiable with immutable `contains_free_form_feedback=False` canonical evidence and fail-closed replacement validation, alongside the existing no-rating-value and no-free-form-model-output controls. - Bind each live issued performance-review packet to its exact construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites or when process-local issuance evidence is unavailable; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. +- Make `Performance Review Quality` retrigger on shared repository Python/test/clean-checkout configuration and enforce that trigger surface with an executable regression so package-quality evidence cannot remain stale after shared tooling changes. From 34b7777dd125af7e8d9647c4f2be8e70f6485c57 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:34:31 -0700 Subject: [PATCH 093/101] docs(performance-review): trace shared-config quality evidence --- docs/traceability/performance-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index 9ceedcacb..a736b68dc 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -21,6 +21,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; export verifies one snapshot and fails closed after a valid-value rewrite or missing issuance state | `test_issuance_integrity.py`; defense-in-depth only, not durable cross-process authorization/persistence | | Preserve deterministic immutable correlation evidence | issuance-verified canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | +| Keep package-quality evidence current after shared repository test/runtime configuration changes | `test_quality_workflow_retriggers_on_shared_test_configuration` requires the package workflow to retrigger on shared Python/test/clean-checkout configuration as well as package-owned paths | Implemented on active PR; supplements rather than replaces central required workflows | | Canonical documentation discoverability | ADR 0018 is indexed; root README/CHANGELOG identify the capability as active-PR truth | Required before review completion | | Standards/research basis | ADR 0018; `docs/doctoring/performance-review-references.md` | Documented on active PR | From ba93d03325562763ebbd90c91527cad28cb46de8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:05:34 -0700 Subject: [PATCH 094/101] test(performance-review): prevent issuance reseal --- .../tests/test_issuance_integrity.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/performance-review/tests/test_issuance_integrity.py b/packages/performance-review/tests/test_issuance_integrity.py index 41704788e..01543f58d 100644 --- a/packages/performance-review/tests/test_issuance_integrity.py +++ b/packages/performance-review/tests/test_issuance_integrity.py @@ -56,3 +56,17 @@ def test_missing_process_local_issuance_evidence_fails_closed() -> None: with pytest.raises(ValueError, match="issuance evidence is unavailable"): packet.canonical_json() + + +def test_reinitialization_cannot_renew_issuance_evidence_after_valid_value_rewrite() -> None: + """Keep one live packet identity bound to its original construction evidence.""" + packet = _build_packet() + original = packet.canonical_json() + + object.__setattr__(packet, "goal_plan_digest", "f" * 64) + + with pytest.raises(ValueError, match="issuance evidence already exists"): + packet.__post_init__() + with pytest.raises(ValueError, match="changed after issuance"): + packet.canonical_json() + assert original != packet_module._canonical_packet_json_unchecked(packet) From 61ba3ec555b28d0560b798a9046905d99541c046 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:09:07 -0700 Subject: [PATCH 095/101] fix(performance-review): make issuance registration single-use --- .../src/orgmetra_performance_review/packet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 83d496757..725acfeab 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -50,9 +50,11 @@ def _discard_packet_seal(packet_id: int) -> None: def _register_packet_seal(packet: object, seal: str) -> None: - """Bind one live review-packet identity to evidence outside writable slots.""" + """Bind one live review-packet identity exactly once outside writable slots.""" packet_id = id(packet) with _PACKET_SEALS_LOCK: + if packet_id in _PACKET_SEALS: + raise ValueError("performance review issuance evidence already exists") _PACKET_SEALS[packet_id] = seal finalize(packet, _discard_packet_seal, packet_id) From 21cd2de2945ae971c80d6d4a74f2593b714f4139 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:09:42 -0700 Subject: [PATCH 096/101] docs(performance-review): document single-use issuance --- packages/performance-review/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 82d0320d3..5b9c7715d 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -10,7 +10,7 @@ Every packet carries a bounded positive integer `evidence_version` (default `1`) `generated_at` is system-recorded issuance evidence and is **not a caller argument**. Construction reads the host clock inside the packet boundary, resolves a concrete timezone offset once, converts the instant to a built-in UTC `datetime`, rejects future instants, and stores only that detached UTC value. Canonical evidence export therefore does not re-enter caller input or a mutable `tzinfo`; invalid clock adapters, missing offsets, provider exceptions, UTC-normalization overflow, datetime subclasses, and post-construction reinjection of a non-UTC timestamp fail closed before evidence emission. Tests replace the internal clock adapter only to make exact canonical bytes deterministic; production callers cannot supply `generated_at`. -A frozen dataclass alone is not an issuance seal because low-level Python mutation can still rewrite otherwise valid fields. Each live issued packet is therefore bound to its exact construction-time canonical JSON by a process-local HMAC seal stored outside packet-writable slots. `canonical_json()` snapshots the current canonical bytes once, verifies that exact snapshot against the external issuance seal, and returns the verified bytes without rereading packet fields. A valid-value rewrite after issuance, or missing process-local issuance evidence, fails closed. This is in-process defense-in-depth only; durable cross-process uniqueness, purpose authorization, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host or persistence boundaries. +A frozen dataclass alone is not an issuance seal because low-level Python mutation can still rewrite otherwise valid fields. Each live issued packet is therefore bound exactly once to its construction-time canonical JSON by a process-local HMAC seal stored outside packet-writable slots. A second seal registration for the same live object is rejected, so calling `__post_init__()` after a valid-value rewrite cannot renew issuance evidence or bless a second canonical truth. `canonical_json()` snapshots the current canonical bytes once, verifies that exact snapshot against the external issuance seal, and returns the verified bytes without rereading packet fields. A valid-value rewrite, attempted reinitialization/reseal, or missing process-local issuance evidence fails closed. This is in-process defense-in-depth only; durable cross-process uniqueness, purpose authorization, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host or persistence boundaries. ## What this packet does not do From 42f3cfe1f5f00b95e29023e74a4566514157d75d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:10:02 -0700 Subject: [PATCH 097/101] docs(traceability): bind performance review issuance once --- docs/traceability/performance-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index a736b68dc..752fa1ebf 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -18,7 +18,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | | Make system-recorded issuance chronology non-caller-controlled | public builder/dataclass signatures exclude `generated_at`; `__post_init__` reads `_system_recorded_at()` and freezes the result to UTC before sealing | `test_system_recorded_issuance_time_is_not_caller_supplied`; `test_system_recorded_timestamp_is_bound_to_canonical_evidence` | | Preserve deterministic system-recorded chronology despite hostile clock/tz behavior | trusted clock result is resolved once to a built-in UTC instant; future instants, datetime subclasses, missing/raising offsets and normalization overflow fail closed; later canonical export accepts only that frozen UTC shape | `test_temporal_evidence_integrity.py` exercises the internal clock adapter, mutable/raising/missing timezone providers, future time, overflow, and post-construction non-UTC reinjection | -| Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; export verifies one snapshot and fails closed after a valid-value rewrite or missing issuance state | `test_issuance_integrity.py`; defense-in-depth only, not durable cross-process authorization/persistence | +| Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; registration is single-use per live packet identity, so valid-value mutation followed by direct `__post_init__()` cannot renew the seal; export also fails closed after mutation or missing issuance state | `test_issuance_integrity.py`, including `test_reinitialization_cannot_renew_issuance_evidence_after_valid_value_rewrite`; defense-in-depth only, not durable cross-process authorization/persistence | | Preserve deterministic immutable correlation evidence | issuance-verified canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Keep package-quality evidence current after shared repository test/runtime configuration changes | `test_quality_workflow_retriggers_on_shared_test_configuration` requires the package workflow to retrigger on shared Python/test/clean-checkout configuration as well as package-owned paths | Implemented on active PR; supplements rather than replaces central required workflows | From ce64f9360d6bf0165917b3b1d0b2099d1dcd0a67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:10:29 -0700 Subject: [PATCH 098/101] docs(adr): require single-use performance review issuance --- docs/adr/0018-governed-performance-review.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index a225a3c87..7cb11294e 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -40,13 +40,15 @@ Because this package cannot prove independently supplied reference provenance, ` `generated_at` is constructed from a trusted internal clock adapter. The resulting exact built-in timezone-aware `datetime` is normalized once to UTC and then sealed; future instants, missing/raising offsets, normalization overflow, datetime subclasses, or post-construction non-UTC reinjection fail closed. Tests may replace the internal clock adapter to make canonical bytes deterministic, but production callers cannot provide the issuance timestamp. +The process-local HMAC issuance seal is registered exactly once for each live packet identity. Re-entering `__post_init__()` on the same object after a valid-value rewrite MUST fail before a replacement seal can be installed, so validation re-entry cannot turn mutated runtime state into a second issued canonical truth. Canonical export continues to compare the current deterministic bytes against the original external seal. This registry is in-process mutation defense only and is not a portable signature, distributed uniqueness service, authorization record, or durable audit/outbox substitute. + Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, reference provenance, or the final rating. ## Consequences Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. The envelope itself now carries explicit canonical proof that rating values, free-form feedback, and free-form model output are excluded from this pre-rating evidence boundary. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle or that UUIDv4-shaped values are opaque. Until authoritative provenance verification occurs, the packet receives the more restrictive identifier-risk classification rather than a false no-direct-identifier assertion. -The system-recorded timestamp can no longer be backdated through public packet construction. Hosts still own purpose-bound authorization, durable immutable audit/outbox, retention/export controls, and the authoritative clock/runtime environment. +The system-recorded timestamp can no longer be backdated through public packet construction, and one live packet cannot renew its process-local issuance seal after mutation. Hosts still own purpose-bound authorization, durable immutable audit/outbox, retention/export controls, and the authoritative clock/runtime environment. This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet preserves actor, purpose, reviewed reason, evidence version, conservative identifier-risk classification, system-recorded issuance time, and explicit content-exclusion flags in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope/provenance resolution evidence, and any applicable policy requirements. From c5ad805371406484c992b29c809f32cb0b2a0039 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 21:10:44 -0700 Subject: [PATCH 099/101] chore(performance-review): record single-use issuance repair --- packages/performance-review/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index 9bdb79bd7..c67969130 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -9,5 +9,5 @@ - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. - Make `generated_at` system-owned rather than caller-supplied: packet construction reads the trusted host clock, freezes it to a detached built-in UTC instant, rejects future/invalid clock results, and prevents callers from falsifying audit chronology by submitting arbitrary historical issuance times. - Make the existing free-form-feedback exclusion machine-verifiable with immutable `contains_free_form_feedback=False` canonical evidence and fail-closed replacement validation, alongside the existing no-rating-value and no-free-form-model-output controls. -- Bind each live issued performance-review packet to its exact construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites or when process-local issuance evidence is unavailable; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. +- Bind each live issued performance-review packet exactly once to its construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites, direct reinitialization cannot renew the seal for the same live identity, and missing process-local issuance evidence also fails closed; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. - Make `Performance Review Quality` retrigger on shared repository Python/test/clean-checkout configuration and enforce that trigger surface with an executable regression so package-quality evidence cannot remain stale after shared tooling changes. From 721f13e7d432c1c96fb4aadd88226f1038891769 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 00:32:08 -0700 Subject: [PATCH 100/101] test(performance-review): reproduce seal-loss reissuance --- .../tests/test_issuance_integrity.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/performance-review/tests/test_issuance_integrity.py b/packages/performance-review/tests/test_issuance_integrity.py index 01543f58d..0b50c57ef 100644 --- a/packages/performance-review/tests/test_issuance_integrity.py +++ b/packages/performance-review/tests/test_issuance_integrity.py @@ -58,6 +58,21 @@ def test_missing_process_local_issuance_evidence_fails_closed() -> None: packet.canonical_json() +def test_seal_loss_cannot_reset_live_issuance_lifecycle() -> None: + """Keep a live packet single-issued even after its process-local seal is lost.""" + packet = _build_packet() + original = packet.canonical_json() + + packet_module._discard_packet_seal(id(packet)) + object.__setattr__(packet, "goal_plan_digest", "f" * 64) + + with pytest.raises(ValueError, match="issuance evidence already exists"): + packet.__post_init__() + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + packet.canonical_json() + assert original != packet_module._canonical_packet_json_unchecked(packet) + + def test_reinitialization_cannot_renew_issuance_evidence_after_valid_value_rewrite() -> None: """Keep one live packet identity bound to its original construction evidence.""" packet = _build_packet() From dbe465daad3c4ec60a2328046d09a93b1b9e67ec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 00:35:15 -0700 Subject: [PATCH 101/101] fix(performance-review): preserve issuance lifecycle after seal loss --- docs/adr/0018-governed-performance-review.md | 4 ++-- docs/traceability/performance-review.md | 2 +- packages/performance-review/CHANGELOG.md | 2 +- packages/performance-review/README.md | 2 +- .../src/orgmetra_performance_review/packet.py | 8 +++++--- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/adr/0018-governed-performance-review.md b/docs/adr/0018-governed-performance-review.md index 7cb11294e..95747604a 100644 --- a/docs/adr/0018-governed-performance-review.md +++ b/docs/adr/0018-governed-performance-review.md @@ -40,7 +40,7 @@ Because this package cannot prove independently supplied reference provenance, ` `generated_at` is constructed from a trusted internal clock adapter. The resulting exact built-in timezone-aware `datetime` is normalized once to UTC and then sealed; future instants, missing/raising offsets, normalization overflow, datetime subclasses, or post-construction non-UTC reinjection fail closed. Tests may replace the internal clock adapter to make canonical bytes deterministic, but production callers cannot provide the issuance timestamp. -The process-local HMAC issuance seal is registered exactly once for each live packet identity. Re-entering `__post_init__()` on the same object after a valid-value rewrite MUST fail before a replacement seal can be installed, so validation re-entry cannot turn mutated runtime state into a second issued canonical truth. Canonical export continues to compare the current deterministic bytes against the original external seal. This registry is in-process mutation defense only and is not a portable signature, distributed uniqueness service, authorization record, or durable audit/outbox substitute. +The process-local HMAC issuance seal is registered exactly once for each live packet identity. A separate weak live-issued-identity registry persists for the lifetime of the packet even if current seal bytes are discarded, so seal loss is a fail-closed export condition rather than permission to issue again. Re-entering `__post_init__()` on the same live object after seal loss or after a valid-value rewrite MUST fail before replacement evidence can be installed. Canonical export continues to compare the current deterministic bytes against the original external seal when that seal exists. This registry is in-process mutation defense only and is not a portable signature, distributed uniqueness service, authorization record, or durable audit/outbox substitute. Canonical JSON and SHA-256 are immutable correlation evidence only. They do not prove the correctness of source evidence, authoritative cross-record scope, substantive validity or fairness of a criterion, lawful use, human completion, reference provenance, or the final rating. @@ -48,7 +48,7 @@ Canonical JSON and SHA-256 are immutable correlation evidence only. They do not Buyers can present a review-ready correlation envelope while keeping authoritative Employment/Job and performance evidence separable from the later human rating/feedback event. The envelope itself now carries explicit canonical proof that rating values, free-form feedback, and free-form model output are excluded from this pre-rating evidence boundary. A consumer cannot truthfully treat the packet itself as proof that all referenced records belong to the same employee/job/cycle or that UUIDv4-shaped values are opaque. Until authoritative provenance verification occurs, the packet receives the more restrictive identifier-risk classification rather than a false no-direct-identifier assertion. -The system-recorded timestamp can no longer be backdated through public packet construction, and one live packet cannot renew its process-local issuance seal after mutation. Hosts still own purpose-bound authorization, durable immutable audit/outbox, retention/export controls, and the authoritative clock/runtime environment. +The system-recorded timestamp can no longer be backdated through public packet construction, and one live packet cannot renew its process-local issuance evidence after mutation or after losing its current seal bytes. Hosts still own purpose-bound authorization, durable immutable audit/outbox, retention/export controls, and the authoritative clock/runtime environment. This slice adds no database migration, no rating computation, no cross-service table access, and no automated employment decision. The pre-rating packet preserves actor, purpose, reviewed reason, evidence version, conservative identifier-risk classification, system-recorded issuance time, and explicit content-exclusion flags in its immutable correlation evidence; later authoritative rating persistence must independently preserve those values plus human confirmation, audit/outbox, temporal scope, authoritative scope/provenance resolution evidence, and any applicable policy requirements. diff --git a/docs/traceability/performance-review.md b/docs/traceability/performance-review.md index 752fa1ebf..f16c4cfef 100644 --- a/docs/traceability/performance-review.md +++ b/docs/traceability/performance-review.md @@ -18,7 +18,7 @@ Status: **active PR / proposed capability**, not protected-main truth. | Version high-impact review evidence | bounded positive `evidence_version` is validated, serialized in canonical JSON, and changes SHA-256 correlation evidence | Implemented on active PR | | Make system-recorded issuance chronology non-caller-controlled | public builder/dataclass signatures exclude `generated_at`; `__post_init__` reads `_system_recorded_at()` and freezes the result to UTC before sealing | `test_system_recorded_issuance_time_is_not_caller_supplied`; `test_system_recorded_timestamp_is_bound_to_canonical_evidence` | | Preserve deterministic system-recorded chronology despite hostile clock/tz behavior | trusted clock result is resolved once to a built-in UTC instant; future instants, datetime subclasses, missing/raising offsets and normalization overflow fail closed; later canonical export accepts only that frozen UTC shape | `test_temporal_evidence_integrity.py` exercises the internal clock adapter, mutable/raising/missing timezone providers, future time, overflow, and post-construction non-UTC reinjection | -| Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; registration is single-use per live packet identity, so valid-value mutation followed by direct `__post_init__()` cannot renew the seal; export also fails closed after mutation or missing issuance state | `test_issuance_integrity.py`, including `test_reinitialization_cannot_renew_issuance_evidence_after_valid_value_rewrite`; defense-in-depth only, not durable cross-process authorization/persistence | +| Prevent a second canonical truth after issuance | Process-local HMAC issuance evidence is stored outside packet-writable slots over exact construction-time canonical JSON; a separate weak live-issued-identity registry makes issuance single-use for the live object's lifetime even if seal bytes are discarded, so valid-value mutation plus direct `__post_init__()` cannot renew evidence; export fails closed after mutation or missing seal state | `test_issuance_integrity.py`, including `test_reinitialization_cannot_renew_issuance_evidence_after_valid_value_rewrite` and `test_seal_loss_cannot_reset_live_issuance_lifecycle`; defense-in-depth only, not durable cross-process authorization/persistence | | Preserve deterministic immutable correlation evidence | issuance-verified canonical JSON plus SHA-256 | Implemented on active PR | | Exact 100% owned statement/branch coverage | `packages/performance-review/pyproject.toml`, `.github/workflows/performance-review-quality.yml` | Required on exact PR head | | Keep package-quality evidence current after shared repository test/runtime configuration changes | `test_quality_workflow_retriggers_on_shared_test_configuration` requires the package workflow to retrigger on shared Python/test/clean-checkout configuration as well as package-owned paths | Implemented on active PR; supplements rather than replaces central required workflows | diff --git a/packages/performance-review/CHANGELOG.md b/packages/performance-review/CHANGELOG.md index c67969130..d0f45c439 100644 --- a/packages/performance-review/CHANGELOG.md +++ b/packages/performance-review/CHANGELOG.md @@ -9,5 +9,5 @@ - Bind a bounded positive `evidence_version` into canonical JSON and SHA-256 correlation evidence so high-impact review evidence versions are explicit and fail closed on invalid values. - Make `generated_at` system-owned rather than caller-supplied: packet construction reads the trusted host clock, freezes it to a detached built-in UTC instant, rejects future/invalid clock results, and prevents callers from falsifying audit chronology by submitting arbitrary historical issuance times. - Make the existing free-form-feedback exclusion machine-verifiable with immutable `contains_free_form_feedback=False` canonical evidence and fail-closed replacement validation, alongside the existing no-rating-value and no-free-form-model-output controls. -- Bind each live issued performance-review packet exactly once to its construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots. Canonical export fails closed after valid-value post-issuance rewrites, direct reinitialization cannot renew the seal for the same live identity, and missing process-local issuance evidence also fails closed; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. +- Bind each live issued performance-review packet exactly once to its construction-time canonical bytes with a process-local HMAC seal stored outside packet-writable slots plus a weak live-issued-identity registry. Canonical export fails closed after valid-value rewrites or missing seal bytes, while seal loss does not reset the live issuance lifecycle and direct reinitialization cannot mint replacement evidence; durable uniqueness, authorization, and immutable audit/outbox remain authoritative host/persistence responsibilities. - Make `Performance Review Quality` retrigger on shared repository Python/test/clean-checkout configuration and enforce that trigger surface with an executable regression so package-quality evidence cannot remain stale after shared tooling changes. diff --git a/packages/performance-review/README.md b/packages/performance-review/README.md index 5b9c7715d..edfb540a7 100644 --- a/packages/performance-review/README.md +++ b/packages/performance-review/README.md @@ -10,7 +10,7 @@ Every packet carries a bounded positive integer `evidence_version` (default `1`) `generated_at` is system-recorded issuance evidence and is **not a caller argument**. Construction reads the host clock inside the packet boundary, resolves a concrete timezone offset once, converts the instant to a built-in UTC `datetime`, rejects future instants, and stores only that detached UTC value. Canonical evidence export therefore does not re-enter caller input or a mutable `tzinfo`; invalid clock adapters, missing offsets, provider exceptions, UTC-normalization overflow, datetime subclasses, and post-construction reinjection of a non-UTC timestamp fail closed before evidence emission. Tests replace the internal clock adapter only to make exact canonical bytes deterministic; production callers cannot supply `generated_at`. -A frozen dataclass alone is not an issuance seal because low-level Python mutation can still rewrite otherwise valid fields. Each live issued packet is therefore bound exactly once to its construction-time canonical JSON by a process-local HMAC seal stored outside packet-writable slots. A second seal registration for the same live object is rejected, so calling `__post_init__()` after a valid-value rewrite cannot renew issuance evidence or bless a second canonical truth. `canonical_json()` snapshots the current canonical bytes once, verifies that exact snapshot against the external issuance seal, and returns the verified bytes without rereading packet fields. A valid-value rewrite, attempted reinitialization/reseal, or missing process-local issuance evidence fails closed. This is in-process defense-in-depth only; durable cross-process uniqueness, purpose authorization, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host or persistence boundaries. +A frozen dataclass alone is not an issuance seal because low-level Python mutation can still rewrite otherwise valid fields. Each live issued packet is therefore bound exactly once to its construction-time canonical JSON by a process-local HMAC seal stored outside packet-writable slots. The runtime keeps a separate weak live-issued-identity registry from the current seal bytes: losing or deliberately discarding the seal makes export fail closed but does **not** reset that live object's issuance lifecycle. A second `__post_init__()` therefore cannot mint replacement evidence after seal loss or after a valid-value rewrite. `canonical_json()` snapshots the current canonical bytes once, verifies that exact snapshot against the external issuance seal, and returns the verified bytes without rereading packet fields. A valid-value rewrite, attempted reinitialization/reseal, or missing process-local issuance evidence fails closed. This is in-process defense-in-depth only; durable cross-process uniqueness, purpose authorization, and immutable audit/outbox remain responsibilities of authoritative Orgmetra host or persistence boundaries. ## What this packet does not do diff --git a/packages/performance-review/src/orgmetra_performance_review/packet.py b/packages/performance-review/src/orgmetra_performance_review/packet.py index 725acfeab..1586b626d 100644 --- a/packages/performance-review/src/orgmetra_performance_review/packet.py +++ b/packages/performance-review/src/orgmetra_performance_review/packet.py @@ -21,7 +21,7 @@ import secrets from threading import RLock from uuid import UUID -from weakref import finalize +from weakref import WeakValueDictionary, finalize _DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") _REFERENCE_PATTERN = re.compile( @@ -40,11 +40,12 @@ ) _PROCESS_PACKET_SEAL_KEY = secrets.token_bytes(32) _PACKET_SEALS: dict[int, str] = {} +_ISSUED_PACKET_IDENTITIES: WeakValueDictionary[int, object] = WeakValueDictionary() _PACKET_SEALS_LOCK = RLock() def _discard_packet_seal(packet_id: int) -> None: - """Discard process-local issuance evidence after its review packet is collected.""" + """Discard seal bytes without resetting the live packet's issuance lifecycle.""" with _PACKET_SEALS_LOCK: _PACKET_SEALS.pop(packet_id, None) @@ -53,9 +54,10 @@ def _register_packet_seal(packet: object, seal: str) -> None: """Bind one live review-packet identity exactly once outside writable slots.""" packet_id = id(packet) with _PACKET_SEALS_LOCK: - if packet_id in _PACKET_SEALS: + if _ISSUED_PACKET_IDENTITIES.get(packet_id) is packet: raise ValueError("performance review issuance evidence already exists") _PACKET_SEALS[packet_id] = seal + _ISSUED_PACKET_IDENTITIES[packet_id] = packet finalize(packet, _discard_packet_seal, packet_id)