Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
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

- Versioned the new employing-organization command boundary: legacy `/v1` terminated payloads remain readable, active/leave V1 payloads without employer facts fail with migration guidance, and employer-required Employment and confirmed-hire writes use `/v2` without weakening the exact-one legal-employer invariant.
- Active-PR employment employing-organization truth: bitemporal, tenant-qualified `employment_employing_organization_record` keeps legal-employer identity independent from Position and Assignment, requires exactly one legal employer for every active/leave Employment coordinate without effective gaps, requires `legal_entity` organization classification and active/leave Employment coverage, persists the relationship from both People employment and confirmed-hire transactions, preserves correction history, and proves forced-RLS isolation. ADR-0141 records the bounded HRIS slice; payroll, statutory-account, compensation, and autonomous employment decisions remain out of scope.
- 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.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ Job evidence

## Status

Protected `develop` includes the employment-truth kernel, governed candidate-to-worker conversion, purpose-bound PII authorization, normalized worker-bound validity studies, criterion-observation scope, bitemporal workforce-composition evidence, the governed Naruon intent adapter, and requisition review packets. This active PR adds durable purpose-bound People mutation and confirmed-hire materialization paths for Employment, Position, and Assignment with atomic audit/outbox evidence and tenant-scoped idempotency; treat those write paths as active-PR truth until this exact head passes all fresh protected-base gates and merges.
Protected `develop` includes the employment-truth kernel, governed candidate-to-worker conversion, purpose-bound PII authorization, normalized worker-bound validity studies, criterion-observation scope, bitemporal workforce-composition evidence, the governed Naruon intent adapter, and requisition review packets. This active PR adds durable purpose-bound People mutation and confirmed-hire materialization paths for Employment, Position, and Assignment with atomic audit/outbox evidence and tenant-scoped idempotency; employer-required Employment and confirmed-hire writes use `/v2`, while legacy `/v1` terminated payloads remain readable and missing-employer active/leave payloads fail with migration guidance. Treat those write paths as active-PR truth until this exact head passes all fresh protected-base gates and merges.
13 changes: 10 additions & 3 deletions docs/API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

## Versioning

Orgmetra APIs use OpenAPI 3.2.0. Major versions are path-scoped under `/v1` until a breaking contract requires `/v2`.
Orgmetra APIs use OpenAPI 3.2.0. Major versions are path-scoped. The existing
`/v1` Employment and confirmed-hire routes retain their former wire shape so
legacy terminated writes remain readable; an `/v1` active or leave write that
lacks an employing organization is rejected with migration guidance. The
employer-required contracts are available under `/v2` and use
`CreateEmploymentRecordCommandV2` for Employment.

## Authentication

Expand Down Expand Up @@ -35,7 +40,7 @@ Every mutating request requires:
- resource-scoped authorization; and
- a command digest stored with the idempotency record.

Employment, position, assignment, person, job-profile, and selection-decision commands carry tenant, actor, and purpose through the reusable `X-Tenant-Reference`, `X-Actor-Reference`, and `X-Purpose-Code` components. The confirmed-hire route instead binds tenant in `/v1/tenants/{tenant_record_id}/candidate-worker-conversions`, purpose in the required query parameter, and actor through the authenticated principal; those path/query/authentication bindings are authoritative for that route and are not duplicated as weaker caller-controlled headers.
Employment, position, assignment, person, job-profile, and selection-decision commands carry tenant, actor, and purpose through the reusable `X-Tenant-Reference`, `X-Actor-Reference`, and `X-Purpose-Code` components. The confirmed-hire route instead binds tenant in `/v1` or `/v2` `/tenants/{tenant_record_id}/candidate-worker-conversions`, purpose in the required query parameter, and actor through the authenticated principal; those path/query/authentication bindings are authoritative for that route and are not duplicated as weaker caller-controlled headers.

High-impact commands additionally require:

Expand All @@ -47,7 +52,7 @@ 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.

Employment creation requires `employing_organization_unit_id` and atomically records the bitemporal employing-organization relationship for active and leave Employment versions. Confirmed-hire materialization requires the employing organization and relationship record identities in its explicit command, and persists that relationship in the same transaction as the Person, Employment, conversion, and audit/outbox evidence.
The `/v2` Employment and confirmed-hire commands require `employing_organization_unit_id` and atomically record the bitemporal employing-organization relationship for active and leave Employment versions. The `/v1` commands preserve the former terminated payload; they do not weaken the database invariant, so active and leave payloads without employer facts fail before persistence with a `/v2` migration action. Confirmed-hire materialization persists the employer relationship in the same transaction as the Person, Employment, conversion, and audit/outbox evidence.

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.

Expand All @@ -58,6 +63,8 @@ POST /v1/person-records
GET /v1/person-records/{person_record_id}
POST /v1/tenants/{tenant_record_id}/candidate-worker-conversions?purpose=candidate_hire
POST /v1/employment-records
POST /v2/tenants/{tenant_record_id}/candidate-worker-conversions?purpose=candidate_hire
POST /v2/employment-records
POST /v1/position-records
POST /v1/assignment-records
POST /v1/job-profiles
Expand Down
2 changes: 1 addition & 1 deletion docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Authorization evidence contains only governance metadata, including the opaque a

## Mutation security contract

Every mutating HTTP operation and its server-side command handler requires one validated `Idempotency-Key` that crosses the command boundary into durable transactional replay state. The published OpenAPI employment, position, assignment, person, job-profile, and selection-decision command families require `X-Tenant-Reference`, `X-Actor-Reference`, and `X-Purpose-Code`; those values must match the authenticated Keyverse principal and the operation-specific least-privilege scope. The executable People mutation handlers added on this branch currently implement employment, position, and assignment creation with those headers. Person, job-profile, and selection-decision remain published foundation API contracts until their server handlers are integrated; their OpenAPI presence is not runtime evidence. Confirmed-hire materialization instead binds the tenant in `/v1/tenants/{tenant_record_id}/candidate-worker-conversions`, the business purpose in its exact query parameter, and the actor through the authenticated principal. It does not accept weaker duplicate actor/tenant/purpose header authorities.
Every mutating HTTP operation and its server-side command handler requires one validated `Idempotency-Key` that crosses the command boundary into durable transactional replay state. The published OpenAPI employment, position, assignment, person, job-profile, and selection-decision command families require `X-Tenant-Reference`, `X-Actor-Reference`, and `X-Purpose-Code`; those values must match the authenticated Keyverse principal and the operation-specific least-privilege scope. The executable People mutation handlers added on this branch currently implement employment, position, and assignment creation with those headers. Person, job-profile, and selection-decision remain published foundation API contracts until their server handlers are integrated; their OpenAPI presence is not runtime evidence. Confirmed-hire materialization binds the tenant in `/v1` or `/v2` `/tenants/{tenant_record_id}/candidate-worker-conversions`, the business purpose in its exact query parameter, and the actor through the authenticated principal. It does not accept weaker duplicate actor/tenant/purpose header authorities. Legacy `/v1` active or leave payloads without employer facts are rejected before persistence; `/v2` requires the employer target and its relationship identity.

All mutation families additionally require resource-scoped authorization and a versioned audit/provenance correlation reference. High-risk commands require an explicit human-confirmation boundary and immutable versioned evidence. Employment, position, and assignment commands carry confirmation/evidence on the command. Confirmed-hire materialization resolves the exact previously sealed `selection_decision` in the same tenant-bound transaction and rejects the mutation unless that decision records explicit human confirmation and sealed evidence provenance.

Expand Down
1 change: 1 addition & 0 deletions docs/TEST_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Required negative and provenance tests include:
- mutation authentication and tenant binding occur before request-body reads or identifier allocation, so unauthenticated input cannot consume parser or persistence work;
- a reused confirmation or idempotency key cannot bind to different command content;
- an identical tenant/route/idempotency-key retry replays the first committed created-record identity rather than issuing a duplicate authoritative write;
- legacy `/v1` terminated Employment and confirmed-hire payloads remain parseable, while employer-required active/leave commands use `/v2` and V1 missing-employer requests fail before persistence with migration guidance;
- concurrent exact-key requests serialize at the persistence boundary and cannot commit two different identities;
- previewed evidence versions must equal recorded evidence versions;
- an open evidence set rejects a caller-supplied digest, preventing a client assertion from masquerading as database-observed membership;
Expand Down
2 changes: 1 addition & 1 deletion docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
| Evidence-backed human selection decisions | Talent Acquisition | `decision_evidence_set`, `selection_decision_evidence`, `selection_decision` | database-owned SHA-256 sealing, non-empty evidence, drift/reuse rejection, OpenAPI human-confirmation tests | ADR-0001 | implemented_on_active_pr |
| Governed candidate-to-worker conversion | Talent Acquisition / People core | `candidate_worker_conversion_record` with candidate, person, employment, selection decision, audit event and outbox evidence | PostgreSQL exact hire/evidence/audit-envelope binding, correction provenance, tenant RLS, legacy-write rejection and bitemporal history contract | ADR-0001, ADR-0003, ADR-0006 | implemented_on_protected_main |
| GET-only People API | People API / purpose-bound read boundary | `GET /v1/tenants/{tenant_record_id}/people/{person_record_id}`, `read_worker_people_record()`, `PostgresPeopleReadPort` | People API HTTP and PostgreSQL read contracts with exact 100% owned statement/branch coverage; current conversion lineage; no mutation writes | ADR-0002, ADR-0008 | implemented_on_protected_main |
| Governed People writes and confirmed-hire materialization | People API / purpose-bound mutation boundary | `POST /v1/employment-records`, `POST /v1/position-records`, `POST /v1/assignment-records`, `POST /v1/tenants/{tenant_record_id}/candidate-worker-conversions`, `people_mutation_idempotency_record` | People command/HTTP/PostgreSQL contracts with exact owned statement/branch coverage plus PostgreSQL tenant-RLS, atomic audit/outbox/idempotency, identical-retry replay, changed-command rejection, rollback, and concurrent-key regression | ADR-0002, ADR-0006, ADR-0008 | implemented_on_protected_main |
| Governed People writes and confirmed-hire materialization | People API / purpose-bound mutation boundary | `POST /v1/employment-records`, `POST /v2/employment-records`, `POST /v1/position-records`, `POST /v1/assignment-records`, `POST /v1` and `/v2` `/tenants/{tenant_record_id}/candidate-worker-conversions`, `people_mutation_idempotency_record` | People command/HTTP/PostgreSQL contracts with exact owned statement/branch coverage plus PostgreSQL tenant-RLS, atomic audit/outbox/idempotency, identical-retry replay, changed-command rejection, rollback, concurrent-key regression, and V1-to-V2 compatibility coverage | ADR-0002, ADR-0006, ADR-0008, ADR-0141 | implemented_on_active_pr |
| Evidence-grounded Job analysis with governed Task/FJA/KSAO persistence | Job Analysis / Workforce Validation | `JobAnalysisSnapshot`, `TaskEvidence`, `KSAORequirement`, `FunctionalJobAnalysisProfile`, `TaskKSAOLink`, `EvidenceSource`, `job_analysis_snapshot`, `job_analysis_task_item`, `job_analysis_ksao_item`, `job_analysis_task_ksao_link`, `job_analysis_write_command`, `POST /v1/tenants/{tenant_record_id}/job-analysis-snapshots`, `GET /v1/tenants/{tenant_record_id}/job-analysis-snapshots/{analysis_record_id}` | domain tenant/Job isolation, source/version/digest provenance, task-KSAO completeness, deterministic canonicalization, accountable human-review and LLM-draft-only regressions; migration 0013 PostgreSQL parent-scope/RLS/append-only/idempotency/audit-outbox persistence; exact route/OpenAPI/error contracts and 100% owned service statement/branch coverage | ADR-0007, ADR-0014 | implemented_on_active_pr |
| Job-, cycle-, and staffing-scoped performance criterion observations | Performance / Workforce Validation | `criterion_observation`, `criterion_blueprint`, `performance_cycle`, `assignment_record`, `employment_record_version`, `position_record`, `position_record_version` | PostgreSQL wrong-Job, pre-assignment, out-of-cycle, frozen-Position, terminated-employment, closed-recorded-time, and session-TimeZone/UTC-midnight rejection plus valid worker-Job/staffing acceptance | ADR-0009 | implemented_on_protected_main |
| Governed immutable audit and transactional outbox persistence | Audit Provenance / Integration Hub | `AuditOutboxEvent.canonical_json()`, `audit_event_record`, `outbox_delivery_record`, SHA-256 envelope digest | canonical-byte/digest regression plus PostgreSQL digest, allowlist/PII, high-impact confirmation, append-only, atomicity, lease-transition, terminal-state, and reserved-UUID tests | ADR-0006 | implemented_on_active_pr |
Expand Down
2 changes: 2 additions & 0 deletions docs/adr/0141-employment-employing-organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Because the exact-one rule spans Employment versions, employer relationships, an

The relationship is independent of Position and Assignment. It stores no Person PII, compensation, payroll, tax, benefits, statutory-account, candidate, performance, or model-output fields.

The API compatibility boundary is explicit. Existing `/v1` Employment and confirmed-hire payloads remain available for legacy terminated writes, while an active or leave `/v1` payload without employer facts fails before persistence with migration guidance. Employer-required writes use `/v2`; this keeps the exact-one database invariant intact without silently inventing a legal employer or changing the meaning of an existing V1 request.

History is correction-not-rewrite: business fields cannot be updated in place; the current recorded interval may only be closed and a replacement fact inserted. DELETE and TRUNCATE are rejected. Tenant-qualified foreign keys and forced RLS independently protect cross-tenant integrity and visibility.

## Consequences
Expand Down
2 changes: 2 additions & 0 deletions docs/doctoring/employment-employing-organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ The active PR instead records a tenant-scoped, bitemporal `employment_employing_

The People employment mutation and confirmed-hire materializer write this relationship in the same tenant-bound transaction that creates the Employment version. Terminated Employment creation does not create a relationship row because the database contract requires employers only for active/leave Employment coordinates.

The API boundary follows the versioning and path/schema semantics of OpenAPI Specification v3.2.0: the former `/v1` payload remains available for legacy terminated writes, while employer-required Employment and confirmed-hire commands are exposed under `/v2`. An active or leave V1 request without employer facts is rejected before persistence with a V2 migration action, preserving both legacy wire compatibility and the exact-one database invariant.

This is an HRIS source-of-truth relationship, not a claim of payroll or statutory-system ownership.

## Reference (APA 7)
Expand Down
Loading