Repository navigation
Description
Track C of #514, carved out of #858 during the 2026-09-26 audit. Enforcement and structure only, no behaviour change. No prerequisites: this can ship at any time, independently of every other Track C slice.
The problem
src/Cluckwork.Infrastructure/Persistence/BusinessRecordModel.cs is 153 lines holding two hand-maintained arrays:
ChronologicalListTypes(:21-34), the census of business records that are paged or read by timeMappedExclusions(:36-48), the deliberate exemptions
ValidateCensus (:84-111) walks the EF model against them. #819 requires every new mapped table to be classified through one of these, so every module must edit one shared file for every new time-paged table. The list already spans six modules, and it grew again in #912 when SalesOrderItem was added.
This is the one item in #858 that degrades with every feature regardless of whether anything ever goes behind a contract, and it is the only part of Track C with nothing in front of it.
Scope
- Each module contributes its own chronological types and its own exclusions, declared beside that module's code rather than in one shared array.
- The contributions are merged before
ValidateCensusruns. The walk and the validation stay centralized and unchanged. - No dependency-injection changes, no contract types, no seam work, no schema change, no migration.
Done when
| Mutation | Expected |
|---|---|
| add a mapped, time-paged table to a module and contribute nothing | red, naming the table and the module that owns it |
| contribute a type that is not mapped | red |
| contribute the same type from two modules | red, duplicate contribution |
| delete a module's contribution while its tables remain | red |
| move a type's contribution between modules with no other change | green |
ValidateCensus's existing assertions pass unedited, and the EF model digest is identical before and after.
Explicitly not in this slice
No module goes behind a contract. No ICreatedRecord / IMutableRecord classification changes. AuditEvent stays excluded per #819. The other items in #858 are untouched.
Activity
mforce commentedon Sep 26, 2026
Correction to the "Done when" table, 2026-09-26
The first row of this issue's mutation table asks for something the mechanism cannot do, and that is my error in writing the issue, not a defect in the implementation.
The row reads:
| add a mapped, time-paged table to a module and contribute nothing | red, naming the table and the module that owns it |
Review of PR #970 tested exactly that. It added a mapped
TimePagedProbeRecord : IMutableRecordthrough a realIEntityTypeConfiguration<>, omitted it from every contribution, and both model tests passed.That is a pre-existing limit, not a regression.
ValidateCensusonorigin/main, before this slice, asserts only four things: chronological lists are a subset of timestamped records, timestamped and excluded do not overlap, every exclusion is mapped, and no mapped type is unclassified. None of those can discover that a table is read by time. Whether users page a list by time is a fact about a query, not about the EF model, so a model walk cannot infer it. The omitted probe is classified as a timestamped record and therefore passes, exactly as it would have before the split.What the mechanism does catch, all verified red by both the implementer and the reviewer: a mapped type with no timestamp policy at all, an exclusion naming an unmapped type, the same type contributed by two modules, the same type contributed twice by one module, and a module's contribution deleted while its registration is still referenced. Moving a type between modules with no other change stays green.
The row should read:
| add a mapped table to a module with no timestamp policy and no contribution | red, naming the type |
and the table should carry a line saying the guard cannot detect an omitted chronological contribution, because chronological membership is policy rather than structure.
The real gap is worth its own decision. Closing it needs an independent, fail-closed signal of chronological use that the walk can compare against contributions, for example a marker on the query or list that reads the table. That is new mechanism rather than a reorganisation, so it does not belong in this slice. Recorded here so it is not lost.
docs(persistence): state chronological census limit (#969)