Repository navigation
Description
Amended 2026-10-04 (#857 E2, PR #1060): both seeders use the non-Production Access read port; simulation creation uses Access operations. At E2 head, 37 rows remain for #858 (29 simulation, 8 demo), including five new Access rows for P5/P6. See this comment.
Amended 2026-10-03 (approved plan): eight PRs, P1 to P8; scope item 5 drops
IUnitOfWorkand now covers the generic repository and legacy registrations; seeders move onto per-module, non-Production fixture ports. See this comment.
Amended 2026-10-03 (#854, PR #1030): both seeders call
ICommerceModuleinstead of the Commerce handlers; eight Commerce seeder reads remain as #850 rows. See this comment.
Amended 2026-10-02 (#853, PR #1028): the lock sweep and both seeders call
IEggOperationsModule; five Egg Operations seeder reads remain as #850 rows. See this comment.
Amended 2026-10-02 (#852, PR #1021): both seeders call
IFlockModule/IFlockLookupinstead of the flock handlers; five Flock Management reads remain as #850 rows. See this comment.
Important
Track C is unscheduled. These slices move production code, and the 2026-08 design's own status
line still governs: "proposed architecture; no production refactor is authorized by this
document." Filed so the analysis is tracked work rather than a comment, not as a commitment to
run it.
The 2026-09-14 re-plan
recommends gating Track C on evidence from Tracks A and B: if the #842 ratchet fires repeatedly in
real pull requests, these become worth their cost; if it stays quiet, they do not. Track C is
57–95 focused engineering days and produces no user-visible change.
Read the re-plan before picking any of these up. Several assumptions in the 2026-08 plan are
corrected there.
Platform composition, jobs, CLI verbs and seeders. The slice that actually makes adding a new module
cheap — worth knowing if only part of Track C is ever run.
Why this one is different from the eight before it
Slices 7–15 clean up existing modules. This one fixes the files that get worse with every module
added, whether or not the existing ones are ever extracted:
| File | Size today | Why it degrades per module |
|---|---|---|
CluckworkFeatureServiceCollectionExtensions.cs |
352 lines | every handler, validator and repository registered by hand |
Program.cs |
577 lines | composition root |
SimulationDataSeeder.cs |
2608 lines | must know every module's datasets |
BusinessRecordModel.cs:21-34 |
11 types, 6 modules | #819 says it grows with every time-paged table |
If a new domain is ever added — raising, processing, anything — this is the work that decides whether
that is pleasant or painful. The BusinessRecordModel item in particular is independent of the
rest of Track C and could be done standalone at any time.
Scope
- Move idempotency, transaction, tenant, persistence and health registration into explicit Platform
composition. Preserve middleware ordering exactly (refactor(api): make process role explicit for boot guards #347, Credential epoch: per-request revocation check, deployed inert ahead of the user-admin mutations #364). - Daily lock-sweep and token-purge jobs call contracts; durable worker and single-leader behaviour
unchanged (Background worker has no single-runner guarantee — double-runs if scaled >1 instance #271). - Convert demo and simulation seeders to module bootstrap contracts, one dataset at a time.
Preserve the durable anchor, the exact counts and the fail-closed validation (Extract DB seeding (Demo/Simulation) out of API startup into an explicit command entry point #280, fix(seed): seeded audit events carry "(unresolved)" as the actor, now visible on record History columns #500). - CLI verbs call contracts.
migratestays a direct Platform migration operation (Deploy: separate the migration/owner DB role from the runtime app role (no DDL at request-time) #263). - Delete the generic repository, unit of work and legacy feature registrations once a search proves
no caller remains. - Tighten the ledger's declared surface and record the final graph.
Done when
Full solution green; frontend typecheck, tests and build green; tools/simulation/verify-harness.sh
passes; Playwright quick suite green. Simulation manifests should need no configuration change,
because no boot guard or config key changes — if one does, every harness file updates in the same
commit (#370), and the AppHost too (#565).
- Change map in the PR (owner, 2026-10-01). The PR carries a before/after diagram of who calls whom, in the shape of the refactor(finance): put Finance behind an IFinanceModule contract #1010 and refactor(insights): read reports, exports and audit provenance through an Insights facade #1012 change maps: the adapters and module types before and after, any ledger edges added or removed, and the end state of any file another open slice also changes. Put it in the PR body or a PR comment as a fenced text block.
Explicitly not in this slice
The idempotency protocol itself. If MCP's #804 is ever built it extracts that, and this slice
consumes the result rather than writing a second copy — writing a second copy is how #307 gets
reopened.
Activity
[-]#514 slice 16: Platform composition, jobs, CLI and seeder conversion[/-][+][C] #514 slice 16: Platform composition, jobs, CLI and seeder conversion[/+]mforce commentedon Sep 26, 2026
Audit 2026-09-26, against
mainat e47288cBody left as written.
Refreshed numbers.
Hosting/CluckworkFeatureServiceCollectionExtensions.csProgram.csSimulationDataSeeder.csCorrection: that growth is not evidence for this slice.
Program.csmoved for CSP nonce plumbing (#874) and a rate-limit test window (#895). The seeder moved for a fixture hen-day fix (#959). None of it is composition-root creep or new module registration. The argument for this slice is what a new domain would cost, not observed drift over the last twelve days, and the issue should say so rather than lean on line counts that grew for unrelated reasons.The
BusinessRecordModelitem is carved out. It is genuinely independent, needs no contracts, no dependency-injection changes and no seam work, so it is now its own issue and can ship at any time. The file is 153 lines; the work is splittingChronologicalListTypesandMappedExclusionsinto per-module contributions merged beforeValidateCensusruns, keeping the walk and the validation centralized. Remove it from this slice's scope and point at the new issue.mforce commentedon Oct 2, 2026
Amendment from #850 (PR #1013)
What shipped.
module-ledger.jsonnow has acompatibilityExceptionssection.CompatibilityExceptionRealTreeTestsfails CI on any read of a contracted module's tables that is neither allowed by structure nor registered. Three rows name this issue as their deletion trigger, all with ownerPlatform:Cluckwork.Infrastructure.Persistence.SimulationDataSeeder.EnsureExpenseCategoryAsynclooks updb.ExpenseCategoriesby name;Cluckwork.Infrastructure.Persistence.SimulationDataSeeder.EnsureExpenseAsyncchecksdb.Expensesfor the fixture description;Cluckwork.Infrastructure.Persistence.SimulationDataSeeder.ComputeCountsAsynccounts both tables for the manifest check.What this issue now owns. Converting those three reads deletes the rows. The guard reports a row as stale once its read is gone, so the slice that converts the seeder must delete the rows in the same PR.
IFinanceModuledeliberately has no count or existence read yet; #850 did not widen it. The rule is indocs/decisions/850-compatibility-exceptions.md.test(arch): register Farm's compatibility exceptions after its contract
mforce commentedon Oct 2, 2026
Amendment from #852 (PR #1021). That PR ships part of this issue's scope. The body above is left as written.
Shipped:
DemoDataSeederandSimulationDataSeederno longer inject the flock lifecycle handlers orIFlockRepository. They callIFlockModuleandIFlockLookup.SimulationDataSeeder.EnsureFlockAsyncresolves by name and fails on a duplicate name instead of taking the first match.Remains: five Flock Management reads are registered as #850 compatibility exceptions with
deleteWhen: #858. They areDemoDataSeeder.CleanupPartialSeedAsync,.SeedAsyncand.SeedDemoAsync, andSimulationDataSeeder.ComputeCountsAsyncand.SeedExplicitBirdMovementsAsync.mforce commentedon Oct 2, 2026
Amendment from #855 (PR #1027)
What changes for this slice once #1027 merges. General Inventory is contracted (
IInventoryModule), so seven moreSimulationDataSeederreads are registered compatibility exceptions withdeleteWhen#858, owner Platform,reaches: GeneralInventory:ComputeCountsAsync(manifest counts of all five Inventory tables)EnsureAdjustmentAsync,SeedFeedAdjustmentsAsync(InventoryMovementsexistence and count)EnsureInventoryItemAsync(InventoryItemsby name)EnsureOpeningPurchaseAsync(InventoryLotsexistence and id)SeedFeedUsageAsync(FeedUsagesexistence)SeedWaterUsageAsync(WaterUsagesexistence)IInventoryModulehas no count, existence or by-name reads, following #850's choice for Finance: the contract does not grow reads only the seeder needs. The seeder's writes already go throughIInventoryModule. Its one formerIInventoryItemRepository.HasLotsAsynccall is now the identicaldb.InventoryLots.AnyAsyncinsideEnsureOpeningPurchaseAsync, covered by that method's row.refactor(inventory): put general inventory behind an IInventoryModule…
mforce commentedon Oct 2, 2026
Amendment from #853 (PR #1028). The body above is left as written.
Shipped:
DailyEntryLockSweeplocks each account's due entries throughIEggOperationsModule.LockSubmittedEntriesAsync. The lock loop moved, unchanged, intoLockDueDailyEntriesHandler. Scope item 2's lock-sweep half is done, apart fromDailyEntryLockSweep.RunAsync's cross-farmdb.Accountsread, which stays an existing [C] #514 slice 8: close or date every compatibility exception #850 row.DemoDataSeederandSimulationDataSeedercallIEggOperationsModuleinstead of the daily-entry handlers and the grade and daily-entry repositories.Remains here. Five new #850
compatibilityExceptionsrows, all owner Platform withdeleteWhen#858:DemoDataSeeder.CleanupPartialSeedAsync, which deletes a partial demo seed'sDailyEntries,DailyEntryGrades,EggInventoryMovementsandEggLots;DemoDataSeeder.MissingBaseDataAsync, which readsEggGrades;SimulationDataSeeder.ComputeCountsAsync, which countsDailyEntriesandEggLots;SimulationDataSeeder.MissingBaseDataAsync, which readsEggGrades;SimulationDataSeeder.SeedFlockHistoryAsync, the natural-key existence check. It becamedb.DailyEntries.AnyAsyncwith the same predicate, so the contract does not grow a read only the seeder needs.mforce commentedon Oct 3, 2026
Amendment from #854 (PR #1030). The body above is left as written.
Shipped:
DemoDataSeederandSimulationDataSeedercallICommerceModuleinstead of the six Commerce handlers (create product, customer and order, add a line, confirm, record a payment).Remains here. Eight new #850
compatibilityExceptionsrows, all owner Platform withdeleteWhen#858:DemoDataSeeder.CleanupPartialSeedAsync, which deletes a partial demo seed'sSalesOrderItems,SalesOrdersandCustomers;SimulationDataSeeder.ComputeCountsAsync, which countsCustomers,SalesOrdersby status andPayments;SimulationDataSeeder.EnsureConfirmedOrderAsync, which reads the order's status before confirming;SimulationDataSeeder.EnsureCustomerAsyncandEnsureProductAsync, the lookups by name;SimulationDataSeeder.EnsureExtraLineAsync, which checksSalesOrderItemsfor the product's line;SimulationDataSeeder.EnsurePartialPaymentAsync, which checksPaymentsand reads the order total;SimulationDataSeeder.FindOrderAsync, the (customer, order date) natural key.The contract did not grow reads only a seeder needs (#850, #853, #855).
Rationale:
docs/decisions/854-commerce-contract.md, which lands with PR #1030.mforce commentedon Oct 3, 2026
Approved plan, 2026-10-03
The owner approved the #858 design checkpoint together with every change from the independent review. The body above is left as written.
Scope amendment
Scope item 5 now reads "Delete the generic repository and legacy registrations once a search proves no caller remains."
IUnitOfWorkstays. 49 handlers andExportEndpointscall it, and it is already the Platform transaction port that joins the request's ambient transaction without replaying the caller's work. The 2026-08 design called itIModuleTransaction; renaming it adds no protection.Decisions
Hosting/Modules/, plus a Platform file. Access keepsAddCluckworkIdentity. The sevenICurrencyBoundRowSourceregistrations stay one ordered block.CreateUserAsyncstays with E'sIAccessOperations.PR list
IInsightsModule,EntityProvenance,ExportDataset)IRepository<T,TId>and the repository members nothing calls; keepIFlockRepository.Updateand the live assignment and logoRemoveIFarmDirectoryfor the cross-farmAccountsreads inlist-accounts, the farm-code lookup and the lock sweep (3 rows)compatibilityExceptions, deliberateLoosenablepruning, decision record,src/AGENTS.md. Also delete theAccountSlugLookup.ResolveAsyncforwarder, inliningGetRequiredService<IFarmDirectory>().FindIdBySlugAsyncinto the four verbs (#1043 review, Opus F1), once #857 D has moved themThe 35 rows naming this issue split 25 simulation seeder, 7 demo seeder, 2 CLI and 1 lock sweep. Recount after #857 E; the total is not a finish-line test.
Every PR carries its own change map, mutation table and deslop record, and this issue's full "Done when" gates apply.
Edited 2026-10-03: P8 gains the
AccountSlugLookup.ResolveAsyncforwarder deletion from the #1043 review.Handover for P4 onward (2026-10-03)
P1 (#1040) and P2 (#1042) are merged; P3 (#1043) is in review. After P3, 32 rows name this issue: 25 in the simulation seeder and 7 in the demo seeder. Recount at #857 E's head.
Notes for the next worker:
FarmDirectoryCallerTests' allowlist in P5 (seeders) and P7 (Hosting/Modules/).ModuleLedgerScanner.ParseOptions.The full notes are in the worker's design file. Open follow-ups from the reviews, not scheduled:
EggUnitConversionRepository,ProductEggGradeMapping);FarmDirectorytype;IAccountRepository.FindBySlugAsynccross-farm seam read;srcwithout preprocessor symbols (listed on refactor(accounts): move the cross-farm account reads behind IFarmDirectory #1043).test(repositories): check that an append-only ledger still has no Upd…
test(accounts): parse the farm-directory caller guard with the build'…
mforce commentedon Oct 4, 2026
Amendment from #857 E2 (PR #1060)
PR #1060 implements the Access precondition for the approved P5/P6 plan. Both seeders read real actor DTOs through
IAccessSeedLookup, registered only outside Production. Simulation alone creates cast users throughIAccessOperations.CreateUserAsync.CurrentUserContextandSimulationOptionsmove from Identity to Platform's Persistence namespace with their bodies unchanged; the scopedICurrentUseralias remains the same actor instance. No fixture configuration key changes.Remaining here: at E2 head
3731894a, 37 compatibility rows name #858: 29 simulation and 8 demo. The five new Access rows are:MissingBaseDataAsync:AspNetRolesprerequisite (P6).MissingBaseDataAsync:AspNetRolesprerequisite (P5).FindOwnerAsync:AspNetRolesprerequisite (P5).ComputeCountsAsync:AspNetUserstotal count (P5).RestrictOneWorkerAsync:UserRoleAssignmentsread/write (P5).The last write deliberately stays in composition for P5's non-Production Access fixture operation and security review. It retains the original ordered, untracked, tenant-filtered read; entity factory; actual Owner actor and flock-name audit; and one save after that audit. An injected audit failure proves that no assignment or completion marker commits separately. The repository's filtered Flocks LEFT JOIN instead expires at #859.
Parity evidence: 54/54 cases pass on baseline and E2 across every requested seeder sibling class. Demo's exact 977 action/actor rows and simulation's exact cast, six products, 453 action/actor rows, worker rotation, manifest, anchor, completion and fingerprint match. Full SQL differs only by one explicit account-scoped Owner reread in each profile. E2 does not implement the rest of P4-P8 or claim #858's full harness/Playwright acceptance gates. Those remain with this issue. PR #1060 is open for CI and independent Astra plus Opus security review; this amendment records its implemented scope without claiming it has merged.
test(hosting): discover serving guards below the Hosting namespace
docs(arch): list only out-of-namespace fixture implementations