From c28fe1bb5171e8620ca53e857e08b620432c3ae4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:46:15 -0700 Subject: [PATCH 01/41] test(validity): require governed analysis handoff --- packages/validity-analysis/pyproject.toml | 24 ++ .../validity-analysis/tests/test_handoff.py | 221 ++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 packages/validity-analysis/pyproject.toml create mode 100644 packages/validity-analysis/tests/test_handoff.py diff --git a/packages/validity-analysis/pyproject.toml b/packages/validity-analysis/pyproject.toml new file mode 100644 index 000000000..5547eabcb --- /dev/null +++ b/packages/validity-analysis/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + +[project] +name = "orgmetra-validity-analysis" +version = "0.1.0" +description = "Governed criterion-related selection-validity analysis handoff 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_validity_analysis", + "--cov-branch", + "--cov-report=term-missing", + "--cov-fail-under=100", +] diff --git a/packages/validity-analysis/tests/test_handoff.py b/packages/validity-analysis/tests/test_handoff.py new file mode 100644 index 000000000..64d1ab8c6 --- /dev/null +++ b/packages/validity-analysis/tests/test_handoff.py @@ -0,0 +1,221 @@ +"""Regression tests for governed selection-validity analysis handoffs.""" + +from dataclasses import replace +from datetime import datetime, timedelta, timezone +import json + +import pytest + +from orgmetra_validity_analysis import ( + REVIEWED_FAST_MLSIRM_REVISION, + ValidationAnalysisHandoff, + build_validation_analysis_handoff, +) + +TENANT = "10000000-0000-7000-8000-000000000001" +HANDOFF = "validation_analysis_handoff:11111111-1111-4111-8111-111111111111" +STUDY = "validation_study:22222222-2222-4222-8222-222222222222" +JOB = "job_profile:33333333-3333-4333-8333-333333333333" +PREDICTOR = "predictor_snapshot:44444444-4444-4444-8444-444444444444" +CRITERION = "criterion_snapshot:55555555-5555-4555-8555-555555555555" +POPULATION = "study_population_snapshot:66666666-6666-4666-8666-666666666666" +POLICY = "decision_policy:77777777-7777-4777-8777-777777777777" +PLAN = "validation_analysis_plan:88888888-8888-4888-8888-888888888888" +ACTOR = "actor:99999999-9999-4999-8999-999999999999" +REVIEWER = "actor:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +DIGEST_A = "a" * 64 +DIGEST_B = "b" * 64 +DIGEST_C = "c" * 64 +DIGEST_D = "d" * 64 +DIGEST_E = "e" * 64 +REQUESTED_AT = datetime(2026, 8, 21, 7, 10, 11, 123456, tzinfo=timezone(timedelta(hours=9))) + + +def valid_kwargs(): + """Return one complete governed handoff input fixture.""" + return { + "tenant_record_id": TENANT, + "handoff_reference": HANDOFF, + "validation_study_reference": STUDY, + "job_profile_reference": JOB, + "predictor_snapshot_reference": PREDICTOR, + "predictor_snapshot_digest": DIGEST_A, + "criterion_snapshot_reference": CRITERION, + "criterion_snapshot_digest": DIGEST_B, + "population_snapshot_reference": POPULATION, + "population_snapshot_digest": DIGEST_C, + "decision_policy_reference": POLICY, + "decision_policy_digest": DIGEST_D, + "analysis_plan_reference": PLAN, + "analysis_plan_digest": DIGEST_E, + "actor_reference": ACTOR, + "reviewer_reference": REVIEWER, + "fast_mlsirm_revision": REVIEWED_FAST_MLSIRM_REVISION, + "requested_at": REQUESTED_AT, + } + + +def handoff(): + """Build the canonical valid handoff fixture.""" + return build_validation_analysis_handoff(**valid_kwargs()) + + +def test_handoff_is_value_minimized_deterministic_and_human_review_only(): + """Bind exact study evidence without exposing raw person-level observations.""" + candidate = handoff() + payload = json.loads(candidate.canonical_json()) + + assert payload["tenant_record_id"] == TENANT + assert payload["validation_study_reference"] == STUDY + assert payload["job_profile_reference"] == JOB + assert payload["fast_mlsirm_revision"] == REVIEWED_FAST_MLSIRM_REVISION + assert payload["requested_at"] == "2026-08-20T22:10:11.123456Z" + assert payload["validation_strategy"] == "criterion_related" + assert payload["kernel_repository"] == "ContextualWisdomLab/fast-mlsirm" + assert payload["kernel_boundary"] == "read_only_pinned_revision" + assert payload["execution_state"] == "not_executed" + assert payload["contains_raw_person_level_values"] is False + assert payload["human_review_required"] is True + assert payload["result_authority"] == "scientific_evidence_only" + assert payload["required_result_evidence"] == [ + "effect_estimate", + "uncertainty_interval", + "sample_size", + "missingness_summary", + "convergence_diagnostics", + ] + assert "person_record" not in candidate.canonical_json() + assert "candidate" not in candidate.canonical_json() + assert repr(candidate) == "ValidationAnalysisHandoff()" + assert len(candidate.sha256_digest()) == 64 + assert candidate.canonical_json() == handoff().canonical_json() + + +@pytest.mark.parametrize( + "bad_tenant", + [ + "not-a-uuid", + "00000000-0000-0000-0000-000000000000", + "ffffffff-ffff-ffff-ffff-ffffffffffff", + "10000000-0000-7000-8000-00000000000A", + 1, + ], +) +def test_tenant_identity_must_follow_protected_operational_uuid_contract(bad_tenant): + """Reject malformed, reserved, non-canonical, and non-text tenant identities.""" + values = valid_kwargs() + values["tenant_record_id"] = bad_tenant + with pytest.raises(ValueError, match="tenant_record_id"): + build_validation_analysis_handoff(**values) + + +@pytest.mark.parametrize( + ("field", "bad", "match"), + [ + ("handoff_reference", "validation_analysis_handoff:not-a-uuid", "handoff_reference"), + ("validation_study_reference", JOB, "validation_study_reference"), + ("job_profile_reference", "job_profile:22222222-2222-7222-8222-222222222222", "job_profile_reference"), + ("predictor_snapshot_reference", 1, "predictor_snapshot_reference"), + ("criterion_snapshot_reference", "criterion_snapshot:" + "a" * 161, "criterion_snapshot_reference"), + ("population_snapshot_reference", "study_population_snapshot:not-a-uuid", "population_snapshot_reference"), + ("decision_policy_reference", "decision_policy:BBBBBBBB-BBBB-4BBB-8BBB-BBBBBBBBBBBB", "decision_policy_reference"), + ("analysis_plan_reference", "validation_analysis_plan:00000000-0000-0000-0000-000000000000", "analysis_plan_reference"), + ("actor_reference", "actor:ffffffff-ffff-ffff-ffff-ffffffffffff", "actor_reference"), + ("reviewer_reference", "actor:bbbbbbbb-bbbb-7bbb-8bbb-bbbbbbbbbbbb", "reviewer_reference"), + ], +) +def test_all_public_references_are_namespaced_opaque_uuid4(field, bad, match): + """Fail closed on wrong namespace, malformed, noncanonical, or non-v4 references.""" + values = valid_kwargs() + values[field] = bad + with pytest.raises(ValueError, match=match): + build_validation_analysis_handoff(**values) + + +@pytest.mark.parametrize( + "field", + [ + "predictor_snapshot_digest", + "criterion_snapshot_digest", + "population_snapshot_digest", + "decision_policy_digest", + "analysis_plan_digest", + ], +) +def test_evidence_digests_are_lowercase_sha256(field): + """Reject weak or noncanonical evidence digests for every source snapshot.""" + values = valid_kwargs() + values[field] = "A" * 64 + with pytest.raises(ValueError, match=field): + build_validation_analysis_handoff(**values) + + +def test_requester_and_reviewer_must_be_distinct(): + """Require accountable independent interpretation instead of self-review.""" + values = valid_kwargs() + values["reviewer_reference"] = ACTOR + with pytest.raises(ValueError, match="different accountable actor"): + build_validation_analysis_handoff(**values) + + +@pytest.mark.parametrize("bad_revision", ["not-a-sha", "A" * 40, "0" * 40]) +def test_fast_mlsirm_revision_is_exactly_the_reviewed_immutable_dependency(bad_revision): + """Reject malformed or unreviewed foreign dependency revisions.""" + values = valid_kwargs() + values["fast_mlsirm_revision"] = bad_revision + with pytest.raises(ValueError, match="fast_mlsirm_revision"): + build_validation_analysis_handoff(**values) + + +@pytest.mark.parametrize( + "bad_requested_at", + [ + datetime(2026, 8, 21, 7, 10), + "2026-08-21T07:10:00+09:00", + ], +) +def test_requested_at_requires_an_aware_datetime(bad_requested_at): + """Reject local-time ambiguity in immutable analysis correlation.""" + values = valid_kwargs() + values["requested_at"] = bad_requested_at + with pytest.raises(ValueError, match="requested_at"): + build_validation_analysis_handoff(**values) + + +@pytest.mark.parametrize( + ("field", "bad", "match"), + [ + ("purpose_code", "other_purpose", "purpose_code"), + ("reason_code", "other_reason", "reason_code"), + ("evidence_version", True, "evidence_version"), + ("evidence_version", 0, "evidence_version"), + ("validation_strategy", "content_validity", "validation_strategy"), + ("kernel_repository", "other/repository", "kernel_repository"), + ("kernel_boundary", "direct_database", "kernel_boundary"), + ("execution_state", "executed", "execution_state"), + ("contains_raw_person_level_values", True, "raw person-level"), + ("human_review_required", False, "human review"), + ("result_authority", "employment_decision", "result_authority"), + ("required_result_evidence", ("effect_estimate",), "required_result_evidence"), + ("next_action", "Auto-approve the result.", "next_action"), + ], +) +def test_direct_construction_cannot_weaken_governance(field, bad, match): + """Keep fixed scientific, privacy, dependency, and human-authority semantics immutable.""" + with pytest.raises(ValueError, match=match): + replace(handoff(), **{field: bad}) + + +def test_codes_must_remain_bounded_descriptive_snake_case_before_fixed_value_check(): + """Exercise code-shape rejection separately from the closed purpose/reason vocabulary.""" + with pytest.raises(ValueError, match="purpose_code"): + replace(handoff(), purpose_code="X") + with pytest.raises(ValueError, match="reason_code"): + replace(handoff(), reason_code="x" * 65) + + +def test_public_dataclass_type_is_constructible_only_with_all_invariants(): + """Document the public immutable type while preserving builder equivalence.""" + values = valid_kwargs() + direct = ValidationAnalysisHandoff(**values) + assert direct == handoff() From 5b0a1e5926497ca46a9c12bcc253d555dbf99bc8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:47:12 -0700 Subject: [PATCH 02/41] feat(validity): add governed fast-mlsirm handoff --- .../orgmetra_validity_analysis/__init__.py | 13 + .../src/orgmetra_validity_analysis/handoff.py | 280 ++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py create mode 100644 packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py b/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py new file mode 100644 index 000000000..d7d15691c --- /dev/null +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py @@ -0,0 +1,13 @@ +"""Public governed selection-validity analysis handoff contract.""" + +from .handoff import ( + REVIEWED_FAST_MLSIRM_REVISION, + ValidationAnalysisHandoff, + build_validation_analysis_handoff, +) + +__all__ = [ + "REVIEWED_FAST_MLSIRM_REVISION", + "ValidationAnalysisHandoff", + "build_validation_analysis_handoff", +] diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py new file mode 100644 index 000000000..e3a347723 --- /dev/null +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py @@ -0,0 +1,280 @@ +"""Governed handoff evidence for criterion-related selection validation. + +This package does not execute statistics, read another service's database, or make an +employment decision. It binds authoritative Orgmetra evidence to one reviewed, +immutable fast-mlsirm revision so an approved offline worker can perform numerical +analysis without silently changing the study definition. +""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime, timezone +from hashlib import sha256 +import json +import re +from uuid import UUID + +_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") +_DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") +_REVISION_PATTERN = re.compile(r"^[0-9a-f]{40}$") +_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 = "selection_validity_analysis" +_REASON_CODE = "criterion_related_validation" +_VALIDATION_STRATEGY = "criterion_related" +_KERNEL_REPOSITORY = "ContextualWisdomLab/fast-mlsirm" +REVIEWED_FAST_MLSIRM_REVISION = "04d0bc21a2a20693bcf16108cd76d394fe844d23" +_KERNEL_BOUNDARY = "read_only_pinned_revision" +_EXECUTION_STATE = "not_executed" +_RESULT_AUTHORITY = "scientific_evidence_only" +_REQUIRED_RESULT_EVIDENCE = ( + "effect_estimate", + "uncertainty_interval", + "sample_size", + "missingness_summary", + "convergence_diagnostics", +) +_NEXT_ACTION = ( + "Within tenant_record_id, re-resolve the validation study, Job, predictor, criterion, " + "population, decision-policy, analysis-plan, requester, and reviewer references; prove " + "the predictor/criterion/population cases belong to the exact study and Job; then let an " + "approved offline validation worker invoke only the pinned fast-mlsirm revision. Preserve " + "the resulting model/provenance diagnostics as draft scientific evidence for an " + "accountable human reviewer; never convert the result directly into an employment decision." +) + + +def _validate_operational_uuid(value: str, field_name: str) -> None: + """Require canonical non-sentinel UUID text owned by authoritative Orgmetra.""" + 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_reference(value: str, prefix: str, field_name: str) -> None: + """Require the expected namespace plus a canonical opaque UUIDv4 suffix.""" + error_message = f"{field_name} must be an opaque {prefix}: UUIDv4 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.version != 4 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 _validate_code(value: str, field_name: str) -> None: + """Require bounded descriptive lower snake_case governance codes.""" + 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_kernel_revision(value: str) -> None: + """Require the exact externally reviewed immutable fast-mlsirm revision.""" + if not isinstance(value, str) or not _REVISION_PATTERN.fullmatch(value): + raise ValueError("fast_mlsirm_revision must be lowercase 40-character Git commit hex") + if value != REVIEWED_FAST_MLSIRM_REVISION: + raise ValueError("fast_mlsirm_revision must equal the reviewed immutable revision") + + +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("requested_at must be timezone-aware") + return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +@dataclass(frozen=True, slots=True, repr=False) +class ValidationAnalysisHandoff: + """Immutable evidence for one not-yet-executed criterion-related validity analysis.""" + + tenant_record_id: str + handoff_reference: str + validation_study_reference: str + job_profile_reference: str + predictor_snapshot_reference: str + predictor_snapshot_digest: str + criterion_snapshot_reference: str + criterion_snapshot_digest: str + population_snapshot_reference: str + population_snapshot_digest: str + decision_policy_reference: str + decision_policy_digest: str + analysis_plan_reference: str + analysis_plan_digest: str + actor_reference: str + reviewer_reference: str + fast_mlsirm_revision: str + requested_at: datetime + purpose_code: str = _PURPOSE_CODE + reason_code: str = _REASON_CODE + evidence_version: int = 1 + validation_strategy: str = _VALIDATION_STRATEGY + kernel_repository: str = _KERNEL_REPOSITORY + kernel_boundary: str = _KERNEL_BOUNDARY + execution_state: str = _EXECUTION_STATE + contains_raw_person_level_values: bool = False + human_review_required: bool = True + result_authority: str = _RESULT_AUTHORITY + required_result_evidence: tuple[str, ...] = _REQUIRED_RESULT_EVIDENCE + next_action: str = _NEXT_ACTION + + def __post_init__(self) -> None: + """Fail closed when direct construction drifts from the governed handoff.""" + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + for value, prefix, field_name in ( + (self.handoff_reference, "validation_analysis_handoff", "handoff_reference"), + (self.validation_study_reference, "validation_study", "validation_study_reference"), + (self.job_profile_reference, "job_profile", "job_profile_reference"), + (self.predictor_snapshot_reference, "predictor_snapshot", "predictor_snapshot_reference"), + (self.criterion_snapshot_reference, "criterion_snapshot", "criterion_snapshot_reference"), + (self.population_snapshot_reference, "study_population_snapshot", "population_snapshot_reference"), + (self.decision_policy_reference, "decision_policy", "decision_policy_reference"), + (self.analysis_plan_reference, "validation_analysis_plan", "analysis_plan_reference"), + (self.actor_reference, "actor", "actor_reference"), + (self.reviewer_reference, "actor", "reviewer_reference"), + ): + _validate_reference(value, prefix, field_name) + for value, field_name in ( + (self.predictor_snapshot_digest, "predictor_snapshot_digest"), + (self.criterion_snapshot_digest, "criterion_snapshot_digest"), + (self.population_snapshot_digest, "population_snapshot_digest"), + (self.decision_policy_digest, "decision_policy_digest"), + (self.analysis_plan_digest, "analysis_plan_digest"), + ): + _validate_digest(value, field_name) + if self.actor_reference == self.reviewer_reference: + raise ValueError("reviewer_reference must identify a different accountable actor") + _validate_kernel_revision(self.fast_mlsirm_revision) + _canonical_timestamp(self.requested_at) + _validate_code(self.purpose_code, "purpose_code") + if self.purpose_code != _PURPOSE_CODE: + raise ValueError("purpose_code must remain selection_validity_analysis") + _validate_code(self.reason_code, "reason_code") + if self.reason_code != _REASON_CODE: + raise ValueError("reason_code must remain criterion_related_validation") + if type(self.evidence_version) is not int or not 1 <= self.evidence_version <= 2_147_483_647: + raise ValueError("evidence_version must be an integer from 1 through 2147483647") + if self.validation_strategy != _VALIDATION_STRATEGY: + raise ValueError("validation_strategy must remain criterion_related") + if self.kernel_repository != _KERNEL_REPOSITORY: + raise ValueError("kernel_repository must remain ContextualWisdomLab/fast-mlsirm") + if self.kernel_boundary != _KERNEL_BOUNDARY: + raise ValueError("kernel_boundary must remain read_only_pinned_revision") + if self.execution_state != _EXECUTION_STATE: + raise ValueError("execution_state must remain not_executed") + if self.contains_raw_person_level_values is not False: + raise ValueError("handoff must not contain raw person-level values") + if self.human_review_required is not True: + raise ValueError("human review is mandatory for selection-validity interpretation") + if self.result_authority != _RESULT_AUTHORITY: + raise ValueError("result_authority must remain scientific_evidence_only") + if self.required_result_evidence != _REQUIRED_RESULT_EVIDENCE: + raise ValueError("required_result_evidence must remain the reviewed evidence set") + if self.next_action != _NEXT_ACTION: + raise ValueError("next_action must remain the governed validation instruction") + + def __repr__(self) -> str: + """Return a fully redacted representation suitable for routine logs.""" + return "ValidationAnalysisHandoff()" + + def canonical_json(self) -> str: + """Return deterministic canonical JSON for audit and result correlation.""" + payload = { + "actor_reference": self.actor_reference, + "analysis_plan_digest": self.analysis_plan_digest, + "analysis_plan_reference": self.analysis_plan_reference, + "contains_raw_person_level_values": self.contains_raw_person_level_values, + "criterion_snapshot_digest": self.criterion_snapshot_digest, + "criterion_snapshot_reference": self.criterion_snapshot_reference, + "decision_policy_digest": self.decision_policy_digest, + "decision_policy_reference": self.decision_policy_reference, + "evidence_version": self.evidence_version, + "execution_state": self.execution_state, + "fast_mlsirm_revision": self.fast_mlsirm_revision, + "handoff_reference": self.handoff_reference, + "human_review_required": self.human_review_required, + "job_profile_reference": self.job_profile_reference, + "kernel_boundary": self.kernel_boundary, + "kernel_repository": self.kernel_repository, + "next_action": self.next_action, + "population_snapshot_digest": self.population_snapshot_digest, + "population_snapshot_reference": self.population_snapshot_reference, + "predictor_snapshot_digest": self.predictor_snapshot_digest, + "predictor_snapshot_reference": self.predictor_snapshot_reference, + "purpose_code": self.purpose_code, + "reason_code": self.reason_code, + "requested_at": _canonical_timestamp(self.requested_at), + "required_result_evidence": list(self.required_result_evidence), + "result_authority": self.result_authority, + "reviewer_reference": self.reviewer_reference, + "tenant_record_id": self.tenant_record_id, + "validation_strategy": self.validation_strategy, + "validation_study_reference": self.validation_study_reference, + } + 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 handoff.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + +def build_validation_analysis_handoff( + *, + tenant_record_id: str, + handoff_reference: str, + validation_study_reference: str, + job_profile_reference: str, + predictor_snapshot_reference: str, + predictor_snapshot_digest: str, + criterion_snapshot_reference: str, + criterion_snapshot_digest: str, + population_snapshot_reference: str, + population_snapshot_digest: str, + decision_policy_reference: str, + decision_policy_digest: str, + analysis_plan_reference: str, + analysis_plan_digest: str, + actor_reference: str, + reviewer_reference: str, + fast_mlsirm_revision: str, + requested_at: datetime, +) -> ValidationAnalysisHandoff: + """Build a governed, non-executing selection-validity analysis handoff.""" + return ValidationAnalysisHandoff( + tenant_record_id=tenant_record_id, + handoff_reference=handoff_reference, + validation_study_reference=validation_study_reference, + job_profile_reference=job_profile_reference, + predictor_snapshot_reference=predictor_snapshot_reference, + predictor_snapshot_digest=predictor_snapshot_digest, + criterion_snapshot_reference=criterion_snapshot_reference, + criterion_snapshot_digest=criterion_snapshot_digest, + population_snapshot_reference=population_snapshot_reference, + population_snapshot_digest=population_snapshot_digest, + decision_policy_reference=decision_policy_reference, + decision_policy_digest=decision_policy_digest, + analysis_plan_reference=analysis_plan_reference, + analysis_plan_digest=analysis_plan_digest, + actor_reference=actor_reference, + reviewer_reference=reviewer_reference, + fast_mlsirm_revision=fast_mlsirm_revision, + requested_at=requested_at, + ) From 3f99928166e4f6b36714c77557f68ec798256208 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:49:08 -0700 Subject: [PATCH 03/41] docs(validity): record governed analysis boundary --- .../workflows/validity-analysis-quality.yml | 59 +++++++++++++++++++ ...ned-selection-validity-analysis-handoff.md | 52 ++++++++++++++++ .../validation-analysis-handoff-references.md | 17 ++++++ .../validation-analysis-handoff.md | 23 ++++++++ packages/validity-analysis/CHANGELOG.md | 7 +++ packages/validity-analysis/README.md | 34 +++++++++++ 6 files changed, 192 insertions(+) create mode 100644 .github/workflows/validity-analysis-quality.yml create mode 100644 docs/adr/0025-governed-selection-validity-analysis-handoff.md create mode 100644 docs/doctoring/validation-analysis-handoff-references.md create mode 100644 docs/traceability/validation-analysis-handoff.md create mode 100644 packages/validity-analysis/CHANGELOG.md create mode 100644 packages/validity-analysis/README.md diff --git a/.github/workflows/validity-analysis-quality.yml b/.github/workflows/validity-analysis-quality.yml new file mode 100644 index 000000000..df8eff10d --- /dev/null +++ b/.github/workflows/validity-analysis-quality.yml @@ -0,0 +1,59 @@ +name: Validity Analysis Handoff Quality + +on: + pull_request: + branches: + - bootstrap + - develop + - main + paths: + - "packages/validity-analysis/**" + - "docs/adr/0025-governed-selection-validity-analysis-handoff.md" + - "docs/doctoring/validation-analysis-handoff-references.md" + - "docs/traceability/validation-analysis-handoff.md" + - ".github/requirements/foundation-test.txt" + - ".github/workflows/validity-analysis-quality.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: validity-analysis-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit: + name: Validity handoff 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 validity-analysis handoff + run: python -m compileall -q packages/validity-analysis/src packages/validity-analysis/tests + - name: Test governed handoff with exact statement and branch coverage + env: + PYTHONPATH: packages/validity-analysis/src + COVERAGE_FILE: /tmp/orgmetra-validity-analysis.coverage + run: python -m pytest -c packages/validity-analysis/pyproject.toml packages/validity-analysis/tests + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" diff --git a/docs/adr/0025-governed-selection-validity-analysis-handoff.md b/docs/adr/0025-governed-selection-validity-analysis-handoff.md new file mode 100644 index 000000000..a1b06d0e8 --- /dev/null +++ b/docs/adr/0025-governed-selection-validity-analysis-handoff.md @@ -0,0 +1,52 @@ +# ADR 0025: Govern selection-validity numerical work through an immutable handoff + +- Status: Proposed +- Maturity: Active PR only; not protected-branch truth +- Date: 2026-08-21 +- Owners: Orgmetra Workforce Validation + +## Context + +Protected Orgmetra already preserves exact validation-study cases, sealed selection evidence, candidate-to-worker lineage, and Job/cycle/staffing-scoped criterion observations. The remaining boundary is dangerous if left implicit: a statistical worker could receive an underspecified study, silently use a different dependency revision, or turn a model result into an employment decision. + +The Uniform Guidelines recognize criterion-related validity evidence as empirical evidence relating a selection procedure to important job-performance elements and require validity studies to be accurate, standardized, documented, and periodically reviewed for currency. SIOP's *Principles for the Validation and Use of Personnel Selection Procedures* likewise treats validation as an evidence-and-inference problem rather than a correlation-only shortcut. + +`ContextualWisdomLab/fast-mlsirm` owns numerical psychometric/statistical kernels. Its protected `main` was freshly resolved to commit `04d0bc21a2a20693bcf16108cd76d394fe844d23` on 2026-08-21. Orgmetra must not copy that implementation or write the foreign repository. + +## Decision + +Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnalysisHandoff`: + +- binds the exact tenant, validation study, Job, predictor snapshot, criterion snapshot, population snapshot, decision policy, and analysis plan through opaque references plus SHA-256 evidence digests; +- binds distinct requester and reviewer actor references; +- pins fast-mlsirm to reviewed immutable commit `04d0bc21a2a20693bcf16108cd76d394fe844d23`; +- declares the numerical boundary `read_only_pinned_revision` and the initial strategy `criterion_related`; +- requires downstream result evidence for effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics; +- serializes no raw person-level predictor, criterion, candidate, or worker values; +- remains `not_executed`, `scientific_evidence_only`, and human-review-required; +- produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. + +The package does not invoke fast-mlsirm. An approved offline worker is the later execution boundary. Before execution, the Orgmetra host must re-resolve every reference inside the tenant, verify exact study/Job membership and evidence provenance, and prove requester/reviewer identities are distinct authoritative actors. + +## Consequences + +### Positive + +- Statistical work cannot silently drift to an unreviewed fast-mlsirm revision. +- A buyer or auditor can identify exactly which governed study evidence a result was supposed to analyze without copying raw worker data into the handoff. +- Human interpretation remains explicit and separate from numerical output. +- The dedicated-writer boundary remains intact: Orgmetra consumes only a pinned foreign revision/contract boundary and never mutates fast-mlsirm. + +### Limitations + +- This slice does not execute a statistical model, estimate validity, correct for measurement error/range restriction, evaluate adverse impact, or assert legal compliance. +- Sampling design, estimator choice, missing-data treatment, reliability evidence, multiplicity, transportability, fairness analysis, and model diagnostics must be encoded in the referenced analysis plan and reviewed before execution. +- A future execution/result adapter must validate the returned model/provenance schema before any result is attached to an Orgmetra study. + +## Verification + +The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, and 100% owned production statement/branch coverage. + +## References + +See `docs/doctoring/validation-analysis-handoff-references.md`. diff --git a/docs/doctoring/validation-analysis-handoff-references.md b/docs/doctoring/validation-analysis-handoff-references.md new file mode 100644 index 000000000..520b30550 --- /dev/null +++ b/docs/doctoring/validation-analysis-handoff-references.md @@ -0,0 +1,17 @@ +# Validation-analysis handoff references + +Material decisions for ADR 0025 were checked against the following primary/authoritative sources on 2026-08-21. + +## APA 7 references + +Electronic Code of Federal Regulations. (2026). *29 C.F.R. pt. 1607—Uniform Guidelines on Employee Selection Procedures (1978).* Retrieved August 21, 2026, from https://www.ecfr.gov/current/title-29/subtitle-B/chapter-XIV/part-1607 + +Society for Industrial and Organizational Psychology. (2018). *Principles for the validation and use of personnel selection procedures* (5th ed.). Cambridge University Press. https://www.apa.org/ed/accreditation/personnel-selection-procedures.pdf + +ContextualWisdomLab. (2026). *fast-mlsirm* (Commit 04d0bc21a2a20693bcf16108cd76d394fe844d23) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/fast-mlsirm/tree/04d0bc21a2a20693bcf16108cd76d394fe844d23 + +## Decision notes + +- 29 C.F.R. §§ 1607.5 and 1607.14 support keeping criterion-related validity evidence tied to an explicit study design, job relevance, accuracy, reporting, and documentation rather than treating a bare coefficient as sufficient evidence. +- The SIOP Principles are the professional validation baseline used for the handoff's evidence-and-human-review posture. +- The fast-mlsirm commit is recorded as a read-only dependency coordinate only. This Orgmetra slice does not modify or duplicate its numerical implementation. diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md new file mode 100644 index 000000000..c1d53a51f --- /dev/null +++ b/docs/traceability/validation-analysis-handoff.md @@ -0,0 +1,23 @@ +# Selection-validity analysis handoff traceability + +## Buyer question + +Can an organization send one exact, reviewable validation study to its statistical engine without copying raw person-level values into a workflow envelope, silently changing the numerical dependency, or treating model output as an employment decision? + +## Active-PR contract + +| Concern | Orgmetra evidence | Verification | +|---|---|---| +| Exact study scope | tenant, validation-study, Job, predictor, criterion, population, decision-policy, and analysis-plan references plus digests | namespace/UUID/digest regressions | +| Dependency integrity | immutable fast-mlsirm commit `04d0bc21a2a20693bcf16108cd76d394fe844d23` | malformed and unreviewed revision rejection | +| Privacy minimization | no raw person-level values in canonical handoff | canonical-payload regression and redacted repr | +| Human authority | distinct requester/reviewer and `human_review_required=true` | direct-construction fail-closed regressions | +| Scientific evidence | effect estimate, uncertainty interval, sample size, missingness summary, convergence diagnostics | immutable required-result-evidence regression | +| Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | +| Reproducibility | canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | deterministic serialization/digest tests | + +## Maturity + +`implemented_on_active_pr`. + +Protected `develop` does **not** gain numerical validity computation from this slice. The handoff is execution preparation only. A future worker/result boundary must independently earn tests for the exact returned numerical/provenance contract before the result can become protected Orgmetra evidence. diff --git a/packages/validity-analysis/CHANGELOG.md b/packages/validity-analysis/CHANGELOG.md new file mode 100644 index 000000000..821ff22f6 --- /dev/null +++ b/packages/validity-analysis/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## 0.1.0 - Unreleased + +- Add a governed, value-minimized criterion-related validity analysis handoff. +- Pin the reviewed read-only fast-mlsirm dependency revision. +- Require separate requester/reviewer authority, deterministic canonical evidence, and 100% owned production statement/branch coverage. diff --git a/packages/validity-analysis/README.md b/packages/validity-analysis/README.md new file mode 100644 index 000000000..c7e14eb37 --- /dev/null +++ b/packages/validity-analysis/README.md @@ -0,0 +1,34 @@ +# Orgmetra validity-analysis handoff + +This package creates an immutable **selection-validity analysis handoff**. It is the boundary between Orgmetra's authoritative validation-study evidence and numerical work owned by `ContextualWisdomLab/fast-mlsirm`. + +## What it does + +`build_validation_analysis_handoff(...)` binds one tenant, validation study, Job, predictor snapshot, criterion snapshot, population snapshot, decision policy, analysis plan, requester, reviewer, and the reviewed fast-mlsirm revision `04d0bc21a2a20693bcf16108cd76d394fe844d23`. + +The resulting canonical JSON is digest-addressable, contains no raw person-level predictor or criterion values, and remains `not_executed`. Required result evidence is explicit: effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics. + +## What it does not do + +- It does **not** run statistics. +- It does **not** query fast-mlsirm or any other CWL application's database. +- It does **not** claim that a selection procedure is valid. +- It does **not** interpret adverse impact. +- It does **not** authorize hiring, promotion, termination, compensation, or another employment decision. + +The fast-mlsirm repository remains a dedicated-writer dependency. This package records only the immutable revision reviewed for the handoff. + +## Host obligations + +Before an approved offline validation worker executes the handoff, the Orgmetra host must re-resolve every reference inside `tenant_record_id` and prove that the predictor, criterion, population, and policy evidence belong to the exact validation study and Job. The requester and reviewer must resolve to distinct authoritative actors. A numerical result is scientific evidence for accountable human interpretation, never an autonomous employment decision. + +## Verification + +Run: + +```bash +PYTHONPATH=packages/validity-analysis/src \ +python -m pytest -c packages/validity-analysis/pyproject.toml packages/validity-analysis/tests +``` + +The package gate requires exact 100% owned production statement and branch coverage. From a9168a9fde7e6de1b4a066098c5c4c2993daa0ce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 16:30:38 -0700 Subject: [PATCH 04/41] test(validity): reject duplicate ADR numbers after integration --- .../tests/test_adr_numbering.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/validity-analysis/tests/test_adr_numbering.py diff --git a/packages/validity-analysis/tests/test_adr_numbering.py b/packages/validity-analysis/tests/test_adr_numbering.py new file mode 100644 index 000000000..8f2ee0828 --- /dev/null +++ b/packages/validity-analysis/tests/test_adr_numbering.py @@ -0,0 +1,17 @@ +"""Regression tests for repository-wide ADR number ownership.""" + +from pathlib import Path + + +def test_adr_numbers_are_unique_across_the_integrated_repository() -> None: + """Every four-digit ADR number must identify exactly one decision record.""" + adr_directory = Path(__file__).resolve().parents[3] / "docs" / "adr" + owners: dict[str, str] = {} + + for adr_path in sorted(adr_directory.glob("[0-9][0-9][0-9][0-9]-*.md")): + adr_number = adr_path.name[:4] + previous_owner = owners.get(adr_number) + assert previous_owner is None, ( + f"ADR {adr_number} is reused by {previous_owner} and {adr_path.name}" + ) + owners[adr_number] = adr_path.name From 6814fd9b3c924f9ed3b1f2e4eaea96cfe7191baf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 16:31:16 -0700 Subject: [PATCH 05/41] fix(validity): reserve ADR 0027 for analysis handoff --- ...ned-selection-validity-analysis-handoff.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/adr/0027-governed-selection-validity-analysis-handoff.md diff --git a/docs/adr/0027-governed-selection-validity-analysis-handoff.md b/docs/adr/0027-governed-selection-validity-analysis-handoff.md new file mode 100644 index 000000000..14fe92e05 --- /dev/null +++ b/docs/adr/0027-governed-selection-validity-analysis-handoff.md @@ -0,0 +1,52 @@ +# ADR 0027: Govern selection-validity numerical work through an immutable handoff + +- Status: Proposed +- Maturity: Active PR only; not protected-branch truth +- Date: 2026-08-21 +- Owners: Orgmetra Workforce Validation + +## Context + +Protected Orgmetra already preserves exact validation-study cases, sealed selection evidence, candidate-to-worker lineage, and Job/cycle/staffing-scoped criterion observations. The remaining boundary is dangerous if left implicit: a statistical worker could receive an underspecified study, silently use a different dependency revision, or turn a model result into an employment decision. + +The Uniform Guidelines recognize criterion-related validity evidence as empirical evidence relating a selection procedure to important job-performance elements and require validity studies to be accurate, standardized, documented, and periodically reviewed for currency. SIOP's *Principles for the Validation and Use of Personnel Selection Procedures* likewise treats validation as an evidence-and-inference problem rather than a correlation-only shortcut. + +`ContextualWisdomLab/fast-mlsirm` owns numerical psychometric/statistical kernels. Its protected `main` was freshly resolved to commit `04d0bc21a2a20693bcf16108cd76d394fe844d23` on 2026-08-21. Orgmetra must not copy that implementation or write the foreign repository. + +## Decision + +Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnalysisHandoff`: + +- binds the exact tenant, validation study, Job, predictor snapshot, criterion snapshot, population snapshot, decision policy, and analysis plan through opaque references plus SHA-256 evidence digests; +- binds distinct requester and reviewer actor references; +- pins fast-mlsirm to reviewed immutable commit `04d0bc21a2a20693bcf16108cd76d394fe844d23`; +- declares the numerical boundary `read_only_pinned_revision` and the initial strategy `criterion_related`; +- requires downstream result evidence for effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics; +- serializes no raw person-level predictor, criterion, candidate, or worker values; +- remains `not_executed`, `scientific_evidence_only`, and human-review-required; +- produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. + +The package does not invoke fast-mlsirm. An approved offline worker is the later execution boundary. Before execution, the Orgmetra host must re-resolve every reference inside the tenant, verify exact study/Job membership and evidence provenance, and prove requester/reviewer identities are distinct authoritative actors. + +## Consequences + +### Positive + +- Statistical work cannot silently drift to an unreviewed fast-mlsirm revision. +- A buyer or auditor can identify exactly which governed study evidence a result was supposed to analyze without copying raw worker data into the handoff. +- Human interpretation remains explicit and separate from numerical output. +- The dedicated-writer boundary remains intact: Orgmetra consumes only a pinned foreign revision/contract boundary and never mutates fast-mlsirm. + +### Limitations + +- This slice does not execute a statistical model, estimate validity, correct for measurement error/range restriction, evaluate adverse impact, or assert legal compliance. +- Sampling design, estimator choice, missing-data treatment, reliability evidence, multiplicity, transportability, fairness analysis, and model diagnostics must be encoded in the referenced analysis plan and reviewed before execution. +- A future execution/result adapter must validate the returned model/provenance schema before any result is attached to an Orgmetra study. + +## Verification + +The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, and 100% owned production statement/branch coverage. The repository-wide ADR numbering regression also fails closed if integration reuses an existing decision number. + +## References + +See `docs/doctoring/validation-analysis-handoff-references.md`. From a4876a8d3ce0c4994cb93de9490c3fc3b46d8ec0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 16:31:26 -0700 Subject: [PATCH 06/41] fix(validity): track reserved ADR 0027 in quality gate --- .github/workflows/validity-analysis-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validity-analysis-quality.yml b/.github/workflows/validity-analysis-quality.yml index df8eff10d..5b1ae41fc 100644 --- a/.github/workflows/validity-analysis-quality.yml +++ b/.github/workflows/validity-analysis-quality.yml @@ -8,7 +8,7 @@ on: - main paths: - "packages/validity-analysis/**" - - "docs/adr/0025-governed-selection-validity-analysis-handoff.md" + - "docs/adr/0027-governed-selection-validity-analysis-handoff.md" - "docs/doctoring/validation-analysis-handoff-references.md" - "docs/traceability/validation-analysis-handoff.md" - ".github/requirements/foundation-test.txt" From 7e9cd769cd2b9fad0c994497847255885e0526c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 16:31:39 -0700 Subject: [PATCH 07/41] docs(validity): bind references to ADR 0027 --- docs/doctoring/validation-analysis-handoff-references.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doctoring/validation-analysis-handoff-references.md b/docs/doctoring/validation-analysis-handoff-references.md index 520b30550..c2e31dbd2 100644 --- a/docs/doctoring/validation-analysis-handoff-references.md +++ b/docs/doctoring/validation-analysis-handoff-references.md @@ -1,6 +1,6 @@ # Validation-analysis handoff references -Material decisions for ADR 0025 were checked against the following primary/authoritative sources on 2026-08-21. +Material decisions for ADR 0027 were checked against the following primary/authoritative sources on 2026-08-21. ## APA 7 references From a872938b508a42a7010c059789e012beffd5ef60 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 16:31:45 -0700 Subject: [PATCH 08/41] fix(validity): remove conflicting ADR 0025 allocation --- ...ned-selection-validity-analysis-handoff.md | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 docs/adr/0025-governed-selection-validity-analysis-handoff.md diff --git a/docs/adr/0025-governed-selection-validity-analysis-handoff.md b/docs/adr/0025-governed-selection-validity-analysis-handoff.md deleted file mode 100644 index a1b06d0e8..000000000 --- a/docs/adr/0025-governed-selection-validity-analysis-handoff.md +++ /dev/null @@ -1,52 +0,0 @@ -# ADR 0025: Govern selection-validity numerical work through an immutable handoff - -- Status: Proposed -- Maturity: Active PR only; not protected-branch truth -- Date: 2026-08-21 -- Owners: Orgmetra Workforce Validation - -## Context - -Protected Orgmetra already preserves exact validation-study cases, sealed selection evidence, candidate-to-worker lineage, and Job/cycle/staffing-scoped criterion observations. The remaining boundary is dangerous if left implicit: a statistical worker could receive an underspecified study, silently use a different dependency revision, or turn a model result into an employment decision. - -The Uniform Guidelines recognize criterion-related validity evidence as empirical evidence relating a selection procedure to important job-performance elements and require validity studies to be accurate, standardized, documented, and periodically reviewed for currency. SIOP's *Principles for the Validation and Use of Personnel Selection Procedures* likewise treats validation as an evidence-and-inference problem rather than a correlation-only shortcut. - -`ContextualWisdomLab/fast-mlsirm` owns numerical psychometric/statistical kernels. Its protected `main` was freshly resolved to commit `04d0bc21a2a20693bcf16108cd76d394fe844d23` on 2026-08-21. Orgmetra must not copy that implementation or write the foreign repository. - -## Decision - -Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnalysisHandoff`: - -- binds the exact tenant, validation study, Job, predictor snapshot, criterion snapshot, population snapshot, decision policy, and analysis plan through opaque references plus SHA-256 evidence digests; -- binds distinct requester and reviewer actor references; -- pins fast-mlsirm to reviewed immutable commit `04d0bc21a2a20693bcf16108cd76d394fe844d23`; -- declares the numerical boundary `read_only_pinned_revision` and the initial strategy `criterion_related`; -- requires downstream result evidence for effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics; -- serializes no raw person-level predictor, criterion, candidate, or worker values; -- remains `not_executed`, `scientific_evidence_only`, and human-review-required; -- produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. - -The package does not invoke fast-mlsirm. An approved offline worker is the later execution boundary. Before execution, the Orgmetra host must re-resolve every reference inside the tenant, verify exact study/Job membership and evidence provenance, and prove requester/reviewer identities are distinct authoritative actors. - -## Consequences - -### Positive - -- Statistical work cannot silently drift to an unreviewed fast-mlsirm revision. -- A buyer or auditor can identify exactly which governed study evidence a result was supposed to analyze without copying raw worker data into the handoff. -- Human interpretation remains explicit and separate from numerical output. -- The dedicated-writer boundary remains intact: Orgmetra consumes only a pinned foreign revision/contract boundary and never mutates fast-mlsirm. - -### Limitations - -- This slice does not execute a statistical model, estimate validity, correct for measurement error/range restriction, evaluate adverse impact, or assert legal compliance. -- Sampling design, estimator choice, missing-data treatment, reliability evidence, multiplicity, transportability, fairness analysis, and model diagnostics must be encoded in the referenced analysis plan and reviewed before execution. -- A future execution/result adapter must validate the returned model/provenance schema before any result is attached to an Orgmetra study. - -## Verification - -The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, and 100% owned production statement/branch coverage. - -## References - -See `docs/doctoring/validation-analysis-handoff-references.md`. From 22fd3745994e8111d94e1355aceb7a2ec1607d5e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:09:44 -0700 Subject: [PATCH 09/41] test(validity): require resolved reviewer identity separation --- .../tests/test_host_resolution_contract.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/validity-analysis/tests/test_host_resolution_contract.py diff --git a/packages/validity-analysis/tests/test_host_resolution_contract.py b/packages/validity-analysis/tests/test_host_resolution_contract.py new file mode 100644 index 000000000..c87fb1a18 --- /dev/null +++ b/packages/validity-analysis/tests/test_host_resolution_contract.py @@ -0,0 +1,34 @@ +"""Regressions for authoritative requester/reviewer identity separation.""" + +from datetime import datetime, timezone + +from orgmetra_validity_analysis import ( + REVIEWED_FAST_MLSIRM_REVISION, + build_validation_analysis_handoff, +) + + +def test_next_action_requires_resolved_actor_identity_separation() -> None: + """Do not let different opaque actor references masquerade as distinct people.""" + handoff = build_validation_analysis_handoff( + tenant_record_id="10000000-0000-7000-8000-000000000001", + handoff_reference="validation_analysis_handoff:11111111-1111-4111-8111-111111111111", + validation_study_reference="validation_study:22222222-2222-4222-8222-222222222222", + job_profile_reference="job_profile:33333333-3333-4333-8333-333333333333", + predictor_snapshot_reference="predictor_snapshot:44444444-4444-4444-8444-444444444444", + predictor_snapshot_digest="a" * 64, + criterion_snapshot_reference="criterion_snapshot:55555555-5555-4555-8555-555555555555", + criterion_snapshot_digest="b" * 64, + population_snapshot_reference="study_population_snapshot:66666666-6666-4666-8666-666666666666", + population_snapshot_digest="c" * 64, + decision_policy_reference="decision_policy:77777777-7777-4777-8777-777777777777", + decision_policy_digest="d" * 64, + analysis_plan_reference="validation_analysis_plan:88888888-8888-4888-8888-888888888888", + analysis_plan_digest="e" * 64, + actor_reference="actor:99999999-9999-4999-8999-999999999999", + reviewer_reference="actor:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + fast_mlsirm_revision=REVIEWED_FAST_MLSIRM_REVISION, + requested_at=datetime(2026, 8, 21, 1, 0, tzinfo=timezone.utc), + ) + + assert "prove requester and reviewer resolve to distinct authoritative actor identities" in handoff.next_action From 8432d1c486c94078a92bd5bd5e0027bc19f7c65e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:10:21 -0700 Subject: [PATCH 10/41] fix(validity): require authoritative actor identity separation --- .../src/orgmetra_validity_analysis/handoff.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py index e3a347723..ad257a098 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py @@ -38,7 +38,8 @@ _NEXT_ACTION = ( "Within tenant_record_id, re-resolve the validation study, Job, predictor, criterion, " "population, decision-policy, analysis-plan, requester, and reviewer references; prove " - "the predictor/criterion/population cases belong to the exact study and Job; then let an " + "requester and reviewer resolve to distinct authoritative actor identities; prove the " + "predictor/criterion/population cases belong to the exact study and Job; then let an " "approved offline validation worker invoke only the pinned fast-mlsirm revision. Preserve " "the resulting model/provenance diagnostics as draft scientific evidence for an " "accountable human reviewer; never convert the result directly into an employment decision." From 68a15c4dcfa4ac38820ac6e902f320f9f38e1e10 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:10:43 -0700 Subject: [PATCH 11/41] docs(validity): trace authoritative actor separation --- docs/traceability/validation-analysis-handoff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md index c1d53a51f..2fb472dbb 100644 --- a/docs/traceability/validation-analysis-handoff.md +++ b/docs/traceability/validation-analysis-handoff.md @@ -11,7 +11,7 @@ Can an organization send one exact, reviewable validation study to its statistic | Exact study scope | tenant, validation-study, Job, predictor, criterion, population, decision-policy, and analysis-plan references plus digests | namespace/UUID/digest regressions | | Dependency integrity | immutable fast-mlsirm commit `04d0bc21a2a20693bcf16108cd76d394fe844d23` | malformed and unreviewed revision rejection | | Privacy minimization | no raw person-level values in canonical handoff | canonical-payload regression and redacted repr | -| Human authority | distinct requester/reviewer and `human_review_required=true` | direct-construction fail-closed regressions | +| Human authority | requester/reviewer references must differ, and the host must re-resolve both within the tenant and prove they resolve to distinct authoritative actor identities before execution | direct-construction fail-closed regression plus `test_next_action_requires_resolved_actor_identity_separation` | | Scientific evidence | effect estimate, uncertainty interval, sample size, missingness summary, convergence diagnostics | immutable required-result-evidence regression | | Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | | Reproducibility | canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | deterministic serialization/digest tests | From f0c30c5e6cfd6cb90afcbef1efd0ba8825f3fd52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:10:48 -0700 Subject: [PATCH 12/41] docs(validity): record identity-separation repair --- packages/validity-analysis/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/validity-analysis/CHANGELOG.md b/packages/validity-analysis/CHANGELOG.md index 821ff22f6..662b49f60 100644 --- a/packages/validity-analysis/CHANGELOG.md +++ b/packages/validity-analysis/CHANGELOG.md @@ -4,4 +4,5 @@ - Add a governed, value-minimized criterion-related validity analysis handoff. - Pin the reviewed read-only fast-mlsirm dependency revision. -- Require separate requester/reviewer authority, deterministic canonical evidence, and 100% owned production statement/branch coverage. +- Require separate requester/reviewer references and authoritative tenant-scoped re-resolution proving they resolve to distinct actor identities before execution. +- Require deterministic canonical evidence and 100% owned production statement/branch coverage. From d0c06d21c5ad6d3a0e6ded11f40b04d220d57cba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 11:49:12 +0900 Subject: [PATCH 13/41] feat(validity): validate pinned numerical result envelopes --- ...ned-selection-validity-analysis-handoff.md | 4 +- .../validation-analysis-handoff-references.md | 3 + .../validation-analysis-handoff.md | 3 +- packages/validity-analysis/CHANGELOG.md | 1 + packages/validity-analysis/README.md | 5 +- .../orgmetra_validity_analysis/__init__.py | 6 +- .../src/orgmetra_validity_analysis/result.py | 231 ++++++++++++++++++ .../validity-analysis/tests/test_result.py | 178 ++++++++++++++ 8 files changed, 427 insertions(+), 4 deletions(-) create mode 100644 packages/validity-analysis/src/orgmetra_validity_analysis/result.py create mode 100644 packages/validity-analysis/tests/test_result.py diff --git a/docs/adr/0027-governed-selection-validity-analysis-handoff.md b/docs/adr/0027-governed-selection-validity-analysis-handoff.md index 14fe92e05..a27a0b09d 100644 --- a/docs/adr/0027-governed-selection-validity-analysis-handoff.md +++ b/docs/adr/0027-governed-selection-validity-analysis-handoff.md @@ -26,6 +26,8 @@ Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnaly - remains `not_executed`, `scientific_evidence_only`, and human-review-required; - produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. +The same package also validates `ValidationAnalysisResult` envelopes returned by the approved offline worker. A result must link to the handoff digest and the same pinned revision, identify a Rust CPU or GPU backend and precision, provide finite effect and interval values, match its sample size to aggregate missingness counts, and include explicit convergence diagnostics. A nonconverged result remains typed scientific evidence requiring human review; it cannot be treated as a valid selection procedure or an employment decision. + The package does not invoke fast-mlsirm. An approved offline worker is the later execution boundary. Before execution, the Orgmetra host must re-resolve every reference inside the tenant, verify exact study/Job membership and evidence provenance, and prove requester/reviewer identities are distinct authoritative actors. ## Consequences @@ -41,7 +43,7 @@ The package does not invoke fast-mlsirm. An approved offline worker is the later - This slice does not execute a statistical model, estimate validity, correct for measurement error/range restriction, evaluate adverse impact, or assert legal compliance. - Sampling design, estimator choice, missing-data treatment, reliability evidence, multiplicity, transportability, fairness analysis, and model diagnostics must be encoded in the referenced analysis plan and reviewed before execution. -- A future execution/result adapter must validate the returned model/provenance schema before any result is attached to an Orgmetra study. +- The package validates the result envelope, but a future execution adapter must still re-resolve the handoff references, verify the result provenance artifact, and attach evidence only after accountable human review. ## Verification diff --git a/docs/doctoring/validation-analysis-handoff-references.md b/docs/doctoring/validation-analysis-handoff-references.md index c2e31dbd2..d5de152f1 100644 --- a/docs/doctoring/validation-analysis-handoff-references.md +++ b/docs/doctoring/validation-analysis-handoff-references.md @@ -10,8 +10,11 @@ Society for Industrial and Organizational Psychology. (2018). *Principles for th ContextualWisdomLab. (2026). *fast-mlsirm* (Commit 04d0bc21a2a20693bcf16108cd76d394fe844d23) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/fast-mlsirm/tree/04d0bc21a2a20693bcf16108cd76d394fe844d23 +Tabassi, E. (2023). *Artificial intelligence risk management framework (AI RMF 1.0)* (NIST AI 100-1). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.AI.100-1 + ## Decision notes - 29 C.F.R. §§ 1607.5 and 1607.14 support keeping criterion-related validity evidence tied to an explicit study design, job relevance, accuracy, reporting, and documentation rather than treating a bare coefficient as sufficient evidence. - The SIOP Principles are the professional validation baseline used for the handoff's evidence-and-human-review posture. - The fast-mlsirm commit is recorded as a read-only dependency coordinate only. This Orgmetra slice does not modify or duplicate its numerical implementation. +- NIST AI RMF's govern, map, measure, and manage functions support preserving backend, precision, provenance, convergence, and human-review fields as inspectable result evidence rather than treating a model response as an autonomous decision. diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md index 2fb472dbb..068886737 100644 --- a/docs/traceability/validation-analysis-handoff.md +++ b/docs/traceability/validation-analysis-handoff.md @@ -13,6 +13,7 @@ Can an organization send one exact, reviewable validation study to its statistic | Privacy minimization | no raw person-level values in canonical handoff | canonical-payload regression and redacted repr | | Human authority | requester/reviewer references must differ, and the host must re-resolve both within the tenant and prove they resolve to distinct authoritative actor identities before execution | direct-construction fail-closed regression plus `test_next_action_requires_resolved_actor_identity_separation` | | Scientific evidence | effect estimate, uncertainty interval, sample size, missingness summary, convergence diagnostics | immutable required-result-evidence regression | +| Numerical result boundary | handoff digest, pinned fast-mlsirm revision, Rust CPU/GPU backend, precision, finite estimate/interval, aggregate missingness, explicit convergence state | `ValidationAnalysisResult` invariant and canonicalization regressions | | Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | | Reproducibility | canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | deterministic serialization/digest tests | @@ -20,4 +21,4 @@ Can an organization send one exact, reviewable validation study to its statistic `implemented_on_active_pr`. -Protected `develop` does **not** gain numerical validity computation from this slice. The handoff is execution preparation only. A future worker/result boundary must independently earn tests for the exact returned numerical/provenance contract before the result can become protected Orgmetra evidence. +Protected `develop` does **not** gain numerical validity computation from this slice. The handoff is execution preparation only. The active package now validates the minimum returned numerical/provenance envelope, but protected Orgmetra evidence still requires host re-resolution, result-artifact verification, terminal checks, independent review, and accountable human interpretation. diff --git a/packages/validity-analysis/CHANGELOG.md b/packages/validity-analysis/CHANGELOG.md index 662b49f60..95cab8f32 100644 --- a/packages/validity-analysis/CHANGELOG.md +++ b/packages/validity-analysis/CHANGELOG.md @@ -6,3 +6,4 @@ - Pin the reviewed read-only fast-mlsirm dependency revision. - Require separate requester/reviewer references and authoritative tenant-scoped re-resolution proving they resolve to distinct actor identities before execution. - Require deterministic canonical evidence and 100% owned production statement/branch coverage. +- Validate a digest-linked Rust CPU/GPU result envelope with finite estimates, aggregate missingness, and explicit convergence or nonconvergence diagnostics. diff --git a/packages/validity-analysis/README.md b/packages/validity-analysis/README.md index c7e14eb37..feec8db9a 100644 --- a/packages/validity-analysis/README.md +++ b/packages/validity-analysis/README.md @@ -1,6 +1,6 @@ # Orgmetra validity-analysis handoff -This package creates an immutable **selection-validity analysis handoff**. It is the boundary between Orgmetra's authoritative validation-study evidence and numerical work owned by `ContextualWisdomLab/fast-mlsirm`. +This package creates an immutable **selection-validity analysis handoff** and validates the matching numerical result envelope. It is the boundary between Orgmetra's authoritative validation-study evidence and numerical work owned by `ContextualWisdomLab/fast-mlsirm`. ## What it does @@ -8,9 +8,12 @@ This package creates an immutable **selection-validity analysis handoff**. It is The resulting canonical JSON is digest-addressable, contains no raw person-level predictor or criterion values, and remains `not_executed`. Required result evidence is explicit: effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics. +`ValidationAnalysisResult` accepts only a result linked to the handoff digest and the same reviewed fast-mlsirm revision. It records the Rust CPU/GPU backend, precision, aggregate missingness counts, finite effect and interval values, and explicit convergence or nonconvergence diagnostics. It never promotes a result to an employment decision; human review remains mandatory. + ## What it does not do - It does **not** run statistics. +- It does **not** run or reproduce the fast-mlsirm numerical kernel. - It does **not** query fast-mlsirm or any other CWL application's database. - It does **not** claim that a selection procedure is valid. - It does **not** interpret adverse impact. diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py b/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py index d7d15691c..81acbc0d1 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/__init__.py @@ -1,13 +1,17 @@ -"""Public governed selection-validity analysis handoff contract.""" +"""Public governed selection-validity analysis handoff and result contracts.""" from .handoff import ( REVIEWED_FAST_MLSIRM_REVISION, ValidationAnalysisHandoff, build_validation_analysis_handoff, ) +from .result import ConvergenceDiagnostics, MissingnessSummary, ValidationAnalysisResult __all__ = [ "REVIEWED_FAST_MLSIRM_REVISION", "ValidationAnalysisHandoff", "build_validation_analysis_handoff", + "ConvergenceDiagnostics", + "MissingnessSummary", + "ValidationAnalysisResult", ] diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py new file mode 100644 index 000000000..4f4d230e5 --- /dev/null +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -0,0 +1,231 @@ +"""Validate one immutable numerical result returned by the approved worker. + +Orgmetra does not fit a model in this package. It accepts only a bounded, +digest-linked result envelope from the pinned ``fast-mlsirm`` worker so that +nonconverged or malformed output cannot be presented as an employment +decision. +""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from hashlib import sha256 +import json +from math import isfinite +from numbers import Real + +from .handoff import ( + REVIEWED_FAST_MLSIRM_REVISION, + _canonical_timestamp, + _validate_code, + _validate_digest, + _validate_kernel_revision, + _validate_operational_uuid, + _validate_reference, +) + +_RESULT_AUTHORITY = "scientific_evidence_only" +_EXECUTION_STATE = "completed" +_ALLOWED_BACKENDS = frozenset({"rust_cpu", "rust_gpu"}) +_ALLOWED_PRECISIONS = frozenset({"f64", "f32"}) + + +def _validate_nonnegative_integer(value: object, field_name: str) -> None: + """Require a real non-negative integer without accepting booleans.""" + if type(value) is not int or value < 0: + raise ValueError(f"{field_name} must be a non-negative integer") + + +def _validate_positive_integer(value: object, field_name: str) -> None: + """Require a real positive integer without accepting booleans.""" + if type(value) is not int or value <= 0: + raise ValueError(f"{field_name} must be a positive integer") + + +def _finite_number(value: object, field_name: str) -> float: + """Return one finite real number and reject booleans or non-numeric text.""" + if isinstance(value, bool) or not isinstance(value, Real): + raise ValueError(f"{field_name} must be a finite number") + number = float(value) + if not isfinite(number): + raise ValueError(f"{field_name} must be a finite number") + return number + + +@dataclass(frozen=True, slots=True) +class MissingnessSummary: + """Describe missingness counts without carrying person-level observations.""" + + total_observations: int + complete_observations: int + missing_predictor_observations: int + missing_criterion_observations: int + + def __post_init__(self) -> None: + """Reject impossible counts before a result can be correlated.""" + for field_name in ( + "total_observations", + "complete_observations", + "missing_predictor_observations", + "missing_criterion_observations", + ): + _validate_nonnegative_integer(getattr(self, field_name), field_name) + if self.total_observations == 0: + raise ValueError("total_observations must be positive") + if self.complete_observations > self.total_observations: + raise ValueError("complete_observations cannot exceed total_observations") + if self.missing_predictor_observations > self.total_observations: + raise ValueError("missing_predictor_observations cannot exceed total_observations") + if self.missing_criterion_observations > self.total_observations: + raise ValueError("missing_criterion_observations cannot exceed total_observations") + + def to_dict(self) -> dict[str, int]: + """Return deterministic count fields for the canonical result JSON.""" + return { + "complete_observations": self.complete_observations, + "missing_criterion_observations": self.missing_criterion_observations, + "missing_predictor_observations": self.missing_predictor_observations, + "total_observations": self.total_observations, + } + + +@dataclass(frozen=True, slots=True) +class ConvergenceDiagnostics: + """Record convergence evidence while preserving an explicit failure state.""" + + converged: bool + iterations: int + objective_value: Real + maximum_gradient: Real + failure_code: str | None = None + + def __post_init__(self) -> None: + """Require diagnostics that distinguish convergence from a failed fit.""" + if type(self.converged) is not bool: + raise ValueError("converged must be a boolean") + _validate_positive_integer(self.iterations, "iterations") + _finite_number(self.objective_value, "objective_value") + gradient = _finite_number(self.maximum_gradient, "maximum_gradient") + if gradient < 0: + raise ValueError("maximum_gradient must be non-negative") + if self.converged and self.failure_code is not None: + raise ValueError("failure_code must be absent for a converged result") + if not self.converged and ( + not isinstance(self.failure_code, str) or not self.failure_code + ): + raise ValueError("failure_code is required for a nonconverged result") + if self.failure_code is not None: + _validate_code(self.failure_code, "failure_code") + + def to_dict(self) -> dict[str, object]: + """Return deterministic convergence fields for the canonical result JSON.""" + payload: dict[str, object] = { + "converged": self.converged, + "iterations": self.iterations, + "maximum_gradient": float(self.maximum_gradient), + "objective_value": float(self.objective_value), + } + if self.failure_code is not None: + payload["failure_code"] = self.failure_code + return payload + + +@dataclass(frozen=True, slots=True, repr=False) +class ValidationAnalysisResult: + """Immutable, digest-linked scientific evidence returned by the offline worker.""" + + tenant_record_id: str + result_reference: str + handoff_digest: str + provenance_digest: str + fast_mlsirm_revision: str + model_code: str + backend: str + precision: str + effect_estimate: Real + uncertainty_lower: Real + uncertainty_upper: Real + sample_size: int + missingness_summary: MissingnessSummary + convergence_diagnostics: ConvergenceDiagnostics + completed_at: datetime + result_authority: str = _RESULT_AUTHORITY + execution_state: str = _EXECUTION_STATE + contains_raw_person_level_values: bool = False + human_review_required: bool = True + evidence_version: int = 1 + + def __post_init__(self) -> None: + """Fail closed on malformed, unlinked, or decision-like result data.""" + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + _validate_reference(self.result_reference, "validation_analysis_result", "result_reference") + _validate_digest(self.handoff_digest, "handoff_digest") + _validate_digest(self.provenance_digest, "provenance_digest") + _validate_kernel_revision(self.fast_mlsirm_revision) + _validate_code(self.model_code, "model_code") + if self.backend not in _ALLOWED_BACKENDS: + raise ValueError("backend must be rust_cpu or rust_gpu") + if self.precision not in _ALLOWED_PRECISIONS: + raise ValueError("precision must be f64 or f32") + estimate = _finite_number(self.effect_estimate, "effect_estimate") + lower = _finite_number(self.uncertainty_lower, "uncertainty_lower") + upper = _finite_number(self.uncertainty_upper, "uncertainty_upper") + if lower > upper: + raise ValueError("uncertainty_lower cannot exceed uncertainty_upper") + if not lower <= estimate <= upper: + raise ValueError("effect_estimate must be inside the uncertainty interval") + _validate_positive_integer(self.sample_size, "sample_size") + if not isinstance(self.missingness_summary, MissingnessSummary): + raise ValueError("missingness_summary must be a MissingnessSummary") + if not isinstance(self.convergence_diagnostics, ConvergenceDiagnostics): + raise ValueError("convergence_diagnostics must be ConvergenceDiagnostics") + if self.sample_size != self.missingness_summary.total_observations: + raise ValueError("sample_size must match total_observations") + _canonical_timestamp(self.completed_at) + if self.result_authority != _RESULT_AUTHORITY: + raise ValueError("result_authority must remain scientific_evidence_only") + if self.execution_state != _EXECUTION_STATE: + raise ValueError("execution_state must remain completed") + if self.contains_raw_person_level_values is not False: + raise ValueError("result must not contain raw person-level values") + if self.human_review_required is not True: + raise ValueError("human review is mandatory for validity interpretation") + if type(self.evidence_version) is not int or self.evidence_version != 1: + raise ValueError("evidence_version must remain 1") + + def __repr__(self) -> str: + """Return a redacted representation suitable for routine application logs.""" + return "ValidationAnalysisResult()" + + def canonical_json(self) -> str: + """Return deterministic, non-person-level JSON for audit correlation.""" + payload = { + "backend": self.backend, + "completed_at": _canonical_timestamp(self.completed_at), + "contains_raw_person_level_values": self.contains_raw_person_level_values, + "convergence_diagnostics": self.convergence_diagnostics.to_dict(), + "effect_estimate": float(self.effect_estimate), + "evidence_version": self.evidence_version, + "execution_state": self.execution_state, + "fast_mlsirm_revision": self.fast_mlsirm_revision, + "handoff_digest": self.handoff_digest, + "human_review_required": self.human_review_required, + "missingness_summary": self.missingness_summary.to_dict(), + "model_code": self.model_code, + "precision": self.precision, + "provenance_digest": self.provenance_digest, + "result_authority": self.result_authority, + "result_reference": self.result_reference, + "sample_size": self.sample_size, + "tenant_record_id": self.tenant_record_id, + "uncertainty_lower": float(self.uncertainty_lower), + "uncertainty_upper": float(self.uncertainty_upper), + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def sha256_digest(self) -> str: + """Return SHA-256 over the exact canonical result bytes.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + +__all__ = ["ConvergenceDiagnostics", "MissingnessSummary", "ValidationAnalysisResult"] diff --git a/packages/validity-analysis/tests/test_result.py b/packages/validity-analysis/tests/test_result.py new file mode 100644 index 000000000..c1f841080 --- /dev/null +++ b/packages/validity-analysis/tests/test_result.py @@ -0,0 +1,178 @@ +"""Regression tests for the bounded numerical result contract.""" + +from dataclasses import asdict, replace +from datetime import datetime, timezone +import json + +import pytest + +from orgmetra_validity_analysis import ( + ConvergenceDiagnostics, + MissingnessSummary, + REVIEWED_FAST_MLSIRM_REVISION, + ValidationAnalysisResult, +) + +TENANT = "10000000-0000-7000-8000-000000000001" +RESULT = "validation_analysis_result:11111111-1111-4111-8111-111111111111" +HANDOFF_DIGEST = "a" * 64 +PROVENANCE_DIGEST = "b" * 64 +COMPLETED_AT = datetime(2026, 8, 21, 7, 10, 11, 123456, tzinfo=timezone.utc) + + +def missingness() -> MissingnessSummary: + """Return one realistic aggregate-only missingness summary.""" + return MissingnessSummary( + total_observations=12, + complete_observations=10, + missing_predictor_observations=1, + missing_criterion_observations=1, + ) + + +def convergence(*, converged: bool = True) -> ConvergenceDiagnostics: + """Return one converged or explicitly nonconverged diagnostic record.""" + return ConvergenceDiagnostics( + converged=converged, + iterations=42, + objective_value=-12.5, + maximum_gradient=0.0001, + failure_code=None if converged else "maximum_iterations", + ) + + +def result(**overrides: object) -> ValidationAnalysisResult: + """Build one valid result envelope and apply targeted test overrides.""" + values: dict[str, object] = { + "tenant_record_id": TENANT, + "result_reference": RESULT, + "handoff_digest": HANDOFF_DIGEST, + "provenance_digest": PROVENANCE_DIGEST, + "fast_mlsirm_revision": REVIEWED_FAST_MLSIRM_REVISION, + "model_code": "mlsirm_criterion_related", + "backend": "rust_cpu", + "precision": "f64", + "effect_estimate": 0.42, + "uncertainty_lower": 0.10, + "uncertainty_upper": 0.70, + "sample_size": 12, + "missingness_summary": missingness(), + "convergence_diagnostics": convergence(), + "completed_at": COMPLETED_AT, + } + values.update(overrides) + return ValidationAnalysisResult(**values) + + +def test_aggregate_evidence_is_deterministic_and_redacted() -> None: + """Serialize only aggregate evidence and preserve exact replay bytes.""" + candidate = result() + payload = json.loads(candidate.canonical_json()) + + assert payload["tenant_record_id"] == TENANT + assert payload["backend"] == "rust_cpu" + assert payload["precision"] == "f64" + assert payload["execution_state"] == "completed" + assert payload["result_authority"] == "scientific_evidence_only" + assert payload["missingness_summary"]["total_observations"] == 12 + assert payload["convergence_diagnostics"]["converged"] is True + assert "person_record" not in candidate.canonical_json() + assert repr(candidate) == "ValidationAnalysisResult()" + assert len(candidate.sha256_digest()) == 64 + assert candidate.canonical_json() == result().canonical_json() + + +def test_gpu_and_nonconverged_result_are_explicitly_typed() -> None: + """Record GPU provenance and a typed nonconvergence state without promotion.""" + candidate = result( + backend="rust_gpu", + precision="f32", + convergence_diagnostics=convergence(converged=False), + ) + payload = json.loads(candidate.canonical_json()) + assert payload["backend"] == "rust_gpu" + assert payload["precision"] == "f32" + assert payload["convergence_diagnostics"]["failure_code"] == "maximum_iterations" + + +@pytest.mark.parametrize( + "bad", + [ + {"total_observations": True}, + {"total_observations": -1}, + {"total_observations": 0}, + {"complete_observations": 13}, + {"missing_predictor_observations": 13}, + {"missing_criterion_observations": 13}, + ], +) +def test_missingness_rejects_invalid_counts(bad: dict[str, object]) -> None: + """Reject booleans, negative counts, empty samples, and impossible totals.""" + values = asdict(missingness()) + values.update(bad) + with pytest.raises(ValueError): + MissingnessSummary(**values) + + +@pytest.mark.parametrize("bad", [True, 0, -1]) +def test_positive_integer_validation_rejects_nonpositive_values(bad: object) -> None: + """Exercise strict sample and iteration bounds.""" + with pytest.raises(ValueError, match="positive integer"): + ConvergenceDiagnostics(True, bad, -1.0, 0.1) + with pytest.raises(ValueError, match="positive integer"): + result(sample_size=bad) + + +@pytest.mark.parametrize("bad", [True, "0.1", float("nan"), float("inf")]) +def test_numeric_fields_reject_boolean_text_and_nonfinite_values(bad: object) -> None: + """Do not accept values that cannot be represented as finite scientific evidence.""" + with pytest.raises(ValueError, match="finite number"): + ConvergenceDiagnostics(True, 1, bad, 0.1) + with pytest.raises(ValueError, match="finite number"): + result(effect_estimate=bad) + + +def test_negative_gradient_and_invalid_convergence_states_fail_closed() -> None: + """Require explicit and internally consistent convergence diagnostics.""" + with pytest.raises(ValueError, match="non-negative"): + ConvergenceDiagnostics(True, 1, 1.0, -0.1) + with pytest.raises(ValueError, match="boolean"): + ConvergenceDiagnostics(1, 1, 1.0, 0.1) + with pytest.raises(ValueError, match="absent"): + ConvergenceDiagnostics(True, 1, 1.0, 0.1, "failed_fit") + with pytest.raises(ValueError, match="required"): + ConvergenceDiagnostics(False, 1, 1.0, 0.1) + with pytest.raises(ValueError, match="required"): + ConvergenceDiagnostics(False, 1, 1.0, 0.1, "") + + +@pytest.mark.parametrize( + "field,bad,match", + [ + ("backend", "numpy", "backend"), + ("precision", "float16", "precision"), + ("uncertainty_lower", 0.8, "uncertainty_lower"), + ("uncertainty_upper", 0.0, "uncertainty_lower"), + ("effect_estimate", 0.8, "effect_estimate"), + ("sample_size", 11, "sample_size"), + ("result_authority", "employment_decision", "result_authority"), + ("execution_state", "not_executed", "execution_state"), + ("contains_raw_person_level_values", True, "raw person-level"), + ("human_review_required", False, "human review"), + ("evidence_version", 2, "evidence_version"), + ], +) +def test_result_invariants_cannot_be_weakened(field: str, bad: object, match: str) -> None: + """Reject malformed intervals, lineage, or governance flags.""" + with pytest.raises(ValueError, match=match): + replace(result(), **{field: bad}) + + +def test_result_requires_canonical_timestamp_and_aggregate_types() -> None: + """Reject a naive completion time and non-summary diagnostic objects.""" + with pytest.raises(ValueError, match="requested_at"): + result(completed_at=datetime(2026, 8, 21, 7, 10)) + with pytest.raises(ValueError, match="missingness_summary"): + result(missingness_summary=object()) + with pytest.raises(ValueError, match="convergence_diagnostics"): + result(convergence_diagnostics=object()) From 0a3173784eeea69a12e3384fbcc3679b5b9e2b6b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:03:48 -0700 Subject: [PATCH 14/41] test(validity): reject impossible complete missingness totals --- packages/validity-analysis/tests/test_result.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/validity-analysis/tests/test_result.py b/packages/validity-analysis/tests/test_result.py index c1f841080..a75518ee8 100644 --- a/packages/validity-analysis/tests/test_result.py +++ b/packages/validity-analysis/tests/test_result.py @@ -104,6 +104,8 @@ def test_gpu_and_nonconverged_result_are_explicitly_typed() -> None: {"complete_observations": 13}, {"missing_predictor_observations": 13}, {"missing_criterion_observations": 13}, + {"complete_observations": 12, "missing_predictor_observations": 1}, + {"complete_observations": 12, "missing_criterion_observations": 1}, ], ) def test_missingness_rejects_invalid_counts(bad: dict[str, object]) -> None: From 2419aa98eeeb67add4ccdd92c5b494479b0cc234 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:04:16 -0700 Subject: [PATCH 15/41] fix(validity): reject impossible missingness summaries --- .../src/orgmetra_validity_analysis/result.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py index 4f4d230e5..4ccce8e66 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -78,6 +78,14 @@ def __post_init__(self) -> None: raise ValueError("missing_predictor_observations cannot exceed total_observations") if self.missing_criterion_observations > self.total_observations: raise ValueError("missing_criterion_observations cannot exceed total_observations") + if self.complete_observations + self.missing_predictor_observations > self.total_observations: + raise ValueError( + "complete_observations and missing_predictor_observations cannot overlap" + ) + if self.complete_observations + self.missing_criterion_observations > self.total_observations: + raise ValueError( + "complete_observations and missing_criterion_observations cannot overlap" + ) def to_dict(self) -> dict[str, int]: """Return deterministic count fields for the canonical result JSON.""" From c17936ce1ebae673208f8c76dcee90cb2ae80285 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:05:06 -0700 Subject: [PATCH 16/41] test(validity): reject subclassed result evidence --- .../validity-analysis/tests/test_result.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/validity-analysis/tests/test_result.py b/packages/validity-analysis/tests/test_result.py index a75518ee8..4209cb203 100644 --- a/packages/validity-analysis/tests/test_result.py +++ b/packages/validity-analysis/tests/test_result.py @@ -171,10 +171,37 @@ def test_result_invariants_cannot_be_weakened(field: str, bad: object, match: st def test_result_requires_canonical_timestamp_and_aggregate_types() -> None: - """Reject a naive completion time and non-summary diagnostic objects.""" + """Reject naive times, non-contract objects, and subclass method overrides.""" + + class LeakyMissingnessSummary(MissingnessSummary): + def to_dict(self) -> dict[str, object]: + return {**super().to_dict(), "person_record": "must-not-serialize"} + + class LeakyConvergenceDiagnostics(ConvergenceDiagnostics): + def to_dict(self) -> dict[str, object]: + return {**super().to_dict(), "employment_decision": "auto_reject"} + with pytest.raises(ValueError, match="requested_at"): result(completed_at=datetime(2026, 8, 21, 7, 10)) with pytest.raises(ValueError, match="missingness_summary"): result(missingness_summary=object()) with pytest.raises(ValueError, match="convergence_diagnostics"): result(convergence_diagnostics=object()) + with pytest.raises(ValueError, match="missingness_summary"): + result( + missingness_summary=LeakyMissingnessSummary( + total_observations=12, + complete_observations=10, + missing_predictor_observations=1, + missing_criterion_observations=1, + ) + ) + with pytest.raises(ValueError, match="convergence_diagnostics"): + result( + convergence_diagnostics=LeakyConvergenceDiagnostics( + converged=True, + iterations=42, + objective_value=-12.5, + maximum_gradient=0.0001, + ) + ) From f31422a473c59a686e4195e592ae629ad7a7eac9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:05:29 -0700 Subject: [PATCH 17/41] fix(validity): require exact result evidence types --- .../src/orgmetra_validity_analysis/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py index 4ccce8e66..25ce3410e 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -183,9 +183,9 @@ def __post_init__(self) -> None: if not lower <= estimate <= upper: raise ValueError("effect_estimate must be inside the uncertainty interval") _validate_positive_integer(self.sample_size, "sample_size") - if not isinstance(self.missingness_summary, MissingnessSummary): + if type(self.missingness_summary) is not MissingnessSummary: raise ValueError("missingness_summary must be a MissingnessSummary") - if not isinstance(self.convergence_diagnostics, ConvergenceDiagnostics): + if type(self.convergence_diagnostics) is not ConvergenceDiagnostics: raise ValueError("convergence_diagnostics must be ConvergenceDiagnostics") if self.sample_size != self.missingness_summary.total_observations: raise ValueError("sample_size must match total_observations") From e11ae2a062364911fac830293906366fc53a3915 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:05:47 -0700 Subject: [PATCH 18/41] docs(validity): record result evidence hardening --- packages/validity-analysis/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/validity-analysis/CHANGELOG.md b/packages/validity-analysis/CHANGELOG.md index 95cab8f32..9e9e67b43 100644 --- a/packages/validity-analysis/CHANGELOG.md +++ b/packages/validity-analysis/CHANGELOG.md @@ -7,3 +7,5 @@ - Require separate requester/reviewer references and authoritative tenant-scoped re-resolution proving they resolve to distinct actor identities before execution. - Require deterministic canonical evidence and 100% owned production statement/branch coverage. - Validate a digest-linked Rust CPU/GPU result envelope with finite estimates, aggregate missingness, and explicit convergence or nonconvergence diagnostics. +- Reject impossible aggregate missingness where complete observations overlap either predictor-missing or criterion-missing counts beyond the sample total. +- Require exact governed missingness/convergence runtime types so subclass method overrides cannot inject unreviewed or person-level fields into canonical result evidence. From e9a7a31fc9066e6ba6e8f81954941c8efa795842 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:05:56 -0700 Subject: [PATCH 19/41] docs(validity): define hardened result envelope --- packages/validity-analysis/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validity-analysis/README.md b/packages/validity-analysis/README.md index feec8db9a..527241d5b 100644 --- a/packages/validity-analysis/README.md +++ b/packages/validity-analysis/README.md @@ -8,7 +8,7 @@ This package creates an immutable **selection-validity analysis handoff** and va The resulting canonical JSON is digest-addressable, contains no raw person-level predictor or criterion values, and remains `not_executed`. Required result evidence is explicit: effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics. -`ValidationAnalysisResult` accepts only a result linked to the handoff digest and the same reviewed fast-mlsirm revision. It records the Rust CPU/GPU backend, precision, aggregate missingness counts, finite effect and interval values, and explicit convergence or nonconvergence diagnostics. It never promotes a result to an employment decision; human review remains mandatory. +`ValidationAnalysisResult` accepts only a result linked to the handoff digest and the same reviewed fast-mlsirm revision. It records the Rust CPU/GPU backend, precision, aggregate missingness counts, finite effect and interval values, and explicit convergence or nonconvergence diagnostics. Missingness counts must be internally possible: complete observations cannot overlap either predictor-missing or criterion-missing observations beyond the declared sample total. The result envelope accepts only the exact governed `MissingnessSummary` and `ConvergenceDiagnostics` runtime types, preventing subclass method overrides from adding unreviewed or person-level fields to canonical audit evidence. It never promotes a result to an employment decision; human review remains mandatory. ## What it does not do From 8b265203c04b24ca3e7526b81914e1ac03776d83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:06:08 -0700 Subject: [PATCH 20/41] docs(validity): trace result integrity regressions --- docs/traceability/validation-analysis-handoff.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md index 068886737..af8f6b622 100644 --- a/docs/traceability/validation-analysis-handoff.md +++ b/docs/traceability/validation-analysis-handoff.md @@ -10,10 +10,10 @@ Can an organization send one exact, reviewable validation study to its statistic |---|---|---| | Exact study scope | tenant, validation-study, Job, predictor, criterion, population, decision-policy, and analysis-plan references plus digests | namespace/UUID/digest regressions | | Dependency integrity | immutable fast-mlsirm commit `04d0bc21a2a20693bcf16108cd76d394fe844d23` | malformed and unreviewed revision rejection | -| Privacy minimization | no raw person-level values in canonical handoff | canonical-payload regression and redacted repr | +| Privacy minimization | no raw person-level values in canonical handoff or result; result canonicalization accepts only exact governed missingness/convergence runtime types | canonical-payload/redacted-repr regressions plus subclass-injection rejection | | Human authority | requester/reviewer references must differ, and the host must re-resolve both within the tenant and prove they resolve to distinct authoritative actor identities before execution | direct-construction fail-closed regression plus `test_next_action_requires_resolved_actor_identity_separation` | -| Scientific evidence | effect estimate, uncertainty interval, sample size, missingness summary, convergence diagnostics | immutable required-result-evidence regression | -| Numerical result boundary | handoff digest, pinned fast-mlsirm revision, Rust CPU/GPU backend, precision, finite estimate/interval, aggregate missingness, explicit convergence state | `ValidationAnalysisResult` invariant and canonicalization regressions | +| Scientific evidence | effect estimate, uncertainty interval, sample size, internally possible aggregate missingness, convergence diagnostics | immutable required-result-evidence regression plus impossible-missingness rejection | +| Numerical result boundary | handoff digest, pinned fast-mlsirm revision, Rust CPU/GPU backend, precision, finite estimate/interval, aggregate missingness, explicit convergence state | `ValidationAnalysisResult` invariant/canonicalization regressions and exact-runtime-type checks | | Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | | Reproducibility | canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | deterministic serialization/digest tests | @@ -21,4 +21,4 @@ Can an organization send one exact, reviewable validation study to its statistic `implemented_on_active_pr`. -Protected `develop` does **not** gain numerical validity computation from this slice. The handoff is execution preparation only. The active package now validates the minimum returned numerical/provenance envelope, but protected Orgmetra evidence still requires host re-resolution, result-artifact verification, terminal checks, independent review, and accountable human interpretation. +Protected `develop` does **not** gain numerical validity computation from this slice. The handoff is execution preparation only. The active package now validates the minimum returned numerical/provenance envelope, including missingness consistency and exact aggregate-evidence runtime types, but protected Orgmetra evidence still requires host re-resolution, result-artifact verification, terminal checks, independent review, and accountable human interpretation. From b33cbdaed094e1ac79f009b193f05debdb55323e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:06:25 -0700 Subject: [PATCH 21/41] docs(validity): bind result integrity in ADR --- .../0027-governed-selection-validity-analysis-handoff.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/adr/0027-governed-selection-validity-analysis-handoff.md b/docs/adr/0027-governed-selection-validity-analysis-handoff.md index a27a0b09d..6db6c3a1c 100644 --- a/docs/adr/0027-governed-selection-validity-analysis-handoff.md +++ b/docs/adr/0027-governed-selection-validity-analysis-handoff.md @@ -26,7 +26,7 @@ Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnaly - remains `not_executed`, `scientific_evidence_only`, and human-review-required; - produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. -The same package also validates `ValidationAnalysisResult` envelopes returned by the approved offline worker. A result must link to the handoff digest and the same pinned revision, identify a Rust CPU or GPU backend and precision, provide finite effect and interval values, match its sample size to aggregate missingness counts, and include explicit convergence diagnostics. A nonconverged result remains typed scientific evidence requiring human review; it cannot be treated as a valid selection procedure or an employment decision. +The same package also validates `ValidationAnalysisResult` envelopes returned by the approved offline worker. A result must link to the handoff digest and the same pinned revision, identify a Rust CPU or GPU backend and precision, provide finite effect and interval values, match its sample size to aggregate missingness counts, reject impossible complete-versus-missing count combinations, and include explicit convergence diagnostics. The canonicalization boundary accepts only the exact governed `MissingnessSummary` and `ConvergenceDiagnostics` runtime types so subclass method overrides cannot add unreviewed or person-level fields to immutable result evidence. A nonconverged result remains typed scientific evidence requiring human review; it cannot be treated as a valid selection procedure or an employment decision. The package does not invoke fast-mlsirm. An approved offline worker is the later execution boundary. Before execution, the Orgmetra host must re-resolve every reference inside the tenant, verify exact study/Job membership and evidence provenance, and prove requester/reviewer identities are distinct authoritative actors. @@ -36,6 +36,8 @@ The package does not invoke fast-mlsirm. An approved offline worker is the later - Statistical work cannot silently drift to an unreviewed fast-mlsirm revision. - A buyer or auditor can identify exactly which governed study evidence a result was supposed to analyze without copying raw worker data into the handoff. +- Aggregate missingness evidence cannot claim all observations are complete while simultaneously reporting predictor- or criterion-missing observations. +- Result canonicalization cannot be extended by an unreviewed subclass to serialize extra decision-like or person-level fields. - Human interpretation remains explicit and separate from numerical output. - The dedicated-writer boundary remains intact: Orgmetra consumes only a pinned foreign revision/contract boundary and never mutates fast-mlsirm. @@ -47,7 +49,7 @@ The package does not invoke fast-mlsirm. An approved offline worker is the later ## Verification -The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, and 100% owned production statement/branch coverage. The repository-wide ADR numbering regression also fails closed if integration reuses an existing decision number. +The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, impossible aggregate missingness rejection, exact governed aggregate-evidence runtime types, and 100% owned production statement/branch coverage. The repository-wide ADR numbering regression also fails closed if integration reuses an existing decision number. ## References From 081942a723c2ae2cb9bb98a8ffd0b33b72a8bd74 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 12:11:13 +0900 Subject: [PATCH 22/41] test(validity): cover criterion overlap guard --- packages/validity-analysis/tests/test_result.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/validity-analysis/tests/test_result.py b/packages/validity-analysis/tests/test_result.py index 4209cb203..ab71fdef3 100644 --- a/packages/validity-analysis/tests/test_result.py +++ b/packages/validity-analysis/tests/test_result.py @@ -105,7 +105,11 @@ def test_gpu_and_nonconverged_result_are_explicitly_typed() -> None: {"missing_predictor_observations": 13}, {"missing_criterion_observations": 13}, {"complete_observations": 12, "missing_predictor_observations": 1}, - {"complete_observations": 12, "missing_criterion_observations": 1}, + { + "complete_observations": 12, + "missing_predictor_observations": 0, + "missing_criterion_observations": 1, + }, ], ) def test_missingness_rejects_invalid_counts(bad: dict[str, object]) -> None: From d6bb5f27603efbebe31a14d4a21e919de5d01cce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:22:20 -0700 Subject: [PATCH 23/41] test(validity): reject temporal evidence subclasses --- .../tests/test_temporal_evidence_integrity.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 packages/validity-analysis/tests/test_temporal_evidence_integrity.py diff --git a/packages/validity-analysis/tests/test_temporal_evidence_integrity.py b/packages/validity-analysis/tests/test_temporal_evidence_integrity.py new file mode 100644 index 000000000..e47628263 --- /dev/null +++ b/packages/validity-analysis/tests/test_temporal_evidence_integrity.py @@ -0,0 +1,84 @@ +"""Regression coverage for selection-validity temporal evidence integrity.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest + +from orgmetra_validity_analysis import ( + ConvergenceDiagnostics, + MissingnessSummary, + REVIEWED_FAST_MLSIRM_REVISION, + ValidationAnalysisResult, + build_validation_analysis_handoff, +) + + +class ForgedDateTime(datetime): + """Datetime subclass able to forge canonical validation evidence.""" + + def astimezone(self, tz=None): # type: ignore[no-untyped-def] + """Keep the hostile subclass alive across UTC normalization.""" + return self + + def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] + """Return an instant different from the underlying evidence instant.""" + return "2099-12-31T23:59:59+00:00" + + +def test_handoff_rejects_datetime_subclass_that_can_forge_requested_at() -> None: + """Handoff canonical evidence must not invoke caller-overridable datetime methods.""" + with pytest.raises(ValueError, match="requested_at"): + build_validation_analysis_handoff( + tenant_record_id="10000000-0000-7000-8000-000000000001", + handoff_reference="validation_analysis_handoff:11111111-1111-4111-8111-111111111111", + validation_study_reference="validation_study:22222222-2222-4222-8222-222222222222", + job_profile_reference="job_profile:33333333-3333-4333-8333-333333333333", + predictor_snapshot_reference="predictor_snapshot:44444444-4444-4444-8444-444444444444", + predictor_snapshot_digest="a" * 64, + criterion_snapshot_reference="criterion_snapshot:55555555-5555-4555-8555-555555555555", + criterion_snapshot_digest="b" * 64, + population_snapshot_reference="study_population_snapshot:66666666-6666-4666-8666-666666666666", + population_snapshot_digest="c" * 64, + decision_policy_reference="decision_policy:77777777-7777-4777-8777-777777777777", + decision_policy_digest="d" * 64, + analysis_plan_reference="validation_analysis_plan:88888888-8888-4888-8888-888888888888", + analysis_plan_digest="e" * 64, + actor_reference="actor:99999999-9999-4999-8999-999999999999", + reviewer_reference="actor:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + fast_mlsirm_revision=REVIEWED_FAST_MLSIRM_REVISION, + requested_at=ForgedDateTime(2026, 8, 21, 4, 45, tzinfo=timezone.utc), + ) + + +def test_result_rejects_datetime_subclass_that_can_forge_completed_at() -> None: + """Result canonical evidence must not invoke caller-overridable datetime methods.""" + with pytest.raises(ValueError, match="requested_at"): + ValidationAnalysisResult( + tenant_record_id="10000000-0000-7000-8000-000000000001", + result_reference="validation_analysis_result:11111111-1111-4111-8111-111111111111", + handoff_digest="a" * 64, + provenance_digest="b" * 64, + fast_mlsirm_revision=REVIEWED_FAST_MLSIRM_REVISION, + model_code="mlsirm_criterion_related", + backend="rust_cpu", + precision="f64", + effect_estimate=0.42, + uncertainty_lower=0.10, + uncertainty_upper=0.70, + sample_size=12, + missingness_summary=MissingnessSummary( + total_observations=12, + complete_observations=10, + missing_predictor_observations=1, + missing_criterion_observations=1, + ), + convergence_diagnostics=ConvergenceDiagnostics( + converged=True, + iterations=42, + objective_value=-12.5, + maximum_gradient=0.0001, + ), + completed_at=ForgedDateTime(2026, 8, 21, 4, 45, tzinfo=timezone.utc), + ) From b616a1306bcbf4d77c83708ea9db93158f7d06b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:23:02 -0700 Subject: [PATCH 24/41] fix(validity): require exact temporal evidence type --- .../src/orgmetra_validity_analysis/handoff.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py index ad257a098..536f96e01 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py @@ -96,8 +96,8 @@ def _validate_kernel_revision(value: 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: + """Render an exact built-in 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: raise ValueError("requested_at must be timezone-aware") return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") From 219c2e3ba58f4156645317cf5b71a5e3804a8678 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:20:20 -0700 Subject: [PATCH 25/41] fix(validity): remove unused kernel revision import --- .../validity-analysis/src/orgmetra_validity_analysis/result.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py index 25ce3410e..cea4c56e3 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -15,7 +15,6 @@ from numbers import Real from .handoff import ( - REVIEWED_FAST_MLSIRM_REVISION, _canonical_timestamp, _validate_code, _validate_digest, From 89c970772fd2ee8328ffd6df9836c7fc68745ff5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:16:46 -0700 Subject: [PATCH 26/41] test(validity): require completed_at diagnostics --- packages/validity-analysis/tests/test_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validity-analysis/tests/test_result.py b/packages/validity-analysis/tests/test_result.py index ab71fdef3..ad00b95f2 100644 --- a/packages/validity-analysis/tests/test_result.py +++ b/packages/validity-analysis/tests/test_result.py @@ -185,7 +185,7 @@ class LeakyConvergenceDiagnostics(ConvergenceDiagnostics): def to_dict(self) -> dict[str, object]: return {**super().to_dict(), "employment_decision": "auto_reject"} - with pytest.raises(ValueError, match="requested_at"): + with pytest.raises(ValueError, match="completed_at"): result(completed_at=datetime(2026, 8, 21, 7, 10)) with pytest.raises(ValueError, match="missingness_summary"): result(missingness_summary=object()) From b72c7a0c267c316b0c0d8189388126df13355d14 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:17:23 -0700 Subject: [PATCH 27/41] fix(validity): report field-correct timestamp errors --- .../src/orgmetra_validity_analysis/handoff.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py index 536f96e01..a77b280c6 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py @@ -95,10 +95,10 @@ def _validate_kernel_revision(value: str) -> None: raise ValueError("fast_mlsirm_revision must equal the reviewed immutable revision") -def _canonical_timestamp(value: datetime) -> str: - """Render an exact built-in aware instant as precision-preserving UTC RFC 3339 text.""" +def _canonical_timestamp(value: datetime, field_name: str) -> str: + """Render an exact built-in aware instant with field-correct diagnostics.""" if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: - raise ValueError("requested_at must be timezone-aware") + raise ValueError(f"{field_name} must be timezone-aware") return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") @@ -164,7 +164,7 @@ def __post_init__(self) -> None: if self.actor_reference == self.reviewer_reference: raise ValueError("reviewer_reference must identify a different accountable actor") _validate_kernel_revision(self.fast_mlsirm_revision) - _canonical_timestamp(self.requested_at) + _canonical_timestamp(self.requested_at, "requested_at") _validate_code(self.purpose_code, "purpose_code") if self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain selection_validity_analysis") @@ -222,7 +222,7 @@ def canonical_json(self) -> str: "predictor_snapshot_reference": self.predictor_snapshot_reference, "purpose_code": self.purpose_code, "reason_code": self.reason_code, - "requested_at": _canonical_timestamp(self.requested_at), + "requested_at": _canonical_timestamp(self.requested_at, "requested_at"), "required_result_evidence": list(self.required_result_evidence), "result_authority": self.result_authority, "reviewer_reference": self.reviewer_reference, From 5197338f19c3096cf25d757ef66a3d37f4a3ae76 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:17:45 -0700 Subject: [PATCH 28/41] fix(validity): bind result timestamp diagnostic --- .../src/orgmetra_validity_analysis/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py index cea4c56e3..ae21c3d9d 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -188,7 +188,7 @@ def __post_init__(self) -> None: raise ValueError("convergence_diagnostics must be ConvergenceDiagnostics") if self.sample_size != self.missingness_summary.total_observations: raise ValueError("sample_size must match total_observations") - _canonical_timestamp(self.completed_at) + _canonical_timestamp(self.completed_at, "completed_at") if self.result_authority != _RESULT_AUTHORITY: raise ValueError("result_authority must remain scientific_evidence_only") if self.execution_state != _EXECUTION_STATE: @@ -208,7 +208,7 @@ def canonical_json(self) -> str: """Return deterministic, non-person-level JSON for audit correlation.""" payload = { "backend": self.backend, - "completed_at": _canonical_timestamp(self.completed_at), + "completed_at": _canonical_timestamp(self.completed_at, "completed_at"), "contains_raw_person_level_values": self.contains_raw_person_level_values, "convergence_diagnostics": self.convergence_diagnostics.to_dict(), "effect_estimate": float(self.effect_estimate), From e7c340495fb057e41e5b0e0338f648480063805e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:19:54 -0700 Subject: [PATCH 29/41] test(validity): align temporal diagnostic regression --- .../validity-analysis/tests/test_temporal_evidence_integrity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validity-analysis/tests/test_temporal_evidence_integrity.py b/packages/validity-analysis/tests/test_temporal_evidence_integrity.py index e47628263..25e4ab83d 100644 --- a/packages/validity-analysis/tests/test_temporal_evidence_integrity.py +++ b/packages/validity-analysis/tests/test_temporal_evidence_integrity.py @@ -54,7 +54,7 @@ def test_handoff_rejects_datetime_subclass_that_can_forge_requested_at() -> None def test_result_rejects_datetime_subclass_that_can_forge_completed_at() -> None: """Result canonical evidence must not invoke caller-overridable datetime methods.""" - with pytest.raises(ValueError, match="requested_at"): + with pytest.raises(ValueError, match="completed_at"): ValidationAnalysisResult( tenant_record_id="10000000-0000-7000-8000-000000000001", result_reference="validation_analysis_result:11111111-1111-4111-8111-111111111111", From a8da84cdd43bca0b1fe4a6bbc4c2207c7cf2072d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:04:31 +0900 Subject: [PATCH 30/41] fix(validity): freeze handoff evidence boundaries --- CHANGELOG.md | 1 + ...ned-selection-validity-analysis-handoff.md | 3 + .../validation-analysis-handoff.md | 2 +- manifest.json | 2 +- packages/validity-analysis/CHANGELOG.md | 1 + packages/validity-analysis/README.md | 4 +- .../src/orgmetra_validity_analysis/handoff.py | 66 ++++-- .../src/orgmetra_validity_analysis/result.py | 21 +- .../tests/test_temporal_evidence_integrity.py | 197 +++++++++++++++++- 9 files changed, 264 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f4752d7..d954dc355 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_validity_analysis` handoff/result boundary: exact tenant and evidence references, reviewed fast-mlsirm pin, distinct requester/reviewer actors, aggregate-only scientific evidence, construction-time UTC and finite-number snapshots, canonical JSON/SHA-256 correlation, and human-review-only result authority. - 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/0027-governed-selection-validity-analysis-handoff.md b/docs/adr/0027-governed-selection-validity-analysis-handoff.md index 6db6c3a1c..c3862a968 100644 --- a/docs/adr/0027-governed-selection-validity-analysis-handoff.md +++ b/docs/adr/0027-governed-selection-validity-analysis-handoff.md @@ -26,6 +26,8 @@ Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnaly - remains `not_executed`, `scientific_evidence_only`, and human-review-required; - produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. +Both handoff and result envelopes detach exact timezone-aware timestamps to one built-in UTC instant at construction. Result numeric evidence is converted to finite built-in floats before storage, so caller-controlled timezone or numeric runtime behavior cannot rewrite canonical evidence after validation. + The same package also validates `ValidationAnalysisResult` envelopes returned by the approved offline worker. A result must link to the handoff digest and the same pinned revision, identify a Rust CPU or GPU backend and precision, provide finite effect and interval values, match its sample size to aggregate missingness counts, reject impossible complete-versus-missing count combinations, and include explicit convergence diagnostics. The canonicalization boundary accepts only the exact governed `MissingnessSummary` and `ConvergenceDiagnostics` runtime types so subclass method overrides cannot add unreviewed or person-level fields to immutable result evidence. A nonconverged result remains typed scientific evidence requiring human review; it cannot be treated as a valid selection procedure or an employment decision. The package does not invoke fast-mlsirm. An approved offline worker is the later execution boundary. Before execution, the Orgmetra host must re-resolve every reference inside the tenant, verify exact study/Job membership and evidence provenance, and prove requester/reviewer identities are distinct authoritative actors. @@ -38,6 +40,7 @@ The package does not invoke fast-mlsirm. An approved offline worker is the later - A buyer or auditor can identify exactly which governed study evidence a result was supposed to analyze without copying raw worker data into the handoff. - Aggregate missingness evidence cannot claim all observations are complete while simultaneously reporting predictor- or criterion-missing observations. - Result canonicalization cannot be extended by an unreviewed subclass to serialize extra decision-like or person-level fields. +- Caller-controlled timestamp and numeric runtime behavior cannot rewrite an accepted canonical digest after construction. - Human interpretation remains explicit and separate from numerical output. - The dedicated-writer boundary remains intact: Orgmetra consumes only a pinned foreign revision/contract boundary and never mutates fast-mlsirm. diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md index af8f6b622..455a8380c 100644 --- a/docs/traceability/validation-analysis-handoff.md +++ b/docs/traceability/validation-analysis-handoff.md @@ -15,7 +15,7 @@ Can an organization send one exact, reviewable validation study to its statistic | Scientific evidence | effect estimate, uncertainty interval, sample size, internally possible aggregate missingness, convergence diagnostics | immutable required-result-evidence regression plus impossible-missingness rejection | | Numerical result boundary | handoff digest, pinned fast-mlsirm revision, Rust CPU/GPU backend, precision, finite estimate/interval, aggregate missingness, explicit convergence state | `ValidationAnalysisResult` invariant/canonicalization regressions and exact-runtime-type checks | | Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | -| Reproducibility | canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | deterministic serialization/digest tests | +| Reproducibility | construction-time UTC timestamp snapshots, finite numeric snapshots, canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | mutable timezone/numeric and UTC-boundary regressions plus deterministic serialization/digest tests | ## Maturity diff --git a/manifest.json b/manifest.json index 97f2bab14..df6ee9743 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":"264a8af7f8a324a044317d24ce8f8d7eee65ef3d54a8819e7a934d6b9859aa83","bytes":17624,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} diff --git a/packages/validity-analysis/CHANGELOG.md b/packages/validity-analysis/CHANGELOG.md index 9e9e67b43..fd7762043 100644 --- a/packages/validity-analysis/CHANGELOG.md +++ b/packages/validity-analysis/CHANGELOG.md @@ -9,3 +9,4 @@ - Validate a digest-linked Rust CPU/GPU result envelope with finite estimates, aggregate missingness, and explicit convergence or nonconvergence diagnostics. - Reject impossible aggregate missingness where complete observations overlap either predictor-missing or criterion-missing counts beyond the sample total. - Require exact governed missingness/convergence runtime types so subclass method overrides cannot inject unreviewed or person-level fields into canonical result evidence. +- Freeze exact UTC timestamps and finite numeric values at construction, and reject runtime-type forgery before canonical evidence serialization. diff --git a/packages/validity-analysis/README.md b/packages/validity-analysis/README.md index 527241d5b..b56e9a040 100644 --- a/packages/validity-analysis/README.md +++ b/packages/validity-analysis/README.md @@ -6,9 +6,9 @@ This package creates an immutable **selection-validity analysis handoff** and va `build_validation_analysis_handoff(...)` binds one tenant, validation study, Job, predictor snapshot, criterion snapshot, population snapshot, decision policy, analysis plan, requester, reviewer, and the reviewed fast-mlsirm revision `04d0bc21a2a20693bcf16108cd76d394fe844d23`. -The resulting canonical JSON is digest-addressable, contains no raw person-level predictor or criterion values, and remains `not_executed`. Required result evidence is explicit: effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics. +The resulting canonical JSON is digest-addressable, contains no raw person-level predictor or criterion values, and remains `not_executed`. Its timestamp is detached to one UTC instant at construction so later timezone-provider changes cannot rewrite the digest. Required result evidence is explicit: effect estimate, uncertainty interval, sample size, missingness summary, and convergence diagnostics. -`ValidationAnalysisResult` accepts only a result linked to the handoff digest and the same reviewed fast-mlsirm revision. It records the Rust CPU/GPU backend, precision, aggregate missingness counts, finite effect and interval values, and explicit convergence or nonconvergence diagnostics. Missingness counts must be internally possible: complete observations cannot overlap either predictor-missing or criterion-missing observations beyond the declared sample total. The result envelope accepts only the exact governed `MissingnessSummary` and `ConvergenceDiagnostics` runtime types, preventing subclass method overrides from adding unreviewed or person-level fields to canonical audit evidence. It never promotes a result to an employment decision; human review remains mandatory. +`ValidationAnalysisResult` accepts only a result linked to the handoff digest and the same reviewed fast-mlsirm revision. It records the Rust CPU/GPU backend, precision, aggregate missingness counts, finite effect and interval values, and explicit convergence or nonconvergence diagnostics. Timestamps and finite numeric values are snapshotted before canonicalization, and the result envelope accepts only the exact governed `MissingnessSummary` and `ConvergenceDiagnostics` runtime types, preventing mutable runtime values or subclass method overrides from adding unreviewed or person-level fields to canonical audit evidence. Missingness counts must be internally possible: complete observations cannot overlap either predictor-missing or criterion-missing observations beyond the declared sample total. It never promotes a result to an employment decision; human review remains mandatory. ## What it does not do diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py index a77b280c6..5f3fb2f57 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/handoff.py @@ -8,7 +8,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from hashlib import sha256 import json import re @@ -46,8 +46,10 @@ ) -def _validate_operational_uuid(value: str, field_name: str) -> None: +def _validate_operational_uuid(value: object, field_name: str) -> None: """Require canonical non-sentinel UUID text owned by authoritative Orgmetra.""" + 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: @@ -56,11 +58,11 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: raise ValueError(f"{field_name} must be a canonical operational UUID") -def _validate_reference(value: str, prefix: str, field_name: str) -> None: +def _validate_reference(value: object, prefix: str, field_name: str) -> None: """Require the expected namespace plus a canonical opaque UUIDv4 suffix.""" error_message = f"{field_name} must be an opaque {prefix}: UUIDv4 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}:") @@ -75,31 +77,47 @@ def _validate_reference(value: str, prefix: str, field_name: str) -> None: raise ValueError(error_message) -def _validate_digest(value: str, field_name: str) -> None: +def _validate_digest(value: object, field_name: str) -> None: """Require lowercase SHA-256 hexadecimal evidence.""" - if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + if type(value) is not str or not _DIGEST_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be lowercase SHA-256 hex") -def _validate_code(value: str, field_name: str) -> None: +def _validate_code(value: object, field_name: str) -> None: """Require bounded descriptive lower snake_case governance codes.""" - if not isinstance(value, str) or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): + if type(value) is not str or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be bounded two-or-more-word lower snake_case") -def _validate_kernel_revision(value: str) -> None: +def _validate_kernel_revision(value: object) -> None: """Require the exact externally reviewed immutable fast-mlsirm revision.""" - if not isinstance(value, str) or not _REVISION_PATTERN.fullmatch(value): + if type(value) is not str or not _REVISION_PATTERN.fullmatch(value): raise ValueError("fast_mlsirm_revision must be lowercase 40-character Git commit hex") if value != REVIEWED_FAST_MLSIRM_REVISION: raise ValueError("fast_mlsirm_revision must equal the reviewed immutable revision") -def _canonical_timestamp(value: datetime, field_name: str) -> str: - """Render an exact built-in aware instant with field-correct diagnostics.""" - if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: +def _freeze_timestamp(value: object, field_name: str) -> datetime: + """Detach caller-controlled timezone behavior and store one immutable UTC instant.""" + if type(value) is not datetime or value.tzinfo is None: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") + try: + offset = value.utcoffset() + except Exception as exc: # noqa: BLE001 - normalize provider behavior at trust boundary. + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") from exc + if type(offset) is not timedelta: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") + try: + return (value.replace(tzinfo=None) - offset).replace(tzinfo=timezone.utc) + except OverflowError as exc: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") from exc + + +def _canonical_timestamp(value: object, field_name: str) -> str: + """Render a previously detached built-in UTC instant as RFC 3339 text.""" + if type(value) is not datetime or value.tzinfo is not timezone.utc: raise ValueError(f"{field_name} must be timezone-aware") - return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + return value.isoformat().replace("+00:00", "Z") @dataclass(frozen=True, slots=True, repr=False) @@ -139,6 +157,8 @@ class ValidationAnalysisHandoff: def __post_init__(self) -> None: """Fail closed when direct construction drifts from the governed handoff.""" + requested_at = _freeze_timestamp(self.requested_at, "requested_at") + object.__setattr__(self, "requested_at", requested_at) _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") for value, prefix, field_name in ( (self.handoff_reference, "validation_analysis_handoff", "handoff_reference"), @@ -173,23 +193,27 @@ def __post_init__(self) -> None: raise ValueError("reason_code must remain criterion_related_validation") if type(self.evidence_version) is not int or not 1 <= self.evidence_version <= 2_147_483_647: raise ValueError("evidence_version must be an integer from 1 through 2147483647") - if self.validation_strategy != _VALIDATION_STRATEGY: + if type(self.validation_strategy) is not str or self.validation_strategy != _VALIDATION_STRATEGY: raise ValueError("validation_strategy must remain criterion_related") - if self.kernel_repository != _KERNEL_REPOSITORY: + if type(self.kernel_repository) is not str or self.kernel_repository != _KERNEL_REPOSITORY: raise ValueError("kernel_repository must remain ContextualWisdomLab/fast-mlsirm") - if self.kernel_boundary != _KERNEL_BOUNDARY: + if type(self.kernel_boundary) is not str or self.kernel_boundary != _KERNEL_BOUNDARY: raise ValueError("kernel_boundary must remain read_only_pinned_revision") - if self.execution_state != _EXECUTION_STATE: + if type(self.execution_state) is not str or self.execution_state != _EXECUTION_STATE: raise ValueError("execution_state must remain not_executed") if self.contains_raw_person_level_values is not False: raise ValueError("handoff must not contain raw person-level values") if self.human_review_required is not True: raise ValueError("human review is mandatory for selection-validity interpretation") - if self.result_authority != _RESULT_AUTHORITY: + if type(self.result_authority) is not str or self.result_authority != _RESULT_AUTHORITY: raise ValueError("result_authority must remain scientific_evidence_only") - if self.required_result_evidence != _REQUIRED_RESULT_EVIDENCE: + if ( + type(self.required_result_evidence) is not tuple + or any(type(item) is not str for item in self.required_result_evidence) + or self.required_result_evidence != _REQUIRED_RESULT_EVIDENCE + ): raise ValueError("required_result_evidence must remain the reviewed evidence set") - 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 validation instruction") def __repr__(self) -> str: diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py index ae21c3d9d..9970ebb57 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -16,6 +16,7 @@ from .handoff import ( _canonical_timestamp, + _freeze_timestamp, _validate_code, _validate_digest, _validate_kernel_revision, @@ -111,18 +112,20 @@ def __post_init__(self) -> None: if type(self.converged) is not bool: raise ValueError("converged must be a boolean") _validate_positive_integer(self.iterations, "iterations") - _finite_number(self.objective_value, "objective_value") + objective = _finite_number(self.objective_value, "objective_value") gradient = _finite_number(self.maximum_gradient, "maximum_gradient") if gradient < 0: raise ValueError("maximum_gradient must be non-negative") if self.converged and self.failure_code is not None: raise ValueError("failure_code must be absent for a converged result") if not self.converged and ( - not isinstance(self.failure_code, str) or not self.failure_code + type(self.failure_code) is not str or not self.failure_code ): raise ValueError("failure_code is required for a nonconverged result") if self.failure_code is not None: _validate_code(self.failure_code, "failure_code") + object.__setattr__(self, "objective_value", objective) + object.__setattr__(self, "maximum_gradient", gradient) def to_dict(self) -> dict[str, object]: """Return deterministic convergence fields for the canonical result JSON.""" @@ -170,9 +173,9 @@ def __post_init__(self) -> None: _validate_digest(self.provenance_digest, "provenance_digest") _validate_kernel_revision(self.fast_mlsirm_revision) _validate_code(self.model_code, "model_code") - if self.backend not in _ALLOWED_BACKENDS: + if type(self.backend) is not str or self.backend not in _ALLOWED_BACKENDS: raise ValueError("backend must be rust_cpu or rust_gpu") - if self.precision not in _ALLOWED_PRECISIONS: + if type(self.precision) is not str or self.precision not in _ALLOWED_PRECISIONS: raise ValueError("precision must be f64 or f32") estimate = _finite_number(self.effect_estimate, "effect_estimate") lower = _finite_number(self.uncertainty_lower, "uncertainty_lower") @@ -188,10 +191,10 @@ def __post_init__(self) -> None: raise ValueError("convergence_diagnostics must be ConvergenceDiagnostics") if self.sample_size != self.missingness_summary.total_observations: raise ValueError("sample_size must match total_observations") - _canonical_timestamp(self.completed_at, "completed_at") - if self.result_authority != _RESULT_AUTHORITY: + completed_at = _freeze_timestamp(self.completed_at, "completed_at") + if type(self.result_authority) is not str or self.result_authority != _RESULT_AUTHORITY: raise ValueError("result_authority must remain scientific_evidence_only") - if self.execution_state != _EXECUTION_STATE: + if type(self.execution_state) is not str or self.execution_state != _EXECUTION_STATE: raise ValueError("execution_state must remain completed") if self.contains_raw_person_level_values is not False: raise ValueError("result must not contain raw person-level values") @@ -199,6 +202,10 @@ def __post_init__(self) -> None: raise ValueError("human review is mandatory for validity interpretation") if type(self.evidence_version) is not int or self.evidence_version != 1: raise ValueError("evidence_version must remain 1") + object.__setattr__(self, "effect_estimate", estimate) + object.__setattr__(self, "uncertainty_lower", lower) + object.__setattr__(self, "uncertainty_upper", upper) + object.__setattr__(self, "completed_at", completed_at) def __repr__(self) -> str: """Return a redacted representation suitable for routine application logs.""" diff --git a/packages/validity-analysis/tests/test_temporal_evidence_integrity.py b/packages/validity-analysis/tests/test_temporal_evidence_integrity.py index 25e4ab83d..0431eee55 100644 --- a/packages/validity-analysis/tests/test_temporal_evidence_integrity.py +++ b/packages/validity-analysis/tests/test_temporal_evidence_integrity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone, tzinfo import pytest @@ -10,9 +10,12 @@ ConvergenceDiagnostics, MissingnessSummary, REVIEWED_FAST_MLSIRM_REVISION, + ValidationAnalysisHandoff, ValidationAnalysisResult, build_validation_analysis_handoff, ) +from test_handoff import valid_kwargs +from test_result import result class ForgedDateTime(datetime): @@ -27,6 +30,87 @@ def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] return "2099-12-31T23:59:59+00:00" +class ForgedReference(str): + """String subclass able to forge namespace and UUID parsing methods.""" + + def startswith(self, prefix, *args): # type: ignore[no-untyped-def] + """Pretend that an invalid namespace has the expected prefix.""" + return True + + def split(self, separator=None, maxsplit=-1): # type: ignore[no-untyped-def] + """Return a valid UUID suffix while retaining invalid source text.""" + return ["validation_analysis_handoff", "11111111-1111-4111-8111-111111111111"] + + +class ForgedFixedText(str): + """String subclass whose comparisons can forge fixed governance values.""" + + def __eq__(self, other): # type: ignore[no-untyped-def] + """Claim equality with any expected governance text.""" + return True + + def __ne__(self, other): # type: ignore[no-untyped-def] + """Claim inequality with no governance text.""" + return False + + def __hash__(self): + """Use a valid fixed-text hash for set membership forgery tests.""" + return hash("rust_cpu") + + +class MutableOffset(tzinfo): + """Timezone fixture whose offset can change after envelope construction.""" + + def __init__(self) -> None: + self.hours = 1 + + def utcoffset(self, dt: datetime | None) -> timedelta: + """Return the currently configured offset.""" + return timedelta(hours=self.hours) + + def dst(self, dt: datetime | None) -> timedelta: + """Return no daylight-saving offset.""" + return timedelta(0) + + +class UnknownOffset(tzinfo): + """Timezone fixture whose UTC offset cannot be resolved.""" + + def utcoffset(self, dt: datetime | None) -> None: + """Return no offset to exercise fail-closed validation.""" + return None + + def dst(self, dt: datetime | None) -> None: + """Return no daylight-saving offset.""" + return None + + +class ExplodingOffset(tzinfo): + """Timezone fixture whose provider raises during offset resolution.""" + + def utcoffset(self, dt: datetime | None) -> timedelta: + """Raise an untrusted provider error.""" + raise RuntimeError("offset provider failed") + + def dst(self, dt: datetime | None) -> timedelta: + """Return no daylight-saving offset when queried separately.""" + return timedelta(0) + + +class MutableReal(float): + """Numeric subclass whose float conversion changes after construction.""" + + def __new__(cls, value: float): + """Create a float-backed value with a separately mutable conversion.""" + instance = super().__new__(cls, value) + instance.current = value + return instance + + def __float__(self) -> float: + """Expose the mutable conversion used by unsafe canonicalization.""" + return self.current + + def test_handoff_rejects_datetime_subclass_that_can_forge_requested_at() -> None: """Handoff canonical evidence must not invoke caller-overridable datetime methods.""" with pytest.raises(ValueError, match="requested_at"): @@ -82,3 +166,114 @@ def test_result_rejects_datetime_subclass_that_can_forge_completed_at() -> None: ), completed_at=ForgedDateTime(2026, 8, 21, 4, 45, tzinfo=timezone.utc), ) + + +def test_handoff_and_result_detach_mutable_timezone_before_digesting() -> None: + """Freeze one UTC instant so later timezone mutation cannot rewrite evidence.""" + handoff_zone = MutableOffset() + handoff_values = valid_kwargs() + handoff_values["requested_at"] = datetime(2026, 8, 21, 4, 45, tzinfo=handoff_zone) + handoff = build_validation_analysis_handoff(**handoff_values) + handoff_before = handoff.canonical_json(), handoff.sha256_digest() + handoff_zone.hours = 2 + assert (handoff.canonical_json(), handoff.sha256_digest()) == handoff_before + + result_zone = MutableOffset() + candidate = result(completed_at=datetime(2026, 8, 21, 4, 45, tzinfo=result_zone)) + result_before = candidate.canonical_json(), candidate.sha256_digest() + result_zone.hours = 2 + assert (candidate.canonical_json(), candidate.sha256_digest()) == result_before + + +@pytest.mark.parametrize( + "timestamp", + [ + datetime.min.replace(tzinfo=timezone(timedelta(hours=1))), + datetime.max.replace(tzinfo=timezone(-timedelta(hours=1))), + datetime(2026, 8, 21, 4, 45, tzinfo=UnknownOffset()), + datetime(2026, 8, 21, 4, 45, tzinfo=ExplodingOffset()), + ], +) +def test_handoff_rejects_unrepresentable_or_untrusted_timestamp(timestamp: datetime) -> None: + """Normalize timezone-provider failures and UTC arithmetic overflow at the boundary.""" + values = valid_kwargs() + values["requested_at"] = timestamp + with pytest.raises(ValueError, match="requested_at"): + build_validation_analysis_handoff(**values) + + +@pytest.mark.parametrize( + "timestamp", + [ + datetime.min.replace(tzinfo=timezone(timedelta(hours=1))), + datetime.max.replace(tzinfo=timezone(-timedelta(hours=1))), + datetime(2026, 8, 21, 4, 45, tzinfo=UnknownOffset()), + datetime(2026, 8, 21, 4, 45, tzinfo=ExplodingOffset()), + ], +) +def test_result_rejects_unrepresentable_or_untrusted_timestamp(timestamp: datetime) -> None: + """Apply the same fail-closed timestamp contract to completed result evidence.""" + with pytest.raises(ValueError, match="completed_at"): + result(completed_at=timestamp) + + +@pytest.mark.parametrize( + "timestamp", + [ + ForgedDateTime(2026, 8, 21, 4, 45, tzinfo=timezone.utc), + datetime(2026, 8, 21, 4, 45, tzinfo=timezone(timedelta(hours=1))), + ], +) +def test_canonicalization_rejects_low_level_timestamp_reinjection(timestamp: datetime) -> None: + """Keep canonicalization fail-closed even if an object is corrupted after construction.""" + handoff = build_validation_analysis_handoff(**valid_kwargs()) + object.__setattr__(handoff, "requested_at", timestamp) + with pytest.raises(ValueError, match="requested_at"): + handoff.canonical_json() + + candidate = result() + object.__setattr__(candidate, "completed_at", timestamp) + with pytest.raises(ValueError, match="completed_at"): + candidate.canonical_json() + + +def test_handoff_rejects_runtime_text_subclasses_before_serialization() -> None: + """Reject text subclasses that can forge reference, digest, code, or fixed-value checks.""" + for field, value in ( + ("tenant_record_id", ForgedFixedText("10000000-0000-7000-8000-000000000001")), + ("handoff_reference", ForgedReference("wrong_namespace:invalid")), + ("predictor_snapshot_digest", ForgedFixedText("a" * 64)), + ("purpose_code", ForgedFixedText("selection_validity_analysis")), + ("fast_mlsirm_revision", ForgedFixedText(REVIEWED_FAST_MLSIRM_REVISION)), + ("validation_strategy", ForgedFixedText("criterion_related")), + ("next_action", ForgedFixedText("governed")), + ): + values = valid_kwargs() + values[field] = value + with pytest.raises(ValueError, match=field): + ValidationAnalysisHandoff(**values) + + +def test_result_snapshots_numeric_values_before_canonicalization() -> None: + """Detach mutable numeric subclasses before recording scientific evidence bytes.""" + objective = MutableReal(-12.5) + gradient = MutableReal(0.0001) + diagnostics = ConvergenceDiagnostics( + converged=True, + iterations=42, + objective_value=objective, + maximum_gradient=gradient, + ) + estimate = MutableReal(0.42) + candidate = result(effect_estimate=estimate, convergence_diagnostics=diagnostics) + before = candidate.canonical_json(), candidate.sha256_digest() + objective.current = -1.0 + gradient.current = 0.5 + estimate.current = 0.69 + assert (candidate.canonical_json(), candidate.sha256_digest()) == before + + +def test_result_rejects_forged_backend_text() -> None: + """Do not allow a string subclass to forge an allowed backend membership check.""" + with pytest.raises(ValueError, match="backend"): + result(backend=ForgedFixedText("numpy")) From 508184420607ccd7213a73cd3e20e3041c9bdd27 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:46:35 -0700 Subject: [PATCH 31/41] test(validity): reject overflowing worker numerics --- .../tests/test_numeric_overflow_contract.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/validity-analysis/tests/test_numeric_overflow_contract.py diff --git a/packages/validity-analysis/tests/test_numeric_overflow_contract.py b/packages/validity-analysis/tests/test_numeric_overflow_contract.py new file mode 100644 index 000000000..a4fb88115 --- /dev/null +++ b/packages/validity-analysis/tests/test_numeric_overflow_contract.py @@ -0,0 +1,18 @@ +"""Regression for malformed worker numerics that overflow float conversion.""" + +import pytest + +from orgmetra_validity_analysis import ConvergenceDiagnostics + + +def test_oversized_worker_numeric_is_rejected_as_value_error() -> None: + """Normalize float-conversion overflow to the package's ValueError contract.""" + oversized_integer = 10**10000 + + with pytest.raises(ValueError, match="finite number"): + ConvergenceDiagnostics( + converged=True, + iterations=1, + objective_value=oversized_integer, + maximum_gradient=0.1, + ) From a55f95cee13b58a064abcafe42a159be940d6854 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:46:43 -0700 Subject: [PATCH 32/41] test(validity): require ADR-wide quality trigger --- .../tests/test_workflow_trigger_contract.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/validity-analysis/tests/test_workflow_trigger_contract.py diff --git a/packages/validity-analysis/tests/test_workflow_trigger_contract.py b/packages/validity-analysis/tests/test_workflow_trigger_contract.py new file mode 100644 index 000000000..2a0295fc9 --- /dev/null +++ b/packages/validity-analysis/tests/test_workflow_trigger_contract.py @@ -0,0 +1,13 @@ +"""Regression for the validity package's repository-wide ADR numbering trigger.""" + +from pathlib import Path + + +def test_any_adr_change_runs_the_adr_numbering_regression() -> None: + """Keep ADR uniqueness enforcement reachable when any decision record changes.""" + repository_root = Path(__file__).resolve().parents[3] + workflow = (repository_root / ".github" / "workflows" / "validity-analysis-quality.yml").read_text( + encoding="utf-8" + ) + + assert ' - "docs/adr/**"' in workflow From 06db36667eb791ff69b8d43bed85e5746887f9ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:47:28 -0700 Subject: [PATCH 33/41] fix(validity): normalize numeric conversion failures --- .../src/orgmetra_validity_analysis/result.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py index 9970ebb57..3f684ab10 100644 --- a/packages/validity-analysis/src/orgmetra_validity_analysis/result.py +++ b/packages/validity-analysis/src/orgmetra_validity_analysis/result.py @@ -43,10 +43,13 @@ def _validate_positive_integer(value: object, field_name: str) -> None: def _finite_number(value: object, field_name: str) -> float: - """Return one finite real number and reject booleans or non-numeric text.""" + """Return one finite real number and normalize invalid numerics to ValueError.""" if isinstance(value, bool) or not isinstance(value, Real): raise ValueError(f"{field_name} must be a finite number") - number = float(value) + try: + number = float(value) + except (OverflowError, TypeError, ValueError) as exc: + raise ValueError(f"{field_name} must be a finite number") from exc if not isfinite(number): raise ValueError(f"{field_name} must be a finite number") return number From be52fc7637656fa2560d8572ba80204c59f25cbe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:47:38 -0700 Subject: [PATCH 34/41] fix(validity): run ADR uniqueness check for all ADR changes --- .github/workflows/validity-analysis-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validity-analysis-quality.yml b/.github/workflows/validity-analysis-quality.yml index 5b1ae41fc..5c9c57e8a 100644 --- a/.github/workflows/validity-analysis-quality.yml +++ b/.github/workflows/validity-analysis-quality.yml @@ -8,7 +8,7 @@ on: - main paths: - "packages/validity-analysis/**" - - "docs/adr/0027-governed-selection-validity-analysis-handoff.md" + - "docs/adr/**" - "docs/doctoring/validation-analysis-handoff-references.md" - "docs/traceability/validation-analysis-handoff.md" - ".github/requirements/foundation-test.txt" From 376c2ca2e9d69b0f8e2ed5337b9fb22a9fbcd0ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:48:07 -0700 Subject: [PATCH 35/41] docs(validity): pin reproducible selection-validation references --- .../validation-analysis-handoff-references.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/doctoring/validation-analysis-handoff-references.md b/docs/doctoring/validation-analysis-handoff-references.md index d5de152f1..cfde9303b 100644 --- a/docs/doctoring/validation-analysis-handoff-references.md +++ b/docs/doctoring/validation-analysis-handoff-references.md @@ -1,20 +1,23 @@ # Validation-analysis handoff references -Material decisions for ADR 0027 were checked against the following primary/authoritative sources on 2026-08-21. +Material decisions for ADR 0027 were checked against the following primary/authoritative sources on 2026-08-21. Regulatory currency was rechecked on 2026-08-29; fixed publication identifiers are retained so an auditor can reproduce the cited text even when agency web pages change. ## APA 7 references -Electronic Code of Federal Regulations. (2026). *29 C.F.R. pt. 1607—Uniform Guidelines on Employee Selection Procedures (1978).* Retrieved August 21, 2026, from https://www.ecfr.gov/current/title-29/subtitle-B/chapter-XIV/part-1607 +Equal Employment Opportunity Commission, Civil Service Commission, Department of Justice, & Department of Labor. (1978). *Uniform Guidelines on Employee Selection Procedures (1978)*, 43 Fed. Reg. 38,290 (August 25, 1978) (codified at 29 C.F.R. pt. 1607). The EEOC continues to list 29 C.F.R. pt. 1607 among its Title VII regulations: https://www.eeoc.gov/regulations-and-guidelines -Society for Industrial and Organizational Psychology. (2018). *Principles for the validation and use of personnel selection procedures* (5th ed.). Cambridge University Press. https://www.apa.org/ed/accreditation/personnel-selection-procedures.pdf +Society for Industrial and Organizational Psychology. (2018). Principles for the validation and use of personnel selection procedures. *Industrial and Organizational Psychology, 11*(S1), 1–97. https://doi.org/10.1017/iop.2018.195 ContextualWisdomLab. (2026). *fast-mlsirm* (Commit 04d0bc21a2a20693bcf16108cd76d394fe844d23) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/fast-mlsirm/tree/04d0bc21a2a20693bcf16108cd76d394fe844d23 Tabassi, E. (2023). *Artificial intelligence risk management framework (AI RMF 1.0)* (NIST AI 100-1). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.AI.100-1 +Office of Personnel Management. (2026). *Removal of references to the Uniform Guidelines on Employee Selection Procedures in federal personnel regulations*, 91 Fed. Reg. 48,234 (July 31, 2026) (interim final rule, RIN 3206-AP20). + ## Decision notes -- 29 C.F.R. §§ 1607.5 and 1607.14 support keeping criterion-related validity evidence tied to an explicit study design, job relevance, accuracy, reporting, and documentation rather than treating a bare coefficient as sufficient evidence. -- The SIOP Principles are the professional validation baseline used for the handoff's evidence-and-human-review posture. +- 43 Fed. Reg. 38,290 and the still-listed EEOC 29 C.F.R. pt. 1607 source support keeping criterion-related validity evidence tied to an explicit study design, job relevance, accuracy, reporting, and documentation rather than treating a bare coefficient as sufficient evidence. The fixed Federal Register identifier, not a mutable `/current/` eCFR URL, is the reproducible source for the 1978 text cited by this ADR. +- The July 31, 2026 OPM interim final rule removed UGESP references from specified federal civil-service regulations. Orgmetra therefore does not present UGESP as an undifferentiated government-wide mandate; applicability must be evaluated for the employer, jurisdiction, decision, and governing law at use time. +- The SIOP Principles are the professional validation baseline used for the handoff's evidence-and-human-review posture. The journal citation above fixes volume 11, Supplement S1, pages 1–97, and DOI 10.1017/iop.2018.195. - The fast-mlsirm commit is recorded as a read-only dependency coordinate only. This Orgmetra slice does not modify or duplicate its numerical implementation. - NIST AI RMF's govern, map, measure, and manage functions support preserving backend, precision, provenance, convergence, and human-review fields as inspectable result evidence rather than treating a model response as an autonomous decision. From 2ff2582709dd03038f5a73816e9cde9605764836 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:49:14 -0700 Subject: [PATCH 36/41] docs(validity): trace repaired evidence boundaries --- docs/traceability/validation-analysis-handoff.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md index 455a8380c..65e22b253 100644 --- a/docs/traceability/validation-analysis-handoff.md +++ b/docs/traceability/validation-analysis-handoff.md @@ -13,9 +13,10 @@ Can an organization send one exact, reviewable validation study to its statistic | Privacy minimization | no raw person-level values in canonical handoff or result; result canonicalization accepts only exact governed missingness/convergence runtime types | canonical-payload/redacted-repr regressions plus subclass-injection rejection | | Human authority | requester/reviewer references must differ, and the host must re-resolve both within the tenant and prove they resolve to distinct authoritative actor identities before execution | direct-construction fail-closed regression plus `test_next_action_requires_resolved_actor_identity_separation` | | Scientific evidence | effect estimate, uncertainty interval, sample size, internally possible aggregate missingness, convergence diagnostics | immutable required-result-evidence regression plus impossible-missingness rejection | -| Numerical result boundary | handoff digest, pinned fast-mlsirm revision, Rust CPU/GPU backend, precision, finite estimate/interval, aggregate missingness, explicit convergence state | `ValidationAnalysisResult` invariant/canonicalization regressions and exact-runtime-type checks | +| Numerical result boundary | handoff digest, pinned fast-mlsirm revision, Rust CPU/GPU backend, precision, finite estimate/interval, aggregate missingness, explicit convergence state | `ValidationAnalysisResult` invariant/canonicalization regressions, exact-runtime-type checks, and oversized-numeric `ValueError` normalization | | Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | | Reproducibility | construction-time UTC timestamp snapshots, finite numeric snapshots, canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | mutable timezone/numeric and UTC-boundary regressions plus deterministic serialization/digest tests | +| Decision-record integrity | ADR numbers remain unique repository-wide and any `docs/adr/**` change reaches the validity quality gate | ADR uniqueness regression plus workflow-trigger contract regression | ## Maturity From 4359cfbb4cd8ee5885acb9110d7723099d712353 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:49:37 -0700 Subject: [PATCH 37/41] docs(validity): record numeric and missingness ownership boundaries --- .../0027-governed-selection-validity-analysis-handoff.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/adr/0027-governed-selection-validity-analysis-handoff.md b/docs/adr/0027-governed-selection-validity-analysis-handoff.md index c3862a968..ac74dbec8 100644 --- a/docs/adr/0027-governed-selection-validity-analysis-handoff.md +++ b/docs/adr/0027-governed-selection-validity-analysis-handoff.md @@ -26,7 +26,7 @@ Orgmetra adds a leaf `orgmetra_validity_analysis` package whose `ValidationAnaly - remains `not_executed`, `scientific_evidence_only`, and human-review-required; - produces deterministic canonical JSON and a SHA-256 digest for audit/result correlation. -Both handoff and result envelopes detach exact timezone-aware timestamps to one built-in UTC instant at construction. Result numeric evidence is converted to finite built-in floats before storage, so caller-controlled timezone or numeric runtime behavior cannot rewrite canonical evidence after validation. +Both handoff and result envelopes detach exact timezone-aware timestamps to one built-in UTC instant at construction. Result numeric evidence is converted to finite built-in floats before storage, and conversion failures including numeric overflow are normalized to the package's fail-closed `ValueError` contract, so caller-controlled timezone or numeric runtime behavior cannot rewrite canonical evidence after validation or escape normal malformed-result handling. The same package also validates `ValidationAnalysisResult` envelopes returned by the approved offline worker. A result must link to the handoff digest and the same pinned revision, identify a Rust CPU or GPU backend and precision, provide finite effect and interval values, match its sample size to aggregate missingness counts, reject impossible complete-versus-missing count combinations, and include explicit convergence diagnostics. The canonicalization boundary accepts only the exact governed `MissingnessSummary` and `ConvergenceDiagnostics` runtime types so subclass method overrides cannot add unreviewed or person-level fields to immutable result evidence. A nonconverged result remains typed scientific evidence requiring human review; it cannot be treated as a valid selection procedure or an employment decision. @@ -40,7 +40,7 @@ The package does not invoke fast-mlsirm. An approved offline worker is the later - A buyer or auditor can identify exactly which governed study evidence a result was supposed to analyze without copying raw worker data into the handoff. - Aggregate missingness evidence cannot claim all observations are complete while simultaneously reporting predictor- or criterion-missing observations. - Result canonicalization cannot be extended by an unreviewed subclass to serialize extra decision-like or person-level fields. -- Caller-controlled timestamp and numeric runtime behavior cannot rewrite an accepted canonical digest after construction. +- Caller-controlled timestamp and numeric runtime behavior cannot rewrite an accepted canonical digest after construction or turn malformed oversized worker output into an uncaught exception type. - Human interpretation remains explicit and separate from numerical output. - The dedicated-writer boundary remains intact: Orgmetra consumes only a pinned foreign revision/contract boundary and never mutates fast-mlsirm. @@ -48,11 +48,12 @@ The package does not invoke fast-mlsirm. An approved offline worker is the later - This slice does not execute a statistical model, estimate validity, correct for measurement error/range restriction, evaluate adverse impact, or assert legal compliance. - Sampling design, estimator choice, missing-data treatment, reliability evidence, multiplicity, transportability, fairness analysis, and model diagnostics must be encoded in the referenced analysis plan and reviewed before execution. +- The generic result envelope does not invent an estimator-specific minimum complete-case count. Whether a converged estimator is identified under a particular missing-data design belongs to the reviewed analysis plan and numerical-worker contract; Orgmetra fails closed on impossible aggregate counts without silently replacing that foreign scientific contract with complete-case analysis. - The package validates the result envelope, but a future execution adapter must still re-resolve the handoff references, verify the result provenance artifact, and attach evidence only after accountable human review. ## Verification -The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, impossible aggregate missingness rejection, exact governed aggregate-evidence runtime types, and 100% owned production statement/branch coverage. The repository-wide ADR numbering regression also fails closed if integration reuses an existing decision number. +The package regression suite starts RED when the public handoff contract is absent and covers canonical operational tenant UUIDs, opaque UUIDv4 references, exact evidence digests, distinct human actors, exact dependency pinning, timezone-aware event time, immutable governance constants, value minimization, deterministic canonicalization, SHA-256 correlation, impossible aggregate missingness rejection, oversized numeric conversion rejection, exact governed aggregate-evidence runtime types, and 100% owned production statement/branch coverage. The repository-wide ADR numbering regression also fails closed if integration reuses an existing decision number, and the validity quality workflow contract requires any `docs/adr/**` change to execute that regression. ## References From 2f6d1dca8a6ec2ed1e23453860018b5b58344418 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:43:55 -0700 Subject: [PATCH 38/41] test(validity): require shared config quality triggers --- .../tests/test_quality_workflow_trigger.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/validity-analysis/tests/test_quality_workflow_trigger.py diff --git a/packages/validity-analysis/tests/test_quality_workflow_trigger.py b/packages/validity-analysis/tests/test_quality_workflow_trigger.py new file mode 100644 index 000000000..aed4f4060 --- /dev/null +++ b/packages/validity-analysis/tests/test_quality_workflow_trigger.py @@ -0,0 +1,27 @@ +"""Regression tests for the validity-analysis quality-gate trigger surface.""" + +from pathlib import Path + + +_WORKFLOW_PATH = Path(".github/workflows/validity-analysis-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 shared test/runtime configuration changes 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 " + "Validity Analysis Handoff Quality" + ) From 8815f73d478b76a4b54b977811466edd23611a29 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:44:50 -0700 Subject: [PATCH 39/41] fix(validity): retrigger quality on shared config --- .github/workflows/validity-analysis-quality.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/validity-analysis-quality.yml b/.github/workflows/validity-analysis-quality.yml index 5c9c57e8a..17d5e8755 100644 --- a/.github/workflows/validity-analysis-quality.yml +++ b/.github/workflows/validity-analysis-quality.yml @@ -13,6 +13,14 @@ on: - "docs/traceability/validation-analysis-handoff.md" - ".github/requirements/foundation-test.txt" - ".github/workflows/validity-analysis-quality.yml" + - ".gitignore" + - ".python-version" + - "conftest.py" + - "packages/conftest.py" + - "pyproject.toml" + - "pytest.ini" + - "setup.cfg" + - "tox.ini" workflow_dispatch: permissions: From 3c2a1dec4cb0668542d73ae71e4605ca6a708f17 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:45:03 -0700 Subject: [PATCH 40/41] docs(validity): record shared-config gate integrity --- packages/validity-analysis/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/validity-analysis/CHANGELOG.md b/packages/validity-analysis/CHANGELOG.md index fd7762043..d0ff8bd9f 100644 --- a/packages/validity-analysis/CHANGELOG.md +++ b/packages/validity-analysis/CHANGELOG.md @@ -10,3 +10,4 @@ - Reject impossible aggregate missingness where complete observations overlap either predictor-missing or criterion-missing counts beyond the sample total. - Require exact governed missingness/convergence runtime types so subclass method overrides cannot inject unreviewed or person-level fields into canonical result evidence. - Freeze exact UTC timestamps and finite numeric values at construction, and reject runtime-type forgery before canonical evidence serialization. +- Make `Validity Analysis Handoff Quality` retrigger on shared repository Python/test/clean-checkout configuration, with an executable regression preventing stale package-quality evidence after shared tooling changes. From 6ca554791595d925a76587378b543e7dbc3dc20b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:45:16 -0700 Subject: [PATCH 41/41] docs(validity): trace shared-config quality evidence --- docs/traceability/validation-analysis-handoff.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/traceability/validation-analysis-handoff.md b/docs/traceability/validation-analysis-handoff.md index 65e22b253..f9f8c755e 100644 --- a/docs/traceability/validation-analysis-handoff.md +++ b/docs/traceability/validation-analysis-handoff.md @@ -17,6 +17,7 @@ Can an organization send one exact, reviewable validation study to its statistic | Execution boundary | `not_executed`, `scientific_evidence_only`, read-only pinned foreign dependency | immutable governance regressions | | Reproducibility | construction-time UTC timestamp snapshots, finite numeric snapshots, canonical RFC 3339 time, canonical JSON, SHA-256 handoff digest | mutable timezone/numeric and UTC-boundary regressions plus deterministic serialization/digest tests | | Decision-record integrity | ADR numbers remain unique repository-wide and any `docs/adr/**` change reaches the validity quality gate | ADR uniqueness regression plus workflow-trigger contract regression | +| Quality-evidence freshness | package quality reruns whenever shared repository Python/test/clean-checkout configuration can alter execution or tracked-tree cleanliness | `test_quality_workflow_retriggers_on_shared_test_configuration` plus `.github/workflows/validity-analysis-quality.yml`; this supplemental package gate does not replace central required workflows | ## Maturity