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
66 changes: 66 additions & 0 deletions .github/workflows/employment-history-http-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Employment History HTTP Quality

on:
pull_request:
branches:
- develop
- feat/people-employment-history-read
paths:
- "services/people-api/**"
- "packages/hris-kernel/**"
- "packages/keyverse-adapter/**"
- "schemas/openapi.yaml"
- ".github/requirements/foundation-test.txt"
- ".github/workflows/employment-history-http-quality.yml"
- "docs/API_CONTRACT.md"
- "docs/SECURITY.md"
- "docs/TEST_STRATEGY.md"
- "docs/TRACEABILITY.md"
- "docs/adr/0155-employment-history-http-read.md"
- "docs/doctoring/employment-history-http-read-references.md"
- "docs/traceability/employment-history-http-read.md"
- "services/people-api/README.md"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: employment-history-http-quality-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
unit:
name: Employment-history HTTP read contract
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Prove exact candidate checkout
env:
ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA"
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
check-latest: false
- name: Install reviewed test toolchain
run: |
python -m pip install --require-hashes --no-deps --only-binary=:all: -r .github/requirements/foundation-test.txt
python -m pip check
- name: Compile People API boundary
run: python -m compileall -q services/people-api/src packages/hris-kernel/src packages/keyverse-adapter/src services/people-api/tests
- name: Test governed People contracts with exact statement and branch coverage
env:
PYTHONPATH: services/people-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src
COVERAGE_FILE: /tmp/orgmetra-employment-history-http.coverage
run: python -m pytest -c services/people-api/pyproject.toml services/people-api/tests
- 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 @@ -7,6 +7,7 @@ All notable changes to Orgmetra will be documented in this file.
### Added

- 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 stacked PR #155 adds the customer-callable `EmploymentHistoryAsgiApp` read route, `GET /v1/tenants/{tenant_record_id}/people/{person_record_id}/employment-history`, with exact UTC knowledge-cutoff parsing, purpose/field authorization through the existing Employment-history service, minimized entries, no-store response controls, client-safe errors, a published OpenAPI 3.2 contract, and a dedicated exact-head 100% People API quality workflow. It does not mutate Employment or make an employment decision.
- 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.
- 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.
Expand Down
5 changes: 3 additions & 2 deletions docs/API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ POST /v1/tenants/{tenant_record_id}/candidate-worker-conversions?purpose=candida
POST /v1/employment-records
POST /v1/position-records
POST /v1/assignment-records
GET /v1/tenants/{tenant_record_id}/people/{person_record_id}/employment-history?known_at=...&purpose=...&fields=...
POST /v1/job-profiles
POST /v1/job-profiles/{job_profile_id}/publish
POST /v1/candidate-profiles
Expand All @@ -60,7 +61,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, selection-decision, and purpose-bound Employment-history read 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 Employment-history read requires `orgmetra.people.employment_history.read`, an exact `known_at` UTC cutoff, a business purpose, and an explicit field list; it returns only policy-authorized entries and never mutates HRIS truth.

## Error shape

Expand All @@ -73,4 +74,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.
10 changes: 10 additions & 0 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ Evaluation fails closed unless request, actor, resource, and policy tenants all

Authorization evidence contains only governance metadata, including the opaque actor and exact target-resource references, plus field names, never protected values. A denial returns a stable reason code and next safe action. An allow decision returns only the exact requested field subset, not every field the policy could permit. Both allow and denial evidence preserve the exact target reference so immutable audit correlation cannot collapse distinct person or employment records into one resource-kind-level event. These rules implement the Orgmetra side of the NIST SP 800-162 ABAC shape and attribute-integrity principles from NIST SP 800-205; ADR 0008 records the boundary.

The Employment-history HTTP read applies this boundary at the customer route:
`GET /v1/tenants/{tenant_record_id}/people/{person_record_id}/employment-history`.
It validates the tenant, Person, UTC `known_at`, purpose, and explicit fields
before authentication; then the authenticated principal and
`orgmetra.people.employment_history.read` scope are evaluated by the existing
Employment-history service. The response contains only authorized
`entries[].fields`, uses `Cache-Control: no-store` and `Vary: Authorization`,
and maps integrity or unexpected backend failures to client-safe opaque support
references. The route adds no mutation or employment-decision authority.

## 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.
Expand Down
1 change: 1 addition & 0 deletions docs/TEST_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The command runs Python repository-integrity validation, the dependency-free Nod
| Performance criterion observation Job, cycle, staffing, current-recorded-time, and UTC date-boundary integrity | `bash tests/test_criterion_observation_scope_postgres.sh` against PostgreSQL 16 in Foundation CI |
| Governed People mutation idempotency: tenant/route/key uniqueness, identical-command replay, changed-command rejection, rollback safety, append-only/TRUNCATE protection, forced RLS and concurrent exact-key serialization | `bash tests/test_people_mutation_idempotency_postgres.sh` against PostgreSQL 16 in Foundation CI |
| Tenant/actor/purpose authorization matrix and negative high-impact commands | service-specific unit and integration test commands recorded in each service package |
| Employment-history HTTP read parsing, authentication order, purpose/field authorization, bitemporal cutoff forwarding, response minimization, client-safe errors, and exact 100% statement/branch coverage | `PYTHONPATH=services/people-api/src:packages/hris-kernel/src:packages/keyverse-adapter/src python -m pytest -c services/people-api/pyproject.toml services/people-api/tests` and `.github/workflows/employment-history-http-quality.yml` |
| AsyncAPI/CloudEvents envelope compatibility | provider and consumer contract test commands recorded beside the versioned event schema |
| External adapter timeout, malformed response, tenant mismatch, and unavailable-state handling | fake-server tests in each adapter package |
| Role-workspace keyboard, focus, exact-value, permission-denied, and confirmation states | Storybook interaction/a11y tests plus browser E2E for the owning workspace |
Expand Down
1 change: 1 addition & 0 deletions docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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 |
| Purpose-bound Employment-history HTTP read | People API / customer read boundary | `GET /v1/tenants/{tenant_record_id}/people/{person_record_id}/employment-history`, `EmploymentHistoryAsgiApp`, `read_employment_history()`, `EmploymentHistoryReadPort` | exact path/query validation before authentication; Keyverse scope and purpose/field authorization; UTC bitemporal cutoff; minimized entries; client-safe 400/401/403/409/500 errors; exact 100% People API statement/branch coverage | ADR-0008, ADR-0149, ADR-0155 | implemented_on_active_pr |
| 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 |
| 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 |
Expand Down
28 changes: 27 additions & 1 deletion docs/UML.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,32 @@ flowchart LR

The cluster is physically shared in the initial modular deployment. Each bounded context has a separate schema and role; direct reads of another context's application tables are prohibited.

## Employment-history read sequence

```mermaid
sequenceDiagram
actor HRUser as Authorized HR user
participant Gateway
participant PeopleHTTP as EmploymentHistoryAsgiApp
participant Auth as Keyverse authentication
participant Service as Employment-history service
participant Store as Injected read port

HRUser->>Gateway: GET Person Employment history(tenant, known_at, purpose, fields)
Gateway->>PeopleHTTP: Forward one versioned request
PeopleHTTP->>PeopleHTTP: Validate route/query before authentication
PeopleHTTP->>Auth: Authenticate one Bearer credential
Auth-->>PeopleHTTP: Principal and operation scope
PeopleHTTP->>Service: Authorize exact target and requested fields
Service->>Store: Read tenant/Person history at known_at
Store-->>Service: Bitemporal Employment versions
Service-->>PeopleHTTP: Authorized entries only
PeopleHTTP-->>HRUser: No-store JSON response or safe error
```

The route is read-only and does not query another service's application tables,
mutate Employment truth, or make an employment decision.

## Selection decision sequence

```mermaid
Expand Down Expand Up @@ -119,4 +145,4 @@ sequenceDiagram
PeopleCore->>Audit: Persist assignment, audit/outbox, and idempotency binding
PeopleCore-->>Gateway: assignment_record Location
Gateway-->>HROps: Review the roster, then approve or correct
```
```
Loading