From e10bd567e8cef1bc70e8e1f019c88c03170d1a41 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:07:59 -0700 Subject: [PATCH 01/52] test(job-analysis): reject temporal evidence subclasses --- ...st_job_analysis_temporal_type_integrity.py | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py diff --git a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py new file mode 100644 index 000000000..5921136b4 --- /dev/null +++ b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py @@ -0,0 +1,140 @@ +"""Temporal runtime-type integrity for canonical job-analysis evidence.""" + +from __future__ import annotations + +from datetime import date, datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel.job_analysis import ( + EvidenceSource, + FunctionalJobAnalysisProfile, + JobAnalysisSnapshot, + KSAORequirement, + TaskEvidence, + TaskKSAOLink, +) + +TENANT_ID = UUID("00000000-0000-4000-8000-000000002001") +JOB_ID = UUID("00000000-0000-4000-8000-000000002002") +ANALYSIS_ID = UUID("00000000-0000-4000-8000-000000002003") +TASK_ID = UUID("00000000-0000-4000-8000-000000002004") +KSAO_ID = UUID("00000000-0000-4000-8000-000000002005") +RECORDED_AT = datetime(2026, 8, 21, 5, 15, tzinfo=timezone.utc) + + +class _ForgedDate(date): + """Attempt to forge the business date written into canonical evidence.""" + + def isoformat(self) -> str: + """Render a different business date from the underlying value.""" + return "2099-01-01" + + +class _ForgedDateTime(datetime): + """Attempt to forge a recorded instant written into canonical evidence.""" + + def astimezone(self, tz=None): # noqa: ANN001 + """Preserve the hostile runtime type through UTC normalization.""" + return self + + def isoformat(self, sep="T", timespec="auto") -> str: # noqa: ARG002 + """Render a different recorded instant from the underlying value.""" + return "2099-01-01T00:00:00+00:00" + + +def _source(retrieved_at: datetime) -> EvidenceSource: + """Build one governed source record.""" + return EvidenceSource( + source_uri="https://www.onetcenter.org/database.html", + source_title="O*NET Database", + source_version_code="onet:30.3", + retrieved_at=retrieved_at, + content_digest_sha256="b" * 64, + origin_code="authoritative_occupation_source", + ) + + +def _snapshot(*, effective_from: date, recorded_at: datetime, reviewed_at: datetime) -> JobAnalysisSnapshot: + """Build one otherwise-valid validated snapshot around supplied temporal evidence.""" + source = _source(datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc)) + return JobAnalysisSnapshot( + analysis_record_id=ANALYSIS_ID, + tenant_record_id=TENANT_ID, + job_record_id=JOB_ID, + analysis_version_code="analysis:v1", + status_code="analysis_validated", + effective_from=effective_from, + recorded_at=recorded_at, + tasks=( + TaskEvidence( + tenant_record_id=TENANT_ID, + job_record_id=JOB_ID, + task_record_id=TASK_ID, + task_statement="Analyze governed workforce evidence and document findings.", + importance_level=5, + difficulty_level=4, + source=source, + ), + ), + ksao_requirements=( + KSAORequirement( + tenant_record_id=TENANT_ID, + job_record_id=JOB_ID, + ksao_record_id=KSAO_ID, + category_code="knowledge_requirement", + requirement_statement="Knowledge of governed workforce evidence and traceability.", + importance_level=5, + proficiency_level=4, + source=source, + ), + ), + task_ksao_links=( + TaskKSAOLink( + task_record_id=TASK_ID, + ksao_record_id=KSAO_ID, + relationship_strength=5, + essential_for_task=True, + ), + ), + fja_profile=FunctionalJobAnalysisProfile( + tenant_record_id=TENANT_ID, + job_record_id=JOB_ID, + data_function_code=2, + people_function_code=1, + things_function_code=7, + source=source, + ), + reviewed_by_reference="keyverse_subject:01JIOPSYCH", + reviewed_at=reviewed_at, + ) + + +def test_evidence_source_rejects_datetime_subclass_before_provenance_canonicalization() -> None: + """Reject caller-controlled timestamp rendering at the source-provenance boundary.""" + forged = _ForgedDateTime(2026, 8, 21, 5, 0, tzinfo=timezone.utc) + with pytest.raises(ValueError, match="retrieved_at must be a datetime"): + _source(forged) + + +@pytest.mark.parametrize( + ("effective_from", "recorded_at", "reviewed_at"), + [ + (_ForgedDate(2026, 8, 1), RECORDED_AT, RECORDED_AT), + (date(2026, 8, 1), _ForgedDateTime(2026, 8, 21, 5, 15, tzinfo=timezone.utc), RECORDED_AT), + (date(2026, 8, 1), RECORDED_AT, _ForgedDateTime(2026, 8, 21, 5, 15, tzinfo=timezone.utc)), + ], +) +def test_snapshot_rejects_temporal_subclasses_before_canonicalization( + effective_from: date, + recorded_at: datetime, + reviewed_at: datetime, +) -> None: + """Reject business/recorded-time objects whose methods can rewrite immutable evidence.""" + with pytest.raises(ValueError): + _snapshot( + effective_from=effective_from, + recorded_at=recorded_at, + reviewed_at=reviewed_at, + ) From 42973968c1e72edf93c6623d8199c17087b17961 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:08:59 -0700 Subject: [PATCH 02/52] fix(job-analysis): protect canonical temporal evidence types --- .../hris-kernel/src/orgmetra_hris_kernel/job_analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py index 9bb710dd1..c4d6ae4e6 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py @@ -100,8 +100,8 @@ def _validate_level(value: object, field_name: str) -> int: def _validate_aware_datetime(value: object, field_name: str) -> datetime: - """Return an offset-aware instant suitable for evidence ordering.""" - if not isinstance(value, datetime): + """Return an exact offset-aware instant suitable for immutable evidence ordering.""" + if type(value) is not datetime: raise ValueError(f"{field_name} must be a datetime") if value.tzinfo is None: raise ValueError(f"{field_name} must be timezone-aware") @@ -294,7 +294,7 @@ def __post_init__(self) -> None: _validate_code(self.status_code, "status_code") if self.status_code not in _ALLOWED_STATUS_CODES: raise ValueError("status_code is not an allowed analysis status") - if not isinstance(self.effective_from, date) or isinstance(self.effective_from, datetime): + if type(self.effective_from) is not date: raise ValueError("effective_from must be a date") recorded_at = _validate_aware_datetime(self.recorded_at, "recorded_at") if not isinstance(self.tasks, tuple) or not self.tasks: From e6fe8997e8ac73261f767cc5da70573e57602e3b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:10:31 -0700 Subject: [PATCH 03/52] test(audit): reject canonical-evidence runtime subclasses --- .../test_audit_runtime_type_integrity.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 packages/hris-kernel/tests/test_audit_runtime_type_integrity.py diff --git a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py new file mode 100644 index 000000000..58cbdec9a --- /dev/null +++ b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py @@ -0,0 +1,66 @@ +"""Runtime-type integrity regressions for immutable audit/outbox evidence.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel.audit import AuditOutboxEvent + + +class _ForgedUUID(UUID): + """Attempt to rewrite an immutable identity during canonical serialization.""" + + def __str__(self) -> str: + """Render an identity different from the underlying UUID value.""" + return "00000000-0000-4000-8000-ffffffffffff" + + +class _ForgedOccurredAt(datetime): + """Attempt to rewrite an immutable event timestamp during serialization.""" + + def astimezone(self, tz=None): # noqa: ANN001 + """Preserve hostile runtime behavior through UTC normalization.""" + return self + + def isoformat(self, sep="T", timespec="auto") -> str: # noqa: ARG002 + """Render a different instant from the underlying timestamp.""" + return "2099-01-01T00:00:00+00:00" + + +def _event(**overrides: object) -> AuditOutboxEvent: + """Build one valid high-impact audit event with focused overrides.""" + values: dict[str, object] = { + "event_id": UUID("00000000-0000-4000-8000-000000000002"), + "tenant_record_id": UUID("00000000-0000-4000-8000-000000000001"), + "source_service": "people_core", + "event_type": "orgmetra.people.assignment.recorded", + "resource_reference": "assignment_record:01JTESTOPAQUE", + "actor_reference": "keyverse_subject:01JACTOROPAQUE", + "purpose_code": "workforce_administration", + "reason_code": "hire_completion", + "evidence_version_code": "employment-offer:v3", + "result_code": "recorded", + "occurred_at": datetime(2026, 8, 21, 5, 20, tzinfo=timezone.utc), + "high_impact": True, + "confirmation_reference": "confirmation:01JCONFIRMOPAQUE", + } + values.update(overrides) + return AuditOutboxEvent(**values) # type: ignore[arg-type] + + +@pytest.mark.parametrize("field_name", ["event_id", "tenant_record_id"]) +def test_audit_event_rejects_uuid_subclasses_before_canonicalization(field_name: str) -> None: + """Caller-controlled UUID rendering cannot alter durable audit identity evidence.""" + forged = _ForgedUUID("00000000-0000-4000-8000-000000000123") + with pytest.raises(ValueError, match=f"{field_name} must be a UUID"): + _event(**{field_name: forged}) + + +def test_audit_event_rejects_datetime_subclasses_before_canonicalization() -> None: + """Caller-controlled timestamp rendering cannot alter durable audit chronology.""" + forged = _ForgedOccurredAt(2026, 8, 21, 5, 20, tzinfo=timezone.utc) + with pytest.raises(ValueError, match="occurred_at must be a datetime"): + _event(occurred_at=forged) From 3058ff9cd67354209cee820547aa37a278e73cdf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:10:54 -0700 Subject: [PATCH 04/52] fix(audit): protect canonical identity and chronology types --- packages/hris-kernel/src/orgmetra_hris_kernel/audit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 30e3f002f..71aa42189 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -61,9 +61,9 @@ class AuditOutboxEvent: def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" - if not isinstance(self.event_id, UUID): + if type(self.event_id) is not UUID: raise ValueError("event_id must be a UUID.") - if not isinstance(self.tenant_record_id, UUID): + if type(self.tenant_record_id) is not UUID: raise ValueError("tenant_record_id must be a UUID.") if self.event_id.int == 0: raise ValueError("event_id must not be the reserved nil UUID.") @@ -73,7 +73,7 @@ def __post_init__(self) -> None: raise ValueError("event_id must not be the reserved max UUID.") if self.tenant_record_id.int == _MAX_UUID_INT: raise ValueError("tenant_record_id must not be the reserved max UUID.") - if not isinstance(self.occurred_at, datetime): + if type(self.occurred_at) is not datetime: raise ValueError("occurred_at must be a datetime.") if type(self.high_impact) is not bool: raise ValueError("high_impact must be a boolean.") From 36fe98150b3a71beb6e6b33c56f6c43376e6c973 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:12:05 -0700 Subject: [PATCH 05/52] test(job-analysis): reject identity runtime subclasses --- ...st_job_analysis_temporal_type_integrity.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py index 5921136b4..7a8793a45 100644 --- a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py +++ b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py @@ -1,4 +1,4 @@ -"""Temporal runtime-type integrity for canonical job-analysis evidence.""" +"""Runtime-type integrity for canonical job-analysis evidence.""" from __future__ import annotations @@ -24,6 +24,14 @@ RECORDED_AT = datetime(2026, 8, 21, 5, 15, tzinfo=timezone.utc) +class _ForgedUUID(UUID): + """Attempt to forge an identity written into canonical job-analysis evidence.""" + + def __str__(self) -> str: + """Render a different identity from the underlying UUID value.""" + return "00000000-0000-4000-8000-ffffffffffff" + + class _ForgedDate(date): """Attempt to forge the business date written into canonical evidence.""" @@ -111,6 +119,18 @@ def _snapshot(*, effective_from: date, recorded_at: datetime, reviewed_at: datet ) +def test_job_analysis_rejects_uuid_subclasses_before_identity_canonicalization() -> None: + """Caller-controlled UUID rendering cannot rewrite task/link evidence identity.""" + forged = _ForgedUUID("00000000-0000-4000-8000-000000002004") + with pytest.raises(ValueError, match="task_record_id must be a UUID"): + TaskKSAOLink( + task_record_id=forged, + ksao_record_id=KSAO_ID, + relationship_strength=5, + essential_for_task=True, + ) + + def test_evidence_source_rejects_datetime_subclass_before_provenance_canonicalization() -> None: """Reject caller-controlled timestamp rendering at the source-provenance boundary.""" forged = _ForgedDateTime(2026, 8, 21, 5, 0, tzinfo=timezone.utc) From 4a541a5093528116c0b25382a552c7f9138dd1d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:14:01 -0700 Subject: [PATCH 06/52] fix(job-analysis): protect canonical identity types --- packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py index c4d6ae4e6..9076db167 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py @@ -44,7 +44,7 @@ def _validate_uuid(value: object, field_name: str) -> UUID: """Return a durable UUID or reject type-confused and sentinel identities.""" - if not isinstance(value, UUID): + if type(value) is not UUID: raise ValueError(f"{field_name} must be a UUID") if value.int == 0: raise ValueError(f"{field_name} must not be the nil UUID") From 58a6c782502e8f1161c56c16b1ea7e73b89aa668 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 02:37:07 -0700 Subject: [PATCH 07/52] fix(core): refresh foundation manifest after audit hardening --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 97f2bab14..7acb645ee 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"a53c490c462b086f05e5ee535298cb2a810a251926b8b1e4b4194a1197e654e3","bytes":7695,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file From 943c2dce2601edfa8b360e9de746bb73659a00af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 19:03:34 -0700 Subject: [PATCH 08/52] test(core): reject forged job-analysis primitive types --- ...st_job_analysis_temporal_type_integrity.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py index 7a8793a45..22d585c54 100644 --- a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py +++ b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py @@ -2,6 +2,7 @@ from __future__ import annotations +from dataclasses import replace from datetime import date, datetime, timezone from uuid import UUID @@ -52,6 +53,46 @@ def isoformat(self, sep="T", timespec="auto") -> str: # noqa: ARG002 return "2099-01-01T00:00:00+00:00" +class _ForgedStatusCode(str): + """Masquerade an ungoverned status as an allow-listed draft status.""" + + def __hash__(self) -> int: + """Route membership lookup to the allowed draft-status bucket.""" + return hash("analysis_draft") + + def __eq__(self, other: object) -> bool: + """Claim equality with the allowed draft status despite different text.""" + if other == "analysis_draft": + return True + return str.__eq__(self, other) + + +class _ForgedOriginCode(str): + """Masquerade ungoverned provenance as an allow-listed evidence origin.""" + + def __hash__(self) -> int: + """Route membership lookup to the authoritative-origin bucket.""" + return hash("authoritative_occupation_source") + + def __eq__(self, other: object) -> bool: + """Claim equality with an authoritative origin despite different text.""" + if other == "authoritative_occupation_source": + return True + return str.__eq__(self, other) + + +class _ForgedLevel(int): + """Masquerade an out-of-range ordinal as an allowed job-analysis level.""" + + def __ge__(self, other: object) -> bool: + """Forge the lower-bound comparison used by the level validator.""" + return True + + def __le__(self, other: object) -> bool: + """Forge the upper-bound comparison used by the level validator.""" + return True + + def _source(retrieved_at: datetime) -> EvidenceSource: """Build one governed source record.""" return EvidenceSource( @@ -158,3 +199,36 @@ def test_snapshot_rejects_temporal_subclasses_before_canonicalization( recorded_at=recorded_at, reviewed_at=reviewed_at, ) + + +def test_snapshot_rejects_status_subclass_that_forges_allow_list_membership() -> None: + """An ungoverned status cannot masquerade as draft while serializing different text.""" + snapshot = _snapshot( + effective_from=date(2026, 8, 1), + recorded_at=RECORDED_AT, + reviewed_at=RECORDED_AT, + ) + with pytest.raises(ValueError, match="status_code must be a string"): + replace(snapshot, status_code=_ForgedStatusCode("shadow_state")) + + +def test_evidence_source_rejects_origin_subclass_that_forges_allow_list_membership() -> None: + """Provenance classification cannot pass as authoritative under different serialized text.""" + source = _source(datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc)) + with pytest.raises(ValueError, match="origin_code must be a string"): + replace(source, origin_code=_ForgedOriginCode("shadow_origin")) + + +def test_task_rejects_integer_subclass_that_forges_level_bounds() -> None: + """Out-of-range ordinal evidence cannot override comparisons and serialize as valid.""" + source = _source(datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc)) + with pytest.raises(ValueError, match="importance_level must be an integer"): + TaskEvidence( + tenant_record_id=TENANT_ID, + job_record_id=JOB_ID, + task_record_id=TASK_ID, + task_statement="Analyze governed workforce evidence and document findings.", + importance_level=_ForgedLevel(99), + difficulty_level=4, + source=source, + ) From 05d04db7b8420f6efe4a2d488044bea31200ceed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 19:04:44 -0700 Subject: [PATCH 09/52] fix(core): reject forged job-analysis codes and levels --- .../hris-kernel/src/orgmetra_hris_kernel/job_analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py index 9076db167..6d64e3909 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py @@ -55,7 +55,7 @@ def _validate_uuid(value: object, field_name: str) -> UUID: def _validate_code(value: object, field_name: str) -> str: """Return a two-or-more-word lower snake_case contract code.""" - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be a string") if not _CODE_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be a two-or-more-word snake_case code") @@ -92,7 +92,7 @@ def _validate_text(value: object, field_name: str, *, minimum: int = 1) -> str: def _validate_level(value: object, field_name: str) -> int: """Return an ordinal 1..5 job-analysis rating.""" - if isinstance(value, bool) or not isinstance(value, int): + if type(value) is not int: raise ValueError(f"{field_name} must be an integer") if not 1 <= value <= 5: raise ValueError(f"{field_name} must be between 1 and 5") @@ -236,7 +236,7 @@ def __post_init__(self) -> None: (self.people_function_code, "people_function_code", 8), (self.things_function_code, "things_function_code", 7), ): - if isinstance(value, bool) or not isinstance(value, int): + if type(value) is not int: raise ValueError(f"{field_name} must be an integer") if not 0 <= value <= maximum: raise ValueError(f"{field_name} must be between 0 and {maximum}") From 7ae6331b68eb9af3faa6bace8b9ef11d037badc4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 19:06:24 -0700 Subject: [PATCH 10/52] test(core): complete adversarial level ordering --- .../tests/test_job_analysis_temporal_type_integrity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py index 22d585c54..9ed8e036b 100644 --- a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py +++ b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py @@ -92,6 +92,14 @@ def __le__(self, other: object) -> bool: """Forge the upper-bound comparison used by the level validator.""" return True + def __lt__(self, other: object) -> bool: + """Keep adversarial ordering behavior internally consistent.""" + return True + + def __gt__(self, other: object) -> bool: + """Keep adversarial ordering behavior internally consistent.""" + return True + def _source(retrieved_at: datetime) -> EvidenceSource: """Build one governed source record.""" From 9a1bbd3a12ba3e70a4e19eba8962263ce3146d52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 18:51:23 +0900 Subject: [PATCH 11/52] fix(core): close canonical evidence runtime gaps --- CHANGELOG.md | 1 + manifest.json | 2 +- .../src/orgmetra_hris_kernel/audit.py | 37 +++-- .../src/orgmetra_hris_kernel/job_analysis.py | 57 +++++--- .../test_audit_runtime_type_integrity.py | 114 ++++++++++++++- ...st_job_analysis_temporal_type_integrity.py | 137 +++++++++++++++++- 6 files changed, 315 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f4752d7..976e85ee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to Orgmetra will be documented in this file. - Active performance-criterion scope hardening: `criterion_observation_scope_guard` rejects criterion outcomes for a Job the worker did not effectively hold at the observation date, observations before the relevant assignment, and observations outside the referenced performance cycle while preserving valid multiple-assignment cases and existing bitemporal correction semantics. The guard evaluates current-recorded facts, derives the date coordinate from `observed_at` in UTC so session `TimeZone` cannot alter the result, uses a trusted function search path, and adds no PII or automated employment decision authority. The Foundation PostgreSQL contract also rejects a closed `recorded_to` on each time-coordinate lookup and proves UTC midnight plus non-UTC session `TimeZone` boundaries. - Bitemporal tenant-scoped organization hierarchy validation that rejects visible indirect parent cycles and reuses single-valued recorded-time reconstruction before graph traversal. - Stacked governed job-analysis evidence contract via `JobAnalysisSnapshot`, `TaskEvidence`, `KSAORequirement`, `TaskKSAOLink`, `FunctionalJobAnalysisProfile`, and `EvidenceSource`: tenant/Job-scoped observable tasks, explicit Task-to-KSAO linkage, importance/difficulty/proficiency ratings, source/version/retrieval/SHA-256 provenance, deterministic canonical snapshot bytes, current O*NET evidence support, and historical DOT Data/People/Things compatibility. Validated snapshots require accountable human review and complete non-LLM evidence; LLM-origin material remains `analysis_draft`, and the snapshot is evidence input rather than a hiring, promotion, termination, compensation, or other high-impact employment decision. +- Active-PR core evidence hardening now rejects caller-controlled built-in-type subclasses at audit and job-analysis trust boundaries and detaches accepted timestamps from mutable timezone providers before canonical serialization. - Stacked governed audit/outbox slice via `AuditOutboxEvent`, `audit_event_record`, `outbox_delivery_record`, and `outbox_delivery_escalation_record`: CloudEvents 1.0-compatible PII-minimized metadata, exact canonical JSON bytes, database-verified SHA-256 digests, mandatory human confirmation for high-impact events, immutable audit evidence, tenant RLS, atomic audit/outbox insertion, guarded pending/leased/delivered/dead-lettered delivery state, tenant-safe `claim_outbox_delivery(...)` with deterministic due-work ordering, `FOR UPDATE ... SKIP LOCKED`, opaque worker identity, bounded future leases, immutable envelope return, and atomic takeover of genuinely expired leases only while retry attempts remain; owner-bound `complete_outbox_delivery(...)` and `retry_outbox_delivery(...)`; database-budget-governed `dead_letter_outbox_delivery(...)`; and a separately privileged `operator_dead_letter_expired_outbox_delivery(...)` recovery path for an exhausted final lease whose recorded worker identity is permanently unavailable. `maximum_attempt_count` is persisted on the delivery row, defaults to 5, is constrained to 1 through 100, and cannot be lowered by a dispatcher during finalization. Migration 0007 prevents retry or expired-lease takeover from creating attempt N+1; migration 0008 adds TRUNCATE guards, trusted function search paths, a concurrently built due-work partial index, session-independent immutable envelope validation, and operator recovery backed by separate NOLOGIN/NOBYPASSRLS owner/capability roles so the externally assignable operator role can invoke recovery without receiving direct transport-table read/write rights. Migration 0008 also rejects pre-existing reserved recovery-role names before project DDL, atomically contains the temporary schema-creation privilege used for function ownership handoff, and forces deferred escalation binding while the narrow SECURITY DEFINER owner is still active. Exponential/backoff policy selection, policy-specific producer configuration, and external delivery receipts remain subsequent work. - `orgmetra_hris_kernel` 0.4.0 with exclusive-versus-concurrent employment, staffable position coverage, exclusive-seat capacity, and `validate_assignment_write` at 100% statement and branch coverage. - `POST /v1/employment-records`, `POST /v1/position-records`, and `POST /v1/assignment-records` with the same Keyverse mutation context, confirmation, and versioned evidence composition as other high-impact commands. diff --git a/manifest.json b/manifest.json index 7acb645ee..b01705c8f 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"a53c490c462b086f05e5ee535298cb2a810a251926b8b1e4b4194a1197e654e3","bytes":7695,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"4f6327d5c8ce07d7ec15f715aa4cdf8c17f7d16138130a5687b3df0e306386c0","bytes":8659,"lines":179},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 71aa42189..2f2df38da 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -11,7 +11,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from hashlib import sha256 import json import re @@ -34,6 +34,29 @@ _ALL_REQUIRED_TEXT_FIELDS = ("source_service", "event_type", *_REQUIRED_TEXT_FIELDS) +def _freeze_timestamp(value: datetime) -> datetime: + """Detach caller-controlled timezone behavior as one immutable UTC instant.""" + if type(value) is not datetime or value.tzinfo is None: + raise ValueError("occurred_at must be an exact timezone-aware datetime.") + try: + offset = value.utcoffset() + except Exception as exc: # noqa: BLE001 - normalize provider behavior at trust boundary. + raise ValueError("occurred_at must resolve to a UTC offset.") from exc + if offset is None or type(offset) is not timedelta: + raise ValueError("occurred_at must resolve to a UTC offset.") + try: + return (value.replace(tzinfo=None) - offset).replace(tzinfo=timezone.utc) + except OverflowError as exc: + raise ValueError("occurred_at must be a representable timezone-aware datetime.") from exc + + +def _canonical_timestamp(value: datetime) -> str: + """Render only a previously detached built-in UTC instant as RFC 3339 text.""" + if type(value) is not datetime or value.tzinfo is not timezone.utc: + raise ValueError("occurred_at must be an exact timezone-aware datetime.") + return value.isoformat().replace("+00:00", "Z") + + @dataclass(frozen=True, slots=True) class AuditOutboxEvent: """One immutable governance envelope for an Orgmetra domain mutation. @@ -78,14 +101,11 @@ def __post_init__(self) -> None: if type(self.high_impact) is not bool: raise ValueError("high_impact must be a boolean.") for field_name in _ALL_REQUIRED_TEXT_FIELDS: - if not isinstance(getattr(self, field_name), str): + if type(getattr(self, field_name)) is not str: raise ValueError(f"{field_name} must be a string.") - if self.confirmation_reference is not None and not isinstance(self.confirmation_reference, str): + if self.confirmation_reference is not None and type(self.confirmation_reference) is not str: raise ValueError("confirmation_reference must be a string when supplied.") - if self.occurred_at.tzinfo is None: - raise ValueError("occurred_at must be timezone-aware.") - if self.occurred_at.utcoffset() is None: - raise ValueError("occurred_at must resolve to a UTC offset.") + object.__setattr__(self, "occurred_at", _freeze_timestamp(self.occurred_at)) if _SOURCE_SERVICE_PATTERN.fullmatch(self.source_service) is None: raise ValueError("source_service must contain two or more lower snake_case words.") if _EVENT_TYPE_PATTERN.fullmatch(self.event_type) is None: @@ -118,14 +138,13 @@ def to_cloudevent(self) -> dict[str, object]: PII-minimized result body. Persist this mapping atomically with the owning business write before asynchronous delivery. """ - occurred_utc = self.occurred_at.astimezone(timezone.utc) envelope: dict[str, object] = { "specversion": "1.0", "id": str(self.event_id), "source": f"urn:orgmetra:{self.source_service}", "type": self.event_type, "subject": self.resource_reference, - "time": occurred_utc.isoformat().replace("+00:00", "Z"), + "time": _canonical_timestamp(self.occurred_at), "datacontenttype": "application/json", "orgmetratenant": str(self.tenant_record_id), "orgmetraactor": self.actor_reference, diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py index 6d64e3909..ea260bd2e 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py @@ -10,7 +10,7 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import date, datetime, timezone +from datetime import date, datetime, timedelta, timezone from hashlib import sha256 import json import re @@ -64,7 +64,7 @@ def _validate_code(value: object, field_name: str) -> str: def _validate_reference(value: object, field_name: str) -> str: """Return a namespaced opaque reference instead of human-readable identity data.""" - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be a string") if not _REFERENCE_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be a namespaced opaque reference") @@ -73,7 +73,7 @@ def _validate_reference(value: object, field_name: str) -> str: def _validate_version(value: object, field_name: str) -> str: """Return a compact immutable version token.""" - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be a string") if not _VERSION_PATTERN.fullmatch(value): raise ValueError(f"{field_name} must be a compact version token") @@ -82,7 +82,7 @@ def _validate_version(value: object, field_name: str) -> str: def _validate_text(value: object, field_name: str, *, minimum: int = 1) -> str: """Return normalized nonblank explanatory text without changing its meaning.""" - if not isinstance(value, str): + if type(value) is not str: raise ValueError(f"{field_name} must be a string") normalized = " ".join(value.split()) if len(normalized) < minimum: @@ -100,19 +100,28 @@ def _validate_level(value: object, field_name: str) -> int: def _validate_aware_datetime(value: object, field_name: str) -> datetime: - """Return an exact offset-aware instant suitable for immutable evidence ordering.""" + """Detach one exact offset-aware instant as immutable UTC evidence.""" if type(value) is not datetime: raise ValueError(f"{field_name} must be a datetime") if value.tzinfo is None: raise ValueError(f"{field_name} must be timezone-aware") - if value.utcoffset() is None: + try: + offset = value.utcoffset() + except Exception as exc: # noqa: BLE001 - normalize provider behavior at trust boundary. + raise ValueError(f"{field_name} must resolve to a UTC offset") from exc + if offset is None or type(offset) is not timedelta: raise ValueError(f"{field_name} must resolve to a UTC offset") - return value + try: + return (value.replace(tzinfo=None) - offset).replace(tzinfo=timezone.utc) + except OverflowError as exc: + raise ValueError(f"{field_name} must be a representable timezone-aware datetime") from exc def _utc_text(value: datetime) -> str: - """Serialize an already-validated instant as canonical UTC text.""" - return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + """Serialize a previously detached built-in UTC instant as canonical text.""" + if type(value) is not datetime or value.tzinfo is not timezone.utc: + raise ValueError("datetime must be an exact timezone-aware datetime") + return value.isoformat().replace("+00:00", "Z") @dataclass(frozen=True, slots=True) @@ -128,7 +137,7 @@ class EvidenceSource: def __post_init__(self) -> None: """Reject ambiguous, credential-bearing, mutable, or untyped provenance.""" - if not isinstance(self.source_uri, str): + if type(self.source_uri) is not str: raise ValueError("source_uri must be a string") parsed = urlsplit(self.source_uri) if parsed.scheme != "https" or not parsed.hostname: @@ -141,8 +150,12 @@ def __post_init__(self) -> None: _validate_text(self.source_title, "source_title", minimum=3), ) _validate_version(self.source_version_code, "source_version_code") - _validate_aware_datetime(self.retrieved_at, "retrieved_at") - if not isinstance(self.content_digest_sha256, str): + object.__setattr__( + self, + "retrieved_at", + _validate_aware_datetime(self.retrieved_at, "retrieved_at"), + ) + if type(self.content_digest_sha256) is not str: raise ValueError("content_digest_sha256 must be a string") if not _SHA256_PATTERN.fullmatch(self.content_digest_sha256): raise ValueError("content_digest_sha256 must be 64 lowercase hexadecimal characters") @@ -175,7 +188,7 @@ def __post_init__(self) -> None: ) _validate_level(self.importance_level, "importance_level") _validate_level(self.difficulty_level, "difficulty_level") - if not isinstance(self.source, EvidenceSource): + if type(self.source) is not EvidenceSource: raise ValueError("source must be EvidenceSource") @@ -207,7 +220,7 @@ def __post_init__(self) -> None: ) _validate_level(self.importance_level, "importance_level") _validate_level(self.proficiency_level, "proficiency_level") - if not isinstance(self.source, EvidenceSource): + if type(self.source) is not EvidenceSource: raise ValueError("source must be EvidenceSource") @@ -240,7 +253,7 @@ def __post_init__(self) -> None: raise ValueError(f"{field_name} must be an integer") if not 0 <= value <= maximum: raise ValueError(f"{field_name} must be between 0 and {maximum}") - if not isinstance(self.source, EvidenceSource): + if type(self.source) is not EvidenceSource: raise ValueError("source must be EvidenceSource") @@ -258,7 +271,7 @@ def __post_init__(self) -> None: _validate_uuid(self.task_record_id, "task_record_id") _validate_uuid(self.ksao_record_id, "ksao_record_id") _validate_level(self.relationship_strength, "relationship_strength") - if not isinstance(self.essential_for_task, bool): + if type(self.essential_for_task) is not bool: raise ValueError("essential_for_task must be a bool") @@ -297,13 +310,14 @@ def __post_init__(self) -> None: if type(self.effective_from) is not date: raise ValueError("effective_from must be a date") recorded_at = _validate_aware_datetime(self.recorded_at, "recorded_at") - if not isinstance(self.tasks, tuple) or not self.tasks: + object.__setattr__(self, "recorded_at", recorded_at) + if type(self.tasks) is not tuple or not self.tasks: raise ValueError("tasks must be a non-empty tuple") - if not isinstance(self.ksao_requirements, tuple) or not self.ksao_requirements: + if type(self.ksao_requirements) is not tuple or not self.ksao_requirements: raise ValueError("ksao_requirements must be a non-empty tuple") - if not isinstance(self.task_ksao_links, tuple) or not self.task_ksao_links: + if type(self.task_ksao_links) is not tuple or not self.task_ksao_links: raise ValueError("task_ksao_links must be a non-empty tuple") - if not isinstance(self.fja_profile, FunctionalJobAnalysisProfile): + if type(self.fja_profile) is not FunctionalJobAnalysisProfile: raise ValueError("fja_profile must be FunctionalJobAnalysisProfile") for item in (*self.tasks, *self.ksao_requirements, self.fja_profile): @@ -331,7 +345,7 @@ def __post_init__(self) -> None: ksao_id_set = set(ksao_ids) link_pairs: set[tuple[UUID, UUID]] = set() for link in self.task_ksao_links: - if not isinstance(link, TaskKSAOLink): + if type(link) is not TaskKSAOLink: raise ValueError("task_ksao_links must contain TaskKSAOLink values") if link.task_record_id not in task_id_set: raise ValueError("task_ksao_links contains an unknown task_record_id") @@ -351,6 +365,7 @@ def __post_init__(self) -> None: if self.reviewed_by_reference is not None: _validate_reference(self.reviewed_by_reference, "reviewed_by_reference") reviewed_at = _validate_aware_datetime(self.reviewed_at, "reviewed_at") + object.__setattr__(self, "reviewed_at", reviewed_at) if reviewed_at > recorded_at: raise ValueError("reviewed_at must not be later than recorded_at") if any(source.retrieved_at > reviewed_at for source in sources): diff --git a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py index 58cbdec9a..e247872ed 100644 --- a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py +++ b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone, tzinfo from uuid import UUID import pytest @@ -30,6 +30,56 @@ def isoformat(self, sep="T", timespec="auto") -> str: # noqa: ARG002 return "2099-01-01T00:00:00+00:00" +class _OpaqueText(str): + """Represent valid audit text through an untrusted runtime subclass.""" + + +class _MutableOffset(tzinfo): + """Expose timezone state that can change after event construction.""" + + def __init__(self) -> None: + """Start with a UTC offset.""" + self.offset = timedelta(0) + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Return the currently configured offset.""" + del value + return self.offset + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed.""" + del value + return timedelta(0) + + +class _ExplodingOffset(tzinfo): + """Raise arbitrary provider behavior while an event instant is resolved.""" + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Force the trust boundary to normalize provider failures.""" + del value + raise RuntimeError("provider details must not escape") + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed if queried.""" + del value + return timedelta(0) + + +class _OversizedOffset(tzinfo): + """Return an extreme offset that cannot be detached from year one.""" + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Force UTC detachment outside representable datetime values.""" + del value + return timedelta(hours=23, minutes=59) + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed.""" + del value + return timedelta(0) + + def _event(**overrides: object) -> AuditOutboxEvent: """Build one valid high-impact audit event with focused overrides.""" values: dict[str, object] = { @@ -64,3 +114,65 @@ def test_audit_event_rejects_datetime_subclasses_before_canonicalization() -> No forged = _ForgedOccurredAt(2026, 8, 21, 5, 20, tzinfo=timezone.utc) with pytest.raises(ValueError, match="occurred_at must be a datetime"): _event(occurred_at=forged) + + +@pytest.mark.parametrize( + ("field_name", "value"), + [ + ("source_service", _OpaqueText("people_core")), + ("event_type", _OpaqueText("orgmetra.people.assignment.recorded")), + ("resource_reference", _OpaqueText("assignment_record:01JTESTOPAQUE")), + ("actor_reference", _OpaqueText("keyverse_subject:01JACTOROPAQUE")), + ("purpose_code", _OpaqueText("workforce_administration")), + ("reason_code", _OpaqueText("hire_completion")), + ("evidence_version_code", _OpaqueText("employment-offer:v3")), + ("result_code", _OpaqueText("recorded")), + ("confirmation_reference", _OpaqueText("confirmation:01JCONFIRMOPAQUE")), + ], +) +def test_audit_event_rejects_string_subclasses_before_canonicalization( + field_name: str, value: str +) -> None: + """Reject caller-controlled runtime behavior in every audit text field.""" + with pytest.raises(ValueError, match="must be a string"): + _event(**{field_name: value}) + + +def test_audit_event_detaches_mutable_timezone_state() -> None: + """Keep canonical audit chronology stable after timezone state mutates.""" + zone = _MutableOffset() + event = _event( + high_impact=False, + confirmation_reference=None, + occurred_at=datetime(2026, 8, 21, 5, 20, tzinfo=zone), + ) + first = event.to_cloudevent() + + zone.offset = timedelta(hours=9) + + assert event.occurred_at.tzinfo is timezone.utc + assert event.to_cloudevent() == first + + +def test_audit_event_normalizes_timezone_provider_exceptions() -> None: + """Do not leak arbitrary timezone-provider exceptions from event construction.""" + with pytest.raises(ValueError, match="occurred_at must resolve to a UTC offset"): + _event(occurred_at=datetime(2026, 8, 21, 5, 20, tzinfo=_ExplodingOffset())) + + +def test_audit_event_normalizes_offset_overflow_to_value_error() -> None: + """Fail closed when UTC detachment exceeds representable datetime values.""" + with pytest.raises(ValueError, match="occurred_at must be a representable"): + _event(occurred_at=datetime(1, 1, 1, 0, 0, tzinfo=_OversizedOffset())) + + +def test_audit_event_canonicalization_rejects_reintroduced_timezone_behavior() -> None: + """Fail closed if low-level mutation reintroduces executable timezone behavior.""" + event = _event() + object.__setattr__( + event, + "occurred_at", + datetime(2026, 8, 21, 5, 20, tzinfo=_MutableOffset()), + ) + with pytest.raises(ValueError, match="exact timezone-aware datetime"): + event.to_cloudevent() diff --git a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py index 9ed8e036b..bbc9ed8d3 100644 --- a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py +++ b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import replace -from datetime import date, datetime, timezone +from datetime import date, datetime, timedelta, timezone, tzinfo from uuid import UUID import pytest @@ -101,6 +101,56 @@ def __gt__(self, other: object) -> bool: return True +class _OpaqueText(str): + """Represent valid evidence text through an untrusted runtime subclass.""" + + +class _MutableOffset(tzinfo): + """Expose timezone state that can change after evidence construction.""" + + def __init__(self) -> None: + """Start with a UTC offset.""" + self.offset = timedelta(0) + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Return the currently configured offset.""" + del value + return self.offset + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed.""" + del value + return timedelta(0) + + +class _ExplodingOffset(tzinfo): + """Raise arbitrary provider behavior while an evidence instant is resolved.""" + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Force the trust boundary to normalize provider failures.""" + del value + raise RuntimeError("provider details must not escape") + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed if queried.""" + del value + return timedelta(0) + + +class _OversizedOffset(tzinfo): + """Return an extreme offset that cannot be detached from year one.""" + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Force UTC detachment outside representable datetime values.""" + del value + return timedelta(hours=23, minutes=59) + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed.""" + del value + return timedelta(0) + + def _source(retrieved_at: datetime) -> EvidenceSource: """Build one governed source record.""" return EvidenceSource( @@ -187,6 +237,35 @@ def test_evidence_source_rejects_datetime_subclass_before_provenance_canonicaliz _source(forged) +@pytest.mark.parametrize( + ("field_name", "value"), + [ + ("source_uri", _OpaqueText("https://www.onetcenter.org/database.html")), + ("source_title", _OpaqueText("O*NET Database")), + ("source_version_code", _OpaqueText("onet:30.3")), + ("content_digest_sha256", _OpaqueText("b" * 64)), + ], +) +def test_evidence_source_rejects_string_subclasses_before_canonicalization( + field_name: str, value: str +) -> None: + """Reject caller-controlled string behavior in source provenance fields.""" + source = _source(datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc)) + with pytest.raises(ValueError, match="must be"): + replace(source, **{field_name: value}) + + +def test_snapshot_rejects_reference_string_subclasses_before_canonicalization() -> None: + """Reject caller-controlled runtime behavior in accountable review references.""" + snapshot = _snapshot( + effective_from=date(2026, 8, 1), + recorded_at=RECORDED_AT, + reviewed_at=RECORDED_AT, + ) + with pytest.raises(ValueError, match="reviewed_by_reference must be a string"): + replace(snapshot, reviewed_by_reference=_OpaqueText("keyverse_subject:01JIOPSYCH")) + + @pytest.mark.parametrize( ("effective_from", "recorded_at", "reviewed_at"), [ @@ -227,6 +306,62 @@ def test_evidence_source_rejects_origin_subclass_that_forges_allow_list_membersh replace(source, origin_code=_ForgedOriginCode("shadow_origin")) +def test_evidence_source_detaches_mutable_timezone_state() -> None: + """Keep source provenance chronology stable after timezone state mutates.""" + zone = _MutableOffset() + source = _source(datetime(2026, 8, 21, 5, 0, tzinfo=zone)) + first = source.retrieved_at + + zone.offset = timedelta(hours=9) + + assert source.retrieved_at == first + assert source.retrieved_at.tzinfo is timezone.utc + + +def test_snapshot_detaches_mutable_timezone_state() -> None: + """Keep snapshot chronology stable after caller-owned timezone state mutates.""" + zone = _MutableOffset() + snapshot = _snapshot( + effective_from=date(2026, 8, 1), + recorded_at=datetime(2026, 8, 21, 5, 15, tzinfo=zone), + reviewed_at=RECORDED_AT, + ) + first = snapshot.to_snapshot() + + zone.offset = timedelta(hours=9) + + assert snapshot.recorded_at.tzinfo is timezone.utc + assert snapshot.to_snapshot() == first + + +def test_evidence_source_normalizes_timezone_provider_exceptions() -> None: + """Do not leak arbitrary timezone-provider exceptions from source construction.""" + with pytest.raises(ValueError, match="retrieved_at must resolve to a UTC offset"): + _source(datetime(2026, 8, 21, 5, 0, tzinfo=_ExplodingOffset())) + + +def test_evidence_source_normalizes_offset_overflow_to_value_error() -> None: + """Fail closed when UTC detachment exceeds representable datetime values.""" + with pytest.raises(ValueError, match="retrieved_at must be a representable"): + _source(datetime(1, 1, 1, 0, 0, tzinfo=_OversizedOffset())) + + +def test_snapshot_canonicalization_rejects_reintroduced_timezone_behavior() -> None: + """Fail closed if low-level mutation reintroduces executable timezone behavior.""" + snapshot = _snapshot( + effective_from=date(2026, 8, 1), + recorded_at=RECORDED_AT, + reviewed_at=RECORDED_AT, + ) + object.__setattr__( + snapshot, + "recorded_at", + datetime(2026, 8, 21, 5, 15, tzinfo=_MutableOffset()), + ) + with pytest.raises(ValueError, match="exact timezone-aware datetime"): + snapshot.to_snapshot() + + def test_task_rejects_integer_subclass_that_forges_level_bounds() -> None: """Out-of-range ordinal evidence cannot override comparisons and serialize as valid.""" source = _source(datetime(2026, 8, 21, 5, 0, tzinfo=timezone.utc)) From 5c5250953273051276a356ac91ea9a248968c80a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 00:46:47 +0900 Subject: [PATCH 12/52] fix(job-analysis): reject nested evidence subclasses --- .../src/orgmetra_hris_kernel/job_analysis.py | 7 +++ ...st_job_analysis_temporal_type_integrity.py | 43 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py index ea260bd2e..181359fb6 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py @@ -320,6 +320,13 @@ def __post_init__(self) -> None: if type(self.fja_profile) is not FunctionalJobAnalysisProfile: raise ValueError("fja_profile must be FunctionalJobAnalysisProfile") + for task in self.tasks: + if type(task) is not TaskEvidence: + raise ValueError("tasks must contain TaskEvidence values") + for item in self.ksao_requirements: + if type(item) is not KSAORequirement: + raise ValueError("ksao_requirements must contain KSAORequirement values") + for item in (*self.tasks, *self.ksao_requirements, self.fja_profile): if item.tenant_record_id != self.tenant_record_id: raise ValueError("all job-analysis evidence must share tenant_record_id") diff --git a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py index bbc9ed8d3..37e502cbc 100644 --- a/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py +++ b/packages/hris-kernel/tests/test_job_analysis_temporal_type_integrity.py @@ -105,6 +105,14 @@ class _OpaqueText(str): """Represent valid evidence text through an untrusted runtime subclass.""" +class _TaskEvidenceSubclass(TaskEvidence): + """Represent a valid task through an untrusted runtime subclass.""" + + +class _KSAORequirementSubclass(KSAORequirement): + """Represent a valid KSAO requirement through an untrusted runtime subclass.""" + + class _MutableOffset(tzinfo): """Expose timezone state that can change after evidence construction.""" @@ -266,6 +274,41 @@ def test_snapshot_rejects_reference_string_subclasses_before_canonicalization() replace(snapshot, reviewed_by_reference=_OpaqueText("keyverse_subject:01JIOPSYCH")) +def test_snapshot_rejects_task_and_ksao_subclasses_before_canonicalization() -> None: + """Reject nested evidence subclasses before their fields reach canonical serialization.""" + snapshot = _snapshot( + effective_from=date(2026, 8, 1), + recorded_at=RECORDED_AT, + reviewed_at=RECORDED_AT, + ) + task = snapshot.tasks[0] + forged_task = _TaskEvidenceSubclass( + tenant_record_id=task.tenant_record_id, + job_record_id=task.job_record_id, + task_record_id=task.task_record_id, + task_statement=task.task_statement, + importance_level=task.importance_level, + difficulty_level=task.difficulty_level, + source=task.source, + ) + with pytest.raises(ValueError, match="tasks must contain TaskEvidence"): + replace(snapshot, tasks=(forged_task,)) + + ksao = snapshot.ksao_requirements[0] + forged_ksao = _KSAORequirementSubclass( + tenant_record_id=ksao.tenant_record_id, + job_record_id=ksao.job_record_id, + ksao_record_id=ksao.ksao_record_id, + category_code=ksao.category_code, + requirement_statement=ksao.requirement_statement, + importance_level=ksao.importance_level, + proficiency_level=ksao.proficiency_level, + source=ksao.source, + ) + with pytest.raises(ValueError, match="ksao_requirements must contain KSAORequirement"): + replace(snapshot, ksao_requirements=(forged_ksao,)) + + @pytest.mark.parametrize( ("effective_from", "recorded_at", "reviewed_at"), [ From 9ff8b4e2a7e2853a2761de80e0ae118df82ac492 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:05:27 +0900 Subject: [PATCH 13/52] test(audit): reject post-construction governance mutation --- .../hris-kernel/tests/test_audit_outbox.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_outbox.py b/packages/hris-kernel/tests/test_audit_outbox.py index 9353e50ec..2a47117e6 100644 --- a/packages/hris-kernel/tests/test_audit_outbox.py +++ b/packages/hris-kernel/tests/test_audit_outbox.py @@ -198,3 +198,37 @@ def test_event_rejects_nonopaque_optional_confirmation_reference(): """Free-text confirmation data cannot enter an opaque-reference field.""" with pytest.raises(ValueError, match="opaque reference"): _event(high_impact=False, confirmation_reference="approved by Ada") + + +def test_canonical_export_revalidates_actor_after_low_level_mutation(): + """Canonical evidence must not emit an actor value that bypassed construction validation.""" + event = _event() + object.__setattr__(event, "actor_reference", "Ada Lovelace") + + with pytest.raises(ValueError, match="opaque reference"): + event.canonical_json() + + +def test_canonical_export_revalidates_high_impact_confirmation_after_low_level_mutation(): + """A high-impact event cannot lose its human-confirmation evidence after construction.""" + event = _event() + object.__setattr__(event, "confirmation_reference", None) + + with pytest.raises(ValueError, match="confirmation_reference"): + event.canonical_json() + + +def test_canonical_export_rejects_mutated_event_identity_before_stringification(): + """Canonicalization must validate identity type before executing arbitrary stringification.""" + + class ExecutableIdentifier: + """Fail if canonicalization stringifies this untrusted replacement identity.""" + + def __str__(self) -> str: + raise AssertionError("untrusted identity stringification executed") + + event = _event() + object.__setattr__(event, "event_id", ExecutableIdentifier()) + + with pytest.raises(ValueError, match="event_id must be a UUID"): + event.canonical_json() From b95ff0a419c847013209f91843ab1f1ac648fa0f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:08:04 +0900 Subject: [PATCH 14/52] fix(audit): revalidate captured canonical evidence --- .../src/orgmetra_hris_kernel/audit.py | 193 ++++++++++++------ 1 file changed, 134 insertions(+), 59 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 2f2df38da..bda898731 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -57,6 +57,82 @@ def _canonical_timestamp(value: datetime) -> str: return value.isoformat().replace("+00:00", "Z") +def _validate_event_snapshot( + *, + event_id: object, + tenant_record_id: object, + source_service: object, + event_type: object, + resource_reference: object, + actor_reference: object, + purpose_code: object, + reason_code: object, + evidence_version_code: object, + result_code: object, + occurred_at: object, + high_impact: object, + confirmation_reference: object, +) -> datetime: + """Validate one captured audit snapshot and return its detached UTC instant.""" + if type(event_id) is not UUID: + raise ValueError("event_id must be a UUID.") + if type(tenant_record_id) is not UUID: + raise ValueError("tenant_record_id must be a UUID.") + if event_id.int == 0: + raise ValueError("event_id must not be the reserved nil UUID.") + if tenant_record_id.int == 0: + raise ValueError("tenant_record_id must not be the reserved nil UUID.") + if event_id.int == _MAX_UUID_INT: + raise ValueError("event_id must not be the reserved max UUID.") + if tenant_record_id.int == _MAX_UUID_INT: + raise ValueError("tenant_record_id must not be the reserved max UUID.") + if type(occurred_at) is not datetime: + raise ValueError("occurred_at must be a datetime.") + if type(high_impact) is not bool: + raise ValueError("high_impact must be a boolean.") + text_values = { + "source_service": source_service, + "event_type": event_type, + "resource_reference": resource_reference, + "actor_reference": actor_reference, + "purpose_code": purpose_code, + "reason_code": reason_code, + "evidence_version_code": evidence_version_code, + "result_code": result_code, + } + for field_name in _ALL_REQUIRED_TEXT_FIELDS: + if type(text_values[field_name]) is not str: + raise ValueError(f"{field_name} must be a string.") + if confirmation_reference is not None and type(confirmation_reference) is not str: + raise ValueError("confirmation_reference must be a string when supplied.") + + frozen_occurred_at = _freeze_timestamp(occurred_at) + if _SOURCE_SERVICE_PATTERN.fullmatch(source_service) is None: + raise ValueError("source_service must contain two or more lower snake_case words.") + if _EVENT_TYPE_PATTERN.fullmatch(event_type) is None: + raise ValueError("event_type must use a canonical lower-case orgmetra.. namespace.") + for field_name in _REQUIRED_TEXT_FIELDS: + value = text_values[field_name] + if not value.strip(): + raise ValueError(f"{field_name} must not be blank.") + for field_name in ("resource_reference", "actor_reference"): + if _OPAQUE_REFERENCE_PATTERN.fullmatch(text_values[field_name]) is None: + raise ValueError(f"{field_name} must be a namespaced opaque reference.") + for field_name in ("purpose_code", "reason_code", "result_code"): + if _CODE_PATTERN.fullmatch(text_values[field_name]) is None: + raise ValueError(f"{field_name} must be lower snake_case code data.") + if _VERSION_CODE_PATTERN.fullmatch(evidence_version_code) is None: + raise ValueError("evidence_version_code must be a whitespace-free version token.") + if confirmation_reference is not None: + if not confirmation_reference.strip(): + raise ValueError("confirmation_reference must not be blank when supplied.") + if _OPAQUE_REFERENCE_PATTERN.fullmatch(confirmation_reference) is None: + raise ValueError("confirmation_reference must be a namespaced opaque reference.") + if high_impact and confirmation_reference is None: + raise ValueError("high-impact events require confirmation_reference.") + return frozen_occurred_at + + @dataclass(frozen=True, slots=True) class AuditOutboxEvent: """One immutable governance envelope for an Orgmetra domain mutation. @@ -84,51 +160,22 @@ class AuditOutboxEvent: def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" - if type(self.event_id) is not UUID: - raise ValueError("event_id must be a UUID.") - if type(self.tenant_record_id) is not UUID: - raise ValueError("tenant_record_id must be a UUID.") - if self.event_id.int == 0: - raise ValueError("event_id must not be the reserved nil UUID.") - if self.tenant_record_id.int == 0: - raise ValueError("tenant_record_id must not be the reserved nil UUID.") - if self.event_id.int == _MAX_UUID_INT: - raise ValueError("event_id must not be the reserved max UUID.") - if self.tenant_record_id.int == _MAX_UUID_INT: - raise ValueError("tenant_record_id must not be the reserved max UUID.") - if type(self.occurred_at) is not datetime: - raise ValueError("occurred_at must be a datetime.") - if type(self.high_impact) is not bool: - raise ValueError("high_impact must be a boolean.") - for field_name in _ALL_REQUIRED_TEXT_FIELDS: - if type(getattr(self, field_name)) is not str: - raise ValueError(f"{field_name} must be a string.") - if self.confirmation_reference is not None and type(self.confirmation_reference) is not str: - raise ValueError("confirmation_reference must be a string when supplied.") - object.__setattr__(self, "occurred_at", _freeze_timestamp(self.occurred_at)) - if _SOURCE_SERVICE_PATTERN.fullmatch(self.source_service) is None: - raise ValueError("source_service must contain two or more lower snake_case words.") - if _EVENT_TYPE_PATTERN.fullmatch(self.event_type) is None: - raise ValueError("event_type must use a canonical lower-case orgmetra.. namespace.") - for field_name in _REQUIRED_TEXT_FIELDS: - value = getattr(self, field_name) - if not value.strip(): - raise ValueError(f"{field_name} must not be blank.") - for field_name in ("resource_reference", "actor_reference"): - if _OPAQUE_REFERENCE_PATTERN.fullmatch(getattr(self, field_name)) is None: - raise ValueError(f"{field_name} must be a namespaced opaque reference.") - for field_name in ("purpose_code", "reason_code", "result_code"): - if _CODE_PATTERN.fullmatch(getattr(self, field_name)) is None: - raise ValueError(f"{field_name} must be lower snake_case code data.") - if _VERSION_CODE_PATTERN.fullmatch(self.evidence_version_code) is None: - raise ValueError("evidence_version_code must be a whitespace-free version token.") - if self.confirmation_reference is not None: - if not self.confirmation_reference.strip(): - raise ValueError("confirmation_reference must not be blank when supplied.") - if _OPAQUE_REFERENCE_PATTERN.fullmatch(self.confirmation_reference) is None: - raise ValueError("confirmation_reference must be a namespaced opaque reference.") - if self.high_impact and self.confirmation_reference is None: - raise ValueError("high-impact events require confirmation_reference.") + frozen_occurred_at = _validate_event_snapshot( + event_id=self.event_id, + tenant_record_id=self.tenant_record_id, + source_service=self.source_service, + event_type=self.event_type, + resource_reference=self.resource_reference, + actor_reference=self.actor_reference, + purpose_code=self.purpose_code, + reason_code=self.reason_code, + evidence_version_code=self.evidence_version_code, + result_code=self.result_code, + occurred_at=self.occurred_at, + high_impact=self.high_impact, + confirmation_reference=self.confirmation_reference, + ) + object.__setattr__(self, "occurred_at", frozen_occurred_at) def to_cloudevent(self) -> dict[str, object]: """Return the canonical structured CloudEvent 1.0 envelope. @@ -138,26 +185,54 @@ def to_cloudevent(self) -> dict[str, object]: PII-minimized result body. Persist this mapping atomically with the owning business write before asynchronous delivery. """ + event_id = self.event_id + tenant_record_id = self.tenant_record_id + source_service = self.source_service + event_type = self.event_type + resource_reference = self.resource_reference + actor_reference = self.actor_reference + purpose_code = self.purpose_code + reason_code = self.reason_code + evidence_version_code = self.evidence_version_code + result_code = self.result_code + occurred_at = self.occurred_at + high_impact = self.high_impact + confirmation_reference = self.confirmation_reference + occurred_at = _validate_event_snapshot( + event_id=event_id, + tenant_record_id=tenant_record_id, + source_service=source_service, + event_type=event_type, + resource_reference=resource_reference, + actor_reference=actor_reference, + purpose_code=purpose_code, + reason_code=reason_code, + evidence_version_code=evidence_version_code, + result_code=result_code, + occurred_at=occurred_at, + high_impact=high_impact, + confirmation_reference=confirmation_reference, + ) envelope: dict[str, object] = { "specversion": "1.0", - "id": str(self.event_id), - "source": f"urn:orgmetra:{self.source_service}", - "type": self.event_type, - "subject": self.resource_reference, - "time": _canonical_timestamp(self.occurred_at), + "id": str(event_id), + "source": f"urn:orgmetra:{source_service}", + "type": event_type, + "subject": resource_reference, + "time": _canonical_timestamp(occurred_at), "datacontenttype": "application/json", - "orgmetratenant": str(self.tenant_record_id), - "orgmetraactor": self.actor_reference, - "orgmetrapurpose": self.purpose_code, - "orgmetrareason": self.reason_code, - "orgmetraevidence": self.evidence_version_code, + "orgmetratenant": str(tenant_record_id), + "orgmetraactor": actor_reference, + "orgmetrapurpose": purpose_code, + "orgmetrareason": reason_code, + "orgmetraevidence": evidence_version_code, "data": { - "result_code": self.result_code, - "high_impact": self.high_impact, + "result_code": result_code, + "high_impact": high_impact, }, } - if self.confirmation_reference is not None: - envelope["orgmetraconfirmation"] = self.confirmation_reference + if confirmation_reference is not None: + envelope["orgmetraconfirmation"] = confirmation_reference return envelope def canonical_json(self) -> str: From a36e1b6db25d104d48c90789ef6d0c741186b011 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:11:25 +0900 Subject: [PATCH 15/52] chore(manifest): reseal audit runtime-integrity evidence --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index b01705c8f..029ac6b8f 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"4f6327d5c8ce07d7ec15f715aa4cdf8c17f7d16138130a5687b3df0e306386c0","bytes":8659,"lines":179},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"539ca5b5486fffe3f7981b519b75b4aa5aaf7fde558c95df519fd5e9d24eb4e4","bytes":11126,"lines":254},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file From c088d97f6875b6dfcae03f34229aed6aa4587dd2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:13:19 +0900 Subject: [PATCH 16/52] fix(audit): preserve detached-time fail-closed canonicalization --- .../hris-kernel/src/orgmetra_hris_kernel/audit.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index bda898731..12e0397ef 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -72,8 +72,8 @@ def _validate_event_snapshot( occurred_at: object, high_impact: object, confirmation_reference: object, -) -> datetime: - """Validate one captured audit snapshot and return its detached UTC instant.""" +) -> None: + """Validate one captured audit snapshot without rereading live event fields.""" if type(event_id) is not UUID: raise ValueError("event_id must be a UUID.") if type(tenant_record_id) is not UUID: @@ -106,7 +106,6 @@ def _validate_event_snapshot( if confirmation_reference is not None and type(confirmation_reference) is not str: raise ValueError("confirmation_reference must be a string when supplied.") - frozen_occurred_at = _freeze_timestamp(occurred_at) if _SOURCE_SERVICE_PATTERN.fullmatch(source_service) is None: raise ValueError("source_service must contain two or more lower snake_case words.") if _EVENT_TYPE_PATTERN.fullmatch(event_type) is None: @@ -130,7 +129,6 @@ def _validate_event_snapshot( raise ValueError("confirmation_reference must be a namespaced opaque reference.") if high_impact and confirmation_reference is None: raise ValueError("high-impact events require confirmation_reference.") - return frozen_occurred_at @dataclass(frozen=True, slots=True) @@ -160,7 +158,7 @@ class AuditOutboxEvent: def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" - frozen_occurred_at = _validate_event_snapshot( + _validate_event_snapshot( event_id=self.event_id, tenant_record_id=self.tenant_record_id, source_service=self.source_service, @@ -175,7 +173,7 @@ def __post_init__(self) -> None: high_impact=self.high_impact, confirmation_reference=self.confirmation_reference, ) - object.__setattr__(self, "occurred_at", frozen_occurred_at) + object.__setattr__(self, "occurred_at", _freeze_timestamp(self.occurred_at)) def to_cloudevent(self) -> dict[str, object]: """Return the canonical structured CloudEvent 1.0 envelope. @@ -198,7 +196,7 @@ def to_cloudevent(self) -> dict[str, object]: occurred_at = self.occurred_at high_impact = self.high_impact confirmation_reference = self.confirmation_reference - occurred_at = _validate_event_snapshot( + _validate_event_snapshot( event_id=event_id, tenant_record_id=tenant_record_id, source_service=source_service, @@ -213,13 +211,14 @@ def to_cloudevent(self) -> dict[str, object]: high_impact=high_impact, confirmation_reference=confirmation_reference, ) + canonical_time = _canonical_timestamp(occurred_at) envelope: dict[str, object] = { "specversion": "1.0", "id": str(event_id), "source": f"urn:orgmetra:{source_service}", "type": event_type, "subject": resource_reference, - "time": _canonical_timestamp(occurred_at), + "time": canonical_time, "datacontenttype": "application/json", "orgmetratenant": str(tenant_record_id), "orgmetraactor": actor_reference, From 08a9cadb888bf363ab55a78cc2cbffb2bcbb9296 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:14:23 +0900 Subject: [PATCH 17/52] chore(manifest): reseal corrected audit canonicalization --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 029ac6b8f..d014e4f27 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"539ca5b5486fffe3f7981b519b75b4aa5aaf7fde558c95df519fd5e9d24eb4e4","bytes":11126,"lines":254},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"952d192c162b8e686fce913e86feb2f9566358d36bb9079c1335f7d3799b4921","bytes":11058,"lines":253},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file From 24ff2a1fd6777963a63e26e25c277e2b15a78624 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:30:39 +0900 Subject: [PATCH 18/52] test(audit): reject valid canonical evidence reissuance --- .../test_audit_creation_identity_integrity.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/hris-kernel/tests/test_audit_creation_identity_integrity.py diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py new file mode 100644 index 000000000..088d624a5 --- /dev/null +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -0,0 +1,51 @@ +"""Regression coverage for creation-bound audit canonical evidence.""" + +from datetime import datetime, timezone +from uuid import UUID + +import pytest + +from orgmetra_hris_kernel.audit import AuditOutboxEvent + + +def _event() -> AuditOutboxEvent: + """Build one valid high-impact audit event with stable opaque evidence.""" + return AuditOutboxEvent( + event_id=UUID("00000000-0000-4000-8000-000000000002"), + tenant_record_id=UUID("00000000-0000-4000-8000-000000000001"), + source_service="people_core", + event_type="orgmetra.people.assignment.recorded", + resource_reference="assignment_record:01JTESTOPAQUE", + actor_reference="keyverse_subject:01JACTOROPAQUE", + purpose_code="workforce_administration", + reason_code="hire_completion", + evidence_version_code="employment-offer:v3", + result_code="recorded", + occurred_at=datetime(2026, 8, 17, 1, 30, tzinfo=timezone.utc), + high_impact=True, + confirmation_reference="confirmation:01JCONFIRMOPAQUE", + ) + + +@pytest.mark.parametrize( + ("field_name", "replacement"), + [ + ("actor_reference", "keyverse_subject:01JOTHERACTOR"), + ("reason_code", "manager_transfer"), + ("result_code", "updated"), + ("confirmation_reference", "confirmation:01JOTHERCONFIRM"), + ], +) +def test_valid_post_construction_replacement_cannot_reissue_canonical_evidence( + field_name: str, + replacement: str, +) -> None: + """A live event cannot mint a second valid canonical truth after issuance.""" + event = _event() + original = event.canonical_json() + object.__setattr__(event, field_name, replacement) + + with pytest.raises(ValueError, match="creation-time audit evidence"): + event.canonical_json() + + assert replacement not in original From 970bae7258c51b8da5db500a7546c1d480fda0db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:31:55 +0900 Subject: [PATCH 19/52] fix(audit): bind canonical export to creation evidence --- .../src/orgmetra_hris_kernel/audit.py | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 12e0397ef..b3488de6b 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -10,7 +10,7 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import datetime, timedelta, timezone from hashlib import sha256 import json @@ -32,6 +32,7 @@ "result_code", ) _ALL_REQUIRED_TEXT_FIELDS = ("source_service", "event_type", *_REQUIRED_TEXT_FIELDS) +_EVENT_SNAPSHOT_FIELD_COUNT = 13 def _freeze_timestamp(value: datetime) -> datetime: @@ -131,6 +132,63 @@ def _validate_event_snapshot( raise ValueError("high-impact events require confirmation_reference.") +def _event_snapshot( + *, + event_id: object, + tenant_record_id: object, + source_service: object, + event_type: object, + resource_reference: object, + actor_reference: object, + purpose_code: object, + reason_code: object, + evidence_version_code: object, + result_code: object, + occurred_at: object, + high_impact: object, + confirmation_reference: object, +) -> tuple[object, ...]: + """Capture one immutable tuple of already-read audit evidence values.""" + return ( + event_id, + tenant_record_id, + source_service, + event_type, + resource_reference, + actor_reference, + purpose_code, + reason_code, + evidence_version_code, + result_code, + occurred_at, + high_impact, + confirmation_reference, + ) + + +def _validate_creation_snapshot(snapshot: object) -> tuple[object, ...]: + """Validate the private creation snapshot before comparing it with live evidence.""" + if type(snapshot) is not tuple or len(snapshot) != _EVENT_SNAPSHOT_FIELD_COUNT: + raise ValueError("creation-time audit evidence is unavailable.") + _validate_event_snapshot( + event_id=snapshot[0], + tenant_record_id=snapshot[1], + source_service=snapshot[2], + event_type=snapshot[3], + resource_reference=snapshot[4], + actor_reference=snapshot[5], + purpose_code=snapshot[6], + reason_code=snapshot[7], + evidence_version_code=snapshot[8], + result_code=snapshot[9], + occurred_at=snapshot[10], + high_impact=snapshot[11], + confirmation_reference=snapshot[12], + ) + _canonical_timestamp(snapshot[10]) + return snapshot + + @dataclass(frozen=True, slots=True) class AuditOutboxEvent: """One immutable governance envelope for an Orgmetra domain mutation. @@ -155,6 +213,7 @@ class AuditOutboxEvent: occurred_at: datetime high_impact: bool confirmation_reference: str | None = None + _creation_snapshot: tuple[object, ...] = field(init=False, repr=False, compare=False) def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" @@ -173,7 +232,27 @@ def __post_init__(self) -> None: high_impact=self.high_impact, confirmation_reference=self.confirmation_reference, ) - object.__setattr__(self, "occurred_at", _freeze_timestamp(self.occurred_at)) + frozen_occurred_at = _freeze_timestamp(self.occurred_at) + object.__setattr__(self, "occurred_at", frozen_occurred_at) + object.__setattr__( + self, + "_creation_snapshot", + _event_snapshot( + event_id=self.event_id, + tenant_record_id=self.tenant_record_id, + source_service=self.source_service, + event_type=self.event_type, + resource_reference=self.resource_reference, + actor_reference=self.actor_reference, + purpose_code=self.purpose_code, + reason_code=self.reason_code, + evidence_version_code=self.evidence_version_code, + result_code=self.result_code, + occurred_at=frozen_occurred_at, + high_impact=self.high_impact, + confirmation_reference=self.confirmation_reference, + ), + ) def to_cloudevent(self) -> dict[str, object]: """Return the canonical structured CloudEvent 1.0 envelope. @@ -211,6 +290,24 @@ def to_cloudevent(self) -> dict[str, object]: high_impact=high_impact, confirmation_reference=confirmation_reference, ) + current_snapshot = _event_snapshot( + event_id=event_id, + tenant_record_id=tenant_record_id, + source_service=source_service, + event_type=event_type, + resource_reference=resource_reference, + actor_reference=actor_reference, + purpose_code=purpose_code, + reason_code=reason_code, + evidence_version_code=evidence_version_code, + result_code=result_code, + occurred_at=occurred_at, + high_impact=high_impact, + confirmation_reference=confirmation_reference, + ) + creation_snapshot = _validate_creation_snapshot(self._creation_snapshot) + if current_snapshot != creation_snapshot: + raise ValueError("canonical audit evidence no longer matches creation-time audit evidence.") canonical_time = _canonical_timestamp(occurred_at) envelope: dict[str, object] = { "specversion": "1.0", From 7f6a2ab4725c37b983d90dd57634794b16fdc044 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:35:49 +0900 Subject: [PATCH 20/52] chore(manifest): seal audit creation identity repair --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index d014e4f27..18b225191 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"952d192c162b8e686fce913e86feb2f9566358d36bb9079c1335f7d3799b4921","bytes":11058,"lines":253},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"165316e91c27c22bc8011f5c0107ae96717aadb2a0e3c8ca9ec2016ca67aeaf8","bytes":14597,"lines":350},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file From cd25ace14b7d7825cbaba05b06816af34c9c735c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:38:18 +0900 Subject: [PATCH 21/52] fix(audit): keep issuance seal outside mutable event slots --- .../src/orgmetra_hris_kernel/audit.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index b3488de6b..e3a6a6c62 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -10,12 +10,13 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime, timedelta, timezone from hashlib import sha256 import json import re from uuid import UUID +from weakref import finalize _SOURCE_SERVICE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _EVENT_TYPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") @@ -33,6 +34,7 @@ ) _ALL_REQUIRED_TEXT_FIELDS = ("source_service", "event_type", *_REQUIRED_TEXT_FIELDS) _EVENT_SNAPSHOT_FIELD_COUNT = 13 +_AUDIT_CREATION_SNAPSHOTS: dict[int, tuple[object, ...]] = {} def _freeze_timestamp(value: datetime) -> datetime: @@ -189,7 +191,7 @@ def _validate_creation_snapshot(snapshot: object) -> tuple[object, ...]: return snapshot -@dataclass(frozen=True, slots=True) +@dataclass(frozen=True, slots=True, weakref_slot=True) class AuditOutboxEvent: """One immutable governance envelope for an Orgmetra domain mutation. @@ -213,7 +215,6 @@ class AuditOutboxEvent: occurred_at: datetime high_impact: bool confirmation_reference: str | None = None - _creation_snapshot: tuple[object, ...] = field(init=False, repr=False, compare=False) def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" @@ -234,25 +235,23 @@ def __post_init__(self) -> None: ) frozen_occurred_at = _freeze_timestamp(self.occurred_at) object.__setattr__(self, "occurred_at", frozen_occurred_at) - object.__setattr__( - self, - "_creation_snapshot", - _event_snapshot( - event_id=self.event_id, - tenant_record_id=self.tenant_record_id, - source_service=self.source_service, - event_type=self.event_type, - resource_reference=self.resource_reference, - actor_reference=self.actor_reference, - purpose_code=self.purpose_code, - reason_code=self.reason_code, - evidence_version_code=self.evidence_version_code, - result_code=self.result_code, - occurred_at=frozen_occurred_at, - high_impact=self.high_impact, - confirmation_reference=self.confirmation_reference, - ), + event_identity = id(self) + _AUDIT_CREATION_SNAPSHOTS[event_identity] = _event_snapshot( + event_id=self.event_id, + tenant_record_id=self.tenant_record_id, + source_service=self.source_service, + event_type=self.event_type, + resource_reference=self.resource_reference, + actor_reference=self.actor_reference, + purpose_code=self.purpose_code, + reason_code=self.reason_code, + evidence_version_code=self.evidence_version_code, + result_code=self.result_code, + occurred_at=frozen_occurred_at, + high_impact=self.high_impact, + confirmation_reference=self.confirmation_reference, ) + finalize(self, _AUDIT_CREATION_SNAPSHOTS.pop, event_identity, None) def to_cloudevent(self) -> dict[str, object]: """Return the canonical structured CloudEvent 1.0 envelope. @@ -305,7 +304,10 @@ def to_cloudevent(self) -> dict[str, object]: high_impact=high_impact, confirmation_reference=confirmation_reference, ) - creation_snapshot = _validate_creation_snapshot(self._creation_snapshot) + creation_snapshot = _AUDIT_CREATION_SNAPSHOTS.get(id(self)) + if creation_snapshot is None: + raise ValueError("creation-time audit evidence is unavailable.") + creation_snapshot = _validate_creation_snapshot(creation_snapshot) if current_snapshot != creation_snapshot: raise ValueError("canonical audit evidence no longer matches creation-time audit evidence.") canonical_time = _canonical_timestamp(occurred_at) From fd18a83ef58d9e3d643b19a989975193ec93398f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:39:22 +0900 Subject: [PATCH 22/52] chore(manifest): reseal external audit issuance proof --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 18b225191..fb644eae7 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"165316e91c27c22bc8011f5c0107ae96717aadb2a0e3c8ca9ec2016ca67aeaf8","bytes":14597,"lines":350},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file +{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"ea55eb0388d91482273ac87cfacfba0b75502de9fd8e43e442b67366058c5f64","bytes":14790,"lines":352},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file From 72ec4ecdd6c4510e81819740cb661f451b835d67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:43:20 +0900 Subject: [PATCH 23/52] test(audit): cover issuance registry failure modes --- .../test_audit_creation_identity_integrity.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index 088d624a5..49d5f36dd 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -5,6 +5,7 @@ import pytest +from orgmetra_hris_kernel import audit as audit_module from orgmetra_hris_kernel.audit import AuditOutboxEvent @@ -49,3 +50,31 @@ def test_valid_post_construction_replacement_cannot_reissue_canonical_evidence( event.canonical_json() assert replacement not in original + + +def test_event_has_no_mutable_instance_slot_for_creation_seal() -> None: + """Low-level event mutation cannot rewrite the module-owned issuance proof.""" + event = _event() + + assert not hasattr(event, "_creation_snapshot") + with pytest.raises(AttributeError): + object.__setattr__(event, "_creation_snapshot", ()) + + +def test_canonical_export_fails_closed_when_issuance_proof_is_missing() -> None: + """Missing process-local issuance evidence cannot silently mint a canonical event.""" + event = _event() + audit_module._AUDIT_CREATION_SNAPSHOTS.pop(id(event)) + + with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): + event.canonical_json() + + +@pytest.mark.parametrize("corrupt_snapshot", [[], tuple(range(12))]) +def test_canonical_export_rejects_malformed_issuance_proof(corrupt_snapshot: object) -> None: + """Malformed module-owned issuance state fails closed before evidence comparison.""" + event = _event() + audit_module._AUDIT_CREATION_SNAPSHOTS[id(event)] = corrupt_snapshot # type: ignore[assignment] + + with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): + event.canonical_json() From 6d4dabf793e30c90252b195b950b54fb2141df28 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 23:14:19 +0900 Subject: [PATCH 24/52] merge(core): preserve #161 changelog delta after restack --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 976e85ee9..06553f5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ All notable changes to Orgmetra will be documented in this file. - `employment_record_version.employment_concurrency_code` constrained to `exclusive` or `concurrent`. - ADR 0005 for exclusive employment and staffable seats. - `orgmetra_hris_kernel` 0.3.0 with identity-scoped bitemporal resolution, assignment-employment coverage, allocation-portfolio checks, and a Memorial Hospital RN correction case at 100% statement and branch coverage. -- `employment_record_version` and `position_record_version` so employment and position identity stay stable across retroactive corrections. +- `employment_record_version` and `position_record_version` so corrections no longer mint a new employment or position identifier. - `assignment_record.employment_record_id` bound to the same person as the covering employment. - `orgmetra_keyverse_adapter` that binds an opaque Keyverse subject to a person and rejects passwords, passkeys, and tokens. - Design tokens for the repeating HR actions: approve, review, correct, request evidence, compare, export, and escalate. @@ -38,6 +38,7 @@ All notable changes to Orgmetra will be documented in this file. ### Changed +- Consolidated repository-owned PR validation from twelve workflows into one Foundation CI job, while keeping the dual-cluster recovery rehearsal separately path-scoped. Central required review and security workflows remain organization-owned. - New predictive-validity membership must use one normalized worker-level case; the three independent validity-study decision/evidence/outcome link relations are historical read surfaces only and can no longer accept new rows. A case insert also rejects a criterion observation whose recorded interval is already closed at `linked_at`. - Canonicalized service identifiers as two-or-more-word `snake_case` across architecture, deployment, ACL, metrics, and client contracts. - Separated fast-mlsirm, TEPP, and Psychometrics Commons into immutable external scientific contracts. From b9296614bd687b15a9fa1580099c5e797af52515 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 23:17:47 +0900 Subject: [PATCH 25/52] fix(ci): reseal shared-kernel manifest after protected restack --- manifest.json | 476 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 475 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index fb644eae7..8ab61a4f6 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1,475 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"db305ff43199426b006694b6b1d1272fea78aaaab807303fc8a569b35ab5bcdb","bytes":17526,"lines":77},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"ea55eb0388d91482273ac87cfacfba0b75502de9fd8e43e442b67366058c5f64","bytes":14790,"lines":352},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100","bytes":8886,"lines":234},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} \ No newline at end of file +{ + "package": "orgmetra-foundation-pack", + "version": "0.1.0", + "generated_for_branch": "feat/audit-outbox-envelope", + "files": [ + { + "path": ".github/workflows/foundation-ci.yml", + "sha256": "b6a4365936b66803a8112f034c77d53d33301a7a798ed4f68746a4f2d8b081d7", + "bytes": 6651, + "lines": 125 + }, + { + "path": ".gitignore", + "sha256": "145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21", + "bytes": 375, + "lines": 37 + }, + { + "path": "AGENTS.md", + "sha256": "28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16", + "bytes": 2246, + "lines": 34 + }, + { + "path": "ARCHITECTURE.md", + "sha256": "52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850", + "bytes": 7864, + "lines": 107 + }, + { + "path": "CHANGELOG.md", + "sha256": "791d0d29d9b27223e86331d91dd0743b48761818b76db0468caa7e90b812ec10", + "bytes": 17761, + "lines": 78 + }, + { + "path": "CLAUDE.md", + "sha256": "add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f", + "bytes": 1229, + "lines": 20 + }, + { + "path": "LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "bytes": 11358, + "lines": 202 + }, + { + "path": "NOTICE", + "sha256": "34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042", + "bytes": 305, + "lines": 4 + }, + { + "path": "README.md", + "sha256": "1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6", + "bytes": 3785, + "lines": 81 + }, + { + "path": "database/migrations/0001_foundation_schema.sql", + "sha256": "ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd", + "bytes": 38747, + "lines": 916 + }, + { + "path": "database/migrations/0002_sealed_evidence_digest.sql", + "sha256": "93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c", + "bytes": 6649, + "lines": 202 + }, + { + "path": "database/migrations/0003_audit_outbox_persistence.sql", + "sha256": "2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc", + "bytes": 15417, + "lines": 423 + }, + { + "path": "database/migrations/0004_outbox_delivery_claim.sql", + "sha256": "d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef", + "bytes": 9451, + "lines": 234 + }, + { + "path": "database/migrations/0005_outbox_delivery_finalization.sql", + "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961", + "bytes": 6125, + "lines": 170 + }, + { + "path": "database/migrations/0006_outbox_delivery_dead_letter.sql", + "sha256": "c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7", + "bytes": 24919, + "lines": 628 + }, + { + "path": "database/migrations/0007_outbox_retry_exhaustion.sql", + "sha256": "812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5", + "bytes": 19081, + "lines": 476 + }, + { + "path": "database/migrations/0008_audit_outbox_review_hardening.sql", + "sha256": "c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b", + "bytes": 17562, + "lines": 448 + }, + { + "path": "database/migrations/0009_candidate_worker_conversion_governance.sql", + "sha256": "4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9", + "bytes": 11537, + "lines": 281 + }, + { + "path": "database/migrations/0010_validity_study_case_integrity.sql", + "sha256": "3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1", + "bytes": 11979, + "lines": 313 + }, + { + "path": "database/migrations/0011_criterion_observation_scope.sql", + "sha256": "f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9", + "bytes": 7444, + "lines": 165 + }, + { + "path": "database/migrations/0012_people_mutation_idempotency.sql", + "sha256": "52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69", + "bytes": 3162, + "lines": 76 + }, + { + "path": "database/migrations/0013_job_analysis_snapshot.sql", + "sha256": "b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee", + "bytes": 12713, + "lines": 260 + }, + { + "path": "docs/API_CONTRACT.md", + "sha256": "63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589", + "bytes": 4555, + "lines": 76 + }, + { + "path": "docs/DATA_MODEL.md", + "sha256": "6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a", + "bytes": 13366, + "lines": 85 + }, + { + "path": "docs/ERD.md", + "sha256": "546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe", + "bytes": 6964, + "lines": 70 + }, + { + "path": "docs/OPERABILITY.md", + "sha256": "82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62", + "bytes": 11189, + "lines": 71 + }, + { + "path": "docs/PRD.md", + "sha256": "3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1", + "bytes": 5490, + "lines": 111 + }, + { + "path": "docs/SECURITY.md", + "sha256": "01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac", + "bytes": 11185, + "lines": 64 + }, + { + "path": "docs/STORYBOARD.md", + "sha256": "6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2", + "bytes": 1342, + "lines": 28 + }, + { + "path": "docs/STORYBOOK.md", + "sha256": "82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9", + "bytes": 1389, + "lines": 50 + }, + { + "path": "docs/TEST_STRATEGY.md", + "sha256": "d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8", + "bytes": 16534, + "lines": 135 + }, + { + "path": "docs/THREAT_MODEL.md", + "sha256": "f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252", + "bytes": 6736, + "lines": 23 + }, + { + "path": "docs/TRACEABILITY.md", + "sha256": "dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e", + "bytes": 11462, + "lines": 40 + }, + { + "path": "docs/TRD.md", + "sha256": "23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077", + "bytes": 9064, + "lines": 101 + }, + { + "path": "docs/UML.md", + "sha256": "fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9", + "bytes": 5528, + "lines": 122 + }, + { + "path": "docs/USER_STORIES.md", + "sha256": "5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f", + "bytes": 2670, + "lines": 37 + }, + { + "path": "docs/WIREFRAMES.md", + "sha256": "b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e", + "bytes": 2005, + "lines": 77 + }, + { + "path": "docs/adr/0001-orgmetra-authoritative-hris-record.md", + "sha256": "0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572", + "bytes": 6108, + "lines": 53 + }, + { + "path": "docs/adr/0002-federated-cwl-integration-boundaries.md", + "sha256": "b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2", + "bytes": 4072, + "lines": 44 + }, + { + "path": "docs/adr/0003-bitemporal-hris-data-contract.md", + "sha256": "d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799", + "bytes": 4453, + "lines": 47 + }, + { + "path": "docs/adr/0004-employment-position-version-and-assignment-binding.md", + "sha256": "fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182", + "bytes": 1872, + "lines": 30 + }, + { + "path": "docs/adr/0005-exclusive-employment-and-staffable-seats.md", + "sha256": "10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b", + "bytes": 2091, + "lines": 34 + }, + { + "path": "docs/adr/0006-governed-audit-outbox-envelope.md", + "sha256": "827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd", + "bytes": 14100, + "lines": 66 + }, + { + "path": "docs/adr/0007-governed-job-analysis-evidence.md", + "sha256": "953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52", + "bytes": 5653, + "lines": 57 + }, + { + "path": "docs/adr/0008-purpose-bound-pii-authorization.md", + "sha256": "c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7", + "bytes": 5988, + "lines": 55 + }, + { + "path": "docs/adr/0009-performance-criterion-observation-scope.md", + "sha256": "1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64", + "bytes": 7057, + "lines": 57 + }, + { + "path": "docs/adr/0010-naruon-calendar-intent-boundary.md", + "sha256": "3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9", + "bytes": 3917, + "lines": 35 + }, + { + "path": "docs/adr/0011-bitemporal-workforce-composition.md", + "sha256": "dbe96dfd47066288cec835789de54cc4293f920d2ad4b0e0dba930191d7d249b", + "bytes": 5551, + "lines": 53 + }, + { + "path": "docs/adr/0012-governed-migration-handoff.md", + "sha256": "c7bfbda34996f717ed31f8307acc16a5d69ae464edb184ab5c8ec4b2d5763cbc", + "bytes": 5958, + "lines": 59 + }, + { + "path": "docs/adr/0013-governed-requisition-review-packet.md", + "sha256": "70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802", + "bytes": 4693, + "lines": 46 + }, + { + "path": "docs/adr/0014-job-analysis-snapshot-persistence.md", + "sha256": "a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105", + "bytes": 5365, + "lines": 49 + }, + { + "path": "docs/adr/README.md", + "sha256": "f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002", + "bytes": 1838, + "lines": 18 + }, + { + "path": "docs/doctoring/REFERENCES.md", + "sha256": "929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5", + "bytes": 6352, + "lines": 69 + }, + { + "path": "docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md", + "sha256": "b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd", + "bytes": 8227, + "lines": 226 + }, + { + "path": "docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md", + "sha256": "4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d", + "bytes": 6237, + "lines": 187 + }, + { + "path": "package.json", + "sha256": "59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5", + "bytes": 388, + "lines": 9 + }, + { + "path": "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", + "sha256": "ea55eb0388d91482273ac87cfacfba0b75502de9fd8e43e442b67366058c5f64", + "bytes": 14790, + "lines": 352 + }, + { + "path": "packages/hris-kernel/tests/test_audit_outbox.py", + "sha256": "d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100", + "bytes": 8886, + "lines": 234 + }, + { + "path": "schemas/openapi.yaml", + "sha256": "09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f", + "bytes": 29503, + "lines": 1020 + }, + { + "path": "scripts/foundation-contract-core.mjs", + "sha256": "9b03efbbdffa60a05f5924e8a61b1cbc3cd75c502df428a5920085e8d0bf3603", + "bytes": 28121, + "lines": 688 + }, + { + "path": "scripts/foundation-contract.mjs", + "sha256": "5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a", + "bytes": 218, + "lines": 6 + }, + { + "path": "tests/dispatcher-inventory.test.mjs", + "sha256": "09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261", + "bytes": 1597, + "lines": 34 + }, + { + "path": "tests/foundation-contract.test.mjs", + "sha256": "648533b4aff8cee643df4afc06b463eda788e002e11d043971c8a16804c68501", + "bytes": 14943, + "lines": 387 + }, + { + "path": "tests/openapi-contract.test.mjs", + "sha256": "80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc", + "bytes": 6438, + "lines": 195 + }, + { + "path": "tests/test_audit_outbox_hardening_postgres.sh", + "sha256": "518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0", + "bytes": 13396, + "lines": 333 + }, + { + "path": "tests/test_audit_outbox_postgres.sh", + "sha256": "e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2", + "bytes": 13443, + "lines": 357 + }, + { + "path": "tests/test_bitemporal_postgres.sh", + "sha256": "7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc", + "bytes": 8209, + "lines": 230 + }, + { + "path": "tests/test_candidate_worker_conversion_postgres.sh", + "sha256": "681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90", + "bytes": 14673, + "lines": 344 + }, + { + "path": "tests/test_criterion_observation_scope_postgres.sh", + "sha256": "0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d", + "bytes": 17811, + "lines": 469 + }, + { + "path": "tests/test_evidence_sealing_postgres.sh", + "sha256": "57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7", + "bytes": 11349, + "lines": 370 + }, + { + "path": "tests/test_job_analysis_snapshot_postgres.sh", + "sha256": "ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f", + "bytes": 13542, + "lines": 296 + }, + { + "path": "tests/test_operational_uuid_postgres.sh", + "sha256": "7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7", + "bytes": 3346, + "lines": 101 + }, + { + "path": "tests/test_outbox_claim_postgres.sh", + "sha256": "1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b", + "bytes": 14817, + "lines": 429 + }, + { + "path": "tests/test_outbox_dead_letter_postgres.sh", + "sha256": "0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d", + "bytes": 14008, + "lines": 377 + }, + { + "path": "tests/test_people_mutation_idempotency_postgres.sh", + "sha256": "3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5", + "bytes": 16191, + "lines": 381 + }, + { + "path": "tests/test_tenant_isolation_postgres.sh", + "sha256": "dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a", + "bytes": 15134, + "lines": 388 + }, + { + "path": "tests/test_validity_study_case_postgres.sh", + "sha256": "0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02", + "bytes": 14708, + "lines": 301 + }, + { + "path": "tests/validate_repository.py", + "sha256": "091836b2f68600a30b08f7da2cea8b3bef10201a123da720a7369bf10985eec2", + "bytes": 27237, + "lines": 637 + } + ] +} From f1447a86bf4518d43030a02fd24a693dee1ee64a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:06:53 +0900 Subject: [PATCH 26/52] test(core): reject reintroduced audit timezone before callback --- .../test_audit_runtime_type_integrity.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py index e247872ed..1f37a0ee2 100644 --- a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py +++ b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py @@ -52,6 +52,24 @@ def dst(self, value): # type: ignore[no-untyped-def] return timedelta(0) +class _TripwireOffset(tzinfo): + """Fail if canonicalization executes reintroduced timezone behavior.""" + + def __init__(self) -> None: + self.calls = 0 + + def utcoffset(self, value): # type: ignore[no-untyped-def] + """Record and reject any callback before the exact UTC gate.""" + del value + self.calls += 1 + raise AssertionError("reintroduced timezone callback executed before rejection") + + def dst(self, value): # type: ignore[no-untyped-def] + """Keep daylight saving fixed if unexpectedly queried.""" + del value + return timedelta(0) + + class _ExplodingOffset(tzinfo): """Raise arbitrary provider behavior while an event instant is resolved.""" @@ -167,12 +185,16 @@ def test_audit_event_normalizes_offset_overflow_to_value_error() -> None: def test_audit_event_canonicalization_rejects_reintroduced_timezone_behavior() -> None: - """Fail closed if low-level mutation reintroduces executable timezone behavior.""" + """Fail before callbacks if low-level mutation reintroduces executable timezone behavior.""" event = _event() + tripwire = _TripwireOffset() object.__setattr__( event, "occurred_at", - datetime(2026, 8, 21, 5, 20, tzinfo=_MutableOffset()), + datetime(2026, 8, 21, 5, 20, tzinfo=tripwire), ) + with pytest.raises(ValueError, match="exact timezone-aware datetime"): event.to_cloudevent() + + assert tripwire.calls == 0 From 6f26acdbb19cb8de0457645af88eda746f90bddc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:07:38 +0900 Subject: [PATCH 27/52] fix(core): validate canonical audit timestamp before snapshot comparison --- packages/hris-kernel/src/orgmetra_hris_kernel/audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index e3a6a6c62..fc31b583a 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -289,6 +289,7 @@ def to_cloudevent(self) -> dict[str, object]: high_impact=high_impact, confirmation_reference=confirmation_reference, ) + canonical_time = _canonical_timestamp(occurred_at) current_snapshot = _event_snapshot( event_id=event_id, tenant_record_id=tenant_record_id, @@ -310,7 +311,6 @@ def to_cloudevent(self) -> dict[str, object]: creation_snapshot = _validate_creation_snapshot(creation_snapshot) if current_snapshot != creation_snapshot: raise ValueError("canonical audit evidence no longer matches creation-time audit evidence.") - canonical_time = _canonical_timestamp(occurred_at) envelope: dict[str, object] = { "specversion": "1.0", "id": str(event_id), From ac31f28d6c5c5f13792dead670c962a166e52e1d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:10:43 +0900 Subject: [PATCH 28/52] fix(core): reseal audit runtime manifest after callback guard --- manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 8ab61a4f6..31c1125b6 100644 --- a/manifest.json +++ b/manifest.json @@ -83,7 +83,7 @@ }, { "path": "database/migrations/0005_outbox_delivery_finalization.sql", - "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961", + "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5ef9e8a92abba5c3cf182", "bytes": 6125, "lines": 170 }, @@ -341,7 +341,7 @@ }, { "path": "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", - "sha256": "ea55eb0388d91482273ac87cfacfba0b75502de9fd8e43e442b67366058c5f64", + "sha256": "208f83b9f1c9e96777d20d20cd58fc4a1fa1b3e9b5cc4b36767d611d51c967bf", "bytes": 14790, "lines": 352 }, From 38c3fde3d42cc9c141fa15fdc74779c0e32addfa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:12:34 +0900 Subject: [PATCH 29/52] fix(core): restore unrelated migration manifest digest --- manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 31c1125b6..c3104e8f6 100644 --- a/manifest.json +++ b/manifest.json @@ -41,7 +41,7 @@ }, { "path": "LICENSE", - "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "sha256": "cfc7749b96f63d3130321415ad53233588ff952aabd1a88952b39c71747253572", "bytes": 11358, "lines": 202 }, @@ -83,7 +83,7 @@ }, { "path": "database/migrations/0005_outbox_delivery_finalization.sql", - "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5ef9e8a92abba5c3cf182", + "sha256": "b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961", "bytes": 6125, "lines": 170 }, From 5d7eef3595531232c5ba49009445a734fe1945c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:14:38 +0900 Subject: [PATCH 30/52] fix(core): restore LICENSE manifest digest after reseal repair --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index c3104e8f6..7b7fbb0b9 100644 --- a/manifest.json +++ b/manifest.json @@ -41,7 +41,7 @@ }, { "path": "LICENSE", - "sha256": "cfc7749b96f63d3130321415ad53233588ff952aabd1a88952b39c71747253572", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", "bytes": 11358, "lines": 202 }, From 50a7dbe3cccc6fea8f4aac3d33d2e13ce5bf1ae3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 00:59:31 +0900 Subject: [PATCH 31/52] test(core): prove audit event cannot reseal after issuance --- .../test_audit_creation_identity_integrity.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index 49d5f36dd..ddeb826f8 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -52,6 +52,31 @@ def test_valid_post_construction_replacement_cannot_reissue_canonical_evidence( assert replacement not in original +def test_post_init_reentry_cannot_reseal_valid_mutated_evidence() -> None: + """Re-entering initialization cannot replace the creation-bound audit truth.""" + event = _event() + original_snapshot = audit_module._AUDIT_CREATION_SNAPSHOTS[id(event)] + object.__setattr__(event, "reason_code", "manager_transfer") + + with pytest.raises(ValueError, match="already issued"): + event.__post_init__() + + assert audit_module._AUDIT_CREATION_SNAPSHOTS[id(event)] is original_snapshot + with pytest.raises(ValueError, match="creation-time audit evidence"): + event.canonical_json() + + +def test_missing_creation_snapshot_does_not_restore_issuance_eligibility() -> None: + """Losing the snapshot cannot let one live event issue a replacement snapshot.""" + event = _event() + audit_module._AUDIT_CREATION_SNAPSHOTS.pop(id(event)) + + with pytest.raises(ValueError, match="already issued"): + event.__post_init__() + + assert id(event) not in audit_module._AUDIT_CREATION_SNAPSHOTS + + def test_event_has_no_mutable_instance_slot_for_creation_seal() -> None: """Low-level event mutation cannot rewrite the module-owned issuance proof.""" event = _event() From d076d1fca18af48f35683fa38b3335548d9104c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:01:21 +0900 Subject: [PATCH 32/52] fix(core): make audit issuance identity single-use --- .../src/orgmetra_hris_kernel/audit.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index fc31b583a..e883055bc 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -16,7 +16,7 @@ import json import re from uuid import UUID -from weakref import finalize +from weakref import finalize, ref _SOURCE_SERVICE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _EVENT_TYPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") @@ -35,6 +35,14 @@ _ALL_REQUIRED_TEXT_FIELDS = ("source_service", "event_type", *_REQUIRED_TEXT_FIELDS) _EVENT_SNAPSHOT_FIELD_COUNT = 13 _AUDIT_CREATION_SNAPSHOTS: dict[int, tuple[object, ...]] = {} +_AUDIT_LIVE_ISSUANCES: dict[int, object] = {} + + +def _clear_audit_creation_state(event_identity: int, identity_marker: object) -> None: + """Release process-local issuance evidence only for the exact finished identity.""" + if _AUDIT_LIVE_ISSUANCES.get(event_identity) is identity_marker: + _AUDIT_LIVE_ISSUANCES.pop(event_identity, None) + _AUDIT_CREATION_SNAPSHOTS.pop(event_identity, None) def _freeze_timestamp(value: datetime) -> datetime: @@ -218,6 +226,12 @@ class AuditOutboxEvent: def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" + event_identity = id(self) + identity_marker = ref(self) + registered_marker = _AUDIT_LIVE_ISSUANCES.setdefault(event_identity, identity_marker) + if registered_marker is not identity_marker: + raise ValueError("audit event identity has already issued canonical evidence.") + finalize(self, _clear_audit_creation_state, event_identity, identity_marker) _validate_event_snapshot( event_id=self.event_id, tenant_record_id=self.tenant_record_id, @@ -235,7 +249,6 @@ def __post_init__(self) -> None: ) frozen_occurred_at = _freeze_timestamp(self.occurred_at) object.__setattr__(self, "occurred_at", frozen_occurred_at) - event_identity = id(self) _AUDIT_CREATION_SNAPSHOTS[event_identity] = _event_snapshot( event_id=self.event_id, tenant_record_id=self.tenant_record_id, @@ -251,7 +264,6 @@ def __post_init__(self) -> None: high_impact=self.high_impact, confirmation_reference=self.confirmation_reference, ) - finalize(self, _AUDIT_CREATION_SNAPSHOTS.pop, event_identity, None) def to_cloudevent(self) -> dict[str, object]: """Return the canonical structured CloudEvent 1.0 envelope. From ddc41bede93eab532675b7dc1332f258bc3a100d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:05:48 +0900 Subject: [PATCH 33/52] fix(core): use unique marker for audit issuance identity --- packages/hris-kernel/src/orgmetra_hris_kernel/audit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index e883055bc..2058a1788 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -16,7 +16,7 @@ import json import re from uuid import UUID -from weakref import finalize, ref +from weakref import finalize _SOURCE_SERVICE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _EVENT_TYPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") @@ -227,7 +227,7 @@ class AuditOutboxEvent: def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" event_identity = id(self) - identity_marker = ref(self) + identity_marker = object() registered_marker = _AUDIT_LIVE_ISSUANCES.setdefault(event_identity, identity_marker) if registered_marker is not identity_marker: raise ValueError("audit event identity has already issued canonical evidence.") From 423cf66a537a0d6bc952dc4eba4207046e3e5537 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:09:17 +0900 Subject: [PATCH 34/52] build(core): reseal single-use audit runtime evidence --- manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 7b7fbb0b9..13beeda6c 100644 --- a/manifest.json +++ b/manifest.json @@ -341,9 +341,9 @@ }, { "path": "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", - "sha256": "208f83b9f1c9e96777d20d20cd58fc4a1fa1b3e9b5cc4b36767d611d51c967bf", - "bytes": 14790, - "lines": 352 + "sha256": "e866fb2aab5257adb7b01e799802f901906980a639d2b0a9e346126d1a348ca4", + "bytes": 15481, + "lines": 364 }, { "path": "packages/hris-kernel/tests/test_audit_outbox.py", From 31fabb5764f4fc6e09884da83348eb0fb50ea5c5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:14:54 +0900 Subject: [PATCH 35/52] test(core): cover audit issuance lifetime cleanup --- .../test_audit_creation_identity_integrity.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index ddeb826f8..3b1b2d487 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -1,6 +1,7 @@ """Regression coverage for creation-bound audit canonical evidence.""" from datetime import datetime, timezone +import gc from uuid import UUID import pytest @@ -77,6 +78,39 @@ def test_missing_creation_snapshot_does_not_restore_issuance_eligibility() -> No assert id(event) not in audit_module._AUDIT_CREATION_SNAPSHOTS +def test_event_lifetime_cleanup_releases_both_creation_registries() -> None: + """Object finalization releases the issuance marker and creation snapshot together.""" + event = _event() + event_identity = id(event) + + assert event_identity in audit_module._AUDIT_LIVE_ISSUANCES + assert event_identity in audit_module._AUDIT_CREATION_SNAPSHOTS + + del event + gc.collect() + + assert event_identity not in audit_module._AUDIT_LIVE_ISSUANCES + assert event_identity not in audit_module._AUDIT_CREATION_SNAPSHOTS + + +def test_stale_finalizer_marker_cannot_clear_reused_identity_state() -> None: + """A stale lifetime callback cannot delete evidence registered by a replacement identity.""" + event_identity = -1 + stale_marker = object() + replacement_marker = object() + replacement_snapshot = ("replacement",) + audit_module._AUDIT_LIVE_ISSUANCES[event_identity] = replacement_marker + audit_module._AUDIT_CREATION_SNAPSHOTS[event_identity] = replacement_snapshot # type: ignore[assignment] + try: + audit_module._clear_audit_creation_state(event_identity, stale_marker) + + assert audit_module._AUDIT_LIVE_ISSUANCES[event_identity] is replacement_marker + assert audit_module._AUDIT_CREATION_SNAPSHOTS[event_identity] is replacement_snapshot + finally: + audit_module._AUDIT_LIVE_ISSUANCES.pop(event_identity, None) + audit_module._AUDIT_CREATION_SNAPSHOTS.pop(event_identity, None) + + def test_event_has_no_mutable_instance_slot_for_creation_seal() -> None: """Low-level event mutation cannot rewrite the module-owned issuance proof.""" event = _event() From 701a179e95bba8e921c4bcdec5cfcf4cd310be34 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:32:21 +0900 Subject: [PATCH 36/52] test(audit): hide issuance authority storage from consumers --- .../tests/test_audit_module_state_integrity.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 packages/hris-kernel/tests/test_audit_module_state_integrity.py diff --git a/packages/hris-kernel/tests/test_audit_module_state_integrity.py b/packages/hris-kernel/tests/test_audit_module_state_integrity.py new file mode 100644 index 000000000..d1f05150d --- /dev/null +++ b/packages/hris-kernel/tests/test_audit_module_state_integrity.py @@ -0,0 +1,9 @@ +"""Regressions for module-level audit issuance authority storage.""" + +from orgmetra_hris_kernel import audit as audit_module + + +def test_audit_issuance_backing_registries_are_not_module_mutation_capabilities() -> None: + """Importing the audit module must not expose mutable canonical-issuance storage.""" + assert not hasattr(audit_module, "_AUDIT_LIVE_ISSUANCES") + assert not hasattr(audit_module, "_AUDIT_CREATION_SNAPSHOTS") From 7b6cb6fb66267c5f14d7e163a84f1e57ab5405ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:32:50 +0900 Subject: [PATCH 37/52] test(audit): exercise private issuance runtime without mutable globals --- .../test_audit_creation_identity_integrity.py | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index 3b1b2d487..0b95ececf 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -10,23 +10,36 @@ from orgmetra_hris_kernel.audit import AuditOutboxEvent +def _event_values() -> dict[str, object]: + """Return one valid high-impact audit event payload with stable opaque evidence.""" + return { + "event_id": UUID("00000000-0000-4000-8000-000000000002"), + "tenant_record_id": UUID("00000000-0000-4000-8000-000000000001"), + "source_service": "people_core", + "event_type": "orgmetra.people.assignment.recorded", + "resource_reference": "assignment_record:01JTESTOPAQUE", + "actor_reference": "keyverse_subject:01JACTOROPAQUE", + "purpose_code": "workforce_administration", + "reason_code": "hire_completion", + "evidence_version_code": "employment-offer:v3", + "result_code": "recorded", + "occurred_at": datetime(2026, 8, 17, 1, 30, tzinfo=timezone.utc), + "high_impact": True, + "confirmation_reference": "confirmation:01JCONFIRMOPAQUE", + } + + def _event() -> AuditOutboxEvent: """Build one valid high-impact audit event with stable opaque evidence.""" - return AuditOutboxEvent( - event_id=UUID("00000000-0000-4000-8000-000000000002"), - tenant_record_id=UUID("00000000-0000-4000-8000-000000000001"), - source_service="people_core", - event_type="orgmetra.people.assignment.recorded", - resource_reference="assignment_record:01JTESTOPAQUE", - actor_reference="keyverse_subject:01JACTOROPAQUE", - purpose_code="workforce_administration", - reason_code="hire_completion", - evidence_version_code="employment-offer:v3", - result_code="recorded", - occurred_at=datetime(2026, 8, 17, 1, 30, tzinfo=timezone.utc), - high_impact=True, - confirmation_reference="confirmation:01JCONFIRMOPAQUE", - ) + return AuditOutboxEvent(**_event_values()) # type: ignore[arg-type] + + +def _unissued_event() -> AuditOutboxEvent: + """Populate an exact event object without running its governed issuance hook.""" + event = object.__new__(AuditOutboxEvent) + for field_name, value in _event_values().items(): + object.__setattr__(event, field_name, value) + return event @pytest.mark.parametrize( @@ -56,41 +69,39 @@ def test_valid_post_construction_replacement_cannot_reissue_canonical_evidence( def test_post_init_reentry_cannot_reseal_valid_mutated_evidence() -> None: """Re-entering initialization cannot replace the creation-bound audit truth.""" event = _event() - original_snapshot = audit_module._AUDIT_CREATION_SNAPSHOTS[id(event)] + original = event.canonical_json() object.__setattr__(event, "reason_code", "manager_transfer") with pytest.raises(ValueError, match="already issued"): event.__post_init__() - assert audit_module._AUDIT_CREATION_SNAPSHOTS[id(event)] is original_snapshot with pytest.raises(ValueError, match="creation-time audit evidence"): event.canonical_json() + assert "manager_transfer" not in original -def test_missing_creation_snapshot_does_not_restore_issuance_eligibility() -> None: - """Losing the snapshot cannot let one live event issue a replacement snapshot.""" - event = _event() - audit_module._AUDIT_CREATION_SNAPSHOTS.pop(id(event)) - - with pytest.raises(ValueError, match="already issued"): - event.__post_init__() +def test_unissued_exact_event_cannot_export_canonical_evidence() -> None: + """Low-level field population cannot substitute for the governed issuance lifecycle.""" + event = _unissued_event() - assert id(event) not in audit_module._AUDIT_CREATION_SNAPSHOTS + with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): + event.canonical_json() -def test_event_lifetime_cleanup_releases_both_creation_registries() -> None: - """Object finalization releases the issuance marker and creation snapshot together.""" +def test_event_lifetime_cleanup_releases_private_runtime_state() -> None: + """The closure-private runtime releases its snapshot when the claimed object dies.""" + claim, record, lookup = audit_module._build_audit_creation_runtime() event = _event() - event_identity = id(event) + event_identity, identity_marker = claim(event) + snapshot = ("isolated-test-snapshot",) + record(event_identity, identity_marker, snapshot) - assert event_identity in audit_module._AUDIT_LIVE_ISSUANCES - assert event_identity in audit_module._AUDIT_CREATION_SNAPSHOTS + assert lookup(event_identity) is snapshot del event gc.collect() - assert event_identity not in audit_module._AUDIT_LIVE_ISSUANCES - assert event_identity not in audit_module._AUDIT_CREATION_SNAPSHOTS + assert lookup(event_identity) is None def test_stale_finalizer_marker_cannot_clear_reused_identity_state() -> None: @@ -99,16 +110,18 @@ def test_stale_finalizer_marker_cannot_clear_reused_identity_state() -> None: stale_marker = object() replacement_marker = object() replacement_snapshot = ("replacement",) - audit_module._AUDIT_LIVE_ISSUANCES[event_identity] = replacement_marker - audit_module._AUDIT_CREATION_SNAPSHOTS[event_identity] = replacement_snapshot # type: ignore[assignment] - try: - audit_module._clear_audit_creation_state(event_identity, stale_marker) + live_issuances = {event_identity: replacement_marker} + creation_snapshots = {event_identity: replacement_snapshot} + + audit_module._clear_audit_creation_state( + live_issuances, + creation_snapshots, + event_identity, + stale_marker, + ) - assert audit_module._AUDIT_LIVE_ISSUANCES[event_identity] is replacement_marker - assert audit_module._AUDIT_CREATION_SNAPSHOTS[event_identity] is replacement_snapshot - finally: - audit_module._AUDIT_LIVE_ISSUANCES.pop(event_identity, None) - audit_module._AUDIT_CREATION_SNAPSHOTS.pop(event_identity, None) + assert live_issuances[event_identity] is replacement_marker + assert creation_snapshots[event_identity] is replacement_snapshot def test_event_has_no_mutable_instance_slot_for_creation_seal() -> None: @@ -120,20 +133,10 @@ def test_event_has_no_mutable_instance_slot_for_creation_seal() -> None: object.__setattr__(event, "_creation_snapshot", ()) -def test_canonical_export_fails_closed_when_issuance_proof_is_missing() -> None: - """Missing process-local issuance evidence cannot silently mint a canonical event.""" - event = _event() - audit_module._AUDIT_CREATION_SNAPSHOTS.pop(id(event)) - - with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): - event.canonical_json() - - @pytest.mark.parametrize("corrupt_snapshot", [[], tuple(range(12))]) -def test_canonical_export_rejects_malformed_issuance_proof(corrupt_snapshot: object) -> None: - """Malformed module-owned issuance state fails closed before evidence comparison.""" - event = _event() - audit_module._AUDIT_CREATION_SNAPSHOTS[id(event)] = corrupt_snapshot # type: ignore[assignment] - +def test_creation_snapshot_validator_rejects_malformed_private_evidence( + corrupt_snapshot: object, +) -> None: + """Malformed private issuance evidence fails closed before value comparison.""" with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): - event.canonical_json() + audit_module._validate_creation_snapshot(corrupt_snapshot) From 70bdd6db87eda94228f91392b72beb2793ca29ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:33:54 +0900 Subject: [PATCH 38/52] fix(audit): hide issuance authority in closure-private state --- .../src/orgmetra_hris_kernel/audit.py | 113 +++++++++++++----- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 2058a1788..943806dcc 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -10,6 +10,7 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta, timezone from hashlib import sha256 @@ -34,15 +35,65 @@ ) _ALL_REQUIRED_TEXT_FIELDS = ("source_service", "event_type", *_REQUIRED_TEXT_FIELDS) _EVENT_SNAPSHOT_FIELD_COUNT = 13 -_AUDIT_CREATION_SNAPSHOTS: dict[int, tuple[object, ...]] = {} -_AUDIT_LIVE_ISSUANCES: dict[int, object] = {} +_AuditSnapshot = tuple[object, ...] +_AuditClaim = Callable[[object], tuple[int, object]] +_AuditRecord = Callable[[int, object, _AuditSnapshot], None] +_AuditLookup = Callable[[int], _AuditSnapshot | None] -def _clear_audit_creation_state(event_identity: int, identity_marker: object) -> None: +def _clear_audit_creation_state( + live_issuances: dict[int, object], + creation_snapshots: dict[int, _AuditSnapshot], + event_identity: int, + identity_marker: object, +) -> None: """Release process-local issuance evidence only for the exact finished identity.""" - if _AUDIT_LIVE_ISSUANCES.get(event_identity) is identity_marker: - _AUDIT_LIVE_ISSUANCES.pop(event_identity, None) - _AUDIT_CREATION_SNAPSHOTS.pop(event_identity, None) + if live_issuances.get(event_identity) is identity_marker: + live_issuances.pop(event_identity, None) + creation_snapshots.pop(event_identity, None) + + +def _build_audit_creation_runtime() -> tuple[_AuditClaim, _AuditRecord, _AuditLookup]: + """Build closure-private single-use issuance state for canonical audit evidence.""" + creation_snapshots: dict[int, _AuditSnapshot] = {} + live_issuances: dict[int, object] = {} + + def claim(event: object) -> tuple[int, object]: + """Claim one live object identity exactly once and attach lifetime cleanup.""" + event_identity = id(event) + identity_marker = object() + registered_marker = live_issuances.setdefault(event_identity, identity_marker) + if registered_marker is not identity_marker: + raise ValueError("audit event identity has already issued canonical evidence.") + finalize( + event, + _clear_audit_creation_state, + live_issuances, + creation_snapshots, + event_identity, + identity_marker, + ) + return event_identity, identity_marker + + def record( + event_identity: int, + identity_marker: object, + snapshot: _AuditSnapshot, + ) -> None: + """Commit one validated creation snapshot only for the active opaque claim.""" + if live_issuances.get(event_identity) is not identity_marker: + raise ValueError("audit event identity is not the active issuance.") + if event_identity in creation_snapshots: + raise ValueError("audit event identity has already issued canonical evidence.") + creation_snapshots[event_identity] = _validate_creation_snapshot(snapshot) + + def lookup(event_identity: int) -> _AuditSnapshot | None: + """Return immutable creation evidence only while the matching identity is live.""" + if event_identity not in live_issuances: + return None + return creation_snapshots.get(event_identity) + + return claim, record, lookup def _freeze_timestamp(value: datetime) -> datetime: @@ -157,7 +208,7 @@ def _event_snapshot( occurred_at: object, high_impact: object, confirmation_reference: object, -) -> tuple[object, ...]: +) -> _AuditSnapshot: """Capture one immutable tuple of already-read audit evidence values.""" return ( event_id, @@ -176,7 +227,7 @@ def _event_snapshot( ) -def _validate_creation_snapshot(snapshot: object) -> tuple[object, ...]: +def _validate_creation_snapshot(snapshot: object) -> _AuditSnapshot: """Validate the private creation snapshot before comparing it with live evidence.""" if type(snapshot) is not tuple or len(snapshot) != _EVENT_SNAPSHOT_FIELD_COUNT: raise ValueError("creation-time audit evidence is unavailable.") @@ -199,6 +250,11 @@ def _validate_creation_snapshot(snapshot: object) -> tuple[object, ...]: return snapshot +_claim_audit_issuance, _record_audit_creation_snapshot, _lookup_audit_creation_snapshot = ( + _build_audit_creation_runtime() +) + + @dataclass(frozen=True, slots=True, weakref_slot=True) class AuditOutboxEvent: """One immutable governance envelope for an Orgmetra domain mutation. @@ -226,12 +282,7 @@ class AuditOutboxEvent: def __post_init__(self) -> None: """Reject envelopes that cannot provide accountable, portable audit evidence.""" - event_identity = id(self) - identity_marker = object() - registered_marker = _AUDIT_LIVE_ISSUANCES.setdefault(event_identity, identity_marker) - if registered_marker is not identity_marker: - raise ValueError("audit event identity has already issued canonical evidence.") - finalize(self, _clear_audit_creation_state, event_identity, identity_marker) + event_identity, identity_marker = _claim_audit_issuance(self) _validate_event_snapshot( event_id=self.event_id, tenant_record_id=self.tenant_record_id, @@ -249,20 +300,24 @@ def __post_init__(self) -> None: ) frozen_occurred_at = _freeze_timestamp(self.occurred_at) object.__setattr__(self, "occurred_at", frozen_occurred_at) - _AUDIT_CREATION_SNAPSHOTS[event_identity] = _event_snapshot( - event_id=self.event_id, - tenant_record_id=self.tenant_record_id, - source_service=self.source_service, - event_type=self.event_type, - resource_reference=self.resource_reference, - actor_reference=self.actor_reference, - purpose_code=self.purpose_code, - reason_code=self.reason_code, - evidence_version_code=self.evidence_version_code, - result_code=self.result_code, - occurred_at=frozen_occurred_at, - high_impact=self.high_impact, - confirmation_reference=self.confirmation_reference, + _record_audit_creation_snapshot( + event_identity, + identity_marker, + _event_snapshot( + event_id=self.event_id, + tenant_record_id=self.tenant_record_id, + source_service=self.source_service, + event_type=self.event_type, + resource_reference=self.resource_reference, + actor_reference=self.actor_reference, + purpose_code=self.purpose_code, + reason_code=self.reason_code, + evidence_version_code=self.evidence_version_code, + result_code=self.result_code, + occurred_at=frozen_occurred_at, + high_impact=self.high_impact, + confirmation_reference=self.confirmation_reference, + ), ) def to_cloudevent(self) -> dict[str, object]: @@ -317,7 +372,7 @@ def to_cloudevent(self) -> dict[str, object]: high_impact=high_impact, confirmation_reference=confirmation_reference, ) - creation_snapshot = _AUDIT_CREATION_SNAPSHOTS.get(id(self)) + creation_snapshot = _lookup_audit_creation_snapshot(id(self)) if creation_snapshot is None: raise ValueError("creation-time audit evidence is unavailable.") creation_snapshot = _validate_creation_snapshot(creation_snapshot) From cd3b2f5ba0234ccb2249fe48fb8ee5e07c773cde Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:36:05 +0900 Subject: [PATCH 39/52] test(audit): use valid isolated creation snapshot for cleanup --- .../test_audit_creation_identity_integrity.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index 0b95ececf..2d697f9a8 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -42,6 +42,25 @@ def _unissued_event() -> AuditOutboxEvent: return event +def _creation_snapshot(event: AuditOutboxEvent) -> tuple[object, ...]: + """Capture the same exact inert values accepted by the private runtime.""" + return audit_module._event_snapshot( + event_id=event.event_id, + tenant_record_id=event.tenant_record_id, + source_service=event.source_service, + event_type=event.event_type, + resource_reference=event.resource_reference, + actor_reference=event.actor_reference, + purpose_code=event.purpose_code, + reason_code=event.reason_code, + evidence_version_code=event.evidence_version_code, + result_code=event.result_code, + occurred_at=event.occurred_at, + high_impact=event.high_impact, + confirmation_reference=event.confirmation_reference, + ) + + @pytest.mark.parametrize( ("field_name", "replacement"), [ @@ -93,7 +112,7 @@ def test_event_lifetime_cleanup_releases_private_runtime_state() -> None: claim, record, lookup = audit_module._build_audit_creation_runtime() event = _event() event_identity, identity_marker = claim(event) - snapshot = ("isolated-test-snapshot",) + snapshot = _creation_snapshot(event) record(event_identity, identity_marker, snapshot) assert lookup(event_identity) is snapshot From 03d1b8b537efce97aa0b695bd9e5e23757a1d9d0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:38:06 +0900 Subject: [PATCH 40/52] chore(manifest): reseal audit runtime authority source --- manifest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 13beeda6c..8b84fcfd2 100644 --- a/manifest.json +++ b/manifest.json @@ -341,9 +341,9 @@ }, { "path": "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", - "sha256": "e866fb2aab5257adb7b01e799802f901906980a639d2b0a9e346126d1a348ca4", - "bytes": 15481, - "lines": 364 + "sha256": "bf9fd96882ba67359e060c513fa3f046e7edb42d8ef87b30268f2a17e9f4a30a", + "bytes": 17496, + "lines": 419 }, { "path": "packages/hris-kernel/tests/test_audit_outbox.py", @@ -472,4 +472,4 @@ "lines": 637 } ] -} +} \ No newline at end of file From 48bfaf7558c8623b70d3c08a736c6d2953f563b5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:38:54 +0900 Subject: [PATCH 41/52] test(audit): cover private issuance marker and duplicate guards --- .../test_audit_creation_identity_integrity.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index 2d697f9a8..2f75503ef 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -107,6 +107,35 @@ def test_unissued_exact_event_cannot_export_canonical_evidence() -> None: event.canonical_json() +def test_private_runtime_rejects_wrong_marker_before_snapshot_commit() -> None: + """Only the opaque marker returned by the first live claim may commit evidence.""" + claim, record, lookup = audit_module._build_audit_creation_runtime() + event = _event() + event_identity, identity_marker = claim(event) + snapshot = _creation_snapshot(event) + + with pytest.raises(ValueError, match="not the active issuance"): + record(event_identity, object(), snapshot) + + assert lookup(event_identity) is None + record(event_identity, identity_marker, snapshot) + assert lookup(event_identity) is snapshot + + +def test_private_runtime_rejects_duplicate_snapshot_commit() -> None: + """One live claim cannot overwrite creation evidence after the first commit.""" + claim, record, lookup = audit_module._build_audit_creation_runtime() + event = _event() + event_identity, identity_marker = claim(event) + snapshot = _creation_snapshot(event) + record(event_identity, identity_marker, snapshot) + + with pytest.raises(ValueError, match="already issued"): + record(event_identity, identity_marker, snapshot) + + assert lookup(event_identity) is snapshot + + def test_event_lifetime_cleanup_releases_private_runtime_state() -> None: """The closure-private runtime releases its snapshot when the claimed object dies.""" claim, record, lookup = audit_module._build_audit_creation_runtime() From c17af4890cf861cddc3b40b03165402d4cd2d82c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:41:31 +0900 Subject: [PATCH 42/52] test(audit): reject closure-exported issuance authority --- .../hris-kernel/tests/test_audit_module_state_integrity.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_module_state_integrity.py b/packages/hris-kernel/tests/test_audit_module_state_integrity.py index d1f05150d..34f02cede 100644 --- a/packages/hris-kernel/tests/test_audit_module_state_integrity.py +++ b/packages/hris-kernel/tests/test_audit_module_state_integrity.py @@ -7,3 +7,10 @@ def test_audit_issuance_backing_registries_are_not_module_mutation_capabilities( """Importing the audit module must not expose mutable canonical-issuance storage.""" assert not hasattr(audit_module, "_AUDIT_LIVE_ISSUANCES") assert not hasattr(audit_module, "_AUDIT_CREATION_SNAPSHOTS") + + +def test_audit_module_does_not_export_closure_backed_issuance_mutators() -> None: + """Consumers must not recover authority dictionaries through exported closure cells.""" + assert not hasattr(audit_module, "_claim_audit_issuance") + assert not hasattr(audit_module, "_record_audit_creation_snapshot") + assert not hasattr(audit_module, "_lookup_audit_creation_snapshot") From 8b20c53ff3afcdfa0f372bd35a06b265187c9db9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:43:02 +0900 Subject: [PATCH 43/52] test(audit): require structural immutability instead of mutable issuance state --- .../test_audit_creation_identity_integrity.py | 139 ++---------------- 1 file changed, 16 insertions(+), 123 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py index 2f75503ef..574a3de88 100644 --- a/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py +++ b/packages/hris-kernel/tests/test_audit_creation_identity_integrity.py @@ -1,12 +1,10 @@ -"""Regression coverage for creation-bound audit canonical evidence.""" +"""Regression coverage for structurally immutable audit canonical evidence.""" from datetime import datetime, timezone -import gc from uuid import UUID import pytest -from orgmetra_hris_kernel import audit as audit_module from orgmetra_hris_kernel.audit import AuditOutboxEvent @@ -34,33 +32,6 @@ def _event() -> AuditOutboxEvent: return AuditOutboxEvent(**_event_values()) # type: ignore[arg-type] -def _unissued_event() -> AuditOutboxEvent: - """Populate an exact event object without running its governed issuance hook.""" - event = object.__new__(AuditOutboxEvent) - for field_name, value in _event_values().items(): - object.__setattr__(event, field_name, value) - return event - - -def _creation_snapshot(event: AuditOutboxEvent) -> tuple[object, ...]: - """Capture the same exact inert values accepted by the private runtime.""" - return audit_module._event_snapshot( - event_id=event.event_id, - tenant_record_id=event.tenant_record_id, - source_service=event.source_service, - event_type=event.event_type, - resource_reference=event.resource_reference, - actor_reference=event.actor_reference, - purpose_code=event.purpose_code, - reason_code=event.reason_code, - evidence_version_code=event.evidence_version_code, - result_code=event.result_code, - occurred_at=event.occurred_at, - high_impact=event.high_impact, - confirmation_reference=event.confirmation_reference, - ) - - @pytest.mark.parametrize( ("field_name", "replacement"), [ @@ -70,121 +41,43 @@ def _creation_snapshot(event: AuditOutboxEvent) -> tuple[object, ...]: ("confirmation_reference", "confirmation:01JOTHERCONFIRM"), ], ) -def test_valid_post_construction_replacement_cannot_reissue_canonical_evidence( +def test_canonical_fields_cannot_be_replaced_after_construction( field_name: str, replacement: str, ) -> None: - """A live event cannot mint a second valid canonical truth after issuance.""" + """Canonical evidence is structurally immutable rather than guarded by resettable state.""" event = _event() original = event.canonical_json() - object.__setattr__(event, field_name, replacement) - with pytest.raises(ValueError, match="creation-time audit evidence"): - event.canonical_json() + with pytest.raises(AttributeError): + object.__setattr__(event, field_name, replacement) + assert event.canonical_json() == original assert replacement not in original -def test_post_init_reentry_cannot_reseal_valid_mutated_evidence() -> None: - """Re-entering initialization cannot replace the creation-bound audit truth.""" +def test_post_init_reentry_cannot_reissue_canonical_evidence() -> None: + """The compatibility re-entry hook is rejection-only after immutable construction.""" event = _event() - original = event.canonical_json() - object.__setattr__(event, "reason_code", "manager_transfer") with pytest.raises(ValueError, match="already issued"): event.__post_init__() - with pytest.raises(ValueError, match="creation-time audit evidence"): - event.canonical_json() - assert "manager_transfer" not in original +def test_low_level_tuple_forgery_is_revalidated_before_export() -> None: + """Bypassing the public constructor cannot bypass export-time contract validation.""" + values = list(_event()) + values[7] = "Manager Transfer" + forged = tuple.__new__(AuditOutboxEvent, values) -def test_unissued_exact_event_cannot_export_canonical_evidence() -> None: - """Low-level field population cannot substitute for the governed issuance lifecycle.""" - event = _unissued_event() - - with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): - event.canonical_json() - - -def test_private_runtime_rejects_wrong_marker_before_snapshot_commit() -> None: - """Only the opaque marker returned by the first live claim may commit evidence.""" - claim, record, lookup = audit_module._build_audit_creation_runtime() - event = _event() - event_identity, identity_marker = claim(event) - snapshot = _creation_snapshot(event) - - with pytest.raises(ValueError, match="not the active issuance"): - record(event_identity, object(), snapshot) - - assert lookup(event_identity) is None - record(event_identity, identity_marker, snapshot) - assert lookup(event_identity) is snapshot - - -def test_private_runtime_rejects_duplicate_snapshot_commit() -> None: - """One live claim cannot overwrite creation evidence after the first commit.""" - claim, record, lookup = audit_module._build_audit_creation_runtime() - event = _event() - event_identity, identity_marker = claim(event) - snapshot = _creation_snapshot(event) - record(event_identity, identity_marker, snapshot) - - with pytest.raises(ValueError, match="already issued"): - record(event_identity, identity_marker, snapshot) - - assert lookup(event_identity) is snapshot - - -def test_event_lifetime_cleanup_releases_private_runtime_state() -> None: - """The closure-private runtime releases its snapshot when the claimed object dies.""" - claim, record, lookup = audit_module._build_audit_creation_runtime() - event = _event() - event_identity, identity_marker = claim(event) - snapshot = _creation_snapshot(event) - record(event_identity, identity_marker, snapshot) - - assert lookup(event_identity) is snapshot - - del event - gc.collect() - - assert lookup(event_identity) is None - - -def test_stale_finalizer_marker_cannot_clear_reused_identity_state() -> None: - """A stale lifetime callback cannot delete evidence registered by a replacement identity.""" - event_identity = -1 - stale_marker = object() - replacement_marker = object() - replacement_snapshot = ("replacement",) - live_issuances = {event_identity: replacement_marker} - creation_snapshots = {event_identity: replacement_snapshot} - - audit_module._clear_audit_creation_state( - live_issuances, - creation_snapshots, - event_identity, - stale_marker, - ) - - assert live_issuances[event_identity] is replacement_marker - assert creation_snapshots[event_identity] is replacement_snapshot + with pytest.raises(ValueError, match="reason_code"): + forged.canonical_json() def test_event_has_no_mutable_instance_slot_for_creation_seal() -> None: - """Low-level event mutation cannot rewrite the module-owned issuance proof.""" + """The immutable value object has no writable per-instance creation seal.""" event = _event() assert not hasattr(event, "_creation_snapshot") with pytest.raises(AttributeError): object.__setattr__(event, "_creation_snapshot", ()) - - -@pytest.mark.parametrize("corrupt_snapshot", [[], tuple(range(12))]) -def test_creation_snapshot_validator_rejects_malformed_private_evidence( - corrupt_snapshot: object, -) -> None: - """Malformed private issuance evidence fails closed before value comparison.""" - with pytest.raises(ValueError, match="creation-time audit evidence is unavailable"): - audit_module._validate_creation_snapshot(corrupt_snapshot) From 1b80534a7f39a56e335b44cb8ca702a551d3f088 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:43:31 +0900 Subject: [PATCH 44/52] test(audit): require immutable timestamp evidence after construction --- .../test_audit_runtime_type_integrity.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py index 1f37a0ee2..11435a3a9 100644 --- a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py +++ b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py @@ -53,13 +53,13 @@ def dst(self, value): # type: ignore[no-untyped-def] class _TripwireOffset(tzinfo): - """Fail if canonicalization executes reintroduced timezone behavior.""" + """Fail if post-construction mutation can reintroduce timezone behavior.""" def __init__(self) -> None: self.calls = 0 def utcoffset(self, value): # type: ignore[no-untyped-def] - """Record and reject any callback before the exact UTC gate.""" + """Record and reject any callback if mutation unexpectedly succeeds.""" del value self.calls += 1 raise AssertionError("reintroduced timezone callback executed before rejection") @@ -184,17 +184,14 @@ def test_audit_event_normalizes_offset_overflow_to_value_error() -> None: _event(occurred_at=datetime(1, 1, 1, 0, 0, tzinfo=_OversizedOffset())) -def test_audit_event_canonicalization_rejects_reintroduced_timezone_behavior() -> None: - """Fail before callbacks if low-level mutation reintroduces executable timezone behavior.""" +def test_audit_event_prevents_reintroduced_timezone_behavior_by_structure() -> None: + """Post-construction mutation fails before caller-controlled timezone callbacks can exist.""" event = _event() tripwire = _TripwireOffset() - object.__setattr__( - event, - "occurred_at", - datetime(2026, 8, 21, 5, 20, tzinfo=tripwire), - ) + replacement = datetime(2026, 8, 21, 5, 20, tzinfo=tripwire) - with pytest.raises(ValueError, match="exact timezone-aware datetime"): - event.to_cloudevent() + with pytest.raises(AttributeError): + object.__setattr__(event, "occurred_at", replacement) + assert event.occurred_at.tzinfo is timezone.utc assert tripwire.calls == 0 From e5d4303fd3fefbf33795a4262830d16bd5a20424 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:44:07 +0900 Subject: [PATCH 45/52] test(audit): enforce structural immutability at canonical boundary --- .../hris-kernel/tests/test_audit_outbox.py | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_outbox.py b/packages/hris-kernel/tests/test_audit_outbox.py index 2a47117e6..0a78c9cb0 100644 --- a/packages/hris-kernel/tests/test_audit_outbox.py +++ b/packages/hris-kernel/tests/test_audit_outbox.py @@ -200,35 +200,46 @@ def test_event_rejects_nonopaque_optional_confirmation_reference(): _event(high_impact=False, confirmation_reference="approved by Ada") -def test_canonical_export_revalidates_actor_after_low_level_mutation(): - """Canonical evidence must not emit an actor value that bypassed construction validation.""" +def test_canonical_event_prevents_actor_replacement_after_construction(): + """Canonical actor evidence is structurally immutable after validation.""" event = _event() - object.__setattr__(event, "actor_reference", "Ada Lovelace") + original = event.canonical_json() - with pytest.raises(ValueError, match="opaque reference"): - event.canonical_json() + with pytest.raises(AttributeError): + object.__setattr__(event, "actor_reference", "Ada Lovelace") + + assert event.canonical_json() == original -def test_canonical_export_revalidates_high_impact_confirmation_after_low_level_mutation(): - """A high-impact event cannot lose its human-confirmation evidence after construction.""" +def test_canonical_event_prevents_confirmation_removal_after_construction(): + """A validated high-impact confirmation cannot be removed from the immutable event.""" event = _event() - object.__setattr__(event, "confirmation_reference", None) + original = event.canonical_json() - with pytest.raises(ValueError, match="confirmation_reference"): - event.canonical_json() + with pytest.raises(AttributeError): + object.__setattr__(event, "confirmation_reference", None) + assert event.canonical_json() == original -def test_canonical_export_rejects_mutated_event_identity_before_stringification(): - """Canonicalization must validate identity type before executing arbitrary stringification.""" + +def test_canonical_event_prevents_identity_replacement_before_stringification(): + """Untrusted replacement identities cannot be installed into the canonical value object.""" class ExecutableIdentifier: - """Fail if canonicalization stringifies this untrusted replacement identity.""" + """Fail if any rejected replacement is unexpectedly stringified.""" + + def __init__(self) -> None: + self.calls = 0 def __str__(self) -> str: + self.calls += 1 raise AssertionError("untrusted identity stringification executed") event = _event() - object.__setattr__(event, "event_id", ExecutableIdentifier()) + replacement = ExecutableIdentifier() + + with pytest.raises(AttributeError): + object.__setattr__(event, "event_id", replacement) - with pytest.raises(ValueError, match="event_id must be a UUID"): - event.canonical_json() + assert replacement.calls == 0 + assert event.event_id == EVENT_ID From e29d180ce2e2bc531abc817fffea4eb6096c53ce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 01:47:12 +0900 Subject: [PATCH 46/52] fix(audit): make canonical evidence structurally immutable --- .../src/orgmetra_hris_kernel/audit.py | 284 ++++++------------ 1 file changed, 86 insertions(+), 198 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 943806dcc..c26523161 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -10,14 +10,12 @@ from __future__ import annotations -from collections.abc import Callable -from dataclasses import dataclass +from collections import namedtuple from datetime import datetime, timedelta, timezone from hashlib import sha256 import json import re from uuid import UUID -from weakref import finalize _SOURCE_SERVICE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _EVENT_TYPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") @@ -34,66 +32,21 @@ "result_code", ) _ALL_REQUIRED_TEXT_FIELDS = ("source_service", "event_type", *_REQUIRED_TEXT_FIELDS) -_EVENT_SNAPSHOT_FIELD_COUNT = 13 -_AuditSnapshot = tuple[object, ...] -_AuditClaim = Callable[[object], tuple[int, object]] -_AuditRecord = Callable[[int, object, _AuditSnapshot], None] -_AuditLookup = Callable[[int], _AuditSnapshot | None] - - -def _clear_audit_creation_state( - live_issuances: dict[int, object], - creation_snapshots: dict[int, _AuditSnapshot], - event_identity: int, - identity_marker: object, -) -> None: - """Release process-local issuance evidence only for the exact finished identity.""" - if live_issuances.get(event_identity) is identity_marker: - live_issuances.pop(event_identity, None) - creation_snapshots.pop(event_identity, None) - - -def _build_audit_creation_runtime() -> tuple[_AuditClaim, _AuditRecord, _AuditLookup]: - """Build closure-private single-use issuance state for canonical audit evidence.""" - creation_snapshots: dict[int, _AuditSnapshot] = {} - live_issuances: dict[int, object] = {} - - def claim(event: object) -> tuple[int, object]: - """Claim one live object identity exactly once and attach lifetime cleanup.""" - event_identity = id(event) - identity_marker = object() - registered_marker = live_issuances.setdefault(event_identity, identity_marker) - if registered_marker is not identity_marker: - raise ValueError("audit event identity has already issued canonical evidence.") - finalize( - event, - _clear_audit_creation_state, - live_issuances, - creation_snapshots, - event_identity, - identity_marker, - ) - return event_identity, identity_marker - - def record( - event_identity: int, - identity_marker: object, - snapshot: _AuditSnapshot, - ) -> None: - """Commit one validated creation snapshot only for the active opaque claim.""" - if live_issuances.get(event_identity) is not identity_marker: - raise ValueError("audit event identity is not the active issuance.") - if event_identity in creation_snapshots: - raise ValueError("audit event identity has already issued canonical evidence.") - creation_snapshots[event_identity] = _validate_creation_snapshot(snapshot) - - def lookup(event_identity: int) -> _AuditSnapshot | None: - """Return immutable creation evidence only while the matching identity is live.""" - if event_identity not in live_issuances: - return None - return creation_snapshots.get(event_identity) - - return claim, record, lookup +_AUDIT_EVENT_FIELDS = ( + "event_id", + "tenant_record_id", + "source_service", + "event_type", + "resource_reference", + "actor_reference", + "purpose_code", + "reason_code", + "evidence_version_code", + "result_code", + "occurred_at", + "high_impact", + "confirmation_reference", +) def _freeze_timestamp(value: datetime) -> datetime: @@ -135,7 +88,7 @@ def _validate_event_snapshot( high_impact: object, confirmation_reference: object, ) -> None: - """Validate one captured audit snapshot without rereading live event fields.""" + """Validate one captured audit value set without executing untrusted coercions.""" if type(event_id) is not UUID: raise ValueError("event_id must be a UUID.") if type(tenant_record_id) is not UUID: @@ -171,7 +124,9 @@ def _validate_event_snapshot( if _SOURCE_SERVICE_PATTERN.fullmatch(source_service) is None: raise ValueError("source_service must contain two or more lower snake_case words.") if _EVENT_TYPE_PATTERN.fullmatch(event_type) is None: - raise ValueError("event_type must use a canonical lower-case orgmetra.. namespace.") + raise ValueError( + "event_type must use a canonical lower-case orgmetra.. namespace." + ) for field_name in _REQUIRED_TEXT_FIELDS: value = text_values[field_name] if not value.strip(): @@ -193,133 +148,85 @@ def _validate_event_snapshot( raise ValueError("high-impact events require confirmation_reference.") -def _event_snapshot( - *, - event_id: object, - tenant_record_id: object, - source_service: object, - event_type: object, - resource_reference: object, - actor_reference: object, - purpose_code: object, - reason_code: object, - evidence_version_code: object, - result_code: object, - occurred_at: object, - high_impact: object, - confirmation_reference: object, -) -> _AuditSnapshot: - """Capture one immutable tuple of already-read audit evidence values.""" - return ( - event_id, - tenant_record_id, - source_service, - event_type, - resource_reference, - actor_reference, - purpose_code, - reason_code, - evidence_version_code, - result_code, - occurred_at, - high_impact, - confirmation_reference, - ) - - -def _validate_creation_snapshot(snapshot: object) -> _AuditSnapshot: - """Validate the private creation snapshot before comparing it with live evidence.""" - if type(snapshot) is not tuple or len(snapshot) != _EVENT_SNAPSHOT_FIELD_COUNT: - raise ValueError("creation-time audit evidence is unavailable.") - _validate_event_snapshot( - event_id=snapshot[0], - tenant_record_id=snapshot[1], - source_service=snapshot[2], - event_type=snapshot[3], - resource_reference=snapshot[4], - actor_reference=snapshot[5], - purpose_code=snapshot[6], - reason_code=snapshot[7], - evidence_version_code=snapshot[8], - result_code=snapshot[9], - occurred_at=snapshot[10], - high_impact=snapshot[11], - confirmation_reference=snapshot[12], - ) - _canonical_timestamp(snapshot[10]) - return snapshot - - -_claim_audit_issuance, _record_audit_creation_snapshot, _lookup_audit_creation_snapshot = ( - _build_audit_creation_runtime() +_AuditOutboxEventTuple = namedtuple( + "_AuditOutboxEventTuple", + _AUDIT_EVENT_FIELDS, + module=__name__, ) -@dataclass(frozen=True, slots=True, weakref_slot=True) -class AuditOutboxEvent: - """One immutable governance envelope for an Orgmetra domain mutation. +class AuditOutboxEvent(_AuditOutboxEventTuple): + """One structurally immutable governance envelope for an Orgmetra mutation. High-impact employment decisions require ``confirmation_reference``. The emitted CloudEvent intentionally excludes mutable HR payload fields so the audit/outbox record can be retained and shared without becoming a shadow system of record for names, compensation, or other necessary PII. Reserved Nil and Max UUID sentinels are rejected before persistence. - """ - event_id: UUID - tenant_record_id: UUID - source_service: str - event_type: str - resource_reference: str - actor_reference: str - purpose_code: str - reason_code: str - evidence_version_code: str - result_code: str - occurred_at: datetime - high_impact: bool - confirmation_reference: str | None = None + Canonical evidence is held directly by the tuple value object. There is no + process-local mutable issuance registry to reset or overwrite. Persistence + authorization remains a separate service/port responsibility. + """ - def __post_init__(self) -> None: - """Reject envelopes that cannot provide accountable, portable audit evidence.""" - event_identity, identity_marker = _claim_audit_issuance(self) + __slots__ = () + + def __new__( + cls, + event_id: UUID, + tenant_record_id: UUID, + source_service: str, + event_type: str, + resource_reference: str, + actor_reference: str, + purpose_code: str, + reason_code: str, + evidence_version_code: str, + result_code: str, + occurred_at: datetime, + high_impact: bool, + confirmation_reference: str | None = None, + ) -> AuditOutboxEvent: + """Validate, detach time-provider behavior, and construct one immutable value.""" + if cls is not AuditOutboxEvent: + raise TypeError("AuditOutboxEvent must be constructed as the exact canonical type.") _validate_event_snapshot( - event_id=self.event_id, - tenant_record_id=self.tenant_record_id, - source_service=self.source_service, - event_type=self.event_type, - resource_reference=self.resource_reference, - actor_reference=self.actor_reference, - purpose_code=self.purpose_code, - reason_code=self.reason_code, - evidence_version_code=self.evidence_version_code, - result_code=self.result_code, - occurred_at=self.occurred_at, - high_impact=self.high_impact, - confirmation_reference=self.confirmation_reference, + event_id=event_id, + tenant_record_id=tenant_record_id, + source_service=source_service, + event_type=event_type, + resource_reference=resource_reference, + actor_reference=actor_reference, + purpose_code=purpose_code, + reason_code=reason_code, + evidence_version_code=evidence_version_code, + result_code=result_code, + occurred_at=occurred_at, + high_impact=high_impact, + confirmation_reference=confirmation_reference, ) - frozen_occurred_at = _freeze_timestamp(self.occurred_at) - object.__setattr__(self, "occurred_at", frozen_occurred_at) - _record_audit_creation_snapshot( - event_identity, - identity_marker, - _event_snapshot( - event_id=self.event_id, - tenant_record_id=self.tenant_record_id, - source_service=self.source_service, - event_type=self.event_type, - resource_reference=self.resource_reference, - actor_reference=self.actor_reference, - purpose_code=self.purpose_code, - reason_code=self.reason_code, - evidence_version_code=self.evidence_version_code, - result_code=self.result_code, - occurred_at=frozen_occurred_at, - high_impact=self.high_impact, - confirmation_reference=self.confirmation_reference, - ), + frozen_occurred_at = _freeze_timestamp(occurred_at) + return super().__new__( + cls, + event_id, + tenant_record_id, + source_service, + event_type, + resource_reference, + actor_reference, + purpose_code, + reason_code, + evidence_version_code, + result_code, + frozen_occurred_at, + high_impact, + confirmation_reference, ) + def __post_init__(self) -> None: + """Reject compatibility-style re-entry; tuple construction already issued evidence.""" + raise ValueError("audit event identity has already issued canonical evidence.") + def to_cloudevent(self) -> dict[str, object]: """Return the canonical structured CloudEvent 1.0 envelope. @@ -328,6 +235,8 @@ def to_cloudevent(self) -> dict[str, object]: PII-minimized result body. Persist this mapping atomically with the owning business write before asynchronous delivery. """ + if type(self) is not AuditOutboxEvent: + raise ValueError("audit event must be the exact canonical type.") event_id = self.event_id tenant_record_id = self.tenant_record_id source_service = self.source_service @@ -357,27 +266,6 @@ def to_cloudevent(self) -> dict[str, object]: confirmation_reference=confirmation_reference, ) canonical_time = _canonical_timestamp(occurred_at) - current_snapshot = _event_snapshot( - event_id=event_id, - tenant_record_id=tenant_record_id, - source_service=source_service, - event_type=event_type, - resource_reference=resource_reference, - actor_reference=actor_reference, - purpose_code=purpose_code, - reason_code=reason_code, - evidence_version_code=evidence_version_code, - result_code=result_code, - occurred_at=occurred_at, - high_impact=high_impact, - confirmation_reference=confirmation_reference, - ) - creation_snapshot = _lookup_audit_creation_snapshot(id(self)) - if creation_snapshot is None: - raise ValueError("creation-time audit evidence is unavailable.") - creation_snapshot = _validate_creation_snapshot(creation_snapshot) - if current_snapshot != creation_snapshot: - raise ValueError("canonical audit evidence no longer matches creation-time audit evidence.") envelope: dict[str, object] = { "specversion": "1.0", "id": str(event_id), From 1d24cef77d8b843f694c5982c5e4ceaf786eb864 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 03:02:46 +0900 Subject: [PATCH 47/52] test(core): reject executable audit timezones before callbacks --- .../test_audit_runtime_type_integrity.py | 91 ++++++------------- 1 file changed, 28 insertions(+), 63 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py index 11435a3a9..f0fce361f 100644 --- a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py +++ b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta, timezone, tzinfo from uuid import UUID +from zoneinfo import ZoneInfo import pytest @@ -34,35 +35,17 @@ class _OpaqueText(str): """Represent valid audit text through an untrusted runtime subclass.""" -class _MutableOffset(tzinfo): - """Expose timezone state that can change after event construction.""" - - def __init__(self) -> None: - """Start with a UTC offset.""" - self.offset = timedelta(0) - - def utcoffset(self, value): # type: ignore[no-untyped-def] - """Return the currently configured offset.""" - del value - return self.offset - - def dst(self, value): # type: ignore[no-untyped-def] - """Keep daylight saving fixed.""" - del value - return timedelta(0) - - class _TripwireOffset(tzinfo): - """Fail if post-construction mutation can reintroduce timezone behavior.""" + """Fail if caller-defined timezone behavior executes at an audit trust boundary.""" def __init__(self) -> None: self.calls = 0 def utcoffset(self, value): # type: ignore[no-untyped-def] - """Record and reject any callback if mutation unexpectedly succeeds.""" + """Record and reject any callback if the trust boundary invokes this provider.""" del value self.calls += 1 - raise AssertionError("reintroduced timezone callback executed before rejection") + raise AssertionError("caller-defined timezone callback executed before rejection") def dst(self, value): # type: ignore[no-untyped-def] """Keep daylight saving fixed if unexpectedly queried.""" @@ -70,34 +53,6 @@ def dst(self, value): # type: ignore[no-untyped-def] return timedelta(0) -class _ExplodingOffset(tzinfo): - """Raise arbitrary provider behavior while an event instant is resolved.""" - - def utcoffset(self, value): # type: ignore[no-untyped-def] - """Force the trust boundary to normalize provider failures.""" - del value - raise RuntimeError("provider details must not escape") - - def dst(self, value): # type: ignore[no-untyped-def] - """Keep daylight saving fixed if queried.""" - del value - return timedelta(0) - - -class _OversizedOffset(tzinfo): - """Return an extreme offset that cannot be detached from year one.""" - - def utcoffset(self, value): # type: ignore[no-untyped-def] - """Force UTC detachment outside representable datetime values.""" - del value - return timedelta(hours=23, minutes=59) - - def dst(self, value): # type: ignore[no-untyped-def] - """Keep daylight saving fixed.""" - del value - return timedelta(0) - - def _event(**overrides: object) -> AuditOutboxEvent: """Build one valid high-impact audit event with focused overrides.""" values: dict[str, object] = { @@ -156,32 +111,42 @@ def test_audit_event_rejects_string_subclasses_before_canonicalization( _event(**{field_name: value}) -def test_audit_event_detaches_mutable_timezone_state() -> None: - """Keep canonical audit chronology stable after timezone state mutates.""" - zone = _MutableOffset() +def test_audit_event_accepts_standard_zoneinfo_and_detaches_to_utc() -> None: + """Psycopg-style standard-library ZoneInfo timestamps remain supported.""" event = _event( high_impact=False, confirmation_reference=None, - occurred_at=datetime(2026, 8, 21, 5, 20, tzinfo=zone), + occurred_at=datetime(2026, 8, 21, 14, 20, tzinfo=ZoneInfo("Asia/Seoul")), ) - first = event.to_cloudevent() - - zone.offset = timedelta(hours=9) + assert event.occurred_at == datetime(2026, 8, 21, 5, 20, tzinfo=timezone.utc) + assert type(event.occurred_at) is datetime assert event.occurred_at.tzinfo is timezone.utc - assert event.to_cloudevent() == first -def test_audit_event_normalizes_timezone_provider_exceptions() -> None: - """Do not leak arbitrary timezone-provider exceptions from event construction.""" - with pytest.raises(ValueError, match="occurred_at must resolve to a UTC offset"): - _event(occurred_at=datetime(2026, 8, 21, 5, 20, tzinfo=_ExplodingOffset())) +def test_audit_event_rejects_custom_timezone_before_provider_callback() -> None: + """Caller-defined tzinfo must fail closed before any executable provider hook runs.""" + tripwire = _TripwireOffset() + occurred_at = datetime(2026, 8, 21, 5, 20, tzinfo=tripwire) + + with pytest.raises(ValueError, match="datetime.timezone or zoneinfo.ZoneInfo"): + _event(occurred_at=occurred_at) + + assert tripwire.calls == 0 def test_audit_event_normalizes_offset_overflow_to_value_error() -> None: - """Fail closed when UTC detachment exceeds representable datetime values.""" + """Fail closed when a standard-library UTC detachment exceeds representable values.""" + occurred_at = datetime( + 1, + 1, + 1, + 0, + 0, + tzinfo=timezone(timedelta(hours=23, minutes=59)), + ) with pytest.raises(ValueError, match="occurred_at must be a representable"): - _event(occurred_at=datetime(1, 1, 1, 0, 0, tzinfo=_OversizedOffset())) + _event(occurred_at=occurred_at) def test_audit_event_prevents_reintroduced_timezone_behavior_by_structure() -> None: From 7216153d55e2767716357d3171030d4aa8a64586 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 03:03:23 +0900 Subject: [PATCH 48/52] fix(core): exact-gate audit timezone providers --- packages/hris-kernel/src/orgmetra_hris_kernel/audit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index c26523161..351fa46cb 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -16,6 +16,7 @@ import json import re from uuid import UUID +from zoneinfo import ZoneInfo _SOURCE_SERVICE_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") _EVENT_TYPE_PATTERN = re.compile(r"^orgmetra(?:\.[a-z][a-z0-9_]*){2,}$") @@ -50,12 +51,15 @@ def _freeze_timestamp(value: datetime) -> datetime: - """Detach caller-controlled timezone behavior as one immutable UTC instant.""" + """Detach only standard-library timezone evidence as one immutable UTC instant.""" if type(value) is not datetime or value.tzinfo is None: raise ValueError("occurred_at must be an exact timezone-aware datetime.") + zone = value.tzinfo + if type(zone) not in (timezone, ZoneInfo): + raise ValueError("occurred_at timezone must be exact datetime.timezone or zoneinfo.ZoneInfo.") try: offset = value.utcoffset() - except Exception as exc: # noqa: BLE001 - normalize provider behavior at trust boundary. + except Exception as exc: # noqa: BLE001 - normalize standard-library provider failures. raise ValueError("occurred_at must resolve to a UTC offset.") from exc if offset is None or type(offset) is not timedelta: raise ValueError("occurred_at must resolve to a UTC offset.") From c9f7de36ff3e6e936b798092fb5bdd62e9dd9412 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 03:09:33 +0900 Subject: [PATCH 49/52] test(core): align audit timezone contract with inert providers --- .../hris-kernel/tests/test_audit_outbox.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/hris-kernel/tests/test_audit_outbox.py b/packages/hris-kernel/tests/test_audit_outbox.py index 0a78c9cb0..1db1e0e47 100644 --- a/packages/hris-kernel/tests/test_audit_outbox.py +++ b/packages/hris-kernel/tests/test_audit_outbox.py @@ -173,19 +173,27 @@ def test_digest_changes_when_governance_context_changes(): assert original != changed -def test_event_rejects_timezone_object_without_resolved_offset(): - """A tzinfo object that cannot resolve an offset is not auditable time evidence.""" +def test_event_rejects_custom_timezone_before_provider_callback(): + """Caller-defined timezone behavior is rejected before any provider hook executes.""" from datetime import tzinfo - class UnresolvedTimezone(tzinfo): - """Minimal tzinfo fixture with intentionally unresolved UTC offset.""" + class ExecutableTimezone(tzinfo): + """Record any forbidden UTC-offset callback at the audit trust boundary.""" + + def __init__(self): + self.calls = 0 def utcoffset(self, dt): - """Return no offset so the contract must reject this timestamp.""" - return None + """Expose a tripwire if the boundary executes this provider.""" + del dt + self.calls += 1 + return timedelta(0) + + provider = ExecutableTimezone() + with pytest.raises(ValueError, match="datetime.timezone or zoneinfo.ZoneInfo"): + _event(occurred_at=datetime(2026, 8, 17, 1, 30, tzinfo=provider)) - with pytest.raises(ValueError, match="resolve to a UTC offset"): - _event(occurred_at=datetime(2026, 8, 17, 1, 30, tzinfo=UnresolvedTimezone())) + assert provider.calls == 0 def test_event_rejects_blank_optional_confirmation_reference(): From 0607d00f257569bb3e649c08526172befc00adc8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 03:10:21 +0900 Subject: [PATCH 50/52] refactor(core): remove unreachable custom-timezone branches --- packages/hris-kernel/src/orgmetra_hris_kernel/audit.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py index 351fa46cb..965f124c3 100644 --- a/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py +++ b/packages/hris-kernel/src/orgmetra_hris_kernel/audit.py @@ -15,6 +15,7 @@ from hashlib import sha256 import json import re +from typing import cast from uuid import UUID from zoneinfo import ZoneInfo @@ -57,12 +58,7 @@ def _freeze_timestamp(value: datetime) -> datetime: zone = value.tzinfo if type(zone) not in (timezone, ZoneInfo): raise ValueError("occurred_at timezone must be exact datetime.timezone or zoneinfo.ZoneInfo.") - try: - offset = value.utcoffset() - except Exception as exc: # noqa: BLE001 - normalize standard-library provider failures. - raise ValueError("occurred_at must resolve to a UTC offset.") from exc - if offset is None or type(offset) is not timedelta: - raise ValueError("occurred_at must resolve to a UTC offset.") + offset = cast(timedelta, value.utcoffset()) try: return (value.replace(tzinfo=None) - offset).replace(tzinfo=timezone.utc) except OverflowError as exc: From 16dce30fea69991277c558688da704554c08b0e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 03:15:51 +0900 Subject: [PATCH 51/52] chore(manifest): reseal audit timezone owner artifacts --- manifest.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/manifest.json b/manifest.json index 8b84fcfd2..a529372db 100644 --- a/manifest.json +++ b/manifest.json @@ -341,15 +341,15 @@ }, { "path": "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", - "sha256": "bf9fd96882ba67359e060c513fa3f046e7edb42d8ef87b30268f2a17e9f4a30a", - "bytes": 17496, - "lines": 419 + "sha256": "dcc7f78fbbe9cf0cf0207e3d18c6173bb1ad3ed133aeb7b578ef3657af49a192", + "bytes": 12593, + "lines": 307 }, { "path": "packages/hris-kernel/tests/test_audit_outbox.py", - "sha256": "d0044548f59493adaca8ca54b78ac678dfe8cea5399737ce493bc904360d7100", - "bytes": 8886, - "lines": 234 + "sha256": "6dd86c98e3b4667e47d99cb009f8aa3fc410a6c1c6e59fee7f198dd52331b412", + "bytes": 9228, + "lines": 253 }, { "path": "schemas/openapi.yaml", @@ -472,4 +472,4 @@ "lines": 637 } ] -} \ No newline at end of file +} From 72070cb4b8d636825ce5b1a326df4c296596ed7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 10:24:52 +0900 Subject: [PATCH 52/52] test(core): cover audit structural rejection branches --- .../test_audit_runtime_type_integrity.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py index f0fce361f..76b772e13 100644 --- a/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py +++ b/packages/hris-kernel/tests/test_audit_runtime_type_integrity.py @@ -35,6 +35,12 @@ class _OpaqueText(str): """Represent valid audit text through an untrusted runtime subclass.""" +class _ForgedAuditOutboxEvent(AuditOutboxEvent): + """Represent a non-canonical audit tuple created through a low-level bypass.""" + + __slots__ = () + + class _TripwireOffset(tzinfo): """Fail if caller-defined timezone behavior executes at an audit trust boundary.""" @@ -160,3 +166,27 @@ def test_audit_event_prevents_reintroduced_timezone_behavior_by_structure() -> N assert event.occurred_at.tzinfo is timezone.utc assert tripwire.calls == 0 + + +def test_audit_event_rejects_subclass_construction_before_snapshot_validation() -> None: + """Canonical construction rejects a tuple subtype before accepting any evidence.""" + with pytest.raises(TypeError, match="exact canonical type"): + _ForgedAuditOutboxEvent(*_event()) + + +def test_audit_event_rejects_low_level_subclass_before_export() -> None: + """A low-level tuple subtype cannot cross the canonical export boundary.""" + forged = tuple.__new__(_ForgedAuditOutboxEvent, tuple(_event())) + + with pytest.raises(ValueError, match="exact canonical type"): + forged.to_cloudevent() + + +def test_audit_event_rejects_low_level_non_utc_datetime_before_rendering() -> None: + """A low-level exact tuple with non-canonical time still fails closed on export.""" + values = list(_event()) + values[10] = datetime(2026, 8, 21, 5, 20) + forged = tuple.__new__(AuditOutboxEvent, values) + + with pytest.raises(ValueError, match="exact timezone-aware datetime"): + forged.to_cloudevent()