From 095b899ebb29bcb6172bc7a42319bc65d2fc4a7c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:05:17 -0700 Subject: [PATCH 001/216] test: define structured interview plan quality contract --- packages/interview-plan/pyproject.toml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 packages/interview-plan/pyproject.toml diff --git a/packages/interview-plan/pyproject.toml b/packages/interview-plan/pyproject.toml new file mode 100644 index 000000000..1bb874772 --- /dev/null +++ b/packages/interview-plan/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + +[project] +name = "orgmetra-interview-plan" +version = "0.1.0" +description = "Candidate-neutral structured-interview plan evidence for Orgmetra." +requires-python = ">=3.12" + +[project.optional-dependencies] +test = ["pytest>=8.3", "pytest-cov>=5.0"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = [ + "--cov=orgmetra_interview_plan", + "--cov-branch", + "--cov-report=term-missing", + "--cov-fail-under=100", +] From f4da61d6968771bde779873be7411211ad72b1ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:05:42 -0700 Subject: [PATCH 002/216] test: add RED structured interview plan regressions --- packages/interview-plan/tests/test_plan.py | 153 +++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 packages/interview-plan/tests/test_plan.py diff --git a/packages/interview-plan/tests/test_plan.py b/packages/interview-plan/tests/test_plan.py new file mode 100644 index 000000000..91bea9867 --- /dev/null +++ b/packages/interview-plan/tests/test_plan.py @@ -0,0 +1,153 @@ +from dataclasses import replace +from datetime import datetime, timedelta, timezone, tzinfo +from hashlib import sha256 +import json +import pytest + +from orgmetra_interview_plan import StructuredInterviewPlan, build_structured_interview_plan + +TENANT = "12345678-1234-4234-8234-123456789abc" +DIGEST_A = "a" * 64 +DIGEST_B = "b" * 64 +DIGEST_C = "c" * 64 + + +def values(): + return dict( + tenant_record_id=TENANT, + interview_plan_reference="interview_plan:si-2026-001", + requisition_reference="requisition:req-2026-001", + job_profile_reference="job_profile:job-001", + job_analysis_reference="job_analysis:analysis-001", + job_analysis_digest=DIGEST_A, + question_set_reference="question_set:questions-v1", + question_set_digest=DIGEST_B, + rating_anchor_reference="rating_anchor:anchors-v1", + rating_anchor_digest=DIGEST_C, + competency_references=("competency:analysis", "competency:communication"), + panel_actor_references=("actor:interviewer-a", "actor:interviewer-b"), + question_count=4, + purpose_code="structured_interview_plan", + reason_code="approved_requisition_interview", + generated_at=datetime(2026, 8, 18, 12, 34, 56, 123456, tzinfo=timezone.utc), + ) + + +def test_builds_candidate_neutral_deterministic_plan(): + plan = build_structured_interview_plan(**values()) + payload = json.loads(plan.canonical_json()) + assert payload["review_state"] == "requires_human_approval" + assert payload["human_confirmation_required"] is True + assert payload["generated_at"].endswith(".123456Z") + assert "candidate" not in plan.canonical_json() + assert plan.sha256_digest() == sha256(plan.canonical_json().encode("utf-8")).hexdigest() + assert plan == StructuredInterviewPlan(**values()) + + +@pytest.mark.parametrize("field,bad", [ + ("tenant_record_id", "not-a-uuid"), + ("tenant_record_id", "00000000-0000-0000-0000-000000000000"), + ("tenant_record_id", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"), + ("interview_plan_reference", "wrong:si-1"), + ("requisition_reference", "wrong:req-1"), + ("job_profile_reference", "wrong:job-1"), + ("job_analysis_reference", "wrong:analysis-1"), + ("question_set_reference", "wrong:q-1"), + ("rating_anchor_reference", "wrong:a-1"), + ("job_analysis_digest", "A" * 64), + ("question_set_digest", "b" * 63), + ("rating_anchor_digest", 7), + ("purpose_code", "wrong_purpose"), + ("purpose_code", "bad"), + ("reason_code", "Bad Reason"), + ("reason_code", "a_" + "b" * 64), + ("generated_at", datetime(2026, 8, 18, 1, 2, 3)), + ("human_confirmation_required", False), + ("human_confirmation_required", 1), + ("review_state", "approved"), + ("next_action", "Skip human review"), +]) +def test_rejects_invalid_scalar_contract(field, bad): + data = values() + data[field] = bad + with pytest.raises((ValueError, TypeError)): + StructuredInterviewPlan(**data) + + +@pytest.mark.parametrize("refs", [(), tuple(f"competency:c{i}" for i in range(13)), ["competency:a"]]) +def test_rejects_bad_competency_collection_shape(refs): + data = values() + data["competency_references"] = refs + with pytest.raises(ValueError, match="competency_references"): + StructuredInterviewPlan(**data) + + +@pytest.mark.parametrize("refs", [ + ("competency:communication", "competency:analysis"), + ("competency:analysis", "competency:analysis"), + ("wrong:analysis",), +]) +def test_rejects_noncanonical_competencies(refs): + data = values() + data["competency_references"] = refs + with pytest.raises(ValueError): + StructuredInterviewPlan(**data) + + +@pytest.mark.parametrize("refs", [ + ("actor:only-one",), + tuple(f"actor:p{i}" for i in range(9)), + ["actor:a", "actor:b"], + ("actor:b", "actor:a"), + ("actor:a", "actor:a"), + ("wrong:a", "actor:b"), +]) +def test_rejects_bad_panel_contract(refs): + data = values() + data["panel_actor_references"] = refs + with pytest.raises(ValueError, match="panel_actor_references|actor"): + StructuredInterviewPlan(**data) + + +@pytest.mark.parametrize("count", [True, 0, 21, 1]) +def test_rejects_bad_question_count(count): + data = values() + data["question_count"] = count + with pytest.raises(ValueError, match="question_count"): + StructuredInterviewPlan(**data) + + +def test_accepts_question_count_equal_to_competency_count(): + data = values() + data["question_count"] = 2 + assert StructuredInterviewPlan(**data).question_count == 2 + + +class UnknownOffset(tzinfo): + def utcoffset(self, dt): + return None + + def dst(self, dt): + return None + + +def test_rejects_timezone_with_unknown_offset(): + data = values() + data["generated_at"] = datetime(2026, 8, 18, tzinfo=UnknownOffset()) + with pytest.raises(ValueError, match="timezone-aware"): + StructuredInterviewPlan(**data) + + +def test_canonicalizes_non_utc_offset_and_preserves_fractional_precision(): + data = values() + data["generated_at"] = datetime( + 2026, 8, 18, 21, 34, 56, 123456, tzinfo=timezone(timedelta(hours=9)) + ) + payload = json.loads(StructuredInterviewPlan(**data).canonical_json()) + assert payload["generated_at"] == "2026-08-18T12:34:56.123456Z" + + +def test_direct_replace_is_revalidated(): + plan = StructuredInterviewPlan(**values()) + with pytest.raises(ValueError, match="question_set_digest"): + replace(plan, question_set_digest="not-a-digest") From 19a4a5f47baf15665f4c011368aba7d095bf60e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:06:15 -0700 Subject: [PATCH 003/216] feat: add governed structured interview plan --- .../src/orgmetra_interview_plan/plan.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 packages/interview-plan/src/orgmetra_interview_plan/plan.py diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py new file mode 100644 index 000000000..5d968da76 --- /dev/null +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -0,0 +1,163 @@ +"""Governed, candidate-neutral structured-interview plan evidence. + +The plan binds an interview to job-analysis evidence, predetermined competencies, +question/rating artifacts, and an accountable interviewer panel. It contains no +candidate PII or candidate response/score and remains pending explicit human approval. +""" +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}$") +_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 = "structured_interview_plan" +_REVIEW_STATE = "requires_human_approval" +_NEXT_ACTION = ( + "Confirm the competencies, predetermined questions, rating anchors, and trained panel " + "are job-related and appropriate before activating this structured interview plan." +) + + +def _validate_operational_uuid(value: str, field_name: str) -> None: + """Require canonical non-sentinel UUID text for a governance identity.""" + try: + parsed = UUID(value) + except (ValueError, AttributeError, TypeError) as exc: + raise ValueError(f"{field_name} must be canonical UUID text") from exc + if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUID") + + +def _validate_code(value: str, field_name: str) -> None: + """Require a bounded descriptive lower snake_case governance code.""" + if not isinstance(value, str) or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): + raise ValueError(f"{field_name} must be bounded two-or-more-word lower snake_case") + + +def _validate_reference(value: str, prefix: str, field_name: str) -> None: + """Require a bounded namespaced opaque reference with the expected prefix.""" + if ( + not isinstance(value, str) + or len(value) > 160 + or not _REFERENCE_PATTERN.fullmatch(value) + or not value.startswith(f"{prefix}:") + ): + raise ValueError(f"{field_name} must be an opaque {prefix}: reference") + + +def _validate_digest(value: str, field_name: str) -> None: + """Require lowercase SHA-256 hexadecimal evidence.""" + if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + raise ValueError(f"{field_name} must be lowercase SHA-256 hex") + + +def _canonical_timestamp(value: datetime) -> str: + """Render an aware instant as precision-preserving UTC RFC 3339 text.""" + if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: + raise ValueError("generated_at must be timezone-aware") + return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +@dataclass(frozen=True, slots=True) +class StructuredInterviewPlan: + """Immutable candidate-neutral interview-plan evidence awaiting human approval.""" + + tenant_record_id: str + interview_plan_reference: str + requisition_reference: str + job_profile_reference: str + job_analysis_reference: str + job_analysis_digest: str + question_set_reference: str + question_set_digest: str + rating_anchor_reference: str + rating_anchor_digest: str + competency_references: tuple[str, ...] + panel_actor_references: tuple[str, ...] + question_count: int + purpose_code: str + reason_code: str + generated_at: datetime + human_confirmation_required: bool = True + review_state: str = _REVIEW_STATE + next_action: str = _NEXT_ACTION + + def __post_init__(self) -> None: + """Fail closed when direct construction drifts from the governed contract.""" + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + _validate_reference(self.interview_plan_reference, "interview_plan", "interview_plan_reference") + _validate_reference(self.requisition_reference, "requisition", "requisition_reference") + _validate_reference(self.job_profile_reference, "job_profile", "job_profile_reference") + _validate_reference(self.job_analysis_reference, "job_analysis", "job_analysis_reference") + _validate_digest(self.job_analysis_digest, "job_analysis_digest") + _validate_reference(self.question_set_reference, "question_set", "question_set_reference") + _validate_digest(self.question_set_digest, "question_set_digest") + _validate_reference(self.rating_anchor_reference, "rating_anchor", "rating_anchor_reference") + _validate_digest(self.rating_anchor_digest, "rating_anchor_digest") + if not isinstance(self.competency_references, tuple) or not 1 <= len(self.competency_references) <= 12: + raise ValueError("competency_references must be a tuple containing 1 through 12 competencies") + for reference in self.competency_references: + _validate_reference(reference, "competency", "competency_references") + if tuple(sorted(set(self.competency_references))) != self.competency_references: + raise ValueError("competency_references must be sorted and unique") + if not isinstance(self.panel_actor_references, tuple) or not 2 <= len(self.panel_actor_references) <= 8: + raise ValueError("panel_actor_references must be a tuple containing 2 through 8 actors") + for reference in self.panel_actor_references: + _validate_reference(reference, "actor", "panel_actor_references") + if tuple(sorted(set(self.panel_actor_references))) != self.panel_actor_references: + raise ValueError("panel_actor_references must be sorted and unique") + if type(self.question_count) is not int or not 1 <= self.question_count <= 20: + raise ValueError("question_count must be an integer from 1 through 20") + if self.question_count < len(self.competency_references): + raise ValueError("question_count must cover every governed competency") + _validate_code(self.purpose_code, "purpose_code") + if self.purpose_code != _PURPOSE_CODE: + raise ValueError("purpose_code must remain structured_interview_plan") + _validate_code(self.reason_code, "reason_code") + _canonical_timestamp(self.generated_at) + if self.human_confirmation_required is not True: + raise ValueError("human confirmation is mandatory for interview-plan approval") + if self.review_state != _REVIEW_STATE: + raise ValueError("review_state must remain requires_human_approval") + if self.next_action != _NEXT_ACTION: + raise ValueError("next_action must remain the governed interview-plan instruction") + + def canonical_json(self) -> str: + """Return deterministic canonical JSON for immutable audit correlation.""" + payload = { + "competency_references": list(self.competency_references), + "generated_at": _canonical_timestamp(self.generated_at), + "human_confirmation_required": self.human_confirmation_required, + "interview_plan_reference": self.interview_plan_reference, + "job_analysis_digest": self.job_analysis_digest, + "job_analysis_reference": self.job_analysis_reference, + "job_profile_reference": self.job_profile_reference, + "next_action": self.next_action, + "panel_actor_references": list(self.panel_actor_references), + "purpose_code": self.purpose_code, + "question_count": self.question_count, + "question_set_digest": self.question_set_digest, + "question_set_reference": self.question_set_reference, + "rating_anchor_digest": self.rating_anchor_digest, + "rating_anchor_reference": self.rating_anchor_reference, + "reason_code": self.reason_code, + "requisition_reference": self.requisition_reference, + "review_state": self.review_state, + "tenant_record_id": self.tenant_record_id, + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def sha256_digest(self) -> str: + """Return SHA-256 over the exact canonical UTF-8 plan.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + +def build_structured_interview_plan(**kwargs: object) -> StructuredInterviewPlan: + """Build a governed structured-interview plan that remains pending human approval.""" + return StructuredInterviewPlan(**kwargs) # type: ignore[arg-type] From 12566d6ca0e618b5c7d5cb6442a86af4ce7821c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:06:25 -0700 Subject: [PATCH 004/216] feat: export structured interview plan contract --- .../interview-plan/src/orgmetra_interview_plan/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/interview-plan/src/orgmetra_interview_plan/__init__.py diff --git a/packages/interview-plan/src/orgmetra_interview_plan/__init__.py b/packages/interview-plan/src/orgmetra_interview_plan/__init__.py new file mode 100644 index 000000000..eefddc536 --- /dev/null +++ b/packages/interview-plan/src/orgmetra_interview_plan/__init__.py @@ -0,0 +1,4 @@ +"""Public structured-interview planning contracts for Orgmetra.""" +from .plan import StructuredInterviewPlan, build_structured_interview_plan + +__all__ = ["StructuredInterviewPlan", "build_structured_interview_plan"] From 89cd211835bf3c6108d947ddf7a60c3a79d3eaf6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:06:48 -0700 Subject: [PATCH 005/216] docs: explain structured interview plan boundary --- packages/interview-plan/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/interview-plan/README.md diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md new file mode 100644 index 000000000..17e7baea0 --- /dev/null +++ b/packages/interview-plan/README.md @@ -0,0 +1,11 @@ +# Orgmetra structured interview plan + +`orgmetra-interview-plan` creates candidate-neutral evidence for approving a structured interview **before** it is used with applicants. + +The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, rating anchors, job-related competency references, and a bounded interviewer panel. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, and provider data out of the packet. + +The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. + +For consistency and immutable audit correlation, all governance references are bounded opaque namespaced identifiers, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. + +This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. From fd89a19c262161ae0a2fc5b5b51e01303647fe5e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:06:59 -0700 Subject: [PATCH 006/216] docs: record structured interview plan slice --- packages/interview-plan/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/interview-plan/CHANGELOG.md diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md new file mode 100644 index 000000000..73d6ac837 --- /dev/null +++ b/packages/interview-plan/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 0.1.0 - Unreleased + +### Added + +- Candidate-neutral `StructuredInterviewPlan` binding an approved requisition and Job to exact job-analysis, question-set, rating-anchor, competency, and interviewer-panel evidence. +- Fail-closed direct-construction validation, deterministic canonical JSON/SHA-256 audit correlation, explicit human approval state, and 100% owned statement/branch regression coverage. From 043e5b4f858e76563f9c744ab55b4315ac4e778a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:07:15 -0700 Subject: [PATCH 007/216] docs: doctor structured interview plan evidence --- .../structured-interview-plan-references.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/doctoring/structured-interview-plan-references.md diff --git a/docs/doctoring/structured-interview-plan-references.md b/docs/doctoring/structured-interview-plan-references.md new file mode 100644 index 000000000..0c222236a --- /dev/null +++ b/docs/doctoring/structured-interview-plan-references.md @@ -0,0 +1,17 @@ +# Structured interview plan references + +These sources inform the active-PR structured-interview planning contract. They do not establish certification or replace organization-specific legal review, job analysis, validation, or adverse-impact monitoring. + +## APA 7 references + +International Organization for Standardization. (2023). *ISO 30405:2023 human resource management — Guidelines on recruitment* (2nd ed.). https://www.iso.org/standard/79488.html + +U.S. Equal Employment Opportunity Commission. (1979, March 1). *Questions and answers to clarify and provide a common interpretation of the Uniform Guidelines on Employee Selection Procedures*. https://www.eeoc.gov/laws/guidance/questions-and-answers-clarify-and-provide-common-interpretation-uniform-guidelines + +U.S. Office of Personnel Management. (n.d.). *Structured interviews*. Retrieved August 18, 2026, from https://www.opm.gov/policy-data-oversight/assessment-and-selection/structured-interviews/ + +U.S. Office of Personnel Management. (n.d.). *How do I select the competencies, or content areas, I want to assess with the structured interview?* Retrieved August 18, 2026, from https://www.opm.gov/frequently-asked-questions/assessment-policy-faq/structured-interviews/how-do-i-select-the-competencies-or-content-areas-i-want-to-assess-with-the-structured-interview/ + +## Applied boundary + +OPM describes structured interviews as standardized, job-related assessment methods using predetermined questions and common rating standards, with competencies selected from job analysis and confirmed by subject-matter experts. The UGESP guidance emphasizes documenting job relatedness and the basis for selection procedures. ISO 30405:2023 provides current recruitment-process guidance covering assessment and stakeholder management. Orgmetra therefore binds the interview plan to exact job-analysis evidence, predetermined question/rating artifacts, and accountable human review before candidate use. From 0dbba0f5c70abcdb401a083358ec1cf9ec15acc3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:07:36 -0700 Subject: [PATCH 008/216] docs: record structured interview plan decision --- ...0014-governed-structured-interview-plan.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/adr/0014-governed-structured-interview-plan.md diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md new file mode 100644 index 000000000..afe6355b6 --- /dev/null +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -0,0 +1,45 @@ +# ADR 0014: Govern structured-interview plans as candidate-neutral evidence + +- **Status:** Proposed — active PR only +- **Date:** 2026-08-18 + +## Context + +Orgmetra already separates authoritative Job/Position/Assignment truth, governed requisition review, selection evidence, and accountable human employment decisions. A buyer still needs a defensible boundary between an approved opening and the interview that will be used as a selection procedure. + +A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity and assessment values are unnecessary at this pre-use boundary and would increase privacy risk. + +## Decision + +Add a transport-neutral `StructuredInterviewPlan` value object that binds: + +- canonical tenant identity and one opaque interview-plan reference; +- one requisition and authoritative Job reference; +- exact job-analysis reference plus SHA-256 digest; +- exact predetermined question-set and rating-anchor references plus SHA-256 digests; +- a sorted, unique set of job-related competency references; +- a sorted, unique interviewer panel of 2–8 accountable actor references; +- a bounded question count that covers every governed competency; +- fixed purpose `structured_interview_plan`, bounded reason metadata, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. + +The packet is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Direct construction and builder construction share the same fail-closed validation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. + +## Consequences + +### Positive + +- Buyers can prove which Job Analysis, competencies, questions, rating anchors, and interview panel were approved before candidate use. +- Candidate PII and assessment values remain outside the planning artifact. +- Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest rather than copying question content. +- The contract supports standalone use and later MSA extraction without cross-service application-table SQL. + +### Costs and constraints + +- The plan does not persist requisitions, Job Analysis, interview responses, or scores. +- Human approval remains mandatory; model output cannot activate or approve the plan. +- Content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. +- This ADR remains proposed until its exact PR head merges into protected `develop`. + +## References + +See `docs/doctoring/structured-interview-plan-references.md`. From 735c27d77e95548e99190465470b6d25996cfe3e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:07:56 -0700 Subject: [PATCH 009/216] docs: trace structured interview plan contract --- .../traceability/structured-interview-plan.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/traceability/structured-interview-plan.md diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md new file mode 100644 index 000000000..70225d863 --- /dev/null +++ b/docs/traceability/structured-interview-plan.md @@ -0,0 +1,22 @@ +# Structured interview plan traceability + +## Truth status + +**Active PR only.** Protected `develop` at branch creation does not contain this capability. Do not describe it as shipped until the exact integrated protected head passes all required gates and merges. + +## Buyer requirement → executable evidence + +| Requirement | Contract | Evidence | +|---|---|---| +| Interview content is tied to job analysis | exact `job_analysis_reference` + lowercase SHA-256 digest | `test_builds_candidate_neutral_deterministic_plan`; invalid-reference/digest regressions | +| Predetermined questions and rating anchors cannot drift silently | exact question-set/rating-anchor references and digests | invalid-reference/digest regressions; deterministic SHA-256 test | +| Every governed competency is covered | sorted unique 1–12 competency references; `question_count >= competency_count` | collection-shape, ordering, duplication, prefix, and question-count regressions | +| Interview panel is accountable and bounded | sorted unique 2–8 `actor:` references | panel size/type/order/duplicate/prefix regressions | +| Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | +| High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and next action | scalar fail-closed regressions | +| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 | naive/unknown-offset/offset/fractional-time regressions and independent digest assertion | +| Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace` regressions | + +## Out of scope + +This slice does not persist interview plans, questions, responses, scores, candidate PII, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. From dac45f0a73d27198932ad4be38ba7d347881836d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:08:08 -0700 Subject: [PATCH 010/216] ci: add structured interview plan quality gate --- .github/workflows/interview-plan-quality.yml | 57 ++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/interview-plan-quality.yml diff --git a/.github/workflows/interview-plan-quality.yml b/.github/workflows/interview-plan-quality.yml new file mode 100644 index 000000000..abebb074a --- /dev/null +++ b/.github/workflows/interview-plan-quality.yml @@ -0,0 +1,57 @@ +name: Structured Interview Plan Quality + +on: + pull_request: + branches: + - develop + paths: + - "packages/interview-plan/**" + - ".github/requirements/foundation-test.txt" + - ".github/workflows/interview-plan-quality.yml" + - "docs/adr/0014-governed-structured-interview-plan.md" + - "docs/doctoring/structured-interview-plan-references.md" + - "docs/traceability/structured-interview-plan.md" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: structured-interview-plan-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + unit: + name: Structured interview plan contract and 100% coverage + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Prove exact candidate checkout + env: + ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + check-latest: false + - name: Install reviewed test toolchain + run: | + python -m pip install --require-hashes --no-deps --only-binary=:all: -r .github/requirements/foundation-test.txt + python -m pip check + - name: Compile structured interview plan package + run: python -m compileall -q packages/interview-plan/src packages/interview-plan/tests + - name: Test structured interview plan with exact statement and branch coverage + env: + PYTHONPATH: packages/interview-plan/src + COVERAGE_FILE: /tmp/orgmetra-structured-interview-plan.coverage + run: python -m pytest -c packages/interview-plan/pyproject.toml packages/interview-plan/tests + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" From ee043e8ec83e45ae62210fcbb4d42f4b59669ac1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:08:42 -0700 Subject: [PATCH 011/216] refactor: keep interview plan builder explicitly typed --- .../src/orgmetra_interview_plan/plan.py | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 5d968da76..b0bc91743 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -158,6 +158,41 @@ def sha256_digest(self) -> str: return sha256(self.canonical_json().encode("utf-8")).hexdigest() -def build_structured_interview_plan(**kwargs: object) -> StructuredInterviewPlan: +def build_structured_interview_plan( + *, + tenant_record_id: str, + interview_plan_reference: str, + requisition_reference: str, + job_profile_reference: str, + job_analysis_reference: str, + job_analysis_digest: str, + question_set_reference: str, + question_set_digest: str, + rating_anchor_reference: str, + rating_anchor_digest: str, + competency_references: tuple[str, ...], + panel_actor_references: tuple[str, ...], + question_count: int, + purpose_code: str, + reason_code: str, + generated_at: datetime, +) -> StructuredInterviewPlan: """Build a governed structured-interview plan that remains pending human approval.""" - return StructuredInterviewPlan(**kwargs) # type: ignore[arg-type] + return StructuredInterviewPlan( + tenant_record_id=tenant_record_id, + interview_plan_reference=interview_plan_reference, + requisition_reference=requisition_reference, + job_profile_reference=job_profile_reference, + job_analysis_reference=job_analysis_reference, + job_analysis_digest=job_analysis_digest, + question_set_reference=question_set_reference, + question_set_digest=question_set_digest, + rating_anchor_reference=rating_anchor_reference, + rating_anchor_digest=rating_anchor_digest, + competency_references=competency_references, + panel_actor_references=panel_actor_references, + question_count=question_count, + purpose_code=purpose_code, + reason_code=reason_code, + generated_at=generated_at, + ) From 91d9860d7dbec9bca41bdb3b6f825395354c9bf6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:14:58 -0700 Subject: [PATCH 012/216] test: require question-to-competency mapping evidence --- packages/interview-plan/tests/test_plan.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/interview-plan/tests/test_plan.py b/packages/interview-plan/tests/test_plan.py index 91bea9867..b52a429de 100644 --- a/packages/interview-plan/tests/test_plan.py +++ b/packages/interview-plan/tests/test_plan.py @@ -10,6 +10,7 @@ DIGEST_A = "a" * 64 DIGEST_B = "b" * 64 DIGEST_C = "c" * 64 +DIGEST_D = "d" * 64 def values(): @@ -22,6 +23,8 @@ def values(): job_analysis_digest=DIGEST_A, question_set_reference="question_set:questions-v1", question_set_digest=DIGEST_B, + question_competency_map_reference="question_competency_map:map-v1", + question_competency_map_digest=DIGEST_D, rating_anchor_reference="rating_anchor:anchors-v1", rating_anchor_digest=DIGEST_C, competency_references=("competency:analysis", "competency:communication"), @@ -39,6 +42,7 @@ def test_builds_candidate_neutral_deterministic_plan(): assert payload["review_state"] == "requires_human_approval" assert payload["human_confirmation_required"] is True assert payload["generated_at"].endswith(".123456Z") + assert payload["question_competency_map_reference"] == "question_competency_map:map-v1" assert "candidate" not in plan.canonical_json() assert plan.sha256_digest() == sha256(plan.canonical_json().encode("utf-8")).hexdigest() assert plan == StructuredInterviewPlan(**values()) @@ -53,9 +57,11 @@ def test_builds_candidate_neutral_deterministic_plan(): ("job_profile_reference", "wrong:job-1"), ("job_analysis_reference", "wrong:analysis-1"), ("question_set_reference", "wrong:q-1"), + ("question_competency_map_reference", "wrong:map-1"), ("rating_anchor_reference", "wrong:a-1"), ("job_analysis_digest", "A" * 64), ("question_set_digest", "b" * 63), + ("question_competency_map_digest", "D" * 64), ("rating_anchor_digest", 7), ("purpose_code", "wrong_purpose"), ("purpose_code", "bad"), From e7a2a7395d22f8ebfe73fb0e8a20931effbd0455 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:15:35 -0700 Subject: [PATCH 013/216] fix: bind questions to governed competency mapping evidence --- .../src/orgmetra_interview_plan/plan.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index b0bc91743..74223cfa9 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -76,6 +76,8 @@ class StructuredInterviewPlan: job_analysis_digest: str question_set_reference: str question_set_digest: str + question_competency_map_reference: str + question_competency_map_digest: str rating_anchor_reference: str rating_anchor_digest: str competency_references: tuple[str, ...] @@ -98,6 +100,12 @@ def __post_init__(self) -> None: _validate_digest(self.job_analysis_digest, "job_analysis_digest") _validate_reference(self.question_set_reference, "question_set", "question_set_reference") _validate_digest(self.question_set_digest, "question_set_digest") + _validate_reference( + self.question_competency_map_reference, + "question_competency_map", + "question_competency_map_reference", + ) + _validate_digest(self.question_competency_map_digest, "question_competency_map_digest") _validate_reference(self.rating_anchor_reference, "rating_anchor", "rating_anchor_reference") _validate_digest(self.rating_anchor_digest, "rating_anchor_digest") if not isinstance(self.competency_references, tuple) or not 1 <= len(self.competency_references) <= 12: @@ -141,6 +149,8 @@ def canonical_json(self) -> str: "next_action": self.next_action, "panel_actor_references": list(self.panel_actor_references), "purpose_code": self.purpose_code, + "question_competency_map_digest": self.question_competency_map_digest, + "question_competency_map_reference": self.question_competency_map_reference, "question_count": self.question_count, "question_set_digest": self.question_set_digest, "question_set_reference": self.question_set_reference, @@ -168,6 +178,8 @@ def build_structured_interview_plan( job_analysis_digest: str, question_set_reference: str, question_set_digest: str, + question_competency_map_reference: str, + question_competency_map_digest: str, rating_anchor_reference: str, rating_anchor_digest: str, competency_references: tuple[str, ...], @@ -187,6 +199,8 @@ def build_structured_interview_plan( job_analysis_digest=job_analysis_digest, question_set_reference=question_set_reference, question_set_digest=question_set_digest, + question_competency_map_reference=question_competency_map_reference, + question_competency_map_digest=question_competency_map_digest, rating_anchor_reference=rating_anchor_reference, rating_anchor_digest=rating_anchor_digest, competency_references=competency_references, From dd9f3aac8e4873780d06ef7dccd3a6e619add586 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:16:12 -0700 Subject: [PATCH 014/216] docs: bind interview questions to competency map evidence --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 17e7baea0..80070f971 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -2,7 +2,7 @@ `orgmetra-interview-plan` creates candidate-neutral evidence for approving a structured interview **before** it is used with applicants. -The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, rating anchors, job-related competency references, and a bounded interviewer panel. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, and provider data out of the packet. +The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, an exact question-to-competency mapping artifact, rating anchors, job-related competency references, and a bounded interviewer panel. The question set and mapping each carry their own immutable SHA-256 evidence digest, so a count of questions cannot be mistaken for proof that every governed competency is actually assessed. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, and provider data out of the packet. The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. From 66ddaac5df93727e36a6043981d8fbdf065534f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:16:30 -0700 Subject: [PATCH 015/216] docs: record competency mapping hardening --- packages/interview-plan/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 73d6ac837..e5f46bb30 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -4,5 +4,9 @@ ### Added -- Candidate-neutral `StructuredInterviewPlan` binding an approved requisition and Job to exact job-analysis, question-set, rating-anchor, competency, and interviewer-panel evidence. +- Candidate-neutral `StructuredInterviewPlan` binding an approved requisition and Job to exact job-analysis, question-set, question-to-competency mapping, rating-anchor, competency, and interviewer-panel evidence. - Fail-closed direct-construction validation, deterministic canonical JSON/SHA-256 audit correlation, explicit human approval state, and 100% owned statement/branch regression coverage. + +### Changed + +- Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. From eca3fa72b33c2bfacdfd45001f9aaf43df023c6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:16:52 -0700 Subject: [PATCH 016/216] docs: require question competency map evidence --- docs/adr/0014-governed-structured-interview-plan.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index afe6355b6..da4324a78 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -7,7 +7,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed requisition review, selection evidence, and accountable human employment decisions. A buyer still needs a defensible boundary between an approved opening and the interview that will be used as a selection procedure. -A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity and assessment values are unnecessary at this pre-use boundary and would increase privacy risk. +A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity and assessment values are unnecessary at this pre-use boundary and would increase privacy risk. ## Decision @@ -16,10 +16,10 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: - canonical tenant identity and one opaque interview-plan reference; - one requisition and authoritative Job reference; - exact job-analysis reference plus SHA-256 digest; -- exact predetermined question-set and rating-anchor references plus SHA-256 digests; +- exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; - a sorted, unique set of job-related competency references; - a sorted, unique interviewer panel of 2–8 accountable actor references; -- a bounded question count that covers every governed competency; +- a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; - fixed purpose `structured_interview_plan`, bounded reason metadata, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. The packet is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Direct construction and builder construction share the same fail-closed validation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. @@ -28,16 +28,16 @@ The packet is candidate-neutral. It contains no candidate identity, response, sc ### Positive -- Buyers can prove which Job Analysis, competencies, questions, rating anchors, and interview panel were approved before candidate use. +- Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, and interview panel were approved before candidate use. - Candidate PII and assessment values remain outside the planning artifact. - Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest rather than copying question content. - The contract supports standalone use and later MSA extraction without cross-service application-table SQL. ### Costs and constraints -- The plan does not persist requisitions, Job Analysis, interview responses, or scores. +- The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, or scores. - Human approval remains mandatory; model output cannot activate or approve the plan. -- Content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. +- The mapping digest proves identity/integrity of the approved mapping artifact, not that its content is scientifically adequate; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. ## References From 8c54d1fc211bdd0f245e80231cb310250f0dbfda Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:17:13 -0700 Subject: [PATCH 017/216] docs: trace question competency mapping evidence --- docs/traceability/structured-interview-plan.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 70225d863..474f9e48f 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -9,14 +9,18 @@ | Requirement | Contract | Evidence | |---|---|---| | Interview content is tied to job analysis | exact `job_analysis_reference` + lowercase SHA-256 digest | `test_builds_candidate_neutral_deterministic_plan`; invalid-reference/digest regressions | -| Predetermined questions and rating anchors cannot drift silently | exact question-set/rating-anchor references and digests | invalid-reference/digest regressions; deterministic SHA-256 test | -| Every governed competency is covered | sorted unique 1–12 competency references; `question_count >= competency_count` | collection-shape, ordering, duplication, prefix, and question-count regressions | +| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | exact question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid-reference/digest regressions; deterministic SHA-256 test | +| Every governed competency has auditable coverage evidence | sorted unique 1–12 competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection-shape/order/duplicate/prefix and question-count regressions plus required mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 `actor:` references | panel size/type/order/duplicate/prefix regressions | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | | High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and next action | scalar fail-closed regressions | | Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 | naive/unknown-offset/offset/fractional-time regressions and independent digest assertion | | Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace` regressions | +## Evidence boundary + +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. It does not by itself prove that the mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. + ## Out of scope -This slice does not persist interview plans, questions, responses, scores, candidate PII, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. +This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. From 092dd77a8ddafcd1b48c7ca7d555174f4e832ea8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 09:03:26 -0700 Subject: [PATCH 018/216] test: require cardinality-only interview error --- packages/interview-plan/tests/test_plan.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/interview-plan/tests/test_plan.py b/packages/interview-plan/tests/test_plan.py index b52a429de..a5cf23abe 100644 --- a/packages/interview-plan/tests/test_plan.py +++ b/packages/interview-plan/tests/test_plan.py @@ -123,6 +123,21 @@ def test_rejects_bad_question_count(count): StructuredInterviewPlan(**data) +def test_question_count_error_describes_only_the_cardinality_constraint(): + data = values() + data["competency_references"] = ( + "competency:analysis", + "competency:communication", + "competency:judgment", + ) + data["question_count"] = 2 + with pytest.raises( + ValueError, + match="question_count must be at least the number of governed competencies", + ): + StructuredInterviewPlan(**data) + + def test_accepts_question_count_equal_to_competency_count(): data = values() data["question_count"] = 2 From 1e4ea432ac7bf26ce16dc2224153390788843c3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 09:04:06 -0700 Subject: [PATCH 019/216] fix: describe interview count constraint precisely --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 74223cfa9..baa912dac 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -123,7 +123,7 @@ def __post_init__(self) -> None: if type(self.question_count) is not int or not 1 <= self.question_count <= 20: raise ValueError("question_count must be an integer from 1 through 20") if self.question_count < len(self.competency_references): - raise ValueError("question_count must cover every governed competency") + raise ValueError("question_count must be at least the number of governed competencies") _validate_code(self.purpose_code, "purpose_code") if self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain structured_interview_plan") From b8fd5be329daffe46b68b083baaf32fa5a0ce3ca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:19:17 -0700 Subject: [PATCH 020/216] test: reject value-bearing structured interview metadata --- packages/interview-plan/tests/test_plan.py | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/interview-plan/tests/test_plan.py b/packages/interview-plan/tests/test_plan.py index a5cf23abe..cef5dad39 100644 --- a/packages/interview-plan/tests/test_plan.py +++ b/packages/interview-plan/tests/test_plan.py @@ -172,3 +172,69 @@ def test_direct_replace_is_revalidated(): plan = StructuredInterviewPlan(**values()) with pytest.raises(ValueError, match="question_set_digest"): replace(plan, question_set_digest="not-a-digest") + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("interview_plan_reference", "interview_plan:Jane-Doe"), + ("requisition_reference", "requisition:customer-42"), + ("job_profile_reference", "job_profile:RN-ICU"), + ("job_analysis_reference", "job_analysis:salary-120000"), + ("question_set_reference", "question_set:executive-candidates"), + ("question_competency_map_reference", "question_competency_map:race-gender"), + ("rating_anchor_reference", "rating_anchor:top-secret"), + ], +) +def test_scalar_trust_references_reject_value_bearing_non_uuid_suffixes(field, value): + """Reject semantic or value-bearing scalar trust references before serialization.""" + data = values() + data[field] = value + with pytest.raises(ValueError): + StructuredInterviewPlan(**data) + + +def test_collection_trust_references_reject_value_bearing_non_uuid_suffixes(): + """Apply opaque-reference requirements to competency and panel collections.""" + for field, refs in ( + ("competency_references", ("competency:analysis", "competency:Jane-Doe")), + ("panel_actor_references", ("actor:interviewer-a", "actor:seonghobae")), + ): + data = values() + data[field] = refs + with pytest.raises(ValueError): + StructuredInterviewPlan(**data) + + +@pytest.mark.parametrize("reason", ["jane_doe", "salary_120000", "race_gender_review"]) +def test_reason_code_rejects_personal_or_value_bearing_free_form_codes(reason): + """Keep interview-plan reason metadata on a reviewed value-free vocabulary.""" + data = values() + data["reason_code"] = reason + with pytest.raises(ValueError): + StructuredInterviewPlan(**data) + + +def test_repr_redacts_interview_plan_correlations(): + """Prevent routine logging from exposing governance references or evidence digests.""" + plan = StructuredInterviewPlan(**values()) + rendered = repr(plan) + assert rendered == "StructuredInterviewPlan()" + for sensitive in ( + plan.interview_plan_reference, + plan.job_profile_reference, + plan.panel_actor_references[0], + plan.job_analysis_digest, + ): + assert sensitive not in rendered + + +def test_replace_cannot_reintroduce_value_bearing_metadata(): + """Preserve the privacy boundary under dataclass replacement.""" + plan = StructuredInterviewPlan(**values()) + for field, value in ( + ("job_profile_reference", "job_profile:RN-ICU"), + ("reason_code", "salary_120000"), + ): + with pytest.raises(ValueError): + replace(plan, **{field: value}) From 870908dca5e25deeaafa16ca680f68cf9fdda20f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:19:48 -0700 Subject: [PATCH 021/216] fix: close structured interview metadata privacy boundary --- .../src/orgmetra_interview_plan/plan.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index baa912dac..7731f946d 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -15,9 +15,9 @@ _CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") -_REFERENCE_PATTERN = re.compile(r"^[a-z][a-z0-9_]{1,31}:[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$") _PURPOSE_CODE = "structured_interview_plan" _REVIEW_STATE = "requires_human_approval" +_ALLOWED_REASON_CODES = frozenset({"approved_requisition_interview"}) _NEXT_ACTION = ( "Confirm the competencies, predetermined questions, rating anchors, and trained panel " "are job-related and appropriate before activating this structured interview plan." @@ -41,14 +41,17 @@ def _validate_code(value: str, field_name: str) -> None: def _validate_reference(value: str, prefix: str, field_name: str) -> None: - """Require a bounded namespaced opaque reference with the expected prefix.""" - if ( - not isinstance(value, str) - or len(value) > 160 - or not _REFERENCE_PATTERN.fullmatch(value) - or not value.startswith(f"{prefix}:") - ): - raise ValueError(f"{field_name} must be an opaque {prefix}: reference") + """Require the expected namespace plus a canonical non-sentinel UUID suffix.""" + namespace = f"{prefix}:" + if not isinstance(value, str) or len(value) > 160 or not value.startswith(namespace): + raise ValueError(f"{field_name} must be an opaque {prefix}: reference") + suffix = value[len(namespace) :] + try: + parsed = UUID(suffix) + except (ValueError, AttributeError, TypeError) as exc: + raise ValueError(f"{field_name} must be an opaque {prefix}: reference") from exc + if str(parsed) != suffix or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be an opaque {prefix}: reference") def _validate_digest(value: str, field_name: str) -> None: @@ -64,7 +67,7 @@ def _canonical_timestamp(value: datetime) -> str: return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") -@dataclass(frozen=True, slots=True) +@dataclass(frozen=True, slots=True, repr=False) class StructuredInterviewPlan: """Immutable candidate-neutral interview-plan evidence awaiting human approval.""" @@ -128,6 +131,8 @@ def __post_init__(self) -> None: if self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain structured_interview_plan") _validate_code(self.reason_code, "reason_code") + if self.reason_code not in _ALLOWED_REASON_CODES: + raise ValueError("reason_code must use a reviewed non-sensitive interview-plan reason") _canonical_timestamp(self.generated_at) if self.human_confirmation_required is not True: raise ValueError("human confirmation is mandatory for interview-plan approval") @@ -136,6 +141,10 @@ def __post_init__(self) -> None: if self.next_action != _NEXT_ACTION: raise ValueError("next_action must remain the governed interview-plan instruction") + def __repr__(self) -> str: + """Return a fully redacted representation safe for routine logs and assertions.""" + return "StructuredInterviewPlan()" + def canonical_json(self) -> str: """Return deterministic canonical JSON for immutable audit correlation.""" payload = { From d0de9add89fd6a0acd1d002c13a302b3f178d3e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:20:22 -0700 Subject: [PATCH 022/216] test: use opaque UUID interview plan fixtures --- packages/interview-plan/tests/test_plan.py | 63 +++++++++++++--------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/packages/interview-plan/tests/test_plan.py b/packages/interview-plan/tests/test_plan.py index cef5dad39..91bf29516 100644 --- a/packages/interview-plan/tests/test_plan.py +++ b/packages/interview-plan/tests/test_plan.py @@ -11,24 +11,36 @@ DIGEST_B = "b" * 64 DIGEST_C = "c" * 64 DIGEST_D = "d" * 64 +INTERVIEW_PLAN = "interview_plan:11111111-1111-4111-8111-111111111111" +REQUISITION = "requisition:22222222-2222-4222-8222-222222222222" +JOB_PROFILE = "job_profile:33333333-3333-4333-8333-333333333333" +JOB_ANALYSIS = "job_analysis:44444444-4444-4444-8444-444444444444" +QUESTION_SET = "question_set:55555555-5555-4555-8555-555555555555" +QUESTION_MAP = "question_competency_map:66666666-6666-4666-8666-666666666666" +RATING_ANCHOR = "rating_anchor:77777777-7777-4777-8777-777777777777" +COMPETENCY_A = "competency:88888888-8888-4888-8888-888888888888" +COMPETENCY_B = "competency:99999999-9999-4999-8999-999999999999" +COMPETENCY_C = "competency:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +PANEL_A = "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +PANEL_B = "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc" def values(): return dict( tenant_record_id=TENANT, - interview_plan_reference="interview_plan:si-2026-001", - requisition_reference="requisition:req-2026-001", - job_profile_reference="job_profile:job-001", - job_analysis_reference="job_analysis:analysis-001", + interview_plan_reference=INTERVIEW_PLAN, + requisition_reference=REQUISITION, + job_profile_reference=JOB_PROFILE, + job_analysis_reference=JOB_ANALYSIS, job_analysis_digest=DIGEST_A, - question_set_reference="question_set:questions-v1", + question_set_reference=QUESTION_SET, question_set_digest=DIGEST_B, - question_competency_map_reference="question_competency_map:map-v1", + question_competency_map_reference=QUESTION_MAP, question_competency_map_digest=DIGEST_D, - rating_anchor_reference="rating_anchor:anchors-v1", + rating_anchor_reference=RATING_ANCHOR, rating_anchor_digest=DIGEST_C, - competency_references=("competency:analysis", "competency:communication"), - panel_actor_references=("actor:interviewer-a", "actor:interviewer-b"), + competency_references=(COMPETENCY_A, COMPETENCY_B), + panel_actor_references=(PANEL_A, PANEL_B), question_count=4, purpose_code="structured_interview_plan", reason_code="approved_requisition_interview", @@ -42,7 +54,7 @@ def test_builds_candidate_neutral_deterministic_plan(): assert payload["review_state"] == "requires_human_approval" assert payload["human_confirmation_required"] is True assert payload["generated_at"].endswith(".123456Z") - assert payload["question_competency_map_reference"] == "question_competency_map:map-v1" + assert payload["question_competency_map_reference"] == QUESTION_MAP assert "candidate" not in plan.canonical_json() assert plan.sha256_digest() == sha256(plan.canonical_json().encode("utf-8")).hexdigest() assert plan == StructuredInterviewPlan(**values()) @@ -59,6 +71,9 @@ def test_builds_candidate_neutral_deterministic_plan(): ("question_set_reference", "wrong:q-1"), ("question_competency_map_reference", "wrong:map-1"), ("rating_anchor_reference", "wrong:a-1"), + ("interview_plan_reference", "interview_plan:00000000-0000-0000-0000-000000000000"), + ("job_profile_reference", "job_profile:FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"), + ("rating_anchor_reference", 7), ("job_analysis_digest", "A" * 64), ("question_set_digest", "b" * 63), ("question_competency_map_digest", "D" * 64), @@ -80,7 +95,7 @@ def test_rejects_invalid_scalar_contract(field, bad): StructuredInterviewPlan(**data) -@pytest.mark.parametrize("refs", [(), tuple(f"competency:c{i}" for i in range(13)), ["competency:a"]]) +@pytest.mark.parametrize("refs", [(), tuple(f"competency:c{i}" for i in range(13)), [COMPETENCY_A]]) def test_rejects_bad_competency_collection_shape(refs): data = values() data["competency_references"] = refs @@ -89,9 +104,10 @@ def test_rejects_bad_competency_collection_shape(refs): @pytest.mark.parametrize("refs", [ - ("competency:communication", "competency:analysis"), - ("competency:analysis", "competency:analysis"), + (COMPETENCY_B, COMPETENCY_A), + (COMPETENCY_A, COMPETENCY_A), ("wrong:analysis",), + ("competency:Jane-Doe",), ]) def test_rejects_noncanonical_competencies(refs): data = values() @@ -101,12 +117,13 @@ def test_rejects_noncanonical_competencies(refs): @pytest.mark.parametrize("refs", [ - ("actor:only-one",), + (PANEL_A,), tuple(f"actor:p{i}" for i in range(9)), - ["actor:a", "actor:b"], - ("actor:b", "actor:a"), - ("actor:a", "actor:a"), - ("wrong:a", "actor:b"), + [PANEL_A, PANEL_B], + (PANEL_B, PANEL_A), + (PANEL_A, PANEL_A), + ("wrong:a", PANEL_B), + (PANEL_A, "actor:seonghobae"), ]) def test_rejects_bad_panel_contract(refs): data = values() @@ -125,11 +142,7 @@ def test_rejects_bad_question_count(count): def test_question_count_error_describes_only_the_cardinality_constraint(): data = values() - data["competency_references"] = ( - "competency:analysis", - "competency:communication", - "competency:judgment", - ) + data["competency_references"] = (COMPETENCY_A, COMPETENCY_B, COMPETENCY_C) data["question_count"] = 2 with pytest.raises( ValueError, @@ -197,8 +210,8 @@ def test_scalar_trust_references_reject_value_bearing_non_uuid_suffixes(field, v def test_collection_trust_references_reject_value_bearing_non_uuid_suffixes(): """Apply opaque-reference requirements to competency and panel collections.""" for field, refs in ( - ("competency_references", ("competency:analysis", "competency:Jane-Doe")), - ("panel_actor_references", ("actor:interviewer-a", "actor:seonghobae")), + ("competency_references", (COMPETENCY_A, "competency:Jane-Doe")), + ("panel_actor_references", (PANEL_A, "actor:seonghobae")), ): data = values() data[field] = refs From 6eedc7badb8fd34b17ec91cb29f52aee4cccb72e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:20:42 -0700 Subject: [PATCH 023/216] docs: harden structured interview privacy guidance --- packages/interview-plan/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 80070f971..1349222fb 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -2,10 +2,14 @@ `orgmetra-interview-plan` creates candidate-neutral evidence for approving a structured interview **before** it is used with applicants. -The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, an exact question-to-competency mapping artifact, rating anchors, job-related competency references, and a bounded interviewer panel. The question set and mapping each carry their own immutable SHA-256 evidence digest, so a count of questions cannot be mistaken for proof that every governed competency is actually assessed. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, and provider data out of the packet. +The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, an exact question-to-competency mapping artifact, rating anchors, job-related competency references, and a bounded interviewer panel. The question set and mapping each carry their own immutable SHA-256 evidence digest, so a count of questions cannot be mistaken for proof that every governed competency is actually assessed. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, provider data, and free-form personal/value-bearing reason text out of the packet. -The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. +Every trust-bearing reference uses its expected namespace plus a canonical non-sentinel UUID suffix. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. -For consistency and immutable audit correlation, all governance references are bounded opaque namespaced identifiers, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. +The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. + +`repr(plan)` is fully redacted as `StructuredInterviewPlan()`, so routine logs and assertion failures do not expose governance correlations or evidence digests. Canonical JSON remains the explicit evidence serialization boundary. + +For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. From 5fa63bb34b4560ed0828204e9f26d3fead78abf9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:21:07 -0700 Subject: [PATCH 024/216] docs: record structured interview privacy boundary --- ...0014-governed-structured-interview-plan.md | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index da4324a78..d33bb0168 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -7,22 +7,24 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed requisition review, selection evidence, and accountable human employment decisions. A buyer still needs a defensible boundary between an approved opening and the interview that will be used as a selection procedure. -A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity and assessment values are unnecessary at this pre-use boundary and would increase privacy risk. +A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. ## Decision Add a transport-neutral `StructuredInterviewPlan` value object that binds: -- canonical tenant identity and one opaque interview-plan reference; -- one requisition and authoritative Job reference; -- exact job-analysis reference plus SHA-256 digest; -- exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; -- a sorted, unique set of job-related competency references; -- a sorted, unique interviewer panel of 2–8 accountable actor references; +- canonical tenant identity and one UUID-backed opaque interview-plan reference; +- UUID-backed requisition and authoritative Job references; +- UUID-backed exact job-analysis reference plus SHA-256 digest; +- UUID-backed exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; +- a sorted, unique set of UUID-backed job-related competency references; +- a sorted, unique interviewer panel of 2–8 UUID-backed accountable actor references; - a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; -- fixed purpose `structured_interview_plan`, bounded reason metadata, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. +- fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. -The packet is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Direct construction and builder construction share the same fail-closed validation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. +All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUID suffix; names, labels, compensation/protected-attribute values, or other semantic suffixes fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. + +The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. Opaque references remain sensitive correlation metadata rather than anonymous data. ## Consequences @@ -30,6 +32,8 @@ The packet is candidate-neutral. It contains no candidate identity, response, sc - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, and interview panel were approved before candidate use. - Candidate PII and assessment values remain outside the planning artifact. +- Value-bearing trust-reference suffixes and free-form reason metadata cannot enter portable evidence. +- Routine representation/logging does not expose references or evidence digests. - Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest rather than copying question content. - The contract supports standalone use and later MSA extraction without cross-service application-table SQL. @@ -37,6 +41,7 @@ The packet is candidate-neutral. It contains no candidate identity, response, sc - The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, or scores. - Human approval remains mandatory; model output cannot activate or approve the plan. +- UUID-backed opacity reduces accidental value leakage but does not remove authorization, retention, export-control, or audit obligations for correlation metadata. - The mapping digest proves identity/integrity of the approved mapping artifact, not that its content is scientifically adequate; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. From 62baef9eda8fc482cfe49e804cb4cda0c53bdc51 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:21:24 -0700 Subject: [PATCH 025/216] docs: trace structured interview privacy regressions --- docs/traceability/structured-interview-plan.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 474f9e48f..3a94f6a53 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -8,18 +8,20 @@ | Requirement | Contract | Evidence | |---|---|---| -| Interview content is tied to job analysis | exact `job_analysis_reference` + lowercase SHA-256 digest | `test_builds_candidate_neutral_deterministic_plan`; invalid-reference/digest regressions | -| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | exact question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid-reference/digest regressions; deterministic SHA-256 test | -| Every governed competency has auditable coverage evidence | sorted unique 1–12 competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection-shape/order/duplicate/prefix and question-count regressions plus required mapping-reference/digest regressions | -| Interview panel is accountable and bounded | sorted unique 2–8 `actor:` references | panel size/type/order/duplicate/prefix regressions | +| Interview content is tied to job analysis | UUID-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical reference and digest regressions | +| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUID-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid/value-bearing-reference and digest regressions; deterministic SHA-256 test | +| Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUID-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity and question-count regressions plus mapping-reference/digest regressions | +| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUID-backed `actor:` references | panel size/type/order/duplicate/namespace/value-bearing-reference regressions | +| Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUID suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions and `dataclasses.replace(...)` bypass regression | +| Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | | High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and next action | scalar fail-closed regressions | | Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 | naive/unknown-offset/offset/fractional-time regressions and independent digest assertion | -| Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace` regressions | +| Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace(...)` regressions | ## Evidence boundary -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. It does not by itself prove that the mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. It does not by itself prove that the mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. UUID-backed opacity prevents semantic values from being embedded in portable reference strings but does not make correlation metadata anonymous; purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope From fb42902403fb5e128967959a835f9c59ad960e12 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 01:21:35 -0700 Subject: [PATCH 026/216] docs: record structured interview privacy hardening --- packages/interview-plan/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index e5f46bb30..a5a62cbbd 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -10,3 +10,9 @@ ### Changed - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. + +### Security and privacy + +- Require canonical non-sentinel UUID suffixes for every trust-bearing scalar and collection reference, rejecting human-readable/value-bearing metadata before serialization. +- Close `reason_code` to the reviewed non-sensitive `approved_requisition_interview` value. +- Redact `StructuredInterviewPlan` representation so routine logs and assertion failures do not expose sensitive correlations or evidence digests. From 8093e76127f2ae69d650294309c125c9745d689e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 02:13:59 -0700 Subject: [PATCH 027/216] test: require structured interview docstrings --- .../interview-plan/tests/test_docstrings.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/interview-plan/tests/test_docstrings.py diff --git a/packages/interview-plan/tests/test_docstrings.py b/packages/interview-plan/tests/test_docstrings.py new file mode 100644 index 000000000..a4a38c6e5 --- /dev/null +++ b/packages/interview-plan/tests/test_docstrings.py @@ -0,0 +1,32 @@ +"""Executable documentation-quality contract for the structured-interview package.""" + +from __future__ import annotations + +import ast +from pathlib import Path + + +PACKAGE_ROOT = Path(__file__).resolve().parents[1] +PYTHON_ROOTS = (PACKAGE_ROOT / "src", PACKAGE_ROOT / "tests") + + +def _undocumented_definitions(path: Path) -> list[str]: + """Return module/class/callable names that lack a beginner-readable docstring.""" + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + missing: list[str] = [] + if ast.get_docstring(tree) is None: + missing.append(f"{path}:") + for node in ast.walk(tree): + if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if ast.get_docstring(node) is None: + missing.append(f"{path}:{node.name}") + return missing + + +def test_owned_python_definitions_have_docstrings() -> None: + """Require readable docstrings across every owned production and regression definition.""" + missing: list[str] = [] + for root in PYTHON_ROOTS: + for path in sorted(root.rglob("*.py")): + missing.extend(_undocumented_definitions(path)) + assert not missing, "Missing docstrings:\n" + "\n".join(missing) From c60f900b11e92b50e1a90d08574b788de1b4c15e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 02:14:43 -0700 Subject: [PATCH 028/216] docs: make structured interview regressions readable --- packages/interview-plan/tests/test_plan.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/interview-plan/tests/test_plan.py b/packages/interview-plan/tests/test_plan.py index 91bf29516..c12b9d67d 100644 --- a/packages/interview-plan/tests/test_plan.py +++ b/packages/interview-plan/tests/test_plan.py @@ -1,3 +1,5 @@ +"""Regression tests for governed, candidate-neutral structured-interview plans.""" + from dataclasses import replace from datetime import datetime, timedelta, timezone, tzinfo from hashlib import sha256 @@ -26,6 +28,7 @@ def values(): + """Return one valid plan input mapping for focused mutation-based regressions.""" return dict( tenant_record_id=TENANT, interview_plan_reference=INTERVIEW_PLAN, @@ -49,6 +52,7 @@ def values(): def test_builds_candidate_neutral_deterministic_plan(): + """Build deterministic evidence without candidate values or autonomous authority.""" plan = build_structured_interview_plan(**values()) payload = json.loads(plan.canonical_json()) assert payload["review_state"] == "requires_human_approval" @@ -89,6 +93,7 @@ def test_builds_candidate_neutral_deterministic_plan(): ("next_action", "Skip human review"), ]) def test_rejects_invalid_scalar_contract(field, bad): + """Reject malformed scalar identity, digest, governance, time, and state inputs.""" data = values() data[field] = bad with pytest.raises((ValueError, TypeError)): @@ -97,6 +102,7 @@ def test_rejects_invalid_scalar_contract(field, bad): @pytest.mark.parametrize("refs", [(), tuple(f"competency:c{i}" for i in range(13)), [COMPETENCY_A]]) def test_rejects_bad_competency_collection_shape(refs): + """Require competencies to use the governed bounded tuple collection shape.""" data = values() data["competency_references"] = refs with pytest.raises(ValueError, match="competency_references"): @@ -110,6 +116,7 @@ def test_rejects_bad_competency_collection_shape(refs): ("competency:Jane-Doe",), ]) def test_rejects_noncanonical_competencies(refs): + """Reject unsorted, duplicate, wrong-namespace, or value-bearing competencies.""" data = values() data["competency_references"] = refs with pytest.raises(ValueError): @@ -126,6 +133,7 @@ def test_rejects_noncanonical_competencies(refs): (PANEL_A, "actor:seonghobae"), ]) def test_rejects_bad_panel_contract(refs): + """Require a sorted unique bounded panel of opaque accountable actor references.""" data = values() data["panel_actor_references"] = refs with pytest.raises(ValueError, match="panel_actor_references|actor"): @@ -134,6 +142,7 @@ def test_rejects_bad_panel_contract(refs): @pytest.mark.parametrize("count", [True, 0, 21, 1]) def test_rejects_bad_question_count(count): + """Reject boolean, out-of-range, or competency-underflow question counts.""" data = values() data["question_count"] = count with pytest.raises(ValueError, match="question_count"): @@ -141,6 +150,7 @@ def test_rejects_bad_question_count(count): def test_question_count_error_describes_only_the_cardinality_constraint(): + """Keep the count failure message limited to cardinality rather than coverage claims.""" data = values() data["competency_references"] = (COMPETENCY_A, COMPETENCY_B, COMPETENCY_C) data["question_count"] = 2 @@ -152,20 +162,26 @@ def test_question_count_error_describes_only_the_cardinality_constraint(): def test_accepts_question_count_equal_to_competency_count(): + """Accept the smallest count consistent with the governed competency cardinality.""" data = values() data["question_count"] = 2 assert StructuredInterviewPlan(**data).question_count == 2 class UnknownOffset(tzinfo): + """Timezone fixture whose UTC offset is intentionally unknowable.""" + def utcoffset(self, dt): + """Return no UTC offset so timestamp validation must fail closed.""" return None def dst(self, dt): + """Return no daylight-saving offset for this deliberately invalid fixture.""" return None def test_rejects_timezone_with_unknown_offset(): + """Reject tzinfo objects that cannot resolve an actual UTC offset.""" data = values() data["generated_at"] = datetime(2026, 8, 18, tzinfo=UnknownOffset()) with pytest.raises(ValueError, match="timezone-aware"): @@ -173,6 +189,7 @@ def test_rejects_timezone_with_unknown_offset(): def test_canonicalizes_non_utc_offset_and_preserves_fractional_precision(): + """Normalize valid offsets to UTC without collapsing fractional-second evidence.""" data = values() data["generated_at"] = datetime( 2026, 8, 18, 21, 34, 56, 123456, tzinfo=timezone(timedelta(hours=9)) @@ -182,6 +199,7 @@ def test_canonicalizes_non_utc_offset_and_preserves_fractional_precision(): def test_direct_replace_is_revalidated(): + """Re-run all fail-closed invariants when immutable plans are copied with changes.""" plan = StructuredInterviewPlan(**values()) with pytest.raises(ValueError, match="question_set_digest"): replace(plan, question_set_digest="not-a-digest") From 7d67ed65f2e8b0a4bfcc58270b4a1e55a366105a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 05:46:42 -0700 Subject: [PATCH 029/216] test: require structured interview evidence version --- .../tests/test_evidence_version.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/interview-plan/tests/test_evidence_version.py diff --git a/packages/interview-plan/tests/test_evidence_version.py b/packages/interview-plan/tests/test_evidence_version.py new file mode 100644 index 000000000..7cc351574 --- /dev/null +++ b/packages/interview-plan/tests/test_evidence_version.py @@ -0,0 +1,49 @@ +"""Regression coverage for immutable structured-interview evidence versions.""" + +from dataclasses import replace +from datetime import datetime, timezone +import json + +import pytest + +from orgmetra_interview_plan import build_structured_interview_plan + + +def _plan_kwargs() -> dict[str, object]: + """Return one valid structured-interview plan input mapping.""" + return { + "tenant_record_id": "12345678-1234-4234-8234-123456789abc", + "interview_plan_reference": "interview_plan:11111111-1111-4111-8111-111111111111", + "requisition_reference": "requisition:22222222-2222-4222-8222-222222222222", + "job_profile_reference": "job_profile:33333333-3333-4333-8333-333333333333", + "job_analysis_reference": "job_analysis:44444444-4444-4444-8444-444444444444", + "job_analysis_digest": "a" * 64, + "question_set_reference": "question_set:55555555-5555-4555-8555-555555555555", + "question_set_digest": "b" * 64, + "question_competency_map_reference": "question_competency_map:66666666-6666-4666-8666-666666666666", + "question_competency_map_digest": "c" * 64, + "rating_anchor_reference": "rating_anchor:77777777-7777-4777-8777-777777777777", + "rating_anchor_digest": "d" * 64, + "competency_references": ("competency:88888888-8888-4888-8888-888888888888",), + "panel_actor_references": ( + "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc", + ), + "question_count": 2, + "purpose_code": "structured_interview_plan", + "reason_code": "approved_requisition_interview", + "generated_at": datetime(2026, 8, 19, 12, 0, tzinfo=timezone.utc), + } + + +def test_evidence_version_is_canonical_bounded_and_revalidated() -> None: + """Bind evidence version identity and reject replacement-path drift.""" + plan = build_structured_interview_plan(**_plan_kwargs(), evidence_version=1) + assert json.loads(plan.canonical_json())["evidence_version"] == 1 + + revised = replace(plan, evidence_version=2) + assert revised.sha256_digest() != plan.sha256_digest() + + for invalid in (True, 0, -1, "1", 2_147_483_648): + with pytest.raises(ValueError, match="evidence_version"): + replace(plan, evidence_version=invalid) From f139416c952cc765330718c52c3dd1d91f27b671 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 05:47:17 -0700 Subject: [PATCH 030/216] fix: bind structured interview evidence version --- .../interview-plan/src/orgmetra_interview_plan/plan.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 7731f946d..2c34e5b14 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -22,6 +22,7 @@ "Confirm the competencies, predetermined questions, rating anchors, and trained panel " "are job-related and appropriate before activating this structured interview plan." ) +_MAX_EVIDENCE_VERSION = 2_147_483_647 def _validate_operational_uuid(value: str, field_name: str) -> None: @@ -89,6 +90,7 @@ class StructuredInterviewPlan: purpose_code: str reason_code: str generated_at: datetime + evidence_version: int = 1 human_confirmation_required: bool = True review_state: str = _REVIEW_STATE next_action: str = _NEXT_ACTION @@ -134,6 +136,8 @@ def __post_init__(self) -> None: if self.reason_code not in _ALLOWED_REASON_CODES: raise ValueError("reason_code must use a reviewed non-sensitive interview-plan reason") _canonical_timestamp(self.generated_at) + if type(self.evidence_version) is not int or not 1 <= self.evidence_version <= _MAX_EVIDENCE_VERSION: + raise ValueError("evidence_version must be an integer from 1 through 2147483647") if self.human_confirmation_required is not True: raise ValueError("human confirmation is mandatory for interview-plan approval") if self.review_state != _REVIEW_STATE: @@ -149,6 +153,7 @@ def canonical_json(self) -> str: """Return deterministic canonical JSON for immutable audit correlation.""" payload = { "competency_references": list(self.competency_references), + "evidence_version": self.evidence_version, "generated_at": _canonical_timestamp(self.generated_at), "human_confirmation_required": self.human_confirmation_required, "interview_plan_reference": self.interview_plan_reference, @@ -197,6 +202,7 @@ def build_structured_interview_plan( purpose_code: str, reason_code: str, generated_at: datetime, + evidence_version: int = 1, ) -> StructuredInterviewPlan: """Build a governed structured-interview plan that remains pending human approval.""" return StructuredInterviewPlan( @@ -218,4 +224,5 @@ def build_structured_interview_plan( purpose_code=purpose_code, reason_code=reason_code, generated_at=generated_at, + evidence_version=evidence_version, ) From 9d76aa6a13cc431d7f96a3fc7e9aa9dc539bfbf3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 05:48:40 -0700 Subject: [PATCH 031/216] docs: trace structured interview evidence versions --- docs/adr/0014-governed-structured-interview-plan.md | 10 +++++----- docs/traceability/structured-interview-plan.md | 3 ++- packages/interview-plan/CHANGELOG.md | 2 ++ packages/interview-plan/README.md | 2 ++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index d33bb0168..4bf441c1f 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -20,9 +20,9 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: - a sorted, unique set of UUID-backed job-related competency references; - a sorted, unique interviewer panel of 2–8 UUID-backed accountable actor references; - a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; -- fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. +- fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, a bounded positive `evidence_version`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. -All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUID suffix; names, labels, compensation/protected-attribute values, or other semantic suffixes fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. +All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUID suffix; names, labels, compensation/protected-attribute values, or other semantic suffixes fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. Opaque references remain sensitive correlation metadata rather than anonymous data. @@ -30,11 +30,11 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor ### Positive -- Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, and interview panel were approved before candidate use. +- Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were approved before candidate use. - Candidate PII and assessment values remain outside the planning artifact. - Value-bearing trust-reference suffixes and free-form reason metadata cannot enter portable evidence. - Routine representation/logging does not expose references or evidence digests. -- Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest rather than copying question content. +- Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest/version rather than copying question content. - The contract supports standalone use and later MSA extraction without cross-service application-table SQL. ### Costs and constraints @@ -42,7 +42,7 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor - The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, or scores. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUID-backed opacity reduces accidental value leakage but does not remove authorization, retention, export-control, or audit obligations for correlation metadata. -- The mapping digest proves identity/integrity of the approved mapping artifact, not that its content is scientifically adequate; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. +- Evidence version and digests identify the reviewed revision but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. ## References diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 3a94f6a53..d7300638c 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -10,6 +10,7 @@ |---|---|---| | Interview content is tied to job analysis | UUID-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical reference and digest regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUID-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid/value-bearing-reference and digest regressions; deterministic SHA-256 test | +| Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUID-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity and question-count regressions plus mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 canonical UUID-backed `actor:` references | panel size/type/order/duplicate/namespace/value-bearing-reference regressions | | Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUID suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions and `dataclasses.replace(...)` bypass regression | @@ -21,7 +22,7 @@ ## Evidence boundary -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. It does not by itself prove that the mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. UUID-backed opacity prevents semantic values from being embedded in portable reference strings but does not make correlation metadata anonymous; purpose-bound authorization, least privilege, retention/export controls, and audit remain required. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. Neither proves that mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. UUID-backed opacity prevents semantic values from being embedded in portable reference strings but does not make correlation metadata anonymous; purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index a5a62cbbd..8cda2b41f 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -6,10 +6,12 @@ - Candidate-neutral `StructuredInterviewPlan` binding an approved requisition and Job to exact job-analysis, question-set, question-to-competency mapping, rating-anchor, competency, and interviewer-panel evidence. - Fail-closed direct-construction validation, deterministic canonical JSON/SHA-256 audit correlation, explicit human approval state, and 100% owned statement/branch regression coverage. +- Bounded positive `evidence_version` in canonical evidence so materially revised plans have explicit immutable revision identity. ### Changed - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. +- Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. ### Security and privacy diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 1349222fb..a36139bfe 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -4,6 +4,8 @@ The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, an exact question-to-competency mapping artifact, rating anchors, job-related competency references, and a bounded interviewer panel. The question set and mapping each carry their own immutable SHA-256 evidence digest, so a count of questions cannot be mistaken for proof that every governed competency is actually assessed. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, provider data, and free-form personal/value-bearing reason text out of the packet. +Every plan also carries a bounded positive `evidence_version` (1 through 2147483647) in canonical evidence. Version changes therefore change the SHA-256 audit correlation, and direct construction plus `dataclasses.replace(...)` revalidate the version fail closed. Version 1 is the default for the initial evidence schema; callers must increment it when the governed plan evidence is materially revised rather than treating a digest alone as semantic version identity. + Every trust-bearing reference uses its expected namespace plus a canonical non-sentinel UUID suffix. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. From 2fbb6bd8fd86e81a368bc4a4e78cdfb7cf314da9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 05:50:10 -0700 Subject: [PATCH 032/216] test: reject UUIDv1 interview trust references --- .../interview-plan/tests/test_uuid_version.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/interview-plan/tests/test_uuid_version.py diff --git a/packages/interview-plan/tests/test_uuid_version.py b/packages/interview-plan/tests/test_uuid_version.py new file mode 100644 index 000000000..851cdeada --- /dev/null +++ b/packages/interview-plan/tests/test_uuid_version.py @@ -0,0 +1,56 @@ +"""Regression coverage for UUIDv4-only structured-interview trust references.""" + +from datetime import datetime, timezone + +import pytest + +from orgmetra_interview_plan import build_structured_interview_plan + +_UUID1 = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + + +def _plan_kwargs() -> dict[str, object]: + """Return one valid structured-interview plan input mapping.""" + return { + "tenant_record_id": "12345678-1234-4234-8234-123456789abc", + "interview_plan_reference": "interview_plan:11111111-1111-4111-8111-111111111111", + "requisition_reference": "requisition:22222222-2222-4222-8222-222222222222", + "job_profile_reference": "job_profile:33333333-3333-4333-8333-333333333333", + "job_analysis_reference": "job_analysis:44444444-4444-4444-8444-444444444444", + "job_analysis_digest": "a" * 64, + "question_set_reference": "question_set:55555555-5555-4555-8555-555555555555", + "question_set_digest": "b" * 64, + "question_competency_map_reference": "question_competency_map:66666666-6666-4666-8666-666666666666", + "question_competency_map_digest": "c" * 64, + "rating_anchor_reference": "rating_anchor:77777777-7777-4777-8777-777777777777", + "rating_anchor_digest": "d" * 64, + "competency_references": ("competency:88888888-8888-4888-8888-888888888888",), + "panel_actor_references": ( + "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc", + ), + "question_count": 2, + "purpose_code": "structured_interview_plan", + "reason_code": "approved_requisition_interview", + "generated_at": datetime(2026, 8, 19, 12, 0, tzinfo=timezone.utc), + "evidence_version": 1, + } + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("job_profile_reference", f"job_profile:{_UUID1}"), + ("competency_references", (f"competency:{_UUID1}",)), + ( + "panel_actor_references", + (f"actor:{_UUID1}", "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc"), + ), + ], +) +def test_uuid1_trust_references_fail_closed(field: str, value: object) -> None: + """Reject time/node-bearing UUIDv1 suffixes across scalar and collection references.""" + data = _plan_kwargs() + data[field] = value + with pytest.raises(ValueError, match="canonical-uuid"): + build_structured_interview_plan(**data) From cd58ec6058c1f758f4ed6141733bae450793fc62 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 05:50:49 -0700 Subject: [PATCH 033/216] fix: require UUIDv4 interview trust references --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 2c34e5b14..e5fc4d886 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -42,7 +42,7 @@ def _validate_code(value: str, field_name: str) -> None: def _validate_reference(value: str, prefix: str, field_name: str) -> None: - """Require the expected namespace plus a canonical non-sentinel UUID suffix.""" + """Require the expected namespace plus a canonical non-sentinel UUIDv4 suffix.""" namespace = f"{prefix}:" if not isinstance(value, str) or len(value) > 160 or not value.startswith(namespace): raise ValueError(f"{field_name} must be an opaque {prefix}: reference") @@ -51,7 +51,7 @@ def _validate_reference(value: str, prefix: str, field_name: str) -> None: parsed = UUID(suffix) except (ValueError, AttributeError, TypeError) as exc: raise ValueError(f"{field_name} must be an opaque {prefix}: reference") from exc - if str(parsed) != suffix or parsed.int in (0, (1 << 128) - 1): + if str(parsed) != suffix or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): raise ValueError(f"{field_name} must be an opaque {prefix}: reference") From ac8a37c15a4580d9efd6936a66f3848f3e89ffdf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 05:52:18 -0700 Subject: [PATCH 034/216] docs: record UUIDv4 trust-reference boundary --- .../0014-governed-structured-interview-plan.md | 18 +++++++++--------- docs/traceability/structured-interview-plan.md | 12 ++++++------ packages/interview-plan/CHANGELOG.md | 3 ++- packages/interview-plan/README.md | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index 4bf441c1f..211de6b39 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -13,16 +13,16 @@ A structured interview is stronger when the assessed competencies come from curr Add a transport-neutral `StructuredInterviewPlan` value object that binds: -- canonical tenant identity and one UUID-backed opaque interview-plan reference; -- UUID-backed requisition and authoritative Job references; -- UUID-backed exact job-analysis reference plus SHA-256 digest; -- UUID-backed exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; -- a sorted, unique set of UUID-backed job-related competency references; -- a sorted, unique interviewer panel of 2–8 UUID-backed accountable actor references; +- canonical tenant identity and one UUIDv4-backed opaque interview-plan reference; +- UUIDv4-backed requisition and authoritative Job references; +- UUIDv4-backed exact job-analysis reference plus SHA-256 digest; +- UUIDv4-backed exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; +- a sorted, unique set of UUIDv4-backed job-related competency references; +- a sorted, unique interviewer panel of 2–8 UUIDv4-backed accountable actor references; - a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; - fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, a bounded positive `evidence_version`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. -All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUID suffix; names, labels, compensation/protected-attribute values, or other semantic suffixes fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. +All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUIDv4 suffix. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque-reference boundary; names, labels, compensation/protected-attribute values, or other semantic suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. Opaque references remain sensitive correlation metadata rather than anonymous data. @@ -32,7 +32,7 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were approved before candidate use. - Candidate PII and assessment values remain outside the planning artifact. -- Value-bearing trust-reference suffixes and free-form reason metadata cannot enter portable evidence. +- UUIDv1/time-node-bearing and value-bearing trust-reference suffixes cannot enter portable evidence. - Routine representation/logging does not expose references or evidence digests. - Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest/version rather than copying question content. - The contract supports standalone use and later MSA extraction without cross-service application-table SQL. @@ -41,7 +41,7 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor - The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, or scores. - Human approval remains mandatory; model output cannot activate or approve the plan. -- UUID-backed opacity reduces accidental value leakage but does not remove authorization, retention, export-control, or audit obligations for correlation metadata. +- UUIDv4-backed opacity reduces accidental value leakage but does not remove authorization, retention, export-control, or audit obligations for correlation metadata. - Evidence version and digests identify the reviewed revision but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index d7300638c..aacdcd1b6 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -8,12 +8,12 @@ | Requirement | Contract | Evidence | |---|---|---| -| Interview content is tied to job analysis | UUID-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical reference and digest regressions | -| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUID-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid/value-bearing-reference and digest regressions; deterministic SHA-256 test | +| Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | +| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test | | Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | -| Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUID-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity and question-count regressions plus mapping-reference/digest regressions | -| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUID-backed `actor:` references | panel size/type/order/duplicate/namespace/value-bearing-reference regressions | -| Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUID suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions and `dataclasses.replace(...)` bypass regression | +| Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | +| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions | +| Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUIDv4 suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions, UUIDv1 regression, and `dataclasses.replace(...)` bypass regression | | Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | | High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and next action | scalar fail-closed regressions | @@ -22,7 +22,7 @@ ## Evidence boundary -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. Neither proves that mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. UUID-backed opacity prevents semantic values from being embedded in portable reference strings but does not make correlation metadata anonymous; purpose-bound authorization, least privilege, retention/export controls, and audit remain required. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only trust references keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. Neither version/digest metadata nor UUID form proves mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 8cda2b41f..3695f527b 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -12,9 +12,10 @@ - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. +- Require canonical non-sentinel UUIDv4 suffixes for every trust-bearing reference; UUIDv1 and other UUID versions now fail closed. ### Security and privacy -- Require canonical non-sentinel UUID suffixes for every trust-bearing scalar and collection reference, rejecting human-readable/value-bearing metadata before serialization. +- Reject timestamp/node-bearing UUIDv1 reference suffixes as well as human-readable/value-bearing metadata before serialization. - Close `reason_code` to the reviewed non-sensitive `approved_requisition_interview` value. - Redact `StructuredInterviewPlan` representation so routine logs and assertion failures do not expose sensitive correlations or evidence digests. diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index a36139bfe..20f4f61ae 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -6,7 +6,7 @@ The plan binds one requisition and authoritative Job to versioned job-analysis e Every plan also carries a bounded positive `evidence_version` (1 through 2147483647) in canonical evidence. Version changes therefore change the SHA-256 audit correlation, and direct construction plus `dataclasses.replace(...)` revalidate the version fail closed. Version 1 is the default for the initial evidence schema; callers must increment it when the governed plan evidence is materially revised rather than treating a digest alone as semantic version identity. -Every trust-bearing reference uses its expected namespace plus a canonical non-sentinel UUID suffix. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. +Every trust-bearing reference uses its expected namespace plus a canonical, non-sentinel UUIDv4 suffix. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque-reference boundary. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. From 5cb75af057c6340b6b6b460eef4bcd84642ca286 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 07:54:27 -0700 Subject: [PATCH 035/216] test: require tenant-scoped interview resolution --- .../interview-plan/tests/test_tenant_scope.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/interview-plan/tests/test_tenant_scope.py diff --git a/packages/interview-plan/tests/test_tenant_scope.py b/packages/interview-plan/tests/test_tenant_scope.py new file mode 100644 index 000000000..faf457793 --- /dev/null +++ b/packages/interview-plan/tests/test_tenant_scope.py @@ -0,0 +1,22 @@ +"""Tenant-scope regressions for governed structured-interview activation.""" + +from orgmetra_interview_plan import StructuredInterviewPlan +from test_plan import values + + +def test_activation_requires_authoritative_tenant_and_job_scope_resolution() -> None: + """Do not infer tenant ownership or Job linkage from opaque references and digests.""" + action = StructuredInterviewPlan(**values()).next_action + + assert "Within tenant_record_id, re-resolve every plan reference" in action + assert "verify the requisition-to-Job-to-job-analysis binding" in action + assert "verify question-set, question-to-competency mapping, and rating-anchor provenance" in action + + +def test_activation_requires_authoritative_panel_actor_separation() -> None: + """Do not treat distinct actor-reference strings as distinct authoritative people.""" + action = StructuredInterviewPlan(**values()).next_action + + assert "re-resolve every panel_actor_reference" in action + assert "prove the resolved panel actor identities are distinct" in action + assert "verify panel eligibility and training" in action From 39a57440a1d0684fe306392990dc32e56d5bf62d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 08:00:36 -0700 Subject: [PATCH 036/216] fix: require authoritative interview plan resolution --- .../interview-plan/src/orgmetra_interview_plan/plan.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index e5fc4d886..02c4194ea 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -19,8 +19,12 @@ _REVIEW_STATE = "requires_human_approval" _ALLOWED_REASON_CODES = frozenset({"approved_requisition_interview"}) _NEXT_ACTION = ( - "Confirm the competencies, predetermined questions, rating anchors, and trained panel " - "are job-related and appropriate before activating this structured interview plan." + "Within tenant_record_id, re-resolve every plan reference and verify the " + "requisition-to-Job-to-job-analysis binding; verify question-set, " + "question-to-competency mapping, and rating-anchor provenance; re-resolve every " + "panel_actor_reference, prove the resolved panel actor identities are distinct, " + "and verify panel eligibility and training before an accountable human activates " + "this structured interview plan." ) _MAX_EVIDENCE_VERSION = 2_147_483_647 @@ -225,4 +229,4 @@ def build_structured_interview_plan( reason_code=reason_code, generated_at=generated_at, evidence_version=evidence_version, - ) + ) \ No newline at end of file From 1dc200b2aec683d338ce1b53fe7b360406f6a25a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 08:03:08 -0700 Subject: [PATCH 037/216] docs: explain authoritative interview activation checks --- packages/interview-plan/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 20f4f61ae..2119ebc0d 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -8,7 +8,9 @@ Every plan also carries a bounded positive `evidence_version` (1 through 2147483 Every trust-bearing reference uses its expected namespace plus a canonical, non-sentinel UUIDv4 suffix. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque-reference boundary. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. -The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action tells an accountable reviewer to confirm job relatedness and the approved interview structure before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. +Opaque references and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. The packet performs none of those authoritative resolutions itself. + +The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action requires those authoritative resolution checks before an accountable human activates the plan. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. `repr(plan)` is fully redacted as `StructuredInterviewPlan()`, so routine logs and assertion failures do not expose governance correlations or evidence digests. Canonical JSON remains the explicit evidence serialization boundary. From cfbf1d6d38303dacf81fb7a5d37876ac66e0c112 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 08:03:50 -0700 Subject: [PATCH 038/216] docs: record authoritative interview resolution boundary --- docs/adr/0014-governed-structured-interview-plan.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index 211de6b39..26004bcc8 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -9,6 +9,8 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. +Opaque references and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. + ## Decision Add a transport-neutral `StructuredInterviewPlan` value object that binds: @@ -24,13 +26,16 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUIDv4 suffix. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque-reference boundary; names, labels, compensation/protected-attribute values, or other semantic suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. -The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, or approved. Opaque references remain sensitive correlation metadata rather than anonymous data. +The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. The packet does not perform or claim those authoritative resolutions. Only after they succeed may an accountable human activate the plan. + +The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or approved. Opaque references remain sensitive correlation metadata rather than anonymous data. ## Consequences ### Positive -- Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were approved before candidate use. +- Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. +- Activation fails closed unless authoritative tenant, Job, evidence-provenance, and panel-identity relationships are re-resolved. - Candidate PII and assessment values remain outside the planning artifact. - UUIDv1/time-node-bearing and value-bearing trust-reference suffixes cannot enter portable evidence. - Routine representation/logging does not expose references or evidence digests. @@ -39,9 +44,10 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor ### Costs and constraints -- The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, or scores. +- The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUIDv4-backed opacity reduces accidental value leakage but does not remove authorization, retention, export-control, or audit obligations for correlation metadata. +- Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. - Evidence version and digests identify the reviewed revision but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. From c1320d504d1d7a01e504dbed42ae0cb4ab8e87c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 08:04:22 -0700 Subject: [PATCH 039/216] docs: trace authoritative interview activation evidence --- docs/traceability/structured-interview-plan.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index aacdcd1b6..cb9e200b8 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -9,21 +9,24 @@ | Requirement | Contract | Evidence | |---|---|---| | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | -| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test | +| Authoritative tenant and Job scope is not inferred from identifiers | immutable next action requires every plan reference to be re-resolved within `tenant_record_id` and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_activation_requires_authoritative_tenant_and_job_scope_resolution` | +| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; next action requires authoritative provenance verification | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test; tenant-scope activation regression | | Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | -| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions | +| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation requires every panel actor to be re-resolved, resolved identities to be distinct, and eligibility/training to be verified | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions; `test_activation_requires_authoritative_panel_actor_separation` | | Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUIDv4 suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions, UUIDv1 regression, and `dataclasses.replace(...)` bypass regression | | Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | -| High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and next action | scalar fail-closed regressions | +| High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and immutable authoritative-resolution next action | scalar fail-closed regressions and tenant/panel activation regressions | | Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 | naive/unknown-offset/offset/fractional-time regressions and independent digest assertion | | Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace(...)` regressions | ## Evidence boundary -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only trust references keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. Neither version/digest metadata nor UUID form proves mapping content is substantively correct or valid; accountable human review of job relatedness remains mandatory. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only trust references keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. + +Neither UUID form, reference inequality, nor digest metadata proves tenant ownership, requisition-to-Job-to-job-analysis relationships, mapping provenance, panel identity separation, panel eligibility, training, substantive correctness, or validity. The host must re-resolve those relationships within the exact tenant immediately before accountable human activation. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope -This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. +This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. From 2955b572e0d28b0ce4fd4ba41f6eb46d5873ed8b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 08:04:44 -0700 Subject: [PATCH 040/216] docs: record authoritative interview activation hardening --- packages/interview-plan/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 3695f527b..c95a77f7a 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -7,15 +7,18 @@ - Candidate-neutral `StructuredInterviewPlan` binding an approved requisition and Job to exact job-analysis, question-set, question-to-competency mapping, rating-anchor, competency, and interviewer-panel evidence. - Fail-closed direct-construction validation, deterministic canonical JSON/SHA-256 audit correlation, explicit human approval state, and 100% owned statement/branch regression coverage. - Bounded positive `evidence_version` in canonical evidence so materially revised plans have explicit immutable revision identity. +- Tenant-scope activation regressions requiring authoritative requisition/Job/Job Analysis, evidence-provenance, and panel-actor resolution before use. ### Changed - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. - Require canonical non-sentinel UUIDv4 suffixes for every trust-bearing reference; UUIDv1 and other UUID versions now fail closed. +- Require the host to re-resolve every plan reference in the exact tenant, prove requisition-to-Job-to-job-analysis binding, verify question/rating provenance, and prove resolved panel identities are distinct and eligible/trained before accountable human activation. ### Security and privacy - Reject timestamp/node-bearing UUIDv1 reference suffixes as well as human-readable/value-bearing metadata before serialization. - Close `reason_code` to the reviewed non-sensitive `approved_requisition_interview` value. - Redact `StructuredInterviewPlan` representation so routine logs and assertion failures do not expose sensitive correlations or evidence digests. +- State explicitly that UUID/digest correlation and reference-string inequality do not prove tenant ownership, authoritative relationship validity, or actor identity separation. From 09a3b8e278a1735ad6e3c6cffa2b786c0f333225 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 11:15:51 -0700 Subject: [PATCH 041/216] test: require honest interview activation traceability --- .../tests/test_traceability_scope.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/interview-plan/tests/test_traceability_scope.py diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py new file mode 100644 index 000000000..669f0bc06 --- /dev/null +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -0,0 +1,19 @@ +"""Regression contracts for honest structured-interview activation traceability.""" + +from __future__ import annotations + +from pathlib import Path + + +TRACEABILITY = Path(__file__).resolve().parents[3] / "docs" / "traceability" / "structured-interview-plan.md" + + +def test_traceability_does_not_misstate_next_action_as_host_activation_evidence() -> None: + """Label next-action assertions as contract evidence when no activation host exists in this slice.""" + text = TRACEABILITY.read_text(encoding="utf-8") + + assert "No host activation path is implemented in this slice." in text + assert "`test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only)" in text + assert "`test_activation_requires_authoritative_panel_actor_separation` (next_action contract regression only)" in text + assert "tenant-scope activation regression" not in text + assert "tenant/panel activation regressions" not in text From 277bf47d5c1781a29eed9a5e9cf26692559a01a3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 11:16:07 -0700 Subject: [PATCH 042/216] docs: correct structured interview activation evidence boundary --- docs/traceability/structured-interview-plan.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index cb9e200b8..aa0a9d797 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -9,24 +9,26 @@ | Requirement | Contract | Evidence | |---|---|---| | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | -| Authoritative tenant and Job scope is not inferred from identifiers | immutable next action requires every plan reference to be re-resolved within `tenant_record_id` and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_activation_requires_authoritative_tenant_and_job_scope_resolution` | -| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; next action requires authoritative provenance verification | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test; tenant-scope activation regression | +| Authoritative tenant and Job scope is not inferred from identifiers | immutable next action requires every plan reference to be re-resolved within `tenant_record_id` and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only) | +| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; next action requires authoritative provenance verification | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test; next_action tenant/provenance contract regression | | Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | -| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation requires every panel actor to be re-resolved, resolved identities to be distinct, and eligibility/training to be verified | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions; `test_activation_requires_authoritative_panel_actor_separation` | +| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation requires every panel actor to be re-resolved, resolved identities to be distinct, and eligibility/training to be verified | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions; `test_activation_requires_authoritative_panel_actor_separation` (next_action contract regression only) | | Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUIDv4 suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions, UUIDv1 regression, and `dataclasses.replace(...)` bypass regression | | Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | -| High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and immutable authoritative-resolution next action | scalar fail-closed regressions and tenant/panel activation regressions | +| High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and immutable authoritative-resolution next action | scalar fail-closed regressions plus next_action tenant/panel contract regressions | | Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 | naive/unknown-offset/offset/fractional-time regressions and independent digest assertion | | Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace(...)` regressions | ## Evidence boundary +No host activation path is implemented in this slice. The two tests whose names begin with `test_activation_` verify only the immutable `next_action` contract: they do not resolve authoritative records, activate a plan, or prove that a runtime host blocks activation. A future host integration must executable-test tenant scope, requisition-to-Job-to-job-analysis binding, question/mapping/anchor provenance, panel actor identity separation, eligibility, and training before it can claim runtime activation enforcement. + The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only trust references keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. Neither UUID form, reference inequality, nor digest metadata proves tenant ownership, requisition-to-Job-to-job-analysis relationships, mapping provenance, panel identity separation, panel eligibility, training, substantive correctness, or validity. The host must re-resolve those relationships within the exact tenant immediately before accountable human activation. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope -This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. +This slice does not implement a host activation path and does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. From ff497512a7587a1efc77e2a7459b5ba19fd26c29 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:42:02 -0700 Subject: [PATCH 043/216] test: reject correlating tenant UUIDv1 in interview plans --- packages/interview-plan/tests/test_uuid_version.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_uuid_version.py b/packages/interview-plan/tests/test_uuid_version.py index 851cdeada..6e264cff6 100644 --- a/packages/interview-plan/tests/test_uuid_version.py +++ b/packages/interview-plan/tests/test_uuid_version.py @@ -37,6 +37,14 @@ def _plan_kwargs() -> dict[str, object]: } +def test_uuid1_tenant_identity_fails_closed() -> None: + """Reject UUIDv1 timestamp/node metadata in the public tenant identity.""" + data = _plan_kwargs() + data["tenant_record_id"] = _UUID1 + with pytest.raises(ValueError, match="tenant_record_id"): + build_structured_interview_plan(**data) + + @pytest.mark.parametrize( ("field", "value"), [ @@ -53,4 +61,4 @@ def test_uuid1_trust_references_fail_closed(field: str, value: object) -> None: data = _plan_kwargs() data[field] = value with pytest.raises(ValueError, match="canonical-uuid"): - build_structured_interview_plan(**data) + build_structured_interview_plan(**data) \ No newline at end of file From 2b320b2036c7ee80911b5ec67118c5b8484431ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:42:38 -0700 Subject: [PATCH 044/216] fix: require opaque UUIDv4 tenant identity in interview plans --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 02c4194ea..1acbbe832 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -30,13 +30,13 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: - """Require canonical non-sentinel UUID text for a governance identity.""" + """Require canonical UUIDv4 text so a public governance identity stays opaque.""" try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as exc: raise ValueError(f"{field_name} must be canonical UUID text") from exc - if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): - raise ValueError(f"{field_name} must be a canonical operational UUID") + if str(parsed) != value or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUIDv4") def _validate_code(value: str, field_name: str) -> None: From 01a7c3ab64fd3d580ae26b5170c23c24597a9224 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:42:57 -0700 Subject: [PATCH 045/216] docs: bind interview-plan tenant identity to UUIDv4 opacity --- packages/interview-plan/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 2119ebc0d..b30b8948c 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -6,14 +6,14 @@ The plan binds one requisition and authoritative Job to versioned job-analysis e Every plan also carries a bounded positive `evidence_version` (1 through 2147483647) in canonical evidence. Version changes therefore change the SHA-256 audit correlation, and direct construction plus `dataclasses.replace(...)` revalidate the version fail closed. Version 1 is the default for the initial evidence schema; callers must increment it when the governed plan evidence is materially revised rather than treating a digest alone as semantic version identity. -Every trust-bearing reference uses its expected namespace plus a canonical, non-sentinel UUIDv4 suffix. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque-reference boundary. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. +The public `tenant_record_id` and every trust-bearing reference use canonical, non-sentinel UUIDv4 identity; namespaced references additionally require their expected namespace. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque public-identity boundary. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. -Opaque references and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. The packet performs none of those authoritative resolutions itself. +Opaque UUIDv4 identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. The packet performs none of those authoritative resolutions itself. The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action requires those authoritative resolution checks before an accountable human activates the plan. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. `repr(plan)` is fully redacted as `StructuredInterviewPlan()`, so routine logs and assertion failures do not expose governance correlations or evidence digests. Canonical JSON remains the explicit evidence serialization boundary. -For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. +For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. +This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. \ No newline at end of file From e4f388a8be9d54d402cc7a9d1a5ef0e7e6d3e345 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:43:16 -0700 Subject: [PATCH 046/216] docs: require UUIDv4 tenant opacity in interview-plan ADR --- .../adr/0014-governed-structured-interview-plan.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index 26004bcc8..6c6094678 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -7,15 +7,15 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed requisition review, selection evidence, and accountable human employment decisions. A buyer still needs a defensible boundary between an approved opening and the interview that will be used as a selection procedure. -A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. +A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. UUIDv1 also carries timestamp/node-derived correlation metadata, so it is unsuitable for a public tenant identifier as well as for fields presented as opaque references. -Opaque references and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. ## Decision Add a transport-neutral `StructuredInterviewPlan` value object that binds: -- canonical tenant identity and one UUIDv4-backed opaque interview-plan reference; +- canonical UUIDv4 tenant identity and one UUIDv4-backed opaque interview-plan reference; - UUIDv4-backed requisition and authoritative Job references; - UUIDv4-backed exact job-analysis reference plus SHA-256 digest; - UUIDv4-backed exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; @@ -24,11 +24,11 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: - a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; - fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, a bounded positive `evidence_version`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. -All trust-bearing references require their expected namespace plus a canonical, non-sentinel UUIDv4 suffix. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque-reference boundary; names, labels, compensation/protected-attribute values, or other semantic suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. +The public tenant identity and all trust-bearing references require canonical, non-sentinel UUIDv4. Namespaced references additionally require their expected prefix. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque public-identity boundary; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. The packet does not perform or claim those authoritative resolutions. Only after they succeed may an accountable human activate the plan. -The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or approved. Opaque references remain sensitive correlation metadata rather than anonymous data. +The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or approved. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. ## Consequences @@ -37,7 +37,7 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Activation fails closed unless authoritative tenant, Job, evidence-provenance, and panel-identity relationships are re-resolved. - Candidate PII and assessment values remain outside the planning artifact. -- UUIDv1/time-node-bearing and value-bearing trust-reference suffixes cannot enter portable evidence. +- UUIDv1/time-node-bearing tenant/reference identities and value-bearing trust-reference suffixes cannot enter portable evidence. - Routine representation/logging does not expose references or evidence digests. - Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest/version rather than copying question content. - The contract supports standalone use and later MSA extraction without cross-service application-table SQL. @@ -53,4 +53,4 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor ## References -See `docs/doctoring/structured-interview-plan-references.md`. +See `docs/doctoring/structured-interview-plan-references.md`. \ No newline at end of file From 62611807ed918f5d25d42d07d8785220f2a6e948 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:44:57 -0700 Subject: [PATCH 047/216] docs: trace UUIDv4 tenant opacity in interview plans --- docs/traceability/structured-interview-plan.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index aa0a9d797..0c7f5eeb5 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -9,12 +9,12 @@ | Requirement | Contract | Evidence | |---|---|---| | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | -| Authoritative tenant and Job scope is not inferred from identifiers | immutable next action requires every plan reference to be re-resolved within `tenant_record_id` and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only) | +| Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel UUIDv4 `tenant_record_id`; immutable next action requires every plan reference to be re-resolved within that tenant and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_uuid1_tenant_identity_fails_closed` plus `test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only) | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; next action requires authoritative provenance verification | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test; next_action tenant/provenance contract regression | | Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation requires every panel actor to be re-resolved, resolved identities to be distinct, and eligibility/training to be verified | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions; `test_activation_requires_authoritative_panel_actor_separation` (next_action contract regression only) | -| Portable governance metadata is value-minimized | all trust-bearing references require canonical non-sentinel UUIDv4 suffixes; reason is closed to `approved_requisition_interview` | scalar/collection direct-constructor privacy regressions, UUIDv1 regression, and `dataclasses.replace(...)` bypass regression | +| Portable governance metadata is value-minimized | `tenant_record_id` and all trust-bearing references require canonical non-sentinel UUIDv4 identity; namespaced references also require their expected prefix; reason is closed to `approved_requisition_interview` | tenant UUIDv1 regression, scalar/collection direct-constructor privacy regressions, UUIDv1 reference regressions, and `dataclasses.replace(...)` bypass regression | | Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | | High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and immutable authoritative-resolution next action | scalar fail-closed regressions plus next_action tenant/panel contract regressions | @@ -25,10 +25,10 @@ No host activation path is implemented in this slice. The two tests whose names begin with `test_activation_` verify only the immutable `next_action` contract: they do not resolve authoritative records, activate a plan, or prove that a runtime host blocks activation. A future host integration must executable-test tenant scope, requisition-to-Job-to-job-analysis binding, question/mapping/anchor provenance, panel actor identity separation, eligibility, and training before it can claim runtime activation enforcement. -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only trust references keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only tenant and trust-reference identities keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. Neither UUID form, reference inequality, nor digest metadata proves tenant ownership, requisition-to-Job-to-job-analysis relationships, mapping provenance, panel identity separation, panel eligibility, training, substantive correctness, or validity. The host must re-resolve those relationships within the exact tenant immediately before accountable human activation. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope -This slice does not implement a host activation path and does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. +This slice does not implement a host activation path and does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. \ No newline at end of file From 9751d45f215582c722f036fa12f6f0ebe2c7e8f8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 00:45:07 -0700 Subject: [PATCH 048/216] docs: record interview-plan tenant UUIDv4 hardening --- packages/interview-plan/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index c95a77f7a..6d74291bd 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -13,12 +13,12 @@ - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. -- Require canonical non-sentinel UUIDv4 suffixes for every trust-bearing reference; UUIDv1 and other UUID versions now fail closed. +- Require canonical non-sentinel UUIDv4 for the public `tenant_record_id` and for every trust-bearing reference suffix; UUIDv1 and other UUID versions now fail closed. - Require the host to re-resolve every plan reference in the exact tenant, prove requisition-to-Job-to-job-analysis binding, verify question/rating provenance, and prove resolved panel identities are distinct and eligible/trained before accountable human activation. ### Security and privacy -- Reject timestamp/node-bearing UUIDv1 reference suffixes as well as human-readable/value-bearing metadata before serialization. +- Reject timestamp/node-bearing UUIDv1 tenant/reference identities as well as human-readable/value-bearing reference metadata before serialization. - Close `reason_code` to the reviewed non-sensitive `approved_requisition_interview` value. - Redact `StructuredInterviewPlan` representation so routine logs and assertion failures do not expose sensitive correlations or evidence digests. -- State explicitly that UUID/digest correlation and reference-string inequality do not prove tenant ownership, authoritative relationship validity, or actor identity separation. +- State explicitly that UUID/digest correlation and reference-string inequality do not prove tenant ownership, authoritative relationship validity, or actor identity separation. \ No newline at end of file From b0f1e182bbe53b239a58cc71610b7bc69d28e640 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:12:12 -0700 Subject: [PATCH 049/216] test: require interview plans to accept canonical tenant UUIDv7 --- .../interview-plan/tests/test_uuid_version.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/interview-plan/tests/test_uuid_version.py b/packages/interview-plan/tests/test_uuid_version.py index 6e264cff6..c7696929b 100644 --- a/packages/interview-plan/tests/test_uuid_version.py +++ b/packages/interview-plan/tests/test_uuid_version.py @@ -1,4 +1,4 @@ -"""Regression coverage for UUIDv4-only structured-interview trust references.""" +"""Regression coverage for tenant interoperability and UUIDv4 trust references.""" from datetime import datetime, timezone @@ -7,6 +7,7 @@ from orgmetra_interview_plan import build_structured_interview_plan _UUID1 = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" +_UUID7_TENANT = "10000000-0000-7000-8000-000000000001" def _plan_kwargs() -> dict[str, object]: @@ -37,12 +38,14 @@ def _plan_kwargs() -> dict[str, object]: } -def test_uuid1_tenant_identity_fails_closed() -> None: - """Reject UUIDv1 timestamp/node metadata in the public tenant identity.""" +def test_authoritative_uuid7_tenant_identity_is_accepted() -> None: + """Accept the canonical UUIDv7 tenant identity already valid in Orgmetra core.""" data = _plan_kwargs() - data["tenant_record_id"] = _UUID1 - with pytest.raises(ValueError, match="tenant_record_id"): - build_structured_interview_plan(**data) + data["tenant_record_id"] = _UUID7_TENANT + + plan = build_structured_interview_plan(**data) + + assert plan.tenant_record_id == _UUID7_TENANT @pytest.mark.parametrize( @@ -61,4 +64,4 @@ def test_uuid1_trust_references_fail_closed(field: str, value: object) -> None: data = _plan_kwargs() data[field] = value with pytest.raises(ValueError, match="canonical-uuid"): - build_structured_interview_plan(**data) \ No newline at end of file + build_structured_interview_plan(**data) From 456e5489ce31a4e06342e5122d605e2799f604f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:12:44 -0700 Subject: [PATCH 050/216] fix: honor authoritative tenant UUID contract in interview plans --- .../interview-plan/src/orgmetra_interview_plan/plan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 1acbbe832..dfa1dac0e 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -30,13 +30,13 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: - """Require canonical UUIDv4 text so a public governance identity stays opaque.""" + """Require canonical non-sentinel UUID text owned by the authoritative HRIS.""" try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as exc: raise ValueError(f"{field_name} must be canonical UUID text") from exc - if str(parsed) != value or parsed.version != 4 or parsed.int in (0, (1 << 128) - 1): - raise ValueError(f"{field_name} must be a canonical operational UUIDv4") + if str(parsed) != value or parsed.int in (0, (1 << 128) - 1): + raise ValueError(f"{field_name} must be a canonical operational UUID") def _validate_code(value: str, field_name: str) -> None: @@ -229,4 +229,4 @@ def build_structured_interview_plan( reason_code=reason_code, generated_at=generated_at, evidence_version=evidence_version, - ) \ No newline at end of file + ) From b9cf16625eb99dbed3df3b5e285f5b6715b64507 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:13:27 -0700 Subject: [PATCH 051/216] docs: align interview tenant identity with Orgmetra core --- packages/interview-plan/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index b30b8948c..41cde8e4d 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -6,9 +6,9 @@ The plan binds one requisition and authoritative Job to versioned job-analysis e Every plan also carries a bounded positive `evidence_version` (1 through 2147483647) in canonical evidence. Version changes therefore change the SHA-256 audit correlation, and direct construction plus `dataclasses.replace(...)` revalidate the version fail closed. Version 1 is the default for the initial evidence schema; callers must increment it when the governed plan evidence is materially revised rather than treating a digest alone as semantic version identity. -The public `tenant_record_id` and every trust-bearing reference use canonical, non-sentinel UUIDv4 identity; namespaced references additionally require their expected namespace. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque public-identity boundary. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. +The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract rather than imposing a second UUID-version policy at this leaf package. Packet-owned trust-bearing references remain canonical, non-sentinel UUIDv4 values and additionally require their expected namespace. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other non-v4 suffixes fail closed for those packet-owned references so timestamp/node-bearing or otherwise nonconforming identifiers cannot be presented as this package's opaque trust references. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. -Opaque UUIDv4 identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. The packet performs none of those authoritative resolutions itself. +Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. The packet performs none of those authoritative resolutions itself. The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action requires those authoritative resolution checks before an accountable human activates the plan. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. @@ -16,4 +16,4 @@ The object is not an interview result and cannot represent an approved employmen For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. \ No newline at end of file +This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. From 887c7c9ae4a7b15e059785eaf6e7c64122007d4f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:13:48 -0700 Subject: [PATCH 052/216] docs: separate tenant and packet UUID ownership --- docs/adr/0014-governed-structured-interview-plan.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0014-governed-structured-interview-plan.md index 6c6094678..b5137d0a3 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0014-governed-structured-interview-plan.md @@ -7,7 +7,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed requisition review, selection evidence, and accountable human employment decisions. A buyer still needs a defensible boundary between an approved opening and the interview that will be used as a selection procedure. -A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. UUIDv1 also carries timestamp/node-derived correlation metadata, so it is unsuitable for a public tenant identifier as well as for fields presented as opaque references. +A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. @@ -15,7 +15,7 @@ Opaque identities and artifact digests identify evidence but do not prove that e Add a transport-neutral `StructuredInterviewPlan` value object that binds: -- canonical UUIDv4 tenant identity and one UUIDv4-backed opaque interview-plan reference; +- canonical non-sentinel Orgmetra tenant identity and one UUIDv4-backed opaque interview-plan reference; - UUIDv4-backed requisition and authoritative Job references; - UUIDv4-backed exact job-analysis reference plus SHA-256 digest; - UUIDv4-backed exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; @@ -24,7 +24,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: - a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; - fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, a bounded positive `evidence_version`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. -The public tenant identity and all trust-bearing references require canonical, non-sentinel UUIDv4. Namespaced references additionally require their expected prefix. UUIDv1 and other UUID versions fail closed so timestamp/node-bearing identifiers cannot weaken the opaque public-identity boundary; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. +`tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. The packet does not perform or claim those authoritative resolutions. Only after they succeed may an accountable human activate the plan. @@ -37,7 +37,7 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Activation fails closed unless authoritative tenant, Job, evidence-provenance, and panel-identity relationships are re-resolved. - Candidate PII and assessment values remain outside the planning artifact. -- UUIDv1/time-node-bearing tenant/reference identities and value-bearing trust-reference suffixes cannot enter portable evidence. +- Packet-owned trust references reject UUIDv1/time-node-bearing suffixes and value-bearing metadata without making the leaf package incompatible with authoritative Orgmetra tenant UUIDs. - Routine representation/logging does not expose references or evidence digests. - Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest/version rather than copying question content. - The contract supports standalone use and later MSA extraction without cross-service application-table SQL. @@ -46,11 +46,11 @@ The plan is candidate-neutral. It contains no candidate identity, response, scor - The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. - Human approval remains mandatory; model output cannot activate or approve the plan. -- UUIDv4-backed opacity reduces accidental value leakage but does not remove authorization, retention, export-control, or audit obligations for correlation metadata. +- UUIDv4-backed packet references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. - Evidence version and digests identify the reviewed revision but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. ## References -See `docs/doctoring/structured-interview-plan-references.md`. \ No newline at end of file +See `docs/doctoring/structured-interview-plan-references.md`. From ed90333a59609fd247353d51c102b1174a5c57da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:14:07 -0700 Subject: [PATCH 053/216] docs: trace authoritative tenant UUID interoperability --- docs/traceability/structured-interview-plan.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 0c7f5eeb5..c7f269c39 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -9,12 +9,12 @@ | Requirement | Contract | Evidence | |---|---|---| | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | -| Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel UUIDv4 `tenant_record_id`; immutable next action requires every plan reference to be re-resolved within that tenant and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_uuid1_tenant_identity_fails_closed` plus `test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only) | +| Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; immutable next action requires every plan reference to be re-resolved within that tenant and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_authoritative_uuid7_tenant_identity_is_accepted` plus `test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only) | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; next action requires authoritative provenance verification | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test; next_action tenant/provenance contract regression | | Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation requires every panel actor to be re-resolved, resolved identities to be distinct, and eligibility/training to be verified | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions; `test_activation_requires_authoritative_panel_actor_separation` (next_action contract regression only) | -| Portable governance metadata is value-minimized | `tenant_record_id` and all trust-bearing references require canonical non-sentinel UUIDv4 identity; namespaced references also require their expected prefix; reason is closed to `approved_requisition_interview` | tenant UUIDv1 regression, scalar/collection direct-constructor privacy regressions, UUIDv1 reference regressions, and `dataclasses.replace(...)` bypass regression | +| Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; packet-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason is closed to `approved_requisition_interview` | authoritative UUIDv7 tenant interoperability regression, scalar/collection direct-constructor privacy regressions, UUIDv1 reference regressions, and `dataclasses.replace(...)` bypass regression | | Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | | Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | | High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and immutable authoritative-resolution next action | scalar fail-closed regressions plus next_action tenant/panel contract regressions | @@ -25,10 +25,10 @@ No host activation path is implemented in this slice. The two tests whose names begin with `test_activation_` verify only the immutable `next_action` contract: they do not resolve authoritative records, activate a plan, or prove that a runtime host blocks activation. A future host integration must executable-test tenant scope, requisition-to-Job-to-job-analysis binding, question/mapping/anchor provenance, panel actor identity separation, eligibility, and training before it can claim runtime activation enforcement. -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. UUIDv4-only tenant and trust-reference identities keep timestamp/node-bearing UUIDv1 identifiers outside portable evidence, but UUIDv4 opacity still does not make correlation metadata anonymous. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. Packet-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside those portable references, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. Neither UUID form, reference inequality, nor digest metadata proves tenant ownership, requisition-to-Job-to-job-analysis relationships, mapping provenance, panel identity separation, panel eligibility, training, substantive correctness, or validity. The host must re-resolve those relationships within the exact tenant immediately before accountable human activation. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. ## Out of scope -This slice does not implement a host activation path and does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. \ No newline at end of file +This slice does not implement a host activation path and does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. From e19984227af36c5ac2d342d170668daa62a7eced Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 06:14:18 -0700 Subject: [PATCH 054/216] docs: record tenant identity interoperability repair --- packages/interview-plan/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 6d74291bd..786e5811b 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -13,12 +13,12 @@ - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. -- Require canonical non-sentinel UUIDv4 for the public `tenant_record_id` and for every trust-bearing reference suffix; UUIDv1 and other UUID versions now fail closed. +- Keep packet-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` now follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. - Require the host to re-resolve every plan reference in the exact tenant, prove requisition-to-Job-to-job-analysis binding, verify question/rating provenance, and prove resolved panel identities are distinct and eligible/trained before accountable human activation. ### Security and privacy -- Reject timestamp/node-bearing UUIDv1 tenant/reference identities as well as human-readable/value-bearing reference metadata before serialization. +- Reject timestamp/node-bearing UUIDv1 values in packet-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Close `reason_code` to the reviewed non-sensitive `approved_requisition_interview` value. - Redact `StructuredInterviewPlan` representation so routine logs and assertion failures do not expose sensitive correlations or evidence digests. -- State explicitly that UUID/digest correlation and reference-string inequality do not prove tenant ownership, authoritative relationship validity, or actor identity separation. \ No newline at end of file +- State explicitly that UUID/digest correlation and reference-string inequality do not prove tenant ownership, authoritative relationship validity, or actor identity separation. From 4a274fe5380c2bb448be784f8c6f17b130983bb4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:06:13 -0700 Subject: [PATCH 055/216] test(interview-plan): expose duplicate ADR numbering --- .../interview-plan/tests/test_adr_numbering.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/interview-plan/tests/test_adr_numbering.py diff --git a/packages/interview-plan/tests/test_adr_numbering.py b/packages/interview-plan/tests/test_adr_numbering.py new file mode 100644 index 000000000..6d90df9fe --- /dev/null +++ b/packages/interview-plan/tests/test_adr_numbering.py @@ -0,0 +1,15 @@ +"""Regression coverage for repository-wide ADR numbering collisions.""" + +from pathlib import Path + + +def test_adr_numeric_prefixes_are_unique() -> None: + """Reject duplicate four-digit ADR identifiers after branch integration.""" + repository_root = Path(__file__).resolve().parents[3] + adr_paths = sorted((repository_root / "docs" / "adr").glob("[0-9][0-9][0-9][0-9]-*.md")) + adr_numbers = [path.name.split("-", 1)[0] for path in adr_paths] + duplicate_numbers = sorted( + number for number in set(adr_numbers) if adr_numbers.count(number) > 1 + ) + + assert duplicate_numbers == [], f"duplicate ADR numeric prefixes: {duplicate_numbers}" From b2da11c46923d685c4e4c3a8b2f2be6bb734a950 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:07:39 -0700 Subject: [PATCH 056/216] fix(interview-plan): renumber ADR after job-analysis integration --- .github/workflows/interview-plan-quality.yml | 2 +- ...rview-plan.md => 0015-governed-structured-interview-plan.md} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/adr/{0014-governed-structured-interview-plan.md => 0015-governed-structured-interview-plan.md} (99%) diff --git a/.github/workflows/interview-plan-quality.yml b/.github/workflows/interview-plan-quality.yml index abebb074a..1edc06a49 100644 --- a/.github/workflows/interview-plan-quality.yml +++ b/.github/workflows/interview-plan-quality.yml @@ -8,7 +8,7 @@ on: - "packages/interview-plan/**" - ".github/requirements/foundation-test.txt" - ".github/workflows/interview-plan-quality.yml" - - "docs/adr/0014-governed-structured-interview-plan.md" + - "docs/adr/0015-governed-structured-interview-plan.md" - "docs/doctoring/structured-interview-plan-references.md" - "docs/traceability/structured-interview-plan.md" workflow_dispatch: diff --git a/docs/adr/0014-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md similarity index 99% rename from docs/adr/0014-governed-structured-interview-plan.md rename to docs/adr/0015-governed-structured-interview-plan.md index b5137d0a3..6cd6ec613 100644 --- a/docs/adr/0014-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -1,4 +1,4 @@ -# ADR 0014: Govern structured-interview plans as candidate-neutral evidence +# ADR 0015: Govern structured-interview plans as candidate-neutral evidence - **Status:** Proposed — active PR only - **Date:** 2026-08-18 From 50540d53beef9776d53ead8efdac7f6a2965c926 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:31:15 -0700 Subject: [PATCH 057/216] test(interview-plan): require executable governed activation --- .../interview-plan/tests/test_activation.py | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 packages/interview-plan/tests/test_activation.py diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py new file mode 100644 index 000000000..b6e47a5a8 --- /dev/null +++ b/packages/interview-plan/tests/test_activation.py @@ -0,0 +1,216 @@ +"""Regression tests for executable, fail-closed structured-interview activation.""" + +from dataclasses import replace +from datetime import datetime, timezone +import json + +import pytest + +from orgmetra_interview_plan import ( + StructuredInterviewActivationReceipt, + StructuredInterviewActivationVerification, + activate_structured_interview_plan, + build_structured_interview_plan, +) + +TENANT = "10000000-0000-7000-8000-000000000001" +INTERVIEW_PLAN = "interview_plan:11111111-1111-4111-8111-111111111111" +REQUISITION = "requisition:22222222-2222-4222-8222-222222222222" +JOB_PROFILE = "job_profile:33333333-3333-4333-8333-333333333333" +JOB_ANALYSIS = "job_analysis:44444444-4444-4444-8444-444444444444" +QUESTION_SET = "question_set:55555555-5555-4555-8555-555555555555" +QUESTION_MAP = "question_competency_map:66666666-6666-4666-8666-666666666666" +RATING_ANCHOR = "rating_anchor:77777777-7777-4777-8777-777777777777" +COMPETENCY_A = "competency:88888888-8888-4888-8888-888888888888" +COMPETENCY_B = "competency:99999999-9999-4999-8999-999999999999" +PANEL_A = "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +PANEL_B = "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc" +APPROVER = "actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd" +AUTHORITY_EVIDENCE = "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" +DIGEST_A = "a" * 64 +DIGEST_B = "b" * 64 +DIGEST_C = "c" * 64 +DIGEST_D = "d" * 64 +DIGEST_E = "e" * 64 +APPROVED_AT = datetime(2026, 8, 21, 5, 0, 0, 123456, tzinfo=timezone.utc) + + +def plan(): + """Return a valid immutable plan for activation-boundary tests.""" + return build_structured_interview_plan( + tenant_record_id=TENANT, + interview_plan_reference=INTERVIEW_PLAN, + requisition_reference=REQUISITION, + job_profile_reference=JOB_PROFILE, + job_analysis_reference=JOB_ANALYSIS, + job_analysis_digest=DIGEST_A, + question_set_reference=QUESTION_SET, + question_set_digest=DIGEST_B, + question_competency_map_reference=QUESTION_MAP, + question_competency_map_digest=DIGEST_D, + rating_anchor_reference=RATING_ANCHOR, + rating_anchor_digest=DIGEST_C, + competency_references=(COMPETENCY_A, COMPETENCY_B), + panel_actor_references=(PANEL_A, PANEL_B), + question_count=4, + purpose_code="structured_interview_plan", + reason_code="approved_requisition_interview", + generated_at=datetime(2026, 8, 21, 4, 30, tzinfo=timezone.utc), + ) + + +def verification_for(candidate_plan, **changes): + """Return matching authoritative host evidence, optionally mutated for failure tests.""" + values = dict( + tenant_record_id=candidate_plan.tenant_record_id, + interview_plan_reference=candidate_plan.interview_plan_reference, + plan_digest=candidate_plan.sha256_digest(), + approving_actor_reference=APPROVER, + authority_evidence_reference=AUTHORITY_EVIDENCE, + authority_evidence_digest=DIGEST_E, + ) + values.update(changes) + return StructuredInterviewActivationVerification(**values) + + +class AllowingAuthority: + """Host fixture that returns evidence only after its authoritative checks succeed.""" + + def __init__(self, verification): + self.verification = verification + self.calls = [] + + def verify_activation(self, *, plan, approving_actor_reference): + """Record the exact requested plan/actor and return authoritative evidence.""" + self.calls.append((plan, approving_actor_reference)) + return self.verification + + +class RejectingAuthority: + """Host fixture representing a failed tenant/job/provenance/panel verification.""" + + def verify_activation(self, *, plan, approving_actor_reference): + """Fail closed instead of producing activation evidence.""" + raise PermissionError("authoritative activation checks failed") + + +def test_activation_executes_authority_and_returns_immutable_human_receipt(): + """Bind human confirmation to the exact plan and authoritative verification evidence.""" + candidate_plan = plan() + authority = AllowingAuthority(verification_for(candidate_plan)) + + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + assert authority.calls == [(candidate_plan, APPROVER)] + payload = json.loads(receipt.canonical_json()) + assert payload["tenant_record_id"] == TENANT + assert payload["interview_plan_reference"] == INTERVIEW_PLAN + assert payload["plan_digest"] == candidate_plan.sha256_digest() + assert payload["approving_actor_reference"] == APPROVER + assert payload["authority_evidence_reference"] == AUTHORITY_EVIDENCE + assert payload["authority_evidence_digest"] == DIGEST_E + assert payload["purpose_code"] == "structured_interview_activation" + assert payload["reason_code"] == "human_approved_plan_activation" + assert payload["evidence_version"] == 1 + assert payload["human_confirmation"] is True + assert payload["activation_state"] == "approved_for_use" + assert payload["approved_at"] == "2026-08-21T05:00:00.123456Z" + assert receipt.sha256_digest() + assert repr(receipt) == "StructuredInterviewActivationReceipt()" + + +def test_authority_rejection_blocks_activation(): + """Propagate authoritative rejection so no activation receipt can be manufactured.""" + with pytest.raises(PermissionError, match="authoritative activation checks failed"): + activate_structured_interview_plan( + plan=plan(), + authority=RejectingAuthority(), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + +def test_activation_rejects_non_verification_result(): + """Reject adapters that do not return the published verification contract.""" + class WrongAuthority: + def verify_activation(self, *, plan, approving_actor_reference): + return object() + + with pytest.raises(TypeError, match="StructuredInterviewActivationVerification"): + activate_structured_interview_plan( + plan=plan(), + authority=WrongAuthority(), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + +@pytest.mark.parametrize( + ("changes", "match"), + [ + ({"tenant_record_id": "20000000-0000-7000-8000-000000000001"}, "different plan or actor"), + ({"interview_plan_reference": "interview_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"}, "different plan or actor"), + ({"plan_digest": "f" * 64}, "different plan or actor"), + ({"approving_actor_reference": PANEL_A}, "different plan or actor"), + ], +) +def test_activation_rejects_authority_evidence_for_other_scope(changes, match): + """Reject otherwise well-shaped verification evidence bound to a different scope.""" + candidate_plan = plan() + authority = AllowingAuthority(verification_for(candidate_plan, **changes)) + with pytest.raises(ValueError, match=match): + activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + +@pytest.mark.parametrize( + ("changes", "match"), + [ + ({"authority_evidence_reference": "activation_verification:human-readable"}, "authority_evidence_reference"), + ({"authority_evidence_digest": "A" * 64}, "authority_evidence_digest"), + ], +) +def test_activation_rejects_untrusted_authority_evidence_shape(changes, match): + """Require opaque verification identity and deterministic evidence digest.""" + candidate_plan = plan() + authority = AllowingAuthority(verification_for(candidate_plan, **changes)) + with pytest.raises(ValueError, match=match): + activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + +@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"), + ("human_confirmation", False, "human confirmation"), + ("activation_state", "pending", "activation_state"), + ], +) +def test_direct_receipt_construction_fails_closed(field, bad, match): + """Preserve fixed human-authority semantics under direct dataclass construction/replacement.""" + candidate_plan = plan() + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification_for(candidate_plan)), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + with pytest.raises(ValueError, match=match): + replace(receipt, **{field: bad}) From f69553f39f0a3f8e7d454a8060586070a73f6246 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:31:35 -0700 Subject: [PATCH 058/216] feat(interview-plan): execute fail-closed human activation --- .../src/orgmetra_interview_plan/activation.py | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 packages/interview-plan/src/orgmetra_interview_plan/activation.py diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py new file mode 100644 index 000000000..9987c9bf0 --- /dev/null +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -0,0 +1,188 @@ +"""Executable human-approval boundary for governed structured-interview plans. + +The authority adapter is owned by the Orgmetra host. It MUST return verification +only after re-resolving the plan inside the exact tenant, proving the +requisition-to-Job-to-job-analysis binding, verifying question/mapping/rating +provenance, resolving distinct panel actors, and confirming panel eligibility +and training. Any failed authoritative check must raise instead of returning +verification evidence. +""" +from __future__ import annotations + +from dataclasses import dataclass +from hashlib import sha256 +import json +from typing import Protocol + +from .plan import ( + StructuredInterviewPlan, + _canonical_timestamp, + _validate_code, + _validate_digest, + _validate_operational_uuid, + _validate_reference, +) + +_PURPOSE_CODE = "structured_interview_activation" +_REASON_CODE = "human_approved_plan_activation" +_ACTIVATION_STATE = "approved_for_use" +_MAX_EVIDENCE_VERSION = 2_147_483_647 + + +@dataclass(frozen=True, slots=True) +class StructuredInterviewActivationVerification: + """Authoritative host evidence returned only after all activation checks pass.""" + + tenant_record_id: str + interview_plan_reference: str + plan_digest: str + approving_actor_reference: str + authority_evidence_reference: str + authority_evidence_digest: str + + +class StructuredInterviewActivationAuthority(Protocol): + """Host contract that fail-closes unless every authoritative activation check passes.""" + + def verify_activation( + self, + *, + plan: StructuredInterviewPlan, + approving_actor_reference: str, + ) -> StructuredInterviewActivationVerification: + """Return exact-scope evidence only after authoritative checks succeed.""" + ... + + +@dataclass(frozen=True, slots=True, repr=False) +class StructuredInterviewActivationReceipt: + """Immutable evidence that an accountable human activated one exact reviewed plan.""" + + tenant_record_id: str + interview_plan_reference: str + plan_digest: str + approving_actor_reference: str + authority_evidence_reference: str + authority_evidence_digest: str + approved_at: object + purpose_code: str = _PURPOSE_CODE + reason_code: str = _REASON_CODE + evidence_version: int = 1 + human_confirmation: bool = True + activation_state: str = _ACTIVATION_STATE + + def __post_init__(self) -> None: + """Reject forged, ambiguous, or weakened activation evidence.""" + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + _validate_reference( + self.interview_plan_reference, + "interview_plan", + "interview_plan_reference", + ) + _validate_digest(self.plan_digest, "plan_digest") + _validate_reference( + self.approving_actor_reference, + "actor", + "approving_actor_reference", + ) + _validate_reference( + self.authority_evidence_reference, + "activation_verification", + "authority_evidence_reference", + ) + _validate_digest(self.authority_evidence_digest, "authority_evidence_digest") + _canonical_timestamp(self.approved_at) + _validate_code(self.purpose_code, "purpose_code") + if self.purpose_code != _PURPOSE_CODE: + raise ValueError("purpose_code must remain structured_interview_activation") + _validate_code(self.reason_code, "reason_code") + if self.reason_code != _REASON_CODE: + raise ValueError("reason_code must remain human_approved_plan_activation") + if type(self.evidence_version) is not int or not 1 <= self.evidence_version <= _MAX_EVIDENCE_VERSION: + raise ValueError("evidence_version must be an integer from 1 through 2147483647") + if self.human_confirmation is not True: + raise ValueError("human confirmation is mandatory for interview-plan activation") + if self.activation_state != _ACTIVATION_STATE: + raise ValueError("activation_state must remain approved_for_use") + + def __repr__(self) -> str: + """Return a redacted representation suitable for routine logs.""" + return "StructuredInterviewActivationReceipt()" + + def canonical_json(self) -> str: + """Return deterministic canonical JSON for immutable audit correlation.""" + payload = { + "activation_state": self.activation_state, + "approved_at": _canonical_timestamp(self.approved_at), + "approving_actor_reference": self.approving_actor_reference, + "authority_evidence_digest": self.authority_evidence_digest, + "authority_evidence_reference": self.authority_evidence_reference, + "evidence_version": self.evidence_version, + "human_confirmation": self.human_confirmation, + "interview_plan_reference": self.interview_plan_reference, + "plan_digest": self.plan_digest, + "purpose_code": self.purpose_code, + "reason_code": self.reason_code, + "tenant_record_id": self.tenant_record_id, + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def sha256_digest(self) -> str: + """Return SHA-256 over the exact canonical UTF-8 activation receipt.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + +def activate_structured_interview_plan( + *, + plan: StructuredInterviewPlan, + authority: StructuredInterviewActivationAuthority, + approving_actor_reference: str, + approved_at: object, +) -> StructuredInterviewActivationReceipt: + """Activate one exact plan only after authoritative host verification succeeds. + + The authority implementation is responsible for the actual tenant-scoped + re-resolution and relationship/provenance/panel checks. This function rejects + a non-contract result or evidence bound to a different plan/actor and emits a + value-minimized immutable human-approval receipt only for the exact verified + scope. + """ + _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") + verification = authority.verify_activation( + plan=plan, + approving_actor_reference=approving_actor_reference, + ) + if not isinstance(verification, StructuredInterviewActivationVerification): + raise TypeError("authority must return StructuredInterviewActivationVerification") + + _validate_reference( + verification.authority_evidence_reference, + "activation_verification", + "authority_evidence_reference", + ) + _validate_digest(verification.authority_evidence_digest, "authority_evidence_digest") + + expected_scope = ( + plan.tenant_record_id, + plan.interview_plan_reference, + plan.sha256_digest(), + approving_actor_reference, + ) + verified_scope = ( + verification.tenant_record_id, + verification.interview_plan_reference, + verification.plan_digest, + verification.approving_actor_reference, + ) + if verified_scope != expected_scope: + raise ValueError("activation authority returned evidence for a different plan or actor") + + return StructuredInterviewActivationReceipt( + tenant_record_id=plan.tenant_record_id, + interview_plan_reference=plan.interview_plan_reference, + plan_digest=plan.sha256_digest(), + approving_actor_reference=approving_actor_reference, + authority_evidence_reference=verification.authority_evidence_reference, + authority_evidence_digest=verification.authority_evidence_digest, + approved_at=approved_at, + ) From 460c4e4fbce041fd6c0b6739a67fd5121715ecc5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:31:41 -0700 Subject: [PATCH 059/216] feat(interview-plan): export governed activation contracts --- .../src/orgmetra_interview_plan/__init__.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/__init__.py b/packages/interview-plan/src/orgmetra_interview_plan/__init__.py index eefddc536..3aefd52c6 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/__init__.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/__init__.py @@ -1,4 +1,17 @@ -"""Public structured-interview planning contracts for Orgmetra.""" +"""Public structured-interview planning and activation contracts for Orgmetra.""" +from .activation import ( + StructuredInterviewActivationAuthority, + StructuredInterviewActivationReceipt, + StructuredInterviewActivationVerification, + activate_structured_interview_plan, +) from .plan import StructuredInterviewPlan, build_structured_interview_plan -__all__ = ["StructuredInterviewPlan", "build_structured_interview_plan"] +__all__ = [ + "StructuredInterviewActivationAuthority", + "StructuredInterviewActivationReceipt", + "StructuredInterviewActivationVerification", + "StructuredInterviewPlan", + "activate_structured_interview_plan", + "build_structured_interview_plan", +] From 69aa96b9266422d0f4578eba597a336296ffa5e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:32:42 -0700 Subject: [PATCH 060/216] test(interview-plan): satisfy executable docstring contract --- packages/interview-plan/tests/test_activation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index b6e47a5a8..20ba10ca8 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -77,6 +77,7 @@ class AllowingAuthority: """Host fixture that returns evidence only after its authoritative checks succeed.""" def __init__(self, verification): + """Store the verification fixture and initialize the call audit list.""" self.verification = verification self.calls = [] @@ -137,8 +138,12 @@ def test_authority_rejection_blocks_activation(): def test_activation_rejects_non_verification_result(): """Reject adapters that do not return the published verification contract.""" + class WrongAuthority: + """Fixture that violates the published authority return type.""" + def verify_activation(self, *, plan, approving_actor_reference): + """Return a non-contract object to prove type fail-closure.""" return object() with pytest.raises(TypeError, match="StructuredInterviewActivationVerification"): From 6e2fc7368ef10094938469b102245e246fbb045f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:33:16 -0700 Subject: [PATCH 061/216] docs(interview-plan): document executable activation boundary --- packages/interview-plan/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 41cde8e4d..d87a868cb 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -1,19 +1,23 @@ # Orgmetra structured interview plan -`orgmetra-interview-plan` creates candidate-neutral evidence for approving a structured interview **before** it is used with applicants. +`orgmetra-interview-plan` creates candidate-neutral evidence for approving a structured interview **before** it is used with applicants and exposes a fail-closed activation boundary that records accountable human approval only after authoritative host verification succeeds. The plan binds one requisition and authoritative Job to versioned job-analysis evidence, a predetermined question set, an exact question-to-competency mapping artifact, rating anchors, job-related competency references, and a bounded interviewer panel. The question set and mapping each carry their own immutable SHA-256 evidence digest, so a count of questions cannot be mistaken for proof that every governed competency is actually assessed. It keeps candidate identity, responses, scores, demographic attributes, model output, credentials, provider data, and free-form personal/value-bearing reason text out of the packet. Every plan also carries a bounded positive `evidence_version` (1 through 2147483647) in canonical evidence. Version changes therefore change the SHA-256 audit correlation, and direct construction plus `dataclasses.replace(...)` revalidate the version fail closed. Version 1 is the default for the initial evidence schema; callers must increment it when the governed plan evidence is materially revised rather than treating a digest alone as semantic version identity. -The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract rather than imposing a second UUID-version policy at this leaf package. Packet-owned trust-bearing references remain canonical, non-sentinel UUIDv4 values and additionally require their expected namespace. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, and panel actors. UUIDv1 and other non-v4 suffixes fail closed for those packet-owned references so timestamp/node-bearing or otherwise nonconforming identifiers cannot be presented as this package's opaque trust references. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The initial reason vocabulary is closed to the reviewed non-sensitive `approved_requisition_interview` value. +The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract rather than imposing a second UUID-version policy at this leaf package. Packet-owned trust-bearing references remain canonical, non-sentinel UUIDv4 values and additionally require their expected namespace. That applies to the interview plan, requisition, Job, Job Analysis, question set, question-to-competency map, rating anchors, competencies, panel actors, accountable approving actor, and activation-verification evidence. UUIDv1 and other non-v4 suffixes fail closed for those packet-owned references so timestamp/node-bearing or otherwise nonconforming identifiers cannot be presented as this package's opaque trust references. Human-readable/value-bearing suffixes such as names, job labels, protected-attribute labels, compensation values, or interviewer names are also rejected before canonical evidence is produced. The plan reason vocabulary remains closed to `approved_requisition_interview`; activation evidence uses fixed `structured_interview_activation` / `human_approved_plan_activation` governance codes. -Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. The packet performs none of those authoritative resolutions itself. +Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -The object is not an interview result and cannot represent an approved employment decision. `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and the next action requires those authoritative resolution checks before an accountable human activates the plan. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed invariants. +`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, and training checks pass. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. + +The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. + +The plan object itself remains pending human review: `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and its next action requires authoritative resolution before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed plan invariants. The activation receipt is separate evidence and does not mutate or rewrite the reviewed plan. `repr(plan)` is fully redacted as `StructuredInterviewPlan()`, so routine logs and assertion failures do not expose governance correlations or evidence digests. Canonical JSON remains the explicit evidence serialization boundary. For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not persist Job Analysis, requisitions, candidates, interview responses, or scores. Those remain separate Orgmetra boundaries and must use purpose-bound authorization, human review, and immutable audit/outbox evidence when they become authoritative writes. +This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries and preserve immutable audit/outbox evidence for any later authoritative write. From 47197a0083a33536e1485aedf80f1314439ef7b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:33:41 -0700 Subject: [PATCH 062/216] docs(traceability): bind structured interview activation evidence --- .../traceability/structured-interview-plan.md | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index c7f269c39..294edca4c 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -2,33 +2,38 @@ ## Truth status -**Active PR only.** Protected `develop` at branch creation does not contain this capability. Do not describe it as shipped until the exact integrated protected head passes all required gates and merges. +**Active PR only.** Protected `develop` does not contain this structured-interview capability until the exact integrated PR head passes all required gates and merges. The active PR now contains both the candidate-neutral plan contract and a transport-neutral executable activation boundary; it still does not claim that a concrete production authority adapter is already deployed. ## Buyer requirement → executable evidence | Requirement | Contract | Evidence | |---|---|---| | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | -| Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; immutable next action requires every plan reference to be re-resolved within that tenant and the requisition-to-Job-to-job-analysis binding to be proven before activation | `test_authoritative_uuid7_tenant_identity_is_accepted` plus `test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only) | -| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; next action requires authoritative provenance verification | invalid/value-bearing/UUIDv1-reference and digest regressions; deterministic SHA-256 test; next_action tenant/provenance contract regression | -| Evidence revisions remain distinguishable and immutable | bounded positive `evidence_version` in canonical JSON; version change alters SHA-256 correlation | `test_evidence_version_is_canonical_bounded_and_revalidated` including boolean/zero/negative/text/overflow and `dataclasses.replace(...)` cases | +| Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | +| Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | +| Evidence revisions remain distinguishable and immutable | bounded positive plan `evidence_version` in canonical JSON; activation receipt separately binds the exact plan digest and its own bounded positive evidence version | plan evidence-version regressions plus activation receipt canonical/digest and direct-replacement fail-closed regressions | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | -| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation requires every panel actor to be re-resolved, resolved identities to be distinct, and eligibility/training to be verified | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions; `test_activation_requires_authoritative_panel_actor_separation` (next_action contract regression only) | -| Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; packet-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason is closed to `approved_requisition_interview` | authoritative UUIDv7 tenant interoperability regression, scalar/collection direct-constructor privacy regressions, UUIDv1 reference regressions, and `dataclasses.replace(...)` bypass regression | -| Routine logs do not reveal plan correlations | custom redacted `StructuredInterviewPlan.__repr__` | exact repr regression proves references and evidence digest are absent | -| Planning evidence is candidate-neutral | no candidate identity, response, score, demographic attribute, or model output fields | canonical JSON regression plus contract surface review | -| High-impact use cannot be self-approved by generated evidence | `human_confirmation_required is True`; fixed `requires_human_approval` state and immutable authoritative-resolution next action | scalar fail-closed regressions plus next_action tenant/panel contract regressions | -| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 | naive/unknown-offset/offset/fractional-time regressions and independent digest assertion | -| Direct construction cannot bypass invariants | `__post_init__` owns validation | direct constructor and `dataclasses.replace(...)` regressions | +| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions plus fail-closed authority rejection path | +| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state | `test_activation_executes_authority_and_returns_immutable_human_receipt` plus direct receipt mutation failures | +| Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | +| Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | +| Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, and `dataclasses.replace(...)` bypass regressions | +| Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__` and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references and evidence digests are absent | +| Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | +| Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | +| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time plan regressions and activation canonical/digest assertions | +| Direct construction cannot bypass invariants | plan and activation receipt `__post_init__` validation | direct constructor and `dataclasses.replace(...)` regressions | ## Evidence boundary -No host activation path is implemented in this slice. The two tests whose names begin with `test_activation_` verify only the immutable `next_action` contract: they do not resolve authoritative records, activate a plan, or prove that a runtime host blocks activation. A future host integration must executable-test tenant scope, requisition-to-Job-to-job-analysis binding, question/mapping/anchor provenance, panel actor identity separation, eligibility, and training before it can claim runtime activation enforcement. +The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` calls an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. -The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. The evidence version identifies the canonical plan-evidence revision and is itself digest-bound. Packet-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside those portable references, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, and training. The protocol contract requires such an adapter to raise rather than return verification evidence when any of those checks fails. The current tests prove the orchestration fail-closure and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. -Neither UUID form, reference inequality, nor digest metadata proves tenant ownership, requisition-to-Job-to-job-analysis relationships, mapping provenance, panel identity separation, panel eligibility, training, substantive correctness, or validity. The host must re-resolve those relationships within the exact tenant immediately before accountable human activation. Purpose-bound authorization, least privilege, retention/export controls, and audit remain required. +The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. + +Neither UUID form, reference inequality, digest metadata, nor the authority protocol by itself proves tenant ownership, relationship validity, panel identity separation, eligibility, training, scientific validity, fairness, or legal compliance. Production hosts must satisfy those obligations at the authoritative boundary and preserve purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence. ## Out of scope -This slice does not implement a host activation path and does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not claim that a structured interview is legally compliant or scientifically validated merely because a plan packet exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, and human-decision evidence. +This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. From 953aa86d2548487e6e22a20cb7cdc4d4a5548a76 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:34:00 -0700 Subject: [PATCH 063/216] docs(adr): record executable activation decision --- ...0015-governed-structured-interview-plan.md | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 6cd6ec613..228debfba 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. ## Decision @@ -26,29 +26,35 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: `tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. -The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. The packet does not perform or claim those authoritative resolutions. Only after they succeed may an accountable human activate the plan. +The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -The plan is candidate-neutral. It contains no candidate identity, response, score, demographic attribute, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or approved. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. The injected host authority must return `StructuredInterviewActivationVerification` only after all authoritative checks succeed and must raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. + +A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. + +The plan and activation receipt are candidate-neutral. They contain no candidate identity, response, score, demographic attribute, compensation value, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or scientifically adequate. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. ## Consequences ### Positive - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. -- Activation fails closed unless authoritative tenant, Job, evidence-provenance, and panel-identity relationships are re-resolved. -- Candidate PII and assessment values remain outside the planning artifact. +- Runtime activation orchestration fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, or returns evidence bound to another tenant/plan/digest/actor. +- Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. +- Candidate PII and assessment values remain outside the planning and activation artifacts. - Packet-owned trust references reject UUIDv1/time-node-bearing suffixes and value-bearing metadata without making the leaf package incompatible with authoritative Orgmetra tenant UUIDs. - Routine representation/logging does not expose references or evidence digests. - Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest/version rather than copying question content. -- The contract supports standalone use and later MSA extraction without cross-service application-table SQL. +- The authority protocol preserves standalone operation and later MSA extraction without cross-service application-table SQL or duplicated foreign service state. ### Costs and constraints -- The plan does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. +- The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. +- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence. - Human approval remains mandatory; model output cannot activate or approve the plan. -- UUIDv4-backed packet references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. +- UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. -- Evidence version and digests identify the reviewed revision but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. +- Evidence versions and digests identify reviewed revisions but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. - This ADR remains proposed until its exact PR head merges into protected `develop`. ## References From 48bc773531f39214a34664c3e7da3d6a9e96b20f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:34:10 -0700 Subject: [PATCH 064/216] docs(changelog): record governed activation receipt --- packages/interview-plan/CHANGELOG.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 786e5811b..63304127b 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -7,18 +7,19 @@ - Candidate-neutral `StructuredInterviewPlan` binding an approved requisition and Job to exact job-analysis, question-set, question-to-competency mapping, rating-anchor, competency, and interviewer-panel evidence. - Fail-closed direct-construction validation, deterministic canonical JSON/SHA-256 audit correlation, explicit human approval state, and 100% owned statement/branch regression coverage. - Bounded positive `evidence_version` in canonical evidence so materially revised plans have explicit immutable revision identity. -- Tenant-scope activation regressions requiring authoritative requisition/Job/Job Analysis, evidence-provenance, and panel-actor resolution before use. +- Executable `StructuredInterviewActivationAuthority` / `activate_structured_interview_plan(...)` boundary that requires exact-scope authoritative verification before emitting any approval evidence. +- Value-minimized `StructuredInterviewActivationReceipt` binding the exact plan digest, accountable approving actor, authority-verification reference/digest, fixed purpose/reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. ### Changed - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. -- Keep packet-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` now follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. -- Require the host to re-resolve every plan reference in the exact tenant, prove requisition-to-Job-to-job-analysis binding, verify question/rating provenance, and prove resolved panel identities are distinct and eligible/trained before accountable human activation. +- Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. +- Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, and approving actor before a receipt can exist. ### Security and privacy -- Reject timestamp/node-bearing UUIDv1 values in packet-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. -- Close `reason_code` to the reviewed non-sensitive `approved_requisition_interview` value. -- Redact `StructuredInterviewPlan` representation so routine logs and assertion failures do not expose sensitive correlations or evidence digests. -- State explicitly that UUID/digest correlation and reference-string inequality do not prove tenant ownership, authoritative relationship validity, or actor identity separation. +- Reject timestamp/node-bearing UUIDv1 values in package-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. +- Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. +- Redact both `StructuredInterviewPlan` and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. +- State explicitly that UUID/digest correlation, reference-string inequality, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From 29318057ab37a3f6df23a027a312e4441c79675d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:59:02 -0700 Subject: [PATCH 065/216] test(interview-plan): align traceability regression with activation boundary --- .../tests/test_traceability_scope.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py index 669f0bc06..ea84b2d2a 100644 --- a/packages/interview-plan/tests/test_traceability_scope.py +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -8,12 +8,16 @@ TRACEABILITY = Path(__file__).resolve().parents[3] / "docs" / "traceability" / "structured-interview-plan.md" -def test_traceability_does_not_misstate_next_action_as_host_activation_evidence() -> None: - """Label next-action assertions as contract evidence when no activation host exists in this slice.""" +def test_traceability_matches_executable_activation_boundary() -> None: + """Keep traceability aligned with the executable host-orchestration boundary and its limits.""" text = TRACEABILITY.read_text(encoding="utf-8") - assert "No host activation path is implemented in this slice." in text - assert "`test_activation_requires_authoritative_tenant_and_job_scope_resolution` (next_action contract regression only)" in text - assert "`test_activation_requires_authoritative_panel_actor_separation` (next_action contract regression only)" in text - assert "tenant-scope activation regression" not in text - assert "tenant/panel activation regressions" not in text + assert "The active PR now implements an executable activation orchestration boundary" in text + assert "`activate_structured_interview_plan(...)` calls an injected `StructuredInterviewActivationAuthority`" in text + assert "`test_activation_executes_authority_and_returns_immutable_human_receipt`" in text + assert "`test_authority_rejection_blocks_activation`" in text + assert "`test_activation_rejects_authority_evidence_for_other_scope`" in text + assert "A concrete production adapter remains responsible" in text + assert "do **not** prove that a particular deployed adapter already performs database/API resolution correctly" in text + assert "No host activation path is implemented in this slice." not in text + assert "(next_action contract regression only)" not in text From 4e6d294f4e5af84e0937a60dab8f9239ee43a6a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:28:29 -0700 Subject: [PATCH 066/216] fix(interview-plan): remove unused receipt import --- packages/interview-plan/tests/test_activation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index 20ba10ca8..c1fee3897 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -7,7 +7,6 @@ import pytest from orgmetra_interview_plan import ( - StructuredInterviewActivationReceipt, StructuredInterviewActivationVerification, activate_structured_interview_plan, build_structured_interview_plan, From 09f88be6616ad0b3b49b854798dc5d62b78a9542 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:28:45 -0700 Subject: [PATCH 067/216] fix(interview-plan): remove protocol no-op expression --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 9987c9bf0..3337f7b98 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -51,7 +51,7 @@ def verify_activation( approving_actor_reference: str, ) -> StructuredInterviewActivationVerification: """Return exact-scope evidence only after authoritative checks succeed.""" - ... + pass @dataclass(frozen=True, slots=True, repr=False) From 867cb118eaeaa8d886b3aed27b0e93ed402eb771 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:02:21 -0700 Subject: [PATCH 068/216] test(interview-plan): reject pre-generation approval evidence --- packages/interview-plan/tests/test_activation.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index c1fee3897..7ee14ead2 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -124,6 +124,20 @@ def test_activation_executes_authority_and_returns_immutable_human_receipt(): assert repr(receipt) == "StructuredInterviewActivationReceipt()" +def test_activation_rejects_approval_before_plan_generation(): + """Reject audit evidence claiming a plan was approved before that plan existed.""" + candidate_plan = plan() + authority = AllowingAuthority(verification_for(candidate_plan)) + + with pytest.raises(ValueError, match="approved_at must not precede plan generated_at"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=datetime(2026, 8, 21, 4, 29, 59, tzinfo=timezone.utc), + ) + + def test_authority_rejection_blocks_activation(): """Propagate authoritative rejection so no activation receipt can be manufactured.""" with pytest.raises(PermissionError, match="authoritative activation checks failed"): From b3b5542bddc162e82f134b10031af606041a0c5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:03:16 -0700 Subject: [PATCH 069/216] fix(interview-plan): enforce activation time ordering --- .../src/orgmetra_interview_plan/activation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 3337f7b98..30f659494 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -10,6 +10,7 @@ from __future__ import annotations from dataclasses import dataclass +from datetime import datetime from hashlib import sha256 import json from typing import Protocol @@ -137,7 +138,7 @@ def activate_structured_interview_plan( plan: StructuredInterviewPlan, authority: StructuredInterviewActivationAuthority, approving_actor_reference: str, - approved_at: object, + approved_at: datetime, ) -> StructuredInterviewActivationReceipt: """Activate one exact plan only after authoritative host verification succeeds. @@ -147,6 +148,9 @@ def activate_structured_interview_plan( value-minimized immutable human-approval receipt only for the exact verified scope. """ + _canonical_timestamp(approved_at) + if approved_at < plan.generated_at: + raise ValueError("approved_at must not precede plan generated_at") _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") verification = authority.verify_activation( plan=plan, From af5d7b2c02cf03f42b0c0a14f3680c10b076f186 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:04:16 -0700 Subject: [PATCH 070/216] docs(interview-plan): document activation chronology guard --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index d87a868cb..8be75824c 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,7 +10,7 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, and training checks pass. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. +`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, and training checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. From eddc92bf059b2ecb7f286754325b801e93a2042a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:04:24 -0700 Subject: [PATCH 071/216] docs(interview-plan): record temporal integrity repair --- packages/interview-plan/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 63304127b..e82293acf 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -16,6 +16,7 @@ - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. - Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, and approving actor before a receipt can exist. +- Validate `approved_at` before authoritative activation work and reject approval evidence that predates the reviewed plan's `generated_at`, preventing impossible audit chronology from reaching the host authority. ### Security and privacy From d4afadf64cd7c023f3a7361dae72a1d87c3a8d8c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:04:40 -0700 Subject: [PATCH 072/216] docs(interview-plan): trace activation chronology invariant --- docs/traceability/structured-interview-plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 294edca4c..5924cf4d6 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -15,6 +15,7 @@ | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state | `test_activation_executes_authority_and_returns_immutable_human_receipt` plus direct receipt mutation failures | +| Activation audit chronology cannot precede the reviewed plan | timezone-aware `approved_at` is validated before host authority execution and must be greater than or equal to the exact plan `generated_at` | `test_activation_rejects_approval_before_plan_generation` plus normal successful activation coverage | | Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | | Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, and `dataclasses.replace(...)` bypass regressions | @@ -26,9 +27,9 @@ ## Evidence boundary -The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` calls an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. +The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then calls an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, and training. The protocol contract requires such an adapter to raise rather than return verification evidence when any of those checks fails. The current tests prove the orchestration fail-closure and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, and training. The protocol contract requires such an adapter to raise rather than return verification evidence when any of those checks fails. The current tests prove the orchestration fail-closure, approval-time ordering, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 6e6cc4f6b9c1356773cb3d89a52bcbf4bdfcadba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:05:08 -0700 Subject: [PATCH 073/216] test(interview-plan): prove temporal guard precedes authority --- packages/interview-plan/tests/test_activation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index 7ee14ead2..029f760e1 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -137,6 +137,8 @@ def test_activation_rejects_approval_before_plan_generation(): approved_at=datetime(2026, 8, 21, 4, 29, 59, tzinfo=timezone.utc), ) + assert authority.calls == [] + def test_authority_rejection_blocks_activation(): """Propagate authoritative rejection so no activation receipt can be manufactured.""" From d641400a5e4a13be61b7d3920354efd09bb2a476 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:26:39 -0700 Subject: [PATCH 074/216] test(interview): reject unvalidated activation plan objects --- .../tests/test_activation_plan_type.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 packages/interview-plan/tests/test_activation_plan_type.py diff --git a/packages/interview-plan/tests/test_activation_plan_type.py b/packages/interview-plan/tests/test_activation_plan_type.py new file mode 100644 index 000000000..2d6cae93b --- /dev/null +++ b/packages/interview-plan/tests/test_activation_plan_type.py @@ -0,0 +1,64 @@ +"""Regression coverage for the structured-interview activation plan type boundary.""" + +from datetime import datetime, timezone + +import pytest + +from orgmetra_interview_plan import ( + StructuredInterviewActivationVerification, + activate_structured_interview_plan, +) + +TENANT = "10000000-0000-7000-8000-000000000001" +INTERVIEW_PLAN = "interview_plan:11111111-1111-4111-8111-111111111111" +APPROVER = "actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd" +AUTHORITY_EVIDENCE = "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" +PLAN_DIGEST = "a" * 64 +AUTHORITY_DIGEST = "b" * 64 + + +class DuckTypedPlan: + """Mimic trusted plan fields without ever passing StructuredInterviewPlan validation.""" + + tenant_record_id = TENANT + interview_plan_reference = INTERVIEW_PLAN + generated_at = datetime(2026, 8, 21, 4, 30, tzinfo=timezone.utc) + + def sha256_digest(self) -> str: + """Return a plausible digest so the old duck-typed boundary can be exercised.""" + return PLAN_DIGEST + + +class RecordingAuthority: + """Return internally consistent evidence while recording whether authority work ran.""" + + def __init__(self) -> None: + """Initialize the authority call counter.""" + self.calls = 0 + + def verify_activation(self, *, plan, approving_actor_reference): + """Return evidence matching whatever plan object the boundary supplied.""" + self.calls += 1 + return StructuredInterviewActivationVerification( + tenant_record_id=plan.tenant_record_id, + interview_plan_reference=plan.interview_plan_reference, + plan_digest=plan.sha256_digest(), + approving_actor_reference=approving_actor_reference, + authority_evidence_reference=AUTHORITY_EVIDENCE, + authority_evidence_digest=AUTHORITY_DIGEST, + ) + + +def test_activation_rejects_duck_typed_plan_before_authority_work(): + """Never let an unvalidated plan-shaped object reach the authoritative adapter.""" + authority = RecordingAuthority() + + with pytest.raises(TypeError, match="plan must be a StructuredInterviewPlan"): + activate_structured_interview_plan( + plan=DuckTypedPlan(), + authority=authority, + approving_actor_reference=APPROVER, + approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), + ) + + assert authority.calls == 0 From 02927900810eb50d2c1290f907f6d24d2507ed98 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:26:58 -0700 Subject: [PATCH 075/216] fix(interview): require validated activation plan type --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 30f659494..340b1acce 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -148,6 +148,8 @@ def activate_structured_interview_plan( value-minimized immutable human-approval receipt only for the exact verified scope. """ + if type(plan) is not StructuredInterviewPlan: + raise TypeError("plan must be a StructuredInterviewPlan") _canonical_timestamp(approved_at) if approved_at < plan.generated_at: raise ValueError("approved_at must not precede plan generated_at") From 049205d8340a53926bddd8789f5725abcc22fd35 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:28:14 -0700 Subject: [PATCH 076/216] docs(interview): document activation plan type boundary --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 8be75824c..cf1bf64d6 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,7 +10,7 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, and training checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. +`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type, so a duck-typed or subclassed plan-shaped object cannot bypass plan construction invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, and training checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. From a2135d6cf97bccbb4557fac7e445c7ba5fcb494e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:28:22 -0700 Subject: [PATCH 077/216] docs(interview): record activation plan type repair --- packages/interview-plan/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index e82293acf..cddc54a8c 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -17,6 +17,7 @@ - Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, and approving actor before a receipt can exist. - Validate `approved_at` before authoritative activation work and reject approval evidence that predates the reviewed plan's `generated_at`, preventing impossible audit chronology from reaching the host authority. +- Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. ### Security and privacy From c8cfe7665a12b682328901919f95a05c6227718c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:29:45 -0700 Subject: [PATCH 078/216] docs(interview): trace validated activation plan boundary --- docs/traceability/structured-interview-plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 5924cf4d6..510995a1e 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -15,6 +15,7 @@ | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state | `test_activation_executes_authority_and_returns_immutable_human_receipt` plus direct receipt mutation failures | +| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type before timestamp checks or authority work; duck-typed and subclassed plan-shaped objects cannot bypass plan construction invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` proves rejection occurs before the authority adapter is invoked | | Activation audit chronology cannot precede the reviewed plan | timezone-aware `approved_at` is validated before host authority execution and must be greater than or equal to the exact plan `generated_at` | `test_activation_rejects_approval_before_plan_generation` plus normal successful activation coverage | | Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | @@ -27,9 +28,9 @@ ## Evidence boundary -The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then calls an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. +The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then calls an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, and training. The protocol contract requires such an adapter to raise rather than return verification evidence when any of those checks fails. The current tests prove the orchestration fail-closure, approval-time ordering, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, and training. The protocol contract requires such an adapter to raise rather than return verification evidence when any of those checks fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, approval-time ordering, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 1f30abbd076a2844d9c0fa6cf053555baadfc591 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:30:25 -0700 Subject: [PATCH 079/216] docs(interview): record exact activation plan type decision --- docs/adr/0015-governed-structured-interview-plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 228debfba..a45148528 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -28,7 +28,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. The injected host authority must return `StructuredInterviewActivationVerification` only after all authoritative checks succeed and must raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The injected host authority must return `StructuredInterviewActivationVerification` only after all authoritative checks succeed and must raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. @@ -39,7 +39,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Positive - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. -- Runtime activation orchestration fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, or returns evidence bound to another tenant/plan/digest/actor. +- Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, or returns evidence bound to another tenant/plan/digest/actor. - Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. - Candidate PII and assessment values remain outside the planning and activation artifacts. - Packet-owned trust references reject UUIDv1/time-node-bearing suffixes and value-bearing metadata without making the leaf package incompatible with authoritative Orgmetra tenant UUIDs. From 58f84fdbec23efa0b0b2e332198b656ab5eafb7f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:55:33 -0700 Subject: [PATCH 080/216] fix(interview-plan): remove unreachable protocol statement --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 340b1acce..86422ef52 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -52,7 +52,6 @@ def verify_activation( approving_actor_reference: str, ) -> StructuredInterviewActivationVerification: """Return exact-scope evidence only after authoritative checks succeed.""" - pass @dataclass(frozen=True, slots=True, repr=False) From 656a374e0198f884fbc306287f450b58b5064b84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:31:09 -0700 Subject: [PATCH 081/216] test(interview-plan): bind approval time to authority --- .../tests/test_activation_approval_time.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 packages/interview-plan/tests/test_activation_approval_time.py diff --git a/packages/interview-plan/tests/test_activation_approval_time.py b/packages/interview-plan/tests/test_activation_approval_time.py new file mode 100644 index 000000000..6ec2c5f92 --- /dev/null +++ b/packages/interview-plan/tests/test_activation_approval_time.py @@ -0,0 +1,81 @@ +"""Regression for authoritative structured-interview approval-time binding.""" + +from datetime import datetime, timezone + +from orgmetra_interview_plan import ( + StructuredInterviewActivationVerification, + activate_structured_interview_plan, + build_structured_interview_plan, +) + +TENANT = "10000000-0000-7000-8000-000000000001" +INTERVIEW_PLAN = "interview_plan:11111111-1111-4111-8111-111111111111" +APPROVER = "actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd" +AUTHORITY_EVIDENCE = "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" +APPROVED_AT = datetime(2026, 8, 21, 5, 0, 0, 123456, tzinfo=timezone.utc) + + +def _plan(): + """Return one valid immutable interview plan for the approval-time boundary.""" + return build_structured_interview_plan( + tenant_record_id=TENANT, + interview_plan_reference=INTERVIEW_PLAN, + requisition_reference="requisition:22222222-2222-4222-8222-222222222222", + job_profile_reference="job_profile:33333333-3333-4333-8333-333333333333", + job_analysis_reference="job_analysis:44444444-4444-4444-8444-444444444444", + job_analysis_digest="a" * 64, + question_set_reference="question_set:55555555-5555-4555-8555-555555555555", + question_set_digest="b" * 64, + question_competency_map_reference=( + "question_competency_map:66666666-6666-4666-8666-666666666666" + ), + question_competency_map_digest="c" * 64, + rating_anchor_reference="rating_anchor:77777777-7777-4777-8777-777777777777", + rating_anchor_digest="d" * 64, + competency_references=("competency:88888888-8888-4888-8888-888888888888",), + panel_actor_references=( + "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc", + ), + question_count=2, + purpose_code="structured_interview_plan", + reason_code="approved_requisition_interview", + generated_at=datetime(2026, 8, 21, 4, 30, tzinfo=timezone.utc), + ) + + +class TimestampRecordingAuthority: + """Require the candidate approval instant to cross the authoritative boundary.""" + + def __init__(self, candidate_plan) -> None: + """Capture the plan and initialize the authoritative-call audit list.""" + self.candidate_plan = candidate_plan + self.calls = [] + + def verify_activation(self, *, plan, approving_actor_reference, approved_at): + """Record the exact approval instant before returning matching evidence.""" + self.calls.append((plan, approving_actor_reference, approved_at)) + return StructuredInterviewActivationVerification( + tenant_record_id=plan.tenant_record_id, + interview_plan_reference=plan.interview_plan_reference, + plan_digest=plan.sha256_digest(), + approving_actor_reference=approving_actor_reference, + authority_evidence_reference=AUTHORITY_EVIDENCE, + authority_evidence_digest="e" * 64, + ) + + +def test_activation_sends_approval_time_through_authoritative_verification(): + """Do not mint approved evidence from a timestamp the authority never reviewed.""" + candidate_plan = _plan() + authority = TimestampRecordingAuthority(candidate_plan) + + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + assert authority.calls == [(candidate_plan, APPROVER, APPROVED_AT)] + assert "2026-08-21T05:00:00.123456Z" in receipt.canonical_json() From e516cb51e3d850052130c782ac0281408ad91bc9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:31:34 -0700 Subject: [PATCH 082/216] fix(interview-plan): verify approval time at authority boundary --- .../src/orgmetra_interview_plan/activation.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 86422ef52..18401a5b2 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -3,9 +3,9 @@ The authority adapter is owned by the Orgmetra host. It MUST return verification only after re-resolving the plan inside the exact tenant, proving the requisition-to-Job-to-job-analysis binding, verifying question/mapping/rating -provenance, resolving distinct panel actors, and confirming panel eligibility -and training. Any failed authoritative check must raise instead of returning -verification evidence. +provenance, resolving distinct panel actors, confirming panel eligibility and +training, and reviewing the exact approval instant carried into the receipt. +Any failed authoritative check must raise instead of returning verification evidence. """ from __future__ import annotations @@ -50,8 +50,9 @@ def verify_activation( *, plan: StructuredInterviewPlan, approving_actor_reference: str, + approved_at: datetime, ) -> StructuredInterviewActivationVerification: - """Return exact-scope evidence only after authoritative checks succeed.""" + """Return exact-scope evidence only after reviewing the exact approval instant.""" @dataclass(frozen=True, slots=True, repr=False) @@ -142,10 +143,10 @@ def activate_structured_interview_plan( """Activate one exact plan only after authoritative host verification succeeds. The authority implementation is responsible for the actual tenant-scoped - re-resolution and relationship/provenance/panel checks. This function rejects - a non-contract result or evidence bound to a different plan/actor and emits a - value-minimized immutable human-approval receipt only for the exact verified - scope. + re-resolution, relationship/provenance/panel checks, and review of the exact + approval instant. This function rejects a non-contract result or evidence bound + to a different plan/actor and emits a value-minimized immutable human-approval + receipt only for the exact verified scope. """ if type(plan) is not StructuredInterviewPlan: raise TypeError("plan must be a StructuredInterviewPlan") @@ -156,6 +157,7 @@ def activate_structured_interview_plan( verification = authority.verify_activation( plan=plan, approving_actor_reference=approving_actor_reference, + approved_at=approved_at, ) if not isinstance(verification, StructuredInterviewActivationVerification): raise TypeError("authority must return StructuredInterviewActivationVerification") From 33ecde86e2a846671542005c8802a37e5e4f4c93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:32:11 -0700 Subject: [PATCH 083/216] test(interview-plan): adapt authority fixtures to approval time --- packages/interview-plan/tests/test_activation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index 029f760e1..a2de37df2 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -80,8 +80,9 @@ def __init__(self, verification): self.verification = verification self.calls = [] - def verify_activation(self, *, plan, approving_actor_reference): - """Record the exact requested plan/actor and return authoritative evidence.""" + def verify_activation(self, *, plan, approving_actor_reference, approved_at): + """Review the approval instant, record plan/actor scope, and return evidence.""" + assert approved_at == APPROVED_AT self.calls.append((plan, approving_actor_reference)) return self.verification @@ -89,7 +90,7 @@ def verify_activation(self, *, plan, approving_actor_reference): class RejectingAuthority: """Host fixture representing a failed tenant/job/provenance/panel verification.""" - def verify_activation(self, *, plan, approving_actor_reference): + def verify_activation(self, *, plan, approving_actor_reference, approved_at): """Fail closed instead of producing activation evidence.""" raise PermissionError("authoritative activation checks failed") @@ -157,7 +158,7 @@ def test_activation_rejects_non_verification_result(): class WrongAuthority: """Fixture that violates the published authority return type.""" - def verify_activation(self, *, plan, approving_actor_reference): + def verify_activation(self, *, plan, approving_actor_reference, approved_at): """Return a non-contract object to prove type fail-closure.""" return object() From 260ace0fcb192afc05fbf174a686c085ba48f35a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:32:23 -0700 Subject: [PATCH 084/216] test(interview-plan): align authority fixture contract --- packages/interview-plan/tests/test_activation_plan_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/tests/test_activation_plan_type.py b/packages/interview-plan/tests/test_activation_plan_type.py index 2d6cae93b..b1f7a4174 100644 --- a/packages/interview-plan/tests/test_activation_plan_type.py +++ b/packages/interview-plan/tests/test_activation_plan_type.py @@ -36,8 +36,8 @@ def __init__(self) -> None: """Initialize the authority call counter.""" self.calls = 0 - def verify_activation(self, *, plan, approving_actor_reference): - """Return evidence matching whatever plan object the boundary supplied.""" + def verify_activation(self, *, plan, approving_actor_reference, approved_at): + """Return evidence matching whatever validated activation request was supplied.""" self.calls += 1 return StructuredInterviewActivationVerification( tenant_record_id=plan.tenant_record_id, From 42a6a282770df71c8f7f032f8fdc8af760413d41 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:32:41 -0700 Subject: [PATCH 085/216] docs(interview-plan): bind approval instant to authority --- packages/interview-plan/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index cf1bf64d6..e950959c6 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,7 +10,7 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type, so a duck-typed or subclassed plan-shaped object cannot bypass plan construction invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, and training checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. +`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type, so a duck-typed or subclassed plan-shaped object cannot bypass plan construction invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, training, and approval-time checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. The same exact `approved_at` is then passed into `verify_activation(...)`; an adapter must review that instant as part of the authoritative approval and bind it into its verification evidence rather than allowing a caller-only timestamp to be minted into the receipt. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. @@ -20,4 +20,4 @@ The plan object itself remains pending human review: `human_confirmation_require For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries and preserve immutable audit/outbox evidence for any later authoritative write. +This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries, bind the exact approved instant into authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. From 680f97736326f1f410ca824405abe543f762bd29 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:33:06 -0700 Subject: [PATCH 086/216] docs(interview-plan): trace authoritative approval-time binding --- docs/traceability/structured-interview-plan.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 510995a1e..6f0033ef8 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -14,9 +14,9 @@ | Evidence revisions remain distinguishable and immutable | bounded positive plan `evidence_version` in canonical JSON; activation receipt separately binds the exact plan digest and its own bounded positive evidence version | plan evidence-version regressions plus activation receipt canonical/digest and direct-replacement fail-closed regressions | | Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions plus fail-closed authority rejection path | -| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state | `test_activation_executes_authority_and_returns_immutable_human_receipt` plus direct receipt mutation failures | +| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus direct receipt mutation failures | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type before timestamp checks or authority work; duck-typed and subclassed plan-shaped objects cannot bypass plan construction invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` proves rejection occurs before the authority adapter is invoked | -| Activation audit chronology cannot precede the reviewed plan | timezone-aware `approved_at` is validated before host authority execution and must be greater than or equal to the exact plan `generated_at` | `test_activation_rejects_approval_before_plan_generation` plus normal successful activation coverage | +| Activation audit chronology cannot precede the reviewed plan or bypass the authority | timezone-aware `approved_at` is validated before host authority execution, must be greater than or equal to the exact plan `generated_at`, and the same instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` for authoritative review | `test_activation_rejects_approval_before_plan_generation`, `test_activation_sends_approval_time_through_authoritative_verification`, plus normal successful activation coverage | | Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | | Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, and `dataclasses.replace(...)` bypass regressions | @@ -28,9 +28,9 @@ ## Evidence boundary -The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then calls an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. +The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then passes that exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents the receipt from carrying an approval timestamp that the authority adapter was never given an opportunity to review. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, and training. The protocol contract requires such an adapter to raise rather than return verification evidence when any of those checks fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, approval-time ordering, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, approval-time ordering, approval-time passage into the authority, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 4ab7b82ef1fb7b9882c5ca24cdb3d16b89ed410c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:33:40 -0700 Subject: [PATCH 087/216] docs(interview-plan): record authority-bound approval time --- docs/adr/0015-governed-structured-interview-plan.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index a45148528..0deb98d4b 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. ## Decision @@ -28,7 +28,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The injected host authority must return `StructuredInterviewActivationVerification` only after all authoritative checks succeed and must raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The activation boundary validates a timezone-aware `approved_at`, rejects impossible chronology before authority work, and then supplies that exact instant to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks; it must bind the reviewed instant into its immutable verification evidence and raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. @@ -40,6 +40,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, or returns evidence bound to another tenant/plan/digest/actor. +- The exact approval instant now crosses the authoritative adapter boundary, so approved receipt chronology cannot be created from a timestamp the authority never reviewed. - Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. - Candidate PII and assessment values remain outside the planning and activation artifacts. - Packet-owned trust references reject UUIDv1/time-node-bearing suffixes and value-bearing metadata without making the leaf package incompatible with authoritative Orgmetra tenant UUIDs. @@ -50,7 +51,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Costs and constraints - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. -- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence. +- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence and must bind the supplied approval instant into their immutable authority evidence. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. From 11838bcc88fd8afcc37ab0cbe3c6d8c5d8f19344 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 19:33:53 -0700 Subject: [PATCH 088/216] docs(interview-plan): record approval-time verification repair --- packages/interview-plan/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index cddc54a8c..3ebb52fe4 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -16,7 +16,7 @@ - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. - Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, and approving actor before a receipt can exist. -- Validate `approved_at` before authoritative activation work and reject approval evidence that predates the reviewed plan's `generated_at`, preventing impossible audit chronology from reaching the host authority. +- Validate `approved_at` before authoritative activation work, reject approval evidence that predates the reviewed plan's `generated_at`, and pass that exact instant into `StructuredInterviewActivationAuthority.verify_activation(...)` so receipt chronology cannot be minted from a timestamp the authoritative adapter never reviewed. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. ### Security and privacy From a4f5c55af7131e69f876155b0c93fb18d1f5fdc0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:12:43 -0700 Subject: [PATCH 089/216] test(interview-plan): reject recorded-time subclasses --- .../tests/test_temporal_evidence_integrity.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 packages/interview-plan/tests/test_temporal_evidence_integrity.py diff --git a/packages/interview-plan/tests/test_temporal_evidence_integrity.py b/packages/interview-plan/tests/test_temporal_evidence_integrity.py new file mode 100644 index 000000000..5ef50fb72 --- /dev/null +++ b/packages/interview-plan/tests/test_temporal_evidence_integrity.py @@ -0,0 +1,60 @@ +"""Regression coverage for interview-plan recorded-time evidence integrity.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest + +from orgmetra_interview_plan import build_structured_interview_plan + + +class ForgedDateTime(datetime): + """Datetime subclass able to forge canonical recorded-time 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 plan evidence.""" + return "2099-12-31T23:59:59+00:00" + + +def valid_kwargs() -> dict[str, object]: + """Return one otherwise valid structured-interview plan input.""" + return { + "tenant_record_id": "12345678-1234-4234-8234-123456789abc", + "interview_plan_reference": "interview_plan:11111111-1111-4111-8111-111111111111", + "requisition_reference": "requisition:22222222-2222-4222-8222-222222222222", + "job_profile_reference": "job_profile:33333333-3333-4333-8333-333333333333", + "job_analysis_reference": "job_analysis:44444444-4444-4444-8444-444444444444", + "job_analysis_digest": "a" * 64, + "question_set_reference": "question_set:55555555-5555-4555-8555-555555555555", + "question_set_digest": "b" * 64, + "question_competency_map_reference": "question_competency_map:66666666-6666-4666-8666-666666666666", + "question_competency_map_digest": "d" * 64, + "rating_anchor_reference": "rating_anchor:77777777-7777-4777-8777-777777777777", + "rating_anchor_digest": "c" * 64, + "competency_references": ( + "competency:88888888-8888-4888-8888-888888888888", + "competency:99999999-9999-4999-8999-999999999999", + ), + "panel_actor_references": ( + "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc", + ), + "question_count": 4, + "purpose_code": "structured_interview_plan", + "reason_code": "approved_requisition_interview", + "generated_at": datetime(2026, 8, 21, 4, 30, tzinfo=timezone.utc), + } + + +def test_rejects_datetime_subclasses_that_can_forge_recorded_time_evidence() -> None: + """Canonical audit evidence must not call caller-overridable datetime methods.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = ForgedDateTime(2026, 8, 21, 4, 30, tzinfo=timezone.utc) + + with pytest.raises(ValueError, match="generated_at"): + build_structured_interview_plan(**kwargs) From 6afb31806f959600ead748a7346cb39a20de9691 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:13:12 -0700 Subject: [PATCH 090/216] fix(interview-plan): require exact recorded-time type --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index dfa1dac0e..c90e589ba 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -67,8 +67,8 @@ def _validate_digest(value: str, field_name: str) -> None: def _canonical_timestamp(value: datetime) -> str: """Render an aware instant as precision-preserving UTC RFC 3339 text.""" - if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None: - raise ValueError("generated_at must be timezone-aware") + if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: + raise ValueError("generated_at must be an exact timezone-aware datetime") return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") From 138073d7e82c6f4c8d9c5d93c485da6e99250b11 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 21:30:13 -0700 Subject: [PATCH 091/216] test(interview-plan): align traceability activation regression --- packages/interview-plan/tests/test_traceability_scope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py index ea84b2d2a..dc9c4556c 100644 --- a/packages/interview-plan/tests/test_traceability_scope.py +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -13,7 +13,7 @@ def test_traceability_matches_executable_activation_boundary() -> None: text = TRACEABILITY.read_text(encoding="utf-8") assert "The active PR now implements an executable activation orchestration boundary" in text - assert "`activate_structured_interview_plan(...)` calls an injected `StructuredInterviewActivationAuthority`" in text + assert "into an injected `StructuredInterviewActivationAuthority`" in text assert "`test_activation_executes_authority_and_returns_immutable_human_receipt`" in text assert "`test_authority_rejection_blocks_activation`" in text assert "`test_activation_rejects_authority_evidence_for_other_scope`" in text From 1cb7dc5d725ed94c4004f914bcfbceceaea21402 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:03:45 -0700 Subject: [PATCH 092/216] test(interview-plan): reject forged string evidence types --- .../test_string_runtime_evidence_integrity.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 packages/interview-plan/tests/test_string_runtime_evidence_integrity.py diff --git a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py new file mode 100644 index 000000000..e22b90a45 --- /dev/null +++ b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py @@ -0,0 +1,92 @@ +"""Regression coverage for string-subclass evidence-boundary integrity.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest + +from orgmetra_interview_plan import build_structured_interview_plan + + +class ForgedReference(str): + """String subclass that forges namespace/suffix validation while keeping hostile text.""" + + def startswith(self, prefix, *args): # type: ignore[no-untyped-def] + """Pretend the hostile value carries every requested namespace.""" + return True + + def __getitem__(self, key): # type: ignore[no-untyped-def] + """Return a valid UUIDv4 only when validation slices the reference suffix.""" + if isinstance(key, slice): + return "11111111-1111-4111-8111-111111111111" + return super().__getitem__(key) + + +class ForgedTenantUUIDText(str): + """String subclass that forges UUID parsing and canonical-equality checks.""" + + def replace(self, old, new, *args): # type: ignore[no-untyped-def] + """Feed UUID() canonical text instead of the stored hostile tenant text.""" + canonical = "12345678-1234-4234-8234-123456789abc" + return canonical.replace(old, new, *args) + + def __eq__(self, other): # type: ignore[no-untyped-def] + """Claim canonical equality while keeping the original hostile payload.""" + if other is None: + return False + return True + + def __ne__(self, other): # type: ignore[no-untyped-def] + """Keep UUID constructor sentinel checks working while defeating canonicality.""" + if other is None: + return True + return False + + +def valid_kwargs() -> dict[str, object]: + """Return one otherwise valid structured-interview plan input.""" + return { + "tenant_record_id": "12345678-1234-4234-8234-123456789abc", + "interview_plan_reference": "interview_plan:11111111-1111-4111-8111-111111111111", + "requisition_reference": "requisition:22222222-2222-4222-8222-222222222222", + "job_profile_reference": "job_profile:33333333-3333-4333-8333-333333333333", + "job_analysis_reference": "job_analysis:44444444-4444-4444-8444-444444444444", + "job_analysis_digest": "a" * 64, + "question_set_reference": "question_set:55555555-5555-4555-8555-555555555555", + "question_set_digest": "b" * 64, + "question_competency_map_reference": "question_competency_map:66666666-6666-4666-8666-666666666666", + "question_competency_map_digest": "d" * 64, + "rating_anchor_reference": "rating_anchor:77777777-7777-4777-8777-777777777777", + "rating_anchor_digest": "c" * 64, + "competency_references": ( + "competency:88888888-8888-4888-8888-888888888888", + "competency:99999999-9999-4999-8999-999999999999", + ), + "panel_actor_references": ( + "actor:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "actor:cccccccc-cccc-4ccc-8ccc-cccccccccccc", + ), + "question_count": 4, + "purpose_code": "structured_interview_plan", + "reason_code": "approved_requisition_interview", + "generated_at": datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), + } + + +def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: + """Canonical evidence must never retain text that only pretended to match a namespace.""" + kwargs = valid_kwargs() + kwargs["interview_plan_reference"] = ForgedReference("attacker-controlled-reference-data") + + with pytest.raises(ValueError, match="interview_plan_reference"): + build_structured_interview_plan(**kwargs) + + +def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: + """Authoritative tenant identity must be exact built-in text before UUID parsing.""" + kwargs = valid_kwargs() + kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") + + with pytest.raises(ValueError, match="tenant_record_id"): + build_structured_interview_plan(**kwargs) From 9cadc9fd08c7bb75ce751c7ddf1d3ddf3734e1ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:04:24 -0700 Subject: [PATCH 093/216] fix(interview-plan): require exact string evidence types --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index c90e589ba..3c8fcadf4 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -31,6 +31,8 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: """Require canonical non-sentinel UUID text owned by the authoritative HRIS.""" + if type(value) is not str: + raise ValueError(f"{field_name} must be canonical UUID text") try: parsed = UUID(value) except (ValueError, AttributeError, TypeError) as exc: @@ -48,7 +50,7 @@ def _validate_code(value: str, field_name: str) -> None: def _validate_reference(value: str, prefix: str, field_name: str) -> None: """Require the expected namespace plus a canonical non-sentinel UUIDv4 suffix.""" namespace = f"{prefix}:" - if not isinstance(value, str) or len(value) > 160 or not value.startswith(namespace): + if type(value) is not str or len(value) > 160 or not value.startswith(namespace): raise ValueError(f"{field_name} must be an opaque {prefix}: reference") suffix = value[len(namespace) :] try: From c9447220dbebb2aef80a5d4d6a9d6799d2424a09 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:19:03 -0700 Subject: [PATCH 094/216] test(interview-plan): reject forged governance codes --- .../test_string_runtime_evidence_integrity.py | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py index e22b90a45..b43db0a88 100644 --- a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py +++ b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py @@ -32,18 +32,29 @@ def replace(self, old, new, *args): # type: ignore[no-untyped-def] return canonical.replace(old, new, *args) def __eq__(self, other): # type: ignore[no-untyped-def] - """Claim canonical equality while keeping the original hostile payload.""" if other is None: return False return True def __ne__(self, other): # type: ignore[no-untyped-def] - """Keep UUID constructor sentinel checks working while defeating canonicality.""" if other is None: return True return False +class ForgedGovernanceCode(str): + """String subclass that forges fixed-code equality and allow-list membership.""" + + def __eq__(self, other): # type: ignore[no-untyped-def] + return True + + def __ne__(self, other): # type: ignore[no-untyped-def] + return False + + def __hash__(self) -> int: + return hash("approved_requisition_interview") + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid structured-interview plan input.""" return { @@ -75,18 +86,28 @@ def valid_kwargs() -> dict[str, object]: def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: - """Canonical evidence must never retain text that only pretended to match a namespace.""" kwargs = valid_kwargs() kwargs["interview_plan_reference"] = ForgedReference("attacker-controlled-reference-data") - with pytest.raises(ValueError, match="interview_plan_reference"): build_structured_interview_plan(**kwargs) def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: - """Authoritative tenant identity must be exact built-in text before UUID parsing.""" kwargs = valid_kwargs() kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") - with pytest.raises(ValueError, match="tenant_record_id"): build_structured_interview_plan(**kwargs) + + +def test_rejects_purpose_code_string_subclass_that_can_forge_fixed_code_check() -> None: + kwargs = valid_kwargs() + kwargs["purpose_code"] = ForgedGovernanceCode("attacker_controlled_purpose") + with pytest.raises(ValueError, match="purpose_code"): + build_structured_interview_plan(**kwargs) + + +def test_rejects_reason_code_string_subclass_that_can_forge_allow_list_check() -> None: + kwargs = valid_kwargs() + kwargs["reason_code"] = ForgedGovernanceCode("attacker_controlled_reason") + with pytest.raises(ValueError, match="reason_code"): + build_structured_interview_plan(**kwargs) From 58eba76477aa35e987cdf2873c7b7925bc5182ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:19:34 -0700 Subject: [PATCH 095/216] fix(interview-plan): require exact governance-code text --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 3c8fcadf4..8f39ff624 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -42,8 +42,8 @@ def _validate_operational_uuid(value: str, field_name: str) -> None: def _validate_code(value: str, field_name: str) -> None: - """Require a bounded descriptive lower snake_case governance code.""" - if not isinstance(value, str) or len(value) > 64 or not _CODE_PATTERN.fullmatch(value): + """Require exact bounded descriptive lower snake_case governance text.""" + 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") From e1a20351b7faf6d8dc731c5accbf979f65b90532 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:31:05 -0700 Subject: [PATCH 096/216] test(interview-plan): document runtime integrity regressions --- .../tests/test_string_runtime_evidence_integrity.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py index b43db0a88..7c2a5637f 100644 --- a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py +++ b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py @@ -32,11 +32,13 @@ def replace(self, old, new, *args): # type: ignore[no-untyped-def] return canonical.replace(old, new, *args) def __eq__(self, other): # type: ignore[no-untyped-def] + """Pretend hostile tenant text equals every non-null comparison target.""" if other is None: return False return True def __ne__(self, other): # type: ignore[no-untyped-def] + """Pretend hostile tenant text differs only from a null comparison target.""" if other is None: return True return False @@ -46,12 +48,15 @@ class ForgedGovernanceCode(str): """String subclass that forges fixed-code equality and allow-list membership.""" def __eq__(self, other): # type: ignore[no-untyped-def] + """Pretend hostile governance text equals every comparison target.""" return True def __ne__(self, other): # type: ignore[no-untyped-def] + """Pretend hostile governance text never differs from a comparison target.""" return False def __hash__(self) -> int: + """Return the hash of an allowed reason code to probe set membership defenses.""" return hash("approved_requisition_interview") @@ -86,6 +91,7 @@ def valid_kwargs() -> dict[str, object]: def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() -> None: + """Reject reference subclasses before forged namespace behavior can affect evidence.""" kwargs = valid_kwargs() kwargs["interview_plan_reference"] = ForgedReference("attacker-controlled-reference-data") with pytest.raises(ValueError, match="interview_plan_reference"): @@ -93,6 +99,7 @@ def test_rejects_reference_string_subclass_that_can_forge_namespace_validation() def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None: + """Reject tenant-text subclasses before forged UUID behavior can affect identity evidence.""" kwargs = valid_kwargs() kwargs["tenant_record_id"] = ForgedTenantUUIDText("not-a-tenant-uuid") with pytest.raises(ValueError, match="tenant_record_id"): @@ -100,6 +107,7 @@ def test_rejects_tenant_string_subclass_that_can_forge_uuid_validation() -> None def test_rejects_purpose_code_string_subclass_that_can_forge_fixed_code_check() -> None: + """Reject purpose-code subclasses before forged equality can bypass the closed code.""" kwargs = valid_kwargs() kwargs["purpose_code"] = ForgedGovernanceCode("attacker_controlled_purpose") with pytest.raises(ValueError, match="purpose_code"): @@ -107,6 +115,7 @@ def test_rejects_purpose_code_string_subclass_that_can_forge_fixed_code_check() def test_rejects_reason_code_string_subclass_that_can_forge_allow_list_check() -> None: + """Reject reason-code subclasses before forged equality or hashing can bypass policy.""" kwargs = valid_kwargs() kwargs["reason_code"] = ForgedGovernanceCode("attacker_controlled_reason") with pytest.raises(ValueError, match="reason_code"): From 09a8fd4d09d162cffd6198784f00556231fb1d46 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:32:51 -0700 Subject: [PATCH 097/216] test(interview-plan): name invalid approval timestamp --- .../tests/test_temporal_evidence_integrity.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_temporal_evidence_integrity.py b/packages/interview-plan/tests/test_temporal_evidence_integrity.py index 5ef50fb72..f6ee32d61 100644 --- a/packages/interview-plan/tests/test_temporal_evidence_integrity.py +++ b/packages/interview-plan/tests/test_temporal_evidence_integrity.py @@ -6,7 +6,10 @@ import pytest -from orgmetra_interview_plan import build_structured_interview_plan +from orgmetra_interview_plan import ( + StructuredInterviewActivationReceipt, + build_structured_interview_plan, +) class ForgedDateTime(datetime): @@ -58,3 +61,17 @@ def test_rejects_datetime_subclasses_that_can_forge_recorded_time_evidence() -> with pytest.raises(ValueError, match="generated_at"): build_structured_interview_plan(**kwargs) + + +def test_activation_receipt_names_approved_at_when_recorded_time_is_invalid() -> None: + """Tell callers which approval timestamp must be repaired before activation can proceed.""" + with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): + StructuredInterviewActivationReceipt( + tenant_record_id="12345678-1234-4234-8234-123456789abc", + interview_plan_reference="interview_plan:11111111-1111-4111-8111-111111111111", + plan_digest="a" * 64, + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + authority_evidence_reference="activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", + authority_evidence_digest="e" * 64, + approved_at=datetime(2026, 8, 21, 5, 0), + ) From d4a6ed60352a78ee7bb77a8a789cddebe2d8183e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:33:19 -0700 Subject: [PATCH 098/216] refactor(interview-plan): make timestamp diagnostics field-aware --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 8f39ff624..2a4503373 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -67,10 +67,10 @@ def _validate_digest(value: str, field_name: str) -> None: raise ValueError(f"{field_name} must be lowercase SHA-256 hex") -def _canonical_timestamp(value: datetime) -> str: - """Render an aware instant as precision-preserving UTC RFC 3339 text.""" +def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> str: + """Render an aware instant as UTC RFC 3339 text with a field-specific error.""" if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: - raise ValueError("generated_at must be an exact timezone-aware datetime") + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") From f770a5f5ec102d40f22e4aa8cfaef2a71956e7eb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:33:40 -0700 Subject: [PATCH 099/216] fix(interview-plan): report approval timestamp failures precisely --- .../src/orgmetra_interview_plan/activation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 18401a5b2..c81135140 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -92,7 +92,7 @@ def __post_init__(self) -> None: "authority_evidence_reference", ) _validate_digest(self.authority_evidence_digest, "authority_evidence_digest") - _canonical_timestamp(self.approved_at) + _canonical_timestamp(self.approved_at, "approved_at") _validate_code(self.purpose_code, "purpose_code") if self.purpose_code != _PURPOSE_CODE: raise ValueError("purpose_code must remain structured_interview_activation") @@ -114,7 +114,7 @@ def canonical_json(self) -> str: """Return deterministic canonical JSON for immutable audit correlation.""" payload = { "activation_state": self.activation_state, - "approved_at": _canonical_timestamp(self.approved_at), + "approved_at": _canonical_timestamp(self.approved_at, "approved_at"), "approving_actor_reference": self.approving_actor_reference, "authority_evidence_digest": self.authority_evidence_digest, "authority_evidence_reference": self.authority_evidence_reference, @@ -150,7 +150,7 @@ def activate_structured_interview_plan( """ if type(plan) is not StructuredInterviewPlan: raise TypeError("plan must be a StructuredInterviewPlan") - _canonical_timestamp(approved_at) + _canonical_timestamp(approved_at, "approved_at") if approved_at < plan.generated_at: raise ValueError("approved_at must not precede plan generated_at") _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") From d26e5a749b5191c9329487781066f052ffa81397 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:00:25 -0700 Subject: [PATCH 100/216] test(interview-plan): require authoritative receipt issuance --- .../tests/test_receipt_issuance.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/interview-plan/tests/test_receipt_issuance.py diff --git a/packages/interview-plan/tests/test_receipt_issuance.py b/packages/interview-plan/tests/test_receipt_issuance.py new file mode 100644 index 000000000..1b29f4898 --- /dev/null +++ b/packages/interview-plan/tests/test_receipt_issuance.py @@ -0,0 +1,23 @@ +"""Regression tests for authoritative structured-interview receipt issuance.""" + +from datetime import datetime, timezone + +import pytest + +from orgmetra_interview_plan import StructuredInterviewActivationReceipt + + +def test_activation_receipt_cannot_be_minted_without_verified_factory_path(): + """Reject valid-looking approval evidence that never crossed the authority boundary.""" + with pytest.raises(TypeError, match="activate_structured_interview_plan"): + StructuredInterviewActivationReceipt( + tenant_record_id="10000000-0000-7000-8000-000000000001", + interview_plan_reference="interview_plan:11111111-1111-4111-8111-111111111111", + plan_digest="a" * 64, + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + authority_evidence_reference=( + "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" + ), + authority_evidence_digest="e" * 64, + approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), + ) From 68a977f1f0e763efce662a6d5770d0ecfea84283 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:00:47 -0700 Subject: [PATCH 101/216] fix(interview-plan): require authoritative receipt issuance --- .../src/orgmetra_interview_plan/activation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index c81135140..c35510cf7 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -9,7 +9,7 @@ """ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import datetime from hashlib import sha256 import json @@ -28,6 +28,7 @@ _REASON_CODE = "human_approved_plan_activation" _ACTIVATION_STATE = "approved_for_use" _MAX_EVIDENCE_VERSION = 2_147_483_647 +_ACTIVATION_RECEIPT_ISSUANCE_TOKEN = object() @dataclass(frozen=True, slots=True) @@ -71,9 +72,15 @@ class StructuredInterviewActivationReceipt: evidence_version: int = 1 human_confirmation: bool = True activation_state: str = _ACTIVATION_STATE + _issuance_token: object = field(default=None, repr=False, compare=False) def __post_init__(self) -> None: - """Reject forged, ambiguous, or weakened activation evidence.""" + """Reject forged, ambiguous, weakened, or non-authoritatively issued evidence.""" + if self._issuance_token is not _ACTIVATION_RECEIPT_ISSUANCE_TOKEN: + raise TypeError( + "StructuredInterviewActivationReceipt can only be issued by " + "activate_structured_interview_plan" + ) _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") _validate_reference( self.interview_plan_reference, @@ -192,4 +199,5 @@ def activate_structured_interview_plan( authority_evidence_reference=verification.authority_evidence_reference, authority_evidence_digest=verification.authority_evidence_digest, approved_at=approved_at, + _issuance_token=_ACTIVATION_RECEIPT_ISSUANCE_TOKEN, ) From 70c9cceea35cd224dfeb6752e5e6f7e26301fac2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:08:38 -0700 Subject: [PATCH 102/216] test(interview-plan): reject receipt scope replacement --- .../tests/test_receipt_issuance.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_receipt_issuance.py b/packages/interview-plan/tests/test_receipt_issuance.py index 1b29f4898..fcb1e842b 100644 --- a/packages/interview-plan/tests/test_receipt_issuance.py +++ b/packages/interview-plan/tests/test_receipt_issuance.py @@ -1,10 +1,15 @@ """Regression tests for authoritative structured-interview receipt issuance.""" +from dataclasses import replace from datetime import datetime, timezone import pytest -from orgmetra_interview_plan import StructuredInterviewActivationReceipt +from orgmetra_interview_plan import ( + StructuredInterviewActivationReceipt, + activate_structured_interview_plan, +) +from test_activation import APPROVED_AT, APPROVER, AllowingAuthority, plan, verification_for def test_activation_receipt_cannot_be_minted_without_verified_factory_path(): @@ -21,3 +26,17 @@ def test_activation_receipt_cannot_be_minted_without_verified_factory_path(): authority_evidence_digest="e" * 64, approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), ) + + +def test_issued_activation_receipt_cannot_be_replaced_with_unverified_scope(): + """Reject dataclass replacement that would reuse issuance proof for changed scope.""" + candidate_plan = plan() + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification_for(candidate_plan)), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + with pytest.raises(TypeError, match="activate_structured_interview_plan"): + replace(receipt, plan_digest="b" * 64) From a0d52aa73f5ae8a1b8377214a2ce550aa097dbcf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:09:22 -0700 Subject: [PATCH 103/216] fix(interview-plan): consume receipt issuance capability --- .../src/orgmetra_interview_plan/activation.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index c35510cf7..6e75af784 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -76,11 +76,6 @@ class StructuredInterviewActivationReceipt: def __post_init__(self) -> None: """Reject forged, ambiguous, weakened, or non-authoritatively issued evidence.""" - if self._issuance_token is not _ACTIVATION_RECEIPT_ISSUANCE_TOKEN: - raise TypeError( - "StructuredInterviewActivationReceipt can only be issued by " - "activate_structured_interview_plan" - ) _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") _validate_reference( self.interview_plan_reference, @@ -112,6 +107,11 @@ def __post_init__(self) -> None: raise ValueError("human confirmation is mandatory for interview-plan activation") if self.activation_state != _ACTIVATION_STATE: raise ValueError("activation_state must remain approved_for_use") + if self._issuance_token is not _ACTIVATION_RECEIPT_ISSUANCE_TOKEN: + raise TypeError( + "StructuredInterviewActivationReceipt can only be issued by " + "activate_structured_interview_plan" + ) def __repr__(self) -> str: """Return a redacted representation suitable for routine logs.""" @@ -191,7 +191,7 @@ def activate_structured_interview_plan( if verified_scope != expected_scope: raise ValueError("activation authority returned evidence for a different plan or actor") - return StructuredInterviewActivationReceipt( + receipt = StructuredInterviewActivationReceipt( tenant_record_id=plan.tenant_record_id, interview_plan_reference=plan.interview_plan_reference, plan_digest=plan.sha256_digest(), @@ -201,3 +201,5 @@ def activate_structured_interview_plan( approved_at=approved_at, _issuance_token=_ACTIVATION_RECEIPT_ISSUANCE_TOKEN, ) + object.__setattr__(receipt, "_issuance_token", None) + return receipt From febd6fe242d381b24578db990363560941af3fa1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:59:25 -0700 Subject: [PATCH 104/216] test(interview-plan): require redacted verification repr --- .../interview-plan/tests/test_activation.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index a2de37df2..30bf1d52e 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -235,3 +235,22 @@ def test_direct_receipt_construction_fails_closed(field, bad, match): ) with pytest.raises(ValueError, match=match): replace(receipt, **{field: bad}) + + +def test_authority_verification_repr_redacts_correlation_evidence(): + """Keep tenant, plan, actor, and evidence correlation identifiers out of routine logs.""" + candidate_plan = plan() + verification = verification_for(candidate_plan) + + text = repr(verification) + + assert text == "StructuredInterviewActivationVerification()" + for sensitive_value in ( + TENANT, + INTERVIEW_PLAN, + candidate_plan.sha256_digest(), + APPROVER, + AUTHORITY_EVIDENCE, + DIGEST_E, + ): + assert sensitive_value not in text From 2a83d9a37f3d319d8cf4e777c5292472c54591a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:59:48 -0700 Subject: [PATCH 105/216] fix(interview-plan): redact activation verification repr --- .../src/orgmetra_interview_plan/activation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 6e75af784..4a696f92f 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -31,7 +31,7 @@ _ACTIVATION_RECEIPT_ISSUANCE_TOKEN = object() -@dataclass(frozen=True, slots=True) +@dataclass(frozen=True, slots=True, repr=False) class StructuredInterviewActivationVerification: """Authoritative host evidence returned only after all activation checks pass.""" @@ -42,6 +42,10 @@ class StructuredInterviewActivationVerification: authority_evidence_reference: str authority_evidence_digest: str + def __repr__(self) -> str: + """Return a redacted representation suitable for routine logs and failures.""" + return "StructuredInterviewActivationVerification()" + class StructuredInterviewActivationAuthority(Protocol): """Host contract that fail-closes unless every authoritative activation check passes.""" From 2a662c0c3eba1438528348c0896f4944f93743df Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:04:18 -0700 Subject: [PATCH 106/216] test(interview-plan): reject mutable verification subclasses --- .../interview-plan/tests/test_activation.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index 30bf1d52e..2222c8305 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -95,6 +95,19 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): raise PermissionError("authoritative activation checks failed") +class SwitchingVerification(StructuredInterviewActivationVerification): + """Hostile result that can change valid authority evidence between attribute reads.""" + + def __getattribute__(self, name): + """Swap one format-valid authority reference after its first validation read.""" + if name == "authority_evidence_reference": + reads = object.__getattribute__(self, "_authority_reference_reads") + object.__setattr__(self, "_authority_reference_reads", reads + 1) + if reads: + return "activation_verification:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + return super().__getattribute__(name) + + def test_activation_executes_authority_and_returns_immutable_human_receipt(): """Bind human confirmation to the exact plan and authoritative verification evidence.""" candidate_plan = plan() @@ -171,6 +184,29 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): ) +def test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge(): + """Reject subclassed host evidence that can change canonical audit fields between reads.""" + candidate_plan = plan() + base = verification_for(candidate_plan) + verification = SwitchingVerification( + tenant_record_id=base.tenant_record_id, + interview_plan_reference=base.interview_plan_reference, + plan_digest=base.plan_digest, + approving_actor_reference=base.approving_actor_reference, + authority_evidence_reference=base.authority_evidence_reference, + authority_evidence_digest=base.authority_evidence_digest, + ) + object.__setattr__(verification, "_authority_reference_reads", 0) + + with pytest.raises(TypeError, match="StructuredInterviewActivationVerification"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + @pytest.mark.parametrize( ("changes", "match"), [ From d8002d82a9fea794695815ed75493f38ea6fd590 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:04:41 -0700 Subject: [PATCH 107/216] fix(interview-plan): require exact verification evidence type --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 4a696f92f..9d19b17a6 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -170,7 +170,7 @@ def activate_structured_interview_plan( approving_actor_reference=approving_actor_reference, approved_at=approved_at, ) - if not isinstance(verification, StructuredInterviewActivationVerification): + if type(verification) is not StructuredInterviewActivationVerification: raise TypeError("authority must return StructuredInterviewActivationVerification") _validate_reference( From 8a88fd4f5865ac7a5de1813e6a44969d47002d7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:06:45 -0700 Subject: [PATCH 108/216] test(interview-plan): reject forged verification scope strings --- .../interview-plan/tests/test_activation.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index 2222c8305..ee1b9989b 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -108,6 +108,20 @@ def __getattribute__(self, name): return super().__getattribute__(name) +class ForgedScopeText(str): + """String subclass that makes foreign scope evidence compare equal to expected scope.""" + + def __eq__(self, other): + """Pretend to equal any string so tuple scope comparison can be forged.""" + return isinstance(other, str) + + def __ne__(self, other): + """Pretend not to differ from any string so fail-closed comparison is bypassed.""" + return not isinstance(other, str) + + __hash__ = str.__hash__ + + def test_activation_executes_authority_and_returns_immutable_human_receipt(): """Bind human confirmation to the exact plan and authoritative verification evidence.""" candidate_plan = plan() @@ -207,6 +221,32 @@ def test_activation_rejects_verification_subclass_before_evidence_reads_can_dive ) +@pytest.mark.parametrize( + ("field", "forged_value"), + [ + ("tenant_record_id", ForgedScopeText("20000000-0000-7000-8000-000000000001")), + ( + "interview_plan_reference", + ForgedScopeText("interview_plan:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + ), + ("plan_digest", ForgedScopeText("f" * 64)), + ("approving_actor_reference", ForgedScopeText(PANEL_A)), + ], +) +def test_activation_rejects_forged_scope_string_subclasses(field, forged_value): + """Validate exact verification scope types before equality can approve foreign evidence.""" + candidate_plan = plan() + authority = AllowingAuthority(verification_for(candidate_plan, **{field: forged_value})) + + with pytest.raises(ValueError, match=field): + activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + @pytest.mark.parametrize( ("changes", "match"), [ From e32ba5e729040f9d0ec6c26b7c4b2a466b49b35f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:07:13 -0700 Subject: [PATCH 109/216] fix(interview-plan): require exact digest runtime type --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 2a4503373..dad84c292 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -62,8 +62,8 @@ def _validate_reference(value: str, prefix: str, field_name: str) -> None: def _validate_digest(value: str, field_name: str) -> None: - """Require lowercase SHA-256 hexadecimal evidence.""" - if not isinstance(value, str) or not _DIGEST_PATTERN.fullmatch(value): + """Require exact built-in string lowercase SHA-256 hexadecimal evidence.""" + if type(value) is not str or not _DIGEST_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be lowercase SHA-256 hex") From 2d4945f0683cfa57acb35324ba923b808b1b59e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:07:35 -0700 Subject: [PATCH 110/216] fix(interview-plan): validate exact authority scope evidence --- .../src/orgmetra_interview_plan/activation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 9d19b17a6..8bcb0a58d 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -173,6 +173,18 @@ def activate_structured_interview_plan( if type(verification) is not StructuredInterviewActivationVerification: raise TypeError("authority must return StructuredInterviewActivationVerification") + _validate_operational_uuid(verification.tenant_record_id, "tenant_record_id") + _validate_reference( + verification.interview_plan_reference, + "interview_plan", + "interview_plan_reference", + ) + _validate_digest(verification.plan_digest, "plan_digest") + _validate_reference( + verification.approving_actor_reference, + "actor", + "approving_actor_reference", + ) _validate_reference( verification.authority_evidence_reference, "activation_verification", From 428b439abc2fe0b376b864c68c23e3d96082f4b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:28:49 -0700 Subject: [PATCH 111/216] test(interview-plan): expose runtime evidence subclass forgery --- .../test_string_runtime_evidence_integrity.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py index 7c2a5637f..5ce61746e 100644 --- a/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py +++ b/packages/interview-plan/tests/test_string_runtime_evidence_integrity.py @@ -2,6 +2,7 @@ from __future__ import annotations +from dataclasses import replace from datetime import datetime, timezone import pytest @@ -60,6 +61,28 @@ def __hash__(self) -> int: return hash("approved_requisition_interview") +class SwitchingReferenceTuple(tuple): + """Tuple subclass that changes references after validation has already completed.""" + + def __new__( + cls, + values: tuple[str, ...], + forged_values: tuple[str, ...], + ) -> "SwitchingReferenceTuple": + """Store valid tuple payload plus later forged references for canonicalization.""" + instance = super().__new__(cls, values) + instance._forged_values = forged_values + instance._iteration_count = 0 + return instance + + def __iter__(self): # type: ignore[no-untyped-def] + """Yield valid references twice, then substitute forged references on later reads.""" + self._iteration_count += 1 + if self._iteration_count >= 3: + return iter(self._forged_values) + return tuple.__iter__(self) + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid structured-interview plan input.""" return { @@ -120,3 +143,52 @@ def test_rejects_reason_code_string_subclass_that_can_forge_allow_list_check() - kwargs["reason_code"] = ForgedGovernanceCode("attacker_controlled_reason") with pytest.raises(ValueError, match="reason_code"): build_structured_interview_plan(**kwargs) + + +@pytest.mark.parametrize( + ("field", "forged_value"), + [ + ("review_state", ForgedGovernanceCode("attacker_controlled_review_state")), + ("next_action", ForgedGovernanceCode("attacker_controlled_next_action")), + ], +) +def test_rejects_fixed_governance_text_subclasses_after_plan_construction( + field: str, + forged_value: ForgedGovernanceCode, +) -> None: + """Reject replacement-time string subclasses before immutable governance text can be forged.""" + candidate_plan = build_structured_interview_plan(**valid_kwargs()) + with pytest.raises(ValueError, match=field): + replace(candidate_plan, **{field: forged_value}) + + +@pytest.mark.parametrize( + ("field", "forged_values"), + [ + ( + "competency_references", + ( + "competency:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "competency:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + ), + ), + ( + "panel_actor_references", + ( + "actor:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + ), + ), + ], +) +def test_rejects_reference_tuple_subclasses_before_iteration_can_switch_evidence( + field: str, + forged_values: tuple[str, ...], +) -> None: + """Reject tuple subclasses that can change canonical references after validation.""" + kwargs = valid_kwargs() + original_values = kwargs[field] + assert type(original_values) is tuple + kwargs[field] = SwitchingReferenceTuple(original_values, forged_values) + with pytest.raises(ValueError, match=field): + build_structured_interview_plan(**kwargs) From d6f32a674e5ae98d08c69510026d0d65bd95e82b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:29:17 -0700 Subject: [PATCH 112/216] fix(interview-plan): require exact canonical evidence containers --- .../interview-plan/src/orgmetra_interview_plan/plan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index dad84c292..b1b5d476c 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -119,13 +119,13 @@ def __post_init__(self) -> None: _validate_digest(self.question_competency_map_digest, "question_competency_map_digest") _validate_reference(self.rating_anchor_reference, "rating_anchor", "rating_anchor_reference") _validate_digest(self.rating_anchor_digest, "rating_anchor_digest") - if not isinstance(self.competency_references, tuple) or not 1 <= len(self.competency_references) <= 12: + if type(self.competency_references) is not tuple or not 1 <= len(self.competency_references) <= 12: raise ValueError("competency_references must be a tuple containing 1 through 12 competencies") for reference in self.competency_references: _validate_reference(reference, "competency", "competency_references") if tuple(sorted(set(self.competency_references))) != self.competency_references: raise ValueError("competency_references must be sorted and unique") - if not isinstance(self.panel_actor_references, tuple) or not 2 <= len(self.panel_actor_references) <= 8: + if type(self.panel_actor_references) is not tuple or not 2 <= len(self.panel_actor_references) <= 8: raise ValueError("panel_actor_references must be a tuple containing 2 through 8 actors") for reference in self.panel_actor_references: _validate_reference(reference, "actor", "panel_actor_references") @@ -146,9 +146,9 @@ def __post_init__(self) -> None: raise ValueError("evidence_version must be an integer from 1 through 2147483647") if self.human_confirmation_required is not True: raise ValueError("human confirmation is mandatory for interview-plan approval") - if self.review_state != _REVIEW_STATE: + if type(self.review_state) is not str or self.review_state != _REVIEW_STATE: raise ValueError("review_state must remain requires_human_approval") - 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 interview-plan instruction") def __repr__(self) -> str: From 050522a1888a9d154febe96813269b6e9db59735 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:29:46 -0700 Subject: [PATCH 113/216] docs(interview-plan): record canonical runtime-type hardening --- packages/interview-plan/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 3ebb52fe4..e622b89af 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -23,5 +23,6 @@ - Reject timestamp/node-bearing UUIDv1 values in package-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. +- Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. - Redact both `StructuredInterviewPlan` and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - State explicitly that UUID/digest correlation, reference-string inequality, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From 8aaf0b726d0b766a0e006eda372e5ae244adff3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:30:07 -0700 Subject: [PATCH 114/216] docs(traceability): bind exact runtime evidence types --- docs/traceability/structured-interview-plan.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 6f0033ef8..4e27bfcf8 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -12,24 +12,26 @@ | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | | Evidence revisions remain distinguishable and immutable | bounded positive plan `evidence_version` in canonical JSON; activation receipt separately binds the exact plan digest and its own bounded positive evidence version | plan evidence-version regressions plus activation receipt canonical/digest and direct-replacement fail-closed regressions | -| Every governed competency has auditable coverage evidence | sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, question-count regressions, and mapping-reference/digest regressions | -| Interview panel is accountable and bounded | sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions plus fail-closed authority rejection path | +| Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | +| Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus direct receipt mutation failures | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type before timestamp checks or authority work; duck-typed and subclassed plan-shaped objects cannot bypass plan construction invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` proves rejection occurs before the authority adapter is invoked | | Activation audit chronology cannot precede the reviewed plan or bypass the authority | timezone-aware `approved_at` is validated before host authority execution, must be greater than or equal to the exact plan `generated_at`, and the same instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` for authoritative review | `test_activation_rejects_approval_before_plan_generation`, `test_activation_sends_approval_time_through_authoritative_verification`, plus normal successful activation coverage | | Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | -| Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, and `dataclasses.replace(...)` bypass regressions | +| Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed; fixed `review_state` and `next_action` require exact built-in strings | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, fixed-governance string-subclass regressions, and `dataclasses.replace(...)` bypass regressions | | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__` and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references and evidence digests are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | | Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time plan regressions and activation canonical/digest assertions | -| Direct construction cannot bypass invariants | plan and activation receipt `__post_init__` validation | direct constructor and `dataclasses.replace(...)` regressions | +| Direct construction cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types are required for trust-bearing reference collections and fixed plan-governance text before canonical serialization | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, and `dataclasses.replace(...)` regressions | ## Evidence boundary The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then passes that exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents the receipt from carrying an approval timestamp that the authority adapter was never given an opportunity to review. +The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. + The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, approval-time ordering, approval-time passage into the authority, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. @@ -38,4 +40,4 @@ Neither UUID form, reference inequality, digest metadata, nor the authority prot ## Out of scope -This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. +This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. \ No newline at end of file From 3c388a46166ad23cb42134170bde382a49743e5b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:07:28 -0700 Subject: [PATCH 115/216] test(interview-plan): reject authority-time plan mutation --- .../tests/test_activation_plan_mutation.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/interview-plan/tests/test_activation_plan_mutation.py diff --git a/packages/interview-plan/tests/test_activation_plan_mutation.py b/packages/interview-plan/tests/test_activation_plan_mutation.py new file mode 100644 index 000000000..864789ca2 --- /dev/null +++ b/packages/interview-plan/tests/test_activation_plan_mutation.py @@ -0,0 +1,47 @@ +"""Regression tests for activation-time mutation of governed interview plans.""" + +import pytest + +from orgmetra_interview_plan import ( + StructuredInterviewActivationVerification, + activate_structured_interview_plan, +) +from test_activation import ( + APPROVED_AT, + APPROVER, + AUTHORITY_EVIDENCE, + DIGEST_E, + plan, +) + + +class MutatingAuthority: + """Authority fixture that rewrites the caller's frozen plan before returning evidence.""" + + def verify_activation(self, *, plan, approving_actor_reference, approved_at): + """Mutate one governed field and return evidence for the rewritten artifact.""" + object.__setattr__(plan, "question_count", plan.question_count - 1) + return StructuredInterviewActivationVerification( + tenant_record_id=plan.tenant_record_id, + interview_plan_reference=plan.interview_plan_reference, + plan_digest=plan.sha256_digest(), + approving_actor_reference=approving_actor_reference, + authority_evidence_reference=AUTHORITY_EVIDENCE, + authority_evidence_digest=DIGEST_E, + ) + + +def test_activation_rejects_plan_mutation_during_authority_verification(): + """Reject authority evidence when the governed plan changes across the authority call.""" + candidate_plan = plan() + original_digest = candidate_plan.sha256_digest() + + with pytest.raises(ValueError, match="plan changed during authority verification"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=MutatingAuthority(), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + assert candidate_plan.sha256_digest() != original_digest From d57cb69f99cc285837e67461c460ac82dd9b4d6a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:07:59 -0700 Subject: [PATCH 116/216] fix(interview-plan): freeze plan evidence across authority call --- .../src/orgmetra_interview_plan/activation.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 8bcb0a58d..bbee7ffe5 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -165,11 +165,19 @@ def activate_structured_interview_plan( if approved_at < plan.generated_at: raise ValueError("approved_at must not precede plan generated_at") _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") + + plan_canonical_json = plan.canonical_json() + plan_digest = sha256(plan_canonical_json.encode("utf-8")).hexdigest() + plan_tenant_record_id = plan.tenant_record_id + interview_plan_reference = plan.interview_plan_reference + verification = authority.verify_activation( plan=plan, approving_actor_reference=approving_actor_reference, approved_at=approved_at, ) + if plan.canonical_json() != plan_canonical_json: + raise ValueError("plan changed during authority verification") if type(verification) is not StructuredInterviewActivationVerification: raise TypeError("authority must return StructuredInterviewActivationVerification") @@ -193,9 +201,9 @@ def activate_structured_interview_plan( _validate_digest(verification.authority_evidence_digest, "authority_evidence_digest") expected_scope = ( - plan.tenant_record_id, - plan.interview_plan_reference, - plan.sha256_digest(), + plan_tenant_record_id, + interview_plan_reference, + plan_digest, approving_actor_reference, ) verified_scope = ( @@ -208,9 +216,9 @@ def activate_structured_interview_plan( raise ValueError("activation authority returned evidence for a different plan or actor") receipt = StructuredInterviewActivationReceipt( - tenant_record_id=plan.tenant_record_id, - interview_plan_reference=plan.interview_plan_reference, - plan_digest=plan.sha256_digest(), + tenant_record_id=plan_tenant_record_id, + interview_plan_reference=interview_plan_reference, + plan_digest=plan_digest, approving_actor_reference=approving_actor_reference, authority_evidence_reference=verification.authority_evidence_reference, authority_evidence_digest=verification.authority_evidence_digest, From ae43337d9ce4df85940b819470d3ae8ce7f552da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:09:30 -0700 Subject: [PATCH 117/216] docs(interview-plan): record authority-call snapshot integrity --- packages/interview-plan/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index e622b89af..465256b63 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -18,6 +18,7 @@ - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, and approving actor before a receipt can exist. - Validate `approved_at` before authoritative activation work, reject approval evidence that predates the reviewed plan's `generated_at`, and pass that exact instant into `StructuredInterviewActivationAuthority.verify_activation(...)` so receipt chronology cannot be minted from a timestamp the authoritative adapter never reviewed. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. +- Snapshot the exact canonical plan evidence before calling the injected activation authority, reject any plan mutation observed across that call, and build verification scope plus the activation receipt from the pre-call snapshot so authority-time in-memory rewriting cannot become approved audit evidence. ### Security and privacy From 1e70ba4500e197d7acb36d4c470f595e4d95528f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:09:54 -0700 Subject: [PATCH 118/216] docs(traceability): bind pre-authority plan snapshot --- docs/traceability/structured-interview-plan.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 4e27bfcf8..2101fc87c 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -16,6 +16,7 @@ | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus direct receipt mutation failures | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type before timestamp checks or authority work; duck-typed and subclassed plan-shaped objects cannot bypass plan construction invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` proves rejection occurs before the authority adapter is invoked | +| The reviewed plan cannot change while the authority is evaluating it | activation snapshots canonical plan JSON, digest, tenant, and interview-plan reference before the authority call; any canonical plan mutation observed when control returns fails closed, and subsequent verification/receipt binding uses the pre-call snapshot rather than rereading mutable in-memory fields | `test_activation_rejects_plan_mutation_during_authority_verification` rewrites a frozen plan through `object.__setattr__` inside an authority fixture and proves no receipt can be issued | | Activation audit chronology cannot precede the reviewed plan or bypass the authority | timezone-aware `approved_at` is validated before host authority execution, must be greater than or equal to the exact plan `generated_at`, and the same instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` for authoritative review | `test_activation_rejects_approval_before_plan_generation`, `test_activation_sends_approval_time_through_authoritative_verification`, plus normal successful activation coverage | | Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | @@ -28,11 +29,11 @@ ## Evidence boundary -The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. It then passes that exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents the receipt from carrying an approval timestamp that the authority adapter was never given an opportunity to review. +The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. Immediately before that authority call it snapshots the plan's canonical JSON, SHA-256 digest, tenant identity, and interview-plan reference. When control returns, any canonical plan mutation fails closed; verification scope and the activation receipt are then bound to the pre-call snapshot rather than to fields reread after untrusted/injected authority work. It passes the exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents both authority-time in-memory plan rewriting and a receipt timestamp that the authority adapter was never given an opportunity to review. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, approval-time ordering, approval-time passage into the authority, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, pre/post authority plan integrity, approval-time ordering, approval-time passage into the authority, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. @@ -40,4 +41,4 @@ Neither UUID form, reference inequality, digest metadata, nor the authority prot ## Out of scope -This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. \ No newline at end of file +This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. From ef8c4f704eee9f6b5ddf6cd65c25428df58aa010 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:10:17 -0700 Subject: [PATCH 119/216] docs(adr): close activation plan-mutation TOCTOU --- docs/adr/0015-governed-structured-interview-plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 0deb98d4b..777166994 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. ## Decision @@ -28,7 +28,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The activation boundary validates a timezone-aware `approved_at`, rejects impossible chronology before authority work, and then supplies that exact instant to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks; it must bind the reviewed instant into its immutable verification evidence and raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The activation boundary validates a timezone-aware `approved_at`, rejects impossible chronology before authority work, snapshots the plan's canonical JSON and SHA-256 together with the tenant and interview-plan reference, and then supplies that exact instant to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. When the authority returns, activation recomputes canonical plan evidence and fails closed if the plan changed across the call; all later scope comparison and receipt construction use the pre-call snapshot rather than rereading mutable fields. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks; it must bind the reviewed instant into its immutable verification evidence and raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. @@ -39,7 +39,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Positive - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. -- Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, or returns evidence bound to another tenant/plan/digest/actor. +- Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, returns evidence bound to another tenant/plan/digest/actor, or mutates the reviewed plan while authority verification is in progress. - The exact approval instant now crosses the authoritative adapter boundary, so approved receipt chronology cannot be created from a timestamp the authority never reviewed. - Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. - Candidate PII and assessment values remain outside the planning and activation artifacts. From 8d8896b14db10a5a4981f0b9e209ea00ee3be64c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:10:35 -0700 Subject: [PATCH 120/216] test(traceability): require activation snapshot evidence --- packages/interview-plan/tests/test_traceability_scope.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py index dc9c4556c..9f783ec4a 100644 --- a/packages/interview-plan/tests/test_traceability_scope.py +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -17,6 +17,8 @@ def test_traceability_matches_executable_activation_boundary() -> None: assert "`test_activation_executes_authority_and_returns_immutable_human_receipt`" in text assert "`test_authority_rejection_blocks_activation`" in text assert "`test_activation_rejects_authority_evidence_for_other_scope`" in text + assert "`test_activation_rejects_plan_mutation_during_authority_verification`" in text + assert "pre-call snapshot" in text assert "A concrete production adapter remains responsible" in text assert "do **not** prove that a particular deployed adapter already performs database/API resolution correctly" in text assert "No host activation path is implemented in this slice." not in text From 06637b869bccc9d96c2cf0fedadead538e20e65e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 17:03:16 -0700 Subject: [PATCH 121/216] test(interview-plan): reject rewritten activation receipts --- .../tests/test_receipt_issuance.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/interview-plan/tests/test_receipt_issuance.py b/packages/interview-plan/tests/test_receipt_issuance.py index fcb1e842b..f440e5bca 100644 --- a/packages/interview-plan/tests/test_receipt_issuance.py +++ b/packages/interview-plan/tests/test_receipt_issuance.py @@ -5,6 +5,7 @@ import pytest +import orgmetra_interview_plan.activation as activation_module from orgmetra_interview_plan import ( StructuredInterviewActivationReceipt, activate_structured_interview_plan, @@ -40,3 +41,35 @@ def test_issued_activation_receipt_cannot_be_replaced_with_unverified_scope(): with pytest.raises(TypeError, match="activate_structured_interview_plan"): replace(receipt, plan_digest="b" * 64) + + +def test_issued_activation_receipt_rejects_post_issuance_rewrite(): + """Reject low-level rewriting of already-issued canonical activation evidence.""" + candidate_plan = plan() + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification_for(candidate_plan)), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + object.__setattr__(receipt, "plan_digest", "b" * 64) + + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + receipt.canonical_json() + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + receipt.sha256_digest() + + +def test_missing_process_local_activation_receipt_issuance_evidence_fails_closed(): + """Reject canonical export when process-local issuance evidence is unavailable.""" + candidate_plan = plan() + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification_for(candidate_plan)), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + activation_module._discard_activation_receipt_seal(id(receipt)) + + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + receipt.canonical_json() From ab299c3898b6acbed3c3bf5f41916e59204fe99a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 17:05:16 -0700 Subject: [PATCH 122/216] fix(interview-plan): bind receipts to issuance evidence --- .../src/orgmetra_interview_plan/activation.py | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index bbee7ffe5..2ec0e516a 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -12,8 +12,12 @@ from dataclasses import dataclass, field from datetime import datetime from hashlib import sha256 +import hmac import json +import secrets +from threading import RLock from typing import Protocol +from weakref import finalize from .plan import ( StructuredInterviewPlan, @@ -29,6 +33,38 @@ _ACTIVATION_STATE = "approved_for_use" _MAX_EVIDENCE_VERSION = 2_147_483_647 _ACTIVATION_RECEIPT_ISSUANCE_TOKEN = object() +_PROCESS_ACTIVATION_RECEIPT_SEAL_KEY = secrets.token_bytes(32) +_ACTIVATION_RECEIPT_SEALS: dict[int, str] = {} +_ACTIVATION_RECEIPT_SEALS_LOCK = RLock() + + +def _discard_activation_receipt_seal(receipt_id: int) -> None: + """Discard process-local activation issuance evidence after receipt collection.""" + with _ACTIVATION_RECEIPT_SEALS_LOCK: + _ACTIVATION_RECEIPT_SEALS.pop(receipt_id, None) + + +def _register_activation_receipt_seal(receipt: object, seal: str) -> None: + """Bind one live receipt identity to evidence outside receipt-writable slots.""" + receipt_id = id(receipt) + with _ACTIVATION_RECEIPT_SEALS_LOCK: + _ACTIVATION_RECEIPT_SEALS[receipt_id] = seal + finalize(receipt, _discard_activation_receipt_seal, receipt_id) + + +def _authoritative_activation_receipt_seal(receipt: object) -> str | None: + """Return process-local issuance evidence without trusting receipt-owned state.""" + with _ACTIVATION_RECEIPT_SEALS_LOCK: + return _ACTIVATION_RECEIPT_SEALS.get(id(receipt)) + + +def _seal_activation_receipt(payload_json: str) -> str: + """Bind one process-local activation issuance to exact canonical payload bytes.""" + return hmac.new( + _PROCESS_ACTIVATION_RECEIPT_SEAL_KEY, + payload_json.encode("utf-8"), + "sha256", + ).hexdigest() @dataclass(frozen=True, slots=True, repr=False) @@ -60,7 +96,7 @@ def verify_activation( """Return exact-scope evidence only after reviewing the exact approval instant.""" -@dataclass(frozen=True, slots=True, repr=False) +@dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) class StructuredInterviewActivationReceipt: """Immutable evidence that an accountable human activated one exact reviewed plan.""" @@ -116,13 +152,17 @@ def __post_init__(self) -> None: "StructuredInterviewActivationReceipt can only be issued by " "activate_structured_interview_plan" ) + _register_activation_receipt_seal( + self, + _seal_activation_receipt(self._canonical_json_unchecked()), + ) def __repr__(self) -> str: """Return a redacted representation suitable for routine logs.""" return "StructuredInterviewActivationReceipt()" - def canonical_json(self) -> str: - """Return deterministic canonical JSON for immutable audit correlation.""" + def _canonical_json_unchecked(self) -> str: + """Render canonical activation bytes without process-local issuance state.""" payload = { "activation_state": self.activation_state, "approved_at": _canonical_timestamp(self.approved_at, "approved_at"), @@ -139,8 +179,24 @@ def canonical_json(self) -> str: } return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + def canonical_json(self) -> str: + """Return creation-bound canonical JSON for immutable audit correlation.""" + canonical = self._canonical_json_unchecked() + authoritative_seal = _authoritative_activation_receipt_seal(self) + if ( + type(authoritative_seal) is not str + or not hmac.compare_digest( + _seal_activation_receipt(canonical), + authoritative_seal, + ) + ): + raise ValueError( + "structured interview activation receipt changed after activation receipt issuance" + ) + return canonical + def sha256_digest(self) -> str: - """Return SHA-256 over the exact canonical UTF-8 activation receipt.""" + """Return SHA-256 over the exact creation-bound activation receipt.""" return sha256(self.canonical_json().encode("utf-8")).hexdigest() From 8eb3a26c217371d988ff85d0145a536873b35eec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 17:06:27 -0700 Subject: [PATCH 123/216] docs(interview-plan): trace receipt issuance integrity --- docs/traceability/structured-interview-plan.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 2101fc87c..5fd9a4693 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -11,10 +11,10 @@ | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and immutable | bounded positive plan `evidence_version` in canonical JSON; activation receipt separately binds the exact plan digest and its own bounded positive evidence version | plan evidence-version regressions plus activation receipt canonical/digest and direct-replacement fail-closed regressions | +| Evidence revisions remain distinguishable and immutable | bounded positive plan `evidence_version` in canonical JSON; activation receipt separately binds the exact plan digest and its own bounded positive evidence version; a process-local issuance seal is stored outside receipt-writable slots and canonical export fails closed if issued receipt fields change or issuance evidence is unavailable | plan evidence-version regressions plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | -| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus direct receipt mutation failures | +| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus receipt issuance-integrity regressions | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type before timestamp checks or authority work; duck-typed and subclassed plan-shaped objects cannot bypass plan construction invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` proves rejection occurs before the authority adapter is invoked | | The reviewed plan cannot change while the authority is evaluating it | activation snapshots canonical plan JSON, digest, tenant, and interview-plan reference before the authority call; any canonical plan mutation observed when control returns fails closed, and subsequent verification/receipt binding uses the pre-call snapshot rather than rereading mutable in-memory fields | `test_activation_rejects_plan_mutation_during_authority_verification` rewrites a frozen plan through `object.__setattr__` inside an authority fixture and proves no receipt can be issued | | Activation audit chronology cannot precede the reviewed plan or bypass the authority | timezone-aware `approved_at` is validated before host authority execution, must be greater than or equal to the exact plan `generated_at`, and the same instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` for authoritative review | `test_activation_rejects_approval_before_plan_generation`, `test_activation_sends_approval_time_through_authoritative_verification`, plus normal successful activation coverage | @@ -25,19 +25,21 @@ | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | | Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time plan regressions and activation canonical/digest assertions | -| Direct construction cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types are required for trust-bearing reference collections and fixed plan-governance text before canonical serialization | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, and `dataclasses.replace(...)` regressions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types are required for trust-bearing reference collections and fixed plan-governance text; activation receipts additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, low-level post-issuance rewrite, and missing-seal regressions | ## Evidence boundary The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. Immediately before that authority call it snapshots the plan's canonical JSON, SHA-256 digest, tenant identity, and interview-plan reference. When control returns, any canonical plan mutation fails closed; verification scope and the activation receipt are then bound to the pre-call snapshot rather than to fields reread after untrusted/injected authority work. It passes the exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents both authority-time in-memory plan rewriting and a receipt timestamp that the authority adapter was never given an opportunity to review. +A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. Missing issuance evidence or any low-level post-issuance field rewrite therefore fails closed instead of silently producing a different apparently valid receipt. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. + The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, pre/post authority plan integrity, approval-time ordering, approval-time passage into the authority, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, pre/post authority plan integrity, approval-time ordering, approval-time passage into the authority, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. -Neither UUID form, reference inequality, digest metadata, nor the authority protocol by itself proves tenant ownership, relationship validity, panel identity separation, eligibility, training, scientific validity, fairness, or legal compliance. Production hosts must satisfy those obligations at the authoritative boundary and preserve purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence. +Neither UUID form, reference inequality, digest metadata, runtime issuance seal, nor the authority protocol by itself proves tenant ownership, relationship validity, panel identity separation, eligibility, training, scientific validity, fairness, or legal compliance. Production hosts must satisfy those obligations at the authoritative boundary and preserve purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence. ## Out of scope From c9079581aa4c7995e4bbb944e6e0cd3bf16b3353 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 17:06:40 -0700 Subject: [PATCH 124/216] docs(interview-plan): record receipt integrity repair --- packages/interview-plan/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 465256b63..e564faab3 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -19,6 +19,7 @@ - Validate `approved_at` before authoritative activation work, reject approval evidence that predates the reviewed plan's `generated_at`, and pass that exact instant into `StructuredInterviewActivationAuthority.verify_activation(...)` so receipt chronology cannot be minted from a timestamp the authoritative adapter never reviewed. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. - Snapshot the exact canonical plan evidence before calling the injected activation authority, reject any plan mutation observed across that call, and build verification scope plus the activation receipt from the pre-call snapshot so authority-time in-memory rewriting cannot become approved audit evidence. +- Bind every successfully issued activation receipt to a process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten or the creation-bound issuance evidence is unavailable. ### Security and privacy @@ -26,4 +27,5 @@ - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. - Redact both `StructuredInterviewPlan` and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. -- State explicitly that UUID/digest correlation, reference-string inequality, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. +- Treat the process-local activation-receipt seal strictly as in-memory issuance-integrity evidence, not as a durable audit store, portable signature, cross-process verification key, or substitute for the host's immutable audit/outbox contract. +- State explicitly that UUID/digest correlation, reference-string inequality, runtime receipt seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From c678ad4d6c2dd41c56e3f9368a39ff19046897a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 17:07:00 -0700 Subject: [PATCH 125/216] docs(interview-plan): define creation-bound receipt integrity --- docs/adr/0015-governed-structured-interview-plan.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 777166994..a8808d4ea 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. Likewise, Python dataclass freezing alone is not an audit-integrity boundary because low-level attribute rewriting can mutate an already-issued receipt after construction; canonical export therefore needs independent creation-bound evidence outside the receipt's writable slots. ## Decision @@ -32,6 +32,8 @@ Make that control flow executable through `StructuredInterviewActivationAuthorit A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. +At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable dataclass slots, keyed only to the live receipt identity and removed when the receipt is collected. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence or a low-level post-issuance field rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. + The plan and activation receipt are candidate-neutral. They contain no candidate identity, response, score, demographic attribute, compensation value, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or scientifically adequate. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. ## Consequences @@ -40,6 +42,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, returns evidence bound to another tenant/plan/digest/actor, or mutates the reviewed plan while authority verification is in progress. +- Already-issued receipt objects cannot silently export rewritten canonical evidence after low-level in-memory mutation; missing or mismatched creation-bound issuance evidence fails closed. - The exact approval instant now crosses the authoritative adapter boundary, so approved receipt chronology cannot be created from a timestamp the authority never reviewed. - Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. - Candidate PII and assessment values remain outside the planning and activation artifacts. @@ -52,6 +55,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. - The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence and must bind the supplied approval instant into their immutable authority evidence. +- The activation-receipt HMAC seal exists only for the lifetime of the in-process receipt object. It is not a portable signature, durable verification credential, key-management facility, or substitute for persisted authoritative audit evidence. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. From 072596e195d91c01f25771ac486eb5352b2d85b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:02:00 -0700 Subject: [PATCH 126/216] test(interview-plan): expose post-issuance plan mutation --- .../tests/test_plan_issuance_integrity.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/interview-plan/tests/test_plan_issuance_integrity.py diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py new file mode 100644 index 000000000..216f6b71a --- /dev/null +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -0,0 +1,26 @@ +"""Regression tests for post-construction structured-interview plan integrity.""" + +import pytest + +from test_activation import plan + + +def test_plan_canonical_evidence_fails_closed_after_low_level_mutation(): + """A built plan must not export different canonical evidence after issuance.""" + candidate_plan = plan() + original_json = candidate_plan.canonical_json() + original_digest = candidate_plan.sha256_digest() + + object.__setattr__( + candidate_plan, + "question_count", + candidate_plan.question_count - 1, + ) + + with pytest.raises(ValueError, match="changed after plan issuance"): + candidate_plan.canonical_json() + with pytest.raises(ValueError, match="changed after plan issuance"): + candidate_plan.sha256_digest() + + assert original_json + assert len(original_digest) == 64 From ae291c61d17d862a26b77684ab0f0b8a49d4c8b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:02:49 -0700 Subject: [PATCH 127/216] fix(interview-plan): bind plan canonical evidence to issuance --- .../src/orgmetra_interview_plan/plan.py | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index b1b5d476c..4a03364f6 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -9,9 +9,13 @@ from dataclasses import dataclass from datetime import datetime, timezone from hashlib import sha256 +import hmac import json import re +import secrets +from threading import RLock from uuid import UUID +from weakref import finalize _CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") @@ -27,6 +31,38 @@ "this structured interview plan." ) _MAX_EVIDENCE_VERSION = 2_147_483_647 +_PROCESS_PLAN_SEAL_KEY = secrets.token_bytes(32) +_PLAN_SEALS: dict[int, str] = {} +_PLAN_SEALS_LOCK = RLock() + + +def _discard_plan_seal(plan_id: int) -> None: + """Discard process-local issuance evidence after the plan is collected.""" + with _PLAN_SEALS_LOCK: + _PLAN_SEALS.pop(plan_id, None) + + +def _register_plan_seal(plan: object, seal: str) -> None: + """Bind one live plan identity to evidence outside plan-writable slots.""" + plan_id = id(plan) + with _PLAN_SEALS_LOCK: + _PLAN_SEALS[plan_id] = seal + finalize(plan, _discard_plan_seal, plan_id) + + +def _authoritative_plan_seal(plan: object) -> str | None: + """Return process-local issuance evidence without trusting plan-owned state.""" + with _PLAN_SEALS_LOCK: + return _PLAN_SEALS.get(id(plan)) + + +def _seal_plan(payload_json: str) -> str: + """Bind one process-local plan issuance to exact canonical payload bytes.""" + return hmac.new( + _PROCESS_PLAN_SEAL_KEY, + payload_json.encode("utf-8"), + "sha256", + ).hexdigest() def _validate_operational_uuid(value: str, field_name: str) -> None: @@ -74,7 +110,7 @@ def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> s return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") -@dataclass(frozen=True, slots=True, repr=False) +@dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) class StructuredInterviewPlan: """Immutable candidate-neutral interview-plan evidence awaiting human approval.""" @@ -150,13 +186,14 @@ def __post_init__(self) -> None: raise ValueError("review_state must remain requires_human_approval") if type(self.next_action) is not str or self.next_action != _NEXT_ACTION: raise ValueError("next_action must remain the governed interview-plan instruction") + _register_plan_seal(self, _seal_plan(self._canonical_json_unchecked())) def __repr__(self) -> str: """Return a fully redacted representation safe for routine logs and assertions.""" return "StructuredInterviewPlan()" - def canonical_json(self) -> str: - """Return deterministic canonical JSON for immutable audit correlation.""" + def _canonical_json_unchecked(self) -> str: + """Render canonical plan bytes without process-local issuance state.""" payload = { "competency_references": list(self.competency_references), "evidence_version": self.evidence_version, @@ -183,8 +220,18 @@ def canonical_json(self) -> str: } return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + def canonical_json(self) -> str: + """Return creation-bound canonical JSON for immutable audit correlation.""" + canonical = self._canonical_json_unchecked() + authoritative_seal = _authoritative_plan_seal(self) + if type(authoritative_seal) is not str: + raise ValueError("structured interview plan issuance evidence is unavailable") + if not hmac.compare_digest(_seal_plan(canonical), authoritative_seal): + raise ValueError("structured interview plan changed after plan issuance") + return canonical + def sha256_digest(self) -> str: - """Return SHA-256 over the exact canonical UTF-8 plan.""" + """Return SHA-256 over the exact creation-bound canonical UTF-8 plan.""" return sha256(self.canonical_json().encode("utf-8")).hexdigest() From 7a131cb2a8a5fdcda7faea07956dc95d9facb742 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:03:10 -0700 Subject: [PATCH 128/216] test(interview-plan): cover missing plan issuance evidence --- .../tests/test_plan_issuance_integrity.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py index 216f6b71a..1aafe3537 100644 --- a/packages/interview-plan/tests/test_plan_issuance_integrity.py +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -1,5 +1,7 @@ """Regression tests for post-construction structured-interview plan integrity.""" +from copy import copy + import pytest from test_activation import plan @@ -24,3 +26,13 @@ def test_plan_canonical_evidence_fails_closed_after_low_level_mutation(): assert original_json assert len(original_digest) == 64 + + +def test_copied_plan_has_no_transferable_process_local_issuance_evidence(): + """Copying fields must not manufacture a second issued plan identity.""" + copied_plan = copy(plan()) + + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + copied_plan.canonical_json() + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + copied_plan.sha256_digest() From ee0791a15c01a8df16c6b68064ff4d1123cd3292 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:03:19 -0700 Subject: [PATCH 129/216] test(interview-plan): align authority mutation with plan seal --- .../tests/test_activation_plan_mutation.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/tests/test_activation_plan_mutation.py b/packages/interview-plan/tests/test_activation_plan_mutation.py index 864789ca2..36c513442 100644 --- a/packages/interview-plan/tests/test_activation_plan_mutation.py +++ b/packages/interview-plan/tests/test_activation_plan_mutation.py @@ -19,7 +19,7 @@ class MutatingAuthority: """Authority fixture that rewrites the caller's frozen plan before returning evidence.""" def verify_activation(self, *, plan, approving_actor_reference, approved_at): - """Mutate one governed field and return evidence for the rewritten artifact.""" + """Mutate one governed field and attempt to attest the rewritten artifact.""" object.__setattr__(plan, "question_count", plan.question_count - 1) return StructuredInterviewActivationVerification( tenant_record_id=plan.tenant_record_id, @@ -32,11 +32,11 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): def test_activation_rejects_plan_mutation_during_authority_verification(): - """Reject authority evidence when the governed plan changes across the authority call.""" + """Reject an authority that rewrites creation-bound plan evidence.""" candidate_plan = plan() original_digest = candidate_plan.sha256_digest() - with pytest.raises(ValueError, match="plan changed during authority verification"): + with pytest.raises(ValueError, match="changed after plan issuance"): activate_structured_interview_plan( plan=candidate_plan, authority=MutatingAuthority(), @@ -44,4 +44,6 @@ def test_activation_rejects_plan_mutation_during_authority_verification(): approved_at=APPROVED_AT, ) - assert candidate_plan.sha256_digest() != original_digest + with pytest.raises(ValueError, match="changed after plan issuance"): + candidate_plan.sha256_digest() + assert len(original_digest) == 64 From b3ad9ce12bd39c18b386376387ba6c9c47c1415f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:03:49 -0700 Subject: [PATCH 130/216] test(interview-plan): cover plan seal cleanup fail-closure --- .../tests/test_plan_issuance_integrity.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py index 1aafe3537..5869c6b21 100644 --- a/packages/interview-plan/tests/test_plan_issuance_integrity.py +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -4,6 +4,7 @@ import pytest +import orgmetra_interview_plan.plan as plan_module from test_activation import plan @@ -28,6 +29,17 @@ def test_plan_canonical_evidence_fails_closed_after_low_level_mutation(): assert len(original_digest) == 64 +def test_missing_process_local_plan_issuance_evidence_fails_closed(): + """Canonical export requires the creation-bound process-local plan seal.""" + candidate_plan = plan() + plan_module._discard_plan_seal(id(candidate_plan)) + + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + candidate_plan.canonical_json() + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + candidate_plan.sha256_digest() + + def test_copied_plan_has_no_transferable_process_local_issuance_evidence(): """Copying fields must not manufacture a second issued plan identity.""" copied_plan = copy(plan()) From 01c26f4c9289ab2aaa54fe1a5ef1039e0f132ba6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:04:06 -0700 Subject: [PATCH 131/216] docs(interview-plan): record creation-bound plan integrity --- packages/interview-plan/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index e564faab3..f54e365bb 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -19,6 +19,7 @@ - Validate `approved_at` before authoritative activation work, reject approval evidence that predates the reviewed plan's `generated_at`, and pass that exact instant into `StructuredInterviewActivationAuthority.verify_activation(...)` so receipt chronology cannot be minted from a timestamp the authoritative adapter never reviewed. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. - Snapshot the exact canonical plan evidence before calling the injected activation authority, reject any plan mutation observed across that call, and build verification scope plus the activation receipt from the pre-call snapshot so authority-time in-memory rewriting cannot become approved audit evidence. +- Bind every constructed `StructuredInterviewPlan` to a process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan after construction or if copied/reconstructed objects lack creation-bound issuance evidence. - Bind every successfully issued activation receipt to a process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten or the creation-bound issuance evidence is unavailable. ### Security and privacy @@ -27,5 +28,5 @@ - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. - Redact both `StructuredInterviewPlan` and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. -- Treat the process-local activation-receipt seal strictly as in-memory issuance-integrity evidence, not as a durable audit store, portable signature, cross-process verification key, or substitute for the host's immutable audit/outbox contract. -- State explicitly that UUID/digest correlation, reference-string inequality, runtime receipt seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. +- Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. +- State explicitly that UUID/digest correlation, reference-string inequality, runtime issuance seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From 46a4bf3ad082062109e20fdb113ee30370025dc0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:04:32 -0700 Subject: [PATCH 132/216] docs(adr): bind structured interview plan issuance integrity --- docs/adr/0015-governed-structured-interview-plan.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index a8808d4ea..13fc3cd65 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. Likewise, Python dataclass freezing alone is not an audit-integrity boundary because low-level attribute rewriting can mutate an already-issued receipt after construction; canonical export therefore needs independent creation-bound evidence outside the receipt's writable slots. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. Python dataclass freezing is not an issuance-integrity boundary: `object.__setattr__` can rewrite a plan after successful construction and, without independent creation evidence, the rewritten object can otherwise become the new canonical plan before activation starts. The same low-level mechanism can mutate an already-issued activation receipt after construction. Canonical export for both artifacts therefore needs independent creation-bound evidence outside their writable slots. ## Decision @@ -26,9 +26,11 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: `tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. +At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. + The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The activation boundary validates a timezone-aware `approved_at`, rejects impossible chronology before authority work, snapshots the plan's canonical JSON and SHA-256 together with the tenant and interview-plan reference, and then supplies that exact instant to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. When the authority returns, activation recomputes canonical plan evidence and fails closed if the plan changed across the call; all later scope comparison and receipt construction use the pre-call snapshot rather than rereading mutable fields. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks; it must bind the reviewed instant into its immutable verification evidence and raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The activation boundary validates a timezone-aware `approved_at`, rejects impossible chronology before authority work, obtains creation-bound canonical plan JSON and SHA-256 together with the tenant and interview-plan reference, and then supplies that exact instant to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. When the authority returns, creation-bound plan validation is repeated; any authority-time mutation therefore fails closed before later scope comparison or receipt construction. All later scope comparison and receipt construction use the pre-call snapshot rather than rereading mutable fields. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks; it must bind the reviewed instant into its immutable verification evidence and raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. @@ -41,6 +43,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Positive - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. +- Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, or mismatched process-local issuance evidence fails closed before activation can rely on it. - Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, returns evidence bound to another tenant/plan/digest/actor, or mutates the reviewed plan while authority verification is in progress. - Already-issued receipt objects cannot silently export rewritten canonical evidence after low-level in-memory mutation; missing or mismatched creation-bound issuance evidence fails closed. - The exact approval instant now crosses the authoritative adapter boundary, so approved receipt chronology cannot be created from a timestamp the authority never reviewed. @@ -55,7 +58,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. - The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence and must bind the supplied approval instant into their immutable authority evidence. -- The activation-receipt HMAC seal exists only for the lifetime of the in-process receipt object. It is not a portable signature, durable verification credential, key-management facility, or substitute for persisted authoritative audit evidence. +- Plan and activation-receipt HMAC seals exist only for the lifetime of each in-process object. They are not portable signatures, durable verification credentials, key-management facilities, or substitutes for persisted authoritative audit evidence; copied or reconstructed plan objects intentionally fail closed unless a future authoritative rehydration contract explicitly re-establishes issuance evidence. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. From 33d700d2e3cb87d90b6baebe734127c80ae93156 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:05:05 -0700 Subject: [PATCH 133/216] docs(traceability): cover creation-bound interview plans --- .../traceability/structured-interview-plan.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 5fd9a4693..8c8fdf9b7 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -11,12 +11,13 @@ | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and immutable | bounded positive plan `evidence_version` in canonical JSON; activation receipt separately binds the exact plan digest and its own bounded positive evidence version; a process-local issuance seal is stored outside receipt-writable slots and canonical export fails closed if issued receipt fields change or issuance evidence is unavailable | plan evidence-version regressions plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | +| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; a process-local plan issuance seal binds the exact post-construction canonical payload; activation receipt separately binds the exact plan digest and its own bounded positive evidence version plus receipt issuance seal | plan evidence-version regressions, `test_plan_issuance_integrity.py`, plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus receipt issuance-integrity regressions | -| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type before timestamp checks or authority work; duck-typed and subclassed plan-shaped objects cannot bypass plan construction invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` proves rejection occurs before the authority adapter is invoked | -| The reviewed plan cannot change while the authority is evaluating it | activation snapshots canonical plan JSON, digest, tenant, and interview-plan reference before the authority call; any canonical plan mutation observed when control returns fails closed, and subsequent verification/receipt binding uses the pre-call snapshot rather than rereading mutable in-memory fields | `test_activation_rejects_plan_mutation_during_authority_verification` rewrites a frozen plan through `object.__setattr__` inside an authority fixture and proves no receipt can be issued | +| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before timestamp checks or authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | +| Constructed plan evidence cannot be silently rewritten | each successful `StructuredInterviewPlan` construction registers a process-local HMAC seal outside plan-writable slots; canonical JSON and SHA-256 require that exact live-object issuance evidence and reject changed fields, discarded evidence, and copied identities | `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, and `test_copied_plan_has_no_transferable_process_local_issuance_evidence` | +| The reviewed plan cannot change while the authority is evaluating it | activation snapshots creation-bound canonical plan JSON, digest, tenant, and interview-plan reference before the authority call; any canonical plan mutation during authority execution fails closed before verification/receipt binding can continue | `test_activation_rejects_plan_mutation_during_authority_verification` rewrites a frozen plan through `object.__setattr__` inside an authority fixture and proves no receipt can be issued | | Activation audit chronology cannot precede the reviewed plan or bypass the authority | timezone-aware `approved_at` is validated before host authority execution, must be greater than or equal to the exact plan `generated_at`, and the same instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` for authoritative review | `test_activation_rejects_approval_before_plan_generation`, `test_activation_sends_approval_time_through_authoritative_verification`, plus normal successful activation coverage | | Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | @@ -24,23 +25,25 @@ | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__` and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references and evidence digests are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | -| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time plan regressions and activation canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types are required for trust-bearing reference collections and fixed plan-governance text; activation receipts additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, low-level post-issuance rewrite, and missing-seal regressions | +| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; creation-bound canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time plan regressions, plan issuance-integrity regressions, and activation canonical/digest assertions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing-seal, receipt low-level rewrite, and receipt missing-seal regressions | ## Evidence boundary -The active PR now implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. Immediately before that authority call it snapshots the plan's canonical JSON, SHA-256 digest, tenant identity, and interview-plan reference. When control returns, any canonical plan mutation fails closed; verification scope and the activation receipt are then bound to the pre-call snapshot rather than to fields reread after untrusted/injected authority work. It passes the exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents both authority-time in-memory plan rewriting and a receipt timestamp that the authority adapter was never given an opportunity to review. +The plan object is now creation-bound before activation begins. Successful `StructuredInterviewPlan` construction computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. + +The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. Immediately before that authority call it obtains the creation-bound plan canonical JSON, SHA-256 digest, tenant identity, and interview-plan reference. When control returns, creation-bound validation runs again; any canonical plan mutation fails closed before verification scope or receipt construction can proceed. It passes the exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents both pre-activation low-level rewriting from becoming new plan truth and authority-time in-memory plan rewriting from becoming approved audit evidence. A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. Missing issuance evidence or any low-level post-issuance field rewrite therefore fails closed instead of silently producing a different apparently valid receipt. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, pre/post authority plan integrity, approval-time ordering, approval-time passage into the authority, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity before and during authority work, approval-time ordering, approval-time passage into the authority, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. -Neither UUID form, reference inequality, digest metadata, runtime issuance seal, nor the authority protocol by itself proves tenant ownership, relationship validity, panel identity separation, eligibility, training, scientific validity, fairness, or legal compliance. Production hosts must satisfy those obligations at the authoritative boundary and preserve purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence. +Neither UUID form, reference inequality, digest metadata, runtime issuance seals, nor the authority protocol by itself proves tenant ownership, relationship validity, panel identity separation, eligibility, training, scientific validity, fairness, or legal compliance. Production hosts must satisfy those obligations at the authoritative boundary and preserve purpose-bound authorization, least privilege, retention/export controls, and immutable audit evidence. ## Out of scope -This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. +This slice does not persist interview plans, questions, mappings, responses, scores, candidate PII, authoritative identity-resolution results, adverse-impact statistics, validity-study results, or final selection decisions. It does not ship a concrete production authority adapter or claim that a structured interview is legally compliant or scientifically validated merely because a plan or activation receipt exists. The process-local seals do not authorize cross-process reconstruction or replace persisted authoritative audit evidence. Those claims require separate job-analysis, selection-validation, fairness, accessibility/accommodation, operational, deployment, and human-decision evidence. From ae6a511a054e4dd27fc7e911848d34af39ea53b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:05:23 -0700 Subject: [PATCH 134/216] docs(interview-plan): explain creation-bound plan evidence --- packages/interview-plan/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index e950959c6..37432d3f4 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,14 +10,16 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -`activate_structured_interview_plan(...)` makes that control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type, so a duck-typed or subclassed plan-shaped object cannot bypass plan construction invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, training, and approval-time checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. The same exact `approved_at` is then passed into `verify_activation(...)`; an adapter must review that instant as part of the authoritative approval and bind it into its verification evidence rather than allowing a caller-only timestamp to be minted into the receipt. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. +A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. The package computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing or mismatched issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. -The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. +`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, training, and approval-time checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. The same exact `approved_at` is then passed into `verify_activation(...)`; an adapter must review that instant as part of the authoritative approval and bind it into its verification evidence rather than allowing a caller-only timestamp to be minted into the receipt. Creation-bound plan evidence is checked again after authority work, so authority-time in-memory rewriting also fails closed. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. + +The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots; receipt mutation or missing issuance evidence fails closed before canonical export. The plan object itself remains pending human review: `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and its next action requires authoritative resolution before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed plan invariants. The activation receipt is separate evidence and does not mutate or rewrite the reviewed plan. -`repr(plan)` is fully redacted as `StructuredInterviewPlan()`, so routine logs and assertion failures do not expose governance correlations or evidence digests. Canonical JSON remains the explicit evidence serialization boundary. +`repr(plan)` is fully redacted as `StructuredInterviewPlan()`, so routine logs and assertion failures do not expose governance correlations or evidence digests. Canonical JSON remains the explicit evidence serialization boundary and is released only after creation-bound integrity verification succeeds. For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries, bind the exact approved instant into authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. +This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The process-local seals do not authorize cross-process reconstruction. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries, bind the exact approved instant into authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. From d3cfc89b384367865d3f7eb43aa0675882dee9e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:07:44 -0700 Subject: [PATCH 135/216] fix(interview-plan): remove obsolete mutation branch --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 2ec0e516a..57458ab1b 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -232,8 +232,7 @@ def activate_structured_interview_plan( approving_actor_reference=approving_actor_reference, approved_at=approved_at, ) - if plan.canonical_json() != plan_canonical_json: - raise ValueError("plan changed during authority verification") + plan.canonical_json() if type(verification) is not StructuredInterviewActivationVerification: raise TypeError("authority must return StructuredInterviewActivationVerification") From a750b1102610117d6fde4c57578883f5779398e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:07:50 -0700 Subject: [PATCH 136/216] test(interview-plan): make traceability assertion semantic --- packages/interview-plan/tests/test_traceability_scope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py index 9f783ec4a..4187353a2 100644 --- a/packages/interview-plan/tests/test_traceability_scope.py +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -12,7 +12,7 @@ def test_traceability_matches_executable_activation_boundary() -> None: """Keep traceability aligned with the executable host-orchestration boundary and its limits.""" text = TRACEABILITY.read_text(encoding="utf-8") - assert "The active PR now implements an executable activation orchestration boundary" in text + assert "implements an executable activation orchestration boundary" in text assert "into an injected `StructuredInterviewActivationAuthority`" in text assert "`test_activation_executes_authority_and_returns_immutable_human_receipt`" in text assert "`test_authority_rejection_blocks_activation`" in text From 2ac88944b8d08ebd98833e86b204e5987975240b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:14:30 -0700 Subject: [PATCH 137/216] test(interview-plan): expose activation integrity review findings --- .../tests/test_activation_integrity_review.py | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 packages/interview-plan/tests/test_activation_integrity_review.py diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py new file mode 100644 index 000000000..670f16308 --- /dev/null +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -0,0 +1,153 @@ +"""Regressions for current-head structured-interview activation integrity findings.""" + +from dataclasses import fields +from datetime import datetime, timedelta, timezone, tzinfo +from threading import Event, Thread +import json + +import pytest + +import orgmetra_interview_plan.activation as activation_module +from orgmetra_interview_plan import ( + StructuredInterviewActivationVerification, + activate_structured_interview_plan, +) +from test_activation import ( + APPROVED_AT, + APPROVER, + AUTHORITY_EVIDENCE, + DIGEST_E, + AllowingAuthority, + RejectingAuthority, + plan, + verification_for, +) + +ALTERNATE_AUTHORITY_EVIDENCE = "activation_verification:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +ALTERNATE_AUTHORITY_DIGEST = "f" * 64 + + +class MutableOffsetTimezone(tzinfo): + """UTC-offset provider whose offset can change after initial validation.""" + + def __init__(self, offset_hours: int) -> None: + """Store the mutable offset used by the adversarial approval-time fixture.""" + self.offset_hours = offset_hours + + def utcoffset(self, value): + """Return the currently configured offset.""" + return timedelta(hours=self.offset_hours) + + def dst(self, value): + """Return zero daylight-saving offset for deterministic test behavior.""" + return timedelta(0) + + def tzname(self, value): + """Return a stable diagnostic name for the mutable test timezone.""" + return "MutableOffsetTimezone" + + +class ApprovalTimeMutatingAuthority: + """Mutate caller-owned timezone state only after receiving the approval snapshot.""" + + def __init__(self, source_timezone: MutableOffsetTimezone) -> None: + """Keep the caller timezone so authority work can mutate it deterministically.""" + self.source_timezone = source_timezone + + def verify_activation(self, *, plan, approving_actor_reference, approved_at): + """Require immutable built-in UTC evidence, then mutate the caller timezone.""" + assert approved_at.tzinfo is timezone.utc + assert approved_at == APPROVED_AT + self.source_timezone.offset_hours = 2 + return StructuredInterviewActivationVerification( + tenant_record_id=plan.tenant_record_id, + interview_plan_reference=plan.interview_plan_reference, + plan_digest=plan.sha256_digest(), + approving_actor_reference=approving_actor_reference, + authority_evidence_reference=AUTHORITY_EVIDENCE, + authority_evidence_digest=DIGEST_E, + approved_at=approved_at, + ) + + +def test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation(): + """Repeated initialization must not legitimize changed bytes on one issued plan identity.""" + candidate_plan = plan() + object.__setattr__(candidate_plan, "question_count", 3) + + with pytest.raises(ValueError, match="issuance evidence already exists"): + candidate_plan.__post_init__() + with pytest.raises(ValueError, match="changed after plan issuance"): + candidate_plan.canonical_json() + with pytest.raises(ValueError, match="changed after plan issuance"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=RejectingAuthority(), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + +def test_activation_freezes_mutable_timezone_before_authority_and_receipt(): + """Authority work cannot make one approved_at value represent two UTC instants.""" + mutable_timezone = MutableOffsetTimezone(1) + caller_time = datetime(2026, 8, 21, 6, 0, 0, 123456, tzinfo=mutable_timezone) + candidate_plan = plan() + + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=ApprovalTimeMutatingAuthority(mutable_timezone), + approving_actor_reference=APPROVER, + approved_at=caller_time, + ) + + payload = json.loads(receipt.canonical_json()) + assert payload["approved_at"] == "2026-08-21T05:00:00.123456Z" + assert receipt.approved_at.tzinfo is timezone.utc + assert receipt.approved_at == APPROVED_AT + + +def test_verification_mutation_after_validation_cannot_rewrite_receipt(monkeypatch): + """Receipt construction must use one detached verification snapshot after authority return.""" + candidate_plan = plan() + verification = verification_for(candidate_plan) + validation_finished = Event() + mutation_finished = Event() + original_validate_digest = activation_module._validate_digest + + def synchronized_validate_digest(value, field_name): + """Pause after evidence-digest validation so a retained authority alias can mutate.""" + original_validate_digest(value, field_name) + if field_name == "authority_evidence_digest": + validation_finished.set() + assert mutation_finished.wait(timeout=2) + + def mutate_retained_verification(): + """Rewrite valid authority evidence only after the activation boundary validated it.""" + assert validation_finished.wait(timeout=2) + object.__setattr__(verification, "authority_evidence_reference", ALTERNATE_AUTHORITY_EVIDENCE) + object.__setattr__(verification, "authority_evidence_digest", ALTERNATE_AUTHORITY_DIGEST) + mutation_finished.set() + + monkeypatch.setattr(activation_module, "_validate_digest", synchronized_validate_digest) + mutator = Thread(target=mutate_retained_verification, daemon=True) + mutator.start() + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + mutator.join(timeout=2) + assert not mutator.is_alive() + + payload = json.loads(receipt.canonical_json()) + assert payload["authority_evidence_reference"] == AUTHORITY_EVIDENCE + assert payload["authority_evidence_digest"] == DIGEST_E + + +def test_verification_contract_explicitly_binds_reviewed_approval_time(): + """Authority verification must expose the exact approval instant it attests.""" + field_names = {field.name for field in fields(StructuredInterviewActivationVerification)} + + assert "approved_at" in field_names From cb3863233fae33b8d6ed734065fbeebecf6f3911 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:16:58 -0700 Subject: [PATCH 138/216] fix(interview-plan): make plan issuance seal single-registration --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 4a03364f6..fbc3f7ff8 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -43,9 +43,11 @@ def _discard_plan_seal(plan_id: int) -> None: def _register_plan_seal(plan: object, seal: str) -> None: - """Bind one live plan identity to evidence outside plan-writable slots.""" + """Bind one live plan identity once to evidence outside plan-writable slots.""" plan_id = id(plan) with _PLAN_SEALS_LOCK: + if plan_id in _PLAN_SEALS: + raise ValueError("structured interview plan issuance evidence already exists") _PLAN_SEALS[plan_id] = seal finalize(plan, _discard_plan_seal, plan_id) From b4b0150ddbc16a51985855f29789f5a03aee3a42 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:17:35 -0700 Subject: [PATCH 139/216] fix(interview-plan): snapshot activation authority evidence --- .../src/orgmetra_interview_plan/activation.py | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 57458ab1b..41e9e7f7f 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -10,7 +10,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from datetime import datetime +from datetime import datetime, timedelta, timezone from hashlib import sha256 import hmac import json @@ -38,6 +38,17 @@ _ACTIVATION_RECEIPT_SEALS_LOCK = RLock() +def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: + """Detach one caller-owned aware datetime into an immutable built-in 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") + offset = value.utcoffset() + if type(offset) is not timedelta: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") + local_naive = value.replace(tzinfo=None) + return (local_naive - offset).replace(tzinfo=timezone.utc) + + def _discard_activation_receipt_seal(receipt_id: int) -> None: """Discard process-local activation issuance evidence after receipt collection.""" with _ACTIVATION_RECEIPT_SEALS_LOCK: @@ -77,6 +88,7 @@ class StructuredInterviewActivationVerification: approving_actor_reference: str authority_evidence_reference: str authority_evidence_digest: str + approved_at: datetime def __repr__(self) -> str: """Return a redacted representation suitable for routine logs and failures.""" @@ -93,7 +105,7 @@ def verify_activation( approving_actor_reference: str, approved_at: datetime, ) -> StructuredInterviewActivationVerification: - """Return exact-scope evidence only after reviewing the exact approval instant.""" + """Return exact-scope evidence bound to the reviewed UTC approval instant.""" @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) @@ -106,7 +118,7 @@ class StructuredInterviewActivationReceipt: approving_actor_reference: str authority_evidence_reference: str authority_evidence_digest: str - approved_at: object + approved_at: datetime purpose_code: str = _PURPOSE_CODE reason_code: str = _REASON_CODE evidence_version: int = 1 @@ -211,18 +223,21 @@ def activate_structured_interview_plan( The authority implementation is responsible for the actual tenant-scoped re-resolution, relationship/provenance/panel checks, and review of the exact - approval instant. This function rejects a non-contract result or evidence bound - to a different plan/actor and emits a value-minimized immutable human-approval - receipt only for the exact verified scope. + approval instant. This function detaches caller/authority-owned runtime values + before using them as audit evidence, rejects a non-contract result or evidence + bound to a different plan/actor/time, and emits a value-minimized immutable + human-approval receipt only for the exact verified scope. """ if type(plan) is not StructuredInterviewPlan: raise TypeError("plan must be a StructuredInterviewPlan") - _canonical_timestamp(approved_at, "approved_at") - if approved_at < plan.generated_at: - raise ValueError("approved_at must not precede plan generated_at") + approved_at_snapshot = _snapshot_utc_datetime(approved_at, "approved_at") _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") plan_canonical_json = plan.canonical_json() + plan_payload = json.loads(plan_canonical_json) + plan_generated_at = datetime.fromisoformat(plan_payload["generated_at"].replace("Z", "+00:00")) + if approved_at_snapshot < plan_generated_at: + raise ValueError("approved_at must not precede plan generated_at") plan_digest = sha256(plan_canonical_json.encode("utf-8")).hexdigest() plan_tenant_record_id = plan.tenant_record_id interview_plan_reference = plan.interview_plan_reference @@ -230,54 +245,66 @@ def activate_structured_interview_plan( verification = authority.verify_activation( plan=plan, approving_actor_reference=approving_actor_reference, - approved_at=approved_at, + approved_at=approved_at_snapshot, ) plan.canonical_json() if type(verification) is not StructuredInterviewActivationVerification: raise TypeError("authority must return StructuredInterviewActivationVerification") - _validate_operational_uuid(verification.tenant_record_id, "tenant_record_id") + verified_tenant_record_id = verification.tenant_record_id + verified_interview_plan_reference = verification.interview_plan_reference + verified_plan_digest = verification.plan_digest + verified_approving_actor_reference = verification.approving_actor_reference + verified_authority_evidence_reference = verification.authority_evidence_reference + verified_authority_evidence_digest = verification.authority_evidence_digest + verified_approved_at = _snapshot_utc_datetime(verification.approved_at, "approved_at") + + _validate_operational_uuid(verified_tenant_record_id, "tenant_record_id") _validate_reference( - verification.interview_plan_reference, + verified_interview_plan_reference, "interview_plan", "interview_plan_reference", ) - _validate_digest(verification.plan_digest, "plan_digest") + _validate_digest(verified_plan_digest, "plan_digest") _validate_reference( - verification.approving_actor_reference, + verified_approving_actor_reference, "actor", "approving_actor_reference", ) _validate_reference( - verification.authority_evidence_reference, + verified_authority_evidence_reference, "activation_verification", "authority_evidence_reference", ) - _validate_digest(verification.authority_evidence_digest, "authority_evidence_digest") + _validate_digest(verified_authority_evidence_digest, "authority_evidence_digest") expected_scope = ( plan_tenant_record_id, interview_plan_reference, plan_digest, approving_actor_reference, + approved_at_snapshot, ) verified_scope = ( - verification.tenant_record_id, - verification.interview_plan_reference, - verification.plan_digest, - verification.approving_actor_reference, + verified_tenant_record_id, + verified_interview_plan_reference, + verified_plan_digest, + verified_approving_actor_reference, + verified_approved_at, ) if verified_scope != expected_scope: - raise ValueError("activation authority returned evidence for a different plan or actor") + raise ValueError( + "activation authority returned evidence for a different plan or actor or approval time" + ) receipt = StructuredInterviewActivationReceipt( tenant_record_id=plan_tenant_record_id, interview_plan_reference=interview_plan_reference, plan_digest=plan_digest, approving_actor_reference=approving_actor_reference, - authority_evidence_reference=verification.authority_evidence_reference, - authority_evidence_digest=verification.authority_evidence_digest, - approved_at=approved_at, + authority_evidence_reference=verified_authority_evidence_reference, + authority_evidence_digest=verified_authority_evidence_digest, + approved_at=approved_at_snapshot, _issuance_token=_ACTIVATION_RECEIPT_ISSUANCE_TOKEN, ) object.__setattr__(receipt, "_issuance_token", None) From 0fef690345897333226ee7dae59a91451ecab556 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:18:05 -0700 Subject: [PATCH 140/216] test(interview-plan): bind verification to approval instant --- packages/interview-plan/tests/test_activation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index ee1b9989b..c13c5004c 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -67,6 +67,7 @@ def verification_for(candidate_plan, **changes): approving_actor_reference=APPROVER, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest=DIGEST_E, + approved_at=APPROVED_AT, ) values.update(changes) return StructuredInterviewActivationVerification(**values) @@ -209,6 +210,7 @@ def test_activation_rejects_verification_subclass_before_evidence_reads_can_dive approving_actor_reference=base.approving_actor_reference, authority_evidence_reference=base.authority_evidence_reference, authority_evidence_digest=base.authority_evidence_digest, + approved_at=base.approved_at, ) object.__setattr__(verification, "_authority_reference_reads", 0) @@ -314,7 +316,7 @@ def test_direct_receipt_construction_fails_closed(field, bad, match): def test_authority_verification_repr_redacts_correlation_evidence(): - """Keep tenant, plan, actor, and evidence correlation identifiers out of routine logs.""" + """Keep tenant, plan, actor, evidence, and reviewed time out of routine logs.""" candidate_plan = plan() verification = verification_for(candidate_plan) @@ -328,5 +330,6 @@ def test_authority_verification_repr_redacts_correlation_evidence(): APPROVER, AUTHORITY_EVIDENCE, DIGEST_E, + APPROVED_AT.isoformat(), ): assert sensitive_value not in text From 927f2a2a240fcaf78fb02e70fc99cc68c35ed3ca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:18:21 -0700 Subject: [PATCH 141/216] test(interview-plan): attest reviewed approval time --- packages/interview-plan/tests/test_activation_approval_time.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_activation_approval_time.py b/packages/interview-plan/tests/test_activation_approval_time.py index 6ec2c5f92..ce3761e62 100644 --- a/packages/interview-plan/tests/test_activation_approval_time.py +++ b/packages/interview-plan/tests/test_activation_approval_time.py @@ -53,7 +53,7 @@ def __init__(self, candidate_plan) -> None: self.calls = [] def verify_activation(self, *, plan, approving_actor_reference, approved_at): - """Record the exact approval instant before returning matching evidence.""" + """Record and attest the exact approval instant before returning evidence.""" self.calls.append((plan, approving_actor_reference, approved_at)) return StructuredInterviewActivationVerification( tenant_record_id=plan.tenant_record_id, @@ -62,6 +62,7 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): approving_actor_reference=approving_actor_reference, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest="e" * 64, + approved_at=approved_at, ) From 21fe4d9d552f83d73527aed06dfc32366769dbc5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:18:32 -0700 Subject: [PATCH 142/216] test(interview-plan): keep mutation fixture contract-current --- packages/interview-plan/tests/test_activation_plan_mutation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/tests/test_activation_plan_mutation.py b/packages/interview-plan/tests/test_activation_plan_mutation.py index 36c513442..e9d4aeda6 100644 --- a/packages/interview-plan/tests/test_activation_plan_mutation.py +++ b/packages/interview-plan/tests/test_activation_plan_mutation.py @@ -28,6 +28,7 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): approving_actor_reference=approving_actor_reference, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest=DIGEST_E, + approved_at=approved_at, ) From 76e9bacc642808ad60f5086e4cb27a537d87bc21 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:18:48 -0700 Subject: [PATCH 143/216] test(interview-plan): keep type fixture contract-current --- packages/interview-plan/tests/test_activation_plan_type.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/tests/test_activation_plan_type.py b/packages/interview-plan/tests/test_activation_plan_type.py index b1f7a4174..8132d3343 100644 --- a/packages/interview-plan/tests/test_activation_plan_type.py +++ b/packages/interview-plan/tests/test_activation_plan_type.py @@ -46,6 +46,7 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): approving_actor_reference=approving_actor_reference, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest=AUTHORITY_DIGEST, + approved_at=approved_at, ) From b043b149b71c54d8d65f5e5412bb2b294649c4d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:19:13 -0700 Subject: [PATCH 144/216] test(interview-plan): cover detached time and authority snapshots --- .../tests/test_activation_integrity_review.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py index 670f16308..085f74ce6 100644 --- a/packages/interview-plan/tests/test_activation_integrity_review.py +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -47,6 +47,22 @@ def tzname(self, value): return "MutableOffsetTimezone" +class UnknownOffsetTimezone(tzinfo): + """Timezone fixture that cannot establish an authoritative UTC offset.""" + + def utcoffset(self, value): + """Return no offset so activation must fail before authority work.""" + return None + + def dst(self, value): + """Return no daylight-saving value for the deliberately invalid fixture.""" + return None + + def tzname(self, value): + """Return a stable diagnostic name for the invalid timezone fixture.""" + return "UnknownOffsetTimezone" + + class ApprovalTimeMutatingAuthority: """Mutate caller-owned timezone state only after receiving the approval snapshot.""" @@ -88,6 +104,28 @@ def test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation(): ) +def test_activation_rejects_naive_approval_time_before_authority_work(): + """A caller must supply an aware approval instant before authoritative review.""" + with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): + activate_structured_interview_plan( + plan=plan(), + authority=RejectingAuthority(), + approving_actor_reference=APPROVER, + approved_at=datetime(2026, 8, 21, 5, 0, 0), + ) + + +def test_activation_rejects_approval_time_with_unknown_offset(): + """An aware-looking timestamp without a concrete UTC offset is not auditable evidence.""" + with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): + activate_structured_interview_plan( + plan=plan(), + authority=RejectingAuthority(), + approving_actor_reference=APPROVER, + approved_at=datetime(2026, 8, 21, 5, 0, 0, tzinfo=UnknownOffsetTimezone()), + ) + + def test_activation_freezes_mutable_timezone_before_authority_and_receipt(): """Authority work cannot make one approved_at value represent two UTC instants.""" mutable_timezone = MutableOffsetTimezone(1) @@ -151,3 +189,20 @@ def test_verification_contract_explicitly_binds_reviewed_approval_time(): field_names = {field.name for field in fields(StructuredInterviewActivationVerification)} assert "approved_at" in field_names + + +def test_activation_rejects_verification_for_different_approval_time(): + """Do not accept authority evidence that attests a different approval instant.""" + candidate_plan = plan() + verification = verification_for( + candidate_plan, + approved_at=APPROVED_AT + timedelta(seconds=1), + ) + + with pytest.raises(ValueError, match="different plan or actor or approval time"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) From c3e3b650964ff191aef85eaec60788a2c5856236 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:19:39 -0700 Subject: [PATCH 145/216] docs(interview-plan): document detached activation evidence --- packages/interview-plan/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 37432d3f4..6a766b83a 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,9 +10,9 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. The package computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing or mismatched issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. +A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. The package computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. -`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, training, and approval-time checks pass. Before invoking that authority, the activation boundary validates a timezone-aware approval instant and rejects any `approved_at` earlier than the exact plan `generated_at`, preventing impossible audit chronology from reaching authoritative verification. The same exact `approved_at` is then passed into `verify_activation(...)`; an adapter must review that instant as part of the authoritative approval and bind it into its verification evidence rather than allowing a caller-only timestamp to be minted into the receipt. Creation-bound plan evidence is checked again after authority work, so authority-time in-memory rewriting also fails closed. A successful authority call returns `StructuredInterviewActivationVerification` bound to the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan/actor before it can emit `StructuredInterviewActivationReceipt`. +`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, training, and approval-time checks pass. Before invoking that authority, the activation boundary detaches caller-owned `approved_at` into one built-in UTC snapshot and compares it with the creation-bound canonical plan time, so a mutable/stateful `tzinfo` cannot change the approved instant after validation. That exact UTC snapshot is passed into `verify_activation(...)` and later into the receipt. The authority must return a `StructuredInterviewActivationVerification` that explicitly carries the same reviewed `approved_at` together with the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. After the authority returns, Orgmetra snapshots every verification field exactly once, validates only those detached values, normalizes the returned approval time, and requires the complete scope—including the approval instant—to equal the requested scope. A retained mutable alias to the authority-returned dataclass therefore cannot change receipt evidence after validation. Creation-bound plan evidence is checked again after authority work, so authority-time in-memory plan rewriting also fails closed. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan, actor, or approval instant before it can emit `StructuredInterviewActivationReceipt`. The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots; receipt mutation or missing issuance evidence fails closed before canonical export. @@ -22,4 +22,4 @@ The plan object itself remains pending human review: `human_confirmation_require For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The process-local seals do not authorize cross-process reconstruction. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries, bind the exact approved instant into authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. +This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The process-local seals do not authorize cross-process reconstruction. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries, bind the exact normalized approval instant into immutable authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. From ba026ad66318e2d8e1e6da95903838a175ea3527 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:19:54 -0700 Subject: [PATCH 146/216] docs(interview-plan): record activation snapshot hardening --- packages/interview-plan/CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index f54e365bb..8555e04d3 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -15,11 +15,12 @@ - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. - Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. -- Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, and approving actor before a receipt can exist. -- Validate `approved_at` before authoritative activation work, reject approval evidence that predates the reviewed plan's `generated_at`, and pass that exact instant into `StructuredInterviewActivationAuthority.verify_activation(...)` so receipt chronology cannot be minted from a timestamp the authoritative adapter never reviewed. +- Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can exist. +- Detach caller-owned `approved_at` into one built-in UTC snapshot before chronology or authority work, pass that snapshot to the authority, require the returned verification to carry the same reviewed instant, and write only that immutable snapshot into the receipt. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. - Snapshot the exact canonical plan evidence before calling the injected activation authority, reject any plan mutation observed across that call, and build verification scope plus the activation receipt from the pre-call snapshot so authority-time in-memory rewriting cannot become approved audit evidence. -- Bind every constructed `StructuredInterviewPlan` to a process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan after construction or if copied/reconstructed objects lack creation-bound issuance evidence. +- Snapshot every exact-type authority-verification field once immediately after the authority returns, validate only those detached values, and use the same detached evidence for scope comparison and receipt issuance so post-validation alias mutation cannot rewrite approved evidence. +- Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. - Bind every successfully issued activation receipt to a process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten or the creation-bound issuance evidence is unavailable. ### Security and privacy @@ -27,6 +28,7 @@ - Reject timestamp/node-bearing UUIDv1 values in package-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. +- Normalize approval-time evidence before crossing the authority boundary so caller-controlled mutable `tzinfo` state cannot make one approved action represent different UTC instants before and after review. - Redact both `StructuredInterviewPlan` and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. - State explicitly that UUID/digest correlation, reference-string inequality, runtime issuance seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From db1ab58180c93cfd6b575a3acd76b1e1c8afb2be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:20:27 -0700 Subject: [PATCH 147/216] docs(adr): define detached activation evidence boundary --- ...0015-governed-structured-interview-plan.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 13fc3cd65..352e47ab7 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan or actor. The approval timestamp is part of the same high-impact evidence boundary: a caller-only timestamp must not be minted into an approved receipt without crossing the authoritative verification call. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. Python dataclass freezing is not an issuance-integrity boundary: `object.__setattr__` can rewrite a plan after successful construction and, without independent creation evidence, the rewritten object can otherwise become the new canonical plan before activation starts. The same low-level mechanism can mutate an already-issued activation receipt after construction. Canonical export for both artifacts therefore needs independent creation-bound evidence outside their writable slots. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. High-impact approval time is itself trust-bearing evidence: caller-controlled mutable timezone state must not make one approval represent two UTC instants, and an authority's return value must explicitly attest the exact normalized instant the receipt will store. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. Python dataclass freezing is not an issuance-integrity boundary: `object.__setattr__` can rewrite a plan or authority-verification object after successful construction, and repeated `__post_init__()` must not be allowed to renew a plan's issuance proof around changed bytes. The same low-level mechanism can mutate an already-issued activation receipt after construction. Canonical export and activation therefore need creation-bound plan evidence plus detached, single-read runtime snapshots at every caller/authority-owned trust boundary. ## Decision @@ -26,13 +26,15 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: `tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. -At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. +At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. Registration is single-use for one live identity: if issuance evidence already exists, repeated initialization fails closed instead of overwriting the original seal. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any approval-time validation or authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type so duck-typed or subclassed plan-shaped objects cannot bypass plan construction invariants. The activation boundary validates a timezone-aware `approved_at`, rejects impossible chronology before authority work, obtains creation-bound canonical plan JSON and SHA-256 together with the tenant and interview-plan reference, and then supplies that exact instant to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. When the authority returns, creation-bound plan validation is repeated; any authority-time mutation therefore fails closed before later scope comparison or receipt construction. All later scope comparison and receipt construction use the pre-call snapshot rather than rereading mutable fields. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks; it must bind the reviewed instant into its immutable verification evidence and raise otherwise. Verification evidence is bound to the exact tenant, interview-plan reference, plan SHA-256 digest, approving actor, opaque `activation_verification:` reference, and verification digest. The activation function rejects non-contract authority results, malformed verification evidence, and well-shaped evidence for a different tenant/plan/digest/actor before producing any approval artifact. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology is compared against `generated_at` parsed from those canonical plan bytes rather than by rereading mutable runtime timestamp state. The exact normalized approval instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. When the authority returns, creation-bound plan validation is repeated; any authority-time plan mutation therefore fails closed. -A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, precision-preserving approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. +`StructuredInterviewActivationVerification` explicitly includes the reviewed `approved_at`. After an exact-type verification object returns, activation reads each verification field exactly once into local snapshots, normalizes its approval time into built-in UTC, validates only those detached values, and compares the complete scope—tenant, interview-plan reference, plan SHA-256 digest, approving actor, and approval instant—to the pre-call request. Receipt construction uses only those detached values. This closes the validation-to-use gap where an authority or retained alias could otherwise use `object.__setattr__` after validation to switch evidence-reference or digest values before receipt construction. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks, bind that exact instant into its immutable verification evidence, and raise otherwise. Non-contract results, malformed verification evidence, or well-shaped evidence for a different tenant/plan/digest/actor/time fail closed before any approval artifact exists. + +A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable dataclass slots, keyed only to the live receipt identity and removed when the receipt is collected. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence or a low-level post-issuance field rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. @@ -43,10 +45,11 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Positive - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. -- Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, or mismatched process-local issuance evidence fails closed before activation can rely on it. -- Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, returns evidence bound to another tenant/plan/digest/actor, or mutates the reviewed plan while authority verification is in progress. +- Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, mismatched, or duplicate process-local issuance evidence fails closed before activation can rely on it. +- Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, returns evidence bound to another tenant/plan/digest/actor/time, mutates the reviewed plan, or mutates a retained verification alias after activation has snapshotted it. +- Caller-owned mutable timezone state cannot alter the receipt's approval instant after validation because activation uses one detached built-in UTC snapshot end to end. - Already-issued receipt objects cannot silently export rewritten canonical evidence after low-level in-memory mutation; missing or mismatched creation-bound issuance evidence fails closed. -- The exact approval instant now crosses the authoritative adapter boundary, so approved receipt chronology cannot be created from a timestamp the authority never reviewed. +- The exact approval instant crosses the authoritative adapter boundary and must return in verification evidence, so approved receipt chronology cannot be created from a timestamp the authority never explicitly attested. - Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. - Candidate PII and assessment values remain outside the planning and activation artifacts. - Packet-owned trust references reject UUIDv1/time-node-bearing suffixes and value-bearing metadata without making the leaf package incompatible with authoritative Orgmetra tenant UUIDs. @@ -57,7 +60,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Costs and constraints - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. -- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence and must bind the supplied approval instant into their immutable authority evidence. +- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence and must bind the supplied normalized approval instant into their immutable authority evidence. - Plan and activation-receipt HMAC seals exist only for the lifetime of each in-process object. They are not portable signatures, durable verification credentials, key-management facilities, or substitutes for persisted authoritative audit evidence; copied or reconstructed plan objects intentionally fail closed unless a future authoritative rehydration contract explicitly re-establishes issuance evidence. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. From aae50068620ffbd63b3c9f0d59427fb0fd7bded9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 21:20:55 -0700 Subject: [PATCH 148/216] docs(traceability): bind exact approval-time evidence --- .../traceability/structured-interview-plan.md | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 8c8fdf9b7..8f5bb7e21 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -11,34 +11,37 @@ | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; a process-local plan issuance seal binds the exact post-construction canonical payload; activation receipt separately binds the exact plan digest and its own bounded positive evidence version plus receipt issuance seal | plan evidence-version regressions, `test_plan_issuance_integrity.py`, plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | +| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; a single-registration process-local plan issuance seal binds the exact post-construction canonical payload; activation receipt separately binds the exact plan digest and its own bounded positive evidence version plus receipt issuance seal | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation`, plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | -| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact approval time, and fixed `approved_for_use` state; the exact approval instant must cross the authoritative verification call rather than being receipt-only caller data | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, plus receipt issuance-integrity regressions | -| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before timestamp checks or authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | -| Constructed plan evidence cannot be silently rewritten | each successful `StructuredInterviewPlan` construction registers a process-local HMAC seal outside plan-writable slots; canonical JSON and SHA-256 require that exact live-object issuance evidence and reject changed fields, discarded evidence, and copied identities | `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, and `test_copied_plan_has_no_transferable_process_local_issuance_evidence` | +| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; `StructuredInterviewActivationVerification` must explicitly return that same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | +| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | +| Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | | The reviewed plan cannot change while the authority is evaluating it | activation snapshots creation-bound canonical plan JSON, digest, tenant, and interview-plan reference before the authority call; any canonical plan mutation during authority execution fails closed before verification/receipt binding can continue | `test_activation_rejects_plan_mutation_during_authority_verification` rewrites a frozen plan through `object.__setattr__` inside an authority fixture and proves no receipt can be issued | -| Activation audit chronology cannot precede the reviewed plan or bypass the authority | timezone-aware `approved_at` is validated before host authority execution, must be greater than or equal to the exact plan `generated_at`, and the same instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` for authoritative review | `test_activation_rejects_approval_before_plan_generation`, `test_activation_sends_approval_time_through_authoritative_verification`, plus normal successful activation coverage | -| Authority evidence cannot be replayed across plan/actor scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, and approving actor supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` | -| Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape` plus exact receipt repr/canonical JSON assertions | +| Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; naive/unknown-offset values fail closed; the same snapshot crosses the authority and receipt boundaries, so mutable `tzinfo` state cannot alter the approved instant | `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | +| Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | +| Authority-owned mutable aliases cannot rewrite receipt evidence after validation | after exact-type verification returns, every verification field is read once into local snapshots; validation, scope comparison, and receipt construction use only those detached values | `test_verification_mutation_after_validation_cannot_rewrite_receipt` synchronizes a retained authority alias mutation after validation and proves the receipt retains the original validated reference/digest | +| Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest + explicit reviewed UTC approval instant; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape`, approval-time binding regressions, plus exact receipt repr/canonical JSON assertions | | Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed; fixed `review_state` and `next_action` require exact built-in strings | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, fixed-governance string-subclass regressions, and `dataclasses.replace(...)` bypass regressions | -| Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__` and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references and evidence digests are absent | +| Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | -| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; creation-bound canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time plan regressions, plan issuance-integrity regressions, and activation canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing-seal, receipt low-level rewrite, and receipt missing-seal regressions | +| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; creation-bound canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot regressions, and canonical/digest assertions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt low-level rewrite, and receipt missing-seal regressions | ## Evidence boundary -The plan object is now creation-bound before activation begins. Successful `StructuredInterviewPlan` construction computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. +The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. -The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type, then validates the approval timestamp and rejects impossible pre-generation approval chronology before it can invoke the host authority. Immediately before that authority call it obtains the creation-bound plan canonical JSON, SHA-256 digest, tenant identity, and interview-plan reference. When control returns, creation-bound validation runs again; any canonical plan mutation fails closed before verification scope or receipt construction can proceed. It passes the exact `approved_at` together with the plan and approving actor into an injected `StructuredInterviewActivationAuthority`; an authority rejection propagates and produces no receipt, a non-contract return type fails closed, malformed verification evidence fails closed, and otherwise well-shaped evidence for another tenant/plan/digest/actor is rejected. This prevents both pre-activation low-level rewriting from becoming new plan truth and authority-time in-memory plan rewriting from becoming approved audit evidence. +The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type. It then detaches caller-owned `approved_at` using one concrete UTC offset into a built-in UTC datetime; naive or unknown-offset values fail before authority work. Creation-bound plan JSON supplies the plan digest and the canonical `generated_at` used for chronology, avoiding a second mutable timestamp source. The exact UTC approval snapshot crosses the injected `StructuredInterviewActivationAuthority` boundary. When control returns, creation-bound plan validation runs again, so authority-time plan mutation fails closed. + +`StructuredInterviewActivationVerification` now explicitly carries the reviewed approval instant. Activation requires the exact verification runtime type, reads each returned field once into local snapshots, normalizes the returned approval time, validates only those detached values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Receipt construction uses only the same detached authority-evidence reference/digest and UTC approval snapshot. A retained alias can therefore mutate the original frozen verification object with `object.__setattr__` after validation without changing the receipt evidence. A different returned approval instant fails closed just like a different plan or actor. A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. Missing issuance evidence or any low-level post-issuance field rewrite therefore fails closed instead of silently producing a different apparently valid receipt. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must bind that reviewed approval instant through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove the orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity before and during authority work, approval-time ordering, approval-time passage into the authority, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity before and during authority work, single-registration issuance, detached approval-time semantics, verification alias isolation, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 52ae7dff79c5b6a65434281b4141d59af093a6d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:01:16 -0700 Subject: [PATCH 149/216] test(interview-plan): bind traceability assertions to contract --- packages/interview-plan/tests/test_traceability_scope.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py index 4187353a2..ffc462997 100644 --- a/packages/interview-plan/tests/test_traceability_scope.py +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -13,12 +13,13 @@ def test_traceability_matches_executable_activation_boundary() -> None: text = TRACEABILITY.read_text(encoding="utf-8") assert "implements an executable activation orchestration boundary" in text - assert "into an injected `StructuredInterviewActivationAuthority`" in text + assert "`StructuredInterviewActivationAuthority`" in text + assert "exact UTC approval snapshot" in text assert "`test_activation_executes_authority_and_returns_immutable_human_receipt`" in text assert "`test_authority_rejection_blocks_activation`" in text assert "`test_activation_rejects_authority_evidence_for_other_scope`" in text assert "`test_activation_rejects_plan_mutation_during_authority_verification`" in text - assert "pre-call snapshot" in text + assert "pre-call request" in text assert "A concrete production adapter remains responsible" in text assert "do **not** prove that a particular deployed adapter already performs database/API resolution correctly" in text assert "No host activation path is implemented in this slice." not in text From f3e8f237ebcd0eac4f16e91b829e25e6e0eceb02 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:04:18 -0700 Subject: [PATCH 150/216] test(interview-plan): prove immutable activation evidence boundaries --- .../tests/test_activation_integrity_review.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py index 085f74ce6..99b9b928b 100644 --- a/packages/interview-plan/tests/test_activation_integrity_review.py +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -2,6 +2,7 @@ from dataclasses import fields from datetime import datetime, timedelta, timezone, tzinfo +from hashlib import sha256 from threading import Event, Thread import json @@ -86,6 +87,31 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): ) +class DetachedPlanEvidenceAuthority: + """Accept only immutable creation-bound plan evidence, never the caller's live plan object.""" + + def __init__(self, verification: StructuredInterviewActivationVerification) -> None: + """Store one matching verification result and initialize the call audit list.""" + self.verification = verification + self.calls = [] + + def verify_activation( + self, + *, + plan_canonical_json: str, + plan_digest: str, + approving_actor_reference: str, + approved_at: datetime, + ) -> StructuredInterviewActivationVerification: + """Prove authority work is bound to detached canonical bytes and their exact digest.""" + assert type(plan_canonical_json) is str + payload = json.loads(plan_canonical_json) + assert payload["question_count"] == 4 + assert plan_digest == sha256(plan_canonical_json.encode("utf-8")).hexdigest() + self.calls.append((plan_canonical_json, plan_digest, approving_actor_reference, approved_at)) + return self.verification + + def test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation(): """Repeated initialization must not legitimize changed bytes on one issued plan identity.""" candidate_plan = plan() @@ -184,6 +210,38 @@ def mutate_retained_verification(): assert payload["authority_evidence_digest"] == DIGEST_E +def test_activation_authority_receives_detached_creation_bound_plan_evidence(): + """Do not expose a live plan that can be changed and restored while authority work runs.""" + candidate_plan = plan() + verification = verification_for(candidate_plan) + authority = DetachedPlanEvidenceAuthority(verification) + + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + + assert len(authority.calls) == 1 + plan_canonical_json, plan_digest, actor_reference, approved_at = authority.calls[0] + assert json.loads(plan_canonical_json)["interview_plan_reference"] == candidate_plan.interview_plan_reference + assert plan_digest == candidate_plan.sha256_digest() + assert actor_reference == APPROVER + assert approved_at == APPROVED_AT + assert receipt.plan_digest == plan_digest + + +def test_verification_contract_cannot_be_rewritten_with_object_setattr(): + """Authority evidence must be runtime-immutable so field reads cannot mix revisions.""" + verification = verification_for(plan()) + + with pytest.raises((AttributeError, TypeError)): + object.__setattr__(verification, "authority_evidence_reference", ALTERNATE_AUTHORITY_EVIDENCE) + + assert verification.authority_evidence_reference == AUTHORITY_EVIDENCE + + def test_verification_contract_explicitly_binds_reviewed_approval_time(): """Authority verification must expose the exact approval instant it attests.""" field_names = {field.name for field in fields(StructuredInterviewActivationVerification)} From ad838931c0206ca8f2abae5250cdf2022a2b4489 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:09:39 -0700 Subject: [PATCH 151/216] fix(interview-plan): detach activation evidence from runtime aliases --- .../src/orgmetra_interview_plan/activation.py | 57 +++++++++-------- .../interview-plan/tests/test_activation.py | 39 +++++++++--- .../tests/test_activation_approval_time.py | 30 ++++++--- .../tests/test_activation_integrity_review.py | 61 ++++--------------- .../tests/test_activation_plan_mutation.py | 58 +++++++++++------- .../tests/test_activation_plan_type.py | 30 ++++----- 6 files changed, 147 insertions(+), 128 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 41e9e7f7f..399c82673 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -1,11 +1,12 @@ """Executable human-approval boundary for governed structured-interview plans. The authority adapter is owned by the Orgmetra host. It MUST return verification -only after re-resolving the plan inside the exact tenant, proving the -requisition-to-Job-to-job-analysis binding, verifying question/mapping/rating -provenance, resolving distinct panel actors, confirming panel eligibility and -training, and reviewing the exact approval instant carried into the receipt. -Any failed authoritative check must raise instead of returning verification evidence. +only after re-resolving detached creation-bound plan evidence inside the exact +tenant, proving the requisition-to-Job-to-job-analysis binding, verifying +question/mapping/rating provenance, resolving distinct panel actors, confirming +panel eligibility and training, and reviewing the exact approval instant carried +into the receipt. Any failed authoritative check must raise instead of returning +verification evidence. """ from __future__ import annotations @@ -16,7 +17,7 @@ import json import secrets from threading import RLock -from typing import Protocol +from typing import NamedTuple, Protocol from weakref import finalize from .plan import ( @@ -78,9 +79,8 @@ def _seal_activation_receipt(payload_json: str) -> str: ).hexdigest() -@dataclass(frozen=True, slots=True, repr=False) -class StructuredInterviewActivationVerification: - """Authoritative host evidence returned only after all activation checks pass.""" +class StructuredInterviewActivationVerification(NamedTuple): + """Runtime-immutable authoritative host evidence returned after activation checks pass.""" tenant_record_id: str interview_plan_reference: str @@ -101,11 +101,12 @@ class StructuredInterviewActivationAuthority(Protocol): def verify_activation( self, *, - plan: StructuredInterviewPlan, + plan_canonical_json: str, + plan_digest: str, approving_actor_reference: str, approved_at: datetime, ) -> StructuredInterviewActivationVerification: - """Return exact-scope evidence bound to the reviewed UTC approval instant.""" + """Verify detached creation-bound plan bytes and return exact-scope evidence.""" @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) @@ -221,11 +222,11 @@ def activate_structured_interview_plan( ) -> StructuredInterviewActivationReceipt: """Activate one exact plan only after authoritative host verification succeeds. - The authority implementation is responsible for the actual tenant-scoped - re-resolution, relationship/provenance/panel checks, and review of the exact - approval instant. This function detaches caller/authority-owned runtime values - before using them as audit evidence, rejects a non-contract result or evidence - bound to a different plan/actor/time, and emits a value-minimized immutable + The authority implementation is responsible for tenant-scoped re-resolution, + relationship/provenance/panel checks, and review of the exact approval instant. + It receives only detached creation-bound canonical plan bytes plus their digest, + never the caller's live plan object. Authority results are runtime-immutable tuple + evidence; this function validates those exact values and emits a value-minimized human-approval receipt only for the exact verified scope. """ if type(plan) is not StructuredInterviewPlan: @@ -239,11 +240,12 @@ def activate_structured_interview_plan( if approved_at_snapshot < plan_generated_at: raise ValueError("approved_at must not precede plan generated_at") plan_digest = sha256(plan_canonical_json.encode("utf-8")).hexdigest() - plan_tenant_record_id = plan.tenant_record_id - interview_plan_reference = plan.interview_plan_reference + plan_tenant_record_id = plan_payload["tenant_record_id"] + interview_plan_reference = plan_payload["interview_plan_reference"] verification = authority.verify_activation( - plan=plan, + plan_canonical_json=plan_canonical_json, + plan_digest=plan_digest, approving_actor_reference=approving_actor_reference, approved_at=approved_at_snapshot, ) @@ -251,13 +253,16 @@ def activate_structured_interview_plan( if type(verification) is not StructuredInterviewActivationVerification: raise TypeError("authority must return StructuredInterviewActivationVerification") - verified_tenant_record_id = verification.tenant_record_id - verified_interview_plan_reference = verification.interview_plan_reference - verified_plan_digest = verification.plan_digest - verified_approving_actor_reference = verification.approving_actor_reference - verified_authority_evidence_reference = verification.authority_evidence_reference - verified_authority_evidence_digest = verification.authority_evidence_digest - verified_approved_at = _snapshot_utc_datetime(verification.approved_at, "approved_at") + ( + verified_tenant_record_id, + verified_interview_plan_reference, + verified_plan_digest, + verified_approving_actor_reference, + verified_authority_evidence_reference, + verified_authority_evidence_digest, + verification_approved_at, + ) = verification + verified_approved_at = _snapshot_utc_datetime(verification_approved_at, "approved_at") _validate_operational_uuid(verified_tenant_record_id, "tenant_record_id") _validate_reference( diff --git a/packages/interview-plan/tests/test_activation.py b/packages/interview-plan/tests/test_activation.py index c13c5004c..8eb784d06 100644 --- a/packages/interview-plan/tests/test_activation.py +++ b/packages/interview-plan/tests/test_activation.py @@ -81,17 +81,31 @@ def __init__(self, verification): self.verification = verification self.calls = [] - def verify_activation(self, *, plan, approving_actor_reference, approved_at): - """Review the approval instant, record plan/actor scope, and return evidence.""" + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): + """Record detached plan/actor scope and return authoritative evidence.""" assert approved_at == APPROVED_AT - self.calls.append((plan, approving_actor_reference)) + self.calls.append((plan_canonical_json, plan_digest, approving_actor_reference)) return self.verification class RejectingAuthority: """Host fixture representing a failed tenant/job/provenance/panel verification.""" - def verify_activation(self, *, plan, approving_actor_reference, approved_at): + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): """Fail closed instead of producing activation evidence.""" raise PermissionError("authoritative activation checks failed") @@ -124,7 +138,7 @@ def __ne__(self, other): def test_activation_executes_authority_and_returns_immutable_human_receipt(): - """Bind human confirmation to the exact plan and authoritative verification evidence.""" + """Bind human confirmation to detached plan bytes and authoritative verification evidence.""" candidate_plan = plan() authority = AllowingAuthority(verification_for(candidate_plan)) @@ -135,7 +149,11 @@ def test_activation_executes_authority_and_returns_immutable_human_receipt(): approved_at=APPROVED_AT, ) - assert authority.calls == [(candidate_plan, APPROVER)] + assert len(authority.calls) == 1 + plan_canonical_json, plan_digest, actor_reference = authority.calls[0] + assert json.loads(plan_canonical_json)["interview_plan_reference"] == INTERVIEW_PLAN + assert plan_digest == candidate_plan.sha256_digest() + assert actor_reference == APPROVER payload = json.loads(receipt.canonical_json()) assert payload["tenant_record_id"] == TENANT assert payload["interview_plan_reference"] == INTERVIEW_PLAN @@ -186,7 +204,14 @@ def test_activation_rejects_non_verification_result(): class WrongAuthority: """Fixture that violates the published authority return type.""" - def verify_activation(self, *, plan, approving_actor_reference, approved_at): + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): """Return a non-contract object to prove type fail-closure.""" return object() diff --git a/packages/interview-plan/tests/test_activation_approval_time.py b/packages/interview-plan/tests/test_activation_approval_time.py index ce3761e62..ca6089b79 100644 --- a/packages/interview-plan/tests/test_activation_approval_time.py +++ b/packages/interview-plan/tests/test_activation_approval_time.py @@ -1,6 +1,7 @@ """Regression for authoritative structured-interview approval-time binding.""" from datetime import datetime, timezone +import json from orgmetra_interview_plan import ( StructuredInterviewActivationVerification, @@ -48,17 +49,25 @@ class TimestampRecordingAuthority: """Require the candidate approval instant to cross the authoritative boundary.""" def __init__(self, candidate_plan) -> None: - """Capture the plan and initialize the authoritative-call audit list.""" + """Keep the expected plan only for test-side correlation and initialize calls.""" self.candidate_plan = candidate_plan self.calls = [] - def verify_activation(self, *, plan, approving_actor_reference, approved_at): - """Record and attest the exact approval instant before returning evidence.""" - self.calls.append((plan, approving_actor_reference, approved_at)) + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): + """Record and attest detached plan evidence plus the exact approval instant.""" + payload = json.loads(plan_canonical_json) + self.calls.append((plan_canonical_json, plan_digest, approving_actor_reference, approved_at)) return StructuredInterviewActivationVerification( - tenant_record_id=plan.tenant_record_id, - interview_plan_reference=plan.interview_plan_reference, - plan_digest=plan.sha256_digest(), + tenant_record_id=payload["tenant_record_id"], + interview_plan_reference=payload["interview_plan_reference"], + plan_digest=plan_digest, approving_actor_reference=approving_actor_reference, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest="e" * 64, @@ -78,5 +87,10 @@ def test_activation_sends_approval_time_through_authoritative_verification(): approved_at=APPROVED_AT, ) - assert authority.calls == [(candidate_plan, APPROVER, APPROVED_AT)] + assert len(authority.calls) == 1 + plan_canonical_json, plan_digest, actor_reference, approved_at = authority.calls[0] + assert json.loads(plan_canonical_json)["interview_plan_reference"] == INTERVIEW_PLAN + assert plan_digest == candidate_plan.sha256_digest() + assert actor_reference == APPROVER + assert approved_at == APPROVED_AT assert "2026-08-21T05:00:00.123456Z" in receipt.canonical_json() diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py index 99b9b928b..8f8817f29 100644 --- a/packages/interview-plan/tests/test_activation_integrity_review.py +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -1,14 +1,11 @@ """Regressions for current-head structured-interview activation integrity findings.""" -from dataclasses import fields from datetime import datetime, timedelta, timezone, tzinfo from hashlib import sha256 -from threading import Event, Thread import json import pytest -import orgmetra_interview_plan.activation as activation_module from orgmetra_interview_plan import ( StructuredInterviewActivationVerification, activate_structured_interview_plan, @@ -25,7 +22,6 @@ ) ALTERNATE_AUTHORITY_EVIDENCE = "activation_verification:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" -ALTERNATE_AUTHORITY_DIGEST = "f" * 64 class MutableOffsetTimezone(tzinfo): @@ -71,15 +67,23 @@ def __init__(self, source_timezone: MutableOffsetTimezone) -> None: """Keep the caller timezone so authority work can mutate it deterministically.""" self.source_timezone = source_timezone - def verify_activation(self, *, plan, approving_actor_reference, approved_at): + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): """Require immutable built-in UTC evidence, then mutate the caller timezone.""" assert approved_at.tzinfo is timezone.utc assert approved_at == APPROVED_AT self.source_timezone.offset_hours = 2 + payload = json.loads(plan_canonical_json) return StructuredInterviewActivationVerification( - tenant_record_id=plan.tenant_record_id, - interview_plan_reference=plan.interview_plan_reference, - plan_digest=plan.sha256_digest(), + tenant_record_id=payload["tenant_record_id"], + interview_plan_reference=payload["interview_plan_reference"], + plan_digest=plan_digest, approving_actor_reference=approving_actor_reference, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest=DIGEST_E, @@ -171,45 +175,6 @@ def test_activation_freezes_mutable_timezone_before_authority_and_receipt(): assert receipt.approved_at == APPROVED_AT -def test_verification_mutation_after_validation_cannot_rewrite_receipt(monkeypatch): - """Receipt construction must use one detached verification snapshot after authority return.""" - candidate_plan = plan() - verification = verification_for(candidate_plan) - validation_finished = Event() - mutation_finished = Event() - original_validate_digest = activation_module._validate_digest - - def synchronized_validate_digest(value, field_name): - """Pause after evidence-digest validation so a retained authority alias can mutate.""" - original_validate_digest(value, field_name) - if field_name == "authority_evidence_digest": - validation_finished.set() - assert mutation_finished.wait(timeout=2) - - def mutate_retained_verification(): - """Rewrite valid authority evidence only after the activation boundary validated it.""" - assert validation_finished.wait(timeout=2) - object.__setattr__(verification, "authority_evidence_reference", ALTERNATE_AUTHORITY_EVIDENCE) - object.__setattr__(verification, "authority_evidence_digest", ALTERNATE_AUTHORITY_DIGEST) - mutation_finished.set() - - monkeypatch.setattr(activation_module, "_validate_digest", synchronized_validate_digest) - mutator = Thread(target=mutate_retained_verification, daemon=True) - mutator.start() - receipt = activate_structured_interview_plan( - plan=candidate_plan, - authority=AllowingAuthority(verification), - approving_actor_reference=APPROVER, - approved_at=APPROVED_AT, - ) - mutator.join(timeout=2) - assert not mutator.is_alive() - - payload = json.loads(receipt.canonical_json()) - assert payload["authority_evidence_reference"] == AUTHORITY_EVIDENCE - assert payload["authority_evidence_digest"] == DIGEST_E - - def test_activation_authority_receives_detached_creation_bound_plan_evidence(): """Do not expose a live plan that can be changed and restored while authority work runs.""" candidate_plan = plan() @@ -244,7 +209,7 @@ def test_verification_contract_cannot_be_rewritten_with_object_setattr(): def test_verification_contract_explicitly_binds_reviewed_approval_time(): """Authority verification must expose the exact approval instant it attests.""" - field_names = {field.name for field in fields(StructuredInterviewActivationVerification)} + field_names = set(StructuredInterviewActivationVerification._fields) assert "approved_at" in field_names diff --git a/packages/interview-plan/tests/test_activation_plan_mutation.py b/packages/interview-plan/tests/test_activation_plan_mutation.py index e9d4aeda6..d6845493d 100644 --- a/packages/interview-plan/tests/test_activation_plan_mutation.py +++ b/packages/interview-plan/tests/test_activation_plan_mutation.py @@ -1,6 +1,6 @@ """Regression tests for activation-time mutation of governed interview plans.""" -import pytest +import json from orgmetra_interview_plan import ( StructuredInterviewActivationVerification, @@ -15,16 +15,32 @@ ) -class MutatingAuthority: - """Authority fixture that rewrites the caller's frozen plan before returning evidence.""" +class RestoringPlanAliasAuthority: + """Authority fixture retaining a live plan alias while reviewing detached plan bytes.""" - def verify_activation(self, *, plan, approving_actor_reference, approved_at): - """Mutate one governed field and attempt to attest the rewritten artifact.""" - object.__setattr__(plan, "question_count", plan.question_count - 1) + def __init__(self, live_plan) -> None: + """Keep an adversarial alias so change-and-restore behavior is deterministic.""" + self.live_plan = live_plan + self.reviewed_question_count = None + + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): + """Mutate and restore the live alias while attesting only detached canonical evidence.""" + original_question_count = self.live_plan.question_count + object.__setattr__(self.live_plan, "question_count", original_question_count - 1) + payload = json.loads(plan_canonical_json) + self.reviewed_question_count = payload["question_count"] + object.__setattr__(self.live_plan, "question_count", original_question_count) return StructuredInterviewActivationVerification( - tenant_record_id=plan.tenant_record_id, - interview_plan_reference=plan.interview_plan_reference, - plan_digest=plan.sha256_digest(), + tenant_record_id=payload["tenant_record_id"], + interview_plan_reference=payload["interview_plan_reference"], + plan_digest=plan_digest, approving_actor_reference=approving_actor_reference, authority_evidence_reference=AUTHORITY_EVIDENCE, authority_evidence_digest=DIGEST_E, @@ -32,19 +48,19 @@ def verify_activation(self, *, plan, approving_actor_reference, approved_at): ) -def test_activation_rejects_plan_mutation_during_authority_verification(): - """Reject an authority that rewrites creation-bound plan evidence.""" +def test_activation_detaches_plan_evidence_from_authority_time_aba_mutation(): + """A live plan change-and-restore cycle cannot alter what the authority reviews or approves.""" candidate_plan = plan() original_digest = candidate_plan.sha256_digest() + authority = RestoringPlanAliasAuthority(candidate_plan) - with pytest.raises(ValueError, match="changed after plan issuance"): - activate_structured_interview_plan( - plan=candidate_plan, - authority=MutatingAuthority(), - approving_actor_reference=APPROVER, - approved_at=APPROVED_AT, - ) + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=authority, + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) - with pytest.raises(ValueError, match="changed after plan issuance"): - candidate_plan.sha256_digest() - assert len(original_digest) == 64 + assert authority.reviewed_question_count == 4 + assert candidate_plan.sha256_digest() == original_digest + assert receipt.plan_digest == original_digest diff --git a/packages/interview-plan/tests/test_activation_plan_type.py b/packages/interview-plan/tests/test_activation_plan_type.py index 8132d3343..6dc952c71 100644 --- a/packages/interview-plan/tests/test_activation_plan_type.py +++ b/packages/interview-plan/tests/test_activation_plan_type.py @@ -4,17 +4,12 @@ import pytest -from orgmetra_interview_plan import ( - StructuredInterviewActivationVerification, - activate_structured_interview_plan, -) +from orgmetra_interview_plan import activate_structured_interview_plan TENANT = "10000000-0000-7000-8000-000000000001" INTERVIEW_PLAN = "interview_plan:11111111-1111-4111-8111-111111111111" APPROVER = "actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd" -AUTHORITY_EVIDENCE = "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" PLAN_DIGEST = "a" * 64 -AUTHORITY_DIGEST = "b" * 64 class DuckTypedPlan: @@ -30,24 +25,23 @@ def sha256_digest(self) -> str: class RecordingAuthority: - """Return internally consistent evidence while recording whether authority work ran.""" + """Record whether authority work incorrectly runs for an untrusted plan-shaped object.""" def __init__(self) -> None: """Initialize the authority call counter.""" self.calls = 0 - def verify_activation(self, *, plan, approving_actor_reference, approved_at): - """Return evidence matching whatever validated activation request was supplied.""" + def verify_activation( + self, + *, + plan_canonical_json, + plan_digest, + approving_actor_reference, + approved_at, + ): + """Fail loudly if a duck-typed plan reaches authoritative work.""" self.calls += 1 - return StructuredInterviewActivationVerification( - tenant_record_id=plan.tenant_record_id, - interview_plan_reference=plan.interview_plan_reference, - plan_digest=plan.sha256_digest(), - approving_actor_reference=approving_actor_reference, - authority_evidence_reference=AUTHORITY_EVIDENCE, - authority_evidence_digest=AUTHORITY_DIGEST, - approved_at=approved_at, - ) + raise AssertionError("duck-typed plan reached authority") def test_activation_rejects_duck_typed_plan_before_authority_work(): From 1d90738a8026594c9753fb0d0916147ce55e8df1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:12:58 -0700 Subject: [PATCH 152/216] docs(interview-plan): align immutable activation evidence contract --- .github/workflows/interview-plan-quality.yml | 8 ++++++++ .../0015-governed-structured-interview-plan.md | 15 ++++++++++----- docs/traceability/structured-interview-plan.md | 14 +++++++------- packages/interview-plan/CHANGELOG.md | 8 +++++--- packages/interview-plan/README.md | 6 ++++-- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/.github/workflows/interview-plan-quality.yml b/.github/workflows/interview-plan-quality.yml index 1edc06a49..f87064f15 100644 --- a/.github/workflows/interview-plan-quality.yml +++ b/.github/workflows/interview-plan-quality.yml @@ -8,6 +8,14 @@ on: - "packages/interview-plan/**" - ".github/requirements/foundation-test.txt" - ".github/workflows/interview-plan-quality.yml" + - ".gitignore" + - ".python-version" + - "conftest.py" + - "packages/conftest.py" + - "pyproject.toml" + - "pytest.ini" + - "setup.cfg" + - "tox.ini" - "docs/adr/0015-governed-structured-interview-plan.md" - "docs/doctoring/structured-interview-plan-references.md" - "docs/traceability/structured-interview-plan.md" diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 352e47ab7..c8ebdc660 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,9 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. High-impact approval time is itself trust-bearing evidence: caller-controlled mutable timezone state must not make one approval represent two UTC instants, and an authority's return value must explicitly attest the exact normalized instant the receipt will store. Because the injected authority receives the exact in-memory plan object, activation must also prevent authority-time mutation from changing the artifact that later scope comparison and receipt construction treat as reviewed evidence. Python dataclass freezing is not an issuance-integrity boundary: `object.__setattr__` can rewrite a plan or authority-verification object after successful construction, and repeated `__post_init__()` must not be allowed to renew a plan's issuance proof around changed bytes. The same low-level mechanism can mutate an already-issued activation receipt after construction. Canonical export and activation therefore need creation-bound plan evidence plus detached, single-read runtime snapshots at every caller/authority-owned trust boundary. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. High-impact approval time is itself trust-bearing evidence: caller-controlled mutable timezone state must not make one approval represent two UTC instants, and an authority's return value must explicitly attest the exact normalized instant the receipt will store. + +Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes, but a post-call seal comparison alone cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, and returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing. ## Decision @@ -30,9 +32,11 @@ At successful plan construction, compute a process-local HMAC over the exact can The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology is compared against `generated_at` parsed from those canonical plan bytes rather than by rereading mutable runtime timestamp state. The exact normalized approval instant is supplied to `StructuredInterviewActivationAuthority.verify_activation(...)` together with the exact plan and approving actor. When the authority returns, creation-bound plan validation is repeated; any authority-time plan mutation therefore fails closed. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology, tenant/interview-plan scope, and SHA-256 are all derived from those canonical bytes instead of rereading live plan attributes. + +The authority receives `plan_canonical_json`, its exact `plan_digest`, the approving actor reference, and the exact normalized approval instant. It does **not** receive the live `StructuredInterviewPlan`. Consequently an external alias may change and restore the caller's plan while the authority runs, but that ABA cycle cannot change the immutable plan revision presented for authoritative review. Activation still repeats creation-bound plan validation after the authority returns so any non-restored live-object mutation fails closed. -`StructuredInterviewActivationVerification` explicitly includes the reviewed `approved_at`. After an exact-type verification object returns, activation reads each verification field exactly once into local snapshots, normalizes its approval time into built-in UTC, validates only those detached values, and compares the complete scope—tenant, interview-plan reference, plan SHA-256 digest, approving actor, and approval instant—to the pre-call request. Receipt construction uses only those detached values. This closes the validation-to-use gap where an authority or retained alias could otherwise use `object.__setattr__` after validation to switch evidence-reference or digest values before receipt construction. The injected host authority must review the supplied approval instant along with all tenant, relationship, provenance, panel, eligibility, and training checks, bind that exact instant into its immutable verification evidence, and raise otherwise. Non-contract results, malformed verification evidence, or well-shaped evidence for a different tenant/plan/digest/actor/time fail closed before any approval artifact exists. +Implement `StructuredInterviewActivationVerification` as a `NamedTuple` carrying tenant, interview-plan reference, plan digest, approving actor, authority-evidence reference/digest, and reviewed `approved_at`. Require the exact runtime type so behavioral subclasses cannot alter reads. Tuple field storage rejects `object.__setattr__`; activation unpacks the exact tuple once, normalizes the returned approval time into built-in UTC, validates those values, and compares the complete scope against the pre-call request. This closes the mixed-revision window possible with a frozen dataclass whose fields could still be rewritten between sequential reads. A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. @@ -46,7 +50,8 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, mismatched, or duplicate process-local issuance evidence fails closed before activation can rely on it. -- Runtime activation orchestration fails closed before authority work for unvalidated plan-shaped objects and also fails closed when the authoritative host rejects, returns the wrong contract type, returns malformed evidence, returns evidence bound to another tenant/plan/digest/actor/time, mutates the reviewed plan, or mutates a retained verification alias after activation has snapshotted it. +- The authoritative adapter reviews detached creation-bound canonical plan evidence rather than a caller-owned live plan object, so temporary change-and-restore mutation cannot substitute a different revision during review. +- Authority verification fields are tuple-immutable at runtime; exact-type enforcement and one-time tuple unpacking prevent mixed authority-evidence revisions between validation reads. - Caller-owned mutable timezone state cannot alter the receipt's approval instant after validation because activation uses one detached built-in UTC snapshot end to end. - Already-issued receipt objects cannot silently export rewritten canonical evidence after low-level in-memory mutation; missing or mismatched creation-bound issuance evidence fails closed. - The exact approval instant crosses the authoritative adapter boundary and must return in verification evidence, so approved receipt chronology cannot be created from a timestamp the authority never explicitly attested. @@ -60,7 +65,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Costs and constraints - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. -- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need their own executable integration evidence and must bind the supplied normalized approval instant into their immutable authority evidence. +- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need executable integration evidence and must bind the supplied canonical plan evidence plus normalized approval instant into their immutable authority evidence. - Plan and activation-receipt HMAC seals exist only for the lifetime of each in-process object. They are not portable signatures, durable verification credentials, key-management facilities, or substitutes for persisted authoritative audit evidence; copied or reconstructed plan objects intentionally fail closed unless a future authoritative rehydration contract explicitly re-establishes issuance evidence. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 8f5bb7e21..dcb0892f6 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -2,7 +2,7 @@ ## Truth status -**Active PR only.** Protected `develop` does not contain this structured-interview capability until the exact integrated PR head passes all required gates and merges. The active PR now contains both the candidate-neutral plan contract and a transport-neutral executable activation boundary; it still does not claim that a concrete production authority adapter is already deployed. +**Active PR only.** Protected `develop` does not contain this structured-interview capability until the exact integrated PR head passes all required gates and merges. The active PR contains both the candidate-neutral plan contract and a transport-neutral executable activation boundary; it still does not claim that a concrete production authority adapter is already deployed. ## Buyer requirement → executable evidence @@ -17,11 +17,11 @@ | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; `StructuredInterviewActivationVerification` must explicitly return that same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | | Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | -| The reviewed plan cannot change while the authority is evaluating it | activation snapshots creation-bound canonical plan JSON, digest, tenant, and interview-plan reference before the authority call; any canonical plan mutation during authority execution fails closed before verification/receipt binding can continue | `test_activation_rejects_plan_mutation_during_authority_verification` rewrites a frozen plan through `object.__setattr__` inside an authority fixture and proves no receipt can be issued | +| Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | | Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; naive/unknown-offset values fail closed; the same snapshot crosses the authority and receipt boundaries, so mutable `tzinfo` state cannot alter the approved instant | `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | | Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | -| Authority-owned mutable aliases cannot rewrite receipt evidence after validation | after exact-type verification returns, every verification field is read once into local snapshots; validation, scope comparison, and receipt construction use only those detached values | `test_verification_mutation_after_validation_cannot_rewrite_receipt` synchronizes a retained authority alias mutation after validation and proves the receipt retains the original validated reference/digest | -| Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest + explicit reviewed UTC approval instant; receipt representation is fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape`, approval-time binding regressions, plus exact receipt repr/canonical JSON assertions | +| Authority verification cannot mix revisions between field reads | the exact verification contract is a runtime-immutable `NamedTuple`; exact-type enforcement rejects behavioral subclasses, `object.__setattr__` cannot rewrite tuple fields, and activation unpacks the tuple once before validation/scope comparison/receipt issuance | `test_verification_contract_cannot_be_rewritten_with_object_setattr` plus `test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge` | +| Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest + explicit reviewed UTC approval instant; verification and receipt representations are fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape`, approval-time binding regressions, plus exact verification/receipt repr and canonical JSON assertions | | Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed; fixed `review_state` and `next_action` require exact built-in strings | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, fixed-governance string-subclass regressions, and `dataclasses.replace(...)` bypass regressions | | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | @@ -33,15 +33,15 @@ The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. -The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type. It then detaches caller-owned `approved_at` using one concrete UTC offset into a built-in UTC datetime; naive or unknown-offset values fail before authority work. Creation-bound plan JSON supplies the plan digest and the canonical `generated_at` used for chronology, avoiding a second mutable timestamp source. The exact UTC approval snapshot crosses the injected `StructuredInterviewActivationAuthority` boundary. When control returns, creation-bound plan validation runs again, so authority-time plan mutation fails closed. +The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; naive or unknown-offset values fail before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. -`StructuredInterviewActivationVerification` now explicitly carries the reviewed approval instant. Activation requires the exact verification runtime type, reads each returned field once into local snapshots, normalizes the returned approval time, validates only those detached values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Receipt construction uses only the same detached authority-evidence reference/digest and UTC approval snapshot. A retained alias can therefore mutate the original frozen verification object with `object.__setattr__` after validation without changing the receipt evidence. A different returned approval instant fails closed just like a different plan or actor. +`StructuredInterviewActivationVerification` explicitly carries the reviewed approval instant and is implemented as a runtime-immutable `NamedTuple`, not a merely frozen dataclass. Activation requires the exact verification runtime type, rejects behavioral subclasses before evidence reads, unpacks the tuple once, normalizes the returned approval time, validates the unpacked values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Tuple field descriptors reject `object.__setattr__`, closing the mixed-revision window where an authority-retained alias could previously rewrite one valid field between sequential reads. A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. Missing issuance evidence or any low-level post-issuance field rewrite therefore fails closed instead of silently producing a different apparently valid receipt. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for the actual authoritative tenant-scoped re-resolution of every plan reference, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity before and during authority work, single-registration issuance, detached approval-time semantics, verification alias isolation, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, detached immutable authority inputs, approval-time semantics, runtime-immutable verification evidence, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 8555e04d3..9f038569c 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -18,10 +18,12 @@ - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can exist. - Detach caller-owned `approved_at` into one built-in UTC snapshot before chronology or authority work, pass that snapshot to the authority, require the returned verification to carry the same reviewed instant, and write only that immutable snapshot into the receipt. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. -- Snapshot the exact canonical plan evidence before calling the injected activation authority, reject any plan mutation observed across that call, and build verification scope plus the activation receipt from the pre-call snapshot so authority-time in-memory rewriting cannot become approved audit evidence. -- Snapshot every exact-type authority-verification field once immediately after the authority returns, validate only those detached values, and use the same detached evidence for scope comparison and receipt issuance so post-validation alias mutation cannot rewrite approved evidence. +- Pass only creation-bound canonical plan JSON plus its exact SHA-256 digest across `StructuredInterviewActivationAuthority`; the authority no longer receives the caller's live plan object, so temporary change-and-restore (ABA) mutation cannot change the plan revision actually reviewed. +- Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. +- Make `StructuredInterviewActivationVerification` a runtime-immutable `NamedTuple`, reject subclasses, and unpack its exact tuple once before validation so `object.__setattr__` cannot create mixed authority-evidence revisions between field reads. - Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. - Bind every successfully issued activation receipt to a process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten or the creation-bound issuance evidence is unavailable. +- Expand Structured Interview Plan Quality path triggers to cover repository-level Python/test configuration and `.gitignore` inputs that can change test collection, execution, or clean-checkout behavior, while retaining package, dependency-lock, workflow, ADR, doctoring, and traceability triggers. ### Security and privacy @@ -29,6 +31,6 @@ - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. - Normalize approval-time evidence before crossing the authority boundary so caller-controlled mutable `tzinfo` state cannot make one approved action represent different UTC instants before and after review. -- Redact both `StructuredInterviewPlan` and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. +- Redact `StructuredInterviewPlan`, `StructuredInterviewActivationVerification`, and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. - State explicitly that UUID/digest correlation, reference-string inequality, runtime issuance seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 6a766b83a..2407dff9d 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -12,7 +12,9 @@ Opaque identities and digests identify the evidence being reviewed; they do not A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. The package computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. -`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants and reach the authoritative adapter. The injected `StructuredInterviewActivationAuthority` is the Orgmetra host boundary and **must fail closed** unless all required tenant, relationship, provenance, panel-identity, eligibility, training, and approval-time checks pass. Before invoking that authority, the activation boundary detaches caller-owned `approved_at` into one built-in UTC snapshot and compares it with the creation-bound canonical plan time, so a mutable/stateful `tzinfo` cannot change the approved instant after validation. That exact UTC snapshot is passed into `verify_activation(...)` and later into the receipt. The authority must return a `StructuredInterviewActivationVerification` that explicitly carries the same reviewed `approved_at` together with the exact tenant, interview-plan reference, plan digest, approving actor, and opaque verification evidence. After the authority returns, Orgmetra snapshots every verification field exactly once, validates only those detached values, normalizes the returned approval time, and requires the complete scope—including the approval instant—to equal the requested scope. A retained mutable alias to the authority-returned dataclass therefore cannot change receipt evidence after validation. Creation-bound plan evidence is checked again after authority work, so authority-time in-memory plan rewriting also fails closed. The activation function rejects a wrong return type, malformed verification evidence, or evidence bound to a different plan, actor, or approval instant before it can emit `StructuredInterviewActivationReceipt`. +`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. + +The authority must return an exact `StructuredInterviewActivationVerification`. This verification contract is a `NamedTuple`, so its trust-bearing tuple fields cannot be rewritten through `object.__setattr__` after return. Activation rejects subclasses before reading evidence, unpacks the exact tuple once, detaches the returned approval time into built-in UTC, validates the unpacked values, and requires the complete scope—including tenant, interview-plan reference, plan digest, approving actor, and approval instant—to equal the request. This removes the mixed-revision window that existed when a merely frozen dataclass could still be rewritten between field reads. The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots; receipt mutation or missing issuance evidence fails closed before canonical export. @@ -22,4 +24,4 @@ The plan object itself remains pending human review: `human_confirmation_require For consistency and immutable audit correlation, evidence digests are lowercase SHA-256, competency and panel tuples must be sorted and unique, and timestamps are timezone-aware RFC 3339 values with fractional precision preserved. Opaque identifiers and references are value-minimized correlation metadata, not anonymous data, and remain subject to purpose-bound authorization, least privilege, retention/export controls, and audit. -This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The process-local seals do not authorize cross-process reconstruction. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published authority contract over authoritative Orgmetra boundaries, bind the exact normalized approval instant into immutable authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. +This package does not itself persist Job Analysis, requisitions, candidates, interview responses, scores, or authoritative identity-resolution results. The process-local seals do not authorize cross-process reconstruction. The authority protocol is an execution contract, not a substitute for a concrete tenant-scoped adapter. Production hosts must implement the published detached-evidence authority contract over authoritative Orgmetra boundaries, bind the exact normalized approval instant into immutable authority evidence, and preserve immutable audit/outbox evidence for any later authoritative write. From fc846e5a38b92f57ddc3586df5df00a348e2208c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:17:25 -0700 Subject: [PATCH 153/216] test(interview-plan): track detached activation traceability --- packages/interview-plan/tests/test_traceability_scope.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/tests/test_traceability_scope.py b/packages/interview-plan/tests/test_traceability_scope.py index ffc462997..fb4db18cb 100644 --- a/packages/interview-plan/tests/test_traceability_scope.py +++ b/packages/interview-plan/tests/test_traceability_scope.py @@ -14,11 +14,11 @@ def test_traceability_matches_executable_activation_boundary() -> None: assert "implements an executable activation orchestration boundary" in text assert "`StructuredInterviewActivationAuthority`" in text - assert "exact UTC approval snapshot" in text + assert "built-in UTC approval snapshot" in text assert "`test_activation_executes_authority_and_returns_immutable_human_receipt`" in text assert "`test_authority_rejection_blocks_activation`" in text assert "`test_activation_rejects_authority_evidence_for_other_scope`" in text - assert "`test_activation_rejects_plan_mutation_during_authority_verification`" in text + assert "`test_activation_detaches_plan_evidence_from_authority_time_aba_mutation`" in text assert "pre-call request" in text assert "A concrete production adapter remains responsible" in text assert "do **not** prove that a particular deployed adapter already performs database/API resolution correctly" in text From 79b890bd21ea4fba1f163aaed0ad92d557126223 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:22:24 -0700 Subject: [PATCH 154/216] test(interview-plan): reproduce mutable generated-time drift --- .../tests/test_temporal_evidence_integrity.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_temporal_evidence_integrity.py b/packages/interview-plan/tests/test_temporal_evidence_integrity.py index f6ee32d61..d1d8ff0f3 100644 --- a/packages/interview-plan/tests/test_temporal_evidence_integrity.py +++ b/packages/interview-plan/tests/test_temporal_evidence_integrity.py @@ -2,7 +2,8 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone, tzinfo +import json import pytest @@ -24,6 +25,26 @@ def isoformat(self, *args, **kwargs) -> str: # type: ignore[no-untyped-def] return "2099-12-31T23:59:59+00:00" +class MutableOffsetTimezone(tzinfo): + """Timezone fixture whose offset can change after plan construction.""" + + def __init__(self, offset_hours: int) -> None: + """Store the mutable offset used by the temporal-integrity regression.""" + self.offset_hours = offset_hours + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Return the currently configured offset.""" + return timedelta(hours=self.offset_hours) + + def dst(self, value): # type: ignore[no-untyped-def] + """Return zero daylight-saving offset for deterministic behavior.""" + return timedelta(0) + + def tzname(self, value): # type: ignore[no-untyped-def] + """Return a stable diagnostic name for the mutable test timezone.""" + return "MutableOffsetTimezone" + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid structured-interview plan input.""" return { @@ -63,6 +84,20 @@ def test_rejects_datetime_subclasses_that_can_forge_recorded_time_evidence() -> build_structured_interview_plan(**kwargs) +def test_plan_detaches_mutable_generated_at_timezone_before_sealing() -> None: + """Caller timezone mutation must not change or invalidate already-issued plan evidence.""" + mutable_timezone = MutableOffsetTimezone(1) + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime(2026, 8, 21, 5, 30, 0, 123456, tzinfo=mutable_timezone) + + candidate_plan = build_structured_interview_plan(**kwargs) + mutable_timezone.offset_hours = 2 + + assert candidate_plan.generated_at.tzinfo is timezone.utc + assert candidate_plan.generated_at == datetime(2026, 8, 21, 4, 30, 0, 123456, tzinfo=timezone.utc) + assert json.loads(candidate_plan.canonical_json())["generated_at"] == "2026-08-21T04:30:00.123456Z" + + def test_activation_receipt_names_approved_at_when_recorded_time_is_invalid() -> None: """Tell callers which approval timestamp must be repaired before activation can proceed.""" with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): From c476e91f021e928335f7e56e5a24917bf2f46213 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:30:15 -0700 Subject: [PATCH 155/216] fix(interview-plan): detach generated time before sealing --- .../src/orgmetra_interview_plan/plan.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index fbc3f7ff8..6566dd6dc 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -7,7 +7,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 hmac import json @@ -105,6 +105,17 @@ def _validate_digest(value: str, field_name: str) -> None: raise ValueError(f"{field_name} must be lowercase SHA-256 hex") +def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: + """Detach one caller-owned aware datetime into an immutable built-in 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") + offset = value.utcoffset() + if type(offset) is not timedelta: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") + local_naive = value.replace(tzinfo=None) + return (local_naive - offset).replace(tzinfo=timezone.utc) + + def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> str: """Render an aware instant as UTC RFC 3339 text with a field-specific error.""" if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: @@ -179,7 +190,8 @@ def __post_init__(self) -> None: _validate_code(self.reason_code, "reason_code") if self.reason_code not in _ALLOWED_REASON_CODES: raise ValueError("reason_code must use a reviewed non-sensitive interview-plan reason") - _canonical_timestamp(self.generated_at) + generated_at_snapshot = _snapshot_utc_datetime(self.generated_at, "generated_at") + object.__setattr__(self, "generated_at", generated_at_snapshot) if type(self.evidence_version) is not int or not 1 <= self.evidence_version <= _MAX_EVIDENCE_VERSION: raise ValueError("evidence_version must be an integer from 1 through 2147483647") if self.human_confirmation_required is not True: From 6e923b37a380b9a748e76461374d44d21fad4721 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:31:03 -0700 Subject: [PATCH 156/216] docs(interview-plan): document generated-time detachment --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 2407dff9d..34d6d4c05 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,7 +10,7 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. The package computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. +A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. `activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. From afc323ab27d65c9968e38758fac650840f3b328f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:31:50 -0700 Subject: [PATCH 157/216] docs(interview-plan): record generated-time integrity repair --- packages/interview-plan/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 9f038569c..2f4358ddb 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -16,6 +16,7 @@ - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. - Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can exist. +- Detach caller-owned plan `generated_at` into one built-in UTC snapshot before creation-seal registration so later mutation of a custom `tzinfo` cannot change or invalidate an already-issued plan instant. - Detach caller-owned `approved_at` into one built-in UTC snapshot before chronology or authority work, pass that snapshot to the authority, require the returned verification to carry the same reviewed instant, and write only that immutable snapshot into the receipt. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. - Pass only creation-bound canonical plan JSON plus its exact SHA-256 digest across `StructuredInterviewActivationAuthority`; the authority no longer receives the caller's live plan object, so temporary change-and-restore (ABA) mutation cannot change the plan revision actually reviewed. @@ -30,7 +31,7 @@ - Reject timestamp/node-bearing UUIDv1 values in package-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. -- Normalize approval-time evidence before crossing the authority boundary so caller-controlled mutable `tzinfo` state cannot make one approved action represent different UTC instants before and after review. +- Normalize both plan-generation and approval-time evidence before creation sealing or authority review so caller-controlled mutable `tzinfo` state cannot make one governed instant later represent a different UTC instant. - Redact `StructuredInterviewPlan`, `StructuredInterviewActivationVerification`, and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. - State explicitly that UUID/digest correlation, reference-string inequality, runtime issuance seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From 296b0fb3b872f6d26ee982e4390c1b2ce02d7ce3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:32:17 -0700 Subject: [PATCH 158/216] docs(adr): bind plan generation time to UTC snapshot --- docs/adr/0015-governed-structured-interview-plan.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index c8ebdc660..7456f22d4 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. High-impact approval time is itself trust-bearing evidence: caller-controlled mutable timezone state must not make one approval represent two UTC instants, and an authority's return value must explicitly attest the exact normalized instant the receipt will store. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant, and an authority's return value must explicitly attest the exact normalized approval instant the receipt will store. Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes, but a post-call seal comparison alone cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, and returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing. @@ -28,6 +28,8 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: `tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. +Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. The plan stores that UTC snapshot, not the caller's mutable `tzinfo` object. A later timezone-state change therefore cannot change the plan's canonical instant or invalidate an otherwise unchanged issued plan. + At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. Registration is single-use for one live identity: if issuance evidence already exists, repeated initialization fails closed instead of overwriting the original seal. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. @@ -40,7 +42,7 @@ Implement `StructuredInterviewActivationVerification` as a `NamedTuple` carrying A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. -At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable dataclass slots, keyed only to the live receipt identity and removed when the receipt is collected. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence or a low-level post-issuance field rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. +At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable slots, keyed only to the live receipt identity and removed when the receipt is collected. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence or a low-level post-issuance field rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. The plan and activation receipt are candidate-neutral. They contain no candidate identity, response, score, demographic attribute, compensation value, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or scientifically adequate. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. @@ -49,6 +51,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate ### Positive - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. +- Caller-owned mutable timezone state cannot change or invalidate an already-issued plan generation instant because construction stores one detached built-in UTC snapshot before sealing. - Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, mismatched, or duplicate process-local issuance evidence fails closed before activation can rely on it. - The authoritative adapter reviews detached creation-bound canonical plan evidence rather than a caller-owned live plan object, so temporary change-and-restore mutation cannot substitute a different revision during review. - Authority verification fields are tuple-immutable at runtime; exact-type enforcement and one-time tuple unpacking prevent mixed authority-evidence revisions between validation reads. From 2afcb01eec3a46cbd1e45fe9b0184273f6c08c72 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:32:54 -0700 Subject: [PATCH 159/216] docs(traceability): bind generated time to mutable-timezone regression --- docs/traceability/structured-interview-plan.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index dcb0892f6..f4f1bc4fc 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -16,8 +16,9 @@ | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; `StructuredInterviewActivationVerification` must explicitly return that same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | -| Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | +| Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction first detaches `generated_at` to a built-in UTC instant, then registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | | Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | +| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; naive/unknown-offset values fail closed, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing` plus naive/unknown-offset/offset/fractional-time plan regressions | | Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; naive/unknown-offset values fail closed; the same snapshot crosses the authority and receipt boundaries, so mutable `tzinfo` state cannot alter the approved instant | `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | | Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | | Authority verification cannot mix revisions between field reads | the exact verification contract is a runtime-immutable `NamedTuple`; exact-type enforcement rejects behavioral subclasses, `object.__setattr__` cannot rewrite tuple fields, and activation unpacks the tuple once before validation/scope comparison/receipt issuance | `test_verification_contract_cannot_be_rewritten_with_object_setattr` plus `test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge` | @@ -26,12 +27,12 @@ | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | -| Audit correlation is deterministic without losing temporal precision | timezone-aware precision-preserving UTC RFC 3339; creation-bound canonical JSON; exact SHA-256 for plan and activation receipt | naive/unknown-offset/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot regressions, and canonical/digest assertions | +| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan mutable-timezone/naive/unknown-offset/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot regressions, and canonical/digest assertions | | Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt low-level rewrite, and receipt missing-seal regressions | ## Evidence boundary -The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. +The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction first validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Construction then computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; naive or unknown-offset values fail before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. @@ -41,7 +42,7 @@ A successfully issued activation receipt also receives a creation-bound HMAC sea The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, detached immutable authority inputs, approval-time semantics, runtime-immutable verification evidence, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable plan-generation time, detached immutable authority inputs, approval-time semantics, runtime-immutable verification evidence, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 460ed3c5c9e762d4171d7e24bfab4e0f2e4acf33 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:36:24 -0700 Subject: [PATCH 160/216] test(interview-plan): reproduce UTC boundary overflow --- .../tests/test_temporal_evidence_integrity.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/interview-plan/tests/test_temporal_evidence_integrity.py b/packages/interview-plan/tests/test_temporal_evidence_integrity.py index d1d8ff0f3..647734fcc 100644 --- a/packages/interview-plan/tests/test_temporal_evidence_integrity.py +++ b/packages/interview-plan/tests/test_temporal_evidence_integrity.py @@ -9,6 +9,7 @@ from orgmetra_interview_plan import ( StructuredInterviewActivationReceipt, + activate_structured_interview_plan, build_structured_interview_plan, ) @@ -45,6 +46,14 @@ def tzname(self, value): # type: ignore[no-untyped-def] return "MutableOffsetTimezone" +class RejectUnexpectedAuthorityCall: + """Fail if activation reaches authority work after invalid time evidence.""" + + def verify_activation(self, **kwargs): # type: ignore[no-untyped-def] + """Prove invalid approval-time normalization fails before authority work.""" + raise AssertionError("authority must not run for out-of-range approval time") + + def valid_kwargs() -> dict[str, object]: """Return one otherwise valid structured-interview plan input.""" return { @@ -98,6 +107,28 @@ def test_plan_detaches_mutable_generated_at_timezone_before_sealing() -> None: assert json.loads(candidate_plan.canonical_json())["generated_at"] == "2026-08-21T04:30:00.123456Z" +def test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error() -> None: + """Out-of-range UTC conversion must fail as governed plan validation, not OverflowError.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime.min.replace(tzinfo=timezone(timedelta(hours=1))) + + with pytest.raises(ValueError, match="generated_at must be an exact timezone-aware datetime"): + build_structured_interview_plan(**kwargs) + + +def test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority() -> None: + """Out-of-range approval UTC conversion must fail before authoritative side effects.""" + candidate_plan = build_structured_interview_plan(**valid_kwargs()) + + with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=RejectUnexpectedAuthorityCall(), + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + approved_at=datetime.max.replace(tzinfo=timezone(-timedelta(hours=1))), + ) + + def test_activation_receipt_names_approved_at_when_recorded_time_is_invalid() -> None: """Tell callers which approval timestamp must be repaired before activation can proceed.""" with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): From 88783537183f06e08aceef6893ad3b41d2d30f13 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:38:22 -0700 Subject: [PATCH 161/216] fix(interview-plan): fail closed on UTC range overflow --- .../interview-plan/src/orgmetra_interview_plan/plan.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 6566dd6dc..9f2614d60 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -106,14 +106,18 @@ def _validate_digest(value: str, field_name: str) -> None: def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: - """Detach one caller-owned aware datetime into an immutable built-in UTC instant.""" + """Detach one caller-owned aware datetime into a representable built-in 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") offset = value.utcoffset() if type(offset) is not timedelta: raise ValueError(f"{field_name} must be an exact timezone-aware datetime") local_naive = value.replace(tzinfo=None) - return (local_naive - offset).replace(tzinfo=timezone.utc) + try: + normalized = local_naive - offset + except OverflowError as exc: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") from exc + return normalized.replace(tzinfo=timezone.utc) def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> str: From 054c3a7e3323261f39b17bc39265e3bf9b8792a4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:39:07 -0700 Subject: [PATCH 162/216] fix(interview-plan): normalize approval range overflow --- .../src/orgmetra_interview_plan/activation.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 399c82673..9e8b56f25 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -40,14 +40,18 @@ def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: - """Detach one caller-owned aware datetime into an immutable built-in UTC instant.""" + """Detach one caller-owned aware datetime into a representable built-in 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") offset = value.utcoffset() if type(offset) is not timedelta: raise ValueError(f"{field_name} must be an exact timezone-aware datetime") local_naive = value.replace(tzinfo=None) - return (local_naive - offset).replace(tzinfo=timezone.utc) + try: + normalized = local_naive - offset + except OverflowError as exc: + raise ValueError(f"{field_name} must be an exact timezone-aware datetime") from exc + return normalized.replace(tzinfo=timezone.utc) def _discard_activation_receipt_seal(receipt_id: int) -> None: From 3651948dc91f65ef63befb3823a6ab4c7306806e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:41:45 -0700 Subject: [PATCH 163/216] docs(interview-plan): document UTC range fail-closure --- packages/interview-plan/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 34d6d4c05..8ddb09670 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,9 +10,9 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. +A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. If that offset would place the instant outside Python's representable `datetime` range, construction fails with the same field-specific `ValueError` as other invalid recorded-time evidence rather than leaking `OverflowError`. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. -`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. +`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. Approval-time normalization that would leave Python's representable `datetime` range fails as field-specific validation before any authority call, so invalid boundary timestamps cannot escape as runtime arithmetic errors or trigger authoritative side effects. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. The authority must return an exact `StructuredInterviewActivationVerification`. This verification contract is a `NamedTuple`, so its trust-bearing tuple fields cannot be rewritten through `object.__setattr__` after return. Activation rejects subclasses before reading evidence, unpacks the exact tuple once, detaches the returned approval time into built-in UTC, validates the unpacked values, and requires the complete scope—including tenant, interview-plan reference, plan digest, approving actor, and approval instant—to equal the request. This removes the mixed-revision window that existed when a merely frozen dataclass could still be rewritten between field reads. From 07b3bca3315dc6f6d47acad87d37c38d835f5b5b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:42:10 -0700 Subject: [PATCH 164/216] docs(adr): fail closed on unrepresentable UTC instants --- docs/adr/0015-governed-structured-interview-plan.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 7456f22d4..756994897 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant, and an authority's return value must explicitly attest the exact normalized approval instant the receipt will store. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant, and an authority's return value must explicitly attest the exact normalized approval instant the receipt will store. Boundary timestamps whose offsets would normalize outside Python's representable `datetime` range are invalid governed evidence and must fail before sealing or authoritative side effects rather than leaking arithmetic exceptions. Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes, but a post-call seal comparison alone cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, and returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing. @@ -28,13 +28,13 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: `tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. -Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. The plan stores that UTC snapshot, not the caller's mutable `tzinfo` object. A later timezone-state change therefore cannot change the plan's canonical instant or invalidate an otherwise unchanged issued plan. +Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. The plan stores that UTC snapshot, not the caller's mutable `tzinfo` object. A later timezone-state change therefore cannot change the plan's canonical instant or invalidate an otherwise unchanged issued plan. If applying the offset would move the instant outside Python's representable `datetime` range, convert that arithmetic failure into the same field-specific `ValueError` used for invalid timezone-aware evidence and do not register issuance evidence. At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. Registration is single-use for one live identity: if issuance evidence already exists, repeated initialization fails closed instead of overwriting the original seal. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology, tenant/interview-plan scope, and SHA-256 are all derived from those canonical bytes instead of rereading live plan attributes. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology, tenant/interview-plan scope, and SHA-256 are all derived from those canonical bytes instead of rereading live plan attributes. If approval-time normalization would exceed Python's representable `datetime` range, fail with field-specific `ValueError` before any authority call. The authority receives `plan_canonical_json`, its exact `plan_digest`, the approving actor reference, and the exact normalized approval instant. It does **not** receive the live `StructuredInterviewPlan`. Consequently an external alias may change and restore the caller's plan while the authority runs, but that ABA cycle cannot change the immutable plan revision presented for authoritative review. Activation still repeats creation-bound plan validation after the authority returns so any non-restored live-object mutation fails closed. @@ -52,6 +52,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Caller-owned mutable timezone state cannot change or invalidate an already-issued plan generation instant because construction stores one detached built-in UTC snapshot before sealing. +- Unrepresentable UTC normalization at `datetime` boundaries fails as field-specific governed validation before plan issuance or activation authority side effects. - Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, mismatched, or duplicate process-local issuance evidence fails closed before activation can rely on it. - The authoritative adapter reviews detached creation-bound canonical plan evidence rather than a caller-owned live plan object, so temporary change-and-restore mutation cannot substitute a different revision during review. - Authority verification fields are tuple-immutable at runtime; exact-type enforcement and one-time tuple unpacking prevent mixed authority-evidence revisions between validation reads. From 40a391afb97acdfe661479be11e5fd54d205e1e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:42:42 -0700 Subject: [PATCH 165/216] docs(traceability): bind UTC boundary overflow regressions --- docs/traceability/structured-interview-plan.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index f4f1bc4fc..4a2f627ac 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -18,8 +18,8 @@ | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | | Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction first detaches `generated_at` to a built-in UTC instant, then registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | | Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | -| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; naive/unknown-offset values fail closed, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing` plus naive/unknown-offset/offset/fractional-time plan regressions | -| Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; naive/unknown-offset values fail closed; the same snapshot crosses the authority and receipt boundaries, so mutable `tzinfo` state cannot alter the approved instant | `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | +| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; naive/unknown-offset and out-of-range UTC normalization fail closed, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | +| Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; naive/unknown-offset and out-of-range UTC normalization fail closed before authority side effects; the same snapshot crosses the authority and receipt boundaries, so mutable `tzinfo` state cannot alter the approved instant | `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | | Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | | Authority verification cannot mix revisions between field reads | the exact verification contract is a runtime-immutable `NamedTuple`; exact-type enforcement rejects behavioral subclasses, `object.__setattr__` cannot rewrite tuple fields, and activation unpacks the tuple once before validation/scope comparison/receipt issuance | `test_verification_contract_cannot_be_rewritten_with_object_setattr` plus `test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest + explicit reviewed UTC approval instant; verification and receipt representations are fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape`, approval-time binding regressions, plus exact verification/receipt repr and canonical JSON assertions | @@ -27,14 +27,14 @@ | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | -| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan mutable-timezone/naive/unknown-offset/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot regressions, and canonical/digest assertions | +| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; unrepresentable UTC normalization is rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot/range-boundary regressions, and canonical/digest assertions | | Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt low-level rewrite, and receipt missing-seal regressions | ## Evidence boundary -The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction first validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Construction then computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. +The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction first validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the field-specific governed `ValueError` and stops before issuance-seal registration. Construction then computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. -The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; naive or unknown-offset values fail before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. +The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; naive, unknown-offset, or UTC normalization beyond the representable datetime range fails as field-specific validation before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. `StructuredInterviewActivationVerification` explicitly carries the reviewed approval instant and is implemented as a runtime-immutable `NamedTuple`, not a merely frozen dataclass. Activation requires the exact verification runtime type, rejects behavioral subclasses before evidence reads, unpacks the tuple once, normalizes the returned approval time, validates the unpacked values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Tuple field descriptors reject `object.__setattr__`, closing the mixed-revision window where an authority-retained alias could previously rewrite one valid field between sequential reads. @@ -42,7 +42,7 @@ A successfully issued activation receipt also receives a creation-bound HMAC sea The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable plan-generation time, detached immutable authority inputs, approval-time semantics, runtime-immutable verification evidence, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From b7535b36f2eb6e275fadf589e1abebc7a7607903 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:42:58 -0700 Subject: [PATCH 166/216] docs(interview-plan): record UTC boundary validation repair --- packages/interview-plan/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 2f4358ddb..a550fba09 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -18,6 +18,7 @@ - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can exist. - Detach caller-owned plan `generated_at` into one built-in UTC snapshot before creation-seal registration so later mutation of a custom `tzinfo` cannot change or invalidate an already-issued plan instant. - Detach caller-owned `approved_at` into one built-in UTC snapshot before chronology or authority work, pass that snapshot to the authority, require the returned verification to carry the same reviewed instant, and write only that immutable snapshot into the receipt. +- Normalize UTC-offset arithmetic that would cross Python `datetime` bounds into field-specific `ValueError` for both plan generation and approval time, failing before plan issuance or activation authority side effects instead of leaking `OverflowError`. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. - Pass only creation-bound canonical plan JSON plus its exact SHA-256 digest across `StructuredInterviewActivationAuthority`; the authority no longer receives the caller's live plan object, so temporary change-and-restore (ABA) mutation cannot change the plan revision actually reviewed. - Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. From 32f5211c4e10ce903ceedbaa9bf49b3ddf179560 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:48:35 -0700 Subject: [PATCH 167/216] test(interview-plan): reproduce receipt seal renewal --- .../tests/test_activation_integrity_review.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py index 8f8817f29..866b26e77 100644 --- a/packages/interview-plan/tests/test_activation_integrity_review.py +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -6,6 +6,7 @@ import pytest +import orgmetra_interview_plan.activation as activation_module from orgmetra_interview_plan import ( StructuredInterviewActivationVerification, activate_structured_interview_plan, @@ -134,6 +135,30 @@ def test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation(): ) +def test_existing_receipt_identity_cannot_renew_issuance_seal_after_mutation(): + """Repeated receipt initialization must not legitimize changed post-authority evidence.""" + candidate_plan = plan() + receipt = activate_structured_interview_plan( + plan=candidate_plan, + authority=AllowingAuthority(verification_for(candidate_plan)), + approving_actor_reference=APPROVER, + approved_at=APPROVED_AT, + ) + object.__setattr__(receipt, "plan_digest", "f" * 64) + object.__setattr__( + receipt, + "_issuance_token", + activation_module._ACTIVATION_RECEIPT_ISSUANCE_TOKEN, + ) + + with pytest.raises(ValueError, match="issuance evidence already exists"): + receipt.__post_init__() + + object.__setattr__(receipt, "_issuance_token", None) + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + receipt.canonical_json() + + def test_activation_rejects_naive_approval_time_before_authority_work(): """A caller must supply an aware approval instant before authoritative review.""" with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): From 7911661a69da210a3b1b8ac688895c821ae01307 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:49:55 -0700 Subject: [PATCH 168/216] fix(interview-plan): preserve receipt issuance seal --- .../src/orgmetra_interview_plan/activation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 9e8b56f25..19bea5946 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -61,9 +61,13 @@ def _discard_activation_receipt_seal(receipt_id: int) -> None: def _register_activation_receipt_seal(receipt: object, seal: str) -> None: - """Bind one live receipt identity to evidence outside receipt-writable slots.""" + """Bind one live receipt identity once to evidence outside receipt-writable slots.""" receipt_id = id(receipt) with _ACTIVATION_RECEIPT_SEALS_LOCK: + if receipt_id in _ACTIVATION_RECEIPT_SEALS: + raise ValueError( + "structured interview activation receipt issuance evidence already exists" + ) _ACTIVATION_RECEIPT_SEALS[receipt_id] = seal finalize(receipt, _discard_activation_receipt_seal, receipt_id) From 3fd52e3789ddb7aa13dfd80b93cb0a872e2576da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:50:25 -0700 Subject: [PATCH 169/216] docs(interview-plan): record receipt single-registration seal --- packages/interview-plan/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index a550fba09..68b0c9cbb 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -24,7 +24,7 @@ - Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. - Make `StructuredInterviewActivationVerification` a runtime-immutable `NamedTuple`, reject subclasses, and unpack its exact tuple once before validation so `object.__setattr__` cannot create mixed authority-evidence revisions between field reads. - Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. -- Bind every successfully issued activation receipt to a process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten or the creation-bound issuance evidence is unavailable. +- Bind every successfully issued activation receipt to a single-registration process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten, if creation-bound issuance evidence is unavailable, or if repeated initialization attempts to renew the seal for the same live receipt identity. - Expand Structured Interview Plan Quality path triggers to cover repository-level Python/test configuration and `.gitignore` inputs that can change test collection, execution, or clean-checkout behavior, while retaining package, dependency-lock, workflow, ADR, doctoring, and traceability triggers. ### Security and privacy From a9ccc1ea4d7c55a0105860f08d43ff191bf9f6f3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:51:04 -0700 Subject: [PATCH 170/216] docs(interview-plan): document receipt seal single registration --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 8ddb09670..97ec7e135 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -16,7 +16,7 @@ A successfully constructed `StructuredInterviewPlan` is creation-bound before ac The authority must return an exact `StructuredInterviewActivationVerification`. This verification contract is a `NamedTuple`, so its trust-bearing tuple fields cannot be rewritten through `object.__setattr__` after return. Activation rejects subclasses before reading evidence, unpacks the exact tuple once, detaches the returned approval time into built-in UTC, validates the unpacked values, and requires the complete scope—including tenant, interview-plan reference, plan digest, approving actor, and approval instant—to equal the request. This removes the mixed-revision window that existed when a merely frozen dataclass could still be rewritten between field reads. -The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots; receipt mutation or missing issuance evidence fails closed before canonical export. +The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots. One live receipt identity may register issuance evidence only once, so low-level field mutation followed by repeated initialization cannot replace the original seal with a seal over altered post-authority evidence. Receipt mutation, missing issuance evidence, or duplicate issuance registration fails closed before canonical export. The plan object itself remains pending human review: `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and its next action requires authoritative resolution before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed plan invariants. The activation receipt is separate evidence and does not mutate or rewrite the reviewed plan. From 885d787695055ed3088d46c66d1dc7c6cbeae5be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:51:44 -0700 Subject: [PATCH 171/216] docs(adr): prevent activation receipt seal renewal --- docs/adr/0015-governed-structured-interview-plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 756994897..c77dae86a 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -11,7 +11,7 @@ A structured interview is stronger when the assessed competencies come from curr Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant, and an authority's return value must explicitly attest the exact normalized approval instant the receipt will store. Boundary timestamps whose offsets would normalize outside Python's representable `datetime` range are invalid governed evidence and must fail before sealing or authoritative side effects rather than leaking arithmetic exceptions. -Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes, but a post-call seal comparison alone cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, and returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing. +Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes only if issuance history itself cannot be rewritten: repeated initialization must not replace the original live-object seal with a seal over altered post-authority evidence. A post-call seal comparison alone also cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing, and plan/receipt seal registration must be single-use per live identity. ## Decision @@ -42,7 +42,7 @@ Implement `StructuredInterviewActivationVerification` as a `NamedTuple` carrying A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. -At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable slots, keyed only to the live receipt identity and removed when the receipt is collected. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence or a low-level post-issuance field rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. +At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable slots, keyed only to the live receipt identity and removed when the receipt is collected. Registration is single-use for one live receipt identity: if issuance evidence already exists, repeated initialization fails closed before assignment or finalizer registration, leaving the original authority-bound seal intact. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence, a low-level post-issuance field rewrite, or an attempt to renew issuance evidence after such a rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. The plan and activation receipt are candidate-neutral. They contain no candidate identity, response, score, demographic attribute, compensation value, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or scientifically adequate. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. @@ -57,7 +57,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - The authoritative adapter reviews detached creation-bound canonical plan evidence rather than a caller-owned live plan object, so temporary change-and-restore mutation cannot substitute a different revision during review. - Authority verification fields are tuple-immutable at runtime; exact-type enforcement and one-time tuple unpacking prevent mixed authority-evidence revisions between validation reads. - Caller-owned mutable timezone state cannot alter the receipt's approval instant after validation because activation uses one detached built-in UTC snapshot end to end. -- Already-issued receipt objects cannot silently export rewritten canonical evidence after low-level in-memory mutation; missing or mismatched creation-bound issuance evidence fails closed. +- Already-issued receipt objects cannot silently export rewritten canonical evidence or renew their issuance history after low-level mutation; missing, mismatched, or duplicate creation-bound issuance evidence fails closed. - The exact approval instant crosses the authoritative adapter boundary and must return in verification evidence, so approved receipt chronology cannot be created from a timestamp the authority never explicitly attested. - Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. - Candidate PII and assessment values remain outside the planning and activation artifacts. From c67c8695b7565e90d957063dbfa260eb3917a969 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 22:52:17 -0700 Subject: [PATCH 172/216] docs(traceability): bind receipt seal renewal regression --- docs/traceability/structured-interview-plan.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 4a2f627ac..28c3376d4 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -11,7 +11,7 @@ | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; a single-registration process-local plan issuance seal binds the exact post-construction canonical payload; activation receipt separately binds the exact plan digest and its own bounded positive evidence version plus receipt issuance seal | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation`, plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | +| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; single-registration process-local seals bind the exact post-construction plan and post-authority activation-receipt payloads | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation`, `test_existing_receipt_identity_cannot_renew_issuance_seal_after_mutation`, plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; `StructuredInterviewActivationVerification` must explicitly return that same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | @@ -28,7 +28,7 @@ | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | | Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; unrepresentable UTC normalization is rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot/range-boundary regressions, and canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt low-level rewrite, and receipt missing-seal regressions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify single-registration creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt low-level rewrite/missing-seal/reseal regressions | ## Evidence boundary @@ -38,11 +38,11 @@ The active PR implements an executable activation orchestration boundary, not me `StructuredInterviewActivationVerification` explicitly carries the reviewed approval instant and is implemented as a runtime-immutable `NamedTuple`, not a merely frozen dataclass. Activation requires the exact verification runtime type, rejects behavioral subclasses before evidence reads, unpacks the tuple once, normalizes the returned approval time, validates the unpacked values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Tuple field descriptors reject `object.__setattr__`, closing the mixed-revision window where an authority-retained alias could previously rewrite one valid field between sequential reads. -A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. Missing issuance evidence or any low-level post-issuance field rewrite therefore fails closed instead of silently producing a different apparently valid receipt. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. +A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Registration for one live receipt identity is single-use: a second registration attempt fails before overwriting the original seal or registering another finalizer. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. A low-level field mutation followed by a forged/repeated `__post_init__()` therefore cannot erase issuance history and legitimize altered post-authority evidence; the duplicate registration fails and the original seal remains, so canonical export still rejects the changed receipt. Missing issuance evidence or any low-level post-issuance field rewrite also fails closed. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, single-registration creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 1a8a3cfb063c0c155e93f67bcbb82db4a1fe34f6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:02:10 -0700 Subject: [PATCH 173/216] test(interview-plan): fail closed on hostile tzinfo evaluation --- .../tests/test_temporal_evidence_integrity.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/interview-plan/tests/test_temporal_evidence_integrity.py b/packages/interview-plan/tests/test_temporal_evidence_integrity.py index 647734fcc..502e9e4be 100644 --- a/packages/interview-plan/tests/test_temporal_evidence_integrity.py +++ b/packages/interview-plan/tests/test_temporal_evidence_integrity.py @@ -46,6 +46,22 @@ def tzname(self, value): # type: ignore[no-untyped-def] return "MutableOffsetTimezone" +class ExplodingOffsetTimezone(tzinfo): + """Timezone fixture that raises while caller-controlled UTC offset is evaluated.""" + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Simulate hostile or broken caller timezone code at the trust boundary.""" + raise RuntimeError("hostile utcoffset evaluation") + + def dst(self, value): # type: ignore[no-untyped-def] + """Return zero daylight-saving offset when queried independently.""" + return timedelta(0) + + def tzname(self, value): # type: ignore[no-untyped-def] + """Return a stable diagnostic name without evaluating the hostile offset.""" + return "ExplodingOffsetTimezone" + + class RejectUnexpectedAuthorityCall: """Fail if activation reaches authority work after invalid time evidence.""" @@ -107,6 +123,15 @@ def test_plan_detaches_mutable_generated_at_timezone_before_sealing() -> None: assert json.loads(candidate_plan.canonical_json())["generated_at"] == "2026-08-21T04:30:00.123456Z" +def test_plan_normalizes_hostile_timezone_failure_to_validation_error() -> None: + """Caller timezone code must not leak arbitrary exceptions through plan validation.""" + kwargs = valid_kwargs() + kwargs["generated_at"] = datetime(2026, 8, 21, 4, 30, tzinfo=ExplodingOffsetTimezone()) + + with pytest.raises(ValueError, match="generated_at must be an exact timezone-aware datetime"): + build_structured_interview_plan(**kwargs) + + def test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error() -> None: """Out-of-range UTC conversion must fail as governed plan validation, not OverflowError.""" kwargs = valid_kwargs() @@ -116,6 +141,19 @@ def test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error( build_structured_interview_plan(**kwargs) +def test_activation_normalizes_hostile_timezone_failure_before_authority() -> None: + """Approval-time timezone failures must remain validation errors before side effects.""" + candidate_plan = build_structured_interview_plan(**valid_kwargs()) + + with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): + activate_structured_interview_plan( + plan=candidate_plan, + authority=RejectUnexpectedAuthorityCall(), + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=ExplodingOffsetTimezone()), + ) + + def test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority() -> None: """Out-of-range approval UTC conversion must fail before authoritative side effects.""" candidate_plan = build_structured_interview_plan(**valid_kwargs()) From 7a0213875f4da025adf04951fa8f1de75c53b147 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:04:15 -0700 Subject: [PATCH 174/216] fix(interview-plan): normalize hostile plan timezone failures --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 9f2614d60..e1b5e0710 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -109,7 +109,10 @@ def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: """Detach one caller-owned aware datetime into a representable built-in 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") - offset = value.utcoffset() + try: + offset = value.utcoffset() + except Exception as exc: + 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") local_naive = value.replace(tzinfo=None) From b023bc4e83b4c61c1ae7e9ce0a967d6769256b08 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:04:42 -0700 Subject: [PATCH 175/216] fix(interview-plan): normalize hostile activation timezone failures --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 19bea5946..f3a842f78 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -43,7 +43,10 @@ def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: """Detach one caller-owned aware datetime into a representable built-in 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") - offset = value.utcoffset() + try: + offset = value.utcoffset() + except Exception as exc: + 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") local_naive = value.replace(tzinfo=None) From 602f33e4002a8a9d5b22532750a385090113df9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:05:17 -0700 Subject: [PATCH 176/216] test(interview-plan): normalize receipt timezone failures --- .../tests/test_temporal_evidence_integrity.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/interview-plan/tests/test_temporal_evidence_integrity.py b/packages/interview-plan/tests/test_temporal_evidence_integrity.py index 502e9e4be..6f10da7d5 100644 --- a/packages/interview-plan/tests/test_temporal_evidence_integrity.py +++ b/packages/interview-plan/tests/test_temporal_evidence_integrity.py @@ -167,6 +167,20 @@ def test_activation_rejects_utc_normalization_beyond_datetime_max_before_authori ) +def test_activation_receipt_normalizes_hostile_timezone_failure() -> None: + """Receipt construction must not leak arbitrary caller timezone exceptions.""" + with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): + StructuredInterviewActivationReceipt( + tenant_record_id="12345678-1234-4234-8234-123456789abc", + interview_plan_reference="interview_plan:11111111-1111-4111-8111-111111111111", + plan_digest="a" * 64, + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + authority_evidence_reference="activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", + authority_evidence_digest="e" * 64, + approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=ExplodingOffsetTimezone()), + ) + + def test_activation_receipt_names_approved_at_when_recorded_time_is_invalid() -> None: """Tell callers which approval timestamp must be repaired before activation can proceed.""" with pytest.raises(ValueError, match="approved_at must be an exact timezone-aware datetime"): From f54bfd72d4b155128047a04947c9030d9603b301 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:05:52 -0700 Subject: [PATCH 177/216] fix(interview-plan): normalize canonical timestamp failures --- packages/interview-plan/src/orgmetra_interview_plan/plan.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index e1b5e0710..9a2777ef6 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -124,10 +124,8 @@ def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> str: - """Render an aware instant as UTC RFC 3339 text with a field-specific error.""" - if type(value) is not datetime or value.tzinfo is None or value.utcoffset() is None: - raise ValueError(f"{field_name} must be an exact timezone-aware datetime") - return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + """Render an aware instant as UTC RFC 3339 text after fail-closed detachment.""" + return _snapshot_utc_datetime(value, field_name).isoformat().replace("+00:00", "Z") @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) From a4342c02bace2cf2ffc635ac4a5fd7bb981413e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:06:36 -0700 Subject: [PATCH 178/216] docs(interview-plan): record timezone trust-boundary repair --- packages/interview-plan/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 68b0c9cbb..d3105285d 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -18,6 +18,7 @@ - Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can exist. - Detach caller-owned plan `generated_at` into one built-in UTC snapshot before creation-seal registration so later mutation of a custom `tzinfo` cannot change or invalidate an already-issued plan instant. - Detach caller-owned `approved_at` into one built-in UTC snapshot before chronology or authority work, pass that snapshot to the authority, require the returned verification to carry the same reviewed instant, and write only that immutable snapshot into the receipt. +- Normalize caller-controlled `tzinfo.utcoffset()` failures into field-specific `ValueError` at plan-generation, activation, verification-time normalization, and canonical timestamp boundaries so arbitrary timezone exceptions cannot escape governed APIs or reach authority side effects. - Normalize UTC-offset arithmetic that would cross Python `datetime` bounds into field-specific `ValueError` for both plan generation and approval time, failing before plan issuance or activation authority side effects instead of leaking `OverflowError`. - Require the exact governed `StructuredInterviewPlan` runtime type before any activation authority work, preventing duck-typed or subclassed plan-shaped objects from bypassing construction invariants and producing approval evidence. - Pass only creation-bound canonical plan JSON plus its exact SHA-256 digest across `StructuredInterviewActivationAuthority`; the authority no longer receives the caller's live plan object, so temporary change-and-restore (ABA) mutation cannot change the plan revision actually reviewed. @@ -32,6 +33,7 @@ - Reject timestamp/node-bearing UUIDv1 values in package-owned trust references as well as human-readable/value-bearing reference metadata before serialization; tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. - Close plan `reason_code` to `approved_requisition_interview` and activation governance to fixed `structured_interview_activation` / `human_approved_plan_activation` codes. - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. +- Treat caller-owned timezone implementations as untrusted code: offset evaluation and canonical-time rendering fail closed to governed field-specific validation errors rather than leaking arbitrary exceptions across plan, activation, or receipt boundaries. - Normalize both plan-generation and approval-time evidence before creation sealing or authority review so caller-controlled mutable `tzinfo` state cannot make one governed instant later represent a different UTC instant. - Redact `StructuredInterviewPlan`, `StructuredInterviewActivationVerification`, and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. From 49b05e03e3cd79a2eca45a33e01418b9b8bdb780 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:07:02 -0700 Subject: [PATCH 179/216] docs(interview-plan): explain untrusted timezone handling --- packages/interview-plan/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 97ec7e135..b7da2b780 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,13 +10,13 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. If that offset would place the instant outside Python's representable `datetime` range, construction fails with the same field-specific `ValueError` as other invalid recorded-time evidence rather than leaking `OverflowError`. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. +A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. Caller-provided timezone implementations are untrusted code: if `tzinfo.utcoffset()` raises, Orgmetra converts that failure into the same field-specific governed `ValueError` instead of leaking the caller exception. If the offset would place the instant outside Python's representable `datetime` range, construction likewise fails with field-specific `ValueError` rather than leaking `OverflowError`. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. -`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. Approval-time normalization that would leave Python's representable `datetime` range fails as field-specific validation before any authority call, so invalid boundary timestamps cannot escape as runtime arithmetic errors or trigger authoritative side effects. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. +`activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. Approval-time offset evaluation failures and normalization that would leave Python's representable `datetime` range both fail as field-specific validation before any authority call, so hostile/broken timezone code cannot leak arbitrary exceptions or trigger authoritative side effects. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. -The authority must return an exact `StructuredInterviewActivationVerification`. This verification contract is a `NamedTuple`, so its trust-bearing tuple fields cannot be rewritten through `object.__setattr__` after return. Activation rejects subclasses before reading evidence, unpacks the exact tuple once, detaches the returned approval time into built-in UTC, validates the unpacked values, and requires the complete scope—including tenant, interview-plan reference, plan digest, approving actor, and approval instant—to equal the request. This removes the mixed-revision window that existed when a merely frozen dataclass could still be rewritten between field reads. +The authority must return an exact `StructuredInterviewActivationVerification`. This verification contract is a `NamedTuple`, so its trust-bearing tuple fields cannot be rewritten through `object.__setattr__` after return. Activation rejects subclasses before reading evidence, unpacks the exact tuple once, detaches the returned approval time into built-in UTC using the same fail-closed timezone boundary, validates the unpacked values, and requires the complete scope—including tenant, interview-plan reference, plan digest, approving actor, and approval instant—to equal the request. This removes the mixed-revision window that existed when a merely frozen dataclass could still be rewritten between field reads. -The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots. One live receipt identity may register issuance evidence only once, so low-level field mutation followed by repeated initialization cannot replace the original seal with a seal over altered post-authority evidence. Receipt mutation, missing issuance evidence, or duplicate issuance registration fails closed before canonical export. +The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Canonical timestamp rendering reuses the same detached UTC validation, so direct receipt construction cannot leak arbitrary exceptions from a caller-controlled timezone implementation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots. One live receipt identity may register issuance evidence only once, so low-level field mutation followed by repeated initialization cannot replace the original seal with a seal over altered post-authority evidence. Receipt mutation, missing issuance evidence, or duplicate issuance registration fails closed before canonical export. The plan object itself remains pending human review: `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and its next action requires authoritative resolution before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed plan invariants. The activation receipt is separate evidence and does not mutate or rewrite the reviewed plan. From 5678d8b8d85b49ecc60405fc108211ecf0f4ab72 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:07:37 -0700 Subject: [PATCH 180/216] docs(traceability): bind hostile timezone regressions --- .../traceability/structured-interview-plan.md | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 28c3376d4..e7e1f0c0c 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -4,6 +4,10 @@ **Active PR only.** Protected `develop` does not contain this structured-interview capability until the exact integrated PR head passes all required gates and merges. The active PR contains both the candidate-neutral plan contract and a transport-neutral executable activation boundary; it still does not claim that a concrete production authority adapter is already deployed. +## Current timezone trust-boundary repair + +Caller-owned `tzinfo` implementations are treated as untrusted executable code. Plan generation, activation approval-time normalization, verification-time normalization, and canonical timestamp rendering convert exceptions raised by `tzinfo.utcoffset()` into field-specific governed `ValueError` and stop before authority side effects or evidence export. `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_activation_normalizes_hostile_timezone_failure_before_authority`, and `test_activation_receipt_normalizes_hostile_timezone_failure` bind this behavior; mutable-offset and representable-range regressions continue to prove UTC detachment and arithmetic fail-closure. + ## Buyer requirement → executable evidence | Requirement | Contract | Evidence | @@ -18,8 +22,8 @@ | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | | Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction first detaches `generated_at` to a built-in UTC instant, then registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | | Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | -| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; naive/unknown-offset and out-of-range UTC normalization fail closed, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | -| Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; naive/unknown-offset and out-of-range UTC normalization fail closed before authority side effects; the same snapshot crosses the authority and receipt boundaries, so mutable `tzinfo` state cannot alter the approved instant | `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | +| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed as field-specific validation, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | +| Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed before authority side effects; the same snapshot crosses the authority and receipt boundaries, and canonical rendering reuses the same fail-closed detachment | `test_activation_normalizes_hostile_timezone_failure_before_authority`, `test_activation_receipt_normalizes_hostile_timezone_failure`, `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | | Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | | Authority verification cannot mix revisions between field reads | the exact verification contract is a runtime-immutable `NamedTuple`; exact-type enforcement rejects behavioral subclasses, `object.__setattr__` cannot rewrite tuple fields, and activation unpacks the tuple once before validation/scope comparison/receipt issuance | `test_verification_contract_cannot_be_rewritten_with_object_setattr` plus `test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest + explicit reviewed UTC approval instant; verification and receipt representations are fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape`, approval-time binding regressions, plus exact verification/receipt repr and canonical JSON assertions | @@ -27,22 +31,22 @@ | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | -| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; unrepresentable UTC normalization is rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation UTC-snapshot/range-boundary regressions, and canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify single-registration creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt low-level rewrite/missing-seal/reseal regressions | +| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; hostile offset evaluation and unrepresentable UTC normalization are rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan hostile-timezone/mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation hostile-timezone/UTC-snapshot/range-boundary regressions, and canonical/digest assertions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify single-registration creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt hostile-timezone/low-level rewrite/missing-seal/reseal regressions | ## Evidence boundary -The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction first validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the field-specific governed `ValueError` and stops before issuance-seal registration. Construction then computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. +The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction first validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Offset evaluation itself is a trust boundary: arbitrary exceptions raised by caller-owned `tzinfo.utcoffset()` are converted into the field-specific governed `ValueError` and stop construction before issuance. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the same field-specific governed `ValueError` and stops before issuance-seal registration. Construction then computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. -The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; naive, unknown-offset, or UTC normalization beyond the representable datetime range fails as field-specific validation before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. +The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; hostile/broken offset evaluation, naive or unknown-offset values, and UTC normalization beyond the representable datetime range all fail as field-specific validation before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. -`StructuredInterviewActivationVerification` explicitly carries the reviewed approval instant and is implemented as a runtime-immutable `NamedTuple`, not a merely frozen dataclass. Activation requires the exact verification runtime type, rejects behavioral subclasses before evidence reads, unpacks the tuple once, normalizes the returned approval time, validates the unpacked values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Tuple field descriptors reject `object.__setattr__`, closing the mixed-revision window where an authority-retained alias could previously rewrite one valid field between sequential reads. +`StructuredInterviewActivationVerification` explicitly carries the reviewed approval instant and is implemented as a runtime-immutable `NamedTuple`, not a merely frozen dataclass. Activation requires the exact verification runtime type, rejects behavioral subclasses before evidence reads, unpacks the tuple once, normalizes the returned approval time through the same fail-closed timezone boundary, validates the unpacked values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Tuple field descriptors reject `object.__setattr__`, closing the mixed-revision window where an authority-retained alias could previously rewrite one valid field between sequential reads. -A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Registration for one live receipt identity is single-use: a second registration attempt fails before overwriting the original seal or registering another finalizer. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. A low-level field mutation followed by a forged/repeated `__post_init__()` therefore cannot erase issuance history and legitimize altered post-authority evidence; the duplicate registration fails and the original seal remains, so canonical export still rejects the changed receipt. Missing issuance evidence or any low-level post-issuance field rewrite also fails closed. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. +A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Registration for one live receipt identity is single-use: a second registration attempt fails before overwriting the original seal or registering another finalizer. Canonical timestamp rendering first detaches the timestamp through the governed UTC helper, so direct receipt construction with a caller-controlled timezone cannot leak arbitrary `tzinfo` exceptions across the package API. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. A low-level field mutation followed by a forged/repeated `__post_init__()` therefore cannot erase issuance history and legitimize altered post-authority evidence; the duplicate registration fails and the original seal remains, so canonical export still rejects the changed receipt. Missing issuance evidence or any low-level post-issuance field rewrite also fails closed. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, single-registration creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, caller-timezone exception normalization, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, single-registration creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From 34b20d8cbe7bc60ae1016d9bd0db6b47d6c79879 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:08:06 -0700 Subject: [PATCH 181/216] docs(adr): treat timezone implementations as untrusted --- docs/adr/0015-governed-structured-interview-plan.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index c77dae86a..5542decde 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -9,7 +9,7 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant, and an authority's return value must explicitly attest the exact normalized approval instant the receipt will store. Boundary timestamps whose offsets would normalize outside Python's representable `datetime` range are invalid governed evidence and must fail before sealing or authoritative side effects rather than leaking arithmetic exceptions. +Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may also raise arbitrary exceptions while `utcoffset()` is evaluated; those failures must be normalized into governed field-specific validation rather than escaping across the public API or reaching authority side effects. An authority's return value must explicitly attest the exact normalized approval instant the receipt will store. Boundary timestamps whose offsets would normalize outside Python's representable `datetime` range are invalid governed evidence and must fail before sealing or authoritative side effects rather than leaking arithmetic exceptions. Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes only if issuance history itself cannot be rewritten: repeated initialization must not replace the original live-object seal with a seal over altered post-authority evidence. A post-call seal comparison alone also cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing, and plan/receipt seal registration must be single-use per live identity. @@ -28,19 +28,19 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: `tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. -Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. The plan stores that UTC snapshot, not the caller's mutable `tzinfo` object. A later timezone-state change therefore cannot change the plan's canonical instant or invalidate an otherwise unchanged issued plan. If applying the offset would move the instant outside Python's representable `datetime` range, convert that arithmetic failure into the same field-specific `ValueError` used for invalid timezone-aware evidence and do not register issuance evidence. +Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. Treat offset evaluation as an untrusted-code boundary: if `tzinfo.utcoffset()` raises, convert the exception into the same field-specific `ValueError` used for invalid timezone-aware evidence and do not register issuance evidence. The plan stores the UTC snapshot, not the caller's mutable `tzinfo` object. A later timezone-state change therefore cannot change the plan's canonical instant or invalidate an otherwise unchanged issued plan. If applying the offset would move the instant outside Python's representable `datetime` range, convert that arithmetic failure into the same field-specific `ValueError` and do not register issuance evidence. Canonical timestamp rendering reuses this detached UTC validation rather than directly invoking caller-controlled timezone methods. At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. Registration is single-use for one live identity: if issuance evidence already exists, repeated initialization fails closed instead of overwriting the original seal. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology, tenant/interview-plan scope, and SHA-256 are all derived from those canonical bytes instead of rereading live plan attributes. If approval-time normalization would exceed Python's representable `datetime` range, fail with field-specific `ValueError` before any authority call. +Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology, tenant/interview-plan scope, and SHA-256 are all derived from those canonical bytes instead of rereading live plan attributes. If caller-controlled offset evaluation raises or approval-time normalization would exceed Python's representable `datetime` range, fail with field-specific `ValueError` before any authority call. The authority receives `plan_canonical_json`, its exact `plan_digest`, the approving actor reference, and the exact normalized approval instant. It does **not** receive the live `StructuredInterviewPlan`. Consequently an external alias may change and restore the caller's plan while the authority runs, but that ABA cycle cannot change the immutable plan revision presented for authoritative review. Activation still repeats creation-bound plan validation after the authority returns so any non-restored live-object mutation fails closed. -Implement `StructuredInterviewActivationVerification` as a `NamedTuple` carrying tenant, interview-plan reference, plan digest, approving actor, authority-evidence reference/digest, and reviewed `approved_at`. Require the exact runtime type so behavioral subclasses cannot alter reads. Tuple field storage rejects `object.__setattr__`; activation unpacks the exact tuple once, normalizes the returned approval time into built-in UTC, validates those values, and compares the complete scope against the pre-call request. This closes the mixed-revision window possible with a frozen dataclass whose fields could still be rewritten between sequential reads. +Implement `StructuredInterviewActivationVerification` as a `NamedTuple` carrying tenant, interview-plan reference, plan digest, approving actor, authority-evidence reference/digest, and reviewed `approved_at`. Require the exact runtime type so behavioral subclasses cannot alter reads. Tuple field storage rejects `object.__setattr__`; activation unpacks the exact tuple once, normalizes the returned approval time through the same fail-closed UTC helper, validates those values, and compares the complete scope against the pre-call request. This closes the mixed-revision window possible with a frozen dataclass whose fields could still be rewritten between sequential reads. -A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. +A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. Direct receipt construction and canonical rendering use the same fail-closed timestamp helper so caller-controlled timezone failures cannot leak arbitrary exceptions through receipt validation. At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable slots, keyed only to the live receipt identity and removed when the receipt is collected. Registration is single-use for one live receipt identity: if issuance evidence already exists, repeated initialization fails closed before assignment or finalizer registration, leaving the original authority-bound seal intact. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence, a low-level post-issuance field rewrite, or an attempt to renew issuance evidence after such a rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. @@ -52,6 +52,7 @@ The plan and activation receipt are candidate-neutral. They contain no candidate - Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. - Caller-owned mutable timezone state cannot change or invalidate an already-issued plan generation instant because construction stores one detached built-in UTC snapshot before sealing. +- Exceptions raised by caller-owned timezone implementations are normalized into field-specific governed validation before plan issuance, activation authority work, verification acceptance, or receipt canonicalization. - Unrepresentable UTC normalization at `datetime` boundaries fails as field-specific governed validation before plan issuance or activation authority side effects. - Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, mismatched, or duplicate process-local issuance evidence fails closed before activation can rely on it. - The authoritative adapter reviews detached creation-bound canonical plan evidence rather than a caller-owned live plan object, so temporary change-and-restore mutation cannot substitute a different revision during review. From b84b57eaa02a73e8ac258c2a93b46536fdbef7c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:11:31 -0700 Subject: [PATCH 182/216] test(interview-plan): reject direct receipt minting with private sentinel --- .../tests/test_receipt_issuance.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/interview-plan/tests/test_receipt_issuance.py b/packages/interview-plan/tests/test_receipt_issuance.py index f440e5bca..a342342dd 100644 --- a/packages/interview-plan/tests/test_receipt_issuance.py +++ b/packages/interview-plan/tests/test_receipt_issuance.py @@ -29,6 +29,23 @@ def test_activation_receipt_cannot_be_minted_without_verified_factory_path(): ) +def test_private_module_sentinel_cannot_mint_verified_receipt_directly(): + """A module-private sentinel must not be usable as authority evidence by callers.""" + with pytest.raises(TypeError, match="_issuance_token"): + StructuredInterviewActivationReceipt( + tenant_record_id="10000000-0000-7000-8000-000000000001", + interview_plan_reference="interview_plan:11111111-1111-4111-8111-111111111111", + plan_digest="a" * 64, + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + authority_evidence_reference=( + "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" + ), + authority_evidence_digest="e" * 64, + approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), + _issuance_token=activation_module._ACTIVATION_RECEIPT_ISSUANCE_TOKEN, + ) + + def test_issued_activation_receipt_cannot_be_replaced_with_unverified_scope(): """Reject dataclass replacement that would reuse issuance proof for changed scope.""" candidate_plan = plan() From 5ab76c163caf8411031fee91b874aa42082b0b37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:13:22 -0700 Subject: [PATCH 183/216] fix(interview-plan): bind receipt issuance to verified factory --- .../src/orgmetra_interview_plan/activation.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index f3a842f78..d1ed4c088 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -10,7 +10,7 @@ """ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime, timedelta, timezone from hashlib import sha256 import hmac @@ -33,6 +33,7 @@ _REASON_CODE = "human_approved_plan_activation" _ACTIVATION_STATE = "approved_for_use" _MAX_EVIDENCE_VERSION = 2_147_483_647 +# Legacy private sentinel retained only so regression proves it confers no issuance authority. _ACTIVATION_RECEIPT_ISSUANCE_TOKEN = object() _PROCESS_ACTIVATION_RECEIPT_SEAL_KEY = secrets.token_bytes(32) _ACTIVATION_RECEIPT_SEALS: dict[int, str] = {} @@ -122,7 +123,7 @@ def verify_activation( @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) class StructuredInterviewActivationReceipt: - """Immutable evidence that an accountable human activated one exact reviewed plan.""" + """Value-minimized activation receipt whose trusted export requires factory issuance.""" tenant_record_id: str interview_plan_reference: str @@ -136,10 +137,9 @@ class StructuredInterviewActivationReceipt: evidence_version: int = 1 human_confirmation: bool = True activation_state: str = _ACTIVATION_STATE - _issuance_token: object = field(default=None, repr=False, compare=False) def __post_init__(self) -> None: - """Reject forged, ambiguous, weakened, or non-authoritatively issued evidence.""" + """Reject forged, ambiguous, or weakened receipt values before possible issuance.""" _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") _validate_reference( self.interview_plan_reference, @@ -171,15 +171,6 @@ def __post_init__(self) -> None: raise ValueError("human confirmation is mandatory for interview-plan activation") if self.activation_state != _ACTIVATION_STATE: raise ValueError("activation_state must remain approved_for_use") - if self._issuance_token is not _ACTIVATION_RECEIPT_ISSUANCE_TOKEN: - raise TypeError( - "StructuredInterviewActivationReceipt can only be issued by " - "activate_structured_interview_plan" - ) - _register_activation_receipt_seal( - self, - _seal_activation_receipt(self._canonical_json_unchecked()), - ) def __repr__(self) -> str: """Return a redacted representation suitable for routine logs.""" @@ -204,7 +195,7 @@ def _canonical_json_unchecked(self) -> str: return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) def canonical_json(self) -> str: - """Return creation-bound canonical JSON for immutable audit correlation.""" + """Return factory-issued canonical JSON for immutable audit correlation.""" canonical = self._canonical_json_unchecked() authoritative_seal = _authoritative_activation_receipt_seal(self) if ( @@ -220,7 +211,7 @@ def canonical_json(self) -> str: return canonical def sha256_digest(self) -> str: - """Return SHA-256 over the exact creation-bound activation receipt.""" + """Return SHA-256 over the exact factory-issued activation receipt.""" return sha256(self.canonical_json().encode("utf-8")).hexdigest() @@ -238,7 +229,8 @@ def activate_structured_interview_plan( It receives only detached creation-bound canonical plan bytes plus their digest, never the caller's live plan object. Authority results are runtime-immutable tuple evidence; this function validates those exact values and emits a value-minimized - human-approval receipt only for the exact verified scope. + human-approval receipt only for the exact verified scope. Process-local issuance + evidence is registered only after all authoritative checks and scope matching pass. """ if type(plan) is not StructuredInterviewPlan: raise TypeError("plan must be a StructuredInterviewPlan") @@ -321,7 +313,9 @@ def activate_structured_interview_plan( authority_evidence_reference=verified_authority_evidence_reference, authority_evidence_digest=verified_authority_evidence_digest, approved_at=approved_at_snapshot, - _issuance_token=_ACTIVATION_RECEIPT_ISSUANCE_TOKEN, ) - object.__setattr__(receipt, "_issuance_token", None) + _register_activation_receipt_seal( + receipt, + _seal_activation_receipt(receipt._canonical_json_unchecked()), + ) return receipt From 4346c2ceea107e37f7c675d4de96f676dcda5495 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:13:34 -0700 Subject: [PATCH 184/216] test(interview-plan): require factory issuance for receipt export --- .../tests/test_receipt_issuance.py | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/interview-plan/tests/test_receipt_issuance.py b/packages/interview-plan/tests/test_receipt_issuance.py index a342342dd..c1e66f631 100644 --- a/packages/interview-plan/tests/test_receipt_issuance.py +++ b/packages/interview-plan/tests/test_receipt_issuance.py @@ -13,20 +13,29 @@ from test_activation import APPROVED_AT, APPROVER, AllowingAuthority, plan, verification_for +def direct_receipt() -> StructuredInterviewActivationReceipt: + """Return one syntactically valid receipt that never crossed the authority factory.""" + return StructuredInterviewActivationReceipt( + tenant_record_id="10000000-0000-7000-8000-000000000001", + interview_plan_reference="interview_plan:11111111-1111-4111-8111-111111111111", + plan_digest="a" * 64, + approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", + authority_evidence_reference=( + "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" + ), + authority_evidence_digest="e" * 64, + approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), + ) + + def test_activation_receipt_cannot_be_minted_without_verified_factory_path(): - """Reject valid-looking approval evidence that never crossed the authority boundary.""" - with pytest.raises(TypeError, match="activate_structured_interview_plan"): - StructuredInterviewActivationReceipt( - tenant_record_id="10000000-0000-7000-8000-000000000001", - interview_plan_reference="interview_plan:11111111-1111-4111-8111-111111111111", - plan_digest="a" * 64, - approving_actor_reference="actor:dddddddd-dddd-4ddd-8ddd-dddddddddddd", - authority_evidence_reference=( - "activation_verification:eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" - ), - authority_evidence_digest="e" * 64, - approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), - ) + """Directly constructed values cannot export authoritative activation evidence.""" + receipt = direct_receipt() + + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + receipt.canonical_json() + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + receipt.sha256_digest() def test_private_module_sentinel_cannot_mint_verified_receipt_directly(): @@ -47,7 +56,7 @@ def test_private_module_sentinel_cannot_mint_verified_receipt_directly(): def test_issued_activation_receipt_cannot_be_replaced_with_unverified_scope(): - """Reject dataclass replacement that would reuse issuance proof for changed scope.""" + """Dataclass replacement creates unissued values rather than reusable trust evidence.""" candidate_plan = plan() receipt = activate_structured_interview_plan( plan=candidate_plan, @@ -56,8 +65,13 @@ def test_issued_activation_receipt_cannot_be_replaced_with_unverified_scope(): approved_at=APPROVED_AT, ) - with pytest.raises(TypeError, match="activate_structured_interview_plan"): - replace(receipt, plan_digest="b" * 64) + replacement = replace(receipt, plan_digest="b" * 64) + + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + replacement.canonical_json() + with pytest.raises(ValueError, match="changed after activation receipt issuance"): + replacement.sha256_digest() + assert receipt.canonical_json() def test_issued_activation_receipt_rejects_post_issuance_rewrite(): From 61ca1fe5de4ed95eff7ea5a807fd16c4fd8397d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:14:05 -0700 Subject: [PATCH 185/216] docs(interview-plan): document factory-bound receipt issuance --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index b7da2b780..20c671595 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -16,7 +16,7 @@ A successfully constructed `StructuredInterviewPlan` is creation-bound before ac The authority must return an exact `StructuredInterviewActivationVerification`. This verification contract is a `NamedTuple`, so its trust-bearing tuple fields cannot be rewritten through `object.__setattr__` after return. Activation rejects subclasses before reading evidence, unpacks the exact tuple once, detaches the returned approval time into built-in UTC using the same fail-closed timezone boundary, validates the unpacked values, and requires the complete scope—including tenant, interview-plan reference, plan digest, approving actor, and approval instant—to equal the request. This removes the mixed-revision window that existed when a merely frozen dataclass could still be rewritten between field reads. -The receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()` while canonical JSON/SHA-256 provides explicit immutable audit correlation. Canonical timestamp rendering reuses the same detached UTC validation, so direct receipt construction cannot leak arbitrary exceptions from a caller-controlled timezone implementation. Successfully issued receipts use the same creation-bound principle with a separate process-local seal outside receipt-writable slots. One live receipt identity may register issuance evidence only once, so low-level field mutation followed by repeated initialization cannot replace the original seal with a seal over altered post-authority evidence. Receipt mutation, missing issuance evidence, or duplicate issuance registration fails closed before canonical export. +`StructuredInterviewActivationReceipt` is a value-minimized receipt shape, not a constructor-level authorization primitive. Direct construction and `dataclasses.replace(...)` still validate fixed fields, references, digests, and timestamps, but those objects are **unissued** and cannot export `canonical_json()` or `sha256_digest()`. Only `activate_structured_interview_plan(...)`, after authoritative verification succeeds and the returned tenant/plan/digest/actor/approval-time scope exactly matches the request, registers the process-local HMAC issuance seal for that exact live receipt. A module-private sentinel is not accepted by the receipt constructor and carries no authority. The issued receipt records the exact plan digest, accountable approving actor, authority-verification reference/digest, purpose, reason, evidence version, precision-preserving approval time, mandatory human confirmation, and fixed `approved_for_use` state. It remains value-minimized and cannot contain candidate identity, responses, scores, protected-attribute values, or free-form model output. `repr(receipt)` is fully redacted as `StructuredInterviewActivationReceipt()`; canonical timestamp rendering reuses the same detached UTC validation; and mutation, missing issuance evidence, or reconstruction/replacement without factory issuance fails closed before canonical export. The seal is same-process runtime-integrity evidence only, not a durable signature, portable attestation, rehydration credential, persisted audit record, or replacement for the host immutable audit/outbox boundary. The plan object itself remains pending human review: `human_confirmation_required` is fixed to `True`, `review_state` is fixed to `requires_human_approval`, and its next action requires authoritative resolution before activation. Direct construction and `dataclasses.replace(...)` re-run the same fail-closed plan invariants. The activation receipt is separate evidence and does not mutate or rewrite the reviewed plan. From 53b40107fcbaf0e3d716d5e9073b85885be9ed96 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:14:19 -0700 Subject: [PATCH 186/216] docs(interview-plan): record verified-factory issuance repair --- packages/interview-plan/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index d3105285d..8334fd927 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -15,7 +15,7 @@ - Require a separately identified and SHA-256-bound question-to-competency mapping artifact so question count alone cannot be treated as proof that every governed competency is assessed. - Revalidate evidence-version changes through direct construction and `dataclasses.replace(...)`; changing the version changes canonical SHA-256 correlation. - Keep package-owned trust-bearing reference suffixes canonical non-sentinel UUIDv4, while `tenant_record_id` follows Orgmetra's authoritative canonical non-sentinel operational UUID contract so valid core tenant identities are not rejected by this leaf package. -- Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can exist. +- Replace prose-only activation assurance with executable fail-closed orchestration: the injected host authority must reject failed tenant/relationship/provenance/panel checks, and returned evidence must match the exact tenant, interview-plan reference, plan digest, approving actor, and reviewed approval instant before a receipt can be issued. - Detach caller-owned plan `generated_at` into one built-in UTC snapshot before creation-seal registration so later mutation of a custom `tzinfo` cannot change or invalidate an already-issued plan instant. - Detach caller-owned `approved_at` into one built-in UTC snapshot before chronology or authority work, pass that snapshot to the authority, require the returned verification to carry the same reviewed instant, and write only that immutable snapshot into the receipt. - Normalize caller-controlled `tzinfo.utcoffset()` failures into field-specific `ValueError` at plan-generation, activation, verification-time normalization, and canonical timestamp boundaries so arbitrary timezone exceptions cannot escape governed APIs or reach authority side effects. @@ -25,7 +25,7 @@ - Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. - Make `StructuredInterviewActivationVerification` a runtime-immutable `NamedTuple`, reject subclasses, and unpack its exact tuple once before validation so `object.__setattr__` cannot create mixed authority-evidence revisions between field reads. - Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. -- Bind every successfully issued activation receipt to a single-registration process-local HMAC seal stored outside receipt-writable slots; canonical JSON and SHA-256 export now fail closed if already-issued receipt fields are rewritten, if creation-bound issuance evidence is unavailable, or if repeated initialization attempts to renew the seal for the same live receipt identity. +- Remove constructor-token authorization from `StructuredInterviewActivationReceipt`: direct construction and `dataclasses.replace(...)` create unissued value objects that cannot export canonical evidence, while only `activate_structured_interview_plan(...)` registers the process-local receipt seal after authoritative verification and exact-scope matching succeed. A module-private sentinel no longer appears in the receipt constructor and cannot mint approval evidence. - Expand Structured Interview Plan Quality path triggers to cover repository-level Python/test configuration and `.gitignore` inputs that can change test collection, execution, or clean-checkout behavior, while retaining package, dependency-lock, workflow, ADR, doctoring, and traceability triggers. ### Security and privacy @@ -35,6 +35,7 @@ - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. - Treat caller-owned timezone implementations as untrusted code: offset evaluation and canonical-time rendering fail closed to governed field-specific validation errors rather than leaking arbitrary exceptions across plan, activation, or receipt boundaries. - Normalize both plan-generation and approval-time evidence before creation sealing or authority review so caller-controlled mutable `tzinfo` state cannot make one governed instant later represent a different UTC instant. +- Prevent callers from converting a module-visible private sentinel into human-approval authority: receipt issuance evidence is now registered exclusively inside the verified activation factory after all host-verification and exact-scope checks pass. - Redact `StructuredInterviewPlan`, `StructuredInterviewActivationVerification`, and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. - State explicitly that UUID/digest correlation, reference-string inequality, runtime issuance seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From f28556ab5b2c4b0d53c2d2c0c4c19647d36efbe4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:14:54 -0700 Subject: [PATCH 187/216] docs(traceability): bind receipt issuance to verified factory --- .../traceability/structured-interview-plan.md | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index e7e1f0c0c..cef251742 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -4,10 +4,12 @@ **Active PR only.** Protected `develop` does not contain this structured-interview capability until the exact integrated PR head passes all required gates and merges. The active PR contains both the candidate-neutral plan contract and a transport-neutral executable activation boundary; it still does not claim that a concrete production authority adapter is already deployed. -## Current timezone trust-boundary repair +## Current trust-boundary repairs Caller-owned `tzinfo` implementations are treated as untrusted executable code. Plan generation, activation approval-time normalization, verification-time normalization, and canonical timestamp rendering convert exceptions raised by `tzinfo.utcoffset()` into field-specific governed `ValueError` and stop before authority side effects or evidence export. `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_activation_normalizes_hostile_timezone_failure_before_authority`, and `test_activation_receipt_normalizes_hostile_timezone_failure` bind this behavior; mutable-offset and representable-range regressions continue to prove UTC detachment and arithmetic fail-closure. +Receipt construction is no longer an authorization mechanism. A directly constructed or `dataclasses.replace(...)`-created `StructuredInterviewActivationReceipt` may validate as a value shape, but it remains unissued and cannot export canonical evidence. Only `activate_structured_interview_plan(...)`, after authoritative verification and exact tenant/plan/digest/actor/time scope matching, registers the process-local receipt issuance seal. `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, and the replacement regression bind this distinction. + ## Buyer requirement → executable evidence | Requirement | Contract | Evidence | @@ -15,24 +17,24 @@ Caller-owned `tzinfo` implementations are treated as untrusted executable code. | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; single-registration process-local seals bind the exact post-construction plan and post-authority activation-receipt payloads | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation`, `test_existing_receipt_identity_cannot_renew_issuance_seal_after_mutation`, plus activation receipt canonical/digest, direct-construction/replacement, post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | +| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; plan construction binds a process-local creation seal, while activation receipt issuance is registered only by the verified factory after exact-scope authority checks | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, replacement/post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | -| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; `StructuredInterviewActivationVerification` must explicitly return that same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | +| High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; canonical export is available only after verified-factory issuance; `StructuredInterviewActivationVerification` must explicitly return the same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | | Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | | Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction first detaches `generated_at` to a built-in UTC instant, then registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | | Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | | Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed as field-specific validation, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | | Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed before authority side effects; the same snapshot crosses the authority and receipt boundaries, and canonical rendering reuses the same fail-closed detachment | `test_activation_normalizes_hostile_timezone_failure_before_authority`, `test_activation_receipt_normalizes_hostile_timezone_failure`, `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | -| Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | +| Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation before receipt issuance is registered | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | | Authority verification cannot mix revisions between field reads | the exact verification contract is a runtime-immutable `NamedTuple`; exact-type enforcement rejects behavioral subclasses, `object.__setattr__` cannot rewrite tuple fields, and activation unpacks the tuple once before validation/scope comparison/receipt issuance | `test_verification_contract_cannot_be_rewritten_with_object_setattr` plus `test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge` | | Authority evidence itself is value-minimized and integrity-bound | canonical UUIDv4 `activation_verification:` reference + lowercase SHA-256 digest + explicit reviewed UTC approval instant; verification and receipt representations are fully redacted | `test_activation_rejects_untrusted_authority_evidence_shape`, approval-time binding regressions, plus exact verification/receipt repr and canonical JSON assertions | | Portable governance metadata is value-minimized without duplicating tenant identity policy | authoritative `tenant_record_id` must be canonical/non-sentinel under the core HRIS contract; package-owned trust references require canonical non-sentinel UUIDv4 plus their expected prefix; reason vocabularies are closed; fixed `review_state` and `next_action` require exact built-in strings | authoritative UUIDv7 tenant interoperability regression, scalar/collection privacy regressions, UUIDv1 reference regressions, activation evidence-shape regressions, fixed-governance string-subclass regressions, and `dataclasses.replace(...)` bypass regressions | | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | -| Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; a rejected host check yields no receipt | scalar fail-closed plan regressions plus `test_authority_rejection_blocks_activation` and non-verification-result regression | -| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; hostile offset evaluation and unrepresentable UTC normalization are rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and activation receipt evidence | plan hostile-timezone/mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation hostile-timezone/UTC-snapshot/range-boundary regressions, and canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and activation receipt `__post_init__` validation; exact runtime types for trust-bearing collections and fixed plan-governance text; plan and activation receipt additionally verify single-registration creation-bound process-local issuance evidence before canonical export | direct constructor, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, receipt hostile-timezone/low-level rewrite/missing-seal/reseal regressions | +| Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; receipt canonical export requires issuance registration performed only after successful exact-scope host verification | scalar fail-closed plan regressions, `test_authority_rejection_blocks_activation`, non-verification-result regression, direct-unissued-receipt regression, and private-sentinel regression | +| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; hostile offset evaluation and unrepresentable UTC normalization are rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and factory-issued activation receipt evidence | plan hostile-timezone/mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation hostile-timezone/UTC-snapshot/range-boundary regressions, and canonical/digest assertions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and receipt shapes re-run value validation; direct/replaced receipt objects remain unissued and cannot export canonical evidence; exact runtime types protect plan collections/governance text; issued plans and receipts verify process-local issuance evidence before canonical export | direct constructor, private-sentinel, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, and receipt hostile-timezone/low-level rewrite/missing-seal regressions | ## Evidence boundary @@ -42,11 +44,11 @@ The active PR implements an executable activation orchestration boundary, not me `StructuredInterviewActivationVerification` explicitly carries the reviewed approval instant and is implemented as a runtime-immutable `NamedTuple`, not a merely frozen dataclass. Activation requires the exact verification runtime type, rejects behavioral subclasses before evidence reads, unpacks the tuple once, normalizes the returned approval time through the same fail-closed timezone boundary, validates the unpacked values, and compares tenant, plan reference, plan digest, approving actor, and approval time against the pre-call request. Tuple field descriptors reject `object.__setattr__`, closing the mixed-revision window where an authority-retained alias could previously rewrite one valid field between sequential reads. -A successfully issued activation receipt also receives a creation-bound HMAC seal kept in process-local state outside the receipt's writable slots. Registration for one live receipt identity is single-use: a second registration attempt fails before overwriting the original seal or registering another finalizer. Canonical timestamp rendering first detaches the timestamp through the governed UTC helper, so direct receipt construction with a caller-controlled timezone cannot leak arbitrary `tzinfo` exceptions across the package API. Before `canonical_json()` or `sha256_digest()` can expose audit-correlation bytes, the receipt recomputes the seal over its current canonical payload using constant-time comparison. A low-level field mutation followed by a forged/repeated `__post_init__()` therefore cannot erase issuance history and legitimize altered post-authority evidence; the duplicate registration fails and the original seal remains, so canonical export still rejects the changed receipt. Missing issuance evidence or any low-level post-issuance field rewrite also fails closed. The process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. +`StructuredInterviewActivationReceipt` construction validates the receipt's shape but does not create authoritative issuance evidence. Direct construction and `dataclasses.replace(...)` therefore produce unissued values whose `canonical_json()` and `sha256_digest()` fail closed because no process-local issuance seal exists. A module-private legacy sentinel is deliberately absent from the constructor and carries no authority. Only after `activate_structured_interview_plan(...)` has accepted exact runtime verification evidence, normalized its returned approval instant, validated every returned field, and matched tenant, plan reference, digest, approving actor, and approval time to the request does the factory create the receipt and register its process-local HMAC seal. Canonical export then recomputes the seal over the current payload using constant-time comparison. Any low-level post-issuance rewrite or loss of issuance evidence fails closed. This process-local seal is runtime integrity evidence only: it is not a durable audit store, signing key, cross-process verification format, or substitute for the host's immutable authoritative audit/outbox record. The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, caller-timezone exception normalization, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, single-registration creation-bound receipt integrity, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, caller-timezone exception normalization, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, factory-bound receipt issuance, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants. From e115b865961f7a1e072e30d90c26f86ed8830155 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:15:22 -0700 Subject: [PATCH 188/216] docs(adr): make receipt issuance factory-bound --- ...0015-governed-structured-interview-plan.md | 72 ++++++++----------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 5542decde..54c34eb5b 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -7,11 +7,13 @@ Orgmetra already separates authoritative Job/Position/Assignment truth, governed requisition review, selection evidence, and accountable human employment decisions. A buyer still needs a defensible boundary between an approved opening and the interview that will be used as a selection procedure. -A structured interview is stronger when the assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count by itself cannot prove that each governed competency is represented, so the approved question-to-competency mapping also needs its own immutable evidence identity. The plan itself should therefore be versioned and auditable before applicant responses or scores exist. Candidate identity, assessment values, and semantic/value-bearing labels in portable trust metadata are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4 so value-bearing and timestamp/node-bearing UUIDv1 suffixes cannot masquerade as this package's opaque reference format. The authoritative tenant identifier is different: it is issued by Orgmetra core, so this leaf package must accept the canonical non-sentinel operational UUID contract owned by that boundary rather than silently imposing a second version policy. +A structured interview is stronger when assessed competencies come from current job analysis, candidates receive the same predetermined questions, and responses are evaluated against common rating standards. A question count cannot prove that each governed competency is represented, so the approved question-to-competency mapping needs its own immutable evidence identity. Candidate identity, assessment values, and semantic/value-bearing labels are unnecessary at this pre-use boundary and would increase privacy risk. Packet-owned trust references therefore use UUIDv4; the authoritative tenant identifier instead follows Orgmetra core's canonical non-sentinel operational UUID contract. -Opaque identities and artifact digests identify evidence but do not prove that every object belongs to the packet tenant, that the requisition is bound to the stated Job and Job Analysis, or that distinct actor references resolve to distinct people. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient runtime enforcement: the package also needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when verification evidence is bound to another plan, actor, or approval instant. Both plan-generation time and high-impact approval time are trust-bearing evidence: caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may also raise arbitrary exceptions while `utcoffset()` is evaluated; those failures must be normalized into governed field-specific validation rather than escaping across the public API or reaching authority side effects. An authority's return value must explicitly attest the exact normalized approval instant the receipt will store. Boundary timestamps whose offsets would normalize outside Python's representable `datetime` range are invalid governed evidence and must fail before sealing or authoritative side effects rather than leaking arithmetic exceptions. +Opaque identities and artifact digests identify evidence but do not prove tenant ownership, requisition-to-Job-to-job-analysis relationships, or distinct human identities. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient: the package needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when returned verification evidence belongs to another plan, actor, or approval instant. -Python `frozen=True` is not a sufficient adversarial immutability boundary because `object.__setattr__` can rewrite dataclass fields. Creation-bound HMAC seals prevent silent post-construction plan/receipt export changes only if issuance history itself cannot be rewritten: repeated initialization must not replace the original live-object seal with a seal over altered post-authority evidence. A post-call seal comparison alone also cannot detect an ABA sequence where an authority observes a temporary live-plan mutation that is restored before the check. Similarly, copying fields one by one from a merely frozen authority-verification dataclass permits a retained alias to move between valid revisions while those reads occur. The authority boundary therefore must not expose the caller's live plan object, returned trust evidence must be runtime-immutable at the field-storage level rather than relying only on dataclass freezing, and plan/receipt seal registration must be single-use per live identity. +Plan-generation time and approval time are trust-bearing evidence. Caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may raise arbitrary exceptions while `utcoffset()` is evaluated; such failures and unrepresentable UTC normalization must become field-specific governed validation before plan issuance, authority side effects, verification acceptance, or canonical export. + +Python `frozen=True` is not an adversarial immutability or authorization boundary. `object.__setattr__` can rewrite dataclass fields, and a module-private constructor token is still reachable by Python callers. Therefore receipt shape construction must not itself confer human-approval authority. Plan construction may register process-local integrity evidence because construction is the governed plan-issuance boundary, but activation-receipt issuance evidence must be registered only by the verified activation factory after authoritative host checks and exact-scope matching have completed. ## Decision @@ -19,63 +21,49 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: - canonical non-sentinel Orgmetra tenant identity and one UUIDv4-backed opaque interview-plan reference; - UUIDv4-backed requisition and authoritative Job references; -- UUIDv4-backed exact job-analysis reference plus SHA-256 digest; -- UUIDv4-backed exact predetermined question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; -- a sorted, unique set of UUIDv4-backed job-related competency references; -- a sorted, unique interviewer panel of 2–8 UUIDv4-backed accountable actor references; -- a bounded question count that is at least the governed competency count, while the separately bound mapping artifact provides the evidence of actual question-to-competency coverage; -- fixed purpose `structured_interview_plan`, closed reviewed reason `approved_requisition_interview`, a bounded positive `evidence_version`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. - -`tenant_record_id` must be canonical and non-sentinel under Orgmetra's authoritative operational UUID contract. The package does not reinterpret the tenant UUID version because tenant identity generation and migration policy belong to the authoritative HRIS boundary. Packet-owned trust-bearing references separately require canonical, non-sentinel UUIDv4 plus their expected namespace. UUIDv1 and other non-v4 suffixes fail closed for those references; names, labels, compensation/protected-attribute values, or other semantic reference suffixes also fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same validation. `evidence_version` is restricted to true integers from 1 through 2147483647, is serialized canonically, and therefore changes immutable SHA-256 correlation when revised; version 1 is the initial schema default. The generated dataclass representation is disabled and replaced with `StructuredInterviewPlan()`; canonical JSON is the explicit evidence serialization boundary. - -Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. Treat offset evaluation as an untrusted-code boundary: if `tzinfo.utcoffset()` raises, convert the exception into the same field-specific `ValueError` used for invalid timezone-aware evidence and do not register issuance evidence. The plan stores the UTC snapshot, not the caller's mutable `tzinfo` object. A later timezone-state change therefore cannot change the plan's canonical instant or invalidate an otherwise unchanged issued plan. If applying the offset would move the instant outside Python's representable `datetime` range, convert that arithmetic failure into the same field-specific `ValueError` and do not register issuance evidence. Canonical timestamp rendering reuses this detached UTC validation rather than directly invoking caller-controlled timezone methods. +- exact job-analysis, question-set, question-to-competency mapping, and rating-anchor references plus independent SHA-256 digests; +- sorted, unique job-related competency references and a bounded 2–8 actor interviewer panel; +- a bounded question count at least as large as the governed competency count, while the separately bound mapping artifact supplies actual coverage evidence; and +- fixed purpose `structured_interview_plan`, closed reason `approved_requisition_interview`, bounded positive `evidence_version`, precision-preserving UTC time, mandatory human confirmation, and `requires_human_approval` state. -At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register that seal outside the plan's writable dataclass slots, keyed only to the live plan identity and removed when the plan is collected. Registration is single-use for one live identity: if issuance evidence already exists, repeated initialization fails closed instead of overwriting the original seal. `canonical_json()` renders the current payload once, requires creation-bound issuance evidence, and uses constant-time comparison against the stored seal before returning any bytes; `sha256_digest()` is downstream of the same validation. A low-level post-construction field rewrite therefore fails closed instead of silently redefining the approved-plan candidate, and copied/reconstructed objects cannot inherit issuance authority merely by reproducing fields. This HMAC is deliberately a same-process integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace immutable authoritative audit/outbox evidence or any future portable signature contract. +`tenant_record_id` follows the authoritative operational UUID contract. Packet-owned trust-bearing references require canonical non-sentinel UUIDv4 plus their expected namespace. Names, labels, compensation/protected-attribute values, and other semantic suffixes fail closed. Direct construction, builder construction, and `dataclasses.replace(...)` share the same plan validation. `evidence_version` is serialized canonically and changes immutable SHA-256 correlation when revised. Routine plan representation is fully redacted. -The immutable next action requires the host, immediately before activation, to re-resolve every plan reference within `tenant_record_id`; prove the requisition-to-Job-to-job-analysis binding; verify question-set, question-to-competency mapping, and rating-anchor provenance; re-resolve every panel actor; prove the resolved panel actor identities are distinct; and verify panel eligibility and training. +Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. Treat offset evaluation as an untrusted-code boundary: exceptions and offset arithmetic beyond Python's representable `datetime` range become the same field-specific `ValueError`. Store the built-in UTC snapshot rather than caller-owned `tzinfo` state. Canonical timestamp rendering reuses this fail-closed detachment. -Make that control flow executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before any authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, detaches caller-owned `approved_at` into one built-in UTC datetime using one concrete UTC offset, validates the approving actor, and obtains the creation-bound canonical plan JSON. Chronology, tenant/interview-plan scope, and SHA-256 are all derived from those canonical bytes instead of rereading live plan attributes. If caller-controlled offset evaluation raises or approval-time normalization would exceed Python's representable `datetime` range, fail with field-specific `ValueError` before any authority call. +At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register it outside plan-writable slots, keyed to the live plan identity and removed when collected. Registration is single-use for one live plan identity. `canonical_json()` requires creation-bound issuance evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. This HMAC is same-process integrity evidence only, not a persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. -The authority receives `plan_canonical_json`, its exact `plan_digest`, the approving actor reference, and the exact normalized approval instant. It does **not** receive the live `StructuredInterviewPlan`. Consequently an external alias may change and restore the caller's plan while the authority runs, but that ABA cycle cannot change the immutable plan revision presented for authoritative review. Activation still repeats creation-bound plan validation after the authority returns so any non-restored live-object mutation fails closed. +Make authoritative activation executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, obtains creation-bound canonical plan JSON, derives tenant/interview-plan scope and SHA-256 from those bytes, detaches caller-owned `approved_at` into built-in UTC, validates the approving actor, and rejects chronology before plan generation. The authority receives only detached canonical plan JSON, its exact digest, approving actor, and normalized approval instant—never the live plan object. A retained alias therefore cannot change what the authority reviews through a temporary change-and-restore cycle; non-restored mutation is still rejected by the post-authority plan integrity check. -Implement `StructuredInterviewActivationVerification` as a `NamedTuple` carrying tenant, interview-plan reference, plan digest, approving actor, authority-evidence reference/digest, and reviewed `approved_at`. Require the exact runtime type so behavioral subclasses cannot alter reads. Tuple field storage rejects `object.__setattr__`; activation unpacks the exact tuple once, normalizes the returned approval time through the same fail-closed UTC helper, validates those values, and compares the complete scope against the pre-call request. This closes the mixed-revision window possible with a frozen dataclass whose fields could still be rewritten between sequential reads. +Implement `StructuredInterviewActivationVerification` as an exact `NamedTuple` carrying tenant, interview-plan reference, plan digest, approving actor, authority-evidence reference/digest, and reviewed `approved_at`. Activation rejects subclasses, unpacks the exact tuple once, normalizes returned approval time through the same fail-closed UTC helper, validates returned values, and compares the complete tenant/plan/digest/actor/time scope against the pre-call request. -A successful activation emits a separate immutable `StructuredInterviewActivationReceipt` rather than mutating the reviewed plan. The receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, the detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Its routine representation is fully redacted and its canonical JSON/SHA-256 is the explicit immutable correlation surface. Direct receipt construction and canonical rendering use the same fail-closed timestamp helper so caller-controlled timezone failures cannot leak arbitrary exceptions through receipt validation. +`StructuredInterviewActivationReceipt` is a value-minimized receipt shape, not an authorization primitive. Its dataclass constructor validates tenant/reference/digest/time/fixed-governance values but **does not register issuance evidence**. Direct construction and `dataclasses.replace(...)` therefore produce unissued values whose `canonical_json()` and `sha256_digest()` fail closed. The constructor has no issuance-token parameter; a module-private legacy sentinel confers no authority and is retained only as a regression target proving that callers cannot mint issued receipts by importing a private module attribute. -At successful receipt construction, compute a process-local HMAC over the exact canonical receipt payload and register that seal outside the receipt's writable slots, keyed only to the live receipt identity and removed when the receipt is collected. Registration is single-use for one live receipt identity: if issuance evidence already exists, repeated initialization fails closed before assignment or finalizer registration, leaving the original authority-bound seal intact. `canonical_json()` recomputes the seal from the current payload and uses constant-time comparison against that creation-bound evidence; `sha256_digest()` is downstream of the same validation. Missing issuance evidence, a low-level post-issuance field rewrite, or an attempt to renew issuance evidence after such a rewrite therefore fails closed instead of exporting changed bytes as if they were the originally issued receipt. This HMAC is deliberately a runtime integrity guard rather than a persisted signing scheme: its key is process-local, is not exported, and does not replace the host's immutable audit/outbox evidence or any future portable signature contract. +Only after `activate_structured_interview_plan(...)` has accepted exact verification evidence, normalized the returned approval instant, validated all returned fields, and matched tenant, interview-plan reference, plan digest, approving actor, and approval time does it construct the receipt and register a process-local HMAC seal over that exact canonical payload. Issued receipt canonical export recomputes and constant-time compares the seal. Any low-level post-issuance rewrite or missing issuance evidence fails closed. The seal is same-process integrity evidence only; it is not a durable signing key, portable attestation, cross-process rehydration credential, or substitute for the host's immutable audit/outbox record. -The plan and activation receipt are candidate-neutral. They contain no candidate identity, response, score, demographic attribute, compensation value, free-form model output, provider credential, or final selection recommendation. Canonical JSON and SHA-256 provide immutable audit correlation; they do not prove the interview is valid, fair, legally compliant, tenant-owned, correctly linked, or scientifically adequate. Opaque identifiers and references remain sensitive correlation metadata rather than anonymous data. +The issued receipt records the exact plan digest, accountable UUIDv4 approving actor, authority-verification reference/digest, fixed purpose `structured_interview_activation`, fixed reason `human_approved_plan_activation`, bounded positive evidence version, detached precision-preserving UTC approval time, `human_confirmation=True`, and fixed `approved_for_use` state. Routine receipt and verification representations are fully redacted. The plan and receipt remain candidate-neutral: they contain no candidate identity, response, score, demographic attribute, compensation value, free-form model output, provider credential, or final selection recommendation. ## Consequences ### Positive -- Buyers can prove which Job Analysis, competencies, questions, question-to-competency mapping, rating anchors, interviewer panel, and evidence revision were reviewed before candidate use. -- Caller-owned mutable timezone state cannot change or invalidate an already-issued plan generation instant because construction stores one detached built-in UTC snapshot before sealing. -- Exceptions raised by caller-owned timezone implementations are normalized into field-specific governed validation before plan issuance, activation authority work, verification acceptance, or receipt canonicalization. -- Unrepresentable UTC normalization at `datetime` boundaries fails as field-specific governed validation before plan issuance or activation authority side effects. -- Once a plan is constructed, low-level in-memory rewriting cannot silently redefine its canonical JSON or SHA-256; missing, copied, mismatched, or duplicate process-local issuance evidence fails closed before activation can rely on it. -- The authoritative adapter reviews detached creation-bound canonical plan evidence rather than a caller-owned live plan object, so temporary change-and-restore mutation cannot substitute a different revision during review. -- Authority verification fields are tuple-immutable at runtime; exact-type enforcement and one-time tuple unpacking prevent mixed authority-evidence revisions between validation reads. -- Caller-owned mutable timezone state cannot alter the receipt's approval instant after validation because activation uses one detached built-in UTC snapshot end to end. -- Already-issued receipt objects cannot silently export rewritten canonical evidence or renew their issuance history after low-level mutation; missing, mismatched, or duplicate creation-bound issuance evidence fails closed. -- The exact approval instant crosses the authoritative adapter boundary and must return in verification evidence, so approved receipt chronology cannot be created from a timestamp the authority never explicitly attested. -- Successful activation evidence names the accountable human actor and binds that approval to the exact reviewed plan digest plus authoritative verification evidence. -- Candidate PII and assessment values remain outside the planning and activation artifacts. -- Packet-owned trust references reject UUIDv1/time-node-bearing suffixes and value-bearing metadata without making the leaf package incompatible with authoritative Orgmetra tenant UUIDs. -- Routine representation/logging does not expose references or evidence digests. -- Downstream interview-result and selection-decision boundaries can reject drift from the approved plan by reference/digest/version rather than copying question content. -- The authority protocol preserves standalone operation and later MSA extraction without cross-service application-table SQL or duplicated foreign service state. +- Buyers can prove which Job Analysis, competencies, questions, mapping, rating anchors, panel, and evidence revision were reviewed before candidate use. +- Caller-controlled timezone failures and mutable timezone state cannot silently redefine governed plan or approval instants. +- The authoritative adapter reviews detached creation-bound plan evidence rather than a caller-owned live plan object. +- Authority verification fields are tuple-immutable at runtime and exact-type checked before one-time unpacking. +- A caller cannot mint an `approved_for_use` evidence artifact by importing a private constructor sentinel: direct and replaced receipt values remain unissued and cannot export canonical evidence. +- Receipt issuance is causally ordered after authoritative host verification and exact tenant/plan/digest/actor/time matching. +- Post-issuance receipt mutation and missing process-local issuance evidence fail closed before canonical export. +- Candidate PII and assessment values remain outside planning and activation artifacts. +- The authority protocol preserves standalone operation and later MSA extraction without cross-service application-table SQL or duplicated foreign-service state. ### Costs and constraints - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. -- The authority protocol is not itself proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters need executable integration evidence and must bind the supplied canonical plan evidence plus normalized approval instant into their immutable authority evidence. -- Plan and activation-receipt HMAC seals exist only for the lifetime of each in-process object. They are not portable signatures, durable verification credentials, key-management facilities, or substitutes for persisted authoritative audit evidence; copied or reconstructed plan objects intentionally fail closed unless a future authoritative rehydration contract explicitly re-establishes issuance evidence. +- The authority protocol is not proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters still need executable integration evidence and immutable authority/audit records. +- Plan and activation-receipt HMAC seals exist only for the lifetime of their in-process objects. They are not portable signatures, durable verification credentials, or key-management facilities. +- Directly constructed receipt values are intentionally unusable as authoritative evidence until a supported future rehydration/issuance contract exists. - Human approval remains mandatory; model output cannot activate or approve the plan. -- UUIDv4-backed package references reduce accidental value leakage but do not remove authorization, retention, export-control, or audit obligations for correlation metadata. Tenant UUID generation/privacy policy remains owned by the authoritative HRIS boundary. -- Reference inequality does not prove distinct authoritative panel identities; the host must resolve and compare those identities in the exact tenant. -- Evidence versions and digests identify reviewed revisions but do not establish substantive scientific adequacy; content validity, criterion-related validity, adverse-impact analysis, interviewer training evidence, accommodations, and jurisdiction-specific legal review remain separate evidence obligations. +- UUID/digest metadata and reference inequality do not establish tenant ownership, identity separation, scientific validity, fairness, or legal compliance. - This ADR remains proposed until its exact PR head merges into protected `develop`. ## References From 4c25757e815b66fe3a76092ed8e46e6abfc5c7d2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:16:28 -0700 Subject: [PATCH 189/216] test(interview-plan): require supported Python compatibility lanes --- .../tests/test_supported_python_versions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/interview-plan/tests/test_supported_python_versions.py diff --git a/packages/interview-plan/tests/test_supported_python_versions.py b/packages/interview-plan/tests/test_supported_python_versions.py new file mode 100644 index 000000000..14a5c0ecc --- /dev/null +++ b/packages/interview-plan/tests/test_supported_python_versions.py @@ -0,0 +1,19 @@ +"""Regression coverage for the package's declared Python compatibility contract.""" + +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] + + +def test_declared_python_floor_has_executable_compatibility_lanes() -> None: + """Require every declared minor from 3.12 through the primary 3.14 lane in CI.""" + package_config = (REPOSITORY_ROOT / "packages/interview-plan/pyproject.toml").read_text() + workflow = (REPOSITORY_ROOT / ".github/workflows/interview-plan-quality.yml").read_text() + + assert 'requires-python = ">=3.12"' in package_config + assert "compatibility:" in workflow + assert '"3.12"' in workflow + assert '"3.13"' in workflow + assert 'python-version: "3.14"' in workflow + assert "python-version: ${{ matrix.python-version }}" in workflow From 199be0cf0d6cb4888fb3f612799487d872e7f868 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:16:45 -0700 Subject: [PATCH 190/216] build(interview-plan): pin coverage wheels for supported Python minors --- .github/requirements/foundation-test.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/requirements/foundation-test.txt b/.github/requirements/foundation-test.txt index 40d926005..8bfe48ae1 100644 --- a/.github/requirements/foundation-test.txt +++ b/.github/requirements/foundation-test.txt @@ -1,6 +1,9 @@ -# Reviewed Foundation CI test toolchain for CPython 3.14 on GitHub-hosted Ubuntu x86_64. +# Reviewed Foundation CI test toolchain for CPython 3.12-3.14 on GitHub-hosted Ubuntu x86_64. # Version and artifact hash changes must be reverified against the official PyPI release JSON. -coverage==7.14.2 --hash=sha256:cda36d8e7bfd63b3e44e75163265429caa5d935b672b00f71bccc8c010518c64 +coverage==7.14.2 \ + --hash=sha256:8b4910cce599cd2438f8da65f5ef199a70a1cdb6ab314926df78271ca5954240 \ + --hash=sha256:1d9a1b5813d00ea6151f6ccf64d1fa16892771dfdda12ba87162d15ec4ea3e1e \ + --hash=sha256:cda36d8e7bfd63b3e44e75163265429caa5d935b672b00f71bccc8c010518c64 iniconfig==2.3.0 --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 From b428ed7aea4cabbcdec51ad115f8109dce41cb27 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:16:59 -0700 Subject: [PATCH 191/216] ci(interview-plan): test every supported Python minor --- .github/workflows/interview-plan-quality.yml | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.github/workflows/interview-plan-quality.yml b/.github/workflows/interview-plan-quality.yml index f87064f15..aafb4c4ed 100644 --- a/.github/workflows/interview-plan-quality.yml +++ b/.github/workflows/interview-plan-quality.yml @@ -63,3 +63,42 @@ jobs: run: | git diff --exit-code test -z "$(git status --porcelain)" + + compatibility: + name: Python ${{ matrix.python-version }} compatibility and 100% coverage + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13"] + 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: ${{ matrix.python-version }} + 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 structured interview plan package + run: python -m compileall -q packages/interview-plan/src packages/interview-plan/tests + - name: Test supported Python with exact statement and branch coverage + env: + PYTHONPATH: packages/interview-plan/src + COVERAGE_FILE: /tmp/orgmetra-structured-interview-plan-${{ matrix.python-version }}.coverage + run: python -m pytest -c packages/interview-plan/pyproject.toml packages/interview-plan/tests + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" From fdf36e9731f930a330dd4ba5a9fc71e10cff9c14 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:17:53 -0700 Subject: [PATCH 192/216] test(interview-plan): preserve factory receipt seal across revalidation --- .../tests/test_activation_integrity_review.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py index 866b26e77..c3310d67c 100644 --- a/packages/interview-plan/tests/test_activation_integrity_review.py +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -136,7 +136,7 @@ def test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation(): def test_existing_receipt_identity_cannot_renew_issuance_seal_after_mutation(): - """Repeated receipt initialization must not legitimize changed post-authority evidence.""" + """Receipt revalidation must not create new issuance evidence after factory issuance.""" candidate_plan = plan() receipt = activate_structured_interview_plan( plan=candidate_plan, @@ -144,17 +144,12 @@ def test_existing_receipt_identity_cannot_renew_issuance_seal_after_mutation(): approving_actor_reference=APPROVER, approved_at=APPROVED_AT, ) + original_seal = activation_module._authoritative_activation_receipt_seal(receipt) object.__setattr__(receipt, "plan_digest", "f" * 64) - object.__setattr__( - receipt, - "_issuance_token", - activation_module._ACTIVATION_RECEIPT_ISSUANCE_TOKEN, - ) - with pytest.raises(ValueError, match="issuance evidence already exists"): - receipt.__post_init__() + receipt.__post_init__() - object.__setattr__(receipt, "_issuance_token", None) + assert activation_module._authoritative_activation_receipt_seal(receipt) == original_seal with pytest.raises(ValueError, match="changed after activation receipt issuance"): receipt.canonical_json() From 1628b5d61cab9519cd8ed5412e50ebf4420e01e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:20:59 -0700 Subject: [PATCH 193/216] test(interview-plan): hide receipt issuance capabilities from module callers --- .../tests/test_receipt_issuance.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/interview-plan/tests/test_receipt_issuance.py b/packages/interview-plan/tests/test_receipt_issuance.py index c1e66f631..db58c1ffb 100644 --- a/packages/interview-plan/tests/test_receipt_issuance.py +++ b/packages/interview-plan/tests/test_receipt_issuance.py @@ -38,8 +38,23 @@ def test_activation_receipt_cannot_be_minted_without_verified_factory_path(): receipt.sha256_digest() -def test_private_module_sentinel_cannot_mint_verified_receipt_directly(): - """A module-private sentinel must not be usable as authority evidence by callers.""" +def test_receipt_issuance_capabilities_are_not_module_attributes(): + """Do not publish callables or secrets that can mint verified receipt evidence.""" + forbidden_names = ( + "_ACTIVATION_RECEIPT_ISSUANCE_TOKEN", + "_PROCESS_ACTIVATION_RECEIPT_SEAL_KEY", + "_ACTIVATION_RECEIPT_SEALS", + "_register_activation_receipt_seal", + "_seal_activation_receipt", + "_discard_activation_receipt_seal", + "_authoritative_activation_receipt_seal", + ) + + assert all(not hasattr(activation_module, name) for name in forbidden_names) + + +def test_private_constructor_argument_cannot_mint_verified_receipt_directly(): + """Constructor-private-looking keywords must never act as issuance authority.""" with pytest.raises(TypeError, match="_issuance_token"): StructuredInterviewActivationReceipt( tenant_record_id="10000000-0000-7000-8000-000000000001", @@ -51,7 +66,7 @@ def test_private_module_sentinel_cannot_mint_verified_receipt_directly(): ), authority_evidence_digest="e" * 64, approved_at=datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc), - _issuance_token=activation_module._ACTIVATION_RECEIPT_ISSUANCE_TOKEN, + _issuance_token=object(), ) @@ -89,18 +104,3 @@ def test_issued_activation_receipt_rejects_post_issuance_rewrite(): receipt.canonical_json() with pytest.raises(ValueError, match="changed after activation receipt issuance"): receipt.sha256_digest() - - -def test_missing_process_local_activation_receipt_issuance_evidence_fails_closed(): - """Reject canonical export when process-local issuance evidence is unavailable.""" - candidate_plan = plan() - receipt = activate_structured_interview_plan( - plan=candidate_plan, - authority=AllowingAuthority(verification_for(candidate_plan)), - approving_actor_reference=APPROVER, - approved_at=APPROVED_AT, - ) - activation_module._discard_activation_receipt_seal(id(receipt)) - - with pytest.raises(ValueError, match="changed after activation receipt issuance"): - receipt.canonical_json() From ebadac3b6aff57872189cc739c9e8f8f4d5956bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:21:36 -0700 Subject: [PATCH 194/216] fix(interview-plan): encapsulate receipt issuance authority --- .../src/orgmetra_interview_plan/activation.py | 445 +++++++++--------- 1 file changed, 226 insertions(+), 219 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index d1ed4c088..edbf334f1 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -33,11 +33,6 @@ _REASON_CODE = "human_approved_plan_activation" _ACTIVATION_STATE = "approved_for_use" _MAX_EVIDENCE_VERSION = 2_147_483_647 -# Legacy private sentinel retained only so regression proves it confers no issuance authority. -_ACTIVATION_RECEIPT_ISSUANCE_TOKEN = object() -_PROCESS_ACTIVATION_RECEIPT_SEAL_KEY = secrets.token_bytes(32) -_ACTIVATION_RECEIPT_SEALS: dict[int, str] = {} -_ACTIVATION_RECEIPT_SEALS_LOCK = RLock() def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: @@ -58,39 +53,6 @@ def _snapshot_utc_datetime(value: datetime, field_name: str) -> datetime: return normalized.replace(tzinfo=timezone.utc) -def _discard_activation_receipt_seal(receipt_id: int) -> None: - """Discard process-local activation issuance evidence after receipt collection.""" - with _ACTIVATION_RECEIPT_SEALS_LOCK: - _ACTIVATION_RECEIPT_SEALS.pop(receipt_id, None) - - -def _register_activation_receipt_seal(receipt: object, seal: str) -> None: - """Bind one live receipt identity once to evidence outside receipt-writable slots.""" - receipt_id = id(receipt) - with _ACTIVATION_RECEIPT_SEALS_LOCK: - if receipt_id in _ACTIVATION_RECEIPT_SEALS: - raise ValueError( - "structured interview activation receipt issuance evidence already exists" - ) - _ACTIVATION_RECEIPT_SEALS[receipt_id] = seal - finalize(receipt, _discard_activation_receipt_seal, receipt_id) - - -def _authoritative_activation_receipt_seal(receipt: object) -> str | None: - """Return process-local issuance evidence without trusting receipt-owned state.""" - with _ACTIVATION_RECEIPT_SEALS_LOCK: - return _ACTIVATION_RECEIPT_SEALS.get(id(receipt)) - - -def _seal_activation_receipt(payload_json: str) -> str: - """Bind one process-local activation issuance to exact canonical payload bytes.""" - return hmac.new( - _PROCESS_ACTIVATION_RECEIPT_SEAL_KEY, - payload_json.encode("utf-8"), - "sha256", - ).hexdigest() - - class StructuredInterviewActivationVerification(NamedTuple): """Runtime-immutable authoritative host evidence returned after activation checks pass.""" @@ -121,201 +83,246 @@ def verify_activation( """Verify detached creation-bound plan bytes and return exact-scope evidence.""" -@dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) -class StructuredInterviewActivationReceipt: - """Value-minimized activation receipt whose trusted export requires factory issuance.""" +def _build_activation_surface(): + """Build the public receipt type and activation factory around a lexical seal vault. - tenant_record_id: str - interview_plan_reference: str - plan_digest: str - approving_actor_reference: str - authority_evidence_reference: str - authority_evidence_digest: str - approved_at: datetime - purpose_code: str = _PURPOSE_CODE - reason_code: str = _REASON_CODE - evidence_version: int = 1 - human_confirmation: bool = True - activation_state: str = _ACTIVATION_STATE - - def __post_init__(self) -> None: - """Reject forged, ambiguous, or weakened receipt values before possible issuance.""" - _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + Receipt issuance state deliberately lives in this lexical scope rather than module + attributes. Python process-local integrity is defense-in-depth, not a sandbox against + arbitrary same-process introspection; production hosts must keep untrusted code out of + the application trust domain and persist authoritative audit/outbox evidence separately. + """ + receipt_seal_key = secrets.token_bytes(32) + receipt_seals: dict[int, str] = {} + receipt_seals_lock = RLock() + + def seal_receipt(payload_json: str) -> str: + """Return a process-local HMAC over one exact canonical receipt payload.""" + return hmac.new( + receipt_seal_key, + payload_json.encode("utf-8"), + "sha256", + ).hexdigest() + + def discard_receipt_seal(receipt_id: int) -> None: + """Discard lexical issuance evidence after the issued receipt is collected.""" + with receipt_seals_lock: + receipt_seals.pop(receipt_id, None) + + def register_receipt_seal(receipt: object, payload_json: str) -> None: + """Register issuance once, only from the verified activation factory below.""" + receipt_id = id(receipt) + with receipt_seals_lock: + if receipt_id in receipt_seals: + raise ValueError( + "structured interview activation receipt issuance evidence already exists" + ) + receipt_seals[receipt_id] = seal_receipt(payload_json) + finalize(receipt, discard_receipt_seal, receipt_id) + + def authoritative_receipt_seal(receipt: object) -> str | None: + """Read lexical issuance evidence without trusting receipt-writable state.""" + with receipt_seals_lock: + return receipt_seals.get(id(receipt)) + + @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) + class StructuredInterviewActivationReceipt: + """Value-minimized activation receipt whose trusted export requires factory issuance.""" + + tenant_record_id: str + interview_plan_reference: str + plan_digest: str + approving_actor_reference: str + authority_evidence_reference: str + authority_evidence_digest: str + approved_at: datetime + purpose_code: str = _PURPOSE_CODE + reason_code: str = _REASON_CODE + evidence_version: int = 1 + human_confirmation: bool = True + activation_state: str = _ACTIVATION_STATE + + def __post_init__(self) -> None: + """Reject forged, ambiguous, or weakened receipt values before possible issuance.""" + _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") + _validate_reference( + self.interview_plan_reference, + "interview_plan", + "interview_plan_reference", + ) + _validate_digest(self.plan_digest, "plan_digest") + _validate_reference( + self.approving_actor_reference, + "actor", + "approving_actor_reference", + ) + _validate_reference( + self.authority_evidence_reference, + "activation_verification", + "authority_evidence_reference", + ) + _validate_digest(self.authority_evidence_digest, "authority_evidence_digest") + _canonical_timestamp(self.approved_at, "approved_at") + _validate_code(self.purpose_code, "purpose_code") + if self.purpose_code != _PURPOSE_CODE: + raise ValueError("purpose_code must remain structured_interview_activation") + _validate_code(self.reason_code, "reason_code") + if self.reason_code != _REASON_CODE: + raise ValueError("reason_code must remain human_approved_plan_activation") + if ( + type(self.evidence_version) is not int + or not 1 <= self.evidence_version <= _MAX_EVIDENCE_VERSION + ): + raise ValueError("evidence_version must be an integer from 1 through 2147483647") + if self.human_confirmation is not True: + raise ValueError("human confirmation is mandatory for interview-plan activation") + if self.activation_state != _ACTIVATION_STATE: + raise ValueError("activation_state must remain approved_for_use") + + def __repr__(self) -> str: + """Return a redacted representation suitable for routine logs.""" + return "StructuredInterviewActivationReceipt()" + + def _canonical_json_unchecked(self) -> str: + """Render canonical activation bytes without process-local issuance state.""" + payload = { + "activation_state": self.activation_state, + "approved_at": _canonical_timestamp(self.approved_at, "approved_at"), + "approving_actor_reference": self.approving_actor_reference, + "authority_evidence_digest": self.authority_evidence_digest, + "authority_evidence_reference": self.authority_evidence_reference, + "evidence_version": self.evidence_version, + "human_confirmation": self.human_confirmation, + "interview_plan_reference": self.interview_plan_reference, + "plan_digest": self.plan_digest, + "purpose_code": self.purpose_code, + "reason_code": self.reason_code, + "tenant_record_id": self.tenant_record_id, + } + return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + def canonical_json(self) -> str: + """Return factory-issued canonical JSON for immutable audit correlation.""" + canonical = self._canonical_json_unchecked() + authoritative_seal = authoritative_receipt_seal(self) + if ( + type(authoritative_seal) is not str + or not hmac.compare_digest( + seal_receipt(canonical), + authoritative_seal, + ) + ): + raise ValueError( + "structured interview activation receipt changed after activation receipt issuance" + ) + return canonical + + def sha256_digest(self) -> str: + """Return SHA-256 over the exact factory-issued activation receipt.""" + return sha256(self.canonical_json().encode("utf-8")).hexdigest() + + def activate_structured_interview_plan( + *, + plan: StructuredInterviewPlan, + authority: StructuredInterviewActivationAuthority, + approving_actor_reference: str, + approved_at: datetime, + ) -> StructuredInterviewActivationReceipt: + """Activate one exact plan only after authoritative host verification succeeds. + + The host authority receives detached creation-bound plan evidence and the + normalized approval instant, never the caller's live plan object. Receipt + issuance is registered only after verification type, fields, and complete + tenant/plan/digest/actor/time scope have all matched the request. + """ + if type(plan) is not StructuredInterviewPlan: + raise TypeError("plan must be a StructuredInterviewPlan") + approved_at_snapshot = _snapshot_utc_datetime(approved_at, "approved_at") + _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") + + plan_canonical_json = plan.canonical_json() + plan_payload = json.loads(plan_canonical_json) + plan_generated_at = datetime.fromisoformat( + plan_payload["generated_at"].replace("Z", "+00:00") + ) + if approved_at_snapshot < plan_generated_at: + raise ValueError("approved_at must not precede plan generated_at") + plan_digest = sha256(plan_canonical_json.encode("utf-8")).hexdigest() + plan_tenant_record_id = plan_payload["tenant_record_id"] + interview_plan_reference = plan_payload["interview_plan_reference"] + + verification = authority.verify_activation( + plan_canonical_json=plan_canonical_json, + plan_digest=plan_digest, + approving_actor_reference=approving_actor_reference, + approved_at=approved_at_snapshot, + ) + plan.canonical_json() + if type(verification) is not StructuredInterviewActivationVerification: + raise TypeError("authority must return StructuredInterviewActivationVerification") + + ( + verified_tenant_record_id, + verified_interview_plan_reference, + verified_plan_digest, + verified_approving_actor_reference, + verified_authority_evidence_reference, + verified_authority_evidence_digest, + verification_approved_at, + ) = verification + verified_approved_at = _snapshot_utc_datetime(verification_approved_at, "approved_at") + + _validate_operational_uuid(verified_tenant_record_id, "tenant_record_id") _validate_reference( - self.interview_plan_reference, + verified_interview_plan_reference, "interview_plan", "interview_plan_reference", ) - _validate_digest(self.plan_digest, "plan_digest") + _validate_digest(verified_plan_digest, "plan_digest") _validate_reference( - self.approving_actor_reference, + verified_approving_actor_reference, "actor", "approving_actor_reference", ) _validate_reference( - self.authority_evidence_reference, + verified_authority_evidence_reference, "activation_verification", "authority_evidence_reference", ) - _validate_digest(self.authority_evidence_digest, "authority_evidence_digest") - _canonical_timestamp(self.approved_at, "approved_at") - _validate_code(self.purpose_code, "purpose_code") - if self.purpose_code != _PURPOSE_CODE: - raise ValueError("purpose_code must remain structured_interview_activation") - _validate_code(self.reason_code, "reason_code") - if self.reason_code != _REASON_CODE: - raise ValueError("reason_code must remain human_approved_plan_activation") - if type(self.evidence_version) is not int or not 1 <= self.evidence_version <= _MAX_EVIDENCE_VERSION: - raise ValueError("evidence_version must be an integer from 1 through 2147483647") - if self.human_confirmation is not True: - raise ValueError("human confirmation is mandatory for interview-plan activation") - if self.activation_state != _ACTIVATION_STATE: - raise ValueError("activation_state must remain approved_for_use") - - def __repr__(self) -> str: - """Return a redacted representation suitable for routine logs.""" - return "StructuredInterviewActivationReceipt()" - - def _canonical_json_unchecked(self) -> str: - """Render canonical activation bytes without process-local issuance state.""" - payload = { - "activation_state": self.activation_state, - "approved_at": _canonical_timestamp(self.approved_at, "approved_at"), - "approving_actor_reference": self.approving_actor_reference, - "authority_evidence_digest": self.authority_evidence_digest, - "authority_evidence_reference": self.authority_evidence_reference, - "evidence_version": self.evidence_version, - "human_confirmation": self.human_confirmation, - "interview_plan_reference": self.interview_plan_reference, - "plan_digest": self.plan_digest, - "purpose_code": self.purpose_code, - "reason_code": self.reason_code, - "tenant_record_id": self.tenant_record_id, - } - return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) - - def canonical_json(self) -> str: - """Return factory-issued canonical JSON for immutable audit correlation.""" - canonical = self._canonical_json_unchecked() - authoritative_seal = _authoritative_activation_receipt_seal(self) - if ( - type(authoritative_seal) is not str - or not hmac.compare_digest( - _seal_activation_receipt(canonical), - authoritative_seal, - ) - ): + _validate_digest(verified_authority_evidence_digest, "authority_evidence_digest") + + expected_scope = ( + plan_tenant_record_id, + interview_plan_reference, + plan_digest, + approving_actor_reference, + approved_at_snapshot, + ) + verified_scope = ( + verified_tenant_record_id, + verified_interview_plan_reference, + verified_plan_digest, + verified_approving_actor_reference, + verified_approved_at, + ) + if verified_scope != expected_scope: raise ValueError( - "structured interview activation receipt changed after activation receipt issuance" + "activation authority returned evidence for a different plan or actor or approval time" ) - return canonical - - def sha256_digest(self) -> str: - """Return SHA-256 over the exact factory-issued activation receipt.""" - return sha256(self.canonical_json().encode("utf-8")).hexdigest() - - -def activate_structured_interview_plan( - *, - plan: StructuredInterviewPlan, - authority: StructuredInterviewActivationAuthority, - approving_actor_reference: str, - approved_at: datetime, -) -> StructuredInterviewActivationReceipt: - """Activate one exact plan only after authoritative host verification succeeds. - - The authority implementation is responsible for tenant-scoped re-resolution, - relationship/provenance/panel checks, and review of the exact approval instant. - It receives only detached creation-bound canonical plan bytes plus their digest, - never the caller's live plan object. Authority results are runtime-immutable tuple - evidence; this function validates those exact values and emits a value-minimized - human-approval receipt only for the exact verified scope. Process-local issuance - evidence is registered only after all authoritative checks and scope matching pass. - """ - if type(plan) is not StructuredInterviewPlan: - raise TypeError("plan must be a StructuredInterviewPlan") - approved_at_snapshot = _snapshot_utc_datetime(approved_at, "approved_at") - _validate_reference(approving_actor_reference, "actor", "approving_actor_reference") - - plan_canonical_json = plan.canonical_json() - plan_payload = json.loads(plan_canonical_json) - plan_generated_at = datetime.fromisoformat(plan_payload["generated_at"].replace("Z", "+00:00")) - if approved_at_snapshot < plan_generated_at: - raise ValueError("approved_at must not precede plan generated_at") - plan_digest = sha256(plan_canonical_json.encode("utf-8")).hexdigest() - plan_tenant_record_id = plan_payload["tenant_record_id"] - interview_plan_reference = plan_payload["interview_plan_reference"] - - verification = authority.verify_activation( - plan_canonical_json=plan_canonical_json, - plan_digest=plan_digest, - approving_actor_reference=approving_actor_reference, - approved_at=approved_at_snapshot, - ) - plan.canonical_json() - if type(verification) is not StructuredInterviewActivationVerification: - raise TypeError("authority must return StructuredInterviewActivationVerification") - - ( - verified_tenant_record_id, - verified_interview_plan_reference, - verified_plan_digest, - verified_approving_actor_reference, - verified_authority_evidence_reference, - verified_authority_evidence_digest, - verification_approved_at, - ) = verification - verified_approved_at = _snapshot_utc_datetime(verification_approved_at, "approved_at") - - _validate_operational_uuid(verified_tenant_record_id, "tenant_record_id") - _validate_reference( - verified_interview_plan_reference, - "interview_plan", - "interview_plan_reference", - ) - _validate_digest(verified_plan_digest, "plan_digest") - _validate_reference( - verified_approving_actor_reference, - "actor", - "approving_actor_reference", - ) - _validate_reference( - verified_authority_evidence_reference, - "activation_verification", - "authority_evidence_reference", - ) - _validate_digest(verified_authority_evidence_digest, "authority_evidence_digest") - - expected_scope = ( - plan_tenant_record_id, - interview_plan_reference, - plan_digest, - approving_actor_reference, - approved_at_snapshot, - ) - verified_scope = ( - verified_tenant_record_id, - verified_interview_plan_reference, - verified_plan_digest, - verified_approving_actor_reference, - verified_approved_at, - ) - if verified_scope != expected_scope: - raise ValueError( - "activation authority returned evidence for a different plan or actor or approval time" + + receipt = StructuredInterviewActivationReceipt( + tenant_record_id=plan_tenant_record_id, + interview_plan_reference=interview_plan_reference, + plan_digest=plan_digest, + approving_actor_reference=approving_actor_reference, + authority_evidence_reference=verified_authority_evidence_reference, + authority_evidence_digest=verified_authority_evidence_digest, + approved_at=approved_at_snapshot, ) + register_receipt_seal(receipt, receipt._canonical_json_unchecked()) + return receipt + + return StructuredInterviewActivationReceipt, activate_structured_interview_plan + - receipt = StructuredInterviewActivationReceipt( - tenant_record_id=plan_tenant_record_id, - interview_plan_reference=interview_plan_reference, - plan_digest=plan_digest, - approving_actor_reference=approving_actor_reference, - authority_evidence_reference=verified_authority_evidence_reference, - authority_evidence_digest=verified_authority_evidence_digest, - approved_at=approved_at_snapshot, - ) - _register_activation_receipt_seal( - receipt, - _seal_activation_receipt(receipt._canonical_json_unchecked()), - ) - return receipt +StructuredInterviewActivationReceipt, activate_structured_interview_plan = _build_activation_surface() +del _build_activation_surface From 10fecfc29ed1c81de1e118a8c225deac50cf1150 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:22:09 -0700 Subject: [PATCH 195/216] test(interview-plan): verify receipt reseal fail-closed through public surface --- .../tests/test_activation_integrity_review.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/tests/test_activation_integrity_review.py b/packages/interview-plan/tests/test_activation_integrity_review.py index c3310d67c..4e5b957bb 100644 --- a/packages/interview-plan/tests/test_activation_integrity_review.py +++ b/packages/interview-plan/tests/test_activation_integrity_review.py @@ -6,7 +6,6 @@ import pytest -import orgmetra_interview_plan.activation as activation_module from orgmetra_interview_plan import ( StructuredInterviewActivationVerification, activate_structured_interview_plan, @@ -144,14 +143,15 @@ def test_existing_receipt_identity_cannot_renew_issuance_seal_after_mutation(): approving_actor_reference=APPROVER, approved_at=APPROVED_AT, ) - original_seal = activation_module._authoritative_activation_receipt_seal(receipt) + original_canonical = receipt.canonical_json() object.__setattr__(receipt, "plan_digest", "f" * 64) receipt.__post_init__() - assert activation_module._authoritative_activation_receipt_seal(receipt) == original_seal with pytest.raises(ValueError, match="changed after activation receipt issuance"): receipt.canonical_json() + object.__setattr__(receipt, "plan_digest", json.loads(original_canonical)["plan_digest"]) + assert receipt.canonical_json() == original_canonical def test_activation_rejects_naive_approval_time_before_authority_work(): From c858c3e2e453995bb63f1d8f8b9818879970bb12 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:57:55 -0700 Subject: [PATCH 196/216] fix(interview-plan): remove unreachable receipt reseal branch --- .../src/orgmetra_interview_plan/activation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index edbf334f1..16dc45742 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -109,13 +109,13 @@ def discard_receipt_seal(receipt_id: int) -> None: receipt_seals.pop(receipt_id, None) def register_receipt_seal(receipt: object, payload_json: str) -> None: - """Register issuance once, only from the verified activation factory below.""" + """Register one fresh receipt after verified factory activation succeeds. + + This lexical helper has one call site, after constructing a fresh receipt. Repeated + ``__post_init__`` validation therefore cannot reach it or renew issuance evidence. + """ receipt_id = id(receipt) with receipt_seals_lock: - if receipt_id in receipt_seals: - raise ValueError( - "structured interview activation receipt issuance evidence already exists" - ) receipt_seals[receipt_id] = seal_receipt(payload_json) finalize(receipt, discard_receipt_seal, receipt_id) @@ -281,7 +281,7 @@ def activate_structured_interview_plan( _validate_reference( verified_approving_actor_reference, "actor", - "approving_actor_reference", + "verified_approving_actor_reference", ) _validate_reference( verified_authority_evidence_reference, From bd4d0cfad9e2e1c484f79087d80775a04afbf80b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 15:58:31 -0700 Subject: [PATCH 197/216] fix(interview-plan): preserve approval evidence field contract --- .../interview-plan/src/orgmetra_interview_plan/activation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/activation.py b/packages/interview-plan/src/orgmetra_interview_plan/activation.py index 16dc45742..b699e857a 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/activation.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/activation.py @@ -281,7 +281,7 @@ def activate_structured_interview_plan( _validate_reference( verified_approving_actor_reference, "actor", - "verified_approving_actor_reference", + "approving_actor_reference", ) _validate_reference( verified_authority_evidence_reference, From bf8897affee31b494b490c8932c62bbf46a06cb4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 16:06:06 -0700 Subject: [PATCH 198/216] fix(ci): validate logical hashed requirements --- .../test_foundation_ci_dependency_hygiene.sh | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/test_foundation_ci_dependency_hygiene.sh b/tests/test_foundation_ci_dependency_hygiene.sh index 6a6cb51a8..660166204 100644 --- a/tests/test_foundation_ci_dependency_hygiene.sh +++ b/tests/test_foundation_ci_dependency_hygiene.sh @@ -58,14 +58,46 @@ if [[ ! -f "${requirements_path}" ]]; then exit 1 fi -mapfile -t package_lines < <(grep -Ev '^[[:space:]]*(#|$)' "${requirements_path}") +# pip requirements may carry multiple platform-specific hashes on backslash- +# continued physical lines. Validate and count logical requirements so adding +# reviewed wheel hashes cannot be misclassified as extra packages. +mapfile -t package_lines < <( + awk ' + /^[[:space:]]*(#|$)/ { next } + { + line=$0 + sub(/^[[:space:]]+/, "", line) + continues=(line ~ /\\[[:space:]]*$/) + sub(/[[:space:]]*\\[[:space:]]*$/, "", line) + if (logical == "") { + logical=line + } else { + logical=logical " " line + } + if (!continues) { + print logical + logical="" + } + } + END { + if (logical != "") { + print "__UNTERMINATED__ " logical + } + } + ' "${requirements_path}" +) + if [[ "${#package_lines[@]}" -ne 7 ]]; then printf 'Foundation CI requirements must contain the seven reviewed direct/runtime test packages.\n' >&2 exit 1 fi for package_line in "${package_lines[@]}"; do - if [[ ! "${package_line}" =~ ^[A-Za-z0-9._-]+==[0-9][A-Za-z0-9._-]*[[:space:]]--hash=sha256:[0-9a-f]{64}$ ]]; then + if [[ "${package_line}" == __UNTERMINATED__* ]]; then + printf 'Foundation CI requirement has an unterminated continuation: %s\n' "${package_line#__UNTERMINATED__ }" >&2 + exit 1 + fi + if [[ ! "${package_line}" =~ ^[A-Za-z0-9._-]+==[0-9][A-Za-z0-9._-]*([[:space:]]--hash=sha256:[0-9a-f]{64})+$ ]]; then printf 'Unpinned or unhashed Foundation CI requirement: %s\n' "${package_line}" >&2 exit 1 fi From cb3b2a0dca610fa168cb1fc15438962130e181a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 16:31:45 -0700 Subject: [PATCH 199/216] test(foundation): reproduce pipefail package lookup failure --- tests/test_foundation_ci_dependency_hygiene.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_foundation_ci_dependency_hygiene.sh b/tests/test_foundation_ci_dependency_hygiene.sh index 660166204..c656cf2d4 100644 --- a/tests/test_foundation_ci_dependency_hygiene.sh +++ b/tests/test_foundation_ci_dependency_hygiene.sh @@ -103,8 +103,23 @@ for package_line in "${package_lines[@]}"; do fi done +package_line_is_present() { + local package_name="$1" + shift + printf '%s\n' "$@" | grep -Eq "^${package_name}==" +} + +stress_package_lines=() +for _ in {1..128}; do + stress_package_lines+=("${package_lines[@]}") +done +if ! package_line_is_present coverage "${stress_package_lines[@]}"; then + printf 'Foundation CI required-package lookup must remain reliable under pipefail after an early match.\n' >&2 + exit 1 +fi + for package_name in coverage iniconfig packaging pluggy Pygments pytest pytest-cov; do - if ! printf '%s\n' "${package_lines[@]}" | grep -Eq "^${package_name}=="; then + if ! package_line_is_present "${package_name}" "${package_lines[@]}"; then printf 'Foundation CI requirement is missing: %s\n' "${package_name}" >&2 exit 1 fi From 540b5855bf26c15dbae40e8254eda8f0b02dfa21 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 16:33:15 -0700 Subject: [PATCH 200/216] fix(foundation): make package lookup pipefail-safe --- tests/test_foundation_ci_dependency_hygiene.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_foundation_ci_dependency_hygiene.sh b/tests/test_foundation_ci_dependency_hygiene.sh index c656cf2d4..5c68789ec 100644 --- a/tests/test_foundation_ci_dependency_hygiene.sh +++ b/tests/test_foundation_ci_dependency_hygiene.sh @@ -106,7 +106,13 @@ done package_line_is_present() { local package_name="$1" shift - printf '%s\n' "$@" | grep -Eq "^${package_name}==" + local package_line + for package_line in "$@"; do + if [[ "${package_line}" == "${package_name}=="* ]]; then + return 0 + fi + done + return 1 } stress_package_lines=() From 242b088f152a78699442deabb6314d7278716690 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:00:51 -0700 Subject: [PATCH 201/216] test(interview-plan): reject constructor-bypassing issuance clone --- .../tests/test_plan_issuance_integrity.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py index 5869c6b21..d5c191d1e 100644 --- a/packages/interview-plan/tests/test_plan_issuance_integrity.py +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -1,6 +1,7 @@ """Regression tests for post-construction structured-interview plan integrity.""" from copy import copy +from dataclasses import fields import pytest @@ -48,3 +49,16 @@ def test_copied_plan_has_no_transferable_process_local_issuance_evidence(): copied_plan.canonical_json() with pytest.raises(ValueError, match="issuance evidence is unavailable"): copied_plan.sha256_digest() + + +def test_object_new_clone_cannot_acquire_plan_issuance_evidence(): + """A constructor-bypassing clone must not mint fresh creation evidence.""" + issued_plan = plan() + forged_plan = object.__new__(type(issued_plan)) + for field in fields(issued_plan): + object.__setattr__(forged_plan, field.name, getattr(issued_plan, field.name)) + + with pytest.raises(ValueError, match="constructor provenance is unavailable"): + forged_plan.__post_init__() + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + forged_plan.canonical_json() From 768b628eebdba59feb6ec5f5df5d886de3814862 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:04:49 -0700 Subject: [PATCH 202/216] fix(interview-plan): bind issuance to constructor provenance --- .../src/orgmetra_interview_plan/plan.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index 9a2777ef6..c221fd360 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -15,7 +15,7 @@ import secrets from threading import RLock from uuid import UUID -from weakref import finalize +from weakref import WeakValueDictionary, finalize _CODE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _DIGEST_PATTERN = re.compile(r"^[0-9a-f]{64}$") @@ -33,6 +33,8 @@ _MAX_EVIDENCE_VERSION = 2_147_483_647 _PROCESS_PLAN_SEAL_KEY = secrets.token_bytes(32) _PLAN_SEALS: dict[int, str] = {} +_CONSTRUCTING_PLAN_IDENTITIES: WeakValueDictionary[int, object] = WeakValueDictionary() +_ISSUED_PLAN_IDENTITIES: WeakValueDictionary[int, object] = WeakValueDictionary() _PLAN_SEALS_LOCK = RLock() @@ -155,8 +157,20 @@ class StructuredInterviewPlan: review_state: str = _REVIEW_STATE next_action: str = _NEXT_ACTION + def __new__(cls, *_args: object, **_kwargs: object) -> StructuredInterviewPlan: + """Mark only instances entering through the governed constructor as eligible.""" + instance = object.__new__(cls) + with _PLAN_SEALS_LOCK: + _CONSTRUCTING_PLAN_IDENTITIES[id(instance)] = instance + return instance + def __post_init__(self) -> None: """Fail closed when direct construction drifts from the governed contract.""" + with _PLAN_SEALS_LOCK: + if _ISSUED_PLAN_IDENTITIES.get(id(self)) is self: + raise ValueError("structured interview plan issuance evidence already exists") + if _CONSTRUCTING_PLAN_IDENTITIES.get(id(self)) is not self: + raise ValueError("structured interview plan constructor provenance is unavailable") _validate_operational_uuid(self.tenant_record_id, "tenant_record_id") _validate_reference(self.interview_plan_reference, "interview_plan", "interview_plan_reference") _validate_reference(self.requisition_reference, "requisition", "requisition_reference") @@ -206,6 +220,9 @@ def __post_init__(self) -> None: if type(self.next_action) is not str or self.next_action != _NEXT_ACTION: raise ValueError("next_action must remain the governed interview-plan instruction") _register_plan_seal(self, _seal_plan(self._canonical_json_unchecked())) + with _PLAN_SEALS_LOCK: + _ISSUED_PLAN_IDENTITIES[id(self)] = self + _CONSTRUCTING_PLAN_IDENTITIES.pop(id(self), None) def __repr__(self) -> str: """Return a fully redacted representation safe for routine logs and assertions.""" @@ -241,6 +258,9 @@ def _canonical_json_unchecked(self) -> str: def canonical_json(self) -> str: """Return creation-bound canonical JSON for immutable audit correlation.""" + with _PLAN_SEALS_LOCK: + if _ISSUED_PLAN_IDENTITIES.get(id(self)) is not self: + raise ValueError("structured interview plan issuance evidence is unavailable") canonical = self._canonical_json_unchecked() authoritative_seal = _authoritative_plan_seal(self) if type(authoritative_seal) is not str: From c58fea31be828378ccd6bb2e3f9ab73550d53e0f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:05:33 -0700 Subject: [PATCH 203/216] docs(interview-plan): record constructor provenance boundary --- docs/adr/0015-governed-structured-interview-plan.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 54c34eb5b..f9252b592 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -13,7 +13,7 @@ Opaque identities and artifact digests identify evidence but do not prove tenant Plan-generation time and approval time are trust-bearing evidence. Caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may raise arbitrary exceptions while `utcoffset()` is evaluated; such failures and unrepresentable UTC normalization must become field-specific governed validation before plan issuance, authority side effects, verification acceptance, or canonical export. -Python `frozen=True` is not an adversarial immutability or authorization boundary. `object.__setattr__` can rewrite dataclass fields, and a module-private constructor token is still reachable by Python callers. Therefore receipt shape construction must not itself confer human-approval authority. Plan construction may register process-local integrity evidence because construction is the governed plan-issuance boundary, but activation-receipt issuance evidence must be registered only by the verified activation factory after authoritative host checks and exact-scope matching have completed. +Python `frozen=True` is not an adversarial immutability or authorization boundary. `object.__setattr__` can rewrite dataclass fields, and `object.__new__` can allocate a dataclass-shaped instance without entering its governed constructor. A module-private constructor token is still reachable by Python callers. Therefore receipt shape construction must not itself confer human-approval authority, and plan issuance must prove that the exact live object entered through the governed constructor before `__post_init__` may register creation evidence. Plan construction may register process-local integrity evidence because construction is the governed plan-issuance boundary, but activation-receipt issuance evidence must be registered only by the verified activation factory after authoritative host checks and exact-scope matching have completed. ## Decision @@ -30,7 +30,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. Treat offset evaluation as an untrusted-code boundary: exceptions and offset arithmetic beyond Python's representable `datetime` range become the same field-specific `ValueError`. Store the built-in UTC snapshot rather than caller-owned `tzinfo` state. Canonical timestamp rendering reuses this fail-closed detachment. -At successful plan construction, compute a process-local HMAC over the exact canonical plan payload and register it outside plan-writable slots, keyed to the live plan identity and removed when collected. Registration is single-use for one live plan identity. `canonical_json()` requires creation-bound issuance evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. This HMAC is same-process integrity evidence only, not a persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. +The class constructor records process-local eligibility for the exact newly allocated live plan identity before dataclass initialization. Successful `__post_init__()` requires and consumes that constructor provenance, computes a process-local HMAC over the exact canonical plan payload, registers the seal outside plan-writable slots, and records the exact identity as issued. Registration remains single-use for one live plan identity. `canonical_json()` requires exact issued-identity membership plus creation-bound HMAC evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. An `object.__new__` clone that copies otherwise valid fields cannot call `__post_init__()` to mint fresh issuance evidence because it never acquired governed-constructor provenance. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. These identity/HMAC controls are same-process integrity evidence only, not a persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. Make authoritative activation executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, obtains creation-bound canonical plan JSON, derives tenant/interview-plan scope and SHA-256 from those bytes, detaches caller-owned `approved_at` into built-in UTC, validates the approving actor, and rejects chronology before plan generation. The authority receives only detached canonical plan JSON, its exact digest, approving actor, and normalized approval instant—never the live plan object. A retained alias therefore cannot change what the authority reviews through a temporary change-and-restore cycle; non-restored mutation is still rejected by the post-authority plan integrity check. @@ -48,6 +48,7 @@ The issued receipt records the exact plan digest, accountable UUIDv4 approving a - Buyers can prove which Job Analysis, competencies, questions, mapping, rating anchors, panel, and evidence revision were reviewed before candidate use. - Caller-controlled timezone failures and mutable timezone state cannot silently redefine governed plan or approval instants. +- Constructor-bypassing `object.__new__` clones cannot mint creation-bound plan issuance evidence merely by copying valid fields and invoking `__post_init__()`. - The authoritative adapter reviews detached creation-bound plan evidence rather than a caller-owned live plan object. - Authority verification fields are tuple-immutable at runtime and exact-type checked before one-time unpacking. - A caller cannot mint an `approved_for_use` evidence artifact by importing a private constructor sentinel: direct and replaced receipt values remain unissued and cannot export canonical evidence. @@ -60,7 +61,7 @@ The issued receipt records the exact plan digest, accountable UUIDv4 approving a - The package does not persist requisitions, Job Analysis, interview questions/mappings, responses, scores, or authoritative relationship-resolution results. - The authority protocol is not proof that a concrete production adapter performs tenant/database/API checks correctly; production adapters still need executable integration evidence and immutable authority/audit records. -- Plan and activation-receipt HMAC seals exist only for the lifetime of their in-process objects. They are not portable signatures, durable verification credentials, or key-management facilities. +- Plan and activation-receipt HMAC seals and live-identity provenance exist only for the lifetime of their in-process objects. They are not portable signatures, durable verification credentials, or key-management facilities. - Directly constructed receipt values are intentionally unusable as authoritative evidence until a supported future rehydration/issuance contract exists. - Human approval remains mandatory; model output cannot activate or approve the plan. - UUID/digest metadata and reference inequality do not establish tenant ownership, identity separation, scientific validity, fairness, or legal compliance. From eea011a9e388dbc64d6984def06bae40347c645e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:05:52 -0700 Subject: [PATCH 204/216] docs(interview-plan): trace issuance provenance repair --- packages/interview-plan/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 8334fd927..016605458 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -25,6 +25,7 @@ - Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. - Make `StructuredInterviewActivationVerification` a runtime-immutable `NamedTuple`, reject subclasses, and unpack its exact tuple once before validation so `object.__setattr__` cannot create mixed authority-evidence revisions between field reads. - Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. +- Bind plan issuance to exact live governed-constructor provenance before `__post_init__()` may register creation evidence, so an `object.__new__` clone cannot copy valid plan fields and mint a fresh issued identity by manually invoking initialization. - Remove constructor-token authorization from `StructuredInterviewActivationReceipt`: direct construction and `dataclasses.replace(...)` create unissued value objects that cannot export canonical evidence, while only `activate_structured_interview_plan(...)` registers the process-local receipt seal after authoritative verification and exact-scope matching succeed. A module-private sentinel no longer appears in the receipt constructor and cannot mint approval evidence. - Expand Structured Interview Plan Quality path triggers to cover repository-level Python/test configuration and `.gitignore` inputs that can change test collection, execution, or clean-checkout behavior, while retaining package, dependency-lock, workflow, ADR, doctoring, and traceability triggers. @@ -37,5 +38,5 @@ - Normalize both plan-generation and approval-time evidence before creation sealing or authority review so caller-controlled mutable `tzinfo` state cannot make one governed instant later represent a different UTC instant. - Prevent callers from converting a module-visible private sentinel into human-approval authority: receipt issuance evidence is now registered exclusively inside the verified activation factory after all host-verification and exact-scope checks pass. - Redact `StructuredInterviewPlan`, `StructuredInterviewActivationVerification`, and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. -- Treat the process-local plan and activation-receipt seals strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. +- Treat the process-local plan and activation-receipt seals plus live-identity provenance strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. - State explicitly that UUID/digest correlation, reference-string inequality, runtime issuance seals, and the authority protocol do not by themselves prove tenant ownership, authoritative relationship validity, actor identity separation, scientific validity, fairness, or legal compliance. From fe8a5884b7574e2b9f6700bd1b6837689ba16224 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:07:01 -0700 Subject: [PATCH 205/216] test(interview-plan): cover duplicate seal fail-closure --- .../tests/test_plan_issuance_integrity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py index d5c191d1e..3151981e9 100644 --- a/packages/interview-plan/tests/test_plan_issuance_integrity.py +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -62,3 +62,14 @@ def test_object_new_clone_cannot_acquire_plan_issuance_evidence(): forged_plan.__post_init__() with pytest.raises(ValueError, match="issuance evidence is unavailable"): forged_plan.canonical_json() + + +def test_existing_plan_seal_cannot_be_replaced_by_secondary_registration(): + """A second seal registration must not overwrite an already issued plan.""" + issued_plan = plan() + original_json = issued_plan.canonical_json() + + with pytest.raises(ValueError, match="issuance evidence already exists"): + plan_module._register_plan_seal(issued_plan, "0" * 64) + + assert issued_plan.canonical_json() == original_json From c971494144ef319dff1839c289d4347856359537 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:12:16 -0700 Subject: [PATCH 206/216] test(interview-plan): reject direct class allocator issuance --- .../tests/test_plan_issuance_integrity.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py index 3151981e9..2b6a2d4ed 100644 --- a/packages/interview-plan/tests/test_plan_issuance_integrity.py +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -52,7 +52,7 @@ def test_copied_plan_has_no_transferable_process_local_issuance_evidence(): def test_object_new_clone_cannot_acquire_plan_issuance_evidence(): - """A constructor-bypassing clone must not mint fresh creation evidence.""" + """An object.__new__ clone must not mint fresh creation evidence.""" issued_plan = plan() forged_plan = object.__new__(type(issued_plan)) for field in fields(issued_plan): @@ -64,6 +64,19 @@ def test_object_new_clone_cannot_acquire_plan_issuance_evidence(): forged_plan.canonical_json() +def test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence(): + """Calling the class allocator directly must not grant constructor provenance.""" + issued_plan = plan() + forged_plan = type(issued_plan).__new__(type(issued_plan)) + for field in fields(issued_plan): + object.__setattr__(forged_plan, field.name, getattr(issued_plan, field.name)) + + with pytest.raises(ValueError, match="constructor provenance is unavailable"): + forged_plan.__post_init__() + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + forged_plan.canonical_json() + + def test_existing_plan_seal_cannot_be_replaced_by_secondary_registration(): """A second seal registration must not overwrite an already issued plan.""" issued_plan = plan() From 82b73d073eb1bb90277b926b722d422307cf240a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:14:56 -0700 Subject: [PATCH 207/216] fix(interview-plan): gate issuance on full class construction --- .../src/orgmetra_interview_plan/plan.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index c221fd360..e633da501 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -6,6 +6,7 @@ """ from __future__ import annotations +from contextvars import ContextVar from dataclasses import dataclass from datetime import datetime, timedelta, timezone from hashlib import sha256 @@ -35,6 +36,7 @@ _PLAN_SEALS: dict[int, str] = {} _CONSTRUCTING_PLAN_IDENTITIES: WeakValueDictionary[int, object] = WeakValueDictionary() _ISSUED_PLAN_IDENTITIES: WeakValueDictionary[int, object] = WeakValueDictionary() +_ACTIVE_PLAN_CONSTRUCTOR = ContextVar("_ACTIVE_PLAN_CONSTRUCTOR", default=None) _PLAN_SEALS_LOCK = RLock() @@ -130,8 +132,20 @@ def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> s return _snapshot_utc_datetime(value, field_name).isoformat().replace("+00:00", "Z") +class _StructuredInterviewPlanMeta(type): + """Gate plan provenance on the normal full class-construction path.""" + + def __call__(cls, *args: object, **kwargs: object) -> object: + """Arm provenance only while Python runs this class's full constructor.""" + token = _ACTIVE_PLAN_CONSTRUCTOR.set(cls) + try: + return super().__call__(*args, **kwargs) + finally: + _ACTIVE_PLAN_CONSTRUCTOR.reset(token) + + @dataclass(frozen=True, slots=True, repr=False, weakref_slot=True) -class StructuredInterviewPlan: +class StructuredInterviewPlan(metaclass=_StructuredInterviewPlanMeta): """Immutable candidate-neutral interview-plan evidence awaiting human approval.""" tenant_record_id: str @@ -158,10 +172,11 @@ class StructuredInterviewPlan: next_action: str = _NEXT_ACTION def __new__(cls, *_args: object, **_kwargs: object) -> StructuredInterviewPlan: - """Mark only instances entering through the governed constructor as eligible.""" + """Register eligibility only during the governed full constructor call.""" instance = object.__new__(cls) - with _PLAN_SEALS_LOCK: - _CONSTRUCTING_PLAN_IDENTITIES[id(instance)] = instance + if _ACTIVE_PLAN_CONSTRUCTOR.get() is cls: + with _PLAN_SEALS_LOCK: + _CONSTRUCTING_PLAN_IDENTITIES[id(instance)] = instance return instance def __post_init__(self) -> None: From a45107206e966f2c0757c8f8f1bc1f9a28900b8e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:15:55 -0700 Subject: [PATCH 208/216] docs(interview-plan): trace allocator provenance repair --- packages/interview-plan/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 016605458..04d8d5098 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -25,7 +25,7 @@ - Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. - Make `StructuredInterviewActivationVerification` a runtime-immutable `NamedTuple`, reject subclasses, and unpack its exact tuple once before validation so `object.__setattr__` cannot create mixed authority-evidence revisions between field reads. - Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. -- Bind plan issuance to exact live governed-constructor provenance before `__post_init__()` may register creation evidence, so an `object.__new__` clone cannot copy valid plan fields and mint a fresh issued identity by manually invoking initialization. +- Arm plan-construction provenance only during the metaclass-mediated full `StructuredInterviewPlan(...)` call and consume it in `__post_init__()`, so neither `object.__new__` nor direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation can copy otherwise valid fields and mint activation-ready issuance evidence by manually invoking initialization. - Remove constructor-token authorization from `StructuredInterviewActivationReceipt`: direct construction and `dataclasses.replace(...)` create unissued value objects that cannot export canonical evidence, while only `activate_structured_interview_plan(...)` registers the process-local receipt seal after authoritative verification and exact-scope matching succeed. A module-private sentinel no longer appears in the receipt constructor and cannot mint approval evidence. - Expand Structured Interview Plan Quality path triggers to cover repository-level Python/test configuration and `.gitignore` inputs that can change test collection, execution, or clean-checkout behavior, while retaining package, dependency-lock, workflow, ADR, doctoring, and traceability triggers. From e367580c1b98b8e30b2b0832f23cda1a1750ed9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:16:24 -0700 Subject: [PATCH 209/216] docs(interview-plan): define full-constructor provenance --- docs/adr/0015-governed-structured-interview-plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index f9252b592..50fe429cb 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -13,7 +13,7 @@ Opaque identities and artifact digests identify evidence but do not prove tenant Plan-generation time and approval time are trust-bearing evidence. Caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may raise arbitrary exceptions while `utcoffset()` is evaluated; such failures and unrepresentable UTC normalization must become field-specific governed validation before plan issuance, authority side effects, verification acceptance, or canonical export. -Python `frozen=True` is not an adversarial immutability or authorization boundary. `object.__setattr__` can rewrite dataclass fields, and `object.__new__` can allocate a dataclass-shaped instance without entering its governed constructor. A module-private constructor token is still reachable by Python callers. Therefore receipt shape construction must not itself confer human-approval authority, and plan issuance must prove that the exact live object entered through the governed constructor before `__post_init__` may register creation evidence. Plan construction may register process-local integrity evidence because construction is the governed plan-issuance boundary, but activation-receipt issuance evidence must be registered only by the verified activation factory after authoritative host checks and exact-scope matching have completed. +Python `frozen=True` is not an adversarial immutability or authorization boundary. `object.__setattr__` can rewrite dataclass fields, and low-level allocation can create a dataclass-shaped instance without completing its governed constructor. Merely executing a class `__new__` method is also not proof of construction because callers can invoke that allocator directly. A module-private constructor token is still reachable by Python callers. Therefore receipt shape construction must not itself confer human-approval authority, and plan issuance must prove that the exact live object entered through the normal full `StructuredInterviewPlan(...)` construction path before `__post_init__` may register creation evidence. Plan construction may register process-local integrity evidence because construction is the governed plan-issuance boundary, but activation-receipt issuance evidence must be registered only by the verified activation factory after authoritative host checks and exact-scope matching have completed. ## Decision @@ -30,7 +30,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. Treat offset evaluation as an untrusted-code boundary: exceptions and offset arithmetic beyond Python's representable `datetime` range become the same field-specific `ValueError`. Store the built-in UTC snapshot rather than caller-owned `tzinfo` state. Canonical timestamp rendering reuses this fail-closed detachment. -The class constructor records process-local eligibility for the exact newly allocated live plan identity before dataclass initialization. Successful `__post_init__()` requires and consumes that constructor provenance, computes a process-local HMAC over the exact canonical plan payload, registers the seal outside plan-writable slots, and records the exact identity as issued. Registration remains single-use for one live plan identity. `canonical_json()` requires exact issued-identity membership plus creation-bound HMAC evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. An `object.__new__` clone that copies otherwise valid fields cannot call `__post_init__()` to mint fresh issuance evidence because it never acquired governed-constructor provenance. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. These identity/HMAC controls are same-process integrity evidence only, not a persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. +A private metaclass marks constructor provenance only for the duration of the normal full `StructuredInterviewPlan(...)` class call by using a context-local token. `StructuredInterviewPlan.__new__()` allocates the instance in all cases but records process-local construction eligibility only while that full class-call context is active. Successful `__post_init__()` requires and consumes that exact live-object provenance, computes a process-local HMAC over the canonical plan payload, registers the seal outside plan-writable slots, and records the exact identity as issued. Registration remains single-use for one live plan identity. `canonical_json()` requires exact issued-identity membership plus creation-bound HMAC evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. An `object.__new__` clone or direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation that copies otherwise valid fields cannot call `__post_init__()` to mint fresh issuance evidence because neither acquired full-constructor provenance. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. These context/identity/HMAC controls are same-process integrity evidence only, not a hostile-interpreter capability boundary, persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. Make authoritative activation executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, obtains creation-bound canonical plan JSON, derives tenant/interview-plan scope and SHA-256 from those bytes, detaches caller-owned `approved_at` into built-in UTC, validates the approving actor, and rejects chronology before plan generation. The authority receives only detached canonical plan JSON, its exact digest, approving actor, and normalized approval instant—never the live plan object. A retained alias therefore cannot change what the authority reviews through a temporary change-and-restore cycle; non-restored mutation is still rejected by the post-authority plan integrity check. @@ -48,7 +48,7 @@ The issued receipt records the exact plan digest, accountable UUIDv4 approving a - Buyers can prove which Job Analysis, competencies, questions, mapping, rating anchors, panel, and evidence revision were reviewed before candidate use. - Caller-controlled timezone failures and mutable timezone state cannot silently redefine governed plan or approval instants. -- Constructor-bypassing `object.__new__` clones cannot mint creation-bound plan issuance evidence merely by copying valid fields and invoking `__post_init__()`. +- Constructor-bypassing `object.__new__` clones and direct class-allocator calls cannot mint creation-bound plan issuance evidence merely by copying valid fields and invoking `__post_init__()`. - The authoritative adapter reviews detached creation-bound plan evidence rather than a caller-owned live plan object. - Authority verification fields are tuple-immutable at runtime and exact-type checked before one-time unpacking. - A caller cannot mint an `approved_for_use` evidence artifact by importing a private constructor sentinel: direct and replaced receipt values remain unissued and cannot export canonical evidence. From 032de1201420bdc528c620a8e70e15db8c2de7d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:17:36 -0700 Subject: [PATCH 210/216] docs(interview-plan): trace full-constructor issuance proof --- docs/traceability/structured-interview-plan.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index cef251742..20866c737 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -8,6 +8,8 @@ Caller-owned `tzinfo` implementations are treated as untrusted executable code. Plan generation, activation approval-time normalization, verification-time normalization, and canonical timestamp rendering convert exceptions raised by `tzinfo.utcoffset()` into field-specific governed `ValueError` and stop before authority side effects or evidence export. `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_activation_normalizes_hostile_timezone_failure_before_authority`, and `test_activation_receipt_normalizes_hostile_timezone_failure` bind this behavior; mutable-offset and representable-range regressions continue to prove UTC detachment and arithmetic fail-closure. +Plan issuance distinguishes a normal full `StructuredInterviewPlan(...)` class call from direct allocator invocation. A context-local construction token is armed by the private metaclass only while Python executes the full class constructor; `__new__()` registers one live identity as construction-eligible only inside that context, and `__post_init__()` must consume that provenance before it can register issuance evidence. `test_object_new_clone_cannot_acquire_plan_issuance_evidence` and `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence` prove that neither `object.__new__` nor direct class-allocator invocation can copy valid fields and mint a new issued plan. + Receipt construction is no longer an authorization mechanism. A directly constructed or `dataclasses.replace(...)`-created `StructuredInterviewActivationReceipt` may validate as a value shape, but it remains unissued and cannot export canonical evidence. Only `activate_structured_interview_plan(...)`, after authoritative verification and exact tenant/plan/digest/actor/time scope matching, registers the process-local receipt issuance seal. `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, and the replacement regression bind this distinction. ## Buyer requirement → executable evidence @@ -17,12 +19,12 @@ Receipt construction is no longer an authorization mechanism. A directly constru | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; plan construction binds a process-local creation seal, while activation receipt issuance is registered only by the verified factory after exact-scope authority checks | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, replacement/post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | +| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; full-constructor provenance plus plan construction bind a process-local creation seal, while activation receipt issuance is registered only by the verified factory after exact-scope authority checks | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, replacement/post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; canonical export is available only after verified-factory issuance; `StructuredInterviewActivationVerification` must explicitly return the same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | -| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work` plus plan issuance-integrity regressions | -| Constructed plan evidence cannot be silently rewritten or resealed | each successful `StructuredInterviewPlan` construction first detaches `generated_at` to a built-in UTC instant, then registers a process-local HMAC seal outside plan-writable slots exactly once for the live identity; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, and `test_existing_plan_identity_cannot_renew_issuance_seal_after_mutation` | +| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, allocator-bypassed, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work`, `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, plus remaining plan issuance-integrity regressions | +| Constructed plan evidence cannot be silently rewritten or resealed | only the normal full class-construction path can arm construction provenance; each successful `StructuredInterviewPlan` construction detaches `generated_at` to a built-in UTC instant, consumes the exact live-object provenance, then registers a process-local HMAC seal outside plan-writable slots exactly once; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied/allocator-bypassed identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, and duplicate-registration/reinitialization regressions | | Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | | Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed as field-specific validation, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | | Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed before authority side effects; the same snapshot crosses the authority and receipt boundaries, and canonical rendering reuses the same fail-closed detachment | `test_activation_normalizes_hostile_timezone_failure_before_authority`, `test_activation_receipt_normalizes_hostile_timezone_failure`, `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | @@ -34,11 +36,11 @@ Receipt construction is no longer an authorization mechanism. A directly constru | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; receipt canonical export requires issuance registration performed only after successful exact-scope host verification | scalar fail-closed plan regressions, `test_authority_rejection_blocks_activation`, non-verification-result regression, direct-unissued-receipt regression, and private-sentinel regression | | Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; hostile offset evaluation and unrepresentable UTC normalization are rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and factory-issued activation receipt evidence | plan hostile-timezone/mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation hostile-timezone/UTC-snapshot/range-boundary regressions, and canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and receipt shapes re-run value validation; direct/replaced receipt objects remain unissued and cannot export canonical evidence; exact runtime types protect plan collections/governance text; issued plans and receipts verify process-local issuance evidence before canonical export | direct constructor, private-sentinel, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, and receipt hostile-timezone/low-level rewrite/missing-seal regressions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and receipt shapes re-run value validation; direct/replaced receipt objects remain unissued and cannot export canonical evidence; exact runtime types protect plan collections/governance text; direct plan allocator calls cannot acquire full-constructor provenance; issued plans and receipts verify process-local issuance evidence before canonical export | direct constructor/allocator, private-sentinel, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, and receipt hostile-timezone/low-level rewrite/missing-seal regressions | ## Evidence boundary -The plan object is creation-bound before activation begins. Successful `StructuredInterviewPlan` construction first validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Offset evaluation itself is a trust boundary: arbitrary exceptions raised by caller-owned `tzinfo.utcoffset()` are converted into the field-specific governed `ValueError` and stop construction before issuance. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the same field-specific governed `ValueError` and stops before issuance-seal registration. Construction then computes an HMAC over the exact canonical payload and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires an issuance record for that exact live object identity, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is intentionally same-process runtime integrity evidence only—not a durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. +The plan object is creation-bound before activation begins. A private metaclass arms a context-local token only around the normal full `StructuredInterviewPlan(...)` class call; `__new__()` allocates in every case but records construction eligibility only while that token identifies the same class. This means direct `object.__new__` and direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation cannot manufacture constructor provenance by copying valid fields and manually invoking `__post_init__()`. Successful full construction then validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Offset evaluation itself is a trust boundary: arbitrary exceptions raised by caller-owned `tzinfo.utcoffset()` are converted into the field-specific governed `ValueError` and stop construction before issuance. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the same field-specific governed `ValueError` and stops before issuance-seal registration. Construction consumes the exact live-object provenance, computes an HMAC over the exact canonical payload, and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires issued identity for that exact live object, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, duplicate, or allocator-bypassed issuance evidence fails closed. The context token, live-identity registry, and HMAC are intentionally same-process runtime integrity evidence only—not a hostile-interpreter capability boundary, durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; hostile/broken offset evaluation, naive or unknown-offset values, and UTC normalization beyond the representable datetime range all fail as field-specific validation before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. From ab035c45eeba7da9c0232dea340837a4c6a8393c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:29:29 -0700 Subject: [PATCH 211/216] test(interview-plan): reproduce timezone provenance reentrancy --- .../tests/test_plan_issuance_integrity.py | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/tests/test_plan_issuance_integrity.py b/packages/interview-plan/tests/test_plan_issuance_integrity.py index 2b6a2d4ed..4f12a8386 100644 --- a/packages/interview-plan/tests/test_plan_issuance_integrity.py +++ b/packages/interview-plan/tests/test_plan_issuance_integrity.py @@ -1,7 +1,8 @@ """Regression tests for post-construction structured-interview plan integrity.""" from copy import copy -from dataclasses import fields +from dataclasses import fields, replace +from datetime import datetime, timedelta, tzinfo import pytest @@ -9,6 +10,30 @@ from test_activation import plan +class ReentrantPlanAllocatorTimezone(tzinfo): + """Retain a plan allocated reentrantly from a caller-owned timezone callback.""" + + def __init__(self) -> None: + """Start without a retained forged plan.""" + self.forged_plan: object | None = None + + def utcoffset(self, _dt: datetime | None) -> timedelta: + """Allocate once while the legitimate constructor normalizes generated_at.""" + if self.forged_plan is None: + self.forged_plan = plan_module.StructuredInterviewPlan.__new__( + plan_module.StructuredInterviewPlan + ) + return timedelta(hours=9) + + def dst(self, _dt: datetime | None) -> timedelta: + """Use a stable zero daylight-saving offset.""" + return timedelta(0) + + def tzname(self, _dt: datetime | None) -> str: + """Return a descriptive test-only timezone name.""" + return "REENTRANT" + + def test_plan_canonical_evidence_fails_closed_after_low_level_mutation(): """A built plan must not export different canonical evidence after issuance.""" candidate_plan = plan() @@ -77,6 +102,25 @@ def test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence(): forged_plan.canonical_json() +def test_timezone_callback_cannot_mint_plan_constructor_provenance(): + """Caller timezone code must not retain constructor privilege for another plan.""" + callback_timezone = ReentrantPlanAllocatorTimezone() + issued_plan = replace( + plan(), + generated_at=datetime(2026, 8, 30, 12, 0, tzinfo=callback_timezone), + ) + forged_plan = callback_timezone.forged_plan + assert forged_plan is not None + + for field in fields(issued_plan): + object.__setattr__(forged_plan, field.name, getattr(issued_plan, field.name)) + + with pytest.raises(ValueError, match="constructor provenance is unavailable"): + forged_plan.__post_init__() + with pytest.raises(ValueError, match="issuance evidence is unavailable"): + forged_plan.canonical_json() + + def test_existing_plan_seal_cannot_be_replaced_by_secondary_registration(): """A second seal registration must not overwrite an already issued plan.""" issued_plan = plan() From f7ca68bb77ccd08d318afec2729c8a573cc0d8be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:31:19 -0700 Subject: [PATCH 212/216] fix(interview-plan): consume constructor provenance before callbacks --- .../interview-plan/src/orgmetra_interview_plan/plan.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/interview-plan/src/orgmetra_interview_plan/plan.py b/packages/interview-plan/src/orgmetra_interview_plan/plan.py index e633da501..aed823a70 100644 --- a/packages/interview-plan/src/orgmetra_interview_plan/plan.py +++ b/packages/interview-plan/src/orgmetra_interview_plan/plan.py @@ -133,10 +133,10 @@ def _canonical_timestamp(value: datetime, field_name: str = "generated_at") -> s class _StructuredInterviewPlanMeta(type): - """Gate plan provenance on the normal full class-construction path.""" + """Gate plan provenance on one allocator ticket per normal class construction.""" def __call__(cls, *args: object, **kwargs: object) -> object: - """Arm provenance only while Python runs this class's full constructor.""" + """Arm one allocator ticket before Python enters this class's constructor.""" token = _ACTIVE_PLAN_CONSTRUCTOR.set(cls) try: return super().__call__(*args, **kwargs) @@ -172,9 +172,10 @@ class StructuredInterviewPlan(metaclass=_StructuredInterviewPlanMeta): next_action: str = _NEXT_ACTION def __new__(cls, *_args: object, **_kwargs: object) -> StructuredInterviewPlan: - """Register eligibility only during the governed full constructor call.""" + """Consume constructor eligibility before caller-controlled validation can run.""" instance = object.__new__(cls) if _ACTIVE_PLAN_CONSTRUCTOR.get() is cls: + _ACTIVE_PLAN_CONSTRUCTOR.set(None) with _PLAN_SEALS_LOCK: _CONSTRUCTING_PLAN_IDENTITIES[id(instance)] = instance return instance From 78e754226d94185770efe036ec3b03e2e5746570 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:32:27 -0700 Subject: [PATCH 213/216] docs(interview-plan): document one-shot constructor provenance --- packages/interview-plan/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interview-plan/README.md b/packages/interview-plan/README.md index 20c671595..72ee8139f 100644 --- a/packages/interview-plan/README.md +++ b/packages/interview-plan/README.md @@ -10,7 +10,7 @@ The public `tenant_record_id` follows Orgmetra's authoritative canonical non-sen Opaque identities and digests identify the evidence being reviewed; they do not prove tenant ownership, authoritative relationships, or actor identity. Immediately before activation, the host must re-resolve every plan reference within `tenant_record_id`, prove the requisition-to-Job-to-job-analysis binding, verify question-set, question-to-competency mapping, and rating-anchor provenance, re-resolve every panel actor, prove the resolved panel identities are distinct, and verify panel eligibility and training. -A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. Caller-provided timezone implementations are untrusted code: if `tzinfo.utcoffset()` raises, Orgmetra converts that failure into the same field-specific governed `ValueError` instead of leaking the caller exception. If the offset would place the instant outside Python's representable `datetime` range, construction likewise fails with field-specific `ValueError` rather than leaking `OverflowError`. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, or duplicate issuance evidence fails closed. This seal is only same-process runtime-integrity evidence: it is not a durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. +A successfully constructed `StructuredInterviewPlan` is creation-bound before activation. Before its canonical payload is sealed, caller-owned `generated_at` is detached using one concrete UTC offset into a built-in `datetime` with `timezone.utc`; later changes to a custom mutable `tzinfo` therefore cannot change or invalidate the already-issued plan instant. Caller-provided timezone implementations are untrusted code: if `tzinfo.utcoffset()` raises, Orgmetra converts that failure into the same field-specific governed `ValueError` instead of leaking the caller exception. If the offset would place the instant outside Python's representable `datetime` range, construction likewise fails with field-specific `ValueError` rather than leaking `OverflowError`. Constructor provenance is a one-shot context-local allocator ticket: the normal class call arms it, and the exact `StructuredInterviewPlan.__new__()` invocation consumes it **before** field validation or caller-owned timezone code can execute. Reentrant code reached through `tzinfo.utcoffset()` therefore cannot retain a second allocator-created object with ambient constructor privilege. The package then computes a process-local HMAC over its exact canonical payload and stores the seal outside plan-writable dataclass slots. One live plan identity can register that issuance evidence only once: rerunning `__post_init__()` cannot renew the seal after a low-level field rewrite. `canonical_json()` and `sha256_digest()` require matching creation evidence for the exact live object, so a low-level `object.__setattr__` rewrite cannot silently redefine the plan after construction and a copied/reconstructed object cannot inherit issuance authority merely by carrying the same fields. Missing, mismatched, duplicate, allocator-bypassed, or reentrantly allocated issuance evidence fails closed. This seal and constructor-provenance mechanism are only same-process runtime-integrity evidence: they are not a hostile-interpreter capability boundary, durable signature, rehydration credential, persisted audit record, or replacement for the host's immutable audit/outbox evidence. `activate_structured_interview_plan(...)` makes the authoritative control flow executable without duplicating authoritative storage. The boundary accepts only the exact governed `StructuredInterviewPlan` runtime type and requires its creation-bound canonical evidence, so a duck-typed, subclassed, copied, or rewritten plan-shaped object cannot bypass construction/issuance invariants. Before authority work, activation captures the exact creation-bound canonical plan JSON and SHA-256 digest, derives tenant/interview-plan scope from those bytes, and detaches caller-owned `approved_at` into one built-in UTC snapshot. Approval-time offset evaluation failures and normalization that would leave Python's representable `datetime` range both fail as field-specific validation before any authority call, so hostile/broken timezone code cannot leak arbitrary exceptions or trigger authoritative side effects. The injected `StructuredInterviewActivationAuthority` receives **only** that built-in canonical JSON string, its exact digest, the approving actor, and the normalized approval instant—never the caller's live `StructuredInterviewPlan` object. A retained plan alias can therefore be changed and restored while authority work runs without changing the immutable plan evidence the authority actually reviews; a non-restored mutation still fails the post-authority creation-seal check. From 22551ccb243fcf36b9b1c95cd887d8bc629dfed3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:32:55 -0700 Subject: [PATCH 214/216] docs(interview-plan): record reentrant constructor boundary --- docs/adr/0015-governed-structured-interview-plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0015-governed-structured-interview-plan.md b/docs/adr/0015-governed-structured-interview-plan.md index 50fe429cb..2014cc3dc 100644 --- a/docs/adr/0015-governed-structured-interview-plan.md +++ b/docs/adr/0015-governed-structured-interview-plan.md @@ -11,7 +11,7 @@ A structured interview is stronger when assessed competencies come from current Opaque identities and artifact digests identify evidence but do not prove tenant ownership, requisition-to-Job-to-job-analysis relationships, or distinct human identities. Those relationships must be re-resolved at authoritative owner boundaries immediately before activation. A prose-only `next_action` is insufficient: the package needs an executable host boundary that cannot issue activation evidence when authoritative checks reject or when returned verification evidence belongs to another plan, actor, or approval instant. -Plan-generation time and approval time are trust-bearing evidence. Caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may raise arbitrary exceptions while `utcoffset()` is evaluated; such failures and unrepresentable UTC normalization must become field-specific governed validation before plan issuance, authority side effects, verification acceptance, or canonical export. +Plan-generation time and approval time are trust-bearing evidence. Caller-controlled mutable timezone state must not make one governed instant later represent a different UTC instant. Caller-owned `tzinfo` implementations are executable code and may raise arbitrary exceptions while `utcoffset()` is evaluated; such failures and unrepresentable UTC normalization must become field-specific governed validation before plan issuance, authority side effects, verification acceptance, or canonical export. Constructor provenance must also not remain ambient while such caller code runs: otherwise a reentrant timezone callback can invoke the plan allocator, retain a second object that inherits constructor eligibility, populate it later with otherwise valid fields, and mint issuance evidence without a normal class construction. Python `frozen=True` is not an adversarial immutability or authorization boundary. `object.__setattr__` can rewrite dataclass fields, and low-level allocation can create a dataclass-shaped instance without completing its governed constructor. Merely executing a class `__new__` method is also not proof of construction because callers can invoke that allocator directly. A module-private constructor token is still reachable by Python callers. Therefore receipt shape construction must not itself confer human-approval authority, and plan issuance must prove that the exact live object entered through the normal full `StructuredInterviewPlan(...)` construction path before `__post_init__` may register creation evidence. Plan construction may register process-local integrity evidence because construction is the governed plan-issuance boundary, but activation-receipt issuance evidence must be registered only by the verified activation factory after authoritative host checks and exact-scope matching have completed. @@ -30,7 +30,7 @@ Add a transport-neutral `StructuredInterviewPlan` value object that binds: Before plan issuance evidence is registered, detach caller-owned `generated_at` into one built-in UTC `datetime` using one concrete offset read from the original aware value. Treat offset evaluation as an untrusted-code boundary: exceptions and offset arithmetic beyond Python's representable `datetime` range become the same field-specific `ValueError`. Store the built-in UTC snapshot rather than caller-owned `tzinfo` state. Canonical timestamp rendering reuses this fail-closed detachment. -A private metaclass marks constructor provenance only for the duration of the normal full `StructuredInterviewPlan(...)` class call by using a context-local token. `StructuredInterviewPlan.__new__()` allocates the instance in all cases but records process-local construction eligibility only while that full class-call context is active. Successful `__post_init__()` requires and consumes that exact live-object provenance, computes a process-local HMAC over the canonical plan payload, registers the seal outside plan-writable slots, and records the exact identity as issued. Registration remains single-use for one live plan identity. `canonical_json()` requires exact issued-identity membership plus creation-bound HMAC evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. An `object.__new__` clone or direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation that copies otherwise valid fields cannot call `__post_init__()` to mint fresh issuance evidence because neither acquired full-constructor provenance. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. These context/identity/HMAC controls are same-process integrity evidence only, not a hostile-interpreter capability boundary, persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. +A private metaclass arms a context-local **one-shot allocator ticket** immediately before the normal `StructuredInterviewPlan(...)` class construction. The exact `StructuredInterviewPlan.__new__()` invocation consumes that ticket before any field validation, `tzinfo.utcoffset()` call, or other caller-controlled callback can execute, then records construction eligibility only for that exact live object. Successful `__post_init__()` requires and consumes that provenance, computes a process-local HMAC over the canonical plan payload, registers the seal outside plan-writable slots, and records the exact identity as issued. Registration remains single-use for one live plan identity. `canonical_json()` requires exact issued-identity membership plus creation-bound HMAC evidence and uses constant-time comparison before returning bytes; `sha256_digest()` is downstream. An `object.__new__` clone, direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation, or allocator call reached reentrantly from caller-owned timezone code cannot call `__post_init__()` to mint fresh issuance evidence because none receives or retains the already-consumed constructor ticket. Low-level mutation, copied/reconstructed identities, missing issuance evidence, and attempted plan resealing fail closed. These context/identity/HMAC controls are same-process integrity evidence only, not a hostile-interpreter capability boundary, persisted signing scheme, portable signature, or replacement for immutable audit/outbox evidence. Make authoritative activation executable through `StructuredInterviewActivationAuthority` and `activate_structured_interview_plan(...)`. Before authority work, activation requires the exact governed `StructuredInterviewPlan` runtime type, obtains creation-bound canonical plan JSON, derives tenant/interview-plan scope and SHA-256 from those bytes, detaches caller-owned `approved_at` into built-in UTC, validates the approving actor, and rejects chronology before plan generation. The authority receives only detached canonical plan JSON, its exact digest, approving actor, and normalized approval instant—never the live plan object. A retained alias therefore cannot change what the authority reviews through a temporary change-and-restore cycle; non-restored mutation is still rejected by the post-authority plan integrity check. @@ -48,7 +48,7 @@ The issued receipt records the exact plan digest, accountable UUIDv4 approving a - Buyers can prove which Job Analysis, competencies, questions, mapping, rating anchors, panel, and evidence revision were reviewed before candidate use. - Caller-controlled timezone failures and mutable timezone state cannot silently redefine governed plan or approval instants. -- Constructor-bypassing `object.__new__` clones and direct class-allocator calls cannot mint creation-bound plan issuance evidence merely by copying valid fields and invoking `__post_init__()`. +- Constructor-bypassing `object.__new__` clones, direct class-allocator calls, and reentrant allocator calls from caller-owned timezone callbacks cannot mint creation-bound plan issuance evidence merely by copying valid fields and invoking `__post_init__()`. - The authoritative adapter reviews detached creation-bound plan evidence rather than a caller-owned live plan object. - Authority verification fields are tuple-immutable at runtime and exact-type checked before one-time unpacking. - A caller cannot mint an `approved_for_use` evidence artifact by importing a private constructor sentinel: direct and replaced receipt values remain unissued and cannot export canonical evidence. From da4162c6131eeefc3122951585fedddee4156df5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:33:13 -0700 Subject: [PATCH 215/216] docs(interview-plan): changelog timezone reentrancy repair --- packages/interview-plan/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interview-plan/CHANGELOG.md b/packages/interview-plan/CHANGELOG.md index 04d8d5098..58adfcb1c 100644 --- a/packages/interview-plan/CHANGELOG.md +++ b/packages/interview-plan/CHANGELOG.md @@ -25,7 +25,7 @@ - Derive activation tenant/interview-plan scope from the same canonical plan bytes supplied to the authority and retain the post-authority creation-seal check for any non-restored live-object mutation. - Make `StructuredInterviewActivationVerification` a runtime-immutable `NamedTuple`, reject subclasses, and unpack its exact tuple once before validation so `object.__setattr__` cannot create mixed authority-evidence revisions between field reads. - Bind every constructed `StructuredInterviewPlan` to a single-registration process-local creation seal outside plan-writable slots; canonical JSON and SHA-256 export now fail closed if low-level mutation changes the plan, if copied/reconstructed objects lack creation-bound issuance evidence, or if the same live identity attempts to renew its seal through repeated initialization. -- Arm plan-construction provenance only during the metaclass-mediated full `StructuredInterviewPlan(...)` call and consume it in `__post_init__()`, so neither `object.__new__` nor direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation can copy otherwise valid fields and mint activation-ready issuance evidence by manually invoking initialization. +- Make plan-construction provenance a one-shot metaclass-mediated allocator ticket consumed by the exact `StructuredInterviewPlan.__new__()` call before field validation or caller-controlled timezone callbacks can run; `object.__new__`, direct class-allocator calls, and reentrant allocator calls from `tzinfo.utcoffset()` therefore cannot copy otherwise valid fields and mint fresh issuance evidence by manually invoking initialization. - Remove constructor-token authorization from `StructuredInterviewActivationReceipt`: direct construction and `dataclasses.replace(...)` create unissued value objects that cannot export canonical evidence, while only `activate_structured_interview_plan(...)` registers the process-local receipt seal after authoritative verification and exact-scope matching succeed. A module-private sentinel no longer appears in the receipt constructor and cannot mint approval evidence. - Expand Structured Interview Plan Quality path triggers to cover repository-level Python/test configuration and `.gitignore` inputs that can change test collection, execution, or clean-checkout behavior, while retaining package, dependency-lock, workflow, ADR, doctoring, and traceability triggers. @@ -36,6 +36,7 @@ - Require exact built-in tuple containers for competency/panel reference collections and exact built-in strings for fixed `review_state` / `next_action` evidence before canonicalization, preventing caller-controlled runtime subclasses from passing validation and later switching serialized immutable evidence. - Treat caller-owned timezone implementations as untrusted code: offset evaluation and canonical-time rendering fail closed to governed field-specific validation errors rather than leaking arbitrary exceptions across plan, activation, or receipt boundaries. - Normalize both plan-generation and approval-time evidence before creation sealing or authority review so caller-controlled mutable `tzinfo` state cannot make one governed instant later represent a different UTC instant. +- Consume constructor provenance before any caller-owned `tzinfo` callback is invoked so reentrant timezone code cannot retain an allocator-created plan with construction eligibility and later turn copied fields into a second issued plan. - Prevent callers from converting a module-visible private sentinel into human-approval authority: receipt issuance evidence is now registered exclusively inside the verified activation factory after all host-verification and exact-scope checks pass. - Redact `StructuredInterviewPlan`, `StructuredInterviewActivationVerification`, and `StructuredInterviewActivationReceipt` representations so routine logs and assertion failures do not expose sensitive correlations or evidence digests. - Treat the process-local plan and activation-receipt seals plus live-identity provenance strictly as in-memory issuance-integrity evidence, not as durable audit stores, portable signatures, cross-process verification keys, or substitutes for the host's immutable audit/outbox contract. From 6917e41f9053fab6f7e99f8185f2137e8fc5fca5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 22:34:20 -0700 Subject: [PATCH 216/216] docs(interview-plan): trace timezone provenance reentrancy --- docs/traceability/structured-interview-plan.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/traceability/structured-interview-plan.md b/docs/traceability/structured-interview-plan.md index 20866c737..89fd59d6b 100644 --- a/docs/traceability/structured-interview-plan.md +++ b/docs/traceability/structured-interview-plan.md @@ -8,7 +8,7 @@ Caller-owned `tzinfo` implementations are treated as untrusted executable code. Plan generation, activation approval-time normalization, verification-time normalization, and canonical timestamp rendering convert exceptions raised by `tzinfo.utcoffset()` into field-specific governed `ValueError` and stop before authority side effects or evidence export. `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_activation_normalizes_hostile_timezone_failure_before_authority`, and `test_activation_receipt_normalizes_hostile_timezone_failure` bind this behavior; mutable-offset and representable-range regressions continue to prove UTC detachment and arithmetic fail-closure. -Plan issuance distinguishes a normal full `StructuredInterviewPlan(...)` class call from direct allocator invocation. A context-local construction token is armed by the private metaclass only while Python executes the full class constructor; `__new__()` registers one live identity as construction-eligible only inside that context, and `__post_init__()` must consume that provenance before it can register issuance evidence. `test_object_new_clone_cannot_acquire_plan_issuance_evidence` and `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence` prove that neither `object.__new__` nor direct class-allocator invocation can copy valid fields and mint a new issued plan. +Plan issuance distinguishes a normal full `StructuredInterviewPlan(...)` class call from direct or reentrant allocator invocation. The private metaclass arms a context-local **one-shot allocator ticket** immediately before normal class construction; the exact `__new__()` invocation consumes that ticket before field validation or caller-owned timezone callbacks can execute, and only that live identity becomes construction-eligible. `__post_init__()` must then consume that exact provenance before it can register issuance evidence. `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, and `test_timezone_callback_cannot_mint_plan_constructor_provenance` prove that `object.__new__`, direct class-allocator invocation, and allocator reentrancy from `tzinfo.utcoffset()` cannot copy valid fields and mint a new issued plan. Receipt construction is no longer an authorization mechanism. A directly constructed or `dataclasses.replace(...)`-created `StructuredInterviewActivationReceipt` may validate as a value shape, but it remains unissued and cannot export canonical evidence. Only `activate_structured_interview_plan(...)`, after authoritative verification and exact tenant/plan/digest/actor/time scope matching, registers the process-local receipt issuance seal. `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, and the replacement regression bind this distinction. @@ -19,14 +19,14 @@ Receipt construction is no longer an authorization mechanism. A directly constru | Interview content is tied to job analysis | UUIDv4-backed exact `job_analysis_reference` + lowercase SHA-256 digest | deterministic-plan test plus wrong-namespace/value-bearing/sentinel/noncanonical/version reference and digest regressions | | Authoritative tenant and Job scope is not inferred from identifiers | canonical non-sentinel `tenant_record_id` following the Orgmetra core operational-UUID contract; activation authority must re-resolve every plan reference in that tenant and prove requisition-to-Job-to-job-analysis binding before returning verification evidence | authoritative UUIDv7 tenant interoperability regression plus `test_authority_rejection_blocks_activation` and exact verification-scope mismatch regressions | | Predetermined questions, their competency mapping, and rating anchors cannot drift silently | UUIDv4-backed question-set, question-to-competency-map, and rating-anchor references plus independent digests; activation authority is required to verify their authoritative provenance | invalid/value-bearing/UUIDv1-reference and digest regressions, deterministic SHA-256 test, authority rejection/mismatch regressions | -| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; full-constructor provenance plus plan construction bind a process-local creation seal, while activation receipt issuance is registered only by the verified factory after exact-scope authority checks | plan evidence-version regressions, `test_plan_issuance_integrity.py`, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, replacement/post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | +| Evidence revisions remain distinguishable and creation-bound | bounded positive plan `evidence_version` in canonical JSON; one-shot full-constructor allocator provenance plus plan construction bind a process-local creation seal, while activation receipt issuance is registered only by the verified factory after exact-scope authority checks | plan evidence-version regressions, `test_plan_issuance_integrity.py` including timezone-callback reentrancy, `test_activation_receipt_cannot_be_minted_without_verified_factory_path`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, replacement/post-issuance rewrite, and missing-issuance-evidence fail-closed regressions | | Every governed competency has auditable coverage evidence | exact built-in tuple containing sorted unique 1–12 canonical UUIDv4-backed competency references; `question_count >= competency_count`; separately identified and digest-bound question-to-competency mapping artifact | collection shape/order/duplicate/opacity, UUIDv1 rejection, tuple-subclass switching-evidence rejection, question-count regressions, and mapping-reference/digest regressions | | Interview panel is accountable and bounded | exact built-in tuple containing sorted unique 2–8 canonical UUIDv4-backed `actor:` references; activation authority must re-resolve panel actors, prove resolved identities distinct, and verify eligibility/training before returning evidence | panel size/type/order/duplicate/namespace/value-bearing/UUIDv1 regressions, tuple-subclass switching-evidence rejection, plus fail-closed authority rejection path | | High-impact activation has an accountable human actor | `StructuredInterviewActivationReceipt` binds one canonical UUIDv4 `approving_actor_reference`, fixed purpose/reason, mandatory `human_confirmation=True`, exact detached UTC approval time, and fixed `approved_for_use` state; canonical export is available only after verified-factory issuance; `StructuredInterviewActivationVerification` must explicitly return the same reviewed instant | `test_activation_executes_authority_and_returns_immutable_human_receipt`, `test_private_module_sentinel_cannot_mint_verified_receipt_directly`, `test_activation_sends_approval_time_through_authoritative_verification`, `test_verification_contract_explicitly_binds_reviewed_approval_time`, and `test_activation_rejects_verification_for_different_approval_time` | -| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, allocator-bypassed, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work`, `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, plus remaining plan issuance-integrity regressions | -| Constructed plan evidence cannot be silently rewritten or resealed | only the normal full class-construction path can arm construction provenance; each successful `StructuredInterviewPlan` construction detaches `generated_at` to a built-in UTC instant, consumes the exact live-object provenance, then registers a process-local HMAC seal outside plan-writable slots exactly once; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied/allocator-bypassed identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, and duplicate-registration/reinitialization regressions | +| Activation accepts only a fully validated governed plan object | `activate_structured_interview_plan(...)` requires the exact `StructuredInterviewPlan` runtime type and requires creation-bound canonical plan evidence before authority work; duck-typed, subclassed, copied, rewritten, allocator-bypassed, reentrantly allocated, or otherwise unissued plan-shaped objects cannot bypass plan construction/issuance invariants | `test_activation_rejects_duck_typed_plan_before_authority_work`, `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, `test_timezone_callback_cannot_mint_plan_constructor_provenance`, plus remaining plan issuance-integrity regressions | +| Constructed plan evidence cannot be silently rewritten or resealed | only the normal full class-construction path can arm one allocator ticket, and the exact `__new__()` consumes it before caller callbacks; each successful `StructuredInterviewPlan` construction detaches `generated_at` to a built-in UTC instant, consumes exact live-object provenance, then registers a process-local HMAC seal outside plan-writable slots exactly once; canonical JSON and SHA-256 reject changed fields, discarded evidence, copied/allocator-bypassed identities, and repeated initialization that attempts to overwrite issuance evidence | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_canonical_evidence_fails_closed_after_low_level_mutation`, `test_missing_process_local_plan_issuance_evidence_fails_closed`, `test_copied_plan_has_no_transferable_process_local_issuance_evidence`, `test_object_new_clone_cannot_acquire_plan_issuance_evidence`, `test_direct_class_new_clone_cannot_acquire_plan_issuance_evidence`, `test_timezone_callback_cannot_mint_plan_constructor_provenance`, and duplicate-registration/reinitialization regressions | | Authority review cannot observe a temporary live-plan revision | activation captures creation-bound canonical plan JSON and its SHA-256 before the call and supplies only those detached built-in values to the authority; the caller's live `StructuredInterviewPlan` never crosses the authority contract, so change-and-restore (ABA) mutation cannot alter the reviewed revision; non-restored mutation still fails the post-call creation-seal check | `test_activation_authority_receives_detached_creation_bound_plan_evidence` plus `test_activation_detaches_plan_evidence_from_authority_time_aba_mutation` | -| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed as field-specific validation, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | +| Plan generation time has one stable audit meaning | caller-owned `generated_at` is detached into a built-in UTC datetime during plan construction before creation-seal registration; constructor privilege has already been consumed before offset evaluation; caller-controlled offset evaluation failures, reentrant allocator attempts, naive/unknown-offset values, and out-of-range UTC normalization fail closed, and later mutation of caller-owned `tzinfo` state cannot change or invalidate the issued instant | `test_plan_detaches_mutable_generated_at_timezone_before_sealing`, `test_plan_normalizes_hostile_timezone_failure_to_validation_error`, `test_timezone_callback_cannot_mint_plan_constructor_provenance`, `test_plan_rejects_utc_normalization_beyond_datetime_min_as_validation_error`, plus naive/unknown-offset/offset/fractional-time plan regressions | | Approval time has one stable audit meaning | caller-owned `approved_at` is detached into a built-in UTC datetime before chronology and authority work; caller-controlled offset evaluation failures, naive/unknown-offset values, and out-of-range UTC normalization fail closed before authority side effects; the same snapshot crosses the authority and receipt boundaries, and canonical rendering reuses the same fail-closed detachment | `test_activation_normalizes_hostile_timezone_failure_before_authority`, `test_activation_receipt_normalizes_hostile_timezone_failure`, `test_activation_rejects_naive_approval_time_before_authority_work`, `test_activation_rejects_approval_time_with_unknown_offset`, `test_activation_rejects_utc_normalization_beyond_datetime_max_before_authority`, `test_activation_freezes_mutable_timezone_before_authority_and_receipt`, and pre-generation chronology regression | | Authority evidence cannot be replayed across plan/actor/time scope | authority result must match the plan tenant, interview-plan reference, exact plan SHA-256 digest, approving actor, and normalized approval instant supplied to activation before receipt issuance is registered | parameterized `test_activation_rejects_authority_evidence_for_other_scope` plus `test_activation_rejects_verification_for_different_approval_time` | | Authority verification cannot mix revisions between field reads | the exact verification contract is a runtime-immutable `NamedTuple`; exact-type enforcement rejects behavioral subclasses, `object.__setattr__` cannot rewrite tuple fields, and activation unpacks the tuple once before validation/scope comparison/receipt issuance | `test_verification_contract_cannot_be_rewritten_with_object_setattr` plus `test_activation_rejects_verification_subclass_before_evidence_reads_can_diverge` | @@ -35,12 +35,12 @@ Receipt construction is no longer an authorization mechanism. A directly constru | Routine logs do not reveal plan or activation correlations | custom redacted `StructuredInterviewPlan.__repr__`, `StructuredInterviewActivationVerification.__repr__`, and `StructuredInterviewActivationReceipt.__repr__` | exact repr regressions prove references, evidence digests, and reviewed time are absent | | Planning and activation evidence remain candidate-neutral | neither plan nor activation receipt has candidate identity, response, score, demographic attribute, compensation value, or model-output fields | canonical JSON regressions plus contract surface review | | Generated evidence cannot self-approve a plan | plan remains `requires_human_approval`; activation requires the injected authoritative host boundary and a distinct explicit approving-actor parameter; receipt canonical export requires issuance registration performed only after successful exact-scope host verification | scalar fail-closed plan regressions, `test_authority_rejection_blocks_activation`, non-verification-result regression, direct-unissued-receipt regression, and private-sentinel regression | -| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; hostile offset evaluation and unrepresentable UTC normalization are rejected as governed validation; canonical JSON preserves fractional precision; exact SHA-256 binds plan and factory-issued activation receipt evidence | plan hostile-timezone/mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation hostile-timezone/UTC-snapshot/range-boundary regressions, and canonical/digest assertions | -| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and receipt shapes re-run value validation; direct/replaced receipt objects remain unissued and cannot export canonical evidence; exact runtime types protect plan collections/governance text; direct plan allocator calls cannot acquire full-constructor provenance; issued plans and receipts verify process-local issuance evidence before canonical export | direct constructor/allocator, private-sentinel, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, and receipt hostile-timezone/low-level rewrite/missing-seal regressions | +| Audit correlation is deterministic without losing temporal precision | caller-owned plan-generation and approval times are detached to built-in UTC instants before their respective trust boundaries; hostile offset evaluation, reentrant plan-allocation attempts, and unrepresentable UTC normalization are rejected as governed validation/integrity failures; canonical JSON preserves fractional precision; exact SHA-256 binds plan and factory-issued activation receipt evidence | plan hostile-timezone/reentrant-timezone/mutable-timezone/naive/unknown-offset/range-boundary/offset/fractional-time regressions, plan issuance-integrity regressions, activation hostile-timezone/UTC-snapshot/range-boundary regressions, and canonical/digest assertions | +| Direct construction or post-issuance in-memory rewriting cannot bypass invariants | plan and receipt shapes re-run value validation; direct/replaced receipt objects remain unissued and cannot export canonical evidence; exact runtime types protect plan collections/governance text; direct or reentrant plan allocator calls cannot acquire/retain full-constructor provenance; issued plans and receipts verify process-local issuance evidence before canonical export | direct constructor/allocator/reentrant-allocator, private-sentinel, tuple-subclass switching-evidence, fixed-governance string-subclass, `dataclasses.replace(...)`, plan low-level rewrite/copy/missing/reseal, and receipt hostile-timezone/low-level rewrite/missing-seal regressions | ## Evidence boundary -The plan object is creation-bound before activation begins. A private metaclass arms a context-local token only around the normal full `StructuredInterviewPlan(...)` class call; `__new__()` allocates in every case but records construction eligibility only while that token identifies the same class. This means direct `object.__new__` and direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)` allocation cannot manufacture constructor provenance by copying valid fields and manually invoking `__post_init__()`. Successful full construction then validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Offset evaluation itself is a trust boundary: arbitrary exceptions raised by caller-owned `tzinfo.utcoffset()` are converted into the field-specific governed `ValueError` and stop construction before issuance. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the same field-specific governed `ValueError` and stops before issuance-seal registration. Construction consumes the exact live-object provenance, computes an HMAC over the exact canonical payload, and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires issued identity for that exact live object, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, duplicate, or allocator-bypassed issuance evidence fails closed. The context token, live-identity registry, and HMAC are intentionally same-process runtime integrity evidence only—not a hostile-interpreter capability boundary, durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. +The plan object is creation-bound before activation begins. A private metaclass arms a context-local one-shot allocator ticket immediately before the normal full `StructuredInterviewPlan(...)` class call. The exact `__new__()` that begins that construction consumes the ticket before any field validation or caller-owned `tzinfo.utcoffset()` callback can execute, then records construction eligibility only for that exact live object. Direct `object.__new__`, direct `StructuredInterviewPlan.__new__(StructuredInterviewPlan)`, and allocator calls reached reentrantly from caller timezone code therefore cannot manufacture or retain constructor provenance by copying valid fields and manually invoking `__post_init__()`. Successful full construction then validates and detaches caller-owned `generated_at` using one concrete offset into a built-in UTC `datetime`, so later changes to the original mutable `tzinfo` object cannot alter or invalidate the issued plan instant. Offset evaluation itself is a trust boundary: arbitrary exceptions raised by caller-owned `tzinfo.utcoffset()` are converted into the field-specific governed `ValueError` and stop construction before issuance. If offset arithmetic would cross `datetime.min` or `datetime.max`, construction converts the arithmetic overflow into the same field-specific governed `ValueError` and stops before issuance-seal registration. Construction consumes the exact live-object provenance, computes an HMAC over the exact canonical payload, and registers it in process-local state outside plan-writable slots. Registration for one live identity is single-use; repeated `__post_init__()` cannot overwrite the original issuance record after low-level field mutation. `canonical_json()` renders the current payload once, requires issued identity for that exact live object, and verifies the creation seal with constant-time comparison before returning bytes; `sha256_digest()` is downstream of the same guard. Low-level `object.__setattr__` rewriting therefore cannot silently redefine the plan after construction, and `copy.copy`/other reconstructed identities do not inherit issuance authority merely by carrying the same fields. Missing, mismatched, duplicate, allocator-bypassed, or reentrantly allocated issuance evidence fails closed. The allocator ticket, live-identity registry, and HMAC are intentionally same-process runtime integrity evidence only—not a hostile-interpreter capability boundary, durable audit record, portable signature, rehydration protocol, or substitute for the host immutable audit/outbox boundary. The active PR implements an executable activation orchestration boundary, not merely a `next_action` string. `activate_structured_interview_plan(...)` first requires the exact governed `StructuredInterviewPlan` runtime type and obtains creation-bound canonical JSON. Tenant/interview-plan scope, canonical `generated_at`, and SHA-256 are derived from that same string. Caller-owned `approved_at` is detached using one concrete UTC offset into a built-in UTC datetime; hostile/broken offset evaluation, naive or unknown-offset values, and UTC normalization beyond the representable datetime range all fail as field-specific validation before authority work. The injected `StructuredInterviewActivationAuthority` receives the exact built-in canonical JSON string, its exact digest, the approving actor, and the built-in UTC approval snapshot. It never receives the caller's live plan object. This removes the ABA window in which an authority could observe a temporary modified plan and restore it before a post-call equality/seal check. A retained external live-plan alias may still be mutated by untrusted code, but it cannot change the detached evidence reviewed through this contract; any mutation left in place is additionally caught by the post-authority creation-seal check. @@ -50,7 +50,7 @@ The active PR implements an executable activation orchestration boundary, not me The plan boundary also requires exact built-in tuple containers for `competency_references` and `panel_actor_references`, plus exact built-in strings for fixed `review_state` and `next_action` evidence. This closes a Python runtime-subclass gap where caller-controlled iteration or equality behavior could satisfy construction checks and then serialize different immutable evidence later. -The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, caller-timezone exception normalization, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, factory-bound receipt issuance, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. +The authority protocol is intentionally not a second data store or cross-service SQL path. A concrete production adapter remains responsible for authoritative tenant-scoped re-resolution of every reference represented by the detached canonical plan evidence, requisition-to-Job-to-job-analysis relationship checks, question/mapping/rating provenance, panel identity separation, eligibility, training, and review of the exact approval instant. Its verification evidence must return that reviewed normalized instant and bind it through the owner's immutable evidence/audit implementation; the adapter must raise rather than return verification evidence when any required check fails. The current tests prove orchestration fail-closure, exact-plan runtime boundary, creation-bound plan integrity, stable and representable plan-generation time, caller-timezone exception normalization, constructor-provenance reentrancy fail-closure, detached immutable authority inputs, approval-time semantics and range fail-closure, runtime-immutable verification evidence, factory-bound receipt issuance, and exact evidence binding; they do **not** prove that a particular deployed adapter already performs database/API resolution correctly. The mapping reference/digest proves which approved mapping artifact was bound to the plan and detects later artifact drift. Plan `evidence_version` identifies the canonical plan-evidence revision; the activation receipt separately binds the exact plan digest, approving actor, authority-verification evidence, approval time, purpose/reason, confirmation, state, and its own evidence version. Package-owned UUIDv4 trust references keep timestamp/node-bearing UUIDv1 suffixes outside portable evidence, while `tenant_record_id` deliberately inherits the authoritative Orgmetra operational-UUID contract so the leaf package does not reject valid existing tenants.