- Shipped
- 13 Agosti 2026, 02:33 UTC
- Author
- Kamo
- Commit
- c9f9a8f
Four nullable columns and a second write-once transition on TimecardPunch. A void says the punch should never have existed — a double tap on the clock, an abandoned break, or the seam between two spans the timecard editor has just merged into one. That is neither a correction ("this happened, at a different time", which leaves a replacement row) nor a delete: supersededByPunchId means "read this other row instead", and here there is no other row. The row survives, voided, with its author and its reason beside it. A wage record that can be made to forget a punch is exactly the inadequacy that shifts the burden to the employer under Anderson v. Mt. Clemens Pottery, so the only thing a void removes is the punch's effect on the calculation. enforceWorm() permits voidedAt on the same terms as supersededByPunchId — null to a value, once — and refuses a void with no reason. Un-voiding in place is deliberately impossible: re-instating a punch means inserting one, which leaves its own row and its own explanation. **************** is not a convenience. The unfiltered variant is wrong for deciding clock state: a superseded row keeps its ORIGINAL instant, so a clock-out corrected backwards leaves the dead row latest by occurredAtUtc, and a voided row stays latest until something newer is punched. Either one answers "are you on the clock?" from a record that no longer exists. Sequenced per the entity-change rule: shared-lib installed locally, KI migration written and RUN against 192.168.4.22:5433 with all four ALTERs applied, and only then is this pushed — so no service can build against an unmigrated schema.