Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
48f9185
test(hire): reject identity runtime subclasses
seonghobae Aug 21, 2026
ef3f995
test(hire): reject validation-bypassing contract subclasses
seonghobae Aug 21, 2026
a78edad
fix(hire): protect governed identity and contract runtime types
seonghobae Aug 21, 2026
15c3ffc
test(people): reject mutation runtime type confusion
seonghobae Aug 21, 2026
6f9db10
test(people): reject idempotency evidence runtime confusion
seonghobae Aug 21, 2026
e98cd1c
fix(people): protect mutation identity and idempotency runtime types
seonghobae Aug 21, 2026
ad38eb4
test(people): reject forged hire authority runtime types
seonghobae Aug 22, 2026
63eb051
fix(people): require exact hire authority runtime types
seonghobae Aug 22, 2026
2762349
test(people): reject forged mutation authorization subtype
seonghobae Aug 22, 2026
76cb5b0
fix(people): require exact mutation authorization type
seonghobae Aug 22, 2026
e859d20
test(people): reject forged mutation command subtypes
seonghobae Aug 22, 2026
0196bf5
fix(people): require exact mutation command runtime types
seonghobae Aug 22, 2026
be156d8
test(people): reject forged mutation text evidence
seonghobae Aug 22, 2026
1967d7b
fix(people): require exact hire display-name text
seonghobae Aug 22, 2026
23fc4f4
fix(people): require exact governed mutation text
seonghobae Aug 22, 2026
c397053
fix(people): close remaining governed text gaps
seonghobae Aug 28, 2026
07e4230
chore(people): adopt protected develop without rewriting runtime hard…
seonghobae Sep 3, 2026
0c53d40
merge(people): adopt protected workflow consolidation
seonghobae Sep 4, 2026
399010c
merge(people): preserve #161 changelog delta after restack
seonghobae Sep 4, 2026
1968592
fix(ci): reseal People manifest after protected restack
seonghobae Sep 4, 2026
cde8df2
test(people): reject executable hire decision timestamps
seonghobae Sep 4, 2026
9888223
fix(people): exact-gate hire decision time providers
seonghobae Sep 4, 2026
61cba0f
fix(people): restore hire insert contract after timestamp repair
seonghobae Sep 4, 2026
42d988a
test(people): document executable timezone tripwire
seonghobae Sep 4, 2026
792dfe3
test(people): reject executable durable hire UUID evidence
seonghobae Sep 4, 2026
6443ee5
fix(people): exact-gate durable hire UUID evidence
seonghobae Sep 4, 2026
2ac5a11
test(people): reject executable hire idempotency digest text
seonghobae Sep 4, 2026
d81260f
fix(people): exact-gate durable hire idempotency digest text
seonghobae Sep 4, 2026
878e98b
test(people): reject executable hire provenance text
seonghobae Sep 4, 2026
c260b05
fix(people): exact-gate durable hire provenance text
seonghobae Sep 4, 2026
5514b74
test(people): reject executable durable hire row containers
seonghobae Sep 4, 2026
adab344
fix(people): exact-gate durable hire row containers
seonghobae Sep 4, 2026
cf76786
test(people): reject executable generic mutation row containers
seonghobae Sep 4, 2026
34c6e55
fix(people): exact-gate generic mutation row containers
seonghobae Sep 4, 2026
e7f84b2
test(people): reject executable generic mutation durable scalars
seonghobae Sep 4, 2026
36bdc32
fix(people): exact-gate generic mutation durable scalars
seonghobae Sep 4, 2026
0498eaa
test(people): reject executable generic replay digest
seonghobae Sep 4, 2026
55033e7
fix(people): exact-gate generic replay digest
seonghobae Sep 4, 2026
19f2129
test(people): reject executable persisted status text
seonghobae Sep 4, 2026
2c0b114
fix(people): exact-gate persisted status codes
seonghobae Sep 4, 2026
ad1c1db
test(people): reject executable persisted allocation Decimal
seonghobae Sep 4, 2026
3a63e92
fix(people): exact-gate persisted allocation Decimal
seonghobae Sep 4, 2026
11c30cd
test(people): reject executable Position parent UUID evidence
seonghobae Sep 4, 2026
46884c2
fix(people): validate Position parent UUIDs before equality
seonghobae Sep 4, 2026
3e7eb20
test(people): prove post-construction mutation escapes runtime integrity
seonghobae Sep 5, 2026
e278023
fix(people): revalidate mutation evidence at consumption boundaries
seonghobae Sep 5, 2026
c7e0319
test(people): prove rewritten command reaches PostgreSQL authority
seonghobae Sep 5, 2026
6826999
fix(people): revalidate commands at PostgreSQL mutation entry
seonghobae Sep 5, 2026
8c40a18
test(people): cover all PostgreSQL rewritten command entries
seonghobae Sep 5, 2026
1ab0bd0
test(people): prove confirmed-hire post-construction integrity gap
seonghobae Sep 5, 2026
0a39455
fix(people): revalidate confirmed-hire application evidence
seonghobae Sep 5, 2026
bf24c2e
fix(people): revalidate confirmed-hire durable entry command
seonghobae Sep 5, 2026
2064a52
test(people): expose post-validation command rewrite
seonghobae Sep 5, 2026
e4cab11
fix(people): detach validated mutation commands before callbacks
seonghobae Sep 5, 2026
0daa68a
test(people): bind mutation results to commanded identities
seonghobae Sep 5, 2026
e09e557
fix(people): bind mutation results to command identities
seonghobae Sep 5, 2026
40ef9b2
fix(people): bind confirmed hire result identities
seonghobae Sep 5, 2026
6142f7d
test(people): bind result checks to pre-port targets
seonghobae Sep 5, 2026
abd5492
fix(people): compare results to detached pre-port targets
seonghobae Sep 5, 2026
62a6b7a
fix(people): compare hire result to detached targets
seonghobae Sep 5, 2026
f741508
test(people): bind mutation commands before authorization callbacks
seonghobae Sep 5, 2026
e4d538c
fix(people): detach commands before authorization callbacks
seonghobae Sep 5, 2026
5a6354a
test(people): bind hire command before authorization callbacks
seonghobae Sep 5, 2026
9771be6
fix(people): detach hire command before authorization callbacks
seonghobae Sep 5, 2026
6fcb1c4
test(people): reject allocation text runtime subtype
seonghobae Sep 5, 2026
7779a85
fix(people): require exact allocation text
seonghobae Sep 5, 2026
8d38776
style(people): preserve source trailing newline
seonghobae Sep 5, 2026
abd5dc5
test(people): expose zero allocation contract split
seonghobae Sep 5, 2026
7a95aa9
fix(people): reject zero allocation before domain construction
seonghobae Sep 5, 2026
f659b65
fix(api): align assignment allocation contract with domain invariant
seonghobae Sep 5, 2026
da4b628
chore(manifest): reseal updated OpenAPI allocation contract
seonghobae Sep 5, 2026
8f98685
fix(manifest): restore unaffected outbox digest
seonghobae Sep 5, 2026
d7440d4
test(people): specify idempotent replay result evidence
seonghobae Sep 5, 2026
cc1cc53
fix(people): bind foreign replay identity to semantic digest
seonghobae Sep 5, 2026
a61617f
fix(people): return verified idempotency replay receipt
seonghobae Sep 5, 2026
3f3b23a
test(people): cover fixed projection shape guards
seonghobae Sep 5, 2026
f933fbc
test(people): use standard UUID attribute trap
seonghobae Sep 5, 2026
4e9e04f
test(people): use standard row-container trap errors
seonghobae Sep 5, 2026
7485cb2
test(people): use standard durable UUID trap
seonghobae Sep 5, 2026
59f6eae
test(people): use standard projection-container trap errors
seonghobae Sep 5, 2026
998e06f
test(people): use standard scalar tripwire protocols
seonghobae Sep 5, 2026
4be7f16
test(people): use numeric protocol errors for Decimal tripwires
seonghobae Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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.
Expand Down Expand Up @@ -54,6 +54,7 @@ All notable changes to Orgmetra will be documented in this file.
- Made assignment coverage status-aware: `active` and `leave` remain staffable while `terminated` and other non-eligible employment statuses fail closed.
- Made organization hierarchy reconstruction fail closed on a cycle at the requested tenant, effective day, and knowledge cutoff while ignoring future-recorded and foreign-tenant facts.
- Build the outbox due-work index concurrently during migration 0008, requiring that index step to run outside an explicit transaction block so established queues do not block writers while the index is built; pre-index hardening and post-index privileged role setup use separate explicit transactions.
- Active-PR People mutation commands now require exact built-in governance text and hire status values before digesting or persisting high-impact employment evidence.

### Security

Expand Down
10 changes: 5 additions & 5 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
},
{
"path": "CHANGELOG.md",
"sha256": "f2d2e0b488c0440533effa821808f2f17e37d92f8fb586174c2fdb594f760ca5",
"bytes": 17539,
"lines": 77
"sha256": "9ad6dad273c94c30741522ca87205ff24eb92c53becc8b53739d93acb28126f9",
"bytes": 17697,
"lines": 78
},
{
"path": "CLAUDE.md",
Expand Down Expand Up @@ -353,8 +353,8 @@
},
{
"path": "schemas/openapi.yaml",
"sha256": "09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f",
"bytes": 29503,
"sha256": "c37522504d1f6ac6410eaac833dddbf09aacc85572da38a1cf7539541833ea8e",
"bytes": 29511,
"lines": 1020
},
{
Expand Down
2 changes: 1 addition & 1 deletion schemas/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ components:
format: uuid
allocation_ratio:
type: string
pattern: '^(0\.[0-9]{4}|1\.0000)$'
pattern: '^(0\.(?!0000)[0-9]{4}|1\.0000)$'
effective_from:
type: string
format: date
Expand Down
27 changes: 20 additions & 7 deletions services/people-api/src/orgmetra_people_api/hire.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, replace
from datetime import date
import re
from typing import Protocol, runtime_checkable
Expand All @@ -35,8 +35,8 @@ class HireDecisionIntegrityError(RuntimeError):


def _validate_operational_uuid(field_name: str, value: object) -> None:
"""Require a real UUID outside Orgmetra's reserved protocol sentinels."""
if not isinstance(value, UUID) or value.int in (0, _MAX_UUID_INT):
"""Require an exact UUID outside Orgmetra's reserved protocol sentinels."""
if type(value) is not UUID or value.int in (0, _MAX_UUID_INT):
raise ValueError(f"{field_name} must be an operational UUID.")


Expand Down Expand Up @@ -83,7 +83,7 @@ def __post_init__(self) -> None:
_validate_operational_uuid(field_name, getattr(self, field_name))
if type(self.effective_from) is not date:
raise ValueError("effective_from must be a business date.")
if not isinstance(self.display_name, str):
if type(self.display_name) is not str:
raise ValueError("display_name must be a string.")
try:
self.display_name.encode("utf-8")
Expand All @@ -95,7 +95,7 @@ def __post_init__(self) -> None:
raise ValueError("display_name must not contain control characters.")
validate_idempotency_key(self.idempotency_key)
if (
not isinstance(self.employment_status_code, str)
type(self.employment_status_code) is not str
or _STATUS_CODE_PATTERN.fullmatch(self.employment_status_code) is None
):
raise ValueError("employment_status_code must be a lower snake_case code.")
Expand Down Expand Up @@ -147,8 +147,14 @@ def accept_confirmed_hire(
``materialize_worker`` operation and ``candidate_worker_conversion`` field;
possession of an identity token or purpose string alone is insufficient.
"""
if not isinstance(command, HireAcceptanceCommand):
if type(command) is not HireAcceptanceCommand:
raise TypeError("command must be a HireAcceptanceCommand")
command = replace(command)
expected_person_record_id = UUID(int=command.person_record_id.int)
expected_employment_record_id = UUID(int=command.employment_record_id.int)
expected_conversion_record_id = UUID(
int=command.candidate_worker_conversion_record_id.int
)
if not isinstance(mutation_port, HireAcceptancePort):
raise TypeError("mutation_port must implement HireAcceptancePort")

Expand All @@ -164,6 +170,13 @@ def accept_confirmed_hire(
policy=policy,
)
result = mutation_port.accept_hire(command=command, authorization=authorization)
if not isinstance(result, HireAcceptanceResult):
if type(result) is not HireAcceptanceResult:
raise TypeError("mutation_port must return HireAcceptanceResult")
HireAcceptanceResult.__post_init__(result)
if (
result.person_record_id != expected_person_record_id
or result.employment_record_id != expected_employment_record_id
or result.candidate_worker_conversion_record_id != expected_conversion_record_id
):
raise HireDecisionIntegrityError("hire result identity does not match command")
return result
Loading
Loading