Repository navigation
feat(people): 재입사를 기존 Person + 새 Employment로 명시적으로 모델링 #302
Description
Verified buyer / domain gap
Protected develop@eb9757f8649aaad026a9865508d9aad50c1a7a4f already separates stable person_record identity from durable employment_record identity and bitemporal employment_record_version truth. That is the right foundation for rehire, but the protected People mutation boundary does not yet model rehire as its own governed employment event.
Current PostgresPeopleMutationPort.create_employment(...) accepts a new Employment for a Person after finding exactly one current candidate_worker_conversion_record for that tenant + Person. The lookup does not bind that conversion to the new Employment being created. As a result, an old/current-open candidate→worker conversion from an earlier hire can satisfy the generic precondition for a later Employment creation. The new write is then audited as ordinary employment_created; there is no durable prior-Employment reference, rehire decision/provenance, or explicit distinction between new hire and rehire.
The HRIS kernel correctly rejects overlapping exclusive Employments and allows a later exclusive Employment after the prior effective period ends, but that temporal rule alone is not rehire authority. “same Person can have another non-overlapping Employment” and “this write is an authorized rehire” are different domain statements.
This issue therefore owns a People/Employment domain increment, not a Candidate matching heuristic and not an identity-backend feature.
Domain direction
Default canonical rehire should preserve the existing person_record and create a new employment_record for the new employment relationship/contract. Prior Employment history, Assignment history, audit evidence, and bitemporal corrections remain immutable/history-preserving. Do not reactivate or rewrite the old Employment anchor merely to make the person active again.
A rehire command/result must be explicit enough to prove at least:
- tenant + existing Person identity;
- prior Employment identity being used as rehire context;
- new Employment identity/version being created;
- effective start of the new Employment;
- authoritative prior separation/termination/end-state evidence and non-overlap at the relevant business/system coordinate;
- accountable actor, purpose, controlled reason, evidence version, and human confirmation;
- whether the rehire originated from a new recruiting/candidate flow or a separately authorized direct People administration flow;
- idempotency identity and immutable audit/outbox evidence.
Do not infer rehire by name, email, date of birth, phone number, or fuzzy matching inside people_core. Matching/deduplication may produce candidate evidence, but the resulting Person binding must be an explicit governed decision before People persists rehire truth.
New Employment vs old Employment
Default: existing Person + new Employment.
A “rehire on old Employment” capability, if Orgmetra ever needs it for seasonal/special cases, is a distinct future policy and mutation contract. It must not emerge accidentally from reusing the old Employment ID or reopening history. It would require its own invariants for contract continuity, downstream references, effective/system time, audit, payroll/integration consequences, and correction semantics.
This distinction is also consistent with current SAP SuccessFactors product guidance: SAP documents both old- and new-employment rehire, states that new-employment rehire adds a new Employment to the existing Person while preserving Person-level identity, and recommends new Employment wherever possible; separate rehire verification asks an administrator to decide whether a potential former-employee match is actually a rehire or a new hire. This is vendor precedent, not a normative Orgmetra standard or legal rule.
Current primary vendor references checked 2026-09-12:
- SAP Help, Rehire with New and Old Employments: https://help.sap.com/docs/successfactors-onboarding/implementing-onboarding/rehire-with-new-and-old-employments
- SAP Help, Recommended Practices for Employee Master Data Replication: https://help.sap.com/docs/SAP_SUCCESSFACTORS_EMPLOYEE_CENTRAL_PAYROLL/185f14fbe60d4bbb8d7d5e4f8d89b24b/f6d617b220da403c8d427b3532e85902.html
- SAP Best Practices 1H 2026, Perform Second Rehire Verification: https://help.sap.com/docs/successfactors-onboarding/manage-onboarding-initiated-from-recruiting-test-script/perform-second-rehire-verification-optional?version=2605
- SAP Help, rehire target-population/configuration guidance, last updated 2026-08-14: https://help.sap.com/docs/successfactors-onboarding/implementing-onboarding/enabling-rehire-with-new-and-rehire-on-old-employments
DDD / ownership
Canonical owner is the existing People/Employment mutation owner (#64 or its normally integrated verified successor).
people_coreowns Person/Employment/Rehire truth and the new Employment transaction.talent_acquisitionmay own a new application/candidate, matching/review evidence, and a new hire/rehire selection decision. People consumes only a released/versioned decision/reference contract; no cross-service application SQL.- Keyverse remains identity/authz backend; authentication identity is not
person_recordor Employment truth. - Organization/Position/Assignment ownership does not move. A rehire may later create Assignments through released People/Organization contracts; it does not mutate a Position or resurrect an old Assignment implicitly.
document_recordsretains candidate/worker artifact lifecycle; rehire is not permission to copy old recruiting documents into the new Employment record.
A normalized employment_rehire_record (or equivalent aggregate-support relation) is a plausible implementation if durable prior→new Employment provenance cannot be expressed safely in existing immutable audit/idempotency evidence. Do not lock that schema in from this issue alone: the #64 owner must first define the Aggregate/UL/API/DB transaction boundary and reject unnecessary duplicated truth.
Required RED before production repair
Add owner-level executable contracts that fail on current behavior before introducing the new persistence path.
- Existing Person, ended prior Employment, valid rehire decision → creates a new Employment identity; prior Employment/version/Assignment history remains unchanged.
- Generic old candidate-worker conversion only → cannot by itself authorize a rehire Employment.
- Wrong prior Employment → tenant-local but different Person, cross-tenant, unknown, or malformed prior Employment fails closed before mutation/audit.
- Prior Employment still active/overlapping → a new
exclusiveEmployment fails under the authoritative business/system-time portfolio;concurrentremains a separate ordinary multi-employment case and must not be mislabeled as rehire. - Old Employment ID reused as the default rehire target → fail closed unless a future separately governed old-employment-rehire contract explicitly authorizes that operation.
- Recruiting-origin rehire → a new candidate/selection decision may bind to the existing Person + new Employment only through explicit match/rehire decision evidence; candidate matching itself never becomes People authorization.
- Direct People-admin rehire → if supported, requires a separately purpose-bound human-confirmed rehire decision and must not fabricate a candidate conversion or selection decision.
- Same idempotency key + same semantic rehire command → deterministically replays the first committed new Employment identity; changed prior/new Employment semantics under the same key fail closed.
- Concurrent first rehire attempts → real PostgreSQL two-session acceptance proves the conflict boundary and exactly one authoritative logical rehire outcome, without timing-only sleeps or broad table locks.
- Failure cleanup → no orphan Employment/version/audit/outbox/idempotency/rehire evidence after rollback; all client/server sessions quiesce before teardown.
Synthetic structural rows are acceptable for PostgreSQL transaction/locking mechanism tests only. Buyer/scientific claims require right-cleared real evidence with provenance.
Persistence / evidence invariants
- Aggregate transaction remains minimal; no external matching, ATS call, LLM, document processing, or long computation inside an explicit DB transaction/lock.
- Same-tenant composite FKs and FORCE RLS/NOBYPASSRLS remain mandatory.
- Rehire provenance stores opaque identifiers, governed codes/digests and timestamps rather than names, contact data, resume content, compensation, ratings, or other duplicated PII.
- Audit/outbox must identify the operation as rehire (not silently ordinary new-hire creation) and bind prior + new Employment references without embedding mutable HR payloads.
- Bitemporal history uses correction-not-rewrite. Rehire does not edit a prior Employment's historical recorded interval except through an already-governed correction/termination owner contract.
- A proper authoritative separation/termination/end-of-employment write boundary is a prerequisite if protected truth cannot yet establish the prior Employment state needed by the rehire decision. Do not fake that prerequisite by creating a new Employment initially marked
terminatedor by treating a review packet as mutation truth.
Delivery / owner order
Do not create a parallel People mutation writer. Implement this after the current #64 owner contracts are reconciled and normally protected, or ordinary-forward on that exact canonical owner if it remains the active writer. #65 authorization reconciliation and #141 employing-organization work must then non-force adopt the resulting protected People truth in their existing order.
Before integration: People owned production docstrings/tests/edge coverage 100%; real PostgreSQL acceptance; PRD/TRD/ARCHITECTURE/Data Model/ERD/UML/SECURITY/THREAT_MODEL/TEST_STRATEGY/OPERABILITY/TRACEABILITY/ADR code-current through canonical writers; exact-head Foundation/SAST/Security/CodeQL/OpenCode/Noema/Strix and qualifying independent review; no self-approval, routine bypass, force push, destructive rebase, mutable sibling source copy, or predecessor GREEN transfer.
Keep this issue open until an executable canonical People owner reaches protected truth. A design note or UI label is not completion.
Activity
[-]idea(people): 재입사자에 관한 처리[/-][+]feat(people): 재입사를 기존 Person + 새 Employment로 명시적으로 모델링[/+]seonghobae commentedon Sep 12, 2026
Prerequisite #314 has moved from design-only to an executable People-owner slice on #64. Current #64 exact head is
fdb33797fde1efc215ab0dcde474ce82e7d27dfd: migrations 0014/0015 define the bitemporal separation transition and deny-default SQL capability; focused PostgreSQL contracts cover separation behavior and function-capability denial. The latest ordinary-forward repair isolates temporary probe-principal lifecycle so acceptance retries cannot be poisoned by leaked fixed roles.This does not unblock rehire yet. #314 is still Draft/Proposed rather than protected truth, current-head hosted PostgreSQL admission and qualifying independent approval are absent, and the explicit application/service-role invocation boundary remains to be completed through #64. Keep #302 ordered after protected #314; do not infer rehire authority from the new SQL files, an old candidate-worker conversion, or a review packet.
seonghobae commentedon Sep 12, 2026
Fresh #64 inspection found a stronger prerequisite than the original Person-only stale-conversion finding.
Exact evidence on
fix/hire-identity-type-integrity:candidate_worker_conversion_recordhas a same-tenant composite FK(tenant_record_id, employment_record_id, person_record_id) -> employment_record; a conversion is therefore evidence about an Employment that already exists.POST /v1/employment-recordsallocates a brand-new Employment ID server-side, thenPostgresPeopleMutationPort.create_employment()checks conversion before inserting that Employment.That means an attempted “fix” that binds the pre-insert conversion lookup to the new Employment ID is circular: the exact conversion cannot exist before the Employment because of the FK, while the Employment is not inserted until after the lookup. I verified this after the RED contract and will not treat that exact-binding attempt as a valid rehire repair.
The domain consequence is clearer:
candidate_worker_conversion_recordis recruiting-origin provenance, not generic People authorization. Rehire must either consume a fresh recruiting-origin decision in an atomic new-Employment+conversion transaction or a separately governed direct People-admin rehire decision, as this issue already requires. An old conversion must never authorize the new Employment, but the fix cannot be “query the impossible future conversion”.Keep #302 open. Canonical repair should explicitly separate (a) confirmed recruiting hire/rehire materialization from (b) direct People employment administration, and then prove that the generic employment route cannot silently become rehire merely because a historical conversion exists. The candidate-conversion FK and the server-generated Employment ID should be included in the RED acceptance so this circular design cannot return.
seonghobae commentedon Sep 12, 2026
#64에서 이 이슈의 prerequisite를 더 정확히 분리했습니다.
candidate_worker_conversion_record는 resulting Employment FK를 가진 recruiting-origin provenance라 generic Employment 생성의 권한/전제조건으로 재사용하면 안 됩니다. Hosted RED20e29962311b17eb004412cd775dd5ada0ca4cba가 실제로 기존PostgresPeopleMutationPort.create_employment(...)의_CONVERSION_SQL의존을 잡았고, current repair7f9fbd08470e5c5ab9292469ca91f7e42c2d8dc9는 generic Employment를 tenant-qualified currentperson_record의FOR UPDATEconflict anchor로 직렬화한 뒤 post-lock DB clock과 fresh READ COMMITTED statement로 Employment portfolio를 다시 평가합니다. Assignment의 conversion provenance requirement는 그대로 유지합니다.따라서 #302의 durable rule은 더 명확해집니다: historical/current-open candidate conversion은 rehire authority가 아니며, rehire는 existing Person + new Employment + authoritative prior separation + fresh recruiting rehire decision 또는 별도 purpose-bound/human-confirmed People-admin rehire decision을 요구해야 합니다. #314 protected separation truth가 prerequisite인 점도 변하지 않습니다. 이 이슈는 계속 open 유지합니다.