Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
269c911
test: require explicit assignment category contract
seonghobae Sep 2, 2026
5160e55
feat(hris): carry explicit assignment category
seonghobae Sep 2, 2026
e794bb0
feat(hris): enforce explicit primary assignment invariant
seonghobae Sep 2, 2026
67f5fca
feat(people): require assignment category on writes
seonghobae Sep 2, 2026
230736a
chore: apply assignment category integration repair
seonghobae Sep 2, 2026
2d05737
test(assignments): reject new legacy assignment categories
seonghobae Sep 2, 2026
c1cef84
fix(assignments): reserve legacy category for migrated history
seonghobae Sep 2, 2026
4863294
chore(assignments): retire temporary source-fix workflow
seonghobae Sep 2, 2026
a2df987
ci(assignments): prove category persistence boundary
seonghobae Sep 2, 2026
87023a1
feat(assignments): require category at HTTP boundary
seonghobae Sep 2, 2026
7f7c2e8
test(assignments): require explicit HTTP category
seonghobae Sep 2, 2026
9fc12cf
feat(assignments): persist category through PostgreSQL adapter
seonghobae Sep 2, 2026
248948a
test(assignments): require category in OpenAPI contract
seonghobae Sep 2, 2026
8e3afd7
feat(assignments): publish category in OpenAPI
seonghobae Sep 2, 2026
b411285
docs(assignments): record explicit category decision
seonghobae Sep 2, 2026
e6bd170
docs(assignments): index assignment category ADR
seonghobae Sep 2, 2026
a434a45
docs(assignments): align data model with category contract
seonghobae Sep 2, 2026
5bebae5
docs(assignments): publish API category semantics
seonghobae Sep 2, 2026
5984c60
docs(assignments): align ERD category invariant
seonghobae Sep 2, 2026
a48cb62
docs(assignments): trace explicit category contract
seonghobae Sep 2, 2026
2bce218
fix(foundation): inventory assignment migration contract
seonghobae Sep 2, 2026
601152b
fix(foundation): mirror assignment execution inventory
seonghobae Sep 2, 2026
e9f7918
chore(foundation): emit exact assignment manifest repair data
seonghobae Sep 2, 2026
274bcea
chore(assignment): finalize manifest and changelog evidence
seonghobae Sep 2, 2026
c9f275f
test(openapi): bound schema fixtures by exact indentation
seonghobae Sep 2, 2026
2529fd9
chore(assignment): retrigger exact-head finalization
seonghobae Sep 2, 2026
09a2dd5
chore(assignment): seal final evidence
github-actions[bot] Sep 2, 2026
e8886c5
test(assignment): reject non-string category writes
seonghobae Sep 2, 2026
edee1ec
test(red): preserve legacy assignment system-time closure
seonghobae Sep 2, 2026
0530b1c
fix(assignments): preserve legacy system-time closure
seonghobae Sep 2, 2026
10daff6
chore(manifest): reseal assignment category migration
seonghobae Sep 2, 2026
03959f6
docs(adr): keep assignment category decision proposed
seonghobae Sep 2, 2026
02c034b
docs(data): clarify legacy assignment closure guard
seonghobae Sep 2, 2026
1694229
chore(manifest): reseal assignment category documentation
seonghobae Sep 2, 2026
ec18afc
test(assignments): cover classified-to-legacy rejection
seonghobae Sep 2, 2026
9912840
test: reject assignment category runtime spoofing
seonghobae Sep 2, 2026
c49e999
fix: require exact assignment category strings
seonghobae Sep 2, 2026
7487b56
fix: reject assignment category runtime spoofing
seonghobae Sep 2, 2026
de7f24e
fix: backfill assignment category without rewriting history
seonghobae Sep 2, 2026
300bbdb
chore: reseal assignment category migration
seonghobae Sep 2, 2026
9b41472
docs(adr): keep assignment category decision proposed
seonghobae Sep 2, 2026
b86fe90
chore(manifest): reseal ADR status correction
seonghobae Sep 2, 2026
4fe2889
fix(manifest): restore unchanged gitignore digest
seonghobae Sep 2, 2026
2f5c555
test: reject legacy sentinel at assignment write boundary
seonghobae Sep 2, 2026
8ccb112
test: preserve historical assignment validation semantics
seonghobae Sep 2, 2026
046e3fc
test: align People assignment fixture with explicit category
seonghobae Sep 2, 2026
a7a2ae5
fix: reseal assignment category PostgreSQL evidence
seonghobae Sep 2, 2026
c67a54e
fix: repair unrelated manifest digest copy
seonghobae Sep 2, 2026
6213830
test: require validated assignment category constraint
seonghobae Sep 2, 2026
f63fc96
fix(db): validate assignment category check after migration
seonghobae Sep 2, 2026
e9dacb7
chore: reseal assignment category migration evidence
seonghobae Sep 2, 2026
0da0c15
chore: preserve manifest trailing newline
seonghobae Sep 2, 2026
a2c220a
fix(ci): pin assignment category runner image
seonghobae Sep 2, 2026
628ffd4
test: expose assignment aggregate serialization race
seonghobae Sep 2, 2026
d4cc84c
fix: serialize assignment writes by employment aggregate
seonghobae Sep 2, 2026
22c93ef
fix: avoid redundant employment row lock
seonghobae Sep 2, 2026
e23f126
test: keep assignment fixture aligned with category row shape
seonghobae Sep 2, 2026
47372c4
test: prove assignment category migration rolls back atomically
seonghobae Sep 2, 2026
2a0ee95
fix: make assignment category migration atomic
seonghobae Sep 2, 2026
2dc6ef7
chore: reseal assignment category recovery evidence
seonghobae Sep 2, 2026
73e2cf7
fix: restore unrelated manifest digest
seonghobae Sep 2, 2026
9a2e3fd
fix(assignment): enforce category guard before history guard
seonghobae Sep 2, 2026
b2cdabf
chore: reseal assignment migration integrity
seonghobae Sep 2, 2026
473f574
test(people): reject forged persisted assignment categories
seonghobae Sep 2, 2026
2172ce7
fix(people): reject forged persisted assignment categories
seonghobae Sep 2, 2026
aecb978
test: pin assignment category provenance inventory
seonghobae Sep 2, 2026
b8b60d8
test: execute assignment category provenance regression
seonghobae Sep 2, 2026
0726253
fix: seal assignment category governance artifacts
seonghobae Sep 2, 2026
60e3461
fix: align Python provenance inventory
seonghobae Sep 2, 2026
06bd520
fix: seal assignment category provenance
seonghobae Sep 2, 2026
84ca6d2
fix: restore unrelated manifest digest
seonghobae Sep 2, 2026
fb51af2
test(people): reject unknown persisted assignment categories
seonghobae Sep 2, 2026
cb3b20b
fix(people): fail closed on unknown persisted assignment category
seonghobae Sep 2, 2026
a92e94a
docs(assignment): bound category correction claim to shipped behavior
seonghobae Sep 2, 2026
680d7b1
chore(foundation): reseal assignment category ADR provenance
seonghobae Sep 2, 2026
28dd172
test(hris): reject forged assignment allocation ratios
seonghobae Sep 2, 2026
f720870
fix(hris): reject executable Decimal allocation subtypes
seonghobae Sep 2, 2026
68acd1c
test(hris): reject forged seat allocation ratios
seonghobae Sep 2, 2026
f3d044a
fix(hris): guard seat allocation numeric runtime type
seonghobae Sep 2, 2026
ed05f82
test(people): reject forged command allocation ratio
seonghobae Sep 2, 2026
8b4e2cd
fix(people): require exact Decimal allocation command
seonghobae Sep 2, 2026
e27c585
merge(develop): adopt queue-saturation workflow relief
seonghobae Sep 3, 2026
67154ff
test(hris): require explicit assignment category construction
seonghobae Sep 3, 2026
fcfaf73
test(hris): mark historical assignment fixtures explicitly
seonghobae Sep 3, 2026
a80569d
test(hris): classify position-capacity assignment fixtures
seonghobae Sep 3, 2026
c1a93d0
test(hris): classify historical lifecycle fixture explicitly
seonghobae Sep 3, 2026
5d2e340
test(hris): classify workforce boundary fixtures explicitly
seonghobae Sep 3, 2026
c17ed82
test(hris): classify workforce composition fixtures explicitly
seonghobae Sep 3, 2026
d168196
test(hris): classify portfolio fixtures explicitly
seonghobae Sep 3, 2026
3f57b78
fix(hris): require explicit assignment category facts
seonghobae Sep 3, 2026
6801445
fix(ci): cover all assignment category governed artifacts
seonghobae Sep 3, 2026
1f079ee
chore(provenance): reseal assignment category workflow
seonghobae Sep 3, 2026
1a3e146
style(hris): format explicit assignment fixtures
seonghobae Sep 3, 2026
5d4133b
merge(develop): adopt restored documentation contract checks
seonghobae Sep 3, 2026
0d61f5b
test(assignment): cover rejected portfolio categories
seonghobae Sep 6, 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
72 changes: 72 additions & 0 deletions .github/workflows/assignment-category-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Assignment Category Quality

on:
pull_request:
branches:
- bootstrap
- develop
- main
paths:
- "database/migrations/0017_assignment_category_code.sql"
- "docs/adr/0015-explicit-assignment-category.md"
- "docs/API_CONTRACT.md"
- "docs/DATA_MODEL.md"
- "docs/ERD.md"
- "docs/TRACEABILITY.md"
- "manifest.json"
- "packages/hris-kernel/**"
- "schemas/openapi.yaml"
- "services/people-api/**"
- "tests/assignment-category-provenance.test.mjs"
- "tests/openapi-contract.test.mjs"
- "tests/test_assignment_category_postgres.sh"
- "tests/validate_repository.py"
- ".github/workflows/assignment-category-quality.yml"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
workflow_dispatch:

permissions:
contents: read

concurrency:
group: assignment-category-quality-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
postgres:
name: Explicit assignment category persistence contract
runs-on: ubuntu-24.04
timeout-minutes: 10
services:
postgres:
image: postgres:16.14@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20
env:
POSTGRES_USER: orgmetra
POSTGRES_PASSWORD: orgmetra
POSTGRES_DB: orgmetra
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U orgmetra -d orgmetra"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgresql://orgmetra:orgmetra@localhost:5432/orgmetra
steps:
- name: Checkout exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Prove exact candidate checkout
env:
ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA"
- name: Prove assignment category provenance inventory
run: node --test tests/assignment-category-provenance.test.mjs
- name: Prove migration compatibility and new-write invariants
run: bash tests/test_assignment_category_postgres.sh
- name: Require clean checkout
run: |
git diff --exit-code
test -z "$(git status --porcelain)"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to Orgmetra will be documented in this file.

### Added

- Active-PR explicit assignment-category contract: new assignment writes must record `primary` or `concurrent_secondary`; historical pre-contract rows remain `legacy_unspecified` without allocation-, row-order-, or topology-based guessing. The People API, idempotency digest, PostgreSQL persistence, OpenAPI contract, and bitemporal tenant/Employment invariant carry the same governed meaning.
- Accepted ADRs 0001–0003 now include buyer-facing Context, Decision, and Consequences grounded in verified ISO 30400:2022, ISO 30414:2025, Uniform Guidelines (29 C.F.R. Part 1607), SIOP (2018), OpenAPI Specification v3.2.0, OpenID Connect Core 1.0 errata set 2, CloudEvents v1.0.2, Jensen and Snodgrass (1999), Snodgrass (1999), and Allen (1983) records already listed in `docs/doctoring/REFERENCES.md`. ADRs 0004 and 0005 gained APA 7th References pointers to that same bibliography without changing their Decision bodies.
- Active-PR governed Job Analysis persistence/API on the canonical `JobAnalysisSnapshot` model: migration `0013_job_analysis_snapshot.sql` stores immutable tenant-scoped snapshot, Task, KSAO, Task–KSAO, FJA and write-command evidence; `POST /v1/tenants/{tenant_record_id}/job-analysis-snapshots` and matching GET enforce purpose-bound Keyverse scope, authenticated-principal actor authority, bounded/strict JSON handling, transactional Idempotency-Key serialization, parent-scope fail-closed integrity, forced RLS, and atomic audit/outbox evidence. ADR 0014 records the persistence decision while ADR 0007 remains the domain/evidence authority; validated evidence still requires accountable human review and non-LLM provenance, and the service does not make a high-impact employment decision.
- Active-PR `orgmetra_selection_review` packet for PII-minimized, evidence-bound human selection review: canonical operational tenant identity, UUID-backed opaque candidate/Job/sealed-evidence/reviewer references, explicit purpose/reason/evidence version, deterministic canonical JSON and SHA-256 correlation, mandatory human decision state, redacted packet repr, and provenance-paired model evidence that remains `untrusted_draft`, with exact 100% owned statement and branch coverage required by its quality gate.
Expand Down
80 changes: 80 additions & 0 deletions database/migrations/0017_assignment_category_code.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
-- Record assignment role classification as authoritative HRIS truth.
-- Historical rows are preserved explicitly; no allocation/order heuristic is allowed.
-- The constant default materializes the historical sentinel for pre-contract rows
-- without issuing an UPDATE that would violate the existing bitemporal history
-- guard. Dropping the default immediately keeps every post-contract write explicit.
-- legacy_unspecified is migration provenance only. The table constraint preserves
-- that historical sentinel so system-time closure remains possible, while the
-- write guard rejects introduction of the sentinel on a new row or by changing
-- an already classified row back to legacy state.

BEGIN;

SET LOCAL search_path = public, pg_catalog;

ALTER TABLE public.assignment_record
ADD COLUMN assignment_category_code text NOT NULL DEFAULT 'legacy_unspecified';

ALTER TABLE public.assignment_record
ALTER COLUMN assignment_category_code DROP DEFAULT;

ALTER TABLE public.assignment_record
ADD CONSTRAINT assignment_record_category_code_check
CHECK (assignment_category_code IN ('legacy_unspecified', 'primary', 'concurrent_secondary')) NOT VALID;

-- Add the constraint without the strongest validation-time table lock, then
-- prove every migrated historical row conforms before the migration completes.
ALTER TABLE public.assignment_record
VALIDATE CONSTRAINT assignment_record_category_code_check;

CREATE FUNCTION public.enforce_assignment_category_write()
RETURNS trigger
LANGUAGE plpgsql
SET search_path = pg_catalog, public
AS $$
BEGIN
IF NEW.assignment_category_code = 'legacy_unspecified' THEN
IF TG_OP = 'INSERT' THEN
RAISE EXCEPTION 'assignment_record_category_code_check: legacy_unspecified is migration provenance only'
USING ERRCODE = 'check_violation',
CONSTRAINT = 'assignment_record_category_code_check',
TABLE = 'assignment_record',
SCHEMA = 'public';
ELSIF OLD.assignment_category_code IS DISTINCT FROM 'legacy_unspecified' THEN
RAISE EXCEPTION 'assignment_record_category_code_check: classified assignment cannot become legacy_unspecified'
USING ERRCODE = 'check_violation',
CONSTRAINT = 'assignment_record_category_code_check',
TABLE = 'assignment_record',
SCHEMA = 'public';
END IF;
END IF;

RETURN NEW;
END;
$$;

COMMENT ON FUNCTION public.enforce_assignment_category_write() IS
'Rejects new or retroactively introduced legacy_unspecified assignment categories while preserving pre-contract system-time history.';

-- PostgreSQL fires same-kind triggers in name order. This name intentionally
-- sorts before assignment_record_bitemporal_guard so a classified-to-sentinel
-- rewrite reports the category invariant before the generic history guard.
CREATE TRIGGER assignment_record_authoritative_category_guard
BEFORE INSERT OR UPDATE OF assignment_category_code ON public.assignment_record
FOR EACH ROW
EXECUTE FUNCTION public.enforce_assignment_category_write();

COMMENT ON TRIGGER assignment_record_authoritative_category_guard ON public.assignment_record IS
'Keeps legacy_unspecified as migration provenance instead of a writable assignment classification.';

ALTER TABLE public.assignment_record
ADD CONSTRAINT assignment_record_primary_bitemporal_exclusion
EXCLUDE USING gist (
tenant_record_id WITH =,
employment_record_id WITH =,
daterange(effective_from, effective_to, '[)') WITH &&,
tstzrange(recorded_from, recorded_to, '[)') WITH &&
)
WHERE (assignment_category_code = 'primary');

COMMIT;
8 changes: 5 additions & 3 deletions docs/API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ High-impact commands additionally require:

For confirmed-hire materialization, those high-impact facts are resolved from the exact already-sealed `selection_decision` and its evidence set inside the tenant-bound transaction rather than accepted again as mutable request-body assertions.

The server rejects a reused idempotency key when its method, resource, tenant, actor, purpose, or semantic command digest differs. People employment, position, assignment, and confirmed-hire writes persist that digest on `people_mutation_idempotency_record` in the same transaction as the authoritative HRIS fact and audit/outbox pair. A matching retry returns the first committed record identity without duplicating authoritative or audit/outbox facts. Generated record identifiers are excluded from the employment/position/assignment digest so a retried POST that allocates fresh UUIDs still replays; the confirmed-hire route requires the caller to repeat the exact confirmed identities and rejects a same-key command whose materialization identities differ.
Assignment creation additionally requires `assignment_category_code` with exactly `primary` or `concurrent_secondary`. The API never accepts `legacy_unspecified` for a new command and never derives category from allocation percentage, row order, position identity, or graph topology. At one tenant/employment/effective/system-time coordinate, creating a second visible `primary` fails closed; additional concurrent work must be recorded explicitly as `concurrent_secondary` while still satisfying employment, position, and allocation-portfolio invariants.

The server rejects a reused idempotency key when its method, resource, tenant, actor, purpose, or semantic command digest differs. People employment, position, assignment, and confirmed-hire writes persist that digest on `people_mutation_idempotency_record` in the same transaction as the authoritative HRIS fact and audit/outbox pair. Assignment category is part of the assignment semantic digest, so changing `primary` to `concurrent_secondary` under the same key is an idempotency conflict. A matching retry returns the first committed record identity without duplicating authoritative or audit/outbox facts. Generated record identifiers are excluded from the employment/position/assignment digest so a retried POST that allocates fresh UUIDs still replays; the confirmed-hire route requires the caller to repeat the exact confirmed identities and rejects a same-key command whose materialization identities differ.

## Example endpoints

Expand All @@ -60,7 +62,7 @@ POST /v1/criterion-observations
POST /v1/validity-studies
```

The foundation OpenAPI contract covers the shared command vocabulary and baseline person, employment, position, assignment, job-profile, and selection-decision operations. Runtime services must publish any additional path-specific contract before release and may not weaken the shared `Idempotency-Key`, least-privilege scope, authorization, evidence, or error semantics. Employment and assignment writes fail closed when exclusive jobs overlap, a seat is not staffable, or visible seat allocations exceed 1.0000.
The foundation OpenAPI contract covers the shared command vocabulary and baseline person, employment, position, assignment, job-profile, and selection-decision operations. Runtime services must publish any additional path-specific contract before release and may not weaken the shared `Idempotency-Key`, least-privilege scope, authorization, evidence, or error semantics. Employment and assignment writes fail closed when exclusive jobs overlap, a seat is not staffable, visible seat allocations exceed 1.0000, assignment category is not explicit, or two primary assignment intervals overlap for the same tenant-local employment.

## Error shape

Expand All @@ -73,4 +75,4 @@ The foundation OpenAPI contract covers the shared command vocabulary and baselin
}
```

`support_reference` is a randomly generated client-safe lookup key. It maps to restricted internal telemetry but never encodes or exposes an internal trace/span identifier, topology, timestamp, tenant identifier, credential, or PII.
`support_reference` is a randomly generated client-safe lookup key. It maps to restricted internal telemetry but never encodes or exposes an internal trace/span identifier, topology, timestamp, tenant identifier, credential, or PII.
6 changes: 4 additions & 2 deletions docs/DATA_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
| `job_profile_version` | Bitemporal title, family, and version definition for a job profile. |
| `position_record` | Durable seat identity that keeps stable organization and job references. |
| `position_record_version` | Bitemporal position status and effective period. |
| `assignment_record` | A person's allocation to a position through one employment. |
| `assignment_record` | A person's allocation and explicit primary/concurrent-secondary category for a position through one employment. |
| `candidate_profile` | Applicant/candidate record before hire. |
| `candidate_worker_link` | Legacy append-only candidate-to-worker linkage retained for historical reads; new writes use `candidate_worker_conversion_record`. |
| `candidate_worker_conversion_record` | Governed bitemporal candidate-to-worker conversion bound to the hire decision, person, employment, immutable audit event, and outbox evidence. |
Expand Down Expand Up @@ -54,6 +54,8 @@ Durable anchors such as `organization_unit`, `job_profile`, `employment_record`,

Assignments remain a legitimately multiple-membership fact. Each assignment must name the covering employment and the same person as that employment. Exclusive employments for one person cannot overlap; a second job must be marked `concurrent`. Allocation totals for one employment, and visible allocations for one position, are enforced by `orgmetra_hris_kernel` rather than a single-valued exclusion. An assignment day must also land on an `active` or `open` position version.

`assignment_category_code` records a different invariant from allocation. Every new assignment write must state `primary` or `concurrent_secondary`; allocation percentage, row order, position identity, and graph topology are never used to infer the category. Rows created before migration 0017 are explicitly preserved as `legacy_unspecified`. The storage CHECK admits only the three known values so those historical rows can still undergo a legitimate system-time closure, while the dedicated assignment-category write guard rejects introducing `legacy_unspecified` on INSERT or changing an already classified assignment back to that sentinel. Replacement/current writes remain limited to `primary | concurrent_secondary`. A tenant/employment-scoped partial GiST exclusion over effective and recorded ranges allows multiple concurrent assignments while rejecting two simultaneously visible `primary` assignments. This keeps the category on the normalized assignment fact instead of denormalizing a mutable current-primary pointer, and localizes exclusion conflicts to one tenant-local employment portfolio.

## High-impact decision evidence

Evidence membership is constructed in `selection_decision_evidence` while its `decision_evidence_set` is open. An open set has no caller-supplied content digest. Finalizing `selection_decision` requires at least one versioned evidence member, canonicalizes the members by `(evidence_reference, evidence_version_code)`, computes SHA-256 inside PostgreSQL, and atomically stores that digest while binding `sealed_selection_decision_id`. Database triggers reject later evidence inserts, second-decision reuse, arbitrary post-seal mutation, and a sealed-set pointer that does not resolve back to the decision that consumed that exact set. This makes the stored digest evidence about database-observed membership at finalization rather than an unverified client assertion.
Expand All @@ -62,7 +64,7 @@ New predictive-validity membership uses `validity_study_case_record` rather than

## People mutation idempotency

`people_mutation_idempotency_record` is the durable retry boundary for governed candidate-worker conversion, Employment, Position, and Assignment mutations. Its unique business key is `(tenant_record_id, command_route, idempotency_key)`; the row stores the canonical semantic-command SHA-256 digest and the first committed created-record identity. Matching retries replay that identity, while a changed command under the same tenant/route/key fails closed instead of creating another HRIS fact.
`people_mutation_idempotency_record` is the durable retry boundary for governed candidate-worker conversion, Employment, Position, and Assignment mutations. Its unique business key is `(tenant_record_id, command_route, idempotency_key)`; the row stores the canonical semantic-command SHA-256 digest and the first committed created-record identity. Matching retries replay that identity, while a changed command under the same tenant/route/key fails closed instead of creating another HRIS fact. Assignment category participates in that semantic digest, so changing `primary` to `concurrent_secondary` under the same Idempotency-Key is a conflict rather than an idempotent replay.

The owning write port acquires an exact-key transaction-scoped advisory lock and writes the HRIS fact, immutable audit/outbox evidence, and idempotency row inside one PostgreSQL transaction. A rolled-back mutation therefore cannot leave a false replay marker. The relation is append-only, TRUNCATE-protected, tenant-RLS isolated, and uses opaque operational UUIDs. The idempotency key is transport correlation, not HR data or authorization evidence; actor, purpose, human-confirmation and resource authorization remain independently required.

Expand Down
Loading
Loading