fix/job-active
needs attentionviewing older commit2099ea7 · incrementalPR #314reviewed 2026-07-16 00:53 UTC0H · 3M · 5L · 2I- Purpose
- Fix status-blind RPU contract resolution that caused live incident 2026-07-14 (Solfium, RPU 077120901845): unordered LIMIT 1 picked a terminated history row, minting a junk site and corpse SUC.
- Goal
- Active-first RPU resolution everywhere + evidence-driven reactivation (self-heal within one billing cycle). Branch 1 of 2 (backend).
- Sub-goals
- SG-1: Active-first resolution via findByContractNumberActiveFirst — adopted by job-intent shell, cfe-jobs handler, wizard PATH-2, payment-status writers
- SG-2: Terminated-only RPUs mint nothing — site/SUC/subscriptions created only after real bills arrive
- SG-3: Evidence-driven reactivation — ReactivateContract decision branch reactivates terminated rows with newer XML evidence
- SG-4: reassignContractIdDeduped on SUC + subscriptions — prevents 23505 on reactivation re-point
- SG-5 (this commit): OCC null in updateContractShell → ContractVersionConflict (409) not databaseError (500)
- What
- Disambiguates updateWithVersion returning null: if the row still exists, return ContractVersionConflict (409, retryable) instead of databaseError (500). Adds integration test covering the OCC-race path.
- Why
- Null from updateWithVersion could be either an OCC race or a phantom delete. A 500 was misleading — callers couldn't distinguish infra errors from retryable conflicts.
- Areas
- apps/platform+19−5docs/development+205−0domains/cross-domain+389−12domains/utility/src/utility-contract+712−19domains/utility/src/bill + subscriptions + suc+153−4services/utility/bills/cfe+3−1
- Blast
- 19 files, +1481/−41 cumulative. Concentrated in utility-contract domain + cross-domain intent shell. No schema migrations. No API surface changes.
Findings · 10
correctness3
READ COMMITTED TOCTOU: diagnostic re-read does not share a snapshot with the failed update
domains/utility/src/utility-contract/utility-contract.shells.ts:236
Under READ COMMITTED, the second findByPublicId sees latest committed state, not the update attempt's snapshot. A hard-delete between the two reads falls through to databaseError('update') misleadingly.
Soft-deleted row triggers ContractVersionConflict misleadingly
domains/utility/src/utility-contract/utility-contract.shells.ts:236
findByPublicId with includeDeleted=true returns soft-deleted rows; a concurrent soft-delete would give a misleading 409.
Comment conflates rollback safety with snapshot safety
domains/utility/src/utility-contract/utility-contract.shells.ts:233
conventions3
updateContractShell OCC test placed in wrong file
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:667
File is scoped to ensureContractFromBillsShell. updateContractShell belongs in utility-contract.integration.test.ts.
Spy-mock of a query inside an integration test — pragmatic but should be noted
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:675
Test comment explains WHAT, not WHY
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:672
tests4
Branch 2 (row gone → databaseError 500) is untested
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:667
Only the row-exists path is covered. The row-gone path needs a spy on findByPublicId returning null.
Test does not assert expectedVersion/actualVersion fields
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:689
No assertion that no outbox event was emitted on the OCC path
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:667
vi.spyOn intercept confirmed valid
domains/utility/src/utility-contract/__tests__/ensure-contract-evidence.integration.test.ts:675
History · 6 commits
- 3e1361aneeds attentionincremental0H · 1M · 0L2026-07-16 01:14
- c7b4e31needs attentionincremental0H · 1M · 5L2026-07-16 01:03
- 2099ea7needs attentionincremental0H · 3M · 5L2026-07-16 00:53current
- ce2dc3eneeds attentionincremental0H · 1M · 1L2026-07-16 00:44
- df0ef09needs attentionincremental0H · 2M · 3L2026-07-16 00:23
- d4a8d84needs attentionfull0H · 3M · 5L2026-07-16 00:04