A new business or platform module is not just a project directory — a directory never told the compiler whom it depends on, what it exposes, or which permissions it owns. Modules must declare compile-time governance facts in their owner directory: module identity, allowed dependencies, and public contracts are projected by a Source Generator from an owner-local typed marker, while permissions and features register in dedicated catalog attributes. The runtime never scans assemblies or reads attributes.
The AppModule typed marker
The V2 governance marker lives at BitzOrcas.Modularity.Governance.AppModuleAttribute and annotates a marker type in the owner project (conventional file name XxxModule.cs). The constructor takes only the display name; the rest of the identity arrives through init properties:
Code— the stable module code (e.g.tracker) used as the permission-code prefix{code}.{resource}.{action}and for catalog indexing; defaults to the kebab-case form ofName;BaseNamespace— the module’s base namespace; when omitted the generator derives it from the annotated type’s assembly;PublicContractNamespaces— only these namespaces may be referenced by other modules; defaults toBaseNamespace + ".Contracts".
A type can carry the marker exactly once. The fundamental difference from the legacy [assembly: AppModule]: declaration moved from assembly level down to an owner-local type so dependency edges and permission ownership stay auditable in one place, with all DI coupling stripped out.
Below is the verbatim Tracker module marker from the framework repository (src/Platform/Tracker/BitzOrcas.Platform.Tracker.Contracts/TrackerModule.cs):
namespace BitzOrcas.Platform.Tracker.Contracts;
using BitzOrcas.Modularity.Governance;
/// <summary>/// Matter-tracking module governance marker/// </summary>[AppModule("Tracker", Code = "tracker", BaseNamespace = "BitzOrcas.Platform.Tracker")]// Every DependsOn is one explicitly declared runtime dependency edge.// Seven edges mean Tracker spans authorization, release tracking, files,// search, notifications, menus, and identity.[DependsOn("Authorization")][DependsOn("ReleaseManagement")][DependsOn("Files")][DependsOn("Search")][DependsOn("Notifications")][DependsOn("Menu")][DependsOn("Identity")]internal sealed class TrackerModule;DependsOn is explicit and cannot auto-expand from observed ProjectReference — removing an edge genuinely disconnects things, which is what makes review diffs meaningful. A Host needing the full graph declares [assembly: GenerateModuleGovernanceCatalog]; the generator aggregates public IModuleContributionProvider indexes from referenced assemblies at compile time and emits ModuleGovernance.Generated.cs. There is no AppDomain.GetAssemblies, Assembly.GetTypes, or DI Service Locator.
Current governance counts
The source currently contains 36 typed [AppModule] markers. PlatformModuleGovernanceLocalizationTests.ExpectedOwnerModules explicitly lists a 27-owner baseline: the former 26 converged modules plus LicenseManagement. The other nine markers are:
- foundation and connectors:
Platform,IndustryExtensions,LegalCalculators,LegalConnectors, andToolConnectors; - newer product owners:
Announcements,CommercialDistribution, andReleaseManagement; - the
SandboxGolden Use Case undersrc/Modules/Sandbox.
Generated-catalog coverage does not trust only that 27-entry dictionary. It discovers every typed marker below src/Platform and src/Modules, then compares the ordered, unique set with the API Host’s compiled GeneratedModuleGovernanceCatalog. All 36 markers must therefore reach the generated catalog.
There are also 36 module handbooks, but their developer-facing capability boundary is not
one-to-one with typed markers. Handbooks add the Framework cross-cutting surfaces Auditing and
Multitenancy, while foundation Platform and the Sandbox Golden Use Case are not production
business-module handbooks. The equal totals are coincidental; reviews must distinguish the marker set
from the handbook set.
The central foundation boundary
src/Platform/BitzOrcas.Platform.Application/PlatformModuleGovernance.cs is currently 27 lines and retains only legacy assembly declarations for LegalConnectors and ToolConnectors. Those declarations supply OwnedPermissions / OwnedFeatures that have not yet moved into owner-local catalogs. Both modules already have V2 typed markers; once their permission and feature catalogs exist, the legacy declarations can be removed.
Platform, IndustryExtensions, and LegalCalculators now use separate typed markers in the same project rather than central assembly declarations. Business modules must not write back into the central file, and the gate keeps it at ≤ 50 lines. Add a module by declaring its marker and catalogs in the owner project, referencing the generator as an analyzer, and letting the target Host reference the assembly normally. Do not create a Host module-name list.
The legacy ledger ratchet
General module intake is still guarded by ModuleGovernanceRegistrationTests and 0001-module-governance-legacy-ledger.json. The ledger is a ratchet that can only shrink; maxLegacyRoots must decrease as legacy roots are deleted (currently zero). A new module root without a compile-time registration fails the test; historical legacy assembly manifests appear only in the compatibility ledger with an owner and deletion condition.
New-module review sequence
- Confirm the directory is a real owner, not a shallow module created only for reuse.
- Review the
AppModulename, Code, BaseNamespace, andDependsOnedges for stability and cycles. - Put permissions, features, published events, and subscriptions in owner-local catalogs.
- Let project references expose the module to the Host generator; add no reflection scan or string registry.
- Run localization, registration, generated-catalog, and readiness-matrix tests.
- If the capability needs a handbook, update its catalog, examples, and source checks without treating prose as the marker.
Source review
Record the typed-marker count, explicit owner baseline, and handbook count together so a later change cannot update only one definition.
# Count typed markers and compare the explicit owner baseline with dynamic coverage.rg -n '\[AppModule\(' src/Platform src/Modules -g '*.cs'rg -n "ExpectedOwnerModules|DiscoverTypedModuleNames|GeneratedModuleGovernanceCatalog" \ tests/BitzOrcas.Architecture.Tests/PlatformModuleGovernanceLocalizationTests.cs
# The central legacy file stays thin, and the ledger only shrinks.wc -l src/Platform/BitzOrcas.Platform.Application/PlatformModuleGovernance.cscat docs/architecture/00-governance/manifests/0001-module-governance-legacy-ledger.json