Repository navigation
test(arch): make the scoped guards module-safe before the package-by-module move - #1088
Conversation
Each guard selected code by a namespace or folder that #1087 moves out of: - SeamSurfaceRealAssemblyTests walks the whole Application assembly; - AdapterRoots and PersistenceForbiddenNamespaces add Cluckwork.Api.Modules; - InsightsReadOnlyTests reads the whole Api project, exempting the Insights registration file by name; - TrackedMutationReadTests selects Repositories/ and Modules/<Owner>/Repositories/; - ServingGuardCoverageTests walks Cluckwork.Api.Modules beside Hosting. Refs #1087
A top-level type in Cluckwork.{Domain,Application}.Modules.<Owner>.Contracts
is <Owner>'s contract. Nested types stay outside it. A type both marked and in
a Contracts namespace is a registry error, so each module slice deletes the
mark when it moves the type. A fixture in the test assembly exercises the
branch, since main has no Contracts namespace yet.
Refs #1087
tools/module-move/move.py splits declarations out of mixed files, moves files by a finite per-file table with the namespace taken from the folder, rewrites qualified names, usings, registry strings and paths, and prunes only the unused usings it introduced (IDE0005 runs read from SARIF). The compiler is the check. Exercised end to end on the S0 trial; no product file moves in this PR. Refs #1087
ModuleContractRealAssemblyTests read only methods, properties and events, so a handler moved into a Contracts folder passed: its repositories sit in its constructor. The contract scan now walks public constructor parameters. Refs #1087
|
Reviewed [P3] Preserve recursive repository discovery. TrackedMutationReadTests.cs:156 now accepts only the exact Verification and other requested judgments:
No additional structural or overengineering finding met the requested threshold. No full integration/browser suite or schema generator was rerun here. Local detail reports, commands and logs are preserved in |
The module-safe selector matched only the exact Repositories and Modules/<Owner>/Repositories folders, so a repository in a subfolder of either dropped out of both the file walk and the reflected namespace set and the guard stayed green. main's walk read subfolders. Both selectors now include descendants. Refs #1087
|
Re Astra's review, P3 (
Mutation: an untracked
The PR body's mutation table now has this as G4c. Your note that move slices must compare the exact normalized contract set against base is recorded under "Follow-on slice requirements". |
Refs #1087
S0 of the package-by-module design: it makes the architecture guards safe for the module moves before any file moves. No product file moves and no behaviour change. Everything is under
tests/andtools/, plus one sentence insrc/AGENTS.md.Review order
efc6f3cf: the five scoped guards (small diffs, each one widens a walk).d8ed4d87+ba6b2803: contract derivation fromContractsnamespaces, and the contract scan now walks constructors.04d4a227: the new folder/namespace rule.68837a92:tools/module-move/move.py, the codemod the slices will use. Skim it; the trial below is its check.673bc853: the three test-type renames (build: enforce file-scoped namespaces and using placement at build time #985).Change map
SeamSurfaceRealAssemblyTestsCluckwork.Applicationassembly instead ofFeatures+Common(today the same set;src/AGENTS.md#847 sentence updated)RealModuleLedger.AdaptersCluckwork.Api.Modulesadded toAdapterRootsand toPersistenceForbiddenNamespacesInsightsReadOnlyTestsInsightsModuleServiceCollectionExtensions.csmay name the read portsTrackedMutationReadTestsRepositories/andModules/<Owner>/Repositories/and their subfolders, by folder and by namespace; the discovery equality, exclusions and floor are unchangedServingGuardCoverageTestsCluckwork.Api.ModulesbesideCluckwork.Api.Hosting; a theory pins both rootsRealModuleLedger.DeriveContracts[ModuleContract]mark ∪ top-level type inCluckwork.{Domain,Application}.Modules.<Owner>.Contracts. Nested types are excluded. A type that has both is a registry error, so each slice deletes the mark when it moves the type. A fixture in the test assembly covers the new branch, sincemainhas noContractsnamespace yetSeamSurfaceScanner(contract mode)Contracts/passedModuleContractRealAssemblyTests, because its repositories appear only in its constructor (see N4 below). All 160 real contracts still passNamespaceFolderAgreementTests(new)Domain/Common/Architecture/Modules→Cluckwork.Domain.Common.Architecture) and namespace-free files that declare no type, exceptPrograminApi/Program.cs. Floor: 500 files.mainalready compliestools/module-move/move.py(new)split(extracts declarations into<Type>.csbeside the file),move(git mvby a finite per-file table; the namespace comes from the folder; rewrites full and, in code only, partial qualified names, usings including extension-method callers, registry strings and paths),marks(deletes marks inContracts/),ide0005+prune(removes only the unused usings the tool introduced, read from SARIF). No restore input, lock file or CI changeFarm→SeededFarm(two private test records);Contracts→NestedContracts(seam fixture class)Counts (measured on
origin/main02c299a and on this head)Architecture+TenantBypass+Documentation+ImagePinServingGuardCoverageTestsTrackedMutationReadTests,AccessFixtureTests,AccessLookupTests,SchemaDocsTestsThe whole solution builds with 0 warnings. The integration and e2e legs run only in CI.
End-to-end trial (scratch copy, not committed)
The trial ran on S1's real files, at this head:
IReportQueries.cssplit into its 7 records;SubmitDailyEntryHandler.cssplit intoSubmitDailyEntryResponse. Each extracted declaration is a verbatim slice of the original, and the remainder equals the original minus those slices.Application/Modules/Insights/Contracts/(Cluckwork.Application.Modules.Insights.Contracts);IReportQueries→Modules/Insights/Reports/; the response →Modules/EggOperations/Contracts/; the handler →Modules/EggOperations/DailyEntries/SubmitDailyEntry/. The Insights and Egg Operations roots were set through the table'sreplace. It compiled first time, with no hand fixes, and introduced no unused using.dotnet build Cluckwork.sln, analyzers onhas-pending-model-changesThe trial also exercised the root change that
nsmove.pymissed:AdapterRootsandPersistenceForbiddenNamespacesalready includeCluckwork.Api.Moduleshere.Mutations (scratch copy of the trial; each case reset, built and run)
"main guard" means the same mutation with that guard put back as it is on
main.Application.ModulesreturnsIQueryable<object>SeamSurfaceRealAssemblyTestsApi.ModulestakesIFlockRepositoryAdapterReachRealTreeTests(undeclared reach)Api.ModulestakesAppDbContextAdapterReachRealTreeTests(forbidden persistence)Api.ModulesnamesIReportQueriesInsightsReadOnlyTestsTask<Flock?>read inInfrastructure/Modules/FlockManagement/RepositoriesTrackedMutationReadTestsTask<Flock?>read in a subfolder:Repositories/Review/andModules/FlockManagement/Repositories/Review/(16c7d86a)TrackedMutationReadTests, both roots; restored green 2/2Repositories/Review/(its file walk read subfolders). The exact-folder selector atba6b2803stayed green for both, the gap Astra foundFlockRepositoryintoModules/FlockManagement/RepositoriesCluckwork.Api.Modules.FarmGuardWalk_ReachesDescendantNamespacesEnsure*method in the moved registrationEveryServingBootGuardMethod_HasACoveringRowContracts/UpdateCustomerCommand.csmoved to its feature folder,.Contractsnamespace keptNamespaceFolderAgreementTestsonly; every other guard green, which is the gap this rule closesContracts/, namespace changed to the featureNamespaceFolderAgreementTests,AdapterReachRealTreeTests(CustomerEndpointsreaches a non-contract type),CouplingMatrixContractsAdapterReachRealTreeTests,CouplingMatrix(no peer takes the command, soPeerContractRealTreeTestsstays green)SubmitDailyEntryHandlermoved intoContracts, namespace includedModuleContractRealAssemblyTestsCluckwork.Application.Modules.Nowhere.Contracts.NowhereProbeCW1003unowned namespace. With analyzers off: registry error "names owner 'Nowhere', which has no ModuleOwner row"!IsNestedfilter droppedContractDerivationTests[ModuleContract]markFor N1–N3,
UpdateCustomerCommandwas first moved intoModules/Commerce/Contracts/by the codemod, because every contract the trial moved is a return type, and the adapter guards read only parameters.Follow-on slice requirements
PeerContractRealTreeTestsstays green when an unused contract leavesContracts(Astra, review ofba6b2803). The trial above did this comparison.Notes for later slices
services.AddFarmModule()) never name the moved class, so the tool now adds their namespace too. Path strings in test data are rewritten too, so the folder rule's examples use a fictitious module.PeerContractRealTreeTestsred; it is red only when a peer takes the moved type.split,ide0005/prune,move,marks.