The sync journal, and a terminal run that really is frozen

Featurekamo-shared-library
Shipped
August 27, 2026 at 7:15 PM UTC
Author
Kamo
Commit
1608785

BL1 Task 5, shared-library half. Five journal enums, three entities and three repositories under **************** No stereotypes: BillingWormGuard is a plain final class with a private constructor, and SharedLibBeanSafetyTest is green. Two deliberate improvements over the payroll source: 1. THE TERMINAL-RUN GUARD PAYROLL DOCUMENTS AND DOES NOT HAVE (defect 5). PayrollSyncRun's javadoc claims "a terminal run is never rewritten" with nothing behind it. BillingSyncRun enforces it. The plan specifies the guard as a bare read of the current field: if (state != null && state != BillingSyncRunState.RUNNING) { throw ... } That version is WRONG and would have broken production. @PreUpdate fires AFTER the caller has mutated the entity, so it sees the terminal state BillingSyncJournal.complete() has just assigned and refuses the one write the row exists to receive — complete() would throw on every run and closeStaleRuns() on every sweep. The guard therefore compares against the state as LOADED, captured by a @PostLoad snapshot into a @Transient field. A row never loaded (a fresh `new`) is judged on its current state, which keeps the plan's own test meaningful. 2. dbNow() (Self-Review's flagged gap). Payroll reads the database clock from TimecardPunchRepository; InvoicingService has no punch table. Added **************** — @Query(value = "SELECT CAST(now() AS timestamp)", nativeQuery = true) — with the SQL-standard CAST, because a `::` cast is silently mangled: Hibernate reads `:` as a named-parameter prefix and turns now()::timestamp into :timestamp. BillingSyncJournal is its only caller and the only real writer of occurredAt; the entities' @PrePersist LocalDateTime.now() fallbacks are kept as last-resort guards and documented as such (defect 6). Both WORM entities are served by NARROW repositories extending Repository, not JpaRepository, so there is no batch delete to walk past @PreRemove. TDD evidence — RED first (compilation, before any of the classes existed): [ERROR] **************** cannot find symbol [ERROR] symbol: class BillingSyncIssue [ERROR] location: class **************** ... 20 further "cannot find symbol" errors, BUILD FAILURE GREEN after implementing: Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 Mutations, each run from an isolated `git archive HEAD` copy with its own target/ and its own -Dmaven.repo.local (this checkout is shared with other sessions and a red suite here is usually someone else's red phase): MUTATION D — enforceTerminalImmutability's `throw` replaced with `return` (the mutation the plan's Step 6 names): Tests run: 7, Failures: 2, Errors: 0, Skipped: 0 <<< FAILURE! **************** <<< FAILURE! java.lang.AssertionError: Expecting code to raise a throwable. at **************** **************** <<< FAILURE! java.lang.AssertionError: Expecting code to raise a throwable. at **************** MUTATION E — the guard rewritten EXACTLY as the plan specifies it, i.e. a bare read of `state` with no @PostLoad snapshot. This is the evidence that the plan's version is broken: Tests run: 7, Failures: 1, Errors: 0, Skipped: 0 <<< FAILURE! **************** <<< FAILURE! java.lang.AssertionError: Expecting code not to raise a throwable but caught **************** BillingSyncRun null is SUCCEEDED and is evidence about a completed run; it cannot be rewritten. at **************** at **************** MUTATION F — BillingWormGuard.reject made a no-op: Tests run: 7, Failures: 4, Errors: 0, Skipped: 0 <<< FAILURE! **************** -- Expecting code to raise a throwable. **************** -- Expecting code to raise a throwable. **************** -- Expecting code to raise a throwable. **************** -- Expecting code to raise a throwable. All three reverted. Full library suite afterwards: Tests run: 2288, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS <version> untouched at 1.6.0. Not pushed — Task 9 lands DDL first.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing