Repository navigation
refactor(arch): move Access into Modules/Access - #1095
Merged
Merged
Conversation
…med ports move.py split: IIdentityProvider.cs keeps only IIdentityProvider, and IStepUpGrantService.cs only IStepUpGrantService. FarmSignIn, RefreshTokenRevocationOutcome, TokenPair, UserSummary, UserProfile, StepUpGrant and StepUpErrorCodes each go to their own file in Application/Common, the same namespace, as verbatim slices. Access's Types claim covers only the two interfaces, so these seven are Platform today; moving them with the interfaces would make them Access-owned, adding an Access -> Commerce edge symbol (UserProfile names EggUnit) and an adapter reach through a non-contract type (AuthEndpoints through TokenPair). Refs #1087
move.py move with the slice's table: the Users feature (Application), the Identity code (Infrastructure) and the Auth, Me and Users endpoints (Api) go under Modules/Access, contract types under Contracts, with the namespace taken from the folder. IIdentityProvider and IStepUpGrantService leave Application/Common for Application/Modules/Access/Users, so the rules file drops its Types claim: their namespace now gives them to Access. The rules file names Cluckwork.Application.Modules.Access and Cluckwork.Infrastructure.Modules.Access; Implementations stay for S9. Tenant-bypass symbols and files, edge symbols and the guards' path lists follow by name. move.py prune drops the usings the move left unused. Refs #1087
AccessOwnershipClaimTests asserted the two ports through Access's Types claim, which the move retires. It now resolves each type through the ledger's namespace index and requires a namespace claim owned by the contracted Access module. GlobalUserLookupGuardTests' fixture and AccountSuspensionTests' source read build their Identity paths by segments, which the codemod's path rewrite does not match. Two comments and the simulation runbook pointed at a stale Identity/SimulationOptions.cs; it lives in Persistence. Refs #1087
The twenty-nine Access contract types now sit in Application's Contracts folder; the folder makes them contract, and a type with both fails. Refs #1087
mforce
force-pushed
the
refactor/1087-access-module
branch
from
October 5, 2026 23:43
c977d41 to
ad0aac7
Compare
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #1087
S8 of the package-by-module design: Access moves into
Modules/Accessin Application, Infrastructure and Api. This is a pure move with no behaviour change. Its repository and EF configurations stay in place until S9, andUserRoleAssignmentstays in Farm. Proofs compare this headad0aac70with the basedeae5245(#1094 merged). After #1094 merged, I rebuilt the branch on it: I re-ran the codemod for split and move, which re-derived the tenant-bypass rows, and cherry-picked the hand-written fix commit unchanged.One deviation from the design's inventory, approved by the owner
The owner approved this split on 2026-10-05, for two reasons: a move PR must not change ownership, and each public type gets its own file.
The inventory moves
Application/Common/IIdentityProvider.csandIStepUpGrantService.cswhole. But today theTypesclaim gives Access only the two interfaces. The other 7 types in those files are Platform:FarmSignIn,RefreshTokenRevocationOutcome,TokenPair,UserSummary,UserProfile,StepUpGrantandStepUpErrorCodes. A whole-file move makes them Access-owned. I measured that change, and it alters the dependency policy:UserProfilenamesDomain.Catalog.EggUnit, so it adds an Access → Commerce edge symbol. The analyzer fails the build with CW1001.AuthEndpoints.LegacyUpgradeResultwould reach Access throughTokenPair, a non-contract type.AdapterReachRealTreeTestsreports that as a contract bypass.So the first commit splits those 7 types into their own files in
Application/Common, each a verbatim slice. Only the two interfaces move. Ownership stays exactly as onmain: the same types are Access, Platform and contract.Review order
Application/Common, in the same namespace.git mvs, with namespaces taken from folders.tools/module-move/move.pyrewrote references, usings, ledger strings, tenant-bypassSymbol/Filestrings and paths.prunethen dropped the usings the move left unused.Namespacesnow holdsCluckwork.Application.Modules.AccessandCluckwork.Infrastructure.Modules.Access.Typesclaim is deleted: the two ports now sit inApplication/Modules/Access/Users/, so the namespace gives them to Access.Implementationsis unchanged until S9.AccessOwnershipClaimTestsnow resolves each port through the ledger's namespace index. It requires a namespace claim (not a type claim) owned by the contracted Access module.GlobalUserLookupGuardTestsandAccountSuspensionTestsbuild Identity paths from segments, which the path rewrite does not match.bootstrap.shpointed at a staleIdentity/SimulationOptions.cs; it lives inPersistence.k6/config.jscomment now gives the newAuthCookies.cspath.[ModuleContract]marks.FarmDirectoryCallerTestsneeded no change: no Access file namesIFarmDirectoryorAccountRepository.Change map
Application/Features/Users/**contracts (21 files, 29 types)Application/Modules/Access/Contracts/Application/Features/Users/**handlers, validators,IUserRoleAssignmentRepository,PasswordRules,UserNameApplication/Modules/Access/Users/**Application/Common/{IIdentityProvider,IStepUpGrantService}.cs(interfaces only, after the split)Application/Modules/Access/Users/Infrastructure/Identity/*.cs(30 files)Infrastructure/Modules/Access/Identity/Api/Endpoints/{Auth,Me,Users}/*.cs(6 files)Api/Modules/Access/{Auth,Me,Users}/: Platform-owned adaptersBypassAllowList.cs(9 rows),FilterFreeSetSites.cs(32 rows)Symbol/Filestrings follow the namespace. NoHashorSignatureline changedChecks at the head (base measured on
deae5245)dotnet build Cluckwork.sln(analyzers on)Architecture+TenantBypass+Documentation+ImagePinServingGuardCoverage,TrackedMutationRead,InsightsReadOnly,GlobalUserLookupGuard,AccountSuspension,RenameAccountDocs,SecretCliLoggingGuardCI at
ad0aac7018 checks pass, including the four test legs, "Build, audit and schema docs", both image smoke tests and the three Playwright shards. "Publish the commit image" is skipped, as on every PR.
Tenant-bypass registry, before and after (#632 keys)
The rows were parsed from
BypassAllowList.csandFilterFreeSetSites.csat the base and at the head. Base rows were then mapped through the move's type and path table.Hash(allow-list) andSignature(filter-free) multisetsSymbol,File), 32 filter-free (Symbol). No other field differsTenantBypassRealTreeTestsgreen at the head. Each hash is SHA-256 over the member's Roslyn tokens, so unchanged hashes mean no query token changed in any of the 41 membersProofs (base
deae5245vs headad0aac70)migrations has-pending-model-changesMigrations/diff is 11 snapshot lines namingApplicationUser,ApplicationRoleandRefreshTokenunder their new namespace. Control:ApplicationUser.LanguageatHasMaxLength(17)makes the same command exit 1tools/schema-docs/generate.sh --checkContracts)coupling-matrix.mdCouplingMatrixRealTreeTestsgreen)Infrastructure.Identity,Application.Features.UsersandApi.Endpoints.AuthnamespacesMutations
Each case ran in a spare worktree at the head. It was reset, built, and run against the
Architecture+TenantBypassfilter. When the build itself failed, the case was rebuilt with analyzers off and warnings not treated as errors, so the guards still ran.Contracts/DisableUserCommand.cs(file and namespace) back toUsers/DisableUser/AdapterReachRealTreeTests,CouplingMatrixRealTreeTests; contract set Access −DisableUserCommand.Contractsnamespace keptNamespaceFolderAgreementTestsonlyUserEndpointsalso injectsIUserRoleAssignmentRepositoryAdapterReachRealTreeTests,CouplingMatrixRealTreeTestsPemKeyreferences Finance'sExpense(no Access → Finance edge)CW1001. With analyzers off: redModuleLedgerRealTreeTests,CouplingMatrixRealTreeTests(2)ConfirmSaleHandleralso takesIUserRoleAssignmentRepositoryPeerContractRealTreeTestsAccessUserSummarygains anAccountaggregate propertyModuleContractRealAssemblyTests; alsoCW1001Access → Farm from a contract type,ModuleLedgerRealTreeTests,CouplingMatrixRealTreeTestsIIdentityProviderback toApplication/CommonAccessOwnershipClaimTests. AlsoCW1002stale edge symbol,ModuleLedgerRealTreeTests,CouplingMatrixRealTreeTestsCredentialEpochVerifier.VerifyAsync:account.Id == user.AccountId→account.Id == accountIdTenantBypassRealTreeTests.RealSourceTree_AllBypassesAreAllowListed/* probe */comment added inside the queryAccessSeedLookup.OwnerRoleExistsAsync:r.Name == Roles.Owner→r.Name != Roles.OwnerTenantBypassRealTreeTests.RealSourceTree_FilterFreeSetSitesAreStableAndClassified