Skip to content

Standardize business-record timestamps and chronological list ordering #819

Description

@mforce

Problem

Several date-based lists used a random UUID as the final sort key. Rows from
the same business day therefore had a stable but arbitrary order. A full model
walk also found inconsistent creation and update metadata across business
tables.

Final plan

  • Keep farm business dates as DateOnly.
  • Add PostgreSQL-stamped CreatedAtUtc to every business record.
  • Add UpdatedAtUtc only to mutable records, equal to creation time on
    insert and replaced on every update.
  • Use composable ICreatedRecord and IMutableRecord interfaces instead
    of base classes.
  • Keep a shadow bigint GENERATED ALWAYS AS IDENTITY Sequence on lists
    that present or page insertion chronology.
  • Sort using business date, CreatedAtUtc, and Sequence, preserving each
    list's existing direction. Payments intentionally remain oldest-first.
  • Apply the same tuple to matching exports.
  • Add interface-driven model enforcement and a repository rule for new
    tables.

Table classification

Mutable records, with both timestamps:

Accounts, FarmLogos, EggUnitConversions, Products,
ProductEggGradeMappings, DailyEntries, DailyEntryGrades, EggGrades,
EggLots, Expenses, ExpenseCategories, Flocks, InventoryItems,
InventoryLots, WaterUsages, Customers, Payments, SalesOrders,
SalesOrderItems, SalesOrderAllocations, and AspNetUsers.

Create-only records, with CreatedAtUtc only:

UserRoleAssignments, BirdMovements, FeedUsages, InventoryMovements, and
EggInventoryMovements.

Chronological tables, with Sequence:

SalesOrders, Expenses, DailyEntries, EggLots, BirdMovements,
Payments, InventoryLots, FeedUsages, WaterUsages,
InventoryMovements, and EggInventoryMovements.

AuditEvents keeps its existing OccurredAtUtc and Sequence. Identity
support tables and operational tables retain their own lifecycle policies.

Migration and review outcome

The migration reuses account-scoped, action-specific audit events for creation
and update times, including User.RoleChanged. Unknown times use the explicit
1970-01-01T00:00:00Z sentinel; an unrecoverable update time equals creation.
Database triggers own future timestamp stamping across EF, bulk, and raw-SQL
writes. Legacy sequence values are deterministic but not historically exact.

Independent Opus, Fable, and Sonnet reviews found and drove fixes for the
payments direction, the missing Sequence regression guard, the user-role
backfill, recalled model census, duplicated ordering, and migration SQL shape.
The Sequence guard was mutation-tested and fails when the key is removed.

Accepted limits: a database clock rollback can defeat the CreatedAtUtc
middle key, offset pagination is not a concurrent-write snapshot, and the
five-second migration timeout bounds lock acquisition rather than execution.

Implementation: #820

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiAPI/endpoint layerbugSomething isn't workingpriority:tier3Real product weight, real costseverity:p2Defect: user-visible wrong behaviour, no data loss

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions