Skip to content

refactor(arch): put module rules beside the code they describe - #1086

Merged
mforce merged 3 commits into
mainfrom
refactor/module-rules-beside-code
Oct 5, 2026
Merged

mforce merged 3 commits into
mainfrom
refactor/module-rules-beside-code

Conversation

@mforce

@mforce mforce commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Refs #859

Follow-up to #1085. Owner decision (2026-10-05): a module rule lives next to the code it describes, and stays central only when it describes a relationship. Behaviour does not change. The analyzer and every architecture test give the same results as on main.

Change map

Read the commits in order.

Commit What to check
refactor(arch): mark module contract types with [ModuleContract] 160 one-line [ModuleContract("<Owner>")] marks, placed by a codemod. RealModuleLedger collects them from Domain, Application and Infrastructure by reflection. The owner rows lose Contract. A mark naming no owner is a registry error. Inherited = false keeps nested subclasses of a marked record (FlockNameResolution.Found and its siblings) out of the contract. Global using of Cluckwork.Domain.Common.Architecture (Platform) in Domain and Application.
refactor(arch): one rules class per module beside its edges ModuleOwners.cs and ModuleEdges.cs are deleted. Modules/<Owner>.cs holds internal static class <Owner>ModuleRules { } with that owner's [ModuleOwner] and its outgoing [ModuleEdge] cells (text moved verbatim). The analyzer walks Domain's types instead of reading assembly attributes. RealModuleLedger fixes the order: owners by design 3.4 (matrix layout), edges by from, then to (the old order). A cell on another owner's class is a registry error. The CW1001, CW1002 and CW1003 messages and the test's undeclared-edge message name Modules/<from>.cs and print [ModuleEdge(...)].
docs(arch): ... New section in 859-typed-rule-registries.md. src/AGENTS.md paragraphs and one line in 514 updated.

Mechanical noise: the marks (about 100 files, one line each) and the moved row text.

Names. <Owner>ModuleRules avoids the existing facade classes AccessModule, FarmModule and the others. The classes live in Cluckwork.Domain.Common.Architecture, so no new namespace segment is added (#985). A repo-wide grep found no *ModuleRules type before this change.

Not done, by design. The analyzer reads no contract today, on main or here. It reads only namespaces, exact namespaces, claimed types and edges. A removed [ModuleContract] is therefore red only in the tests. An analyzer contract rule would be new behaviour, so it belongs in a separate decision. Contract lists are now ordinal-sorted. The hand order is gone, and no test reads it. No contract type sits in an assembly the tests cannot see.

Proofs

Check Result
Parity: temporary JSON dump test, run on main and on each head Owners, kinds, namespaces, exact namespaces, implementations, seams, claimed types and contract sets are identical. All 22 edges are identical, in order. Checked at commit 1 and at the final head. The dump test is not committed.
Analyzer edges vs ModuleLedgerRealTreeTests' LiveEdges (temporary CW1099 dump, dotnet build src/Cluckwork.Api --no-incremental) 73 and 73, identical
Coupling matrix, CLUCKWORK_REGENERATE_MATRIX=1 no content change
dotnet build Cluckwork.sln clean, analyzers on
Application.Tests count (--list-tests) base 725, head 725
Architecture classes, one at a time 22/22 green, analyzer tests included
Documentation 20/20, ImagePin 2/2 green

Mutations

Each mutation was applied, then restored.

Mutation dotnet build src/Cluckwork.Api Existing test, built with -p:RunAnalyzers=false
[ModuleContract] removed from IFlockLookup green (the analyzer has no contract rule, as on main) PeerContractRealTreeTests and AdapterReachRealTreeTests red: contract bypass
[ModuleContract("Farm")] on IFlockRepository (a Flocks type) green, as on main AdapterReachRealTreeTests and PeerContractRealTreeTests red: not in a namespace 'Farm' owns
[ModuleContract("Fram")] (no such owner) green ModuleLedgerRealTreeTests red: names owner 'Fram', which has no ModuleOwner row
FlockManagement -> Farm cell deleted red, CW1001 naming Modules/FlockManagement.cs ModuleLedgerRealTreeTests red: undeclared edge, same file
UpdateEggGradeHandler added to EggOperations -> Farm red, CW1002 naming Modules/EggOperations.cs ModuleLedgerRealTreeTests red: stale ledger row
FlockManagement -> Farm cell moved onto FarmModuleRules green ModuleLedgerRealTreeTests red: cell sits on the wrong class
Finance -> Farm cell on a class with no [ModuleOwner] not run ModuleLedgerRealTreeTests red: sits on OrphanRules

Not verified

The full test suite was not run (host rule: filtered runs only); CI is the authority. Live IDE squiggles were not observed.

mforce added 3 commits October 5, 2026 16:02
Each of the 160 contract types declares its owner beside its own declaration.
RealModuleLedger collects the marks of Domain, Application and Infrastructure
by reflection, so the owner rows lose their Contract lists. A mark naming no
owner is a registry error. The contract sets are identical to the old lists;
their order is now ordinal, which no test reads.

Refs #859
Each owner's [ModuleOwner] row and its outgoing [ModuleEdge] cells move from
the assembly-level ModuleOwners.cs and ModuleEdges.cs onto one
internal static class <Owner>ModuleRules in
src/Cluckwork.Domain/Common/Architecture/Modules/<Owner>.cs. The analyzer finds
the classes by walking Domain's types; RealModuleLedger reads them by
reflection and fixes the order (owners by design 3.4, edges by from, to), so
every row and the coupling matrix are unchanged. A cell on another owner's
class is a registry error. CW1001, CW1002 and the test's undeclared-edge
message name Modules/<from>.cs and print [ModuleEdge(...)].

Refs #859
@mforce

mforce commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

No P0–P3 findings at 11cd28a1be0704f51992a8231323ef492ef6dd3a, reviewed against main at ebf8200765c6f7e44b90ca083fb2b6798b68a9ee. The slice preserves behavior and the existing tests' authority.

I independently compiled the base commit's ModuleOwners.cs and ModuleEdges.cs rows in a temporary test and compared them with head's reflection output. This did not rely on the implementer's saved dumps.

Check Result
Owner parity All nine owners match, including kinds, namespaces, exact namespaces, implementations, seams, claimed types and contract sets.
Edge parity All 22 cells match in order, including reasons and symbol order.
Contract marks Exactly 160 explicit marks; every owner/type pair matches the base lists, with none missing or extra. Nested record subclasses do not inherit the mark.
Ordering The nine owners have unique fixed ranks; valid edge pairs and contracts sort ordinally. Contract consumers use membership or their own sorting. Matrix regeneration produces zero diff.
Analyzer parity A fresh diagnostic dump and the real-tree scanner produce identical sets of 73 edges.
Shadowing and behavior No new namespace segment; all rule classes use the existing architecture namespace. Outside the architecture mechanism, changes are marks, global usings and comments. No executable product changes.

Reproduced mutations, each restored:

Mutation Analyzer build Existing tests with analyzers disabled
Remove IFlockLookup's mark Passes Peer and adapter contract guards fail with contract bypasses.
Delete FlockManagement → Farm CW1001 Ledger guard fails with the undeclared edge.
Move that edge onto FarmModuleRules Passes Ledger guard reports the wrong rules class.
Move Finance → Farm onto ownerless OrphanRules Not run Ledger guard reports the ownerless class. This isolated probe introduces no duplicate cell.
Add UpdateEggGradeHandler to EggOperations → Farm CW1002 Ledger guard reports the stale symbol.

CW1001 points to Modules/FlockManagement.cs and prints a syntactically pasteable [ModuleEdge(...)], with the reason left for the author. CW1002 correctly points to Modules/EggOperations.cs and asks to remove the stale symbol.

The analyzer never read contract lists on main and still does not. Test-only enforcement of removed marks matches #1085's stated scope: edge and namespace diagnostics, with architecture tests retaining authority. The decision docs explain this and introduce no copied issue-state claims. Two wording clarifications fall below P3: RealModuleLedger's introductory comment should limit “the analyzer reads too” to owner/edge rows; the decision record's diagnostic sentence should distinguish CW1001's insertion attribute from CW1002's deletion guidance.

Applied pstack:thermo-nuclear-code-quality-review and ponytail-review, slice-scoped through P3. No structural regression or warranted simplification within the accepted design. The collector is 79 lines; module files are 17–55 lines; no changed file crosses 1,000 lines. Ponytail verdict: Lean already. Ship.

Verification: solution build passes with zero warnings; all 377 architecture tests pass across 22 separately run classes; all 20 documentation tests pass. Every test command used DOTNET_USE_POLLING_FILE_WATCHER=1. After restoring the mutations, the solution build and ledger/analyzer classes pass again, and the checkout is clean. Full integration tests and live IDE diagnostics were not run.

Reproduction scripts, independent dumps, individual logs and detailed review notes are retained locally in /home/mforce/.cluckwork-slices/reviews/1086-11cd28a1-astra-evidence/. No product changes, commits or pushes.

@mforce
mforce merged commit 02c299a into main Oct 5, 2026
19 checks passed
@mforce
mforce deleted the refactor/module-rules-beside-code branch October 5, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant