Skip to content

fix(people): protect governed mutation runtime types - #64

Draft
seonghobae wants to merge 233 commits into
developfrom
fix/hire-identity-type-integrity
Draft

fix(people): protect governed mutation runtime types#64
seonghobae wants to merge 233 commits into
developfrom
fix/hire-identity-type-integrity

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Current execution receipt — 2026-09-13

  • Exact head: ec3fb59f269e8b6f6849d3c2e9c3f4ccf53b6a73; protected base: develop@eb9757f8649aaad026a9865508d9aad50c1a7a4f; open, Draft, mechanically mergeable.
  • 20e29962311b17eb004412cd775dd5ada0ca4cba produced a real hosted Foundation RED: test_generic_employment_creation_does_not_require_candidate_conversion failed because generic PostgresPeopleMutationPort.create_employment(...) still depended on _CONVERSION_SQL. The rest of the People lane passed and production coverage remained 100%, so this was a deliberate domain RED rather than gate noise.
  • 7f9fbd08470e5c5ab9292469ca91f7e42c2d8dc9 is the minimal production repair. 5eb7d1524e3a5992d7d575d93f260eb02bbe6061 ordinary-forward reconciles the existing post-lock recorded-time regression with the new Person conflict anchor. ec3fb59f269e8b6f6849d3c2e9c3f4ccf53b6a73 adds the remaining malformed anchor-time edge so the new helper's fail-closed condition is exercised rather than hidden by the 100% coverage gate.
  • Exact-head Foundation/Recovery/SAST/Security/CodeQL are newly materialized for ec3fb59...; predecessor verdicts are not transferred. No qualifying independent APPROVED review is bound to this exact head. Draft-skipped bot status is not approval.

Causal People-domain repair: generic Employment serializes on Person, not recruiting provenance

candidate_worker_conversion_record is recruiting-origin provenance and has a tenant-qualified FK to the resulting Employment. It cannot be the generic precondition for creating an arbitrary future Employment: exact binding would be circular before that Employment exists, while a Person-only historical conversion can incorrectly act as authority for an unrelated later Employment/rehire.

The current repair removes _CONVERSION_SQL from the generic Employment persistence path and replaces its incidental serialization role with the canonical current Person conflict anchor:

  1. after idempotency lookup, lock the tenant-qualified current person_record row with FOR UPDATE OF person;
  2. fail closed unless the locked Person identity exactly matches the command Person and the durable anchor row is structurally valid;
  3. obtain database-owned clock_timestamp() only after that conflict lock is held;
  4. under READ COMMITTED, issue the Employment-version read as the following statement so a waiter gets a fresh snapshot after the preceding creator commits;
  5. run the existing Employment portfolio/exclusivity kernel and preserve the existing Employment/version/audit/outbox/idempotency transaction semantics.

This removes recruiting provenance from generic People authorization without opening a different-idempotency-key race between two concurrent Employment creates for one Person. Assignment deliberately retains its candidate-worker conversion requirement; the repair is scoped to generic Employment creation only.

The recorded-time regression now asserts Person lock < post-lock clock_timestamp() < Employment portfolio read. The additional edge regression rejects a valid Person identity paired with non-database temporal evidence. Neither follow-up weakens the production contract or treats caller time as recorded-history authority.

Rehire / separation ownership

  • feat(people): 재입사를 기존 Person + 새 Employment로 명시적으로 모델링 #302 remains open for explicit governed rehire: existing Person + new Employment, with authoritative prior-separation truth plus fresh recruiting-origin rehire evidence or a separately purpose-bound, human-confirmed direct People-admin rehire decision. A historical candidate conversion is not rehire authority.
  • feat(people): add authoritative bitemporal Employment separation transition #314 remains open for authoritative bitemporal Employment separation. Rehire may consume that protected truth only after normal integration.
  • The earlier Assignment/Employment separation serialization stack remains unchanged: Assignment and separation share the Employment conflict anchor, Assignment re-reads coverage after lock wait, and both commit orders have dedicated PostgreSQL acceptance artifacts.

Foundation / documentation owner boundaries

No self-approval, force-push, destructive rebase, mutable sibling consumption, gate weakening, predecessor-evidence transfer, synthetic status, or routine bypass is authorized by this PR.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: b7c5b8f6-6a9e-48af-80fd-42512f13eafa

📥 Commits

Reviewing files that changed from the base of the PR and between 61cba0f and adab344.

📒 Files selected for processing (6)
  • services/people-api/src/orgmetra_people_api/postgres_hire.py
  • services/people-api/tests/test_postgres_hire_idempotency_text_runtime_integrity.py
  • services/people-api/tests/test_postgres_hire_provenance_text_runtime_integrity.py
  • services/people-api/tests/test_postgres_hire_row_container_runtime_integrity.py
  • services/people-api/tests/test_postgres_hire_timestamp_runtime_integrity.py
  • services/people-api/tests/test_postgres_hire_uuid_runtime_integrity.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/people-api/tests/test_postgres_hire_timestamp_runtime_integrity.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

People 변이와 채용 수락 경계가 서브클래스를 거부하도록 변경되었습니다. UUID, 문자열, Decimal, 명령, 결과, 권한 결정, 시간값, PostgreSQL 행 형태에 정확한 타입 검사를 적용했습니다. 런타임 무결성 회귀 테스트와 변경 기록을 갱신했습니다.

Changes

People 런타임 무결성

Layer / File(s) Summary
정확한 타입 검증과 변이 경로
services/people-api/src/orgmetra_people_api/hire.py, services/people-api/src/orgmetra_people_api/mutations.py
People 변이와 채용 수락의 입력, 명령, 결과, 권한 결정 검사를 정확한 타입 검사로 변경했습니다. idempotency_record_id는 테넌트 UUID를 검증한 뒤 ID를 파생합니다.
PostgreSQL 경계 검증
services/people-api/src/orgmetra_people_api/postgres_hire.py, services/people-api/src/orgmetra_people_api/postgres_mutations.py
PostgreSQL 포트가 UUID, 시간, 고정 행 형태, digest, provenance 텍스트, 명령, 권한 결정의 정확한 타입을 검증합니다. 잘못된 입력은 데이터베이스 작업 전에 기존 오류 유형으로 거부합니다.
런타임 무결성 회귀 테스트
services/people-api/tests/*runtime_integrity.py
위조된 UUID, 문자열, Decimal, 명령, 결과, 권한 결정, 시간값, 행 컨테이너가 생성, digest, 라우팅, 서비스 경계, 데이터베이스 경계를 통과하지 못하는지 검증합니다.
변경 기록과 매니페스트
CHANGELOG.md, manifest.json
고용 및 position 식별자 변경과 governance 문구 검증 변경을 기록했습니다. CHANGELOG 메타데이터를 갱신했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to adab3

This change hardens People mutation and hire-acceptance inputs against executable subtype values while preserving supported built-in values and persistence behavior. No concrete current-head merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 95.57% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 158 functions across 16 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 governed mutation의 runtime type 보호라는 PR의 주요 변경 사항을 정확하고 간결하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hire-identity-type-integrity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae seonghobae changed the title fix(hire): protect governed identity runtime types fix(people): protect governed mutation runtime types Aug 21, 2026
@seonghobae
seonghobae marked this pull request as ready for review August 21, 2026 09:37
devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae
seonghobae marked this pull request as draft August 22, 2026 00:33
@seonghobae
seonghobae marked this pull request as ready for review August 22, 2026 00:37

Copy link
Copy Markdown
Contributor Author

Exact-head repair/evidence update for the current branch state:

  • RED ad38eb452ac1b072a146b5edf41a64a8e6553c78: direct PostgreSQL hire authority must reject HireAcceptanceCommand / AuthorizationDecision subclasses before database work.
  • GREEN 63eb051935df4291b2e423189503c0aec71b6ff1: PostgresHireAcceptancePort now requires the exact governed command and authorization runtime types.
  • RED 27623490127834413c9ffd2dc900cb06e6ca00a6: direct PostgreSQL People mutation authorization must reject AuthorizationDecision subclasses.
  • Current/root repair 76cb5b0d963ce5c2d273f23f8dce01444a38499a: _require_authorization(...) now uses the same exact governed AuthorizationDecision runtime contract as mutation_command_digest(...); the addressed review thread is resolved.

Fresh exact-current-head hosted evidence is terminal GREEN: People API Quality 32540865696, Foundation CI 32540865669, SAST Semgrep 32540865701, Security Scan 32540865651, Recovery Rehearsal Quality 32540865671. People API Quality explicitly passed the exact statement/branch coverage step on this head.

Live protected base remains develop@9e3e4847510e1e612b48474ba42b177b8ed824df; GitHub reports this PR open, mergeable, and ready-for-review. There is still no qualifying formal independent non-author APPROVE, so do not merge or reuse predecessor evidence.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae
seonghobae marked this pull request as draft August 22, 2026 00:42
@seonghobae
seonghobae marked this pull request as ready for review August 22, 2026 00:43

Copy link
Copy Markdown
Contributor Author

Current exact-head follow-up after fresh review:

A new review correctly identified that the three direct PostgresPeopleMutationPort entry points still used isinstance(...) for command gates even after the direct hire port was hardened. That left employment/position/assignment persistence entry points able to accept validation-bypassing command subclasses before reaching the exact-type command_route(...)/digest boundary.

  • RED e859d208766463be243dd84443a615fbc2c83da1 adds direct persistence regressions for forged EmploymentMutationCommand, PositionMutationCommand, and AssignmentMutationCommand subclasses and requires rejection before any database work.
  • Root repair/current head 0196bf545b6254a410c99be80216ac977a706683 makes all three PostgreSQL mutation entry points require the exact governed command runtime type. The matching review thread is resolved.

Fresh exact-current-head hosted evidence is terminal GREEN:

  • People API Quality 32541189508 — success; its People API contract and 100% coverage job passed the exact statement/branch coverage step.
  • Foundation CI 32541189493 — success.
  • SAST Semgrep 32541189460 — success.
  • Security Scan 32541189467 — success.
  • Recovery Rehearsal Quality 32541189512 — success.

Live protected base remains develop@9e3e4847510e1e612b48474ba42b177b8ed824df. The PR is open, mergeable and ready-for-review on current head 0196bf545b6254a410c99be80216ac977a706683. Submitted reviews remain COMMENTED only; there is no qualifying independent non-author APPROVE, so do not merge or reuse predecessor evidence.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae
seonghobae marked this pull request as draft August 22, 2026 01:07
@seonghobae
seonghobae marked this pull request as ready for review August 22, 2026 01:08
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Current execution receipt — exact head 0d0b18ef9204077463d044073c92722d455dcd14, base develop@eb9757f8649aaad026a9865508d9aad50c1a7a4f, Draft and mechanically mergeable.

New verified finding: Assignment INSERT and Employment separation previously did not share one database conflict boundary. Separation locked employment_record before checking Assignment truth, while Assignment creation could validate Employment coverage without that lock; both commit orders admitted a TOCTOU window. A same-statement version FOR UPDATE is not an adequate repair under READ COMMITTED because its snapshot can predate the wait.

Ordinary-forward lineage in this increment: RED contract 7fdde5cb21194950c0828de067dde670f33f940e → DB guard acd99acd1bba5e8f789c052551a5195e43dcf71c → database-owned regression 0bd405a6699d6a99b41227cd7900ca711c8f6c92 → real two-session PostgreSQL acceptance f3d6bf890b6d08cf5b25ad50b91533b83fbd5a41 → least-privilege SECURITY DEFINER repair 012005d9910bebc517fb06b025b8aaa966ab3230 → static capability assertions ef5facd414cae33abaa95bb72f24d505de3178f4 → ADR currentization dba7ff5a5adffffb332e9c13140d58aa8e388940 → traceability 0d0b18ef9204077463d044073c92722d455dcd14.

The new PostgreSQL root is not claimed GREEN yet. Canonical Foundation #311 has received registration/reconciliation guidance, including the cluster-global guard-owner role lifecycle; #314 remains open; #100 received baseline invariants without a competing source write. Current-head hosted workflows are still non-terminal/queued, so predecessor success is not transferred.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 0d0b18ef9204077463d044073c92722d455dcd14, focusing on migration 0017 and tests/test_assignment_separation_serialization_postgres.sh: READ COMMITTED freshness after the Employment anchor wait, SECURITY DEFINER/RLS and minimal row-lock privilege, both lock-order interleavings, role lifecycle/collision behavior, cleanup/quiescence, and any false-GREEN/false-RED path. Please do not infer approval from earlier heads.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ContextualWisdomLab/Orgmetra"
head="0d0b18ef9204077463d044073c92722d455dcd14"
migration="database/migrations/0017_assignment_separation_serialization.sql"
script="tests/test_assignment_separation_serialization_postgres.sh"

echo '== Exact revision and PR state =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${head}^{commit}"
gh pr view 64 --repo "$repo" \
  --json number,state,isDraft,headRefOid,baseRefName,baseRefOid,mergeable,url

echo '== Commit scope and whitespace =='
git show --no-patch --format='commit %H%nparents %P%nsubject %s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --check "${head}^" "$head"
git diff --unified=160 "${head}^" "$head" -- "$migration" "$script"

echo '== Target migration structure =='
ast-grep outline "$migration" --items all || true
rg -n -C 18 \
  '(CREATE( OR REPLACE)? FUNCTION|SECURITY DEFINER|SECURITY INVOKER|search_path|row_security|FORCE ROW LEVEL SECURITY|ENABLE ROW LEVEL SECURITY|CREATE POLICY|GRANT|REVOKE|SET ROLE|FOR UPDATE|LOCK TABLE|pg_advisory|assignment|separation|employment_record|current|recorded_to|idempotency|audit|outbox|EXCEPTION|RAISE)' \
  "$migration"

echo '== Target migration in bounded sections =='
sed -n '1,260p' "$migration"
sed -n '261,560p' "$migration"

echo '== Target test script =='
cat -n "$script"

echo '== Test lifecycle, interleavings, role handling, and assertions =='
rg -n -C 14 \
  '(set -e|set \+e|trap|cleanup|wait\b|kill\b|pg_terminate_backend|pg_stat_activity|pg_blocking_pids|mkfifo|FIFO|barrier|release|COMMIT|ROLLBACK|psql|application_name|transactionid|tuple|advisory|FOR UPDATE|stale|current|role|CREATE ROLE|DROP ROLE|DROP OWNED|collision|quiescen|idempotency|audit|outbox|COUNT\(|row_security|SET ROLE|exit.status|status=)' \
  "$script"

echo '== Existing related database contracts and access model =='
rg -n -C 12 \
  '(separate_employment_record_once|create_assignment|assignment.*separation|employment.*assignment|FOR UPDATE|SECURITY DEFINER|FORCE ROW LEVEL SECURITY|GRANT .*separation|GRANT .*assignment|REVOKE .*assignment)' \
  database/migrations services/people-api/src/orgmetra_people_api tests \
  -g '*.sql' -g '*.py' -g '*.sh'

echo '== Script registration and references =='
rg -n -C 6 --glob '*.{yml,yaml,sh,py,mjs,md,json}' \
  '(test_assignment_separation_serialization_postgres|assignment_separation_serialization)' \
  .github tests scripts docs 2>/dev/null || true

echo '== Exact-head check and review status =='
gh api "repos/${repo}/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "null")\t\(.details_url)"'
gh pr view 64 --repo "$repo" --json reviews \
  --jq '.reviews[] | "\(.author.login)\t\(.state)\t\(.commit.oid)"'

Length of output: 4049


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ContextualWisdomLab/Orgmetra"
head="0d0b18ef9204077463d044073c92722d455dcd14"
script="tests/test_assignment_separation_serialization_postgres.sh"

echo '== Locate the target migration and related files =='
fd -t f . database/migrations tests docs services \
  | rg -i '(0017|assignment.*separation|separation.*assignment)' || true

echo '== Recent migration and test history =='
git log --reverse --format='commit %H%nparents %P%nsubject %s%n' \
  -- database/migrations tests/test_assignment_separation_serialization_postgres.sh \
  | tail -n 120

echo '== Current-head paths introduced since the PR base =='
base="eb9757f8649aaad026a9865508d9aad50c1a7a4f"
git diff --name-status "$base" "$head" \
  | rg '(database/migrations|test_assignment_separation|employment-separation)' || true

echo '== Target script =='
cat -n "$script"

echo '== Target script lifecycle and verification constructs =='
rg -n -C 14 \
  '(set -e|set \+e|trap|cleanup|wait\b|kill\b|pg_terminate_backend|pg_stat_activity|pg_blocking_pids|mkfifo|FIFO|barrier|release|COMMIT|ROLLBACK|psql|application_name|transactionid|tuple|advisory|FOR UPDATE|stale|current|role|CREATE ROLE|DROP ROLE|DROP OWNED|collision|quiescen|idempotency|audit|outbox|COUNT\(|row_security|SET ROLE|exit.status|status=)' \
  "$script"

echo '== Target script diff and exact head context =='
git log -n 8 --format='commit %H%nparents %P%nsubject %s%n' -- "$script"
git show --format='commit %H%nparents %P%nsubject %s' --stat -- "$script"
git diff --unified=160 "${head}^" "$head" -- "$script"

echo '== Migration candidates: database contracts and privileges =='
for file in $(fd -t f -e sql . database/migrations); do
  if rg -q '(assignment|separation)' "$file"; then
    echo "== $file =="
    rg -n -C 12 \
      '(CREATE( OR REPLACE)? FUNCTION|SECURITY DEFINER|search_path|row_security|FORCE ROW LEVEL SECURITY|ENABLE ROW LEVEL SECURITY|CREATE POLICY|GRANT|REVOKE|FOR UPDATE|pg_advisory|assignment|separation|employment_record|recorded_to|idempotency|audit|outbox|EXCEPTION|RAISE)' \
      "$file"
  fi
done

echo '== Registration and status =='
rg -n -C 6 --glob '*.{yml,yaml,sh,py,mjs,md,json}' \
  '(test_assignment_separation_serialization_postgres|assignment_separation_serialization)' \
  .github tests scripts docs 2>/dev/null || true
gh api "repos/${repo}/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "null")"' || true
gh pr view 64 --repo "$repo" --json reviews \
  --jq '.reviews[] | "\(.author.login)\t\(.state)\t\(.commit.oid)"' || true

Length of output: 141787


@seonghobae I found one defect in the reviewed companion script.

High — Verify PostgreSQL backend quiescence in cleanup().

tests/test_assignment_separation_serialization_postgres.sh has correct normal-path process handling. It waits for both client processes before each durable-state query.

However, cleanup() does not verify server-side quiescence on an error path. At Lines 114-119, it requests pg_terminate_backend(...) for matching sessions. At Line 120, it immediately removes runtime_dir.

pg_terminate_backend(...) requests termination. It does not prove that the backend has left pg_stat_activity. If wait_for_client_read(...), wait_for_blocker(...), or another assertion fails after a writer starts, the script can exit while a named backend still unwinds its transaction.

The application names are fixed:

  • orgmetra_assignment_separation_assignment_first
  • orgmetra_assignment_separation_separation_after_assignment
  • orgmetra_assignment_separation_separation_first
  • orgmetra_assignment_separation_assignment_after_separation

A later run can therefore detect or conflict with a surviving backend from an earlier failed run. This can produce non-deterministic false failures and can let live work overlap test-environment cleanup.

Make cleanup() poll pg_catalog.pg_stat_activity after the termination request. It must fail cleanup if any backend with application_name LIKE 'orgmetra_assignment_separation_%' remains, before it removes runtime_dir.

The migration itself has the required serialization order. guard_assignment_employment_coverage() locks the Employment anchor before it reads Employment versions. Since these are separate statements, the coverage query receives a fresh READ COMMITTED snapshot after an anchor-lock wait. The two test interleavings also correctly require a transactionid or tuple wait and verify the specific blocker with pg_blocking_pids(...).

This finding is static review evidence for the exact head only. I did not use predecessor approval or workflow evidence.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 20e29962311b17eb004412cd775dd5ada0ca4cba, focusing on the new candidate-conversion/Employment authority RED. In particular verify: (1) the protected conversion FK makes exact pre-insert conversion binding circular for server-generated generic Employment IDs; (2) the current Person-only conversion lookup is provenance coupling rather than target-aggregate authorization; (3) any eventual replacement must preserve the Person/Employment conflict-serialization guarantee without reintroducing #271's redundant lock; and (4) test_conversion_employment_binding_regression.py is a valid RED rather than a gate-weakening test. Do not treat a Draft-skip status as approval.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ContextualWisdomLab/Orgmetra"
head="20e29962311b17eb004412cd775dd5ada0ca4cba"
base="eb9757f8649aaad026a9865508d9aad50c1a7a4f"
test_file="services/people-api/tests/test_conversion_employment_binding_regression.py"

echo '== Exact head, ancestry, and PR state =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${head}^{commit}"
git cat-file -e "${base}^{commit}"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=true'
gh pr view 64 --repo "$repo" \
  --json number,state,isDraft,headRefOid,baseRefOid,mergeable,url

echo '== Head commit scope and whitespace =='
git show --no-patch --format='commit %H%nparents %P%nsubject %s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --check "${head}^" "$head"
git diff --unified=160 "${head}^" "$head" -- "$test_file"

echo '== Locate candidate-conversion, employment creation, and lock artifacts =='
fd -t f . services/people-api database tests docs \
  | rg -i '(conversion.*employment|employment.*conversion|employment.*separation|separation.*employment|mutation|001[0-9].*\.sql)' \
  | sort

echo '== Test structure and complete test source =='
ast-grep outline "$test_file" --items all
cat -n "$test_file"

echo '== Current implementation: conversion references and employment creation =='
rg -n -C 14 \
  '(candidate_worker_conversion_record|conversion_record|create_employment|EmploymentMutationCommand|employment_record_id|employment_record)' \
  services/people-api/src database/migrations \
  -g '*.py' -g '*.sql'

echo '== FK definitions and employment/conversion insert order =='
rg -n -C 16 \
  '(FOREIGN KEY|REFERENCES .*employment|REFERENCES .*candidate_worker_conversion|candidate_worker_conversion_record|INSERT INTO .*employment|INSERT INTO .*candidate_worker_conversion)' \
  database/migrations -g '*.sql'

echo '== Existing serialization and lock order for Person/Employment and Assignment/Employment =='
rg -n -C 18 \
  '(FOR UPDATE|pg_advisory|advisory|guard_assignment_employment_coverage|create_employment|candidate_worker_conversion_record|person_record)' \
  database/migrations services/people-api/src \
  -g '*.sql' -g '*.py'

echo '== Test registrations and test changes since base =='
rg -n -C 5 \
  '(test_conversion_employment_binding_regression|conversion_employment_binding)' \
  .github tests services docs -g '*.{py,sh,yml,yaml,md,json}' || true
git diff --name-status "$base" "$head" -- services/people-api/tests database/migrations services/people-api/src
git diff --unified=80 "$base" "$head" -- "$test_file"

echo '== Exact-head check and review status =='
gh api "repos/${repo}/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "null")"' || true
gh pr view 64 --repo "$repo" --json reviews \
  --jq '.reviews[] | "\(.author.login)\t\(.state)\t\(.commit.oid)"' || true

Length of output: 203436


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="20e29962311b17eb004412cd775dd5ada0ca4cba"
test_file="services/people-api/tests/test_conversion_employment_binding_regression.py"
source_file="services/people-api/src/orgmetra_people_api/postgres_mutations.py"

echo '== Test introduction and parent context =='
git log --format='commit %H%nparents %P%nsubject %s%n' -n 8 -- "$test_file"
git show --format=fuller --stat --find-renames "$head" -- "$test_file"
git diff --check "${head}^" "$head"
sed -n '1,80p' "$test_file"

echo '== Exact conversion schema and foreign-key constraints =='
for file in $(fd -t f -e sql . database/migrations | sort); do
  if rg -q 'candidate_worker_conversion_record' "$file"; then
    echo "== $file =="
    rg -n -C 18 \
      '(CREATE TABLE.*candidate_worker_conversion_record|candidate_worker_conversion_record|FOREIGN KEY|REFERENCES public\.employment_record|employment_record_id)' \
      "$file"
  fi
done

echo '== Full generic employment write sequence =='
sed -n '50,110p' "$source_file"
sed -n '517,555p' "$source_file"
sed -n '574,720p' "$source_file"

echo '== Existing tests covering generic employment conversion, idempotency, and exclusivity =='
rg -n -C 12 --glob '*.py' \
  '(create_employment|_CONVERSION_SQL|candidate-worker conversion|candidate_worker_conversion|person has no governed|validate_person_employment_exclusivity)' \
  services/people-api/tests

echo '== Lock statements and their callers in the current generic employment path =='
rg -n -C 10 --glob '*.py' \
  '(FOR UPDATE OF conversion|_CONVERSION_SQL|_EMPLOYMENT_VERSIONS_SQL|validate_person_employment_exclusivity)' \
  "$source_file" services/people-api/tests

echo '== Status, exact check associations, and recent review state =='
gh pr view 64 --repo ContextualWisdomLab/Orgmetra \
  --json state,isDraft,headRefOid,baseRefOid,mergeable,reviews
gh api "repos/ContextualWisdomLab/Orgmetra/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | select(.status != "completed" or .conclusion != "success") | "\(.name)\t\(.status)\t\(.conclusion // "null")"' || true

Length of output: 202616


@seonghobae I found one defect in the new RED test.

Medium — Test the authority contract and the replacement serialization boundary.

test_conversion_employment_binding_regression.py at Lines 14-17 only asserts that the source of PostgresPeopleMutationPort.create_employment does not contain the private token _CONVERSION_SQL.

This is an intentional RED on the current head. PostgresPeopleMutationPort.create_employment still calls _CONVERSION_SQL at Line 603. The current lookup selects a current conversion by tenant_record_id and person_record_id. It does not bind that conversion to the new employment_record_id.

The protected conversion FK makes a pre-insert requirement for a conversion already bound to the server-generated generic Employment circular. The confirmed-hire path inserts the Employment before it inserts the conversion. Therefore, the generic Employment path cannot use an existing conversion as authorization for the new Employment.

However, the new test does not prove this contract. A later change can move the lookup into a helper, rename _CONVERSION_SQL, or issue equivalent SQL. The test would pass while generic Employment creation still depends on Person-only conversion provenance.

The test also does not preserve the required concurrency property. The current conversion row lock is the Person/Employment conflict-serialization boundary. create_employment takes that lock before it reads Employment versions and calls validate_person_employment_exclusivity(...). Removing the lookup without a replacement serialized anchor can allow conflicting Employment decisions to use stale history. The replacement must keep this serialization without adding the redundant lock from #271.

Replace this source-text assertion with behavioral tests. The tests should prove that generic Employment creation succeeds without a candidate conversion, rejects invalid target-aggregate authority, and still serializes competing Employment writes for the same Person. A PostgreSQL concurrency test should verify the replacement lock boundary and fresh post-wait read behavior.

The exact head is Draft. Its current checks are queued or pending. The review list contains no qualifying APPROVED review. I did not treat Draft status or predecessor evidence as approval.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 7f9fbd08470e5c5ab9292469ca91f7e42c2d8dc9, especially the generic Employment change from candidate-conversion locking to the current Person row conflict anchor: READ COMMITTED post-wait visibility, lock ordering/deadlock risk, Person identity validation, and whether Assignment correctly retains its separate conversion-provenance requirement. Treat predecessor heads as non-authoritative.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ContextualWisdomLab/Orgmetra"
head="7f9fbd08470e5c5ab9292469ca91f7e42c2d8dc9"
base="eb9757f8649aaad026a9865508d9aad50c1a7a4f"
source="services/people-api/src/orgmetra_people_api/postgres_mutations.py"

echo '== Exact revision and pull request state =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${head}^{commit}"
git cat-file -e "${base}^{commit}"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=true'
gh pr view 64 --repo "$repo" \
  --json number,state,isDraft,headRefOid,baseRefName,baseRefOid,mergeable,url

echo '== Exact-head change scope and whitespace =='
git show --no-patch --format='commit %H%nparents %P%nsubject %s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --check "${head}^" "$head"
git diff --name-status "${head}^" "$head"
git diff --stat "${head}^" "$head"

echo '== Locate changed and related People mutation artifacts =='
git diff --name-only "${head}^" "$head" | sort
fd -t f . services/people-api/tests database/migrations docs \
  | rg -i '(employment|assignment|conversion|mutation|serialization|concurrency)' \
  | sort

echo '== Source map and current-head targeted diff =='
ast-grep outline "$source" --items all
git diff --unified=180 "${head}^" "$head" -- "$source"
rg -n -C 20 \
  '(class PostgresPeopleMutationPort|def create_employment|def create_assignment|_CONVERSION_SQL|person_record|FOR UPDATE|validate_person_employment_exclusivity|candidate_worker_conversion_record|employment_record|assignment)' \
  "$source"

echo '== Full relevant source sections =='
rg -n 'def create_employment\b|def create_assignment\b|_CONVERSION_SQL|_PERSON|validate_person_employment_exclusivity' "$source"
sed -n '1,180p' "$source"
sed -n '500,780p' "$source"

echo '== Test changes and existing behavioral/concurrency coverage =='
for file in $(git diff --name-only "${head}^" "$head" | rg '^services/people-api/tests/.*\.py$' || true); do
  echo "== $file =="
  ast-grep outline "$file" --items all
  cat -n "$file"
done
rg -n -C 16 --glob '*.py' \
  '(create_employment|create_assignment|candidate_worker_conversion_record|_CONVERSION_SQL|Person.*FOR UPDATE|person.*lock|employment.*concurr|serializ|stale.*version|FOR UPDATE)' \
  services/people-api/tests

echo '== Schema constraints, assignment conversion provenance, and lock ordering =='
rg -n -C 20 -g '*.sql' \
  '(candidate_worker_conversion_record|CREATE TABLE.*employment_record|CREATE TABLE.*assignment|FOREIGN KEY|REFERENCES public\.employment_record|FOR UPDATE|CREATE( OR REPLACE)? FUNCTION.*assignment|guard_assignment_employment_coverage|person_record)' \
  database/migrations

echo '== Relevant migration and service implementation sections =='
for file in $(fd -t f -e sql . database/migrations); do
  if rg -q '(candidate_worker_conversion_record|guard_assignment_employment_coverage|CREATE.*assignment)' "$file"; then
    echo "== $file =="
    rg -n -C 24 \
      '(candidate_worker_conversion_record|guard_assignment_employment_coverage|FOR UPDATE|employment_record|assignment_record|person_record|CREATE( OR REPLACE)? FUNCTION)' \
      "$file"
  fi
done

echo '== Registration or executable Postgres concurrency contracts =='
rg -n -C 10 --glob '*.{py,sh,yml,yaml,md,json}' \
  '(conversion_employment|employment.*concurr|person.*anchor|assignment.*conversion|assignment_separation)' \
  .github tests services docs 2>/dev/null || true

echo '== Exact-head check and review status =='
gh api "repos/${repo}/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "null")\t\(.details_url)"' || true
gh pr view 64 --repo "$repo" --json reviews \
  --jq '.reviews[] | "\(.author.login)\t\(.state)\t\(.commit.oid)"' || true

Length of output: 204303


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="7f9fbd08470e5c5ab9292469ca91f7e42c2d8dc9"
source="services/people-api/src/orgmetra_people_api/postgres_mutations.py"
test="services/people-api/tests/test_postgres_people_mutations.py"

echo '== Head delta =='
git show --format=fuller --stat "$head"
git diff --unified=220 "${head}^" "$head" -- "$source" "$test"

echo '== Exact SQL constants and current create paths =='
sed -n '45,115p' "$source"
sed -n '530,575p' "$source"
sed -n '600,735p' "$source"
sed -n '830,1010p' "$source"

echo '== Test setup, fake cursor sequencing, and relevant tests =='
ast-grep outline "$test" --items all --match 'PostgresPeopleMutationPortTests'
rg -n -A140 -B30 \
  '(class .*Cursor|def _port\b|def test_employment|def test_assignment|person employment anchor|candidate-worker conversion)' \
  "$test"

echo '== Every current use of the conversion and Person anchor SQL constants =='
rg -n -C 15 \
  '(_PERSON_EMPLOYMENT_ANCHOR_SQL|_CONVERSION_SQL)' \
  "$source" services/people-api/tests -g '*.py'

echo '== Current service entrypoint ordering =='
rg -n -A150 -B20 \
  'def create_employment_record\b|def create_assignment_record\b' \
  services/people-api/src/orgmetra_people_api -g '*.py'

echo '== Commit ancestry and exact check state =='
git show --no-patch --format='commit %H%nparents %P%nsubject %s' "$head"
git merge-base --is-ancestor "${head}^" "$head" && echo 'parent_is_ancestor=true'
gh api "repos/ContextualWisdomLab/Orgmetra/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "null")"' || true

Length of output: 141825


@seonghobae I found one defect in the exact-head change.

Medium — Update the Employment concurrency contract to require the Person anchor.

services/people-api/tests/test_postgres_mutation_concurrency_contract.py still states that Employment serialization locks the current conversion. It asserts:

assert "FOR UPDATE OF conversion" in postgres_mutations._CONVERSION_SQL

That assertion does not test the new conflict boundary. The generic Employment path now depends on _PERSON_EMPLOYMENT_ANCHOR_SQL. A future change can remove or weaken FOR UPDATE OF person while this test still passes because Assignment still retains _CONVERSION_SQL.

Update the test to require:

  • READ COMMITTED transaction isolation.
  • public.person_record as the Employment conflict anchor.
  • Current Person filtering with person.recorded_to IS NULL.
  • FOR UPDATE OF person.
  • The absence of _CONVERSION_SQL from PostgresPeopleMutationPort.create_employment.

The production ordering is otherwise correct. create_employment() obtains the Person row lock before it calls _EMPLOYMENT_VERSIONS_SQL. Because these are separate SQL statements under READ COMMITTED, a waiting transaction reads a fresh Employment snapshot after the first transaction commits. The Person identity check also rejects an absent, duplicate, malformed, reserved, or mismatched anchor before any Employment insert.

create_assignment() still uses _CONVERSION_SQL and rejects a missing conversion before its Assignment insert. This preserves the separate Assignment conversion-provenance requirement.

This is static review evidence for 7f9fbd08470e5c5ab9292469ca91f7e42c2d8dc9 only. The exact-head checks are queued or skipped. No qualifying independent approval is present.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact current head ec3fb59f269e8b6f6849d3c2e9c3f4ccf53b6a73. Focus on the production Person-row conflict anchor, fresh READ COMMITTED Employment portfolio visibility after lock wait, the 5eb7d... recorded-time regression reconciliation, and the ec3fb59... malformed anchor-time edge. Predecessor review evidence is non-authoritative.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ContextualWisdomLab/Orgmetra"
head="ec3fb59f269e8b6f6849d3c2e9c3f4ccf53b6a73"
base="eb9757f8649aaad026a9865508d9aad50c1a7a4f"
source="services/people-api/src/orgmetra_people_api/postgres_mutations.py"

echo '== Exact revision and pull request state =='
printf 'checked_out='; git rev-parse HEAD
git cat-file -e "${head}^{commit}"
git cat-file -e "${base}^{commit}"
git merge-base --is-ancestor "$base" "$head" && echo 'base_is_ancestor=true'
gh pr view 64 --repo "$repo" \
  --json number,state,isDraft,headRefOid,baseRefName,baseRefOid,mergeable,url

echo '== Exact head and immediate history =='
git show --no-patch --format='commit %H%nparents %P%nsubject %s' "$head"
git log --format='commit %H%nparents %P%nsubject %s%n' -n 30 "$head"
git diff --check "${head}^" "$head"
git diff --name-status "${head}^" "$head"
git diff --stat "${head}^" "$head"

echo '== Identify the recorded-time reconciliation commit and relevant files =='
git log --all --oneline --decorate --grep='recorded\|anchor\|employment' -i -n 100
git log --all --format='%H %s' -- services/people-api/src/orgmetra_people_api/postgres_mutations.py \
  services/people-api/tests | rg -i '5eb7d|recorded|anchor|employment' || true
git diff --unified=180 "${head}^" "$head" -- "$source"

echo '== Source map and production employment path =='
ast-grep outline "$source" --items all
rg -n -C 24 \
  '(def create_employment\b|_PERSON_EMPLOYMENT_ANCHOR_SQL|_EMPLOYMENT_VERSIONS_SQL|recorded_at|recorded_to|FOR UPDATE OF person|person_record|validate_person_employment_exclusivity|READ COMMITTED|candidate_worker_conversion)' \
  "$source"
sed -n '1,180p' "$source"
sed -n '520,800p' "$source"

echo '== All exact-head changed tests and relevant regression tests =='
git diff --name-only "${head}^" "$head" | while IFS= read -r file; do
  case "$file" in
    *.py)
      echo "== $file =="
      ast-grep outline "$file" --items all || true
      cat -n "$file"
      ;;
  esac
done
rg -n -C 18 --glob '*.py' \
  '(PERSON_EMPLOYMENT_ANCHOR|create_employment|anchor.*recorded|recorded_at|recorded_to|malformed.*anchor|employment.*concurr|FOR UPDATE OF person|current Person)' \
  services/people-api/tests

echo '== Data model and transaction contracts =='
rg -n -C 18 --glob '*.sql' \
  '(CREATE TABLE.*person_record|CREATE TABLE.*employment_record|recorded_at|recorded_to|FOR UPDATE|person_record|employment_record|validate_person_employment_exclusivity|READ COMMITTED)' \
  database/migrations

echo '== Caller boundary and fake cursor sequencing =='
rg -n -A180 -B20 \
  'def create_employment_record\b|def create_employment\b' \
  services/people-api/src/orgmetra_people_api -g '*.py'
rg -n -C 14 --glob '*.py' \
  '(class .*Cursor|fetchone\(|fetchall\(|PERSON_EMPLOYMENT_ANCHOR|employment.*anchor)' \
  services/people-api/tests

echo '== Exact-head checks and reviews =='
gh api "repos/${repo}/commits/${head}/check-runs?per_page=100" \
  --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "null")\t\(.details_url)"' || true
gh pr view 64 --repo "$repo" --json reviews \
  --jq '.reviews[] | "\(.author.login)\t\(.state)\t\(.commit.oid)"' || true

Length of output: 203698


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="ec3fb59f269e8b6f6849d3c2e9c3f4ccf53b6a73"
source="services/people-api/src/orgmetra_people_api/postgres_mutations.py"
test_main="services/people-api/tests/test_postgres_people_mutations.py"
test_time="services/people-api/tests/test_postgres_mutation_recorded_time_regression.py"
test_binding="services/people-api/tests/test_conversion_employment_binding_regression.py"

echo '== Exact commit delta =='
git show --format=fuller --stat "$head"
git diff --unified=240 "${head}^" "$head" -- "$source" "$test_main" "$test_time" "$test_binding"

echo '== SQL constants and validation helpers =='
rg -n -A55 -B10 \
  '^(_READ_WRITE_SQL|_PERSON_EMPLOYMENT_ANCHOR_SQL|_EMPLOYMENT_VERSIONS_SQL|_POST_LOCK_CLOCK_SQL)|^def (_require_one_person_employment_anchor|_post_lock_recorded_at|_is_aware_datetime|_employment_version_from_row|_unpack_fixed_rows)\b' \
  "$source"

echo '== Complete narrow source range =='
sed -n '40,180p' "$source"
sed -n '350,590p' "$source"
sed -n '610,735p' "$source"

echo '== Recorded-time and malformed-anchor tests =='
cat -n "$test_time"
cat -n "$test_binding"
sed -n '250,350p' "$test_main"

echo '== Test fixtures: cursor result sequencing and normal employment path =='
sed -n '40,135p' "$test_main"
rg -n -A80 -B15 \
  'def _port\b|def test_employment|test.*anchor|test.*recorded' \
  "$test_main"

echo '== History for recorded-time reconciliation =='
git log --all --oneline --decorate -- "$test_time" "$test_binding" "$source" | head -n 100
git log --all --format='%H%n%P%n%s%n' -- "$test_time" | head -n 100
git log --all --format='%H %s' | rg -i 'recorded.time|post.lock|anchor.time|malformed.anchor|5eb7d' || true

echo '== Static production ordering and isolation checks =='
rg -n -C 12 \
  '(SET TRANSACTION ISOLATION LEVEL|READ COMMITTED|_PERSON_EMPLOYMENT_ANCHOR_SQL|_POST_LOCK_CLOCK_SQL|_EMPLOYMENT_VERSIONS_SQL)' \
  "$source"

Length of output: 74528


@seonghobae I reviewed exact head ec3fb59f269e8b6f6849d3c2e9c3f4ccf53b6a73.

I found no defect in the requested scope.

PostgresPeopleMutationPort.create_employment sets READ COMMITTED before it obtains the Person conflict anchor. It then locks one current public.person_record row with tenant, Person ID, recorded_to IS NULL, and FOR UPDATE OF person.

The implementation reads the Employment portfolio only after it obtains the Person-row lock. PostgreSQL gives this later statement a new READ COMMITTED snapshot. A competing generic Employment creation for the same Person must use the same anchor. The waiting command can therefore observe the committed winner before validate_person_employment_exclusivity(...) runs.

The recorded-time reconciliation is correct. The anchor query returns transaction_timestamp(), but the implementation uses it only as validated anchor evidence. _post_lock_recorded_at(...) obtains clock_timestamp() after the lock. The implementation uses this post-lock value for:

  • EmploymentVersion.recorded
  • validate_person_employment_exclusivity(..., known_at=recorded_at)
  • Employment and Employment-version recorded_from
  • Audit event time

services/people-api/tests/test_postgres_mutation_recorded_time_regression.py verifies the required statement order: Person lock, post-lock clock, then Employment portfolio query. Its winner scenario rejects the conflicting Employment.

The ec3fb59... regression is valid. _require_one_person_employment_anchor(...) rejects the string timestamp before the port can obtain the post-lock clock or issue an Employment insert. The helper also rejects malformed row shapes, multiple rows, non-operational UUID values, mismatched Person IDs, and invalid timestamp values.

The generic Employment path does not use _CONVERSION_SQL. The Assignment path still uses _CONVERSION_SQL and retains its separate candidate-conversion provenance requirement.

This is a static review of the exact head. At inspection time, the exact-head required workflows were queued, and no qualifying independent APPROVED review was present.

You are interacting with an AI system.

Comment on lines +367 to +377
return authorize_resource_fields(
principal=principal,
tenant_record_id=tenant_record_id,
purpose_code=purpose_code,
resource_kind=resource_kind,
resource_id=resource_id,
requested_fields=requested_fields,
required_scope_code=required_scope_code,
permitted_fields=requested_fields,
policy=policy,
)
) -> EmploymentMutationResult:
"""Persist one employment or raise without partial writes."""
"""Persist one Employment mutation."""
...
) -> PositionMutationResult:
"""Persist one position or raise without partial writes."""
"""Persist one Position mutation."""
...
) -> AssignmentMutationResult:
"""Persist one assignment or raise without partial writes."""
"""Persist one Assignment mutation."""
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant