Skip to content

refactor(arch): finish package-by-module — contracts by folder, no marks or claim lists - #1099

Merged
mforce merged 4 commits into
mainfrom
refactor/1087-cleanup
Oct 6, 2026
Merged

mforce merged 4 commits into
mainfrom
refactor/1087-cleanup

Conversation

@mforce

@mforce mforce commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Refs #1087

S10 of the package-by-module design: the cleanup. Every module now lives in Modules/<Owner>/ with its contract in Contracts/, so the transition mechanisms go. Only how the policy is enforced changes. The policy itself, and the product, do not. Proofs compare this head with the base fb3a9cd3 (#1097, S9); the long proofs are being added below as they finish. S11 (#1098, the diagram) runs in parallel and reads the edge cells, which this PR keeps; whichever merges second rebases.

Review order

  1. Contracts only from folders (read this): [ModuleContract], its attribute, the mark branch of RealModuleLedger.DeriveContracts and the mark-plus-folder overlap error are deleted. A contract is a top-level type in Cluckwork.{Domain,Application}.Modules.<Owner>.Contracts; NamespaceFolderAgreementTests keeps that namespace equal to its folder. The contract-derivation fixture loses its marked type and the overlap test.
  2. Empty ledger lists (read this): Implementations and Types are removed from ModuleOwnerAttribute and OwnerDefinition, along with their validation, the analyzer's Types reading, the peer walk's claimed-type roots, the compatibility scanner's "declared implementation" allowance, and the fixture tests of those features. No owner used either: S9 made repositories module code, and S8 moved Access's identity ports into Access's namespace.
  3. Global usings: global using Cluckwork.Domain.Common.Architecture; is deleted from both Application and Domain. Nothing outside Domain/Common/Architecture uses it (checked by grep, since IDE0005 is not enforced), and the rules files sit in that namespace.
  4. Docs: src/AGENTS.md now says the contract is the Contracts/ folder, owner rows claim fixed roots, and registrations live under Api/Modules/<Owner>/; the type-claim and Implementations rules are removed. Amendments are appended to Modular-monolith architecture design + implementation plan (from PR #423) #514, [C] #514 slice 7: Finance pilot — first module behind a contract #849, [C] #514 slice 8: close or date every compatibility exception #850, [C] #514 slice 17: assembly split — conditional on Track A/B evidence #859 and test(arch): guard module-to-module calls against non-contract types #1023. Two stale code comments (IFlockModule, ModuleMap) are fixed.

graphify update . is left out of this PR at the owner's request: AGENTS.md says not to bundle graphify regeneration into code PRs.

The five scoped guards

S0 widened these five guards for the transition. None of them names an old root any more, and each root it still walks holds live code, so narrowing any of them would make it looser:

Guard Walks Why it stays
SeamSurfaceRealAssemblyTests the whole Application assembly a superset of Modules + Common; Application has no other namespace
AdapterRoots / PersistenceForbiddenNamespaces Api.Endpoints, Api.Modules (+ Cli, Jobs, seeders) Api/Endpoints/ClientErrors stays there by design
InsightsReadOnlyTests the whole Api project a superset; only the Insights registration may name the read ports
TrackedMutationReadTests Repositories/** and Modules/<Owner>/Repositories/** Infrastructure/Repositories still holds Platform code (AuditWriter, LiteralSearch, ReferenceMarkers)
ServingGuardCoverageTests Api.Hosting and Api.Modules ServingBootGuards and the composition files stay in Hosting

A stray old-root namespace fails as unowned through the owner rows, which claim only the fixed roots. The mutation below proves it.

Checks at push time

Check Result
dotnet build Cluckwork.sln 0 warnings
Application.Tests Architecture + TenantBypass + Documentation + ImagePin 457/457. Base 474; the 17 removed tests exercised deleted features (1 mark-overlap, 2 claimed-type ledger, 9 claimed-type/types peer rows, 5 Implementations compatibility)
SchemaDocsTests, ServingGuardCoverageTests, TrackedMutationReadTests; Domain.Tests 4/4, 7/7, 2/2; 495/495

Proofs: nothing got looser (base fb3a9cd3 vs head bc4e3501; the head is now 5dcafde8, the same tree without the dropped graphify commit, so the proofs still apply)

No file moved in this slice, so base and head reports compare directly. File and line positions are excluded.

Proof Result
Exact contract set per owner identical: 160 types (Access 29, Farm 10, Flock Management 15, Egg Operations 33, Commerce 36, General Inventory 16, Finance 10, Insights 11), now read from Contracts/ folders only
Module-edge report (live edges) identical
Peer-reach report identical
Table-owner report identical
Adapter-reach report identical, including the walked count (409 → 409)
Tenant-bypass registry unchanged (0 changed lines in TenantBypass/)
coupling-matrix.md unchanged (0 changed lines; CouplingMatrixRealTreeTests green)
EF migrations has-pending-model-changes "No changes have been made to the model since the last migration." No Migrations/ change. Control: HasMaxLength(ExpenseCategory.MaxNameLength + 1) makes the same command exit 1
tools/schema-docs/generate.sh --check "docs/schema/ is up to date"
Ordered DI descriptors, Production/Development × serving/one-shot × Redis absent/configured (8) identical
Generated OpenAPI document (Development) identical
Unused usings (IDE0005, whole solution) base 198, head 196, 0 new. The 2 gone are the two deleted global usings, which IDE0005 already flagged as unused at base

Mutations (spare worktree at the head; each case reset, built and run against the Architecture + TenantBypass filter; a case the analyzer stops is rebuilt with analyzers off so the guards still run)

# Mutation Build Guards Contract-set diff vs base
(a1) codemod moves Contracts/UpdateCustomerCommand.cs (file and namespace) back to Customers/UpdateCustomer/ green red AdapterReachRealTreeTests (CustomerEndpoints reaches a non-contract type), CouplingMatrixRealTreeTests Commerce −UpdateCustomerCommand: a type moved out of Contracts/ loses contract status
(a2) same file moved, .Contracts namespace kept green red NamespaceFolderAgreementTests none: the folder rule stops the file keeping its contract namespace
(b) Finance's CreateExpenseHandler.cs references Commerce's Customer (no Finance → Commerce cell) red CW1001 undeclared cross-owner edge Finance → Commerce red ModuleLedgerRealTreeTests.RealSourceTree_EveryCrossOwnerEdgeIsLedgered, CouplingMatrixRealTreeTests (2) —
(c) stray old-root namespace Cluckwork.Application.Features.Stray (a record under Application/Features/Stray/) red CW1003 unowned namespace Cluckwork.Application.Features.Stray red ModuleLedgerRealTreeTests ("unowned namespace 'Cluckwork.Application.Features.Stray' declared in src/Cluckwork.Application/Features/Stray/StrayProbe.cs"), CouplingMatrixRealTreeTests —

mforce added 4 commits October 6, 2026 02:24
…oduleContract]

Every module's contract now sits in Modules/<Owner>/Contracts, so the mark,
its attribute, the mark branch of RealModuleLedger.DeriveContracts and the
mark-plus-folder overlap error go. A contract is a top-level type in a
Cluckwork.{Domain,Application}.Modules.<Owner>.Contracts namespace;
NamespaceFolderAgreementTests keeps that namespace equal to its folder. The
attribute's Implementations and Types properties go with it (next commit
removes their readers).

Refs #1087
No owner used either after the module moves: repositories read their tables as
their module's own code (S9), and Access's identity ports sit in Access's
namespace (S8). The ledger model, its validation, the analyzer's Types
reading, the peer walk's claimed-type roots, the compatibility scanner's
implementation allowance and the fixture tests of those features go.

Refs #1087
…global usings

Nothing outside Domain/Common/Architecture uses that namespace now that the
marks are gone; the rules files sit in it and need no using.

Refs #1087
src/AGENTS.md now says a contract is the Modules/<Owner>/Contracts folder,
owner rows claim fixed roots, registrations live under Api/Modules/<Owner>,
and drops the type-claim and Implementations rules. Amendments to #514,
#849, #850, #859 and #1023 record what changed. Two stale code comments
(IFlockModule, ModuleMap) follow.

Refs #1087
@mforce mforce added this to the Modular monolith milestone Oct 6, 2026
@mforce
mforce force-pushed the refactor/1087-cleanup branch from bc4e350 to 5dcafde Compare October 6, 2026 02:39
@mforce
mforce merged commit 23bb192 into main Oct 6, 2026
19 checks passed
@mforce
mforce deleted the refactor/1087-cleanup branch October 6, 2026 03:04
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