Repository navigation
Description
Goal
Group the code by module, so each module's files live together and its public contract sits in a clearly named folder. The projects stay as they are (Domain, Application, Infrastructure and Api); this is not an assembly split (#859).
Today code is grouped by feature (Features/Customers, Features/Sales, …), and the module ledger maps scattered folders onto modules. Example: src/Cluckwork.Application/Features/Customers/UpdateCustomer/UpdateCustomerCommand.cs belongs to Commerce, but nothing in its location says so.
Target shape (exact names to be settled by the design):
src/Cluckwork.Application/Modules/Commerce/
Contracts/ ← what other modules may use
Customers/UpdateCustomer/...
Sales/ConfirmSale/...
src/Cluckwork.Domain/Modules/Commerce/...
src/Cluckwork.Infrastructure/Modules/Commerce/...
Namespaces follow folders. Platform and shared code get their own place.
Why
Package-by-module is standard modular-monolith practice. It should also simplify the rules:
- the module could be read from the folder, so most of the owner map goes away;
- anything in
Contracts/could be the contract, which may replace the 160[ModuleContract]marks added in refactor(arch): put module rules beside the code they describe #1086.
The edges between modules, with their reasons, stay declared.
Done when
- Every module's code in Domain, Application, Infrastructure and Api endpoints lives under its module folder, and its contract lives under
Contracts/. - Each move is a pure move with no behaviour change, and each one proves it:
- the build is clean;
- the EF model is unchanged and no migration is added (feat(eggs): make cracked and dirty eggs sellable stock via condition grades (#396) #407);
- schema docs are regenerated (chore(schema): generate PostgreSQL schema documentation #417);
- the DI registrations are identical;
- the architecture guards still pass, with mutations still caught;
- the tenant-bypass registry is re-keyed in one reviewable commit. The 40 allow-list hashes stay unchanged. Any filter-free identity whose hash changes, such as the 6 in
IdentityProviderthat spell a namespace in full, is re-keyed with a proof that only the namespace text changed (Replace TenantBypass line-number anchors with stable query identities #632).
- The module map, the
[ModuleContract]marks and the<Owner>ModuleRulesfiles are simplified where the folders make them redundant. - AGENTS.md, src/AGENTS.md and the decision records are updated.
- A final slice adds an architecture diagram of the reorganized solution to
docs/architecture.md: the four projects, the module folders, the Platform hub and the declared module-to-module edges. The module diagram is generated from the module rules, like the coupling matrix ([B] #514 slice 6: regenerate the coupling matrix from the ledger, and delete the hand-written one #848), so a test fails when it drifts from the code. - The work ships one module per PR, smallest first.
A design is in progress and will be linked here before implementation starts.
Activity
mforce commentedon Oct 5, 2026
Design approved by the owner on 2026-10-05. Astra reviewed it, and its corrections are applied.
Layout. Each project gets
Modules/<Module>/<feature>/…, and namespaces follow folders. Every module's public types live inContracts/: in Application for most, and in Domain for 13 enums that entities use. Handlers and validators stay in the module's feature folders. Endpoints stay in the Api project, grouped asApi/Modules/<Module>/, and are still treated as Platform-owned adapters that may only use contracts.Inventory. 362 of 556 C# files under
src/move, and 194 stay shared. 22 mixed files are split first, which creates 39 new files. A file's placement follows the module rules plus documented placement decisions.Rules after the move
[ModuleContract]marks, which are removed module by module.Contracts/while keeping its namespace.<Owner>ModuleRulesfiles stay, holding fewer rows.Safety, as measured in a spike on
mainIdentityProviderchange hash only because a fully qualified namespace changed. They are re-keyed with a namespace-only proof. The 40 allow-list hashes stay unchanged.service_name="Cluckwork.Api", with no logger-category filters, so changed category names affect nothing there.Slices, one PR each, each with a short merge window against fresh
mainServingGuardCoverageTests, so a moved module can never be skipped silently. The PR also adds the folder agreement test and a trial split.FlockScopeGuard(Access → Farm), plus one new cell, FlockManagement → EggOperations.The estimate is about 54 agent-hours, plus review and CI. It is provisional until S0 and S1 are done.
test(arch): keep repository subfolders in the tracked-read guard
refactor(arch): split the Egg Operations contract types out of their …
refactor(arch): move Egg Operations into Modules/EggOperations
docs(arch): point the daily-entry lock and submit paths at Modules/Eg…
refactor(arch): delete the Egg Operations [ModuleContract] marks
refactor(arch): split the Commerce contract types out of mixed files
refactor(arch): move Commerce into Modules/Commerce
refactor(arch): say a moved module's contract is its Contracts folder
refactor(arch): delete the Commerce [ModuleContract] marks
refactor(arch): split the Platform records out of the two Access-clai…
refactor(arch): move Access into Modules/Access
refactor(arch): read Access's Identity-port ownership from the namespace
refactor(arch): delete the Access [ModuleContract] marks
refactor(arch): split the Farm logo contract records out of IFarmLogo…
refactor(arch): move Farm into Modules/Farm
refactor(arch): say Farm's contract is its Contracts folder
test(tenant): re-key the six IdentityProvider filter-free identities …
refactor(arch): delete the Farm [ModuleContract] marks
refactor(arch): point two Farm references at the moved Account namespace
docs(arch): show the analyzer and AppHost references in the projects …
docs(arch): split the architecture picture into three simple views
mforce commentedon Oct 6, 2026
All slices are merged and
mainis green at 23bb192 (CI run 37407252236). Every module's code now lives underModules/<Module>/, with its public types inContracts/.[ModuleContract],ImplementationsandTypesdeleted; guards narrowed to the module roots; AGENTS.md and decision records updatedEvery move slice proved parity: the EF model was unchanged with no migration, the DI and OpenAPI output was identical, the contract set was identical, and the tenant-bypass hashes were unchanged except for the 6 namespace-only re-keys in #1096. Mutations showed that the guards still catch violations.
Not done here, on purpose:
graphify update .was left out of refactor(arch): finish package-by-module — contracts by folder, no marks or claim lists #1099 at the owner's request.Contracts/is seen only by the contract-set comparison, as recorded in refactor(arch): move Egg Operations into Modules/EggOperations #1093 and refactor(arch): move Commerce into Modules/Commerce #1094. This is a candidate follow-up issue, not filed.docs(arch): replace slice ids and stale claims in the module rules