From 42cef9feb60ec952fc0b6ec167f5b8df3ca1687d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:05:19 -0700 Subject: [PATCH 01/50] test(hris): require employing organization bitemporal truth --- ...loyment_employing_organization_postgres.sh | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 tests/test_employment_employing_organization_postgres.sh diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh new file mode 100644 index 000000000..77103ccbd --- /dev/null +++ b/tests/test_employment_employing_organization_postgres.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0001_foundation_schema.sql +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0040_employment_employing_organization.sql + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +INSERT INTO tenant_record (tenant_record_id, tenant_reference) VALUES + ('10000000-0000-7000-8000-000000000001', 'tenant_alpha'), + ('20000000-0000-7000-8000-000000000001', 'tenant_beta'); + +INSERT INTO person_record (tenant_record_id, person_record_id) VALUES + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000010'), + ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000010'); + +INSERT INTO employment_record (tenant_record_id, employment_record_id, person_record_id) VALUES + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000020', '10000000-0000-7000-8000-000000000010'), + ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000020', '20000000-0000-7000-8000-000000000010'); + +INSERT INTO employment_record_version ( + tenant_record_id, employment_record_version_id, employment_record_id, + employment_status_code, effective_from, effective_to, recorded_from +) VALUES + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000021', '10000000-0000-7000-8000-000000000020', 'active', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000021', '20000000-0000-7000-8000-000000000020', 'active', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'); + +INSERT INTO organization_unit (tenant_record_id, organization_unit_id) VALUES + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000030'), + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000031'), + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000032'), + ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000030'); + +INSERT INTO organization_unit_version ( + tenant_record_id, organization_unit_version_id, organization_unit_id, + unit_name, organization_type_code, effective_from, effective_to, recorded_from +) VALUES + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000040', '10000000-0000-7000-8000-000000000030', 'Alpha Legal Employer', 'legal_entity', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000041', '10000000-0000-7000-8000-000000000031', 'Alpha Second Employer', 'legal_entity', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000042', '10000000-0000-7000-8000-000000000032', 'People Department', 'department', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000040', '20000000-0000-7000-8000-000000000030', 'Beta Legal Employer', 'legal_entity', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'); + +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, + effective_from, effective_to, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001', + '10000000-0000-7000-8000-000000000050', + '10000000-0000-7000-8000-000000000020', + '10000000-0000-7000-8000-000000000030', + DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-03 00:00:00+00' +); +SQL + +current_count="$(psql "${DATABASE_URL}" -Atqc " +SELECT count(*) +FROM employment_employing_organization_record +WHERE tenant_record_id = '10000000-0000-7000-8000-000000000001'::uuid + AND employment_record_id = '10000000-0000-7000-8000-000000000020'::uuid + AND daterange(effective_from, effective_to, '[)') @> DATE '2026-08-28' + AND tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-08-28 00:00:00+00'; +")" +[[ "${current_count}" == "1" ]] || { echo "expected exactly one employer at one business/system coordinate, got ${current_count}" >&2; exit 1; } + +set +e +overlap_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, + effective_from, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001', + '10000000-0000-7000-8000-000000000051', + '10000000-0000-7000-8000-000000000020', + '10000000-0000-7000-8000-000000000031', + DATE '2026-06-01', TIMESTAMPTZ '2026-01-04 00:00:00+00' +); +SQL +} 2>&1)" +overlap_status=$? +set -e +[[ ${overlap_status} -ne 0 && "${overlap_output}" == *"employment_employing_organization_bitemporal_exclusion"* ]] || { + echo "overlapping employing organization did not fail at the bitemporal exclusion: ${overlap_output}" >&2; exit 1; +} + +set +e +non_legal_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, + effective_from, effective_to, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001', + '10000000-0000-7000-8000-000000000052', + '10000000-0000-7000-8000-000000000020', + '10000000-0000-7000-8000-000000000032', + DATE '2025-01-01', DATE '2025-06-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' +); +SQL +} 2>&1)" +non_legal_status=$? +set -e +[[ ${non_legal_status} -ne 0 && "${non_legal_output}" == *"employing organization must be a legal_entity"* ]] || { + echo "department was accepted as an employing legal entity: ${non_legal_output}" >&2; exit 1; +} + +set +e +cross_tenant_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, + effective_from, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001', + '10000000-0000-7000-8000-000000000053', + '10000000-0000-7000-8000-000000000020', + '20000000-0000-7000-8000-000000000030', + DATE '2025-01-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' +); +SQL +} 2>&1)" +cross_tenant_status=$? +set -e +[[ ${cross_tenant_status} -ne 0 && "${cross_tenant_output}" == *"employment_employing_organization_unit_tenant_fk"* ]] || { + echo "cross-tenant employer reference was not rejected by tenant-qualified integrity: ${cross_tenant_output}" >&2; exit 1; +} + +set +e +rewrite_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c " +UPDATE employment_employing_organization_record +SET employing_organization_unit_id = '10000000-0000-7000-8000-000000000031' +WHERE employment_employing_organization_record_id = '10000000-0000-7000-8000-000000000050'; +"; } 2>&1)" +rewrite_status=$? +set -e +[[ ${rewrite_status} -ne 0 && "${rewrite_output}" == *"bitemporal correction may only close an open recorded interval"* ]] || { + echo "in-place employer rewrite was not rejected: ${rewrite_output}" >&2; exit 1; +} + +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +UPDATE employment_employing_organization_record +SET recorded_to = TIMESTAMPTZ '2026-09-01 00:00:00+00' +WHERE employment_employing_organization_record_id = '10000000-0000-7000-8000-000000000050'; + +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, + effective_from, effective_to, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001', + '10000000-0000-7000-8000-000000000054', + '10000000-0000-7000-8000-000000000020', + '10000000-0000-7000-8000-000000000031', + DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-09-01 00:00:00+00' +); + +CREATE ROLE orgmetra_employer_reader NOSUPERUSER NOBYPASSRLS; +GRANT SELECT ON employment_employing_organization_record TO orgmetra_employer_reader; +SET ROLE orgmetra_employer_reader; +SELECT set_config('orgmetra.tenant_record_id', '10000000-0000-7000-8000-000000000001', false); +DO $$ +BEGIN + IF (SELECT count(*) FROM employment_employing_organization_record) <> 1 THEN + RAISE EXCEPTION 'tenant alpha should see exactly one current-system employer fact'; + END IF; +END +$$; +SELECT set_config('orgmetra.tenant_record_id', '20000000-0000-7000-8000-000000000001', false); +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM employment_employing_organization_record) THEN + RAISE EXCEPTION 'tenant beta unexpectedly observed tenant alpha employer facts'; + END IF; +END +$$; +RESET ROLE; +SQL + +echo "Employment employing-organization PostgreSQL contract passed" From 4eb28864d563fbe9f090c729220b54b8701172ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:05:30 -0700 Subject: [PATCH 02/50] test(hris): run employing organization PostgreSQL contract --- ...loyment-employing-organization-quality.yml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/employment-employing-organization-quality.yml diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml new file mode 100644 index 000000000..4a656179e --- /dev/null +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -0,0 +1,56 @@ +name: Employment Employing Organization Quality + +on: + pull_request: + branches: + - develop + paths: + - "database/migrations/0040_employment_employing_organization.sql" + - "tests/test_employment_employing_organization_postgres.sh" + - ".github/workflows/employment-employing-organization-quality.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: employment-employing-organization-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + postgres: + name: Employer scope bitemporal and tenant boundaries + runs-on: ubuntu-latest + 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 Employment employing-organization contract + run: bash tests/test_employment_employing_organization_postgres.sh + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" From b8ac031197f6589fce9891c68d32dc14e9309da5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:08:07 -0700 Subject: [PATCH 03/50] test(hris): harden employer scope regression --- ...loyment_employing_organization_postgres.sh | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 77103ccbd..2bdd7e403 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -36,10 +36,10 @@ INSERT INTO organization_unit_version ( tenant_record_id, organization_unit_version_id, organization_unit_id, unit_name, organization_type_code, effective_from, effective_to, recorded_from ) VALUES - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000040', '10000000-0000-7000-8000-000000000030', 'Alpha Legal Employer', 'legal_entity', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000041', '10000000-0000-7000-8000-000000000031', 'Alpha Second Employer', 'legal_entity', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000042', '10000000-0000-7000-8000-000000000032', 'People Department', 'department', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000040', '20000000-0000-7000-8000-000000000030', 'Beta Legal Employer', 'legal_entity', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'); + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000040', '10000000-0000-7000-8000-000000000030', 'Alpha Legal Employer', 'legal_entity', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000041', '10000000-0000-7000-8000-000000000031', 'Alpha Second Employer', 'legal_entity', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000042', '10000000-0000-7000-8000-000000000032', 'People Department', 'department', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000040', '20000000-0000-7000-8000-000000000030', 'Beta Legal Employer', 'legal_entity', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'); INSERT INTO employment_employing_organization_record ( tenant_record_id, employment_employing_organization_record_id, @@ -96,7 +96,7 @@ INSERT INTO employment_employing_organization_record ( '10000000-0000-7000-8000-000000000052', '10000000-0000-7000-8000-000000000020', '10000000-0000-7000-8000-000000000032', - DATE '2025-01-01', DATE '2025-06-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' + DATE '2026-01-01', DATE '2026-06-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' ); SQL } 2>&1)" @@ -106,6 +106,27 @@ set -e echo "department was accepted as an employing legal entity: ${non_legal_output}" >&2; exit 1; } +set +e +employment_gap_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, + effective_from, effective_to, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001', + '10000000-0000-7000-8000-000000000055', + '10000000-0000-7000-8000-000000000020', + '10000000-0000-7000-8000-000000000030', + DATE '2025-01-01', DATE '2025-06-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' +); +SQL +} 2>&1)" +employment_gap_status=$? +set -e +[[ ${employment_gap_status} -ne 0 && "${employment_gap_output}" == *"employing organization interval must be covered by active or leave Employment truth"* ]] || { + echo "employer scope outside active/leave Employment truth was not rejected: ${employment_gap_output}" >&2; exit 1; +} + set +e cross_tenant_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' INSERT INTO employment_employing_organization_record ( @@ -117,7 +138,7 @@ INSERT INTO employment_employing_organization_record ( '10000000-0000-7000-8000-000000000053', '10000000-0000-7000-8000-000000000020', '20000000-0000-7000-8000-000000000030', - DATE '2025-01-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' + DATE '2026-01-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' ); SQL } 2>&1)" @@ -139,6 +160,14 @@ set -e echo "in-place employer rewrite was not rejected: ${rewrite_output}" >&2; exit 1; } +set +e +truncate_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c "TRUNCATE employment_employing_organization_record;"; } 2>&1)" +truncate_status=$? +set -e +[[ ${truncate_status} -ne 0 && "${truncate_output}" == *"employment employing-organization history cannot be truncated"* ]] || { + echo "employer history TRUNCATE was not rejected: ${truncate_output}" >&2; exit 1; +} + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' UPDATE employment_employing_organization_record SET recorded_to = TIMESTAMPTZ '2026-09-01 00:00:00+00' @@ -162,7 +191,7 @@ SET ROLE orgmetra_employer_reader; SELECT set_config('orgmetra.tenant_record_id', '10000000-0000-7000-8000-000000000001', false); DO $$ BEGIN - IF (SELECT count(*) FROM employment_employing_organization_record) <> 1 THEN + IF (SELECT count(*) FROM employment_employing_organization_record WHERE recorded_to IS NULL) <> 1 THEN RAISE EXCEPTION 'tenant alpha should see exactly one current-system employer fact'; END IF; END From 17adafc33f0337cf267589443e3d4c3ccc450e07 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:08:26 -0700 Subject: [PATCH 04/50] feat(hris): persist employing legal organization --- ...0040_employment_employing_organization.sql | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 database/migrations/0040_employment_employing_organization.sql diff --git a/database/migrations/0040_employment_employing_organization.sql b/database/migrations/0040_employment_employing_organization.sql new file mode 100644 index 000000000..48839107a --- /dev/null +++ b/database/migrations/0040_employment_employing_organization.sql @@ -0,0 +1,132 @@ +-- Employment employing-organization truth. +-- +-- An Employment and a Position answer different questions. A Position belongs +-- to an organizational seat; this relation records which legal organization +-- employs the worker for the Employment itself. It is intentionally separate +-- from Position/Assignment so a legal-employer change is not inferred from a +-- seat move and a seat move is not inferred from a legal-employer change. + +CREATE TABLE employment_employing_organization_record ( + tenant_record_id uuid NOT NULL REFERENCES tenant_record(tenant_record_id), + employment_employing_organization_record_id uuid PRIMARY KEY, + employment_record_id uuid NOT NULL, + employing_organization_unit_id uuid NOT NULL, + effective_from date NOT NULL, + effective_to date, + recorded_from timestamptz NOT NULL DEFAULT now(), + recorded_to timestamptz, + CONSTRAINT employment_employing_organization_employment_tenant_fk + FOREIGN KEY (tenant_record_id, employment_record_id) + REFERENCES employment_record(tenant_record_id, employment_record_id), + CONSTRAINT employment_employing_organization_unit_tenant_fk + FOREIGN KEY (tenant_record_id, employing_organization_unit_id) + REFERENCES organization_unit(tenant_record_id, organization_unit_id), + CONSTRAINT employment_employing_organization_effective_period_check + CHECK (effective_to IS NULL OR effective_to > effective_from), + CONSTRAINT employment_employing_organization_recorded_period_check + CHECK (recorded_to IS NULL OR recorded_to > recorded_from), + CONSTRAINT employment_employing_organization_tenant_identity_unique + UNIQUE (tenant_record_id, employment_employing_organization_record_id), + CONSTRAINT employment_employing_organization_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 && + ) +); + +CREATE FUNCTION validate_employment_employing_organization_scope() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + requested_effective_range daterange; + legal_entity_coverage datemultirange; + employment_coverage datemultirange; +BEGIN + requested_effective_range := daterange(NEW.effective_from, NEW.effective_to, '[)'); + + -- Missing/cross-tenant organization anchors are left to the tenant-qualified + -- foreign key so callers receive the structural integrity failure rather + -- than a misleading classification error. + IF NOT EXISTS ( + SELECT 1 + FROM organization_unit + WHERE tenant_record_id = NEW.tenant_record_id + AND organization_unit_id = NEW.employing_organization_unit_id + ) THEN + RETURN NEW; + END IF; + + SELECT range_agg(daterange(effective_from, effective_to, '[)')) + INTO legal_entity_coverage + FROM organization_unit_version + WHERE tenant_record_id = NEW.tenant_record_id + AND organization_unit_id = NEW.employing_organization_unit_id + AND organization_type_code = 'legal_entity' + AND tstzrange(recorded_from, recorded_to, '[)') @> NEW.recorded_from; + + IF legal_entity_coverage IS NULL + OR NOT (legal_entity_coverage @> requested_effective_range) THEN + RAISE EXCEPTION 'employing organization must be a legal_entity over the full effective interval at the recorded-time coordinate' + USING ERRCODE = '23514'; + END IF; + + SELECT range_agg(daterange(effective_from, effective_to, '[)')) + INTO employment_coverage + FROM employment_record_version + WHERE tenant_record_id = NEW.tenant_record_id + AND employment_record_id = NEW.employment_record_id + AND employment_status_code IN ('active', 'leave') + AND tstzrange(recorded_from, recorded_to, '[)') @> NEW.recorded_from; + + IF employment_coverage IS NULL + OR NOT (employment_coverage @> requested_effective_range) THEN + RAISE EXCEPTION 'employing organization interval must be covered by active or leave Employment truth at the recorded-time coordinate' + USING ERRCODE = '23514'; + END IF; + + RETURN NEW; +END; +$$; + +CREATE TRIGGER employment_employing_organization_scope_guard +BEFORE INSERT ON employment_employing_organization_record +FOR EACH ROW +EXECUTE FUNCTION validate_employment_employing_organization_scope(); + +CREATE TRIGGER employment_employing_organization_bitemporal_guard +BEFORE UPDATE OR DELETE ON employment_employing_organization_record +FOR EACH ROW +EXECUTE FUNCTION protect_bitemporal_history(); + +CREATE FUNCTION reject_employment_employing_organization_truncate() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE EXCEPTION 'employment employing-organization history cannot be truncated' + USING ERRCODE = '55000'; +END; +$$; + +CREATE TRIGGER employment_employing_organization_truncate_guard +BEFORE TRUNCATE ON employment_employing_organization_record +FOR EACH STATEMENT +EXECUTE FUNCTION reject_employment_employing_organization_truncate(); + +ALTER TABLE employment_employing_organization_record ENABLE ROW LEVEL SECURITY; +ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY; +CREATE POLICY employment_employing_organization_scope_policy +ON employment_employing_organization_record +USING (tenant_record_id = current_tenant_record_id()) +WITH CHECK (tenant_record_id = current_tenant_record_id()); + +REVOKE ALL ON FUNCTION validate_employment_employing_organization_scope() FROM PUBLIC; +REVOKE ALL ON FUNCTION reject_employment_employing_organization_truncate() FROM PUBLIC; + +COMMENT ON TABLE employment_employing_organization_record IS + 'Bitemporal tenant-scoped legal-employer relationship for one Employment; distinct from Position and Assignment and corrected by closing recorded time rather than rewriting history.'; +COMMENT ON COLUMN employment_employing_organization_record.employing_organization_unit_id IS + 'Organization unit that is legal_entity throughout this effective interval at the row recorded-time coordinate.'; From 3c8a03043067b64f9a64df5da4fd40a601a7b9b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:09:00 -0700 Subject: [PATCH 05/50] docs(adr): record employing organization boundary --- .../0141-employment-employing-organization.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/adr/0141-employment-employing-organization.md diff --git a/docs/adr/0141-employment-employing-organization.md b/docs/adr/0141-employment-employing-organization.md new file mode 100644 index 000000000..e409838ec --- /dev/null +++ b/docs/adr/0141-employment-employing-organization.md @@ -0,0 +1,55 @@ +# ADR-0141: Employment employing-organization truth + +- Status: proposed_on_active_pr +- Date: 2026-08-28 +- Owner: Orgmetra HRIS core +- Scope: Employment / Organization + +## Context + +Protected `develop` models Person, Employment, Organization, Job, Position, and Assignment as distinct concepts, but Employment itself has no authoritative link to the organization that is the legal employer. Inferring legal employer from Position is unsafe: a Position move need not change the employing legal entity, and a legal-employer transfer need not imply the same Position semantics. + +For commercial HRIS use, the employer relationship must therefore be a first-class temporal HR fact, not a denormalized Position attribute or an application-only lookup. + +ISO 30414:2025 is the current published second edition of the human-capital reporting/disclosure standard and includes workforce composition among its core reporting areas. That supports the need for defensible organizational scope in workforce reporting; it does **not** prescribe this schema or authorize statutory/payroll interpretation. + +## Decision + +Add `employment_employing_organization_record` as a 3NF bitemporal relationship between one tenant-scoped Employment and one tenant-scoped Organization. + +At any single effective-date/system-recorded-time coordinate, one Employment may resolve to at most one employing organization. The organization must be classified as `legal_entity` across the relationship's full effective interval at the relationship's recorded-time coordinate. The relationship interval must also be fully covered by `active` or `leave` Employment truth at that same recorded-time coordinate. + +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. + +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 + +- Workforce and employment-contract scope can distinguish legal employer from seat/organization placement. +- Position transfers do not silently rewrite employer identity. +- Legal-employer corrections preserve what Orgmetra knew and when. +- The model remains usable without payroll or statutory-account ownership. +- A future mutation API must add purpose-bound authorization, accountable actor/reason/evidence, immutable audit/outbox, and idempotency; this persistence slice alone does not authorize a high-impact Employment change. + +## Alternatives rejected + +1. **Infer employer from Position.organization_unit_id.** Rejected because seat ownership and legal employment are different facts and can change independently. +2. **Store employer directly on `employment_record`.** Rejected because it would erase effective/system history and conflate durable Employment identity with a mutable relationship. +3. **Store a free-text legal-employer code.** Rejected because it breaks tenant-qualified Organization integrity and workforce lineage. +4. **Add payroll/legal-accounting tables.** Rejected as outside this bounded HRIS slice and outside accepted owner contracts. + +## Verification + +`tests/test_employment_employing_organization_postgres.sh` proves: + +- one employer per Employment business/system coordinate; +- full-interval `legal_entity` classification; +- full-interval active/leave Employment coverage; +- tenant-qualified cross-tenant rejection; +- correction-not-rewrite history; +- TRUNCATE protection; and +- forced-RLS isolation under a `NOSUPERUSER NOBYPASSRLS` application role. + +## References + +International Organization for Standardization. (2025). *ISO 30414:2025 Human resource management — Requirements and recommendations for human capital reporting and disclosure* (2nd ed.). ISO. From 871ec6cc5b3797c383cd0de5b168c134d353aaa0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:09:13 -0700 Subject: [PATCH 06/50] docs(doctoring): ground employer scope decision --- .../employment-employing-organization.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/doctoring/employment-employing-organization.md diff --git a/docs/doctoring/employment-employing-organization.md b/docs/doctoring/employment-employing-organization.md new file mode 100644 index 000000000..482a4501e --- /dev/null +++ b/docs/doctoring/employment-employing-organization.md @@ -0,0 +1,23 @@ +# Employment employing-organization evidence note + +## Question + +Why does Orgmetra model an Employment's employing legal organization separately from Position and Assignment? + +## Current authoritative standard evidence + +ISO 30414:2025 is the published second edition of *Human resource management — Requirements and recommendations for human capital reporting and disclosure*. ISO lists workforce composition among the standard's core human-capital reporting areas and states that the standard is intended to support comprehensive internal and external human-capital reporting across organizations of different sizes and sectors. + +Orgmetra uses that published standard only as reporting-context evidence that workforce composition needs defensible organizational scope. ISO 30414 does not define this database schema and does not by itself determine the legal employer for any jurisdiction. + +## Product implication + +A Position is an organizational seat. An Employment is the durable worker-employment relationship. Treating `position_record.organization_unit_id` as the legal employer would therefore make a seat transfer silently change employment-contract scope and would make a legal-employer transfer indistinguishable from a seat move. + +The active PR instead records a tenant-scoped, bitemporal `employment_employing_organization_record` relationship. The referenced Organization must be `legal_entity` over the full effective interval at the row's recorded-time coordinate, while the same interval must be covered by active/leave Employment truth. No payroll, withholding, tax, statutory-accounting, benefits, compensation, candidate, performance, or model-output fields are added. + +This is an HRIS source-of-truth relationship, not a claim of payroll or statutory-system ownership. + +## Reference (APA 7) + +International Organization for Standardization. (2025). *ISO 30414:2025 Human resource management — Requirements and recommendations for human capital reporting and disclosure* (2nd ed.). ISO. https://www.iso.org/standard/30414 From d69639a8759b190e169325984cce8b46afedff80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:12:10 -0700 Subject: [PATCH 07/50] test(hris): isolate cross-tenant employer regression --- ...loyment_employing_organization_postgres.sh | 233 ++++-------------- 1 file changed, 53 insertions(+), 180 deletions(-) diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 2bdd7e403..a875160e2 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -8,202 +8,75 @@ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0040_employment psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' INSERT INTO tenant_record (tenant_record_id, tenant_reference) VALUES - ('10000000-0000-7000-8000-000000000001', 'tenant_alpha'), - ('20000000-0000-7000-8000-000000000001', 'tenant_beta'); - + ('10000000-0000-7000-8000-000000000001','tenant_alpha'), + ('20000000-0000-7000-8000-000000000001','tenant_beta'); INSERT INTO person_record (tenant_record_id, person_record_id) VALUES - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000010'), - ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000010'); - + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000010'), + ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000010'); INSERT INTO employment_record (tenant_record_id, employment_record_id, person_record_id) VALUES - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000020', '10000000-0000-7000-8000-000000000010'), - ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000020', '20000000-0000-7000-8000-000000000010'); - + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000010'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000022','10000000-0000-7000-8000-000000000010'), + ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000020','20000000-0000-7000-8000-000000000010'); INSERT INTO employment_record_version ( - tenant_record_id, employment_record_version_id, employment_record_id, - employment_status_code, effective_from, effective_to, recorded_from + tenant_record_id, employment_record_version_id, employment_record_id, + employment_status_code, employment_concurrency_code, effective_from, recorded_from ) VALUES - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000021', '10000000-0000-7000-8000-000000000020', 'active', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000021', '20000000-0000-7000-8000-000000000020', 'active', DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'); - + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000021','10000000-0000-7000-8000-000000000020','active','exclusive',DATE '2026-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000023','10000000-0000-7000-8000-000000000022','active','concurrent',DATE '2026-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000021','20000000-0000-7000-8000-000000000020','active','exclusive',DATE '2026-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'); INSERT INTO organization_unit (tenant_record_id, organization_unit_id) VALUES - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000030'), - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000031'), - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000032'), - ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000030'); - + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000030'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000031'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000032'), + ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000030'); INSERT INTO organization_unit_version ( - tenant_record_id, organization_unit_version_id, organization_unit_id, - unit_name, organization_type_code, effective_from, effective_to, recorded_from + tenant_record_id, organization_unit_version_id, organization_unit_id, + unit_name, organization_type_code, effective_from, recorded_from ) VALUES - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000040', '10000000-0000-7000-8000-000000000030', 'Alpha Legal Employer', 'legal_entity', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000041', '10000000-0000-7000-8000-000000000031', 'Alpha Second Employer', 'legal_entity', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('10000000-0000-7000-8000-000000000001', '10000000-0000-7000-8000-000000000042', '10000000-0000-7000-8000-000000000032', 'People Department', 'department', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'), - ('20000000-0000-7000-8000-000000000001', '20000000-0000-7000-8000-000000000040', '20000000-0000-7000-8000-000000000030', 'Beta Legal Employer', 'legal_entity', DATE '2025-01-01', NULL, TIMESTAMPTZ '2026-01-02 00:00:00+00'); - -INSERT INTO employment_employing_organization_record ( - tenant_record_id, employment_employing_organization_record_id, - employment_record_id, employing_organization_unit_id, - effective_from, effective_to, recorded_from -) VALUES ( - '10000000-0000-7000-8000-000000000001', - '10000000-0000-7000-8000-000000000050', - '10000000-0000-7000-8000-000000000020', - '10000000-0000-7000-8000-000000000030', - DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-01-03 00:00:00+00' -); -SQL - -current_count="$(psql "${DATABASE_URL}" -Atqc " -SELECT count(*) -FROM employment_employing_organization_record -WHERE tenant_record_id = '10000000-0000-7000-8000-000000000001'::uuid - AND employment_record_id = '10000000-0000-7000-8000-000000000020'::uuid - AND daterange(effective_from, effective_to, '[)') @> DATE '2026-08-28' - AND tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-08-28 00:00:00+00'; -")" -[[ "${current_count}" == "1" ]] || { echo "expected exactly one employer at one business/system coordinate, got ${current_count}" >&2; exit 1; } - -set +e -overlap_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' -INSERT INTO employment_employing_organization_record ( - tenant_record_id, employment_employing_organization_record_id, - employment_record_id, employing_organization_unit_id, - effective_from, recorded_from -) VALUES ( - '10000000-0000-7000-8000-000000000001', - '10000000-0000-7000-8000-000000000051', - '10000000-0000-7000-8000-000000000020', - '10000000-0000-7000-8000-000000000031', - DATE '2026-06-01', TIMESTAMPTZ '2026-01-04 00:00:00+00' -); -SQL -} 2>&1)" -overlap_status=$? -set -e -[[ ${overlap_status} -ne 0 && "${overlap_output}" == *"employment_employing_organization_bitemporal_exclusion"* ]] || { - echo "overlapping employing organization did not fail at the bitemporal exclusion: ${overlap_output}" >&2; exit 1; -} - -set +e -non_legal_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' -INSERT INTO employment_employing_organization_record ( - tenant_record_id, employment_employing_organization_record_id, - employment_record_id, employing_organization_unit_id, - effective_from, effective_to, recorded_from -) VALUES ( - '10000000-0000-7000-8000-000000000001', - '10000000-0000-7000-8000-000000000052', - '10000000-0000-7000-8000-000000000020', - '10000000-0000-7000-8000-000000000032', - DATE '2026-01-01', DATE '2026-06-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' -); -SQL -} 2>&1)" -non_legal_status=$? -set -e -[[ ${non_legal_status} -ne 0 && "${non_legal_output}" == *"employing organization must be a legal_entity"* ]] || { - echo "department was accepted as an employing legal entity: ${non_legal_output}" >&2; exit 1; -} - -set +e -employment_gap_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000040','10000000-0000-7000-8000-000000000030','Alpha Legal Employer','legal_entity',DATE '2025-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000041','10000000-0000-7000-8000-000000000031','Alpha Second Employer','legal_entity',DATE '2025-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000042','10000000-0000-7000-8000-000000000032','People Department','department',DATE '2025-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000040','20000000-0000-7000-8000-000000000030','Beta Legal Employer','legal_entity',DATE '2025-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'); INSERT INTO employment_employing_organization_record ( - tenant_record_id, employment_employing_organization_record_id, - employment_record_id, employing_organization_unit_id, - effective_from, effective_to, recorded_from + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, effective_from, recorded_from ) VALUES ( - '10000000-0000-7000-8000-000000000001', - '10000000-0000-7000-8000-000000000055', - '10000000-0000-7000-8000-000000000020', - '10000000-0000-7000-8000-000000000030', - DATE '2025-01-01', DATE '2025-06-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' -); + '10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000050', + '10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030', + DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); SQL -} 2>&1)" -employment_gap_status=$? -set -e -[[ ${employment_gap_status} -ne 0 && "${employment_gap_output}" == *"employing organization interval must be covered by active or leave Employment truth"* ]] || { - echo "employer scope outside active/leave Employment truth was not rejected: ${employment_gap_output}" >&2; exit 1; -} -set +e -cross_tenant_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' -INSERT INTO employment_employing_organization_record ( - tenant_record_id, employment_employing_organization_record_id, - employment_record_id, employing_organization_unit_id, - effective_from, recorded_from -) VALUES ( - '10000000-0000-7000-8000-000000000001', - '10000000-0000-7000-8000-000000000053', - '10000000-0000-7000-8000-000000000020', - '20000000-0000-7000-8000-000000000030', - DATE '2026-01-01', TIMESTAMPTZ '2026-01-03 00:00:00+00' -); -SQL -} 2>&1)" -cross_tenant_status=$? -set -e -[[ ${cross_tenant_status} -ne 0 && "${cross_tenant_output}" == *"employment_employing_organization_unit_tenant_fk"* ]] || { - echo "cross-tenant employer reference was not rejected by tenant-qualified integrity: ${cross_tenant_output}" >&2; exit 1; +count="$(psql "${DATABASE_URL}" -Atqc "SELECT count(*) FROM employment_employing_organization_record WHERE tenant_record_id='10000000-0000-7000-8000-000000000001'::uuid AND employment_record_id='10000000-0000-7000-8000-000000000020'::uuid AND daterange(effective_from,effective_to,'[)') @> DATE '2026-08-28' AND tstzrange(recorded_from,recorded_to,'[)') @> TIMESTAMPTZ '2026-08-28 00:00:00+00';")" +[[ "$count" == 1 ]] || { echo "expected one employer at one business/system coordinate, got $count" >&2; exit 1; } + +expect_failure() { + local expected="$1"; shift + set +e + local output + output="$({ "$@"; } 2>&1)" + local status=$? + set -e + [[ $status -ne 0 && "$output" == *"$expected"* ]] || { echo "expected failure '$expected', got: $output" >&2; exit 1; } } -set +e -rewrite_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c " -UPDATE employment_employing_organization_record -SET employing_organization_unit_id = '10000000-0000-7000-8000-000000000031' -WHERE employment_employing_organization_record_id = '10000000-0000-7000-8000-000000000050'; -"; } 2>&1)" -rewrite_status=$? -set -e -[[ ${rewrite_status} -ne 0 && "${rewrite_output}" == *"bitemporal correction may only close an open recorded interval"* ]] || { - echo "in-place employer rewrite was not rejected: ${rewrite_output}" >&2; exit 1; -} - -set +e -truncate_output="$({ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -c "TRUNCATE employment_employing_organization_record;"; } 2>&1)" -truncate_status=$? -set -e -[[ ${truncate_status} -ne 0 && "${truncate_output}" == *"employment employing-organization history cannot be truncated"* ]] || { - echo "employer history TRUNCATE was not rejected: ${truncate_output}" >&2; exit 1; -} - -psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' -UPDATE employment_employing_organization_record -SET recorded_to = TIMESTAMPTZ '2026-09-01 00:00:00+00' -WHERE employment_employing_organization_record_id = '10000000-0000-7000-8000-000000000050'; - -INSERT INTO employment_employing_organization_record ( - tenant_record_id, employment_employing_organization_record_id, - employment_record_id, employing_organization_unit_id, - effective_from, effective_to, recorded_from -) VALUES ( - '10000000-0000-7000-8000-000000000001', - '10000000-0000-7000-8000-000000000054', - '10000000-0000-7000-8000-000000000020', - '10000000-0000-7000-8000-000000000031', - DATE '2026-01-01', NULL, TIMESTAMPTZ '2026-09-01 00:00:00+00' -); - +expect_failure employment_employing_organization_bitemporal_exclusion psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000051','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000031',DATE '2026-06-01',TIMESTAMPTZ '2026-01-04 00:00:00+00');" +expect_failure "employing organization must be a legal_entity" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000052','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000032',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" +expect_failure "employing organization interval must be covered by active or leave Employment truth" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000055','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030',DATE '2025-01-01',DATE '2025-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" +# Separate Employment prevents the cardinality exclusion from masking the tenant FK boundary. +expect_failure employment_employing_organization_unit_tenant_fk psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000053','10000000-0000-7000-8000-000000000022','20000000-0000-7000-8000-000000000030',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" +expect_failure "bitemporal correction may only close an open recorded interval" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "UPDATE employment_employing_organization_record SET employing_organization_unit_id='10000000-0000-7000-8000-000000000031' WHERE employment_employing_organization_record_id='10000000-0000-7000-8000-000000000050';" +expect_failure "employment employing-organization history cannot be truncated" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "TRUNCATE employment_employing_organization_record;" + +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' +UPDATE employment_employing_organization_record SET recorded_to=TIMESTAMPTZ '2026-09-01 00:00:00+00' WHERE employment_employing_organization_record_id='10000000-0000-7000-8000-000000000050'; +INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000054','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000031',DATE '2026-01-01',TIMESTAMPTZ '2026-09-01 00:00:00+00'); CREATE ROLE orgmetra_employer_reader NOSUPERUSER NOBYPASSRLS; GRANT SELECT ON employment_employing_organization_record TO orgmetra_employer_reader; SET ROLE orgmetra_employer_reader; -SELECT set_config('orgmetra.tenant_record_id', '10000000-0000-7000-8000-000000000001', false); -DO $$ -BEGIN - IF (SELECT count(*) FROM employment_employing_organization_record WHERE recorded_to IS NULL) <> 1 THEN - RAISE EXCEPTION 'tenant alpha should see exactly one current-system employer fact'; - END IF; -END -$$; -SELECT set_config('orgmetra.tenant_record_id', '20000000-0000-7000-8000-000000000001', false); -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM employment_employing_organization_record) THEN - RAISE EXCEPTION 'tenant beta unexpectedly observed tenant alpha employer facts'; - END IF; -END -$$; +SELECT set_config('orgmetra.tenant_record_id','10000000-0000-7000-8000-000000000001',false); +DO $$ BEGIN IF (SELECT count(*) FROM employment_employing_organization_record WHERE recorded_to IS NULL) <> 1 THEN RAISE EXCEPTION 'tenant alpha should see one current-system employer fact'; END IF; END $$; +SELECT set_config('orgmetra.tenant_record_id','20000000-0000-7000-8000-000000000001',false); +DO $$ BEGIN IF EXISTS (SELECT 1 FROM employment_employing_organization_record) THEN RAISE EXCEPTION 'tenant beta observed tenant alpha employer facts'; END IF; END $$; RESET ROLE; SQL From 59517e33991c74841ff36d58342fb19bbe4966fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:12:43 -0700 Subject: [PATCH 08/50] ci(hris): bind employer docs to focused gate --- .../employment-employing-organization-quality.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml index 4a656179e..57ff5df4d 100644 --- a/.github/workflows/employment-employing-organization-quality.yml +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -8,6 +8,13 @@ on: - "database/migrations/0040_employment_employing_organization.sql" - "tests/test_employment_employing_organization_postgres.sh" - ".github/workflows/employment-employing-organization-quality.yml" + - "docs/adr/0141-employment-employing-organization.md" + - "docs/doctoring/employment-employing-organization.md" + - "docs/DATA_MODEL.md" + - "docs/ERD.md" + - "docs/TRACEABILITY.md" + - "CHANGELOG.md" + - "manifest.json" workflow_dispatch: permissions: From f24d87748fb19713b0edd5618bce0b8320250d38 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:17:39 -0700 Subject: [PATCH 09/50] ci(foundation): require employer scope provenance --- scripts/foundation-contract-core.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index 1e9fb267c..a8770c53c 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -68,6 +68,7 @@ export const REQUIRED_FILES = Object.freeze([ 'database/migrations/0011_criterion_observation_scope.sql', 'database/migrations/0012_people_mutation_idempotency.sql', 'database/migrations/0013_job_analysis_snapshot.sql', + 'database/migrations/0040_employment_employing_organization.sql', 'packages/hris-kernel/src/orgmetra_hris_kernel/audit.py', 'packages/hris-kernel/tests/test_audit_outbox.py', 'schemas/openapi.yaml', @@ -89,6 +90,7 @@ export const REQUIRED_FILES = Object.freeze([ 'tests/test_criterion_observation_scope_postgres.sh', 'tests/test_people_mutation_idempotency_postgres.sh', 'tests/test_job_analysis_snapshot_postgres.sh', + 'tests/test_employment_employing_organization_postgres.sh', 'tests/validate_repository.py' ]); From 60fe5d7839f4a79bfea49df0315bf9f101238c9f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:19:53 -0700 Subject: [PATCH 10/50] ci(foundation): require employer scope provenance --- tests/validate_repository.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/validate_repository.py b/tests/validate_repository.py index fe0a329ff..9cd83efea 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -71,6 +71,7 @@ "database/migrations/0011_criterion_observation_scope.sql", "database/migrations/0012_people_mutation_idempotency.sql", "database/migrations/0013_job_analysis_snapshot.sql", + "database/migrations/0040_employment_employing_organization.sql", "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", "packages/hris-kernel/tests/test_audit_outbox.py", "schemas/openapi.yaml", @@ -92,6 +93,7 @@ "tests/test_criterion_observation_scope_postgres.sh", "tests/test_people_mutation_idempotency_postgres.sh", "tests/test_job_analysis_snapshot_postgres.sh", + "tests/test_employment_employing_organization_postgres.sh", "tests/validate_repository.py", ] @@ -270,7 +272,7 @@ def _validate_database_contract() -> None: "CREATE FUNCTION protect_bitemporal_history", "to_jsonb(NEW) - 'recorded_to' <> to_jsonb(OLD) - 'recorded_to'", "CREATE TRIGGER person_record_bitemporal_guard", - "CREATE TRIGGER person_name_bitemporal_guard", + "CREATE TRIGGER person_name_record_bitemporal_guard", "CREATE TRIGGER employment_record_bitemporal_guard", "CREATE TRIGGER organization_unit_anchor_bitemporal_guard", "CREATE TRIGGER organization_unit_bitemporal_guard", From a44a5659a88e7eeb74778f4d1dd7dac5fca3822c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:20:18 -0700 Subject: [PATCH 11/50] ci(hris): expose exact provenance candidate --- .../workflows/employment-employing-organization-quality.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml index 57ff5df4d..88d082435 100644 --- a/.github/workflows/employment-employing-organization-quality.yml +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -7,6 +7,8 @@ on: paths: - "database/migrations/0040_employment_employing_organization.sql" - "tests/test_employment_employing_organization_postgres.sh" + - "scripts/foundation-contract-core.mjs" + - "tests/validate_repository.py" - ".github/workflows/employment-employing-organization-quality.yml" - "docs/adr/0141-employment-employing-organization.md" - "docs/doctoring/employment-employing-organization.md" @@ -55,6 +57,8 @@ jobs: env: ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" + - name: Print exact foundation manifest candidate + run: python tests/validate_repository.py --print-manifest - name: Prove Employment employing-organization contract run: bash tests/test_employment_employing_organization_postgres.sh - name: Require clean checkout From 401b11351b5309335ccfe2cf0041fc8e3374e110 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:21:04 -0700 Subject: [PATCH 12/50] ci(hris): cover foundation migration changes --- .github/workflows/employment-employing-organization-quality.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml index 88d082435..b42546287 100644 --- a/.github/workflows/employment-employing-organization-quality.yml +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -5,6 +5,7 @@ on: branches: - develop paths: + - "database/migrations/0001_foundation_schema.sql" - "database/migrations/0040_employment_employing_organization.sql" - "tests/test_employment_employing_organization_postgres.sh" - "scripts/foundation-contract-core.mjs" From c96a90ea7e2d13da9791120b8d78f5af6c3139a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:24:41 -0700 Subject: [PATCH 13/50] fix(foundation): preserve canonical person-name trigger contract --- tests/validate_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validate_repository.py b/tests/validate_repository.py index 9cd83efea..4ff2c690a 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -272,7 +272,7 @@ def _validate_database_contract() -> None: "CREATE FUNCTION protect_bitemporal_history", "to_jsonb(NEW) - 'recorded_to' <> to_jsonb(OLD) - 'recorded_to'", "CREATE TRIGGER person_record_bitemporal_guard", - "CREATE TRIGGER person_name_record_bitemporal_guard", + "CREATE TRIGGER person_name_bitemporal_guard", "CREATE TRIGGER employment_record_bitemporal_guard", "CREATE TRIGGER organization_unit_anchor_bitemporal_guard", "CREATE TRIGGER organization_unit_bitemporal_guard", From 88b102faf30753a3ec20990476b20d0266c322f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:25:16 -0700 Subject: [PATCH 14/50] test(hris): prove positive tenant reads at system time --- ...loyment_employing_organization_postgres.sh | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index a875160e2..3e6d4d689 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -40,10 +40,13 @@ INSERT INTO organization_unit_version ( INSERT INTO employment_employing_organization_record ( tenant_record_id, employment_employing_organization_record_id, employment_record_id, employing_organization_unit_id, effective_from, recorded_from -) VALUES ( - '10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000050', - '10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030', - DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); +) VALUES + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000050', + '10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030', + DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'), + ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000050', + '20000000-0000-7000-8000-000000000020','20000000-0000-7000-8000-000000000030', + DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); SQL count="$(psql "${DATABASE_URL}" -Atqc "SELECT count(*) FROM employment_employing_organization_record WHERE tenant_record_id='10000000-0000-7000-8000-000000000001'::uuid AND employment_record_id='10000000-0000-7000-8000-000000000020'::uuid AND daterange(effective_from,effective_to,'[)') @> DATE '2026-08-28' AND tstzrange(recorded_from,recorded_to,'[)') @> TIMESTAMPTZ '2026-08-28 00:00:00+00';")" @@ -74,9 +77,17 @@ CREATE ROLE orgmetra_employer_reader NOSUPERUSER NOBYPASSRLS; GRANT SELECT ON employment_employing_organization_record TO orgmetra_employer_reader; SET ROLE orgmetra_employer_reader; SELECT set_config('orgmetra.tenant_record_id','10000000-0000-7000-8000-000000000001',false); -DO $$ BEGIN IF (SELECT count(*) FROM employment_employing_organization_record WHERE recorded_to IS NULL) <> 1 THEN RAISE EXCEPTION 'tenant alpha should see one current-system employer fact'; END IF; END $$; +DO $$ BEGIN + IF (SELECT count(*) FROM employment_employing_organization_record WHERE tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN + RAISE EXCEPTION 'tenant alpha should see exactly one employer fact at the selected system-time coordinate'; + END IF; +END $$; SELECT set_config('orgmetra.tenant_record_id','20000000-0000-7000-8000-000000000001',false); -DO $$ BEGIN IF EXISTS (SELECT 1 FROM employment_employing_organization_record) THEN RAISE EXCEPTION 'tenant beta observed tenant alpha employer facts'; END IF; END $$; +DO $$ BEGIN + IF (SELECT count(*) FROM employment_employing_organization_record WHERE tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN + RAISE EXCEPTION 'tenant beta should see exactly one employer fact at the selected system-time coordinate'; + END IF; +END $$; RESET ROLE; SQL From ede293c0ff7cce6c329f8b4dfef9a4cd7fcc3dca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 18:38:53 -0700 Subject: [PATCH 15/50] fix(foundation): seal employing-organization provenance --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 97f2bab14..e500ba54f 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":"database/migrations/0040_employment_employing_organization.sql","sha256":"6c2f38b74f9bcf2e3cfe582cbdda3511c1cd0d7c8e28495266faae39c9a1e2c3","bytes":6058,"lines":132},{"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":"f7b6007d3e63e0d5e09fd35b16ad22a4b140e216825d637eb1182459b887a969","bytes":28303,"lines":691},{"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_employment_employing_organization_postgres.sh","sha256":"e39c2be1b5934336f8f3e81e605de972ed0ed056321194e8673bd9f60075effd","bytes":9449,"lines":94},{"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":"bba14584ee84fd859ea77b57e04af60890d7fbaf16f83580294a62d68eb435fa","bytes":27425,"lines":640}]} From 5873efdb07b28c627c518e96610776a63c97bbba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 15:17:54 +0900 Subject: [PATCH 16/50] fix(foundation): register employing organization evidence --- CHANGELOG.md | 1 + docs/DATA_MODEL.md | 3 +++ docs/ERD.md | 4 ++++ docs/TRACEABILITY.md | 1 + docs/adr/README.md | 1 + manifest.json | 2 +- scripts/foundation-contract-core.mjs | 6 ++++-- tests/foundation-contract.test.mjs | 22 ++++++++++++++++++++++ 8 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f4752d7..b3a971fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to Orgmetra will be documented in this file. ### Added +- Active-PR employment employing-organization truth: bitemporal, tenant-qualified `employment_employing_organization_record` keeps legal-employer identity independent from Position and Assignment, requires `legal_entity` organization classification and `active`/`leave` Employment coverage, 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. - 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. diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md index 7d4afd563..a8995056d 100644 --- a/docs/DATA_MODEL.md +++ b/docs/DATA_MODEL.md @@ -8,6 +8,7 @@ | `person_record` | Durable person entity inside Orgmetra, not an authentication subject. | | `employment_record` | Durable employment identity for a person. | | `employment_record_version` | Bitemporal employment status, exclusive-or-concurrent code, and effective period. | +| `employment_employing_organization_record` | Bitemporal, tenant-qualified relationship that records an Employment's legal employing Organization independently of Position and Assignment. | | `organization_unit` | Durable organizational identity referenced by positions and hierarchy facts. | | `organization_unit_version` | Bitemporal organizational name, type, and parent relationship for an organization unit. | | `job_profile` | Durable job identity referenced by positions, criteria, and decisions. | @@ -52,6 +53,8 @@ Intervals are half-open and non-empty: an end value, when present, must be stric Durable anchors such as `organization_unit`, `job_profile`, `employment_record`, and `position_record` do not repeat mutable descriptive attributes. Their descriptive versions live in `organization_unit_version`, `job_profile_version`, `employment_record_version`, and `position_record_version`. Single-valued bitemporal version families reject overlapping effective/system intervals, so one `effective_from`/`effective_to` interval combined with one `recorded_from`/`recorded_to` interval cannot yield contradictory current descriptions. Corrections close the previous recorded interval and insert a replacement; in-place business mutation is rejected. +`employment_employing_organization_record` is a separate bitemporal Employment-to-Organization fact. At a business-time/system-time coordinate, an Employment has at most one employing Organization; the Organization must be a `legal_entity`, and the relationship interval must be covered by `active` or `leave` Employment truth. Position placement and Assignment allocation do not determine legal-employer identity. + 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. ## High-impact decision evidence diff --git a/docs/ERD.md b/docs/ERD.md index a547cc3a0..0f5d7f0e5 100644 --- a/docs/ERD.md +++ b/docs/ERD.md @@ -12,7 +12,9 @@ erDiagram person_record ||--o{ person_name_record : has_names person_record ||--o{ employment_record : has employment_record ||--o{ employment_record_version : has_versions + employment_record ||--o{ employment_employing_organization_record : has_employer_history organization_unit ||--o{ organization_unit_version : has_versions + organization_unit ||--o{ employment_employing_organization_record : employs organization_unit_version }o--o| organization_unit : may_parent organization_unit ||--o{ position_record : contains job_profile ||--o{ job_profile_version : has_versions @@ -49,6 +51,8 @@ erDiagram `organization_unit`, `job_profile`, `employment_record`, and `position_record` are durable anchors. Mutable names, classifications, parent relationships, titles, families, version codes, and employment or position status live in bitemporal version rows. Positions retain stable organization/job references while retroactive corrections append or supersede version facts rather than rewriting identity. An organization version may reference another durable organization as its parent; self-parenting is rejected at the database boundary. An assignment names the employment that covers it, so a person cannot be assigned through another worker's employment. Exclusive employment versions for one person cannot overlap. An assignment day must land on an `active` or `open` position version, and visible allocations for one seat cannot exceed 1.0000. +`employment_employing_organization_record` is the independent legal-employer relationship. It is bitemporal and tenant-qualified, permits at most one employer for an Employment at one effective/system coordinate, requires `legal_entity` classification across the full effective interval, and requires `active` or `leave` Employment coverage. A Position or Assignment therefore cannot silently substitute for legal-employer truth. + Every owned HRIS fact carries `tenant_record_id`. Relationships that cross table boundaries use tenant-qualified foreign keys, and row-level security independently filters every tenant-scoped relation. The tenant column is therefore both a referential-integrity boundary and a runtime isolation boundary, not a caller-supplied business attribute. A candidate profile can be linked to at most one worker identity within its tenant. A person identity can have multiple candidate-worker links across reapplications or historical candidate profiles, so the person-side cardinality is one-to-many. diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 22a4178fe..c5716ba63 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -9,6 +9,7 @@ | Tenant-qualified HRIS integrity and fail-closed isolation | Core bounded contexts / Security architecture | `tenant_record`, tenant-qualified foreign keys, forced row-level security policies, tenant-scoped kernel query parameters | PostgreSQL cross-tenant FK/application-role RLS contracts plus kernel cross-tenant reconstruction, employment coverage, position coverage, seat-capacity, portfolio, exclusivity, and organization-hierarchy regressions | ADR-0001, ADR-0003 | implemented_on_active_pr | | Reserved UUID sentinel exclusion | Persistence integrity boundary | every foundation UUID `*_id` column plus audit/outbox identifiers | PostgreSQL inventory proof plus Nil/Max foundation and audit/outbox persistence regressions | ADR-0001, RFC 9562 | implemented_on_active_pr | | Normalized bitemporal organization/job/employment/position history | Core bounded contexts | `organization_unit_version`, `job_profile_version`, `employment_record_version`, `position_record_version` | PostgreSQL non-overlap, concurrent conflict, correction, rewrite-rejection, assignment-employment binding, and single-valued historical reconstruction | ADR-0001, ADR-0003, ADR-0004 | implemented_on_active_pr | +| Authoritative employing legal-organization relationship | Employment / Organization core | `employment_employing_organization_record` | `test_employment_employing_organization_postgres.sh` proves one employer per Employment coordinate, legal-entity and active/leave coverage, correction history, tenant-qualified integrity, and forced-RLS isolation | ADR-0141 | implemented_on_active_pr | | Acyclic organization hierarchy at historical coordinates | Organization core | `organization_unit_version.parent_organization_unit_id` | indirect A→B→C→A rejection plus future-recorded and foreign-tenant isolation in `orgmetra_hris_kernel` | ADR-0001, ADR-0003 | implemented_on_protected_main | | Effective/system time | Bitemporal HRIS | `effective_from`, `recorded_from` | strict half-open interval and historical-coordinate tests | ADR-0003 | implemented_on_active_pr | | 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 | diff --git a/docs/adr/README.md b/docs/adr/README.md index 099a21139..a57939cc6 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -16,3 +16,4 @@ | [0012](0012-governed-migration-handoff.md) | Governed migration handoff | Accepted on active implementation branch | | [0013](0013-governed-requisition-review-packet.md) | Governed requisition review packet | Accepted on active implementation branch | | [0014](0014-job-analysis-snapshot-persistence.md) | Persist governed job-analysis snapshots | Accepted on active implementation branch | +| [0141](0141-employment-employing-organization.md) | Employment employing-organization truth | proposed_on_active_pr | diff --git a/manifest.json b/manifest.json index e500ba54f..5bf27d74c 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":"database/migrations/0040_employment_employing_organization.sql","sha256":"6c2f38b74f9bcf2e3cfe582cbdda3511c1cd0d7c8e28495266faae39c9a1e2c3","bytes":6058,"lines":132},{"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":"f7b6007d3e63e0d5e09fd35b16ad22a4b140e216825d637eb1182459b887a969","bytes":28303,"lines":691},{"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_employment_employing_organization_postgres.sh","sha256":"e39c2be1b5934336f8f3e81e605de972ed0ed056321194e8673bd9f60075effd","bytes":9449,"lines":94},{"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":"bba14584ee84fd859ea77b57e04af60890d7fbaf16f83580294a62d68eb435fa","bytes":27425,"lines":640}]} +{"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":"d3ab9c9a73ee1ce3ff84607fccf06754312d5e54a1405c60b2a2d974ccf8c3ee","bytes":17789,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"6c2f38b74f9bcf2e3cfe582cbdda3511c1cd0d7c8e28495266faae39c9a1e2c3","bytes":6058,"lines":132},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6d805fd4ce54ef53831d7b68da2628b5a73fbaa812a4697fa1c84f6e1eb79618","bytes":13973,"lines":88},{"path":"docs/ERD.md","sha256":"0b1bf00abb1184b9f538e6826d4398422872f1e8c386a568bd317d3ed0b1cb8f","bytes":7568,"lines":74},{"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":"2ddedd16b9f8bbbdaf4b08dc3d55dfd6f05813911e0b7d300a567ce98d727954","bytes":11853,"lines":41},{"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":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"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":"756f1e85653b365fed53c3e4ea5ece48f1a44e689442989263ebf37b0ec3cd04","bytes":28417,"lines":693},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"e39c2be1b5934336f8f3e81e605de972ed0ed056321194e8673bd9f60075effd","bytes":9449,"lines":94},{"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":"bba14584ee84fd859ea77b57e04af60890d7fbaf16f83580294a62d68eb435fa","bytes":27425,"lines":640}]} \ No newline at end of file diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index a8770c53c..fed0a7104 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -109,6 +109,7 @@ export const MATURITY_VALUES = Object.freeze(new Set([ export const DATABASE_OBJECT_NAMES = Object.freeze([ 'tenant_record', 'person_record', 'person_name_record', 'person_contact_record', 'external_identity_link', 'employment_record', 'employment_record_version', + 'employment_employing_organization_record', 'employment_contract', 'employment_status_history', 'employment_transition', 'legal_entity', 'organization_unit', 'organization_relation', 'business_location', @@ -141,11 +142,12 @@ export const MIGRATION_BACKED_DATABASE_OBJECT_NAMES = Object.freeze([ 'job_analysis_task_item', 'job_analysis_ksao_item', 'job_analysis_task_ksao_link', - 'job_analysis_write_command' + 'job_analysis_write_command', + 'employment_employing_organization_record' ]); const UNFINISHED_MARKER_LINE_PATTERN = /^\s*(?:#{1,6}\s+|[-*+]\s+)?(?:\[(?:TODO|TBD|FIXME)\]|\{\{(?:TODO|TBD|FIXME)\}\}|<(?:TODO|TBD|FIXME)>|(?:TODO|TBD|FIXME)(?:\s*:\s*.*)?\s*)$/i; -const ADR_STATUS_PATTERN = /^\|\s*\[\d{4}\]\(([^)]+)\)\s*\|.*\|\s*(Proposed|Accepted|Superseded|Rejected)\s*\|$/; +const ADR_STATUS_PATTERN = /^\|\s*\[\d{4}\]\(([^)]+)\)\s*\|.*\|\s*(Proposed|Accepted|Superseded|Rejected|proposed_on_active_pr)\s*\|$/; const LOCAL_LINK_PATTERN = /\[[^\]]+\]\((?!https?:\/\/|mailto:|#)([^)]+)\)/g; const CREATE_TABLE_PATTERN = /\bCREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+(?:[a-z_][a-z0-9_]*\.)?([a-z_][a-z0-9_]*)/gi; const DOLLAR_QUOTE_START_PATTERN = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/; diff --git a/tests/foundation-contract.test.mjs b/tests/foundation-contract.test.mjs index 72b18466f..8c8097fa7 100644 --- a/tests/foundation-contract.test.mjs +++ b/tests/foundation-contract.test.mjs @@ -53,6 +53,11 @@ function writeMigrationBackedTables(root) { 'CREATE TABLE job_analysis_write_command (tenant_record_id uuid NOT NULL);' ].join('\n') + '\n' ); + write( + root, + 'database/migrations/0040_employment_employing_organization.sql', + 'CREATE TABLE employment_employing_organization_record (tenant_record_id uuid NOT NULL);\n' + ); } function makeMinimalValidFoundation(root) { @@ -128,6 +133,8 @@ test('required constants are frozen and use accepted values', () => { assert.ok(REQUIRED_FILES.length > 20); assert.ok(DATABASE_OBJECT_NAMES.every(isValidDatabaseObjectName)); assert.ok(DATABASE_OBJECT_NAMES.includes('people_mutation_idempotency_record')); + assert.ok(DATABASE_OBJECT_NAMES.includes('employment_employing_organization_record')); + assert.ok(MIGRATION_BACKED_DATABASE_OBJECT_NAMES.includes('employment_employing_organization_record')); assert.ok(MATURITY_VALUES.has('accepted_architecture')); }); @@ -293,6 +300,21 @@ test('ADR validation is empty when the index is absent', () => { } }); +test('ADR index validates the active-PR status used by proposed ADRs', () => { + const root = temporaryDirectory(); + try { + write( + root, + 'docs/adr/README.md', + '# Index\n| ADR | Title | Status |\n|---|---|---|\n| [0141](0141.md) | Employer | proposed_on_active_pr |\n' + ); + write(root, 'docs/adr/0141.md', '# ADR\n\nStatus: proposed_on_active_pr\n'); + assert.deepEqual(validateAdrIndex(root), []); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test('foundation validator reports a missing repository root', () => { const root = join(tmpdir(), `orgmetra-absent-${Date.now()}`); assert.match(validateFoundation(root)[0], /Repository root does not exist/); From 33169e2c3607fc3c8a24ba549ce4b5f3bcf21273 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 15:25:16 +0900 Subject: [PATCH 17/50] fix(sql): guard employing organization UUIDs --- .../migrations/0040_employment_employing_organization.sql | 8 ++++++++ manifest.json | 2 +- tests/test_operational_uuid_postgres.sh | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/database/migrations/0040_employment_employing_organization.sql b/database/migrations/0040_employment_employing_organization.sql index 48839107a..917779ce2 100644 --- a/database/migrations/0040_employment_employing_organization.sql +++ b/database/migrations/0040_employment_employing_organization.sql @@ -25,6 +25,14 @@ CREATE TABLE employment_employing_organization_record ( CHECK (effective_to IS NULL OR effective_to > effective_from), CONSTRAINT employment_employing_organization_recorded_period_check CHECK (recorded_to IS NULL OR recorded_to > recorded_from), + CONSTRAINT employment_employing_organization_tenant_operational_uuid_check + CHECK (is_operational_uuid(tenant_record_id)), + CONSTRAINT employment_employing_organization_record_operational_uuid_check + CHECK (is_operational_uuid(employment_employing_organization_record_id)), + CONSTRAINT employment_employing_organization_employment_operational_uuid_check + CHECK (is_operational_uuid(employment_record_id)), + CONSTRAINT employment_employing_organization_unit_operational_uuid_check + CHECK (is_operational_uuid(employing_organization_unit_id)), CONSTRAINT employment_employing_organization_tenant_identity_unique UNIQUE (tenant_record_id, employment_employing_organization_record_id), CONSTRAINT employment_employing_organization_bitemporal_exclusion diff --git a/manifest.json b/manifest.json index 5bf27d74c..69ddb3ced 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":"d3ab9c9a73ee1ce3ff84607fccf06754312d5e54a1405c60b2a2d974ccf8c3ee","bytes":17789,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"6c2f38b74f9bcf2e3cfe582cbdda3511c1cd0d7c8e28495266faae39c9a1e2c3","bytes":6058,"lines":132},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6d805fd4ce54ef53831d7b68da2628b5a73fbaa812a4697fa1c84f6e1eb79618","bytes":13973,"lines":88},{"path":"docs/ERD.md","sha256":"0b1bf00abb1184b9f538e6826d4398422872f1e8c386a568bd317d3ed0b1cb8f","bytes":7568,"lines":74},{"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":"2ddedd16b9f8bbbdaf4b08dc3d55dfd6f05813911e0b7d300a567ce98d727954","bytes":11853,"lines":41},{"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":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"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":"756f1e85653b365fed53c3e4ea5ece48f1a44e689442989263ebf37b0ec3cd04","bytes":28417,"lines":693},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"e39c2be1b5934336f8f3e81e605de972ed0ed056321194e8673bd9f60075effd","bytes":9449,"lines":94},{"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":"bba14584ee84fd859ea77b57e04af60890d7fbaf16f83580294a62d68eb435fa","bytes":27425,"lines":640}]} \ 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":"d3ab9c9a73ee1ce3ff84607fccf06754312d5e54a1405c60b2a2d974ccf8c3ee","bytes":17789,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"92ba02cb484bfbce85c1e5592f0c84ff5b3bf7ee13a11e4cf2cb25d4ea10da1b","bytes":6641,"lines":140},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6d805fd4ce54ef53831d7b68da2628b5a73fbaa812a4697fa1c84f6e1eb79618","bytes":13973,"lines":88},{"path":"docs/ERD.md","sha256":"0b1bf00abb1184b9f538e6826d4398422872f1e8c386a568bd317d3ed0b1cb8f","bytes":7568,"lines":74},{"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":"2ddedd16b9f8bbbdaf4b08dc3d55dfd6f05813911e0b7d300a567ce98d727954","bytes":11853,"lines":41},{"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":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"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":"756f1e85653b365fed53c3e4ea5ece48f1a44e689442989263ebf37b0ec3cd04","bytes":28417,"lines":693},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"e39c2be1b5934336f8f3e81e605de972ed0ed056321194e8673bd9f60075effd","bytes":9449,"lines":94},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"bba14584ee84fd859ea77b57e04af60890d7fbaf16f83580294a62d68eb435fa","bytes":27425,"lines":640}]} \ No newline at end of file diff --git a/tests/test_operational_uuid_postgres.sh b/tests/test_operational_uuid_postgres.sh index d77e88588..ef61d5478 100644 --- a/tests/test_operational_uuid_postgres.sh +++ b/tests/test_operational_uuid_postgres.sh @@ -5,6 +5,7 @@ set -euo pipefail psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0001_foundation_schema.sql psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0002_sealed_evidence_digest.sql +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0040_employment_employing_organization.sql uncovered_uuid_columns="$(psql "${DATABASE_URL}" -Atqc " SELECT count(*) From 52f732053e2485c39a569c495e0ef40be5dfc523 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 15:42:46 +0900 Subject: [PATCH 18/50] fix(sql): enforce employing organization coverage --- CHANGELOG.md | 2 +- ...0040_employment_employing_organization.sql | 212 ++++++++++++++++++ docs/DATA_MODEL.md | 2 +- docs/ERD.md | 2 +- docs/TRACEABILITY.md | 2 +- .../0141-employment-employing-organization.md | 4 +- .../employment-employing-organization.md | 2 +- manifest.json | 2 +- scripts/foundation-contract-core.mjs | 2 + ...loyment_employing_organization_postgres.sh | 17 +- tests/validate_repository.py | 2 + 11 files changed, 237 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a971fe8..2a7dc8817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to Orgmetra will be documented in this file. ### Added -- Active-PR employment employing-organization truth: bitemporal, tenant-qualified `employment_employing_organization_record` keeps legal-employer identity independent from Position and Assignment, requires `legal_entity` organization classification and `active`/`leave` Employment coverage, 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. +- 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, 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. - 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. diff --git a/database/migrations/0040_employment_employing_organization.sql b/database/migrations/0040_employment_employing_organization.sql index 917779ce2..93f5603a4 100644 --- a/database/migrations/0040_employment_employing_organization.sql +++ b/database/migrations/0040_employment_employing_organization.sql @@ -104,6 +104,218 @@ BEFORE INSERT ON employment_employing_organization_record FOR EACH ROW EXECUTE FUNCTION validate_employment_employing_organization_scope(); +CREATE FUNCTION employment_employing_organization_exact_one_is_valid( + target_tenant_record_id uuid, + target_employment_record_id uuid +) +RETURNS boolean +LANGUAGE sql +STABLE +AS $$ +WITH active_employment_versions AS ( + SELECT effective_from, effective_to, recorded_from, recorded_to + FROM employment_record_version + WHERE tenant_record_id = target_tenant_record_id + AND employment_record_id = target_employment_record_id + AND employment_status_code IN ('active', 'leave') +), relationship_rows AS ( + SELECT effective_from, effective_to, recorded_from, recorded_to, + employing_organization_unit_id + FROM employment_employing_organization_record + WHERE tenant_record_id = target_tenant_record_id + AND employment_record_id = target_employment_record_id +), recorded_points AS ( + SELECT recorded_from AS recorded_point FROM active_employment_versions + UNION + SELECT recorded_to FROM active_employment_versions WHERE recorded_to IS NOT NULL + UNION + SELECT recorded_from FROM relationship_rows + UNION + SELECT recorded_to FROM relationship_rows WHERE recorded_to IS NOT NULL + UNION + SELECT organization_version.recorded_from + FROM organization_unit_version organization_version + WHERE organization_version.tenant_record_id = target_tenant_record_id + AND organization_version.organization_unit_id IN ( + SELECT employing_organization_unit_id FROM relationship_rows + ) + UNION + SELECT organization_version.recorded_to + FROM organization_unit_version organization_version + WHERE organization_version.tenant_record_id = target_tenant_record_id + AND organization_version.organization_unit_id IN ( + SELECT employing_organization_unit_id FROM relationship_rows + ) + AND organization_version.recorded_to IS NOT NULL +) +SELECT NOT EXISTS ( + SELECT 1 + FROM active_employment_versions employment_version + JOIN recorded_points point + ON tstzrange( + employment_version.recorded_from, + employment_version.recorded_to, + '[)' + ) @> point.recorded_point + WHERE NOT EXISTS ( + SELECT 1 + FROM ( + SELECT range_agg( + daterange( + relationship.effective_from, + relationship.effective_to, + '[)' + ) + ) AS covered_effective_period + FROM relationship_rows relationship + WHERE tstzrange( + relationship.recorded_from, + relationship.recorded_to, + '[)' + ) @> point.recorded_point + AND EXISTS ( + SELECT 1 + FROM ( + SELECT range_agg( + daterange( + organization_version.effective_from, + organization_version.effective_to, + '[)' + ) + ) AS legal_entity_effective_period + FROM organization_unit_version organization_version + WHERE organization_version.tenant_record_id = target_tenant_record_id + AND organization_version.organization_unit_id = relationship.employing_organization_unit_id + AND organization_version.organization_type_code = 'legal_entity' + AND tstzrange( + organization_version.recorded_from, + organization_version.recorded_to, + '[)' + ) @> point.recorded_point + ) legal_entity + WHERE legal_entity.legal_entity_effective_period @> daterange( + relationship.effective_from, + relationship.effective_to, + '[)' + ) + ) + ) relationship_coverage + WHERE relationship_coverage.covered_effective_period @> daterange( + employment_version.effective_from, + employment_version.effective_to, + '[)' + ) + ) +); +$$; + +CREATE FUNCTION enforce_employment_employing_organization_exact_one() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + v_tenant_record_id uuid; + v_employment_record_id uuid; + v_organization_unit_id uuid; + old_tenant_record_id uuid; + old_organization_unit_id uuid; + affected_tenant_record_id uuid; + affected_employment_record_id uuid; +BEGIN + IF TG_TABLE_NAME IN ( + 'employment_record_version', + 'employment_employing_organization_record' + ) THEN + IF TG_OP = 'DELETE' THEN + v_tenant_record_id := OLD.tenant_record_id; + v_employment_record_id := OLD.employment_record_id; + ELSE + v_tenant_record_id := NEW.tenant_record_id; + v_employment_record_id := NEW.employment_record_id; + END IF; + + IF NOT employment_employing_organization_exact_one_is_valid( + v_tenant_record_id, + v_employment_record_id + ) THEN + RAISE EXCEPTION 'active or leave Employment must have exactly one legal employer across every effective and recorded-time coordinate' + USING ERRCODE = '23514'; + END IF; + + IF TG_OP = 'UPDATE' + AND ( + OLD.tenant_record_id IS DISTINCT FROM NEW.tenant_record_id + OR OLD.employment_record_id IS DISTINCT FROM NEW.employment_record_id + ) + AND NOT employment_employing_organization_exact_one_is_valid( + OLD.tenant_record_id, + OLD.employment_record_id + ) THEN + RAISE EXCEPTION 'active or leave Employment must have exactly one legal employer across every effective and recorded-time coordinate' + USING ERRCODE = '23514'; + END IF; + ELSE + IF TG_OP = 'DELETE' THEN + v_tenant_record_id := OLD.tenant_record_id; + v_organization_unit_id := OLD.organization_unit_id; + ELSE + v_tenant_record_id := NEW.tenant_record_id; + v_organization_unit_id := NEW.organization_unit_id; + END IF; + + IF TG_OP = 'UPDATE' THEN + old_tenant_record_id := OLD.tenant_record_id; + old_organization_unit_id := OLD.organization_unit_id; + END IF; + + FOR affected_tenant_record_id, affected_employment_record_id IN + SELECT DISTINCT relation.tenant_record_id, relation.employment_record_id + FROM employment_employing_organization_record relation + WHERE ( + relation.tenant_record_id = v_tenant_record_id + AND relation.employing_organization_unit_id = v_organization_unit_id + ) + OR ( + TG_OP = 'UPDATE' + AND relation.tenant_record_id = old_tenant_record_id + AND relation.employing_organization_unit_id = old_organization_unit_id + ) + LOOP + IF NOT employment_employing_organization_exact_one_is_valid( + affected_tenant_record_id, + affected_employment_record_id + ) THEN + RAISE EXCEPTION 'active or leave Employment must have exactly one legal employer across every effective and recorded-time coordinate' + USING ERRCODE = '23514'; + END IF; + END LOOP; + END IF; + + IF TG_OP = 'DELETE' THEN + RETURN OLD; + END IF; + RETURN NEW; +END; +$$; + +CREATE CONSTRAINT TRIGGER employment_employing_organization_exact_one_employment_guard +AFTER INSERT OR UPDATE OR DELETE ON employment_record_version +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE FUNCTION enforce_employment_employing_organization_exact_one(); + +CREATE CONSTRAINT TRIGGER employment_employing_organization_exact_one_relationship_guard +AFTER INSERT OR UPDATE OR DELETE ON employment_employing_organization_record +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE FUNCTION enforce_employment_employing_organization_exact_one(); + +CREATE CONSTRAINT TRIGGER employment_employing_organization_exact_one_organization_guard +AFTER INSERT OR UPDATE OR DELETE ON organization_unit_version +DEFERRABLE INITIALLY DEFERRED +FOR EACH ROW +EXECUTE FUNCTION enforce_employment_employing_organization_exact_one(); + CREATE TRIGGER employment_employing_organization_bitemporal_guard BEFORE UPDATE OR DELETE ON employment_employing_organization_record FOR EACH ROW diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md index a8995056d..4674dd01b 100644 --- a/docs/DATA_MODEL.md +++ b/docs/DATA_MODEL.md @@ -53,7 +53,7 @@ Intervals are half-open and non-empty: an end value, when present, must be stric Durable anchors such as `organization_unit`, `job_profile`, `employment_record`, and `position_record` do not repeat mutable descriptive attributes. Their descriptive versions live in `organization_unit_version`, `job_profile_version`, `employment_record_version`, and `position_record_version`. Single-valued bitemporal version families reject overlapping effective/system intervals, so one `effective_from`/`effective_to` interval combined with one `recorded_from`/`recorded_to` interval cannot yield contradictory current descriptions. Corrections close the previous recorded interval and insert a replacement; in-place business mutation is rejected. -`employment_employing_organization_record` is a separate bitemporal Employment-to-Organization fact. At a business-time/system-time coordinate, an Employment has at most one employing Organization; the Organization must be a `legal_entity`, and the relationship interval must be covered by `active` or `leave` Employment truth. Position placement and Assignment allocation do not determine legal-employer identity. +`employment_employing_organization_record` is a separate bitemporal Employment-to-Organization fact. Every `active` or `leave` Employment coordinate has exactly one employing Organization, never more than one, with deferred database validation across effective and recorded-time changes. The Organization must be a `legal_entity`, and the relationship interval must be covered by `active` or `leave` Employment truth. Position placement and Assignment allocation do not determine legal-employer identity. 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. diff --git a/docs/ERD.md b/docs/ERD.md index 0f5d7f0e5..51592a39f 100644 --- a/docs/ERD.md +++ b/docs/ERD.md @@ -51,7 +51,7 @@ erDiagram `organization_unit`, `job_profile`, `employment_record`, and `position_record` are durable anchors. Mutable names, classifications, parent relationships, titles, families, version codes, and employment or position status live in bitemporal version rows. Positions retain stable organization/job references while retroactive corrections append or supersede version facts rather than rewriting identity. An organization version may reference another durable organization as its parent; self-parenting is rejected at the database boundary. An assignment names the employment that covers it, so a person cannot be assigned through another worker's employment. Exclusive employment versions for one person cannot overlap. An assignment day must land on an `active` or `open` position version, and visible allocations for one seat cannot exceed 1.0000. -`employment_employing_organization_record` is the independent legal-employer relationship. It is bitemporal and tenant-qualified, permits at most one employer for an Employment at one effective/system coordinate, requires `legal_entity` classification across the full effective interval, and requires `active` or `leave` Employment coverage. A Position or Assignment therefore cannot silently substitute for legal-employer truth. +`employment_employing_organization_record` is the independent legal-employer relationship. It is bitemporal and tenant-qualified, requires exactly one employer for every `active` or `leave` Employment effective/system coordinate with no effective gaps, and rejects overlaps through the bitemporal exclusion. Deferred database validation also follows Employment, relationship, and legal-organization recorded-time changes. The employer requires `legal_entity` classification across the full effective interval and `active` or `leave` Employment coverage. A Position or Assignment therefore cannot silently substitute for legal-employer truth. Every owned HRIS fact carries `tenant_record_id`. Relationships that cross table boundaries use tenant-qualified foreign keys, and row-level security independently filters every tenant-scoped relation. The tenant column is therefore both a referential-integrity boundary and a runtime isolation boundary, not a caller-supplied business attribute. diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index c5716ba63..591361a9d 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -9,7 +9,7 @@ | Tenant-qualified HRIS integrity and fail-closed isolation | Core bounded contexts / Security architecture | `tenant_record`, tenant-qualified foreign keys, forced row-level security policies, tenant-scoped kernel query parameters | PostgreSQL cross-tenant FK/application-role RLS contracts plus kernel cross-tenant reconstruction, employment coverage, position coverage, seat-capacity, portfolio, exclusivity, and organization-hierarchy regressions | ADR-0001, ADR-0003 | implemented_on_active_pr | | Reserved UUID sentinel exclusion | Persistence integrity boundary | every foundation UUID `*_id` column plus audit/outbox identifiers | PostgreSQL inventory proof plus Nil/Max foundation and audit/outbox persistence regressions | ADR-0001, RFC 9562 | implemented_on_active_pr | | Normalized bitemporal organization/job/employment/position history | Core bounded contexts | `organization_unit_version`, `job_profile_version`, `employment_record_version`, `position_record_version` | PostgreSQL non-overlap, concurrent conflict, correction, rewrite-rejection, assignment-employment binding, and single-valued historical reconstruction | ADR-0001, ADR-0003, ADR-0004 | implemented_on_active_pr | -| Authoritative employing legal-organization relationship | Employment / Organization core | `employment_employing_organization_record` | `test_employment_employing_organization_postgres.sh` proves one employer per Employment coordinate, legal-entity and active/leave coverage, correction history, tenant-qualified integrity, and forced-RLS isolation | ADR-0141 | implemented_on_active_pr | +| Authoritative employing legal-organization relationship | Employment / Organization core | `employment_employing_organization_record` | `test_employment_employing_organization_postgres.sh` proves exactly one legal employer for every active/leave Employment coordinate, rejects missing relationships and effective gaps, proves legal-entity and active/leave coverage, correction history, tenant-qualified integrity, and forced-RLS isolation | ADR-0141 | implemented_on_active_pr | | Acyclic organization hierarchy at historical coordinates | Organization core | `organization_unit_version.parent_organization_unit_id` | indirect A→B→C→A rejection plus future-recorded and foreign-tenant isolation in `orgmetra_hris_kernel` | ADR-0001, ADR-0003 | implemented_on_protected_main | | Effective/system time | Bitemporal HRIS | `effective_from`, `recorded_from` | strict half-open interval and historical-coordinate tests | ADR-0003 | implemented_on_active_pr | | 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 | diff --git a/docs/adr/0141-employment-employing-organization.md b/docs/adr/0141-employment-employing-organization.md index e409838ec..b142170a9 100644 --- a/docs/adr/0141-employment-employing-organization.md +++ b/docs/adr/0141-employment-employing-organization.md @@ -17,7 +17,7 @@ ISO 30414:2025 is the current published second edition of the human-capital repo Add `employment_employing_organization_record` as a 3NF bitemporal relationship between one tenant-scoped Employment and one tenant-scoped Organization. -At any single effective-date/system-recorded-time coordinate, one Employment may resolve to at most one employing organization. The organization must be classified as `legal_entity` across the relationship's full effective interval at the relationship's recorded-time coordinate. The relationship interval must also be fully covered by `active` or `leave` Employment truth at that same recorded-time coordinate. +At every effective-date/system-recorded-time coordinate where Employment status is `active` or `leave`, exactly one employing organization must resolve: overlaps are rejected by the bitemporal exclusion and missing relationships or effective gaps are rejected by deferred database constraint triggers. Those triggers revalidate Employment version, relationship, and legal-organization recorded-time changes together at transaction commit. The organization must be classified as `legal_entity` across the relationship's full effective interval at the relationship's recorded-time coordinate. The relationship interval must also be fully covered by `active` or `leave` Employment truth at that same recorded-time coordinate. 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. @@ -42,7 +42,7 @@ History is correction-not-rewrite: business fields cannot be updated in place; t `tests/test_employment_employing_organization_postgres.sh` proves: -- one employer per Employment business/system coordinate; +- exactly one employer per active/leave Employment business/system coordinate, including missing and effective-gap rejection; - full-interval `legal_entity` classification; - full-interval active/leave Employment coverage; - tenant-qualified cross-tenant rejection; diff --git a/docs/doctoring/employment-employing-organization.md b/docs/doctoring/employment-employing-organization.md index 482a4501e..125c2f6ad 100644 --- a/docs/doctoring/employment-employing-organization.md +++ b/docs/doctoring/employment-employing-organization.md @@ -14,7 +14,7 @@ Orgmetra uses that published standard only as reporting-context evidence that wo A Position is an organizational seat. An Employment is the durable worker-employment relationship. Treating `position_record.organization_unit_id` as the legal employer would therefore make a seat transfer silently change employment-contract scope and would make a legal-employer transfer indistinguishable from a seat move. -The active PR instead records a tenant-scoped, bitemporal `employment_employing_organization_record` relationship. The referenced Organization must be `legal_entity` over the full effective interval at the row's recorded-time coordinate, while the same interval must be covered by active/leave Employment truth. No payroll, withholding, tax, statutory-accounting, benefits, compensation, candidate, performance, or model-output fields are added. +The active PR instead records a tenant-scoped, bitemporal `employment_employing_organization_record` relationship. Every active/leave Employment effective/system coordinate must have exactly one such legal employer, with deferred database checks rejecting missing relationships, effective gaps, and changes that invalidate recorded-time coverage. The referenced Organization must be `legal_entity` over the full effective interval at the row's recorded-time coordinate, while the same interval must be covered by active/leave Employment truth. No payroll, withholding, tax, statutory-accounting, benefits, compensation, candidate, performance, or model-output fields are added. This is an HRIS source-of-truth relationship, not a claim of payroll or statutory-system ownership. diff --git a/manifest.json b/manifest.json index 69ddb3ced..3647a47e4 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":"d3ab9c9a73ee1ce3ff84607fccf06754312d5e54a1405c60b2a2d974ccf8c3ee","bytes":17789,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"92ba02cb484bfbce85c1e5592f0c84ff5b3bf7ee13a11e4cf2cb25d4ea10da1b","bytes":6641,"lines":140},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6d805fd4ce54ef53831d7b68da2628b5a73fbaa812a4697fa1c84f6e1eb79618","bytes":13973,"lines":88},{"path":"docs/ERD.md","sha256":"0b1bf00abb1184b9f538e6826d4398422872f1e8c386a568bd317d3ed0b1cb8f","bytes":7568,"lines":74},{"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":"2ddedd16b9f8bbbdaf4b08dc3d55dfd6f05813911e0b7d300a567ce98d727954","bytes":11853,"lines":41},{"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":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"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":"756f1e85653b365fed53c3e4ea5ece48f1a44e689442989263ebf37b0ec3cd04","bytes":28417,"lines":693},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"e39c2be1b5934336f8f3e81e605de972ed0ed056321194e8673bd9f60075effd","bytes":9449,"lines":94},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"bba14584ee84fd859ea77b57e04af60890d7fbaf16f83580294a62d68eb435fa","bytes":27425,"lines":640}]} \ 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"c23c5a7ce6840011b631fb631fe4388b6975a4f0994916e0d600f18c0d31df74","bytes":11748,"lines":103},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ No newline at end of file diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index fed0a7104..f2c538c08 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -52,7 +52,9 @@ export const REQUIRED_FILES = Object.freeze([ 'docs/adr/0012-governed-migration-handoff.md', 'docs/adr/0013-governed-requisition-review-packet.md', 'docs/adr/0014-job-analysis-snapshot-persistence.md', + 'docs/adr/0141-employment-employing-organization.md', 'docs/doctoring/REFERENCES.md', + 'docs/doctoring/employment-employing-organization.md', 'docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md', 'docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md', 'database/migrations/0001_foundation_schema.sql', diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 3e6d4d689..8c9e500e3 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -7,6 +7,7 @@ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0001_foundation psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0040_employment_employing_organization.sql psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' +BEGIN; INSERT INTO tenant_record (tenant_record_id, tenant_reference) VALUES ('10000000-0000-7000-8000-000000000001','tenant_alpha'), ('20000000-0000-7000-8000-000000000001','tenant_beta'); @@ -43,10 +44,14 @@ INSERT INTO employment_employing_organization_record ( ) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000050', '10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030', - DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'), + DATE '2026-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000051', + '10000000-0000-7000-8000-000000000022','10000000-0000-7000-8000-000000000031', + DATE '2026-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'), ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000050', '20000000-0000-7000-8000-000000000020','20000000-0000-7000-8000-000000000030', - DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); + DATE '2026-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00'); +COMMIT; SQL count="$(psql "${DATABASE_URL}" -Atqc "SELECT count(*) FROM employment_employing_organization_record WHERE tenant_record_id='10000000-0000-7000-8000-000000000001'::uuid AND employment_record_id='10000000-0000-7000-8000-000000000020'::uuid AND daterange(effective_from,effective_to,'[)') @> DATE '2026-08-28' AND tstzrange(recorded_from,recorded_to,'[)') @> TIMESTAMPTZ '2026-08-28 00:00:00+00';")" @@ -62,17 +67,21 @@ expect_failure() { [[ $status -ne 0 && "$output" == *"$expected"* ]] || { echo "expected failure '$expected', got: $output" >&2; exit 1; } } -expect_failure employment_employing_organization_bitemporal_exclusion psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000051','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000031',DATE '2026-06-01',TIMESTAMPTZ '2026-01-04 00:00:00+00');" -expect_failure "employing organization must be a legal_entity" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000052','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000032',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" +expect_failure employment_employing_organization_bitemporal_exclusion psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000058','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000031',DATE '2026-06-01',TIMESTAMPTZ '2026-01-04 00:00:00+00');" +expect_failure "employing organization must be a legal_entity" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000059','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000032',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" expect_failure "employing organization interval must be covered by active or leave Employment truth" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000055','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030',DATE '2025-01-01',DATE '2025-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" # Separate Employment prevents the cardinality exclusion from masking the tenant FK boundary. expect_failure employment_employing_organization_unit_tenant_fk psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000053','10000000-0000-7000-8000-000000000022','20000000-0000-7000-8000-000000000030',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" +expect_failure "exactly one legal employer" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO employment_record (tenant_record_id,employment_record_id,person_record_id) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000026','10000000-0000-7000-8000-000000000010'); INSERT INTO employment_record_version (tenant_record_id,employment_record_version_id,employment_record_id,employment_status_code,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000027','10000000-0000-7000-8000-000000000026','active',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); COMMIT;" +expect_failure "exactly one legal employer" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO employment_record (tenant_record_id,employment_record_id,person_record_id) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000010'); INSERT INTO employment_record_version (tenant_record_id,employment_record_version_id,employment_record_id,employment_status_code,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000029','10000000-0000-7000-8000-000000000028','active',DATE '2026-01-01',DATE '2026-12-31',TIMESTAMPTZ '2026-01-03 00:00:00+00'); INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000060','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000030',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'), ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000061','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000030',DATE '2026-07-01',DATE '2026-12-31',TIMESTAMPTZ '2026-01-03 00:00:00+00'); COMMIT;" expect_failure "bitemporal correction may only close an open recorded interval" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "UPDATE employment_employing_organization_record SET employing_organization_unit_id='10000000-0000-7000-8000-000000000031' WHERE employment_employing_organization_record_id='10000000-0000-7000-8000-000000000050';" expect_failure "employment employing-organization history cannot be truncated" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "TRUNCATE employment_employing_organization_record;" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' +BEGIN; UPDATE employment_employing_organization_record SET recorded_to=TIMESTAMPTZ '2026-09-01 00:00:00+00' WHERE employment_employing_organization_record_id='10000000-0000-7000-8000-000000000050'; INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000054','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000031',DATE '2026-01-01',TIMESTAMPTZ '2026-09-01 00:00:00+00'); +COMMIT; CREATE ROLE orgmetra_employer_reader NOSUPERUSER NOBYPASSRLS; GRANT SELECT ON employment_employing_organization_record TO orgmetra_employer_reader; SET ROLE orgmetra_employer_reader; diff --git a/tests/validate_repository.py b/tests/validate_repository.py index 4ff2c690a..1ac1e7322 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -55,7 +55,9 @@ "docs/adr/0012-governed-migration-handoff.md", "docs/adr/0013-governed-requisition-review-packet.md", "docs/adr/0014-job-analysis-snapshot-persistence.md", + "docs/adr/0141-employment-employing-organization.md", "docs/doctoring/REFERENCES.md", + "docs/doctoring/employment-employing-organization.md", "docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md", "docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md", "database/migrations/0001_foundation_schema.sql", From 1f6e282255e34db9f413d618a17e177bac21803d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 15:45:03 +0900 Subject: [PATCH 19/50] fix(ci): load UUID guard dependency --- .github/workflows/employment-employing-organization-quality.yml | 1 + manifest.json | 2 +- tests/test_employment_employing_organization_postgres.sh | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml index b42546287..355b85313 100644 --- a/.github/workflows/employment-employing-organization-quality.yml +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -6,6 +6,7 @@ on: - develop paths: - "database/migrations/0001_foundation_schema.sql" + - "database/migrations/0002_sealed_evidence_digest.sql" - "database/migrations/0040_employment_employing_organization.sql" - "tests/test_employment_employing_organization_postgres.sh" - "scripts/foundation-contract-core.mjs" diff --git a/manifest.json b/manifest.json index 3647a47e4..0f33641af 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"c23c5a7ce6840011b631fb631fe4388b6975a4f0994916e0d600f18c0d31df74","bytes":11748,"lines":103},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"cbd7942774250f9968a1a3f05dec524089ac0b7e58d1fb7eef32414695d55c20","bytes":11845,"lines":104},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ No newline at end of file diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 8c9e500e3..db7a2e140 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -4,6 +4,7 @@ set -euo pipefail : "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0001_foundation_schema.sql +psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0002_sealed_evidence_digest.sql psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0040_employment_employing_organization.sql psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' From 852dc9aa8d17b68ad7efe9e5fb3a82954fd03b56 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 15:49:33 +0900 Subject: [PATCH 20/50] test(sql): isolate foreign-employer fixture --- manifest.json | 2 +- .../test_employment_employing_organization_postgres.sh | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index 0f33641af..25e080e00 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"cbd7942774250f9968a1a3f05dec524089ac0b7e58d1fb7eef32414695d55c20","bytes":11845,"lines":104},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"f30769042f73c7c2cfff892c4bde526a0a9fb24b489cf7c625afc26ba6482071","bytes":11666,"lines":106},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ No newline at end of file diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index db7a2e140..97228cf1a 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -18,6 +18,8 @@ INSERT INTO person_record (tenant_record_id, person_record_id) VALUES INSERT INTO employment_record (tenant_record_id, employment_record_id, person_record_id) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000010'), ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000022','10000000-0000-7000-8000-000000000010'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000026','10000000-0000-7000-8000-000000000010'), + ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000010'), ('20000000-0000-7000-8000-000000000001','20000000-0000-7000-8000-000000000020','20000000-0000-7000-8000-000000000010'); INSERT INTO employment_record_version ( tenant_record_id, employment_record_version_id, employment_record_id, @@ -71,10 +73,10 @@ expect_failure() { expect_failure employment_employing_organization_bitemporal_exclusion psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000058','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000031',DATE '2026-06-01',TIMESTAMPTZ '2026-01-04 00:00:00+00');" expect_failure "employing organization must be a legal_entity" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000059','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000032',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" expect_failure "employing organization interval must be covered by active or leave Employment truth" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000055','10000000-0000-7000-8000-000000000020','10000000-0000-7000-8000-000000000030',DATE '2025-01-01',DATE '2025-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" -# Separate Employment prevents the cardinality exclusion from masking the tenant FK boundary. -expect_failure employment_employing_organization_unit_tenant_fk psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000053','10000000-0000-7000-8000-000000000022','20000000-0000-7000-8000-000000000030',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" -expect_failure "exactly one legal employer" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO employment_record (tenant_record_id,employment_record_id,person_record_id) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000026','10000000-0000-7000-8000-000000000010'); INSERT INTO employment_record_version (tenant_record_id,employment_record_version_id,employment_record_id,employment_status_code,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000027','10000000-0000-7000-8000-000000000026','active',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); COMMIT;" -expect_failure "exactly one legal employer" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO employment_record (tenant_record_id,employment_record_id,person_record_id) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000010'); INSERT INTO employment_record_version (tenant_record_id,employment_record_version_id,employment_record_id,employment_status_code,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000029','10000000-0000-7000-8000-000000000028','active',DATE '2026-01-01',DATE '2026-12-31',TIMESTAMPTZ '2026-01-03 00:00:00+00'); INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000060','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000030',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'), ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000061','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000030',DATE '2026-07-01',DATE '2026-12-31',TIMESTAMPTZ '2026-01-03 00:00:00+00'); COMMIT;" +# Separate Employment anchors prevent the cardinality exclusion from masking the tenant FK boundary. +expect_failure employment_employing_organization_unit_tenant_fk psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000053','10000000-0000-7000-8000-000000000026','20000000-0000-7000-8000-000000000030',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00');" +expect_failure "exactly one legal employer" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO employment_record_version (tenant_record_id,employment_record_version_id,employment_record_id,employment_status_code,effective_from,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000027','10000000-0000-7000-8000-000000000026','active',DATE '2026-01-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'); COMMIT;" +expect_failure "exactly one legal employer" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "BEGIN; INSERT INTO employment_record_version (tenant_record_id,employment_record_version_id,employment_record_id,employment_status_code,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000029','10000000-0000-7000-8000-000000000028','active',DATE '2026-01-01',DATE '2026-12-31',TIMESTAMPTZ '2026-01-03 00:00:00+00'); INSERT INTO employment_employing_organization_record (tenant_record_id,employment_employing_organization_record_id,employment_record_id,employing_organization_unit_id,effective_from,effective_to,recorded_from) VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000060','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000030',DATE '2026-01-01',DATE '2026-06-01',TIMESTAMPTZ '2026-01-03 00:00:00+00'), ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000061','10000000-0000-7000-8000-000000000028','10000000-0000-7000-8000-000000000030',DATE '2026-07-01',DATE '2026-12-31',TIMESTAMPTZ '2026-01-03 00:00:00+00'); COMMIT;" expect_failure "bitemporal correction may only close an open recorded interval" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "UPDATE employment_employing_organization_record SET employing_organization_unit_id='10000000-0000-7000-8000-000000000031' WHERE employment_employing_organization_record_id='10000000-0000-7000-8000-000000000050';" expect_failure "employment employing-organization history cannot be truncated" psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "TRUNCATE employment_employing_organization_record;" From a9fb199893f72bb8ce5a11a647b4b9aa6268b6ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 15:51:42 +0900 Subject: [PATCH 21/50] test(sql): scope RLS correction assertion --- manifest.json | 2 +- tests/test_employment_employing_organization_postgres.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 25e080e00..9ab7196f0 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"f30769042f73c7c2cfff892c4bde526a0a9fb24b489cf7c625afc26ba6482071","bytes":11666,"lines":106},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"495ddad49a409d43ed1bfd05dec0726316623c495ea000b23a989ab9adbba51f","bytes":11806,"lines":106},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ No newline at end of file diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 97228cf1a..9461ce381 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -90,13 +90,13 @@ GRANT SELECT ON employment_employing_organization_record TO orgmetra_employer_re SET ROLE orgmetra_employer_reader; SELECT set_config('orgmetra.tenant_record_id','10000000-0000-7000-8000-000000000001',false); DO $$ BEGIN - IF (SELECT count(*) FROM employment_employing_organization_record WHERE tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN + IF (SELECT count(*) FROM employment_employing_organization_record WHERE employment_record_id='10000000-0000-7000-8000-000000000020'::uuid AND tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN RAISE EXCEPTION 'tenant alpha should see exactly one employer fact at the selected system-time coordinate'; END IF; END $$; SELECT set_config('orgmetra.tenant_record_id','20000000-0000-7000-8000-000000000001',false); DO $$ BEGIN - IF (SELECT count(*) FROM employment_employing_organization_record WHERE tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN + IF (SELECT count(*) FROM employment_employing_organization_record WHERE employment_record_id='20000000-0000-7000-8000-000000000020'::uuid AND tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN RAISE EXCEPTION 'tenant beta should see exactly one employer fact at the selected system-time coordinate'; END IF; END $$; From f7bb1e3b7d14bac5b9a77b268fa176c7230702fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 16:22:25 +0900 Subject: [PATCH 22/50] fix(people): materialize employing organization --- CHANGELOG.md | 2 +- docs/API_CONTRACT.md | 4 +- .../employment-employing-organization.md | 2 + manifest.json | 2 +- schemas/openapi.yaml | 4 ++ .../src/orgmetra_people_api/hire.py | 4 ++ .../src/orgmetra_people_api/hire_http.py | 8 ++++ .../src/orgmetra_people_api/mutation_http.py | 7 +++- .../src/orgmetra_people_api/mutations.py | 5 +++ .../src/orgmetra_people_api/postgres_hire.py | 27 ++++++++++++++ .../orgmetra_people_api/postgres_mutations.py | 23 ++++++++++++ .../tests/test_decision_reason_binding.py | 3 ++ ...t_evidence_reference_binding_regression.py | 5 ++- .../people-api/tests/test_hire_acceptance.py | 4 ++ .../people-api/tests/test_hire_http_route.py | 4 ++ .../tests/test_hire_http_scalar_contract.py | 2 + ...test_mutation_http_authentication_order.py | 4 +- .../tests/test_mutation_http_route.py | 4 +- .../tests/test_mutation_http_schema_types.py | 3 ++ .../people-api/tests/test_people_mutations.py | 2 + .../tests/test_postgres_hire_acceptance.py | 37 +++++++++++++++++-- .../tests/test_postgres_people_mutations.py | 30 ++++++++++++++- .../tests/test_review_regressions.py | 4 ++ .../test_support_reference_correlation.py | 5 +++ ...test_support_reference_response_privacy.py | 3 ++ 25 files changed, 186 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7dc8817..75522655f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to Orgmetra will be documented in this file. ### Added -- 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, 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. +- 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. - 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. diff --git a/docs/API_CONTRACT.md b/docs/API_CONTRACT.md index 27235d9c9..7303eca6a 100644 --- a/docs/API_CONTRACT.md +++ b/docs/API_CONTRACT.md @@ -41,6 +41,8 @@ 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 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. ## Example endpoints @@ -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. \ No newline at end of file +`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. diff --git a/docs/doctoring/employment-employing-organization.md b/docs/doctoring/employment-employing-organization.md index 125c2f6ad..6b5f4ac23 100644 --- a/docs/doctoring/employment-employing-organization.md +++ b/docs/doctoring/employment-employing-organization.md @@ -16,6 +16,8 @@ A Position is an organizational seat. An Employment is the durable worker-employ The active PR instead records a tenant-scoped, bitemporal `employment_employing_organization_record` relationship. Every active/leave Employment effective/system coordinate must have exactly one such legal employer, with deferred database checks rejecting missing relationships, effective gaps, and changes that invalidate recorded-time coverage. The referenced Organization must be `legal_entity` over the full effective interval at the row's recorded-time coordinate, while the same interval must be covered by active/leave Employment truth. No payroll, withholding, tax, statutory-accounting, benefits, compensation, candidate, performance, or model-output fields are added. +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. + This is an HRIS source-of-truth relationship, not a claim of payroll or statutory-system ownership. ## Reference (APA 7) diff --git a/manifest.json b/manifest.json index 9ab7196f0..c761fdfab 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":"4b5a81acac1cc23b25b45ede43492c307117101e6923a5947062756ba1d92e66","bytes":17890,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"0314bc3158da40b7adf71a8638467e5d575b4051ffe8426b393f72b1c1ec36d3","bytes":2275,"lines":23},{"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":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"495ddad49a409d43ed1bfd05dec0726316623c495ea000b23a989ab9adbba51f","bytes":11806,"lines":106},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ 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":"f423ccc844ff7f520c8c487464a83dc1433217b1acbe7db1f303379cb1d96085","bytes":17977,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"20cb5a8fc24f586f0c220438effa6cff256a758f52b9b0fcd51736b8b1155a72","bytes":4983,"lines":78},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"495ddad49a409d43ed1bfd05dec0726316623c495ea000b23a989ab9adbba51f","bytes":11806,"lines":106},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} diff --git a/schemas/openapi.yaml b/schemas/openapi.yaml index 0fd397e92..7ae9e61f7 100644 --- a/schemas/openapi.yaml +++ b/schemas/openapi.yaml @@ -525,6 +525,7 @@ components: type: object additionalProperties: false required: + - employing_organization_unit_id - person_record_id - employment_status_code - employment_concurrency_code @@ -533,6 +534,9 @@ components: - confirmation_reference - evidence_references properties: + employing_organization_unit_id: + type: string + format: uuid person_record_id: type: string format: uuid diff --git a/services/people-api/src/orgmetra_people_api/hire.py b/services/people-api/src/orgmetra_people_api/hire.py index 6823f4c59..db2693910 100644 --- a/services/people-api/src/orgmetra_people_api/hire.py +++ b/services/people-api/src/orgmetra_people_api/hire.py @@ -52,12 +52,14 @@ class HireAcceptanceCommand: """ tenant_record_id: UUID + employing_organization_unit_id: UUID candidate_profile_id: UUID selection_decision_id: UUID person_record_id: UUID person_name_record_id: UUID employment_record_id: UUID employment_record_version_id: UUID + employment_employing_organization_record_id: UUID candidate_worker_conversion_record_id: UUID audit_event_record_id: UUID outbox_delivery_record_id: UUID @@ -70,12 +72,14 @@ def __post_init__(self) -> None: """Fail closed before authorization or persistence on malformed input.""" for field_name in ( "tenant_record_id", + "employing_organization_unit_id", "candidate_profile_id", "selection_decision_id", "person_record_id", "person_name_record_id", "employment_record_id", "employment_record_version_id", + "employment_employing_organization_record_id", "candidate_worker_conversion_record_id", "audit_event_record_id", "outbox_delivery_record_id", diff --git a/services/people-api/src/orgmetra_people_api/hire_http.py b/services/people-api/src/orgmetra_people_api/hire_http.py index 3b1ddf461..cf6fbff7a 100644 --- a/services/people-api/src/orgmetra_people_api/hire_http.py +++ b/services/people-api/src/orgmetra_people_api/hire_http.py @@ -52,12 +52,14 @@ _SUPPORT_REFERENCE_RANDOM_BYTES = 24 _REQUIRED_BODY_KEYS = frozenset( { + "employing_organization_unit_id", "candidate_profile_id", "selection_decision_id", "person_record_id", "person_name_record_id", "employment_record_id", "employment_record_version_id", + "employment_employing_organization_record_id", "candidate_worker_conversion_record_id", "audit_event_record_id", "outbox_delivery_record_id", @@ -512,6 +514,9 @@ def _command_from_payload( effective_from = date.fromisoformat(effective_from_raw) return HireAcceptanceCommand( tenant_record_id=tenant_record_id, + employing_organization_unit_id=UUID( + _require_string_field(payload, "employing_organization_unit_id") + ), candidate_profile_id=UUID(_require_string_field(payload, "candidate_profile_id")), selection_decision_id=UUID(_require_string_field(payload, "selection_decision_id")), person_record_id=UUID(_require_string_field(payload, "person_record_id")), @@ -520,6 +525,9 @@ def _command_from_payload( employment_record_version_id=UUID( _require_string_field(payload, "employment_record_version_id") ), + employment_employing_organization_record_id=UUID( + _require_string_field(payload, "employment_employing_organization_record_id") + ), candidate_worker_conversion_record_id=UUID( _require_string_field(payload, "candidate_worker_conversion_record_id") ), diff --git a/services/people-api/src/orgmetra_people_api/mutation_http.py b/services/people-api/src/orgmetra_people_api/mutation_http.py index 5158e9871..bbc1484cf 100644 --- a/services/people-api/src/orgmetra_people_api/mutation_http.py +++ b/services/people-api/src/orgmetra_people_api/mutation_http.py @@ -49,6 +49,7 @@ _MAX_UUID_INT = (1 << 128) - 1 _EMPLOYMENT_BODY_KEYS = frozenset( { + "employing_organization_unit_id", "person_record_id", "employment_status_code", "employment_concurrency_code", @@ -544,9 +545,13 @@ def _command_for_route( raise _InvalidHttpRequest("employment command fields are incomplete or unsupported") return EmploymentMutationCommand( tenant_record_id=tenant_record_id, + employing_organization_unit_id=UUID( + _require_string_field(payload, "employing_organization_unit_id") + ), person_record_id=UUID(_require_string_field(payload, "person_record_id")), employment_record_id=id_factory(), employment_record_version_id=id_factory(), + employment_employing_organization_record_id=id_factory(), audit_event_record_id=id_factory(), outbox_delivery_record_id=id_factory(), employment_status_code=_require_string_field(payload, "employment_status_code"), @@ -634,4 +639,4 @@ def _dispatch_mutation( mutation_port=app.mutation_port, ) created = str(result.assignment_record_id) - return {"assignment_record_id": created}, f"/v1/assignment-records/{created}" \ No newline at end of file + return {"assignment_record_id": created}, f"/v1/assignment-records/{created}" diff --git a/services/people-api/src/orgmetra_people_api/mutations.py b/services/people-api/src/orgmetra_people_api/mutations.py index 6baeac684..2c17bc838 100644 --- a/services/people-api/src/orgmetra_people_api/mutations.py +++ b/services/people-api/src/orgmetra_people_api/mutations.py @@ -123,6 +123,7 @@ def mutation_command_digest( "confirmation_reference": command.confirmation_reference, "effective_from": command.effective_from.isoformat(), "employment_concurrency_code": command.employment_concurrency_code, + "employing_organization_unit_id": str(command.employing_organization_unit_id), "employment_status_code": command.employment_status_code, "evidence_version_code": command.evidence_version_code, "person_record_id": str(command.person_record_id), @@ -166,9 +167,11 @@ class EmploymentMutationCommand: """Opaque identities and high-impact evidence needed to create one employment.""" tenant_record_id: UUID + employing_organization_unit_id: UUID person_record_id: UUID employment_record_id: UUID employment_record_version_id: UUID + employment_employing_organization_record_id: UUID audit_event_record_id: UUID outbox_delivery_record_id: UUID employment_status_code: str @@ -182,9 +185,11 @@ def __post_init__(self) -> None: """Fail closed before authorization or persistence on malformed input.""" for field_name in ( "tenant_record_id", + "employing_organization_unit_id", "person_record_id", "employment_record_id", "employment_record_version_id", + "employment_employing_organization_record_id", "audit_event_record_id", "outbox_delivery_record_id", ): diff --git a/services/people-api/src/orgmetra_people_api/postgres_hire.py b/services/people-api/src/orgmetra_people_api/postgres_hire.py index 4c328e02f..18dbf7ca9 100644 --- a/services/people-api/src/orgmetra_people_api/postgres_hire.py +++ b/services/people-api/src/orgmetra_people_api/postgres_hire.py @@ -134,6 +134,17 @@ ) VALUES (%s, %s, %s, %s, %s, %s) """.strip() +_INSERT_EMPLOYMENT_EMPLOYING_ORGANIZATION_SQL = """ +INSERT INTO public.employment_employing_organization_record ( + tenant_record_id, + employment_employing_organization_record_id, + employment_record_id, + employing_organization_unit_id, + effective_from, + recorded_from +) VALUES (%s, %s, %s, %s, %s, %s) +""".strip() + _RECORD_AUDIT_OUTBOX_SQL = "SELECT public.record_audit_outbox_event(%s, %s, %s, %s, %s, %s)" _INSERT_CONVERSION_SQL = """ @@ -204,7 +215,11 @@ def _hire_command_digest(command: HireAcceptanceCommand, authorization: Authoriz "candidate_worker_conversion_record_id": str(command.candidate_worker_conversion_record_id), "display_name": command.display_name, "effective_from": command.effective_from.isoformat(), + "employing_organization_unit_id": str(command.employing_organization_unit_id), "employment_record_id": str(command.employment_record_id), + "employment_employing_organization_record_id": str( + command.employment_employing_organization_record_id + ), "employment_record_version_id": str(command.employment_record_version_id), "employment_status_code": command.employment_status_code, "outbox_delivery_record_id": str(command.outbox_delivery_record_id), @@ -422,6 +437,18 @@ def accept_hire( transaction_recorded_at, ), ) + if command.employment_status_code in {"active", "leave"}: + cursor.execute( + _INSERT_EMPLOYMENT_EMPLOYING_ORGANIZATION_SQL, + ( + command.tenant_record_id, + command.employment_employing_organization_record_id, + command.employment_record_id, + command.employing_organization_unit_id, + command.effective_from, + transaction_recorded_at, + ), + ) cursor.execute( _RECORD_AUDIT_OUTBOX_SQL, ( diff --git a/services/people-api/src/orgmetra_people_api/postgres_mutations.py b/services/people-api/src/orgmetra_people_api/postgres_mutations.py index d94832cf8..7cd22b4cd 100644 --- a/services/people-api/src/orgmetra_people_api/postgres_mutations.py +++ b/services/people-api/src/orgmetra_people_api/postgres_mutations.py @@ -105,6 +105,17 @@ ) VALUES (%s, %s, %s, %s, %s, %s, %s) """.strip() +_INSERT_EMPLOYMENT_EMPLOYING_ORGANIZATION_SQL = """ +INSERT INTO public.employment_employing_organization_record ( + tenant_record_id, + employment_employing_organization_record_id, + employment_record_id, + employing_organization_unit_id, + effective_from, + recorded_from +) VALUES (%s, %s, %s, %s, %s, %s) +""".strip() + _POSITION_PARENTS_SQL = """ SELECT organization.organization_unit_id, @@ -601,6 +612,18 @@ def create_employment( recorded_at, ), ) + if command.employment_status_code in {"active", "leave"}: + cursor.execute( + _INSERT_EMPLOYMENT_EMPLOYING_ORGANIZATION_SQL, + ( + command.tenant_record_id, + command.employment_employing_organization_record_id, + command.employment_record_id, + command.employing_organization_unit_id, + command.effective_from, + recorded_at, + ), + ) _record_audit( cursor, command_tenant=command.tenant_record_id, diff --git a/services/people-api/tests/test_decision_reason_binding.py b/services/people-api/tests/test_decision_reason_binding.py index dd1b11eed..c2a4f85fb 100644 --- a/services/people-api/tests/test_decision_reason_binding.py +++ b/services/people-api/tests/test_decision_reason_binding.py @@ -13,11 +13,13 @@ TENANT = UUID("0198a412-8a00-7000-8000-000000000001") PERSON = UUID("0198a412-8a00-7000-8000-000000000002") +ORGANIZATION = UUID("0198a412-8a00-7000-8000-000000000003") GENERATED_IDS = ( UUID("0198a412-8a00-7000-8000-000000000010"), UUID("0198a412-8a00-7000-8000-000000000011"), UUID("0198a412-8a00-7000-8000-000000000012"), UUID("0198a412-8a00-7000-8000-000000000013"), + UUID("0198a412-8a00-7000-8000-000000000014"), ) _GOVERNANCE_BINDING_PATTERN = re.compile(r"^governance_evidence_v1:[0-9a-f]{64}$") @@ -34,6 +36,7 @@ def next_id() -> UUID: def _payload(*, decision_reason: str, evidence_references: list[dict[str, str]] | None = None) -> dict[str, object]: """Return one high-impact employment command payload.""" return { + "employing_organization_unit_id": str(ORGANIZATION), "person_record_id": str(PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", diff --git a/services/people-api/tests/test_evidence_reference_binding_regression.py b/services/people-api/tests/test_evidence_reference_binding_regression.py index 5c0d061c3..5e77380cb 100644 --- a/services/people-api/tests/test_evidence_reference_binding_regression.py +++ b/services/people-api/tests/test_evidence_reference_binding_regression.py @@ -14,6 +14,8 @@ PERSON = UUID("0198a412-8200-7000-8000-000000000020") EMPLOYMENT = UUID("0198a412-8200-7000-8000-000000000030") EMPLOYMENT_VERSION = UUID("0198a412-8200-7000-8000-000000000031") +ORGANIZATION = UUID("0198a412-8200-7000-8000-000000000050") +EMPLOYMENT_EMPLOYER = UUID("0198a412-8200-7000-8000-000000000082") AUDIT_EVENT = UUID("0198a412-8200-7000-8000-000000000080") OUTBOX = UUID("0198a412-8200-7000-8000-000000000081") @@ -22,7 +24,7 @@ class SequentialIdFactory: """Return deterministic identifiers for one HTTP command construction.""" def __init__(self) -> None: - self._values = iter((EMPLOYMENT, EMPLOYMENT_VERSION, AUDIT_EVENT, OUTBOX)) + self._values = iter((EMPLOYMENT, EMPLOYMENT_VERSION, EMPLOYMENT_EMPLOYER, AUDIT_EVENT, OUTBOX)) def __call__(self) -> UUID: """Return the next deterministic operational UUID.""" @@ -32,6 +34,7 @@ def __call__(self) -> UUID: def employment_payload(evidence_references: list[object]) -> dict[str, object]: """Return one otherwise-valid high-impact employment request body.""" return { + "employing_organization_unit_id": str(ORGANIZATION), "person_record_id": str(PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", diff --git a/services/people-api/tests/test_hire_acceptance.py b/services/people-api/tests/test_hire_acceptance.py index af276ee90..22c03ec50 100644 --- a/services/people-api/tests/test_hire_acceptance.py +++ b/services/people-api/tests/test_hire_acceptance.py @@ -22,6 +22,8 @@ PERSON_NAME = UUID("0198a412-7000-7000-8000-000000000021") EMPLOYMENT = UUID("0198a412-7000-7000-8000-000000000030") EMPLOYMENT_VERSION = UUID("0198a412-7000-7000-8000-000000000031") +ORGANIZATION = UUID("0198a412-7000-7000-8000-000000000070") +EMPLOYMENT_EMPLOYER = UUID("0198a412-7000-7000-8000-000000000071") CONVERSION = UUID("0198a412-7000-7000-8000-000000000040") AUDIT_EVENT = UUID("0198a412-7000-7000-8000-000000000050") OUTBOX_DELIVERY = UUID("0198a412-7000-7000-8000-000000000051") @@ -33,12 +35,14 @@ def command(**overrides: object) -> HireAcceptanceCommand: """Build one deterministic accepted-hire command for tests.""" values: dict[str, object] = { "tenant_record_id": TENANT, + "employing_organization_unit_id": ORGANIZATION, "candidate_profile_id": CANDIDATE, "selection_decision_id": DECISION, "person_record_id": PERSON, "person_name_record_id": PERSON_NAME, "employment_record_id": EMPLOYMENT, "employment_record_version_id": EMPLOYMENT_VERSION, + "employment_employing_organization_record_id": EMPLOYMENT_EMPLOYER, "candidate_worker_conversion_record_id": CONVERSION, "audit_event_record_id": AUDIT_EVENT, "outbox_delivery_record_id": OUTBOX_DELIVERY, diff --git a/services/people-api/tests/test_hire_http_route.py b/services/people-api/tests/test_hire_http_route.py index 6e8272490..34f4b0059 100644 --- a/services/people-api/tests/test_hire_http_route.py +++ b/services/people-api/tests/test_hire_http_route.py @@ -23,6 +23,8 @@ PERSON_NAME = UUID("0198a412-7200-7000-8000-000000000021") EMPLOYMENT = UUID("0198a412-7200-7000-8000-000000000030") EMPLOYMENT_VERSION = UUID("0198a412-7200-7000-8000-000000000031") +ORGANIZATION = UUID("0198a412-7200-7000-8000-000000000070") +EMPLOYMENT_EMPLOYER = UUID("0198a412-7200-7000-8000-000000000071") CONVERSION = UUID("0198a412-7200-7000-8000-000000000040") AUDIT_EVENT = UUID("0198a412-7200-7000-8000-000000000050") OUTBOX_DELIVERY = UUID("0198a412-7200-7000-8000-000000000051") @@ -34,12 +36,14 @@ def request_body(**overrides: object) -> bytes: """Return one canonical UTF-8 JSON hire request body.""" payload: dict[str, object] = { + "employing_organization_unit_id": str(ORGANIZATION), "candidate_profile_id": str(CANDIDATE), "selection_decision_id": str(DECISION), "person_record_id": str(PERSON), "person_name_record_id": str(PERSON_NAME), "employment_record_id": str(EMPLOYMENT), "employment_record_version_id": str(EMPLOYMENT_VERSION), + "employment_employing_organization_record_id": str(EMPLOYMENT_EMPLOYER), "candidate_worker_conversion_record_id": str(CONVERSION), "audit_event_record_id": str(AUDIT_EVENT), "outbox_delivery_record_id": str(OUTBOX_DELIVERY), diff --git a/services/people-api/tests/test_hire_http_scalar_contract.py b/services/people-api/tests/test_hire_http_scalar_contract.py index 5ba2f987d..db744f3b5 100644 --- a/services/people-api/tests/test_hire_http_scalar_contract.py +++ b/services/people-api/tests/test_hire_http_scalar_contract.py @@ -20,6 +20,8 @@ def _valid_payload() -> dict[str, object]: "person_name_record_id": "0198a412-7200-7000-8000-000000000021", "employment_record_id": "0198a412-7200-7000-8000-000000000030", "employment_record_version_id": "0198a412-7200-7000-8000-000000000031", + "employing_organization_unit_id": "0198a412-7200-7000-8000-000000000070", + "employment_employing_organization_record_id": "0198a412-7200-7000-8000-000000000071", "candidate_worker_conversion_record_id": "0198a412-7200-7000-8000-000000000040", "audit_event_record_id": "0198a412-7200-7000-8000-000000000050", "outbox_delivery_record_id": "0198a412-7200-7000-8000-000000000051", diff --git a/services/people-api/tests/test_mutation_http_authentication_order.py b/services/people-api/tests/test_mutation_http_authentication_order.py index 8c1f2bb7c..3adecebe2 100644 --- a/services/people-api/tests/test_mutation_http_authentication_order.py +++ b/services/people-api/tests/test_mutation_http_authentication_order.py @@ -25,6 +25,7 @@ UUID("0198a412-8100-7000-8000-000000000031"), UUID("0198a412-8100-7000-8000-000000000032"), UUID("0198a412-8100-7000-8000-000000000033"), + UUID("0198a412-8100-7000-8000-000000000034"), ] @@ -203,6 +204,7 @@ async def test_unexpected_backend_error_logs_only_safe_correlation_fields(self) ) body = json.dumps( { + "employing_organization_unit_id": "0198a412-8100-7000-8000-000000000022", "person_record_id": str(PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", @@ -236,7 +238,7 @@ async def send(message: dict[str, object]) -> None: self.assertEqual(record.tenant_record_id, str(TENANT)) self.assertEqual( record.correlation_reference, - f"audit_event_record:{MUTATION_IDS[2].hex}", + f"audit_event_record:{MUTATION_IDS[3].hex}", ) self.assertEqual(record.exception_type, "RuntimeError") rendered = " ".join(captured.output) diff --git a/services/people-api/tests/test_mutation_http_route.py b/services/people-api/tests/test_mutation_http_route.py index 941580e40..1db10c201 100644 --- a/services/people-api/tests/test_mutation_http_route.py +++ b/services/people-api/tests/test_mutation_http_route.py @@ -32,6 +32,7 @@ IDS = [ EMPLOYMENT, UUID("0198a412-8100-7000-8000-000000000031"), + UUID("0198a412-8100-7000-8000-000000000086"), UUID("0198a412-8100-7000-8000-000000000080"), UUID("0198a412-8100-7000-8000-000000000081"), POSITION, @@ -103,6 +104,7 @@ def create_assignment(self, *, command: AssignmentMutationCommand, authorization def employment_body(**overrides: object) -> bytes: """Return one canonical employment command body.""" payload: dict[str, object] = { + "employing_organization_unit_id": str(ORGANIZATION), "person_record_id": str(PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", @@ -494,4 +496,4 @@ async def send(message: dict[str, object]) -> None: if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/services/people-api/tests/test_mutation_http_schema_types.py b/services/people-api/tests/test_mutation_http_schema_types.py index 7a42df43d..a8d37fa94 100644 --- a/services/people-api/tests/test_mutation_http_schema_types.py +++ b/services/people-api/tests/test_mutation_http_schema_types.py @@ -10,11 +10,13 @@ TENANT = UUID("0198a412-8100-7000-8000-000000000001") PERSON = UUID("0198a412-8100-7000-8000-000000000020") +ORGANIZATION = UUID("0198a412-8100-7000-8000-000000000022") _GENERATED_IDS = tuple( UUID(value) for value in ( "0198a412-8100-7000-8000-000000000030", "0198a412-8100-7000-8000-000000000031", + "0198a412-8100-7000-8000-000000000032", "0198a412-8100-7000-8000-000000000080", "0198a412-8100-7000-8000-000000000081", ) @@ -30,6 +32,7 @@ def id_factory() -> Callable[[], UUID]: def employment_payload(**overrides: object) -> dict[str, object]: """Return one canonical employment request payload with optional field overrides.""" payload: dict[str, object] = { + "employing_organization_unit_id": str(ORGANIZATION), "person_record_id": str(PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", diff --git a/services/people-api/tests/test_people_mutations.py b/services/people-api/tests/test_people_mutations.py index b629355e9..765379ec7 100644 --- a/services/people-api/tests/test_people_mutations.py +++ b/services/people-api/tests/test_people_mutations.py @@ -47,9 +47,11 @@ def employment_command(**overrides: object) -> EmploymentMutationCommand: """Build one deterministic employment command.""" values: dict[str, object] = { "tenant_record_id": TENANT, + "employing_organization_unit_id": ORGANIZATION, "person_record_id": PERSON, "employment_record_id": EMPLOYMENT, "employment_record_version_id": EMPLOYMENT_VERSION, + "employment_employing_organization_record_id": UUID("0198a412-8000-7000-8000-000000000051"), "audit_event_record_id": AUDIT_EVENT, "outbox_delivery_record_id": OUTBOX, "employment_status_code": "active", diff --git a/services/people-api/tests/test_postgres_hire_acceptance.py b/services/people-api/tests/test_postgres_hire_acceptance.py index ccd6009ff..cfd158d9b 100644 --- a/services/people-api/tests/test_postgres_hire_acceptance.py +++ b/services/people-api/tests/test_postgres_hire_acceptance.py @@ -29,6 +29,8 @@ PERSON_NAME = UUID("0198a412-7100-7000-8000-000000000021") EMPLOYMENT = UUID("0198a412-7100-7000-8000-000000000030") EMPLOYMENT_VERSION = UUID("0198a412-7100-7000-8000-000000000031") +ORGANIZATION = UUID("0198a412-7100-7000-8000-000000000070") +EMPLOYMENT_EMPLOYER = UUID("0198a412-7100-7000-8000-000000000071") CONVERSION = UUID("0198a412-7100-7000-8000-000000000040") AUDIT_EVENT = UUID("0198a412-7100-7000-8000-000000000050") OUTBOX_DELIVERY = UUID("0198a412-7100-7000-8000-000000000051") @@ -62,12 +64,14 @@ def command(**overrides: object) -> HireAcceptanceCommand: """Build one deterministic hire command.""" values: dict[str, object] = { "tenant_record_id": TENANT, + "employing_organization_unit_id": ORGANIZATION, "candidate_profile_id": CANDIDATE, "selection_decision_id": DECISION, "person_record_id": PERSON, "person_name_record_id": PERSON_NAME, "employment_record_id": EMPLOYMENT, "employment_record_version_id": EMPLOYMENT_VERSION, + "employment_employing_organization_record_id": EMPLOYMENT_EMPLOYER, "candidate_worker_conversion_record_id": CONVERSION, "audit_event_record_id": AUDIT_EVENT, "outbox_delivery_record_id": OUTBOX_DELIVERY, @@ -250,12 +254,20 @@ def test_materializes_confirmed_hire_and_pii_minimized_audit_atomically(self) -> "public.person_name_record", "public.employment_record", "public.employment_record_version", + "public.employment_employing_organization_record", ) - for execution, table_name in zip(cursor.executions[5:9], expected_tables, strict=True): + for execution, table_name in zip(cursor.executions[5:10], expected_tables, strict=True): self.assertIn(table_name, execution[0]) self.assertEqual(cursor.executions[6][1][3], "Ada Lovelace") - audit_sql, audit_parameters = cursor.executions[9] + relation_sql, relation_parameters = cursor.executions[9] + self.assertEqual( + relation_parameters, + (TENANT, EMPLOYMENT_EMPLOYER, EMPLOYMENT, ORGANIZATION, DECIDED_AT.date(), TRANSACTION_AT), + ) + self.assertIn("employing_organization_unit_id", relation_sql) + + audit_sql, audit_parameters = cursor.executions[10] self.assertEqual(audit_sql, "SELECT public.record_audit_outbox_event(%s, %s, %s, %s, %s, %s)") self.assertIsNotNone(audit_parameters) assert audit_parameters is not None @@ -277,13 +289,13 @@ def test_materializes_confirmed_hire_and_pii_minimized_audit_atomically(self) -> self.assertEqual(envelope["time"], "2026-08-18T00:01:00Z") self.assertNotIn("Ada Lovelace", envelope_text) - conversion_sql, conversion_parameters = cursor.executions[10] + conversion_sql, conversion_parameters = cursor.executions[11] self.assertIn("public.candidate_worker_conversion_record", conversion_sql) self.assertEqual( conversion_parameters, (TENANT, CONVERSION, CANDIDATE, PERSON, EMPLOYMENT, DECISION, AUDIT_EVENT, DECIDED_AT.date(), TRANSACTION_AT), ) - idempotency_sql, idempotency_parameters = cursor.executions[11] + idempotency_sql, idempotency_parameters = cursor.executions[12] self.assertIn("public.people_mutation_idempotency_record", idempotency_sql) self.assertIsNotNone(idempotency_parameters) assert idempotency_parameters is not None @@ -292,6 +304,23 @@ def test_materializes_confirmed_hire_and_pii_minimized_audit_atomically(self) -> self.assertRegex(str(idempotency_parameters[4]), r"^[0-9a-f]{64}$") self.assertEqual(idempotency_parameters[5], CONVERSION) + def test_terminated_hire_does_not_write_employing_organization(self) -> None: + port, connection, cursor = self._port([decision_row()]) + + result = accept_confirmed_hire( + principal=PRINCIPAL, + command=command(employment_status_code="terminated"), + purpose_code=PURPOSE, + policy=policy(), + mutation_port=port, + ) + + self.assertEqual(result.employment_record_id, EMPLOYMENT) + self.assertFalse( + any("public.employment_employing_organization_record" in sql for sql, _ in cursor.executions) + ) + self.assertIsNone(connection.exit_exception) + def test_exact_committed_hire_replay_returns_without_duplicate_business_writes(self) -> None: request = command() digest = _hire_command_digest(request, allowed_authorization()) diff --git a/services/people-api/tests/test_postgres_people_mutations.py b/services/people-api/tests/test_postgres_people_mutations.py index 0175fc6d4..98f6bcd1d 100644 --- a/services/people-api/tests/test_postgres_people_mutations.py +++ b/services/people-api/tests/test_postgres_people_mutations.py @@ -209,9 +209,10 @@ def _port( def test_employment_requires_conversion_and_records_audit_atomically(self) -> None: port, cursor = self._port([[(CONVERSION, RECORDED_AT)]], [[]]) + request = employment_command() result = create_employment_record( principal=PRINCIPAL, - command=employment_command(), + command=request, purpose_code="workforce_admin", policy=employment_policy(), mutation_port=port, @@ -225,6 +226,16 @@ def test_employment_requires_conversion_and_records_audit_atomically(self) -> No self.assertIn("conversion.recorded_to IS NULL", conversion_sql) self.assertIn("public.employment_record", sql_text) self.assertIn("employment_concurrency_code", sql_text) + relation_sql, relation_parameters = next( + execution + for execution in cursor.executions + if "public.employment_employing_organization_record" in execution[0] + ) + self.assertEqual( + relation_parameters, + (TENANT, request.employment_employing_organization_record_id, EMPLOYMENT, ORGANIZATION, request.effective_from, RECORDED_AT), + ) + self.assertIn("employing_organization_unit_id", relation_sql) self.assertIn("public.record_audit_outbox_event", sql_text) self.assertIn("public.people_mutation_idempotency_record", sql_text) self.assertNotIn("candidate_worker_link", sql_text) @@ -238,6 +249,23 @@ def test_employment_requires_conversion_and_records_audit_atomically(self) -> No self.assertEqual(envelope["orgmetraconfirmation"], "human_confirmation:review-88") self.assertEqual(audit_parameters[4], sha256(audit_parameters[3].encode("utf-8")).hexdigest()) + def test_terminated_employment_does_not_write_employing_organization(self) -> None: + port, cursor = self._port([[(CONVERSION, RECORDED_AT)]], [[]]) + request = employment_command(employment_status_code="terminated") + + result = create_employment_record( + principal=PRINCIPAL, + command=request, + purpose_code="workforce_admin", + policy=employment_policy(), + mutation_port=port, + ) + + self.assertEqual(result.employment_record_id, EMPLOYMENT) + self.assertFalse( + any("public.employment_employing_organization_record" in sql for sql, _ in cursor.executions) + ) + def test_position_requires_parents_and_records_audit(self) -> None: port, cursor = self._port([[(ORGANIZATION, JOB, RECORDED_AT)]]) result = create_position_record( diff --git a/services/people-api/tests/test_review_regressions.py b/services/people-api/tests/test_review_regressions.py index 82da96660..4108cc6c3 100644 --- a/services/people-api/tests/test_review_regressions.py +++ b/services/people-api/tests/test_review_regressions.py @@ -75,12 +75,14 @@ def test_confirmed_hire_command_carries_validated_idempotency_key(self) -> None: """Confirmed-hire retries need the same durable key boundary as other writes.""" command = HireAcceptanceCommand( tenant_record_id=UUID("0198a412-8000-7000-8000-000000000001"), + employing_organization_unit_id=UUID("0198a412-8000-7000-8000-000000000050"), candidate_profile_id=UUID("0198a412-8000-7000-8000-000000000002"), selection_decision_id=UUID("0198a412-8000-7000-8000-000000000003"), person_record_id=UUID("0198a412-8000-7000-8000-000000000004"), person_name_record_id=UUID("0198a412-8000-7000-8000-000000000005"), employment_record_id=UUID("0198a412-8000-7000-8000-000000000006"), employment_record_version_id=UUID("0198a412-8000-7000-8000-000000000007"), + employment_employing_organization_record_id=UUID("0198a412-8000-7000-8000-00000000000b"), candidate_worker_conversion_record_id=UUID("0198a412-8000-7000-8000-000000000008"), audit_event_record_id=UUID("0198a412-8000-7000-8000-000000000009"), outbox_delivery_record_id=UUID("0198a412-8000-7000-8000-00000000000a"), @@ -93,12 +95,14 @@ def test_confirmed_hire_command_carries_validated_idempotency_key(self) -> None: with self.assertRaises(ValueError): HireAcceptanceCommand( tenant_record_id=command.tenant_record_id, + employing_organization_unit_id=command.employing_organization_unit_id, candidate_profile_id=command.candidate_profile_id, selection_decision_id=command.selection_decision_id, person_record_id=command.person_record_id, person_name_record_id=command.person_name_record_id, employment_record_id=command.employment_record_id, employment_record_version_id=command.employment_record_version_id, + employment_employing_organization_record_id=command.employment_employing_organization_record_id, candidate_worker_conversion_record_id=command.candidate_worker_conversion_record_id, audit_event_record_id=command.audit_event_record_id, outbox_delivery_record_id=command.outbox_delivery_record_id, diff --git a/services/people-api/tests/test_support_reference_correlation.py b/services/people-api/tests/test_support_reference_correlation.py index c60f774be..5516f1b7b 100644 --- a/services/people-api/tests/test_support_reference_correlation.py +++ b/services/people-api/tests/test_support_reference_correlation.py @@ -22,6 +22,7 @@ CANDIDATE = UUID("0198a412-9900-7000-8000-000000000010") DECISION = UUID("0198a412-9900-7000-8000-000000000011") EMPLOYMENT = UUID("0198a412-9900-7000-8000-000000000030") +ORGANIZATION = UUID("0198a412-9900-7000-8000-000000000040") AUDIT_EVENT = UUID("0198a412-9900-7000-8000-000000000050") @@ -78,6 +79,7 @@ def __init__(self) -> None: ( EMPLOYMENT, UUID("0198a412-9900-7000-8000-000000000031"), + UUID("0198a412-9900-7000-8000-000000000032"), AUDIT_EVENT, UUID("0198a412-9900-7000-8000-000000000051"), ) @@ -188,12 +190,14 @@ async def test_confirmed_hire_500_support_reference_matches_error_log(self) -> N mutation_port=FailingHirePort(), ) payload = { + "employing_organization_unit_id": str(ORGANIZATION), "candidate_profile_id": str(CANDIDATE), "selection_decision_id": str(DECISION), "person_record_id": str(PERSON), "person_name_record_id": "0198a412-9900-7000-8000-000000000021", "employment_record_id": str(EMPLOYMENT), "employment_record_version_id": "0198a412-9900-7000-8000-000000000031", + "employment_employing_organization_record_id": "0198a412-9900-7000-8000-000000000032", "candidate_worker_conversion_record_id": "0198a412-9900-7000-8000-000000000040", "audit_event_record_id": str(AUDIT_EVENT), "outbox_delivery_record_id": "0198a412-9900-7000-8000-000000000051", @@ -232,6 +236,7 @@ async def test_people_mutation_500_support_reference_matches_error_log(self) -> id_factory=SequentialIdFactory(), ) payload = { + "employing_organization_unit_id": str(ORGANIZATION), "person_record_id": str(PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", diff --git a/services/people-api/tests/test_support_reference_response_privacy.py b/services/people-api/tests/test_support_reference_response_privacy.py index 0db1f7363..492495cc2 100644 --- a/services/people-api/tests/test_support_reference_response_privacy.py +++ b/services/people-api/tests/test_support_reference_response_privacy.py @@ -30,12 +30,14 @@ async def test_confirmed_hire_persistence_secret_is_absent_from_response(self) - mutation_port=_SUPPORT.FailingHirePort(), ) payload = { + "employing_organization_unit_id": str(_SUPPORT.ORGANIZATION), "candidate_profile_id": str(_SUPPORT.CANDIDATE), "selection_decision_id": str(_SUPPORT.DECISION), "person_record_id": str(_SUPPORT.PERSON), "person_name_record_id": "0198a412-9900-7000-8000-000000000021", "employment_record_id": str(_SUPPORT.EMPLOYMENT), "employment_record_version_id": "0198a412-9900-7000-8000-000000000031", + "employment_employing_organization_record_id": "0198a412-9900-7000-8000-000000000032", "candidate_worker_conversion_record_id": "0198a412-9900-7000-8000-000000000040", "audit_event_record_id": str(_SUPPORT.AUDIT_EVENT), "outbox_delivery_record_id": "0198a412-9900-7000-8000-000000000051", @@ -74,6 +76,7 @@ async def test_people_mutation_persistence_secret_is_absent_from_response(self) id_factory=_SUPPORT.SequentialIdFactory(), ) payload = { + "employing_organization_unit_id": str(_SUPPORT.ORGANIZATION), "person_record_id": str(_SUPPORT.PERSON), "employment_status_code": "active", "employment_concurrency_code": "exclusive", From 93b10659346e5b6628f0510837d0df80928867ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 16:46:27 +0900 Subject: [PATCH 23/50] fix(people): enforce organization target scope --- CHANGELOG.md | 1 + docs/API_CONTRACT.md | 6 ++++ docs/SECURITY.md | 4 +-- manifest.json | 2 +- .../authorization.py | 21 +++++++++++-- .../tests/test_authorization.py | 29 +++++++++++++++++ .../src/orgmetra_people_api/authorization.py | 12 +++++++ .../src/orgmetra_people_api/hire.py | 3 +- .../src/orgmetra_people_api/mutations.py | 3 +- .../people-api/tests/test_hire_acceptance.py | 31 +++++++++++++++++-- .../people-api/tests/test_hire_http_route.py | 5 ++- ...test_mutation_http_authentication_order.py | 4 ++- .../tests/test_mutation_http_route.py | 9 +++++- .../people-api/tests/test_people_mutations.py | 31 ++++++++++++++++++- .../tests/test_postgres_hire_acceptance.py | 5 ++- .../tests/test_postgres_people_mutations.py | 9 +++++- .../test_support_reference_correlation.py | 2 ++ ...loyment_employing_organization_postgres.sh | 6 ++++ 18 files changed, 167 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75522655f..3e75be9ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ All notable changes to Orgmetra will be documented in this file. ### Security +- Employment and confirmed-hire writes now require an authenticated exact employing-organization target scope in addition to the coarse People operation scope; missing same-tenant organization authorization fails before persistence. - Predictive-validity cases fail closed when selection evidence, Job scope, study criterion, converted worker, or system-recorded visibility does not match; the normalized case relation is tenant-qualified, append-only, TRUNCATE-protected, and forced through row-level security. - Purpose-bound PII authorization now fails closed across active tenant, authenticated actor tenant, resource tenant, resource kind, purpose, operation, operation-specific Keyverse scope, and requested-field subset; malformed/wildcard-like attributes, mutable field/scope collections, reserved UUID sentinels, and cross-tenant confused-deputy contexts are rejected before protected values are returned. Authorization requests and allow/deny evidence now also require and preserve one namespaced opaque target-resource reference, so immutable audit correlation identifies the exact HR record without copying its protected values. Authorization evidence otherwise contains governance metadata and field names only, with stable denial reasons and actionable next steps rather than PII. - LLM output constrained to draft evidence. diff --git a/docs/API_CONTRACT.md b/docs/API_CONTRACT.md index 7303eca6a..1dcd127f7 100644 --- a/docs/API_CONTRACT.md +++ b/docs/API_CONTRACT.md @@ -19,6 +19,12 @@ The baseline scope contract is: Scopes are coarse API capabilities. A caller-supplied business purpose remains a finer authorization input and cannot enlarge a token's scope or authorize itself. +Employment creation and confirmed-hire materialization additionally require the +exact employing-organization target scope +`orgmetra.people.write.organization_unit_` in the +authenticated token. The target scope is checked alongside the operation scope; +an organization identifier in the request body is never permission by itself. + ## Command requirements Every mutating request requires: diff --git a/docs/SECURITY.md b/docs/SECURITY.md index fd6dd3ea6..522f0308b 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -33,9 +33,9 @@ Orgmetra evaluates PII access before protected field values leave the authoritative HR boundary. Keyverse supplies authenticated identity and scope attributes through its published contract; Orgmetra owns the HR authorization policy and decision. -Every `PurposeBoundAccessRequest` must bind the active request tenant, authenticated actor tenant, target resource tenant, opaque actor reference, opaque target-resource reference, resource kind, purpose, operation, requested field names, and authenticated scope set. The target reference must identify the exact Orgmetra record with an opaque namespaced value suitable for audit correlation; it must not encode protected HR field values. The matching `PurposeBoundAccessPolicy` binds one tenant and immutable policy version to exactly one resource kind, purpose, operation, required Orgmetra scope, and permitted field set. There is no wildcard policy form. +Every `PurposeBoundAccessRequest` must bind the active request tenant, authenticated actor tenant, target resource tenant, opaque actor reference, opaque target-resource reference, resource kind, purpose, operation, requested field names, and authenticated scope set. A mutation may also carry an exact target scope, such as the employing organization scope, which must be present in that authenticated scope set. The target reference must identify the exact Orgmetra record with an opaque namespaced value suitable for audit correlation; it must not encode protected HR field values. The matching `PurposeBoundAccessPolicy` binds one tenant and immutable policy version to exactly one resource kind, purpose, operation, required Orgmetra scope, and permitted field set. There is no wildcard policy form. -Evaluation fails closed unless request, actor, resource, and policy tenants all agree; resource, purpose, and operation exactly match; the required operation-specific scope is present; and requested fields are a non-empty subset of permitted fields. UUID sentinels, malformed opaque actor or target references, wildcard-like codes, malformed scopes, empty sets, and mutable field/scope collections are rejected before evaluation. A valid purpose header cannot compensate for a missing scope or foreign resource tenant. +Evaluation fails closed unless request, actor, resource, and policy tenants all agree; resource, purpose, and operation exactly match; the required operation-specific scope and any exact target scope are present; and requested fields are a non-empty subset of permitted fields. UUID sentinels, malformed opaque actor or target references, wildcard-like codes, malformed scopes, empty sets, and mutable field/scope collections are rejected before evaluation. A valid purpose header cannot compensate for a missing scope or foreign resource tenant. 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. diff --git a/manifest.json b/manifest.json index c761fdfab..4a946f766 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":"f423ccc844ff7f520c8c487464a83dc1433217b1acbe7db1f303379cb1d96085","bytes":17977,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"20cb5a8fc24f586f0c220438effa6cff256a758f52b9b0fcd51736b8b1155a72","bytes":4983,"lines":78},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"495ddad49a409d43ed1bfd05dec0726316623c495ea000b23a989ab9adbba51f","bytes":11806,"lines":106},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} +{"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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"7dced5ba3c2424ec2ed53e879135edb71e9460a0879b83b8522313d487b5f9e3","bytes":12274,"lines":112},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index b1a5f92c2..6ce3864e7 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -142,9 +142,11 @@ class PurposeBoundAccessRequest: ``tenant_record_id`` is the active Orgmetra request context, and ``resource_tenant_record_id`` comes from the target record identity. The opaque ``resource_reference`` identifies that exact target for audit - correlation without copying its PII. All tenant identifiers must match the - policy tenant. Only field names are carried here; field values remain behind - the authoritative data boundary until access is allowed. + correlation without copying its PII. An optional target scope narrows the + operation to one exact organization or other governed target. All tenant + identifiers must match the policy tenant. Only field names are carried here; + field values remain behind the authoritative data boundary until access is + allowed. """ tenant_record_id: UUID @@ -157,6 +159,7 @@ class PurposeBoundAccessRequest: resource_kind: str requested_fields: frozenset[str] granted_scope_codes: frozenset[str] + required_target_scope_code: str | None = None def __post_init__(self) -> None: """Reject untrusted identity, target, tenant, purpose, field, or scope attributes.""" @@ -174,6 +177,8 @@ def __post_init__(self) -> None: _validate_code("operation_code", self.operation_code) _validate_field_set("requested_fields", self.requested_fields) _validate_scope_set(self.granted_scope_codes) + if self.required_target_scope_code is not None: + _validate_scope("required_target_scope_code", self.required_target_scope_code) @dataclass(frozen=True, slots=True) @@ -286,6 +291,16 @@ def evaluate_purpose_bound_access( allowed=False, reason_code="required_scope_missing", ) + if ( + request.required_target_scope_code is not None + and request.required_target_scope_code not in request.granted_scope_codes + ): + return _decision( + request=request, + policy=policy, + allowed=False, + reason_code="required_scope_missing", + ) if not request.requested_fields.issubset(policy.permitted_fields): return _decision( request=request, diff --git a/packages/keyverse-adapter/tests/test_authorization.py b/packages/keyverse-adapter/tests/test_authorization.py index 77f4ea701..2a00fe2fb 100644 --- a/packages/keyverse-adapter/tests/test_authorization.py +++ b/packages/keyverse-adapter/tests/test_authorization.py @@ -53,6 +53,35 @@ def test_exact_tenant_purpose_scope_and_field_subset_is_authorized() -> None: assert decision.next_action == "Continue with only the authorized fields." +def test_exact_target_scope_is_authorized() -> None: + """Allow a request only when its additional target scope is explicitly granted.""" + target_scope = "orgmetra.people.write.organization_unit_0198a412820070008000000000000050" + request = replace( + REQUEST, + granted_scope_codes=REQUEST.granted_scope_codes | {target_scope}, + required_target_scope_code=target_scope, + ) + + decision = require_purpose_bound_access(request=request, policy=POLICY) + + assert decision.allowed is True + + +def test_missing_exact_target_scope_is_denied() -> None: + """Deny an otherwise valid request when its exact target scope is absent.""" + request = replace( + REQUEST, + required_target_scope_code=( + "orgmetra.people.write.organization_unit_0198a412820070008000000000000050" + ), + ) + + decision = evaluate_purpose_bound_access(request=request, policy=POLICY) + + assert decision.allowed is False + assert decision.reason_code == "required_scope_missing" + + @pytest.mark.parametrize( ("access_request", "reason_code", "next_action"), [ diff --git a/services/people-api/src/orgmetra_people_api/authorization.py b/services/people-api/src/orgmetra_people_api/authorization.py index cb066a88c..3448e26f7 100644 --- a/services/people-api/src/orgmetra_people_api/authorization.py +++ b/services/people-api/src/orgmetra_people_api/authorization.py @@ -13,6 +13,15 @@ from orgmetra_people_api.auth import AuthenticatedPrincipal +_MAX_UUID_INT = (1 << 128) - 1 + + +def organization_unit_scope_code(organization_unit_id: UUID) -> str: + """Return the exact Keyverse scope for one employing organization unit.""" + if not isinstance(organization_unit_id, UUID) or organization_unit_id.int in (0, _MAX_UUID_INT): + raise ValueError("organization_unit_id must be an operational UUID") + return f"orgmetra.people.write.organization_unit_{organization_unit_id.hex}" + def authorize_resource_fields( *, @@ -25,12 +34,14 @@ def authorize_resource_fields( resource_kind: str, requested_fields: frozenset[str], policy: PurposeBoundAccessPolicy, + required_target_scope_code: str | None = None, ) -> AuthorizationDecision: """Authorize one exact HR resource without duplicating Keyverse policy logic. The People API contributes only request-edge identity/scope attributes and the resolved target. The integrated Orgmetra adapter remains the single owner of tenant/resource/purpose/operation/scope/field evaluation and denial evidence. + ``required_target_scope_code`` narrows a valid operation to one exact target. """ request = PurposeBoundAccessRequest( tenant_record_id=tenant_record_id, @@ -43,5 +54,6 @@ def authorize_resource_fields( resource_kind=resource_kind, requested_fields=requested_fields, granted_scope_codes=principal.granted_scope_codes, + required_target_scope_code=required_target_scope_code, ) return require_purpose_bound_access(request=request, policy=policy) diff --git a/services/people-api/src/orgmetra_people_api/hire.py b/services/people-api/src/orgmetra_people_api/hire.py index db2693910..ce3b22ee4 100644 --- a/services/people-api/src/orgmetra_people_api/hire.py +++ b/services/people-api/src/orgmetra_people_api/hire.py @@ -17,7 +17,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision, PurposeBoundAccessPolicy from orgmetra_people_api.auth import AuthenticatedPrincipal -from orgmetra_people_api.authorization import authorize_resource_fields +from orgmetra_people_api.authorization import authorize_resource_fields, organization_unit_scope_code from orgmetra_people_api.mutations import validate_idempotency_key _MAX_UUID_INT = (1 << 128) - 1 @@ -166,6 +166,7 @@ def accept_confirmed_hire( resource_kind="selection_decision", requested_fields=_HIRE_MUTATION_FIELDS, policy=policy, + required_target_scope_code=organization_unit_scope_code(command.employing_organization_unit_id), ) result = mutation_port.accept_hire(command=command, authorization=authorization) if not isinstance(result, HireAcceptanceResult): diff --git a/services/people-api/src/orgmetra_people_api/mutations.py b/services/people-api/src/orgmetra_people_api/mutations.py index 2c17bc838..687dc09e2 100644 --- a/services/people-api/src/orgmetra_people_api/mutations.py +++ b/services/people-api/src/orgmetra_people_api/mutations.py @@ -22,7 +22,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision, PurposeBoundAccessPolicy from orgmetra_people_api.auth import AuthenticatedPrincipal -from orgmetra_people_api.authorization import authorize_resource_fields +from orgmetra_people_api.authorization import authorize_resource_fields, organization_unit_scope_code _MAX_UUID_INT = (1 << 128) - 1 _IDEMPOTENCY_MIN = 16 @@ -381,6 +381,7 @@ def create_employment_record( resource_kind="employment_record", requested_fields=_EMPLOYMENT_FIELDS, policy=policy, + required_target_scope_code=organization_unit_scope_code(command.employing_organization_unit_id), ) result = port.create_employment(command=command, authorization=authorization) if not isinstance(result, EmploymentMutationResult): diff --git a/services/people-api/tests/test_hire_acceptance.py b/services/people-api/tests/test_hire_acceptance.py index 22c03ec50..d5802f874 100644 --- a/services/people-api/tests/test_hire_acceptance.py +++ b/services/people-api/tests/test_hire_acceptance.py @@ -8,6 +8,7 @@ from orgmetra_keyverse_adapter import AuthorizationDeniedError, PurposeBoundAccessPolicy from orgmetra_people_api.auth import AuthenticatedPrincipal +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.hire import ( HireAcceptanceCommand, HireAcceptancePort, @@ -23,6 +24,7 @@ EMPLOYMENT = UUID("0198a412-7000-7000-8000-000000000030") EMPLOYMENT_VERSION = UUID("0198a412-7000-7000-8000-000000000031") ORGANIZATION = UUID("0198a412-7000-7000-8000-000000000070") +OTHER_ORGANIZATION = UUID("0198a412-7000-7000-8000-000000000072") EMPLOYMENT_EMPLOYER = UUID("0198a412-7000-7000-8000-000000000071") CONVERSION = UUID("0198a412-7000-7000-8000-000000000040") AUDIT_EVENT = UUID("0198a412-7000-7000-8000-000000000050") @@ -71,7 +73,9 @@ def policy(*, purpose_code: str = "candidate_hire") -> PurposeBoundAccessPolicy: PRINCIPAL = AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", - granted_scope_codes=frozenset({"orgmetra.people.materialize_worker"}), + granted_scope_codes=frozenset( + {"orgmetra.people.materialize_worker", organization_unit_scope_code(ORGANIZATION)} + ), ) @@ -138,6 +142,27 @@ def test_policy_denial_prevents_mutation(self) -> None: self.assertEqual(port.calls, []) + def test_missing_organization_scope_prevents_hire_materialization(self) -> None: + """Reject an unauthorized same-tenant employing organization before persistence.""" + port = RecordingHirePort() + principal = AuthenticatedPrincipal( + tenant_record_id=TENANT, + actor_reference="keyverse_subject:operator-17", + granted_scope_codes=frozenset( + {"orgmetra.people.materialize_worker", organization_unit_scope_code(ORGANIZATION)} + ), + ) + with self.assertRaises(AuthorizationDeniedError) as caught: + accept_confirmed_hire( + principal=principal, + command=command(employing_organization_unit_id=OTHER_ORGANIZATION), + purpose_code="candidate_hire", + policy=policy(), + mutation_port=port, + ) + self.assertEqual(caught.exception.reason_code, "required_scope_missing") + self.assertEqual(port.calls, []) + def test_command_rejects_reserved_or_malformed_identifiers(self) -> None: invalid_values = ( {"tenant_record_id": UUID(int=0)}, @@ -182,7 +207,9 @@ def test_service_rejects_mismatched_tenant_before_port_call(self) -> None: other_tenant_principal = AuthenticatedPrincipal( tenant_record_id=UUID("0198a412-7000-7000-8000-000000000099"), actor_reference="keyverse_subject:operator-99", - granted_scope_codes=frozenset({"orgmetra.people.materialize_worker"}), + granted_scope_codes=frozenset( + {"orgmetra.people.materialize_worker", organization_unit_scope_code(ORGANIZATION)} + ), ) with self.assertRaises(AuthorizationDeniedError): diff --git a/services/people-api/tests/test_hire_http_route.py b/services/people-api/tests/test_hire_http_route.py index 34f4b0059..1796cda91 100644 --- a/services/people-api/tests/test_hire_http_route.py +++ b/services/people-api/tests/test_hire_http_route.py @@ -8,6 +8,7 @@ from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_people_api import AuthenticatedPrincipal, AuthenticationFailed +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.hire import ( HireAcceptanceCommand, HireAcceptanceResult, @@ -104,7 +105,9 @@ def setUp(self) -> None: self.principal = AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", - granted_scope_codes=frozenset({"orgmetra.people.materialize_worker"}), + granted_scope_codes=frozenset( + {"orgmetra.people.materialize_worker", organization_unit_scope_code(ORGANIZATION)} + ), ) self.policy = PurposeBoundAccessPolicy( tenant_record_id=TENANT, diff --git a/services/people-api/tests/test_mutation_http_authentication_order.py b/services/people-api/tests/test_mutation_http_authentication_order.py index 3adecebe2..c185e70d4 100644 --- a/services/people-api/tests/test_mutation_http_authentication_order.py +++ b/services/people-api/tests/test_mutation_http_authentication_order.py @@ -8,6 +8,7 @@ from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_people_api import AuthenticatedPrincipal, AuthenticationFailed +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.mutation_http import PeopleMutationAsgiApp from orgmetra_people_api.mutations import ( AssignmentMutationCommand, @@ -20,6 +21,7 @@ TENANT = UUID("0198a412-8100-7000-8000-000000000001") PERSON = UUID("0198a412-8100-7000-8000-000000000020") +ORGANIZATION = UUID("0198a412-8100-7000-8000-000000000022") MUTATION_IDS = [ UUID("0198a412-8100-7000-8000-000000000030"), UUID("0198a412-8100-7000-8000-000000000031"), @@ -120,7 +122,7 @@ def _principal() -> AuthenticatedPrincipal: return AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", - granted_scope_codes=frozenset({"orgmetra.people.write"}), + granted_scope_codes=frozenset({"orgmetra.people.write", organization_unit_scope_code(ORGANIZATION)}), ) diff --git a/services/people-api/tests/test_mutation_http_route.py b/services/people-api/tests/test_mutation_http_route.py index 1db10c201..df8dc39ec 100644 --- a/services/people-api/tests/test_mutation_http_route.py +++ b/services/people-api/tests/test_mutation_http_route.py @@ -10,6 +10,7 @@ from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_people_api import AuthenticatedPrincipal, AuthenticationFailed +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.mutation_http import PeopleMutationAsgiApp from orgmetra_people_api.mutations import ( AssignmentMutationCommand, @@ -159,7 +160,13 @@ def setUp(self) -> None: self.principal = AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", - granted_scope_codes=frozenset({"orgmetra.people.write", "orgmetra.job_architecture.write"}), + granted_scope_codes=frozenset( + { + "orgmetra.people.write", + "orgmetra.job_architecture.write", + organization_unit_scope_code(ORGANIZATION), + } + ), ) self.employment_policy = PurposeBoundAccessPolicy( tenant_record_id=TENANT, diff --git a/services/people-api/tests/test_people_mutations.py b/services/people-api/tests/test_people_mutations.py index 765379ec7..45fc531e4 100644 --- a/services/people-api/tests/test_people_mutations.py +++ b/services/people-api/tests/test_people_mutations.py @@ -9,6 +9,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision, AuthorizationDeniedError, PurposeBoundAccessPolicy from orgmetra_people_api.auth import AuthenticatedPrincipal +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.mutations import ( AssignmentMutationCommand, AssignmentMutationResult, @@ -33,6 +34,7 @@ POSITION = UUID("0198a412-8000-7000-8000-000000000040") POSITION_VERSION = UUID("0198a412-8000-7000-8000-000000000041") ORGANIZATION = UUID("0198a412-8000-7000-8000-000000000050") +OTHER_ORGANIZATION = UUID("0198a412-8000-7000-8000-000000000051") JOB = UUID("0198a412-8000-7000-8000-000000000060") ASSIGNMENT = UUID("0198a412-8000-7000-8000-000000000070") AUDIT_EVENT = UUID("0198a412-8000-7000-8000-000000000080") @@ -147,7 +149,13 @@ def assignment_policy() -> PurposeBoundAccessPolicy: PRINCIPAL = AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference="keyverse_subject:operator-17", - granted_scope_codes=frozenset({"orgmetra.people.write", "orgmetra.job_architecture.write"}), + granted_scope_codes=frozenset( + { + "orgmetra.people.write", + "orgmetra.job_architecture.write", + organization_unit_scope_code(ORGANIZATION), + } + ), ) @@ -234,6 +242,27 @@ def test_policy_denial_prevents_mutation(self) -> None: ) self.assertEqual(port.employment_calls, []) + def test_missing_organization_scope_prevents_employment_mutation(self) -> None: + """Reject an unauthorized same-tenant employing organization before persistence.""" + port = RecordingMutationPort() + with self.assertRaises(AuthorizationDeniedError) as caught: + create_employment_record( + principal=PRINCIPAL, + command=employment_command(employing_organization_unit_id=OTHER_ORGANIZATION), + purpose_code="workforce_admin", + policy=employment_policy(), + mutation_port=port, + ) + self.assertEqual(caught.exception.reason_code, "required_scope_missing") + self.assertEqual(port.employment_calls, []) + + def test_organization_scope_code_rejects_reserved_or_malformed_ids(self) -> None: + """Reject invalid organization identities before constructing a target scope.""" + with self.assertRaises(ValueError): + organization_unit_scope_code(UUID(int=0)) + with self.assertRaises(ValueError): + organization_unit_scope_code("not-a-uuid") # type: ignore[arg-type] + def test_commands_reject_reserved_or_malformed_values(self) -> None: cases = ( lambda: employment_command(tenant_record_id=UUID(int=0)), diff --git a/services/people-api/tests/test_postgres_hire_acceptance.py b/services/people-api/tests/test_postgres_hire_acceptance.py index cfd158d9b..8ac9794a8 100644 --- a/services/people-api/tests/test_postgres_hire_acceptance.py +++ b/services/people-api/tests/test_postgres_hire_acceptance.py @@ -10,6 +10,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision, PurposeBoundAccessPolicy from orgmetra_people_api.auth import AuthenticatedPrincipal +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.hire import ( HireAcceptanceCommand, HireAcceptanceResult, @@ -136,7 +137,9 @@ def allowed_authorization() -> AuthorizationDecision: PRINCIPAL = AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference=ACTOR, - granted_scope_codes=frozenset({"orgmetra.people.materialize_worker"}), + granted_scope_codes=frozenset( + {"orgmetra.people.materialize_worker", organization_unit_scope_code(ORGANIZATION)} + ), ) diff --git a/services/people-api/tests/test_postgres_people_mutations.py b/services/people-api/tests/test_postgres_people_mutations.py index 98f6bcd1d..7133bcbc1 100644 --- a/services/people-api/tests/test_postgres_people_mutations.py +++ b/services/people-api/tests/test_postgres_people_mutations.py @@ -11,6 +11,7 @@ from orgmetra_keyverse_adapter import AuthorizationDecision from orgmetra_people_api.auth import AuthenticatedPrincipal +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.mutations import ( PeopleMutationIntegrityError, PeopleMutationNotFound, @@ -184,7 +185,13 @@ def covering_position_row() -> tuple[object, ...]: PRINCIPAL = AuthenticatedPrincipal( tenant_record_id=TENANT, actor_reference=ACTOR, - granted_scope_codes=frozenset({"orgmetra.people.write", "orgmetra.job_architecture.write"}), + granted_scope_codes=frozenset( + { + "orgmetra.people.write", + "orgmetra.job_architecture.write", + organization_unit_scope_code(ORGANIZATION), + } + ), ) diff --git a/services/people-api/tests/test_support_reference_correlation.py b/services/people-api/tests/test_support_reference_correlation.py index 5516f1b7b..0b33126c0 100644 --- a/services/people-api/tests/test_support_reference_correlation.py +++ b/services/people-api/tests/test_support_reference_correlation.py @@ -8,6 +8,7 @@ from orgmetra_keyverse_adapter import PurposeBoundAccessPolicy from orgmetra_people_api import AuthenticatedPrincipal +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.hire import HireAcceptanceCommand from orgmetra_people_api.hire_http import HireAcceptanceAsgiApp from orgmetra_people_api.mutation_http import PeopleMutationAsgiApp @@ -132,6 +133,7 @@ def setUp(self) -> None: actor_reference="keyverse_subject:operator-99", granted_scope_codes=frozenset( {"orgmetra.people.materialize_worker", "orgmetra.people.write", "orgmetra.job_architecture.write"} + | {organization_unit_scope_code(ORGANIZATION)} ), ) diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 9461ce381..dbb3945db 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -93,12 +93,18 @@ DO $$ BEGIN IF (SELECT count(*) FROM employment_employing_organization_record WHERE employment_record_id='10000000-0000-7000-8000-000000000020'::uuid AND tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN RAISE EXCEPTION 'tenant alpha should see exactly one employer fact at the selected system-time coordinate'; END IF; + IF (SELECT count(*) FROM employment_employing_organization_record WHERE employment_record_id='20000000-0000-7000-8000-000000000020'::uuid) <> 0 THEN + RAISE EXCEPTION 'tenant alpha must not see tenant beta employer facts'; + END IF; END $$; SELECT set_config('orgmetra.tenant_record_id','20000000-0000-7000-8000-000000000001',false); DO $$ BEGIN IF (SELECT count(*) FROM employment_employing_organization_record WHERE employment_record_id='20000000-0000-7000-8000-000000000020'::uuid AND tstzrange(recorded_from, recorded_to, '[)') @> TIMESTAMPTZ '2026-09-01 00:00:00+00') <> 1 THEN RAISE EXCEPTION 'tenant beta should see exactly one employer fact at the selected system-time coordinate'; END IF; + IF (SELECT count(*) FROM employment_employing_organization_record WHERE employment_record_id='10000000-0000-7000-8000-000000000020'::uuid) <> 0 THEN + RAISE EXCEPTION 'tenant beta must not see tenant alpha employer facts'; + END IF; END $$; RESET ROLE; SQL From 4172010016a3872a43a8880ae0b485bce80da717 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 04:37:25 -0700 Subject: [PATCH 24/50] test(hris): require employer organization lookup index --- ...ment_employing_organization_index_postgres.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/test_employment_employing_organization_index_postgres.sh diff --git a/tests/test_employment_employing_organization_index_postgres.sh b/tests/test_employment_employing_organization_index_postgres.sh new file mode 100644 index 000000000..60d4cea0f --- /dev/null +++ b/tests/test_employment_employing_organization_index_postgres.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +index_definition="$(psql "${DATABASE_URL}" -Atqc "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'employment_employing_organization_record' AND indexname = 'employment_employing_organization_unit_lookup_index';")" + +[[ -n "${index_definition}" ]] || { + echo "missing employment_employing_organization_unit_lookup_index" >&2 + exit 1 +} + +[[ "${index_definition}" == *"(tenant_record_id, employing_organization_unit_id)"* ]] || { + echo "employment_employing_organization_unit_lookup_index does not cover tenant_record_id and employing_organization_unit_id: ${index_definition}" >&2 + exit 1 +} From ac382dd0004ff745c3e4c82cddcf35aee6d12985 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 04:37:37 -0700 Subject: [PATCH 25/50] test(hris): exercise employer organization lookup index --- .../workflows/employment-employing-organization-quality.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml index 355b85313..513b9db2e 100644 --- a/.github/workflows/employment-employing-organization-quality.yml +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -9,6 +9,7 @@ on: - "database/migrations/0002_sealed_evidence_digest.sql" - "database/migrations/0040_employment_employing_organization.sql" - "tests/test_employment_employing_organization_postgres.sh" + - "tests/test_employment_employing_organization_index_postgres.sh" - "scripts/foundation-contract-core.mjs" - "tests/validate_repository.py" - ".github/workflows/employment-employing-organization-quality.yml" @@ -63,6 +64,8 @@ jobs: run: python tests/validate_repository.py --print-manifest - name: Prove Employment employing-organization contract run: bash tests/test_employment_employing_organization_postgres.sh + - name: Prove employing-organization parent lookup index + run: bash tests/test_employment_employing_organization_index_postgres.sh - name: Require clean checkout run: | git diff --exit-code From 092b4f3651633419e4a989af6672d8144e08b894 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 04:40:37 -0700 Subject: [PATCH 26/50] fix(hris): index employing organization lookups --- .../migrations/0040_employment_employing_organization.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/database/migrations/0040_employment_employing_organization.sql b/database/migrations/0040_employment_employing_organization.sql index 93f5603a4..1995a8095 100644 --- a/database/migrations/0040_employment_employing_organization.sql +++ b/database/migrations/0040_employment_employing_organization.sql @@ -44,6 +44,12 @@ CREATE TABLE employment_employing_organization_record ( ) ); +CREATE INDEX employment_employing_organization_unit_lookup_index +ON employment_employing_organization_record ( + tenant_record_id, + employing_organization_unit_id +); + CREATE FUNCTION validate_employment_employing_organization_scope() RETURNS trigger LANGUAGE plpgsql From 8c75bbcbcc85754df56fa74e4736bd609679bf55 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 04:47:31 -0700 Subject: [PATCH 27/50] test(hris): pin employer organization foundation contract --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ceb2fb8fc..bf7a467d0 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "description": "Orgmetra evidence-centered HRIS foundation baseline.", "scripts": { - "validate": "python3 tests/validate_repository.py && node scripts/foundation-contract.mjs && node --test tests/foundation-contract.test.mjs tests/openapi-contract.test.mjs tests/dispatcher-inventory.test.mjs" + "validate:employment-employer-contract": "python3 -c \"from pathlib import Path; sql=Path('database/migrations/0040_employment_employing_organization.sql').read_text(encoding='utf-8'); required=('CREATE TABLE employment_employing_organization_record','employment_employing_organization_employment_tenant_fk','employment_employing_organization_unit_tenant_fk','employment_employing_organization_bitemporal_exclusion','employment_employing_organization_exact_one_relationship_guard','employment_employing_organization_exact_one_organization_guard','employment_employing_organization_bitemporal_guard','employment_employing_organization_truncate_guard','ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY','employment_employing_organization_scope_policy','employment_employing_organization_unit_lookup_index'); missing=[item for item in required if item not in sql]; assert not missing, f'Missing Employment employing-organization contract fragments: {missing}'\"", + "validate": "npm run validate:employment-employer-contract && python3 tests/validate_repository.py && node scripts/foundation-contract.mjs && node --test tests/foundation-contract.test.mjs tests/openapi-contract.test.mjs tests/dispatcher-inventory.test.mjs" } } From ba43e7604117bd21ee750299a08e3fadcab13f2f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:02:38 -0700 Subject: [PATCH 28/50] test(hris): reject truncated postgres constraint names --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf7a467d0..4b1a7f9ac 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "description": "Orgmetra evidence-centered HRIS foundation baseline.", "scripts": { - "validate:employment-employer-contract": "python3 -c \"from pathlib import Path; sql=Path('database/migrations/0040_employment_employing_organization.sql').read_text(encoding='utf-8'); required=('CREATE TABLE employment_employing_organization_record','employment_employing_organization_employment_tenant_fk','employment_employing_organization_unit_tenant_fk','employment_employing_organization_bitemporal_exclusion','employment_employing_organization_exact_one_relationship_guard','employment_employing_organization_exact_one_organization_guard','employment_employing_organization_bitemporal_guard','employment_employing_organization_truncate_guard','ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY','employment_employing_organization_scope_policy','employment_employing_organization_unit_lookup_index'); missing=[item for item in required if item not in sql]; assert not missing, f'Missing Employment employing-organization contract fragments: {missing}'\"", + "validate:employment-employer-contract": "python3 -c \"from pathlib import Path; import re; sql=Path('database/migrations/0040_employment_employing_organization.sql').read_text(encoding='utf-8'); required=('CREATE TABLE employment_employing_organization_record','employment_employing_organization_employment_tenant_fk','employment_employing_organization_unit_tenant_fk','employment_employing_organization_bitemporal_exclusion','employment_employing_organization_exact_one_relationship_guard','employment_employing_organization_exact_one_organization_guard','employment_employing_organization_bitemporal_guard','employment_employing_organization_truncate_guard','ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY','employment_employing_organization_scope_policy','employment_employing_organization_unit_lookup_index'); missing=[item for item in required if item not in sql]; assert not missing, f'Missing Employment employing-organization contract fragments: {missing}'; identifiers=re.findall(r'\\\\bCONSTRAINT\\\\s+([a-z_][a-z0-9_]*)', sql, flags=re.IGNORECASE); too_long=[name for name in identifiers if len(name.encode('utf-8')) > 63]; assert not too_long, f'PostgreSQL constraint identifiers exceed 63 bytes and would be truncated: {too_long}'\"", "validate": "npm run validate:employment-employer-contract && python3 tests/validate_repository.py && node scripts/foundation-contract.mjs && node --test tests/foundation-contract.test.mjs tests/openapi-contract.test.mjs tests/dispatcher-inventory.test.mjs" } } From 9cc34682b9ab5c7d6def8e80905aa5e9b34fefab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:03:16 -0700 Subject: [PATCH 29/50] test(hris): make identifier-length regression executable --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b1a7f9ac..e15e862a8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "description": "Orgmetra evidence-centered HRIS foundation baseline.", "scripts": { - "validate:employment-employer-contract": "python3 -c \"from pathlib import Path; import re; sql=Path('database/migrations/0040_employment_employing_organization.sql').read_text(encoding='utf-8'); required=('CREATE TABLE employment_employing_organization_record','employment_employing_organization_employment_tenant_fk','employment_employing_organization_unit_tenant_fk','employment_employing_organization_bitemporal_exclusion','employment_employing_organization_exact_one_relationship_guard','employment_employing_organization_exact_one_organization_guard','employment_employing_organization_bitemporal_guard','employment_employing_organization_truncate_guard','ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY','employment_employing_organization_scope_policy','employment_employing_organization_unit_lookup_index'); missing=[item for item in required if item not in sql]; assert not missing, f'Missing Employment employing-organization contract fragments: {missing}'; identifiers=re.findall(r'\\\\bCONSTRAINT\\\\s+([a-z_][a-z0-9_]*)', sql, flags=re.IGNORECASE); too_long=[name for name in identifiers if len(name.encode('utf-8')) > 63]; assert not too_long, f'PostgreSQL constraint identifiers exceed 63 bytes and would be truncated: {too_long}'\"", + "validate:employment-employer-contract": "python3 -c \"from pathlib import Path; import re; sql=Path('database/migrations/0040_employment_employing_organization.sql').read_text(encoding='utf-8'); required=('CREATE TABLE employment_employing_organization_record','employment_employing_organization_employment_tenant_fk','employment_employing_organization_unit_tenant_fk','employment_employing_organization_bitemporal_exclusion','employment_employing_organization_exact_one_relationship_guard','employment_employing_organization_exact_one_organization_guard','employment_employing_organization_bitemporal_guard','employment_employing_organization_truncate_guard','ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY','employment_employing_organization_scope_policy','employment_employing_organization_unit_lookup_index'); missing=[item for item in required if item not in sql]; assert not missing, f'Missing Employment employing-organization contract fragments: {missing}'; identifiers=re.findall(r'\\bCONSTRAINT\\s+([a-z_][a-z0-9_]*)', sql, flags=re.IGNORECASE); too_long=[name for name in identifiers if len(name.encode('utf-8')) > 63]; assert not too_long, f'PostgreSQL constraint identifiers exceed 63 bytes and would be truncated: {too_long}'\"", "validate": "npm run validate:employment-employer-contract && python3 tests/validate_repository.py && node scripts/foundation-contract.mjs && node --test tests/foundation-contract.test.mjs tests/openapi-contract.test.mjs tests/dispatcher-inventory.test.mjs" } } From 138f054e28510fbc3146df381c2a762cfe724541 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:07:00 -0700 Subject: [PATCH 30/50] fix(hris): keep postgres constraint names canonical --- database/migrations/0040_employment_employing_organization.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/0040_employment_employing_organization.sql b/database/migrations/0040_employment_employing_organization.sql index 1995a8095..ad58c4f1b 100644 --- a/database/migrations/0040_employment_employing_organization.sql +++ b/database/migrations/0040_employment_employing_organization.sql @@ -29,7 +29,7 @@ CREATE TABLE employment_employing_organization_record ( CHECK (is_operational_uuid(tenant_record_id)), CONSTRAINT employment_employing_organization_record_operational_uuid_check CHECK (is_operational_uuid(employment_employing_organization_record_id)), - CONSTRAINT employment_employing_organization_employment_operational_uuid_check + CONSTRAINT employment_employing_organization_employment_uuid_check CHECK (is_operational_uuid(employment_record_id)), CONSTRAINT employment_employing_organization_unit_operational_uuid_check CHECK (is_operational_uuid(employing_organization_unit_id)), From a5fdab299266388f08216a71dab041da935885fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:14:28 -0700 Subject: [PATCH 31/50] chore(provenance): reseal employer organization contract --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 4a946f766..52176cd68 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"107abb399161cadfc412ae6d24cc38db4fad30307b6d307362429e79b02d2ded","bytes":15125,"lines":352},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"7dced5ba3c2424ec2ed53e879135edb71e9460a0879b83b8522313d487b5f9e3","bytes":12274,"lines":112},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} +{"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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"7dced5ba3c2424ec2ed53e879135edb71e9460a0879b83b8522313d487b5f9e3","bytes":12274,"lines":112},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} From d399e6945086dbeb688b7d5f77232a0350f680c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:19:05 -0700 Subject: [PATCH 32/50] test(hris): fold employer index proof into canonical contract --- .../test_employment_employing_organization_postgres.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index dbb3945db..3d79412b3 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -7,6 +7,16 @@ psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0001_foundation psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0002_sealed_evidence_digest.sql psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f database/migrations/0040_employment_employing_organization.sql +index_definition="$(psql "${DATABASE_URL}" -Atqc "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'employment_employing_organization_record' AND indexname = 'employment_employing_organization_unit_lookup_index';")" +[[ -n "${index_definition}" ]] || { + echo "missing employment_employing_organization_unit_lookup_index" >&2 + exit 1 +} +[[ "${index_definition}" == *"(tenant_record_id, employing_organization_unit_id)"* ]] || { + echo "employment_employing_organization_unit_lookup_index does not cover tenant_record_id and employing_organization_unit_id: ${index_definition}" >&2 + exit 1 +} + psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 <<'SQL' BEGIN; INSERT INTO tenant_record (tenant_record_id, tenant_reference) VALUES From f95225786f5c6d668dec25710fdb971266fd36db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:19:19 -0700 Subject: [PATCH 33/50] test(hris): retire duplicate standalone employer index probe --- ...ment_employing_organization_index_postgres.sh | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 tests/test_employment_employing_organization_index_postgres.sh diff --git a/tests/test_employment_employing_organization_index_postgres.sh b/tests/test_employment_employing_organization_index_postgres.sh deleted file mode 100644 index 60d4cea0f..000000000 --- a/tests/test_employment_employing_organization_index_postgres.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" - -index_definition="$(psql "${DATABASE_URL}" -Atqc "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'employment_employing_organization_record' AND indexname = 'employment_employing_organization_unit_lookup_index';")" - -[[ -n "${index_definition}" ]] || { - echo "missing employment_employing_organization_unit_lookup_index" >&2 - exit 1 -} - -[[ "${index_definition}" == *"(tenant_record_id, employing_organization_unit_id)"* ]] || { - echo "employment_employing_organization_unit_lookup_index does not cover tenant_record_id and employing_organization_unit_id: ${index_definition}" >&2 - exit 1 -} From acbe4b32934166296f40105150732f6d02346075 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:22:20 -0700 Subject: [PATCH 34/50] chore(provenance): reseal employer postgres contract --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 52176cd68..24f5e1349 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"7dced5ba3c2424ec2ed53e879135edb71e9460a0879b83b8522313d487b5f9e3","bytes":12274,"lines":112},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} +{"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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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":"0f8055b73c63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"20b2bef968fc8147b51b0170ffa89e0a4387faffb141e22062bd7674c74ce54d","bytes":12893,"lines":122},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} From f190a1abf69679886f6425b906809473b6826420 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:31:50 -0700 Subject: [PATCH 35/50] test(hris): restore employer lookup index regression --- ...ment_employing_organization_index_postgres.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/test_employment_employing_organization_index_postgres.sh diff --git a/tests/test_employment_employing_organization_index_postgres.sh b/tests/test_employment_employing_organization_index_postgres.sh new file mode 100644 index 000000000..60d4cea0f --- /dev/null +++ b/tests/test_employment_employing_organization_index_postgres.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" + +index_definition="$(psql "${DATABASE_URL}" -Atqc "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'employment_employing_organization_record' AND indexname = 'employment_employing_organization_unit_lookup_index';")" + +[[ -n "${index_definition}" ]] || { + echo "missing employment_employing_organization_unit_lookup_index" >&2 + exit 1 +} + +[[ "${index_definition}" == *"(tenant_record_id, employing_organization_unit_id)"* ]] || { + echo "employment_employing_organization_unit_lookup_index does not cover tenant_record_id and employing_organization_unit_id: ${index_definition}" >&2 + exit 1 +} From e46c4b56cc74cf4273c039c114970bc340de9dfb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:38:12 -0700 Subject: [PATCH 36/50] chore(provenance): repair ADR manifest digest --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 24f5e1349..1a5a58f34 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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":"0f8055b73c63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"20b2bef968fc8147b51b0170ffa89e0a4387faffb141e22062bd7674c74ce54d","bytes":12893,"lines":122},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} +{"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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"20b2bef968fc8147b51b0170ffa89e0a4387faffb141e22062bd7674c74ce54d","bytes":12893,"lines":122},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ No newline at end of file From b1f45237115598a637a657ed0d0fe7c8209f23b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:41:36 -0700 Subject: [PATCH 37/50] test(auth): require target-specific denial guidance --- .../test_target_scope_denial_guidance.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 packages/keyverse-adapter/tests/test_target_scope_denial_guidance.py diff --git a/packages/keyverse-adapter/tests/test_target_scope_denial_guidance.py b/packages/keyverse-adapter/tests/test_target_scope_denial_guidance.py new file mode 100644 index 000000000..c8eee092e --- /dev/null +++ b/packages/keyverse-adapter/tests/test_target_scope_denial_guidance.py @@ -0,0 +1,46 @@ +"""Regression coverage for target-specific authorization denial guidance.""" + +from uuid import UUID + +from orgmetra_keyverse_adapter.authorization import ( + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + evaluate_purpose_bound_access, +) + + +def test_missing_target_scope_tells_operator_to_obtain_target_scope() -> None: + """A target-scope denial must not mislabel the missing grant as operation scope.""" + tenant = UUID("10000000-0000-7000-8000-000000000501") + target_scope = "orgmetra.people.write.organization_unit_0198a412820070008000000000000050" + policy = PurposeBoundAccessPolicy( + tenant_record_id=tenant, + policy_version_code="people_write_v1", + resource_kind="employment_record", + purpose_code="hr_operations", + operation_code="create_employment", + required_scope_code="orgmetra.people.write", + permitted_fields=frozenset({"employing_organization_unit_id"}), + ) + request = PurposeBoundAccessRequest( + tenant_record_id=tenant, + actor_tenant_record_id=tenant, + resource_tenant_record_id=tenant, + actor_reference="keyverse_subject:sub_operator", + resource_reference="employment_record:emp_target", + purpose_code="hr_operations", + operation_code="create_employment", + resource_kind="employment_record", + requested_fields=frozenset({"employing_organization_unit_id"}), + granted_scope_codes=frozenset({"orgmetra.people.write"}), + required_target_scope_code=target_scope, + ) + + decision = evaluate_purpose_bound_access(request=request, policy=policy) + + assert decision.allowed is False + assert decision.reason_code == "required_scope_missing" + assert decision.next_action == ( + "Obtain the exact Keyverse scope for the governed target before retrying; " + "the operation scope alone cannot authorize that target." + ) From fb230145761dc0702e563da786439a48bdb37b27 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:45:53 -0700 Subject: [PATCH 38/50] test(hris): deduplicate employer index regression --- .../workflows/employment-employing-organization-quality.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/employment-employing-organization-quality.yml b/.github/workflows/employment-employing-organization-quality.yml index 513b9db2e..355b85313 100644 --- a/.github/workflows/employment-employing-organization-quality.yml +++ b/.github/workflows/employment-employing-organization-quality.yml @@ -9,7 +9,6 @@ on: - "database/migrations/0002_sealed_evidence_digest.sql" - "database/migrations/0040_employment_employing_organization.sql" - "tests/test_employment_employing_organization_postgres.sh" - - "tests/test_employment_employing_organization_index_postgres.sh" - "scripts/foundation-contract-core.mjs" - "tests/validate_repository.py" - ".github/workflows/employment-employing-organization-quality.yml" @@ -64,8 +63,6 @@ jobs: run: python tests/validate_repository.py --print-manifest - name: Prove Employment employing-organization contract run: bash tests/test_employment_employing_organization_postgres.sh - - name: Prove employing-organization parent lookup index - run: bash tests/test_employment_employing_organization_index_postgres.sh - name: Require clean checkout run: | git diff --exit-code From 5b8da31562aa60d1f3785434984d882cc5f4850d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:46:17 -0700 Subject: [PATCH 39/50] test(hris): remove duplicate employer index contract --- ...ment_employing_organization_index_postgres.sh | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 tests/test_employment_employing_organization_index_postgres.sh diff --git a/tests/test_employment_employing_organization_index_postgres.sh b/tests/test_employment_employing_organization_index_postgres.sh deleted file mode 100644 index 60d4cea0f..000000000 --- a/tests/test_employment_employing_organization_index_postgres.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -: "${DATABASE_URL:=postgresql://orgmetra:orgmetra@localhost:5432/orgmetra}" - -index_definition="$(psql "${DATABASE_URL}" -Atqc "SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' AND tablename = 'employment_employing_organization_record' AND indexname = 'employment_employing_organization_unit_lookup_index';")" - -[[ -n "${index_definition}" ]] || { - echo "missing employment_employing_organization_unit_lookup_index" >&2 - exit 1 -} - -[[ "${index_definition}" == *"(tenant_record_id, employing_organization_unit_id)"* ]] || { - echo "employment_employing_organization_unit_lookup_index does not cover tenant_record_id and employing_organization_unit_id: ${index_definition}" >&2 - exit 1 -} From 4d3ed041d5a9d93fcb6238a2be4fd3b0a7d1db29 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 06:48:16 -0700 Subject: [PATCH 40/50] fix(auth): explain missing governed target scope --- .../src/orgmetra_keyverse_adapter/authorization.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 6ce3864e7..52fb044fa 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -216,13 +216,18 @@ def _decision( policy: PurposeBoundAccessPolicy, allowed: bool, reason_code: str, + next_action_override: str | None = None, ) -> AuthorizationDecision: """Build one immutable allow/deny record without copying protected values.""" authorized_fields = request.requested_fields if allowed else frozenset() next_action = ( "Continue with only the authorized fields." if allowed - else _DENIAL_NEXT_ACTION[reason_code] + else ( + next_action_override + if next_action_override is not None + else _DENIAL_NEXT_ACTION[reason_code] + ) ) return AuthorizationDecision( allowed=allowed, @@ -300,6 +305,10 @@ def evaluate_purpose_bound_access( policy=policy, allowed=False, reason_code="required_scope_missing", + next_action_override=( + "Obtain the exact Keyverse scope for the governed target before retrying; " + "the operation scope alone cannot authorize that target." + ), ) if not request.requested_fields.issubset(policy.permitted_fields): return _decision( From 48de15641530cdaedbe384525e5b4aa3463cf965 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 07:32:34 -0700 Subject: [PATCH 41/50] test(authz): bind employing organization scope evidence --- ...ying_organization_authorization_binding.py | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 services/people-api/tests/test_employing_organization_authorization_binding.py diff --git a/services/people-api/tests/test_employing_organization_authorization_binding.py b/services/people-api/tests/test_employing_organization_authorization_binding.py new file mode 100644 index 000000000..805bde4ce --- /dev/null +++ b/services/people-api/tests/test_employing_organization_authorization_binding.py @@ -0,0 +1,147 @@ +"""Regression contracts for exact employing-organization authorization evidence binding.""" + +from types import SimpleNamespace +from uuid import UUID + +import pytest + +from orgmetra_keyverse_adapter import ( + AuthorizationDecision, + PurposeBoundAccessPolicy, + PurposeBoundAccessRequest, + require_purpose_bound_access, +) +from orgmetra_people_api.authorization import organization_unit_scope_code +from orgmetra_people_api.hire import HireDecisionIntegrityError +from orgmetra_people_api.mutations import PeopleMutationIntegrityError +from orgmetra_people_api.postgres_hire import _validate_authorization as validate_hire_authorization +from orgmetra_people_api.postgres_mutations import _require_authorization as require_mutation_authorization + +TENANT = UUID("0198a412-8200-7000-8000-000000000001") +EMPLOYMENT = UUID("0198a412-8200-7000-8000-000000000030") +SELECTION_DECISION = UUID("0198a412-8200-7000-8000-000000000081") +ORGANIZATION = UUID("0198a412-8200-7000-8000-000000000050") +OTHER_ORGANIZATION = UUID("0198a412-8200-7000-8000-000000000051") +TARGET_SCOPE = organization_unit_scope_code(ORGANIZATION) +OTHER_TARGET_SCOPE = organization_unit_scope_code(OTHER_ORGANIZATION) +ACTOR = "keyverse_subject:operator-17" + + +def _allowed_decision( + *, + resource_kind: str, + resource_reference: str, + operation_code: str, + requested_fields: frozenset[str], + target_scope: str, +) -> AuthorizationDecision: + """Return a real policy-evaluator decision bound to one exact target scope.""" + policy = PurposeBoundAccessPolicy( + tenant_record_id=TENANT, + policy_version_code="target-binding-v1", + resource_kind=resource_kind, + purpose_code="workforce_admin", + operation_code=operation_code, + required_scope_code="orgmetra.people.write", + permitted_fields=requested_fields, + ) + request = PurposeBoundAccessRequest( + tenant_record_id=TENANT, + actor_tenant_record_id=TENANT, + resource_tenant_record_id=TENANT, + actor_reference=ACTOR, + resource_reference=resource_reference, + purpose_code="workforce_admin", + operation_code=operation_code, + resource_kind=resource_kind, + requested_fields=requested_fields, + granted_scope_codes=frozenset({"orgmetra.people.write", target_scope}), + required_target_scope_code=target_scope, + ) + return require_purpose_bound_access(request=request, policy=policy) + + +def test_allow_decision_preserves_exact_target_scope_as_evidence() -> None: + """An allow decision must prove which governed organization target was checked.""" + decision = _allowed_decision( + resource_kind="employment_record", + resource_reference=f"employment_record:{EMPLOYMENT.hex}", + operation_code="create_record", + requested_fields=frozenset({"employment_record"}), + target_scope=TARGET_SCOPE, + ) + + assert decision.required_target_scope_code == TARGET_SCOPE + + +def test_employment_persistence_rejects_allow_for_different_organization_scope() -> None: + """The DB adapter must not trust an allow decision bound to another organization.""" + decision = _allowed_decision( + resource_kind="employment_record", + resource_reference=f"employment_record:{EMPLOYMENT.hex}", + operation_code="create_record", + requested_fields=frozenset({"employment_record"}), + target_scope=OTHER_TARGET_SCOPE, + ) + + with pytest.raises(PeopleMutationIntegrityError, match="authorization"): + require_mutation_authorization( + authorization=decision, + tenant_record_id=TENANT, + resource_reference=f"employment_record:{EMPLOYMENT.hex}", + resource_kind="employment_record", + requested_fields=frozenset({"employment_record"}), + required_target_scope_code=TARGET_SCOPE, + ) + + +def test_hire_persistence_rejects_allow_for_different_organization_scope() -> None: + """Confirmed-hire persistence independently binds the allow to its employer target.""" + decision = _allowed_decision( + resource_kind="selection_decision", + resource_reference=f"selection_decision:{SELECTION_DECISION.hex}", + operation_code="materialize_worker", + requested_fields=frozenset({"candidate_worker_conversion"}), + target_scope=OTHER_TARGET_SCOPE, + ) + command = SimpleNamespace( + tenant_record_id=TENANT, + selection_decision_id=SELECTION_DECISION, + employing_organization_unit_id=ORGANIZATION, + ) + + with pytest.raises(HireDecisionIntegrityError, match="authorization"): + validate_hire_authorization(command, decision) # type: ignore[arg-type] + + +def test_matching_target_scope_is_accepted_by_both_persistence_validators() -> None: + """The added defense-in-depth check must preserve correctly bound callers.""" + employment_decision = _allowed_decision( + resource_kind="employment_record", + resource_reference=f"employment_record:{EMPLOYMENT.hex}", + operation_code="create_record", + requested_fields=frozenset({"employment_record"}), + target_scope=TARGET_SCOPE, + ) + assert require_mutation_authorization( + authorization=employment_decision, + tenant_record_id=TENANT, + resource_reference=f"employment_record:{EMPLOYMENT.hex}", + resource_kind="employment_record", + requested_fields=frozenset({"employment_record"}), + required_target_scope_code=TARGET_SCOPE, + ) is employment_decision + + hire_decision = _allowed_decision( + resource_kind="selection_decision", + resource_reference=f"selection_decision:{SELECTION_DECISION.hex}", + operation_code="materialize_worker", + requested_fields=frozenset({"candidate_worker_conversion"}), + target_scope=TARGET_SCOPE, + ) + command = SimpleNamespace( + tenant_record_id=TENANT, + selection_decision_id=SELECTION_DECISION, + employing_organization_unit_id=ORGANIZATION, + ) + assert validate_hire_authorization(command, hire_decision) is hire_decision # type: ignore[arg-type] From ed4a7b2e0413e5dfa60ada2eb381cbbb261ded14 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 07:37:37 -0700 Subject: [PATCH 42/50] fix(authz): preserve exact governed target scope --- .../src/orgmetra_keyverse_adapter/authorization.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py index 52fb044fa..7e1121585 100644 --- a/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py +++ b/packages/keyverse-adapter/src/orgmetra_keyverse_adapter/authorization.py @@ -197,6 +197,12 @@ class AuthorizationDecision: authorized_fields: frozenset[str] reason_code: str next_action: str + required_target_scope_code: str | None = None + + def __post_init__(self) -> None: + """Reject malformed governed-target evidence on manually constructed decisions.""" + if self.required_target_scope_code is not None: + _validate_scope("required_target_scope_code", self.required_target_scope_code) class AuthorizationDeniedError(PermissionError): @@ -242,6 +248,7 @@ def _decision( authorized_fields=authorized_fields, reason_code=reason_code, next_action=next_action, + required_target_scope_code=request.required_target_scope_code, ) From 323d90591cc18d3e88b97f20c50bca5c4780dd1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 07:39:09 -0700 Subject: [PATCH 43/50] fix(authz): verify employer target at persistence boundary --- .../src/orgmetra_people_api/postgres_mutations.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/people-api/src/orgmetra_people_api/postgres_mutations.py b/services/people-api/src/orgmetra_people_api/postgres_mutations.py index 7cd22b4cd..dafa4b6f4 100644 --- a/services/people-api/src/orgmetra_people_api/postgres_mutations.py +++ b/services/people-api/src/orgmetra_people_api/postgres_mutations.py @@ -28,6 +28,7 @@ ) from orgmetra_keyverse_adapter import AuthorizationDecision +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.mutations import ( AssignmentMutationCommand, AssignmentMutationResult, @@ -331,8 +332,9 @@ def _require_authorization( resource_reference: str, resource_kind: str, requested_fields: frozenset[str], + required_target_scope_code: str | None = None, ) -> AuthorizationDecision: - """Require an exact allow decision for the intended mutation target.""" + """Require an exact allow decision for the intended mutation and governed target.""" if not isinstance(authorization, AuthorizationDecision): raise PeopleMutationIntegrityError("people mutation requires a typed authorization decision") if ( @@ -343,6 +345,7 @@ def _require_authorization( or authorization.operation_code != "create_record" or authorization.requested_fields != requested_fields or authorization.authorized_fields != requested_fields + or authorization.required_target_scope_code != required_target_scope_code ): raise PeopleMutationIntegrityError("people mutation authorization does not match the exact record") return authorization @@ -553,6 +556,7 @@ def create_employment( resource_reference=f"employment_record:{command.employment_record_id.hex}", resource_kind="employment_record", requested_fields=_EMPLOYMENT_FIELDS, + required_target_scope_code=organization_unit_scope_code(command.employing_organization_unit_id), ) with self.connection_factory() as connection: with connection.cursor() as cursor: From 459793a036a2c4e4a18cc68668b2248afc4cdf00 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 07:40:17 -0700 Subject: [PATCH 44/50] fix(authz): bind confirmed hire to employer scope --- services/people-api/src/orgmetra_people_api/postgres_hire.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/people-api/src/orgmetra_people_api/postgres_hire.py b/services/people-api/src/orgmetra_people_api/postgres_hire.py index 18dbf7ca9..a05276f1a 100644 --- a/services/people-api/src/orgmetra_people_api/postgres_hire.py +++ b/services/people-api/src/orgmetra_people_api/postgres_hire.py @@ -22,6 +22,7 @@ from orgmetra_hris_kernel.audit import AuditOutboxEvent from orgmetra_keyverse_adapter import AuthorizationDecision +from orgmetra_people_api.authorization import organization_unit_scope_code from orgmetra_people_api.hire import ( HireAcceptanceCommand, HireAcceptanceResult, @@ -185,8 +186,9 @@ def _is_aware_datetime(value: object) -> bool: def _validate_authorization(command: HireAcceptanceCommand, authorization: object) -> AuthorizationDecision: - """Require an exact allow decision for this immutable selection decision.""" + """Require an exact allow decision for this selection decision and employer target.""" expected_reference = f"selection_decision:{command.selection_decision_id.hex}" + expected_target_scope = organization_unit_scope_code(command.employing_organization_unit_id) if not isinstance(authorization, AuthorizationDecision): raise HireDecisionIntegrityError("hire mutation requires a typed authorization decision") if ( @@ -197,6 +199,7 @@ def _validate_authorization(command: HireAcceptanceCommand, authorization: objec or authorization.operation_code != "materialize_worker" or authorization.requested_fields != _HIRE_MUTATION_FIELDS or authorization.authorized_fields != _HIRE_MUTATION_FIELDS + or authorization.required_target_scope_code != expected_target_scope ): raise HireDecisionIntegrityError("hire mutation authorization does not match the exact decision") return authorization From 5936a12ab03650025e3ee6620d76db05128b308e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 03:01:03 +0900 Subject: [PATCH 45/50] test: pin employing organization migration contract --- manifest.json | 2 +- tests/validate_repository.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 1a5a58f34..8cbfb5b93 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"20b2bef968fc8147b51b0170ffa89e0a4387faffb141e22062bd7674c74ce54d","bytes":12893,"lines":122},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"d4c776165681f49974251b2ade0eda7d356a20ec1caef33a33e6de867618add0","bytes":27542,"lines":642}]} \ 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"20b2bef968fc8147b51b0170ffa89e0a4387faffb141e22062bd7674c74ce54d","bytes":12893,"lines":122},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"e70ed4b0c7ef8bd24e362ff8e9f04336c1c21766ecdf228d59bf2a32df876986","bytes":28573,"lines":655}]} \ No newline at end of file diff --git a/tests/validate_repository.py b/tests/validate_repository.py index 1ac1e7322..83c87fd77 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -368,6 +368,19 @@ def _validate_database_contract() -> None: "CREATE TRIGGER job_analysis_snapshot_append_only_guard", "ALTER TABLE job_analysis_snapshot FORCE ROW LEVEL SECURITY", "CREATE POLICY job_analysis_snapshot_scope_policy", + "CREATE TABLE employment_employing_organization_record", + "employment_employing_organization_employment_tenant_fk", + "employment_employing_organization_unit_tenant_fk", + "CONSTRAINT employment_employing_organization_bitemporal_exclusion", + "CREATE FUNCTION validate_employment_employing_organization_scope", + "CREATE CONSTRAINT TRIGGER employment_employing_organization_exact_one_employment_guard", + "CREATE CONSTRAINT TRIGGER employment_employing_organization_exact_one_relationship_guard", + "CREATE CONSTRAINT TRIGGER employment_employing_organization_exact_one_organization_guard", + "CREATE TRIGGER employment_employing_organization_bitemporal_guard", + "CREATE TRIGGER employment_employing_organization_truncate_guard", + "ALTER TABLE employment_employing_organization_record FORCE ROW LEVEL SECURITY", + "CREATE POLICY employment_employing_organization_scope_policy", + "CREATE INDEX employment_employing_organization_unit_lookup_index", ] for fragment in required_fragments: if fragment not in sql: From b27be87b36282ecff2604b990f8d1a253d2e5f23 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 11:36:39 -0700 Subject: [PATCH 46/50] test(hris): reproduce employer write-skew race --- ...loyment_employing_organization_postgres.sh | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/test_employment_employing_organization_postgres.sh b/tests/test_employment_employing_organization_postgres.sh index 3d79412b3..7510e3712 100644 --- a/tests/test_employment_employing_organization_postgres.sh +++ b/tests/test_employment_employing_organization_postgres.sh @@ -119,4 +119,86 @@ END $$; RESET ROLE; SQL +# The exact-one invariant spans Employment, employer relationships, and legal-entity +# classification. Conflicting tenant-local writes must serialize before deferred +# validation so two READ COMMITTED transactions cannot validate stale snapshots. +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' +INSERT INTO employment_record (tenant_record_id, employment_record_id, person_record_id) +VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000024','10000000-0000-7000-8000-000000000010'); +INSERT INTO organization_unit (tenant_record_id, organization_unit_id) +VALUES ('10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000033'); +INSERT INTO organization_unit_version ( + tenant_record_id, organization_unit_version_id, organization_unit_id, + unit_name, organization_type_code, effective_from, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000043', + '10000000-0000-7000-8000-000000000033','Race Legal Employer','legal_entity', + DATE '2025-01-01',TIMESTAMPTZ '2026-01-02 00:00:00+00' +); +SQL + +race_org_log="$(mktemp)" +( + psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' +BEGIN; +UPDATE organization_unit_version +SET recorded_to=TIMESTAMPTZ '2026-10-01 00:00:00+00' +WHERE organization_unit_version_id='10000000-0000-7000-8000-000000000043'; +INSERT INTO organization_unit_version ( + tenant_record_id, organization_unit_version_id, organization_unit_id, + unit_name, organization_type_code, effective_from, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000044', + '10000000-0000-7000-8000-000000000033','Race Department','department', + DATE '2025-01-01',TIMESTAMPTZ '2026-10-01 00:00:00+00' +); +SELECT pg_sleep(3); +ROLLBACK; +SQL +) >"${race_org_log}" 2>&1 & +race_org_pid=$! + +# Give the organization transaction enough time to reach its deliberate hold. +sleep 1 +set +e +race_employment_output="$(psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' 2>&1 +BEGIN; +SET LOCAL statement_timeout = '750ms'; +INSERT INTO employment_record_version ( + tenant_record_id, employment_record_version_id, employment_record_id, + employment_status_code, employment_concurrency_code, effective_from, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000025', + '10000000-0000-7000-8000-000000000024','active','exclusive', + DATE '2026-01-01',TIMESTAMPTZ '2026-10-02 00:00:00+00' +); +INSERT INTO employment_employing_organization_record ( + tenant_record_id, employment_employing_organization_record_id, + employment_record_id, employing_organization_unit_id, effective_from, recorded_from +) VALUES ( + '10000000-0000-7000-8000-000000000001','10000000-0000-7000-8000-000000000062', + '10000000-0000-7000-8000-000000000024','10000000-0000-7000-8000-000000000033', + DATE '2026-01-01',TIMESTAMPTZ '2026-10-02 00:00:00+00' +); +COMMIT; +SQL +)" +race_employment_status=$? +set -e +wait "${race_org_pid}" + +if [[ ${race_employment_status} -eq 0 ]]; then + cat "${race_org_log}" >&2 + rm -f "${race_org_log}" + echo "expected concurrent legal-entity and active Employment mutations to serialize, but the competing Employment committed" >&2 + exit 1 +fi +if [[ "${race_employment_output}" != *"statement timeout"* ]]; then + cat "${race_org_log}" >&2 + rm -f "${race_org_log}" + echo "expected tenant-local invariant serialization wait, got: ${race_employment_output}" >&2 + exit 1 +fi +rm -f "${race_org_log}" + echo "Employment employing-organization PostgreSQL contract passed" From db0e1a6e3bb482571187aac4e47947d635654dc5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 11:41:15 -0700 Subject: [PATCH 47/50] fix(hris): serialize employer exact-one mutations --- ...0040_employment_employing_organization.sql | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/database/migrations/0040_employment_employing_organization.sql b/database/migrations/0040_employment_employing_organization.sql index ad58c4f1b..809ccbe38 100644 --- a/database/migrations/0040_employment_employing_organization.sql +++ b/database/migrations/0040_employment_employing_organization.sql @@ -50,6 +50,90 @@ ON employment_employing_organization_record ( employing_organization_unit_id ); +-- The exact-one rule spans three relations. Deferred constraint triggers alone +-- can suffer READ COMMITTED write skew when two transactions validate before +-- either commit becomes visible. Serialize only mutations that can change this +-- invariant, scoped to one tenant. Hash collisions merely serialize unrelated +-- tenants; they cannot weaken integrity. +CREATE FUNCTION serialize_employment_employing_organization_invariant() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + v_tenant_record_id uuid; + v_organization_unit_id uuid; + touches_exact_one boolean := false; +BEGIN + IF TG_OP = 'DELETE' THEN + v_tenant_record_id := OLD.tenant_record_id; + ELSE + v_tenant_record_id := NEW.tenant_record_id; + END IF; + + IF TG_TABLE_NAME = 'employment_record_version' THEN + IF TG_OP = 'INSERT' THEN + touches_exact_one := NEW.employment_status_code IN ('active', 'leave'); + ELSIF TG_OP = 'DELETE' THEN + touches_exact_one := OLD.employment_status_code IN ('active', 'leave'); + ELSE + touches_exact_one := OLD.employment_status_code IN ('active', 'leave') + OR NEW.employment_status_code IN ('active', 'leave'); + END IF; + ELSIF TG_TABLE_NAME = 'organization_unit_version' THEN + IF TG_OP = 'DELETE' THEN + v_organization_unit_id := OLD.organization_unit_id; + touches_exact_one := OLD.organization_type_code = 'legal_entity'; + ELSIF TG_OP = 'INSERT' THEN + v_organization_unit_id := NEW.organization_unit_id; + touches_exact_one := NEW.organization_type_code = 'legal_entity'; + ELSE + v_organization_unit_id := NEW.organization_unit_id; + touches_exact_one := OLD.organization_type_code = 'legal_entity' + OR NEW.organization_type_code = 'legal_entity'; + END IF; + + IF NOT touches_exact_one THEN + touches_exact_one := EXISTS ( + SELECT 1 + FROM employment_employing_organization_record relation + WHERE relation.tenant_record_id = v_tenant_record_id + AND relation.employing_organization_unit_id = v_organization_unit_id + ); + END IF; + ELSE + -- Every employer-relationship mutation changes the exact-one graph. + touches_exact_one := true; + END IF; + + IF touches_exact_one THEN + PERFORM pg_advisory_xact_lock( + hashtext('orgmetra.employment_employer_exact_one'), + hashtext(v_tenant_record_id::text) + ); + END IF; + + IF TG_OP = 'DELETE' THEN + RETURN OLD; + END IF; + RETURN NEW; +END; +$$; + +CREATE TRIGGER employment_employing_organization_invariant_employment_lock +BEFORE INSERT OR UPDATE OR DELETE ON employment_record_version +FOR EACH ROW +EXECUTE FUNCTION serialize_employment_employing_organization_invariant(); + +CREATE TRIGGER employment_employing_organization_invariant_relationship_lock +BEFORE INSERT OR UPDATE OR DELETE ON employment_employing_organization_record +FOR EACH ROW +EXECUTE FUNCTION serialize_employment_employing_organization_invariant(); + +CREATE TRIGGER employment_employing_organization_invariant_organization_lock +BEFORE INSERT OR UPDATE OR DELETE ON organization_unit_version +FOR EACH ROW +EXECUTE FUNCTION serialize_employment_employing_organization_invariant(); + CREATE FUNCTION validate_employment_employing_organization_scope() RETURNS trigger LANGUAGE plpgsql @@ -349,6 +433,7 @@ ON employment_employing_organization_record USING (tenant_record_id = current_tenant_record_id()) WITH CHECK (tenant_record_id = current_tenant_record_id()); +REVOKE ALL ON FUNCTION serialize_employment_employing_organization_invariant() FROM PUBLIC; REVOKE ALL ON FUNCTION validate_employment_employing_organization_scope() FROM PUBLIC; REVOKE ALL ON FUNCTION reject_employment_employing_organization_truncate() FROM PUBLIC; From 7bf7accb3db46efa37a8dd7e06c0bbf839267b70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 11:42:15 -0700 Subject: [PATCH 48/50] docs(hris): record employer invariant serialization --- docs/adr/0141-employment-employing-organization.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/adr/0141-employment-employing-organization.md b/docs/adr/0141-employment-employing-organization.md index b142170a9..80c51083d 100644 --- a/docs/adr/0141-employment-employing-organization.md +++ b/docs/adr/0141-employment-employing-organization.md @@ -17,7 +17,9 @@ ISO 30414:2025 is the current published second edition of the human-capital repo Add `employment_employing_organization_record` as a 3NF bitemporal relationship between one tenant-scoped Employment and one tenant-scoped Organization. -At every effective-date/system-recorded-time coordinate where Employment status is `active` or `leave`, exactly one employing organization must resolve: overlaps are rejected by the bitemporal exclusion and missing relationships or effective gaps are rejected by deferred database constraint triggers. Those triggers revalidate Employment version, relationship, and legal-organization recorded-time changes together at transaction commit. The organization must be classified as `legal_entity` across the relationship's full effective interval at the relationship's recorded-time coordinate. The relationship interval must also be fully covered by `active` or `leave` Employment truth at that same recorded-time coordinate. +At every effective-date/system-recorded-time coordinate where Employment status is `active` or `leave`, exactly one employing organization must resolve: overlaps are rejected by the bitemporal exclusion and missing relationships or effective gaps are rejected by deferred database constraint triggers. The organization must be classified as `legal_entity` across the relationship's full effective interval at the relationship's recorded-time coordinate. The relationship interval must also be fully covered by `active` or `leave` Employment truth at that same recorded-time coordinate. + +Because the exact-one rule spans Employment versions, employer relationships, and Organization versions, deferred validation alone is insufficient under `READ COMMITTED`: two concurrent transactions can otherwise validate snapshots that exclude each other's uncommitted changes. Before a mutation that can affect the invariant proceeds, Orgmetra therefore takes one tenant-scoped transaction advisory lock for active/leave Employment version changes, every employer-relationship mutation, and legal-entity or already-referenced Organization-version changes. The lock serializes only the integrity-changing transaction class; deferred triggers still perform the authoritative cross-table validation at commit after any competing transaction becomes visible. The lock is an integrity mechanism, not an authorization mechanism, and a hash collision may only add harmless serialization rather than permit invalid state. 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. @@ -28,15 +30,18 @@ History is correction-not-rewrite: business fields cannot be updated in place; t - Workforce and employment-contract scope can distinguish legal employer from seat/organization placement. - Position transfers do not silently rewrite employer identity. - Legal-employer corrections preserve what Orgmetra knew and when. +- Concurrent employer-classification, relationship, and active/leave Employment mutations for one tenant serialize before exact-one validation, preventing READ COMMITTED write skew. +- Tenant-local bulk Employment/employer migrations must account for this integrity lock in batching and lock-wait observability; any future reduction in lock granularity must preserve the concurrency regression before adoption. - The model remains usable without payroll or statutory-account ownership. -- A future mutation API must add purpose-bound authorization, accountable actor/reason/evidence, immutable audit/outbox, and idempotency; this persistence slice alone does not authorize a high-impact Employment change. +- Mutation APIs additionally require purpose-bound authorization, accountable actor/reason/evidence, immutable audit/outbox, and idempotency; persistence integrity alone does not authorize a high-impact Employment change. ## Alternatives rejected 1. **Infer employer from Position.organization_unit_id.** Rejected because seat ownership and legal employment are different facts and can change independently. 2. **Store employer directly on `employment_record`.** Rejected because it would erase effective/system history and conflate durable Employment identity with a mutable relationship. 3. **Store a free-text legal-employer code.** Rejected because it breaks tenant-qualified Organization integrity and workforce lineage. -4. **Add payroll/legal-accounting tables.** Rejected as outside this bounded HRIS slice and outside accepted owner contracts. +4. **Rely on deferred cross-table triggers without write serialization.** Rejected because concurrent `READ COMMITTED` transactions can validate before either commit becomes visible and leave a write-skewed final state. +5. **Add payroll/legal-accounting tables.** Rejected as outside this bounded HRIS slice and outside accepted owner contracts. ## Verification @@ -45,6 +50,7 @@ History is correction-not-rewrite: business fields cannot be updated in place; t - exactly one employer per active/leave Employment business/system coordinate, including missing and effective-gap rejection; - full-interval `legal_entity` classification; - full-interval active/leave Employment coverage; +- deterministic serialization of a legal-entity reclassification against a competing active Employment/employer write before deferred validation; - tenant-qualified cross-tenant rejection; - correction-not-rewrite history; - TRUNCATE protection; and From 0d0a51ed86685a08f4545d44757fc11d6a1105f3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 11:42:40 -0700 Subject: [PATCH 49/50] docs(traceability): bind employer race regression --- docs/TRACEABILITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 591361a9d..021288d05 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -9,7 +9,7 @@ | Tenant-qualified HRIS integrity and fail-closed isolation | Core bounded contexts / Security architecture | `tenant_record`, tenant-qualified foreign keys, forced row-level security policies, tenant-scoped kernel query parameters | PostgreSQL cross-tenant FK/application-role RLS contracts plus kernel cross-tenant reconstruction, employment coverage, position coverage, seat-capacity, portfolio, exclusivity, and organization-hierarchy regressions | ADR-0001, ADR-0003 | implemented_on_active_pr | | Reserved UUID sentinel exclusion | Persistence integrity boundary | every foundation UUID `*_id` column plus audit/outbox identifiers | PostgreSQL inventory proof plus Nil/Max foundation and audit/outbox persistence regressions | ADR-0001, RFC 9562 | implemented_on_active_pr | | Normalized bitemporal organization/job/employment/position history | Core bounded contexts | `organization_unit_version`, `job_profile_version`, `employment_record_version`, `position_record_version` | PostgreSQL non-overlap, concurrent conflict, correction, rewrite-rejection, assignment-employment binding, and single-valued historical reconstruction | ADR-0001, ADR-0003, ADR-0004 | implemented_on_active_pr | -| Authoritative employing legal-organization relationship | Employment / Organization core | `employment_employing_organization_record` | `test_employment_employing_organization_postgres.sh` proves exactly one legal employer for every active/leave Employment coordinate, rejects missing relationships and effective gaps, proves legal-entity and active/leave coverage, correction history, tenant-qualified integrity, and forced-RLS isolation | ADR-0141 | implemented_on_active_pr | +| Authoritative employing legal-organization relationship | Employment / Organization core | `employment_employing_organization_record` plus tenant-scoped exact-one serialization lock across relevant Employment/Organization mutations | `test_employment_employing_organization_postgres.sh` proves exactly one legal employer for every active/leave Employment coordinate, rejects missing relationships and effective gaps, proves legal-entity and active/leave coverage, deterministically blocks the concurrent legal-entity-reclassification/active-Employment write-skew window before deferred validation, and proves correction history, tenant-qualified integrity, and forced-RLS isolation | ADR-0141 | implemented_on_active_pr | | Acyclic organization hierarchy at historical coordinates | Organization core | `organization_unit_version.parent_organization_unit_id` | indirect A→B→C→A rejection plus future-recorded and foreign-tenant isolation in `orgmetra_hris_kernel` | ADR-0001, ADR-0003 | implemented_on_protected_main | | Effective/system time | Bitemporal HRIS | `effective_from`, `recorded_from` | strict half-open interval and historical-coordinate tests | ADR-0003 | implemented_on_active_pr | | 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 | From 8d12ee7cd05549a69c6debf5e0de0c5fc8668f33 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 11:46:33 -0700 Subject: [PATCH 50/50] chore(provenance): reseal employer concurrency repair --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 8cbfb5b93..9c6809f82 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"2fbc239d7064be339f38b3501878df2568e1c27d0784ad17773ce7eaf1228676","bytes":15285,"lines":358},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"049c7ee1bacabfe2f95f2755636e8b9c96f9e9c92546ef18966fc0d78314605f","bytes":11943,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"2cc49ce6a4526a9571dfd302c09840e3700d81876d8f5117273ef14aa1fbd1c8","bytes":4351,"lines":55},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"20b2bef968fc8147b51b0170ffa89e0a4387faffb141e22062bd7674c74ce54d","bytes":12893,"lines":122},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"e70ed4b0c7ef8bd24e362ff8e9f04336c1c21766ecdf228d59bf2a32df876986","bytes":28573,"lines":655}]} \ 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":"a2293c3462a44ba1d7667a1c3ac17b4beaba56fe218353a53f2fe451506c3761","bytes":18210,"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":"database/migrations/0040_employment_employing_organization.sql","sha256":"1c2c61aa0e66a67e789a8d2132fe621fd7488cd578428c5dad10e4d48b85c485","bytes":18725,"lines":443},{"path":"docs/API_CONTRACT.md","sha256":"3c449f7c1b7d25614bb791f60a2ffdeef2ed812777f4d9038420ba16852ea2e5","bytes":5340,"lines":84},{"path":"docs/DATA_MODEL.md","sha256":"acd720587a0340993bdd2fae6625a4b7d7fa88a7ddfc24cf35823bb1fbb600d0","bytes":14063,"lines":88},{"path":"docs/ERD.md","sha256":"75299bf271fe99ee5137e1b02b9ca30af6d528adf546ed430d68993a18a6f9fa","bytes":7780,"lines":74},{"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":"e112826e47079eb7225c7e683e361501a46ac4432818a09b263a27770a0377ad","bytes":11359,"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":"ad4593172aa0492fc3d8caab4d84f3dad26e745036fccad1f5175fe231855394","bytes":12186,"lines":41},{"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/0141-employment-employing-organization.md","sha256":"d435a1eae3f4c7c795006f744ee46a98db4a6f9ddae01695f1f9c6d9ea2c85d1","bytes":5910,"lines":61},{"path":"docs/adr/README.md","sha256":"dfb41046f5fd0511bb2907f93fda9a65f5b5b6c37b350603240291e525e9524b","bytes":1958,"lines":19},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/doctoring/employment-employing-organization.md","sha256":"84c697134340ac9690d6716595c7336c9a58777d5d91741cfa51cb2ba08bd51c","bytes":2598,"lines":25},{"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":"30ec8533247a74659cc3251312c2d6ade75dffdd179d0117084c34e3704f0d92","bytes":1717,"lines":10},{"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":"b41582fb5c593f0d178a3dc34151be00eaf7cd8073174f47440b74cf12c26501","bytes":29630,"lines":1024},{"path":"scripts/foundation-contract-core.mjs","sha256":"b2a5939f6a9e4590beb17fe2e717e0615e4d9baf74ef0e3d85f6a0d85dec1fd8","bytes":28530,"lines":695},{"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":"c046ebb5b5b4800e24a8450ad549df29e1472552129a68b33e90daf8f43f7e17","bytes":15748,"lines":408},{"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_employment_employing_organization_postgres.sh","sha256":"1b8f8b76fc31758c02bede2959ee86c014d4380759744752e6c943b5cd437aae","bytes":16326,"lines":204},{"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":"d01f63265f9970f162c8574f9887c6c7af1f4630e930aeb38bc654e2fe444316","bytes":3454,"lines":102},{"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":"e70ed4b0c7ef8bd24e362ff8e9f04336c1c21766ecdf228d59bf2a32df876986","bytes":28573,"lines":655}]} \ No newline at end of file