Repository navigation
feat(data): standardize business record chronology - #820
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai full review |
|
Final load and migration rehearsalI discarded the first measurements because another local test was contending k6:
|
| Capacity median | main f769681 |
PR eabae19 |
Delta |
|---|---|---|---|
| requests/s | 10.930 | 10.947 | +0.15% |
| p50 | 4.03 ms | 4.20 ms | +4.2% |
| p95 | 11.13 ms | 11.36 ms | +2.0% |
| p99 | 65.08 ms | 73.96 ms | +13.6% |
| iterations | 643 | 637 | -0.9% |
| app CPU median | 10.3% | 10.2% | -0.1 pp |
| app memory median | 230 MiB | 249 MiB | +19 MiB |
All 6 reps passed: 100% checks, zero unexpected statuses, all five personas
and all ten distinct users covered. The uncapped co-located harness is a
relative shakeout, not a production sizing result. The throughput and p50/p95
changes are within the observed local spread; there is no demonstrated
capacity regression. PR database size starts about 0.5 MiB higher from the
new columns/indexes; per-run growth remained 0.3-0.4 MiB on both commits.
In-place upgrade rehearsal
I then created main's 16-migration schema, seeded its full 90 days of
activity, and applied the PR migration to that same database.
- Exactly one migration applied; DDL took about 0.45s (3.23s including the
one-shot container startup). - Exact row counts for every public table were unchanged.
- The old serving binary remained ready (
/health/ready200) after migration. - 17 migrations present; zero invalid constraints; zero invalid/unready
indexes. - Exactly 26
CreatedAtUtccolumns/triggers, 21UpdatedAtUtccolumns, and
12GENERATED ALWAYSsequence identities. - Every audit-derived creation/update backfill matched its account-scoped
expected audit event across the seeded data. - Every one of the 11 legacy sequence backfills matched its specified ranking;
no nulls, duplicates, or lagging generators. - A real trigger update advanced
UpdatedAtUtc(then rolled back). - The PR binary started on the upgraded database and passed the five-persona
smoke: readiness 200, 243/243 checks, zero unexpected statuses.
|
Before and after for the user-visible same-day ordering change. Both captures use a freshly built image at 1280x800 and the exact same upgraded 90-day simulation database. Main orders the five 09/13/2026 sales by random GUID; the PR orders them by creation chronology with Sequence as the final tie-breaker. |
## What this is Measurement for #839, plus one small scheduling change. **Read the numbers before the title's verb** — this does not make the suite faster. It stops the suite sometimes being much slower than it needs to be. `docs/plans/839-integration-wall-clock/measurement.md` is the deliverable. The code is two files. ## The measurement Local, Ryzen 5 6600H / 12 logical CPUs / 18.8 GiB, cached images, Docker 29.8.0, .NET SDK 10.0.112. | Run | Config | Wall | Tests | | --- | --- | ---: | --- | | `main` (`18b45dc`) | Debug | 320s | 1,801 passed | | this branch | Debug | 326s | 1,815 passed | | this branch | Release | 341s | 1,815 passed | Full-suite counts differ because the branch is rebased past #820 and #844. **What the ~5.5 minutes is made of** (instrumented run, 331.5s wall): - **Container readiness: 156.6 distinct wall-clock seconds**, overlapping test execution. 119 Postgres + 7 Redis + 1 Ryuk. - **The shared `integration` collection: 1,024 of 1,815 tests**, one fixture, running serially end to end. - **Three one-shot-process classes dominate**: `SeedCommandTests` 131.14s, `ProcessRoleGuardTests` 127.26s, `OneShotVerbMinimalConfigTests` 119.05s. Repeated app process startup, not SQL. ## Two premises in the issue did not survive 1. **Container reuse is already done.** Lever 1 assumed per-class startup was the dominant cost. The shared collection already holds one `ICollectionFixture<CluckworkWebApplicationFactory>` covering 1,024 tests. The remaining 119 Postgres containers are the specialized factories that genuinely need their own database, migration state, or advisory-lock behaviour. 2. **The remaining cost is subprocess, not SQL.** The issue lists container reuse and parallelism. The measured cost is repeated one-shot process startup in three classes, which the issue never names. ## The change `IntegrationCollectionOrderer` puts the shared collection first and delegates every other collection to xUnit's default orderer. Fixture ownership, concurrency limits, serialization, and the race assertions are untouched — `StealLossConnectionReleaseTests` keeps its dedicated factory, one-slot pool, and timing assertions. Why it is **variance, not speedup**: xUnit 2.9.3 documents its default collection order as unstable between runs. When the 95-class serialized collection draws a late slot, its ~300s of serial work becomes a tail nothing else can overlap. Pinning it first removes that schedule. It makes no test cheaper. The first draft of this PR claimed 25.8% (434.51s → 322.46s). That compared against a single slow run. A second run of the **unmodified** scheduler came in at 327.06s, and the paired runs above put both versions inside each other's spread. The correction is in the measurement doc, not quietly dropped. ## Also in here - `tools/test-timing/` — `measure.py` captures wall clock, TRX, and Docker events; `summarize.py` reports per-class cost, container readiness, and fixture phases. Opt-in timing in the base factory behind `CLUCKWORK_TEST_TIMING=1`. - Two guard collisions found while building the tooling, both fixed in the tooling, **neither guard weakened** — worth knowing because they will bite the next person who adds a file under `tools/`: - the `#508` tracked-file image-pin guard read a Python dict key as a live image reference (fixed by naming the variable `reference`); - the same guard's bare-`postgres:` literal detector fired on a timing phase *label* (fixed by naming the phase `container`). ## Verification - Solution build: 0 warnings, 0 errors. - Integration suite on this branch: **1,815/1,815 passed** in Debug and Release. - Domain 491/491, Application 290/290. - `tools/test-timing/measure.py` driven end to end on the branch as it now stands: exit 0, 1,815 passed, summary regenerated. - CI is **not** measured. Local results do not establish a CI improvement or an optimal worker count on hosted runners. ## Not done No test deletion, no SQLite, no higher parallelism, no product change. Remaining levers, in the order the data now suggests: the three subprocess classes; splitting the shared collection after auditing its shared-state assumptions; CI-side measurement. Closes #839
🤖 I have created a release *beep* *boop* --- ## [0.1.2](v0.1.1...v0.1.2) (2026-09-16) ### Features * **data:** standardize business record chronology ([#820](#820)) ([6231b31](6231b31)) * **infra:** optional leader-lease endpoint for pooled deploys ([#869](#869)) ([e9bc6a7](e9bc6a7)) * **sim:** seed a second farm for the README dashboard capture ([#867](#867)) ([de407c6](de407c6)) * **web:** adopt MUI, themed from the farm palette tokens ([#674](#674)) ([#860](#860)) ([6c83c5c](6c83c5c)) * **web:** convert Daily entry to MUI, field-first on the phone ([#888](#888)) ([b66f8b8](b66f8b8)) * **web:** convert the Dashboard and app shell to MUI ([#829](#829)) ([#883](#883)) ([2e94277](2e94277)) * **web:** retire the Slack-blue link colour for ink + a rule underline ([#884](#884)) ([c08f9d8](c08f9d8)) * **web:** serve a per-request CSP nonce so Emotion's styles apply under style-src 'self' ([#874](#874)) ([ba4e6f3](ba4e6f3)) * **web:** visual language theme overrides for the MUI revamp ([#864](#864)) ([#882](#882)) ([0bb6b73](0bb6b73)) * **web:** whole-app MUI baseline, theme policy guard and the [#740](#740) phone action rule ([#823](#823)) ([#871](#871)) ([af565e4](af565e4)) ### Bug fixes * **auth:** fail closed on unresolved flock-scope actors ([#787](#787)) ([#868](#868)) ([16d0350](16d0350)) * **auth:** make farm configuration owner-only ([#870](#870)) ([42f9036](42f9036)) * **e2e:** repoint the canary at the markup two PRs replaced ([#844](#844)) ([18b45dc](18b45dc)) * **i18n:** tl glossary uses the standard passive of ilagay ([#813](#813)) ([20dec10](20dec10)), closes [#738](#738) * **sim:** stop the k6-baseline EXIT trap masking a clean run as failed ([#838](#838)) ([f5ec96f](f5ec96f)) * **web:** declare the rule tokens the Dashboard reads, and guard undeclared custom properties ([#885](#885)) ([5bead1f](5bead1f)) ### Performance * **ci:** start the serialized integration collection first ([#861](#861)) ([1dcc7f6](1dcc7f6)), closes [#839](#839) ### Documentation * **auth:** record the OAuth 2.1 decision for MCP authentication ([#801](#801)) ([0510854](0510854)) * **designs:** MUI revamp design doc, component map, layout system, IA ([#862](#862)) ([da49481](da49481)) * **readme:** recapture the daily entry, reports and sales screenshots ([#865](#865)) ([f18e336](f18e336)) * **specs:** correct the sales_order_items column list in §10.5 ([#812](#812)) ([afe4a02](afe4a02)), closes [#737](#737) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: cluckwork-lockfix[bot] <309265648+cluckwork-lockfix[bot]@users.noreply.github.com>


What changed
Fixes #819.
This replaces random-UUID tie-breaking with one business-record chronology
policy:
DateOnly.ICreatedRecord; mutable records alsoimplement
IMutableRecord.CreatedAtUtcandUpdatedAtUtcfor every writepath. Mutable inserts receive the same value for both fields.
Sequence.direction is preserved, including the payments settlement history's
intentional oldest-first order.
sequence. Matching exports use the same complete tuple.
timestamp policy.
The change uses composable interfaces rather than base classes because the
entities already have domain and Identity inheritance.
Migration behavior
AddBusinessRecordChronology:audit events where available, including
User.RoleChanged;1970-01-01T00:00:00Zunknown-time sentinel otherwise;The migration runs atomically through the pre-deploy job. The lock timeout
limits acquisition only; production-volume duration must still be measured.
Legacy sequences are deterministic but cannot recover historical chronology.
Generic timestamps and
Sequenceremain persistence-only. Existing API andCSV contracts, FIFO allocation, and
FOR UPDATElock ordering are unchanged.Independent review and remediation
Claude Opus, Fable, and Sonnet independently reviewed the change. Their two
blocking findings are fixed:
Sequenceis removedfrom the ordering tuple.
The same pass replaced recalled type lists with interface-driven model
discovery, centralized chronology ordering, added
AccountIdto auditbackfills, included
User.RoleChanged, split migration SQL statements, andremoved duplicated Identity timestamp snapshot bookkeeping.
Two limits are deliberate and documented. A database clock rollback can
defeat the
CreatedAtUtcmiddle key. Offset pagination is not a snapshot whenconcurrent inserts occur.
Sequenceprovides the unique final key.Load and upgrade rehearsal
The first local measurements were discarded because another test contended
for the host. The clean comparison used k6 v2.1.0, the same generated 90-day
fixture, and three fresh-database reps per commit. Each rep used 2 warm-up VUs
for 20 seconds, then all 10 cast users for 2 minutes.
Median throughput changed from 10.930 to 10.947 requests/s (+0.15%). Median
p50 changed from 4.03 to 4.20 ms (+4.2%), and p95 changed from 11.13 to 11.36
ms (+2.0%). Median p99 changed from 65.08 to 73.96 ms (+13.6%). App memory
changed from 230 to 249 MiB. All six reps had 100% checks, zero unexpected
statuses, and complete persona/user coverage. This uncapped local run shows no
throughput or p50/p95 regression outside host noise; it is not a production
sizing result. Detailed k6 evidence.
The upgrade rehearsal seeded
main's full 90 days of activity, then appliedthe PR migration in place. The migration changed no table row counts and took
about 0.45 seconds, or 3.23 seconds with container startup. The old binary
remained ready after the migration. The upgraded database had zero invalid
constraints or indexes, and every timestamp backfill, sequence ranking,
trigger, and generator check passed. The PR binary then returned readiness
200 and passed all 243 five-persona smoke checks.
Review order
docs/decisions/819-business-record-chronology.mdBusinessRecordModel.cs,ICreatedRecord.cs, andIMutableRecord.cs20260913212515_AddBusinessRecordChronology.csBusinessRecordOrdering.csand repository/export consumersBusinessRecord*Tests.csandListChronologyTests.csGenerated migration metadata and
docs/schema/account for most of the diff.Verification
GitGuardian, and the image/Trivy scan.
git diff --check: passed.Review status
The PR has no GitHub approval yet. CodeRabbit skipped the review because the
diff has 105 files, above its 100-file limit. The repository-required
before/after screenshot of the same-day ordering change is not attached yet.