SaaS Template Baseline Checklist¶
Single, repo-agnostic checklist every ConnectSoft.Saas.<Context>Template repo under C:\Git\ConnectSoft\ConnectSoft.Saas.*Template must satisfy. Use it as the PR gate and the template installer CI gate.
This checklist is derived from:
- Extended Template (Base Submodule) Guide — Layer-3 mechanics.
- Template Metadata Composition — composed installer pattern.
- Template Naming Guide —
connectsoft-saas-*short names. - SaaS Bounded Contexts & Templates — canonical repos.
- SaaS Platform Solution Plan — end-to-end target.
- Multitenancy guide: guides/saas/multitenancy.md.
The five SaaS bounded-context repos this checklist applies to:
| Repo | Aggregate root | Short name |
|---|---|---|
ConnectSoft.Saas.TenantsTemplate |
Tenant |
connectsoft-saas-tenants |
ConnectSoft.Saas.ProductsCatalogTemplate |
Product |
connectsoft-saas-productscatalog |
ConnectSoft.Saas.EntitlementsTemplate |
Entitlement |
connectsoft-saas-entitlements |
ConnectSoft.Saas.BillingTemplate |
Subscription |
connectsoft-saas-billing |
ConnectSoft.Saas.MeteringTemplate |
UsageMeter |
connectsoft-saas-metering |
1. Layer-3 mechanics (non-negotiable)¶
- Repo is under
C:\Git\ConnectSoft\ConnectSoft.Saas.*Templateand has its own git remote. -
.gitmodulesmapsbase-template/toConnectSoft.BaseTemplate(relative URL../ConnectSoft.BaseTemplate, branchmaster). - Clean clone +
git submodule update --init --recursivesucceeds. - Root
Directory.Build.propsimportsConnectSoft.TemplateRepositoryDirectory.Build.propsand thenbase-template/Directory.Build.props. - Root
Directory.Packages.propsimportsbase-template/Directory.Packages.props. -
ConnectSoft.TemplateRepositoryDirectory.Build.propssetsConnectSoftBaseTemplateDirectoryPostImporttobuild/DisableMicrosoftExtensionsStackForMinimalHost.props. -
build/DisableMicrosoftExtensionsStackForMinimalHost.propsimportsExtendedHost.BaseTemplateSatelliteDefaults.propsfor satellite paths andDisableMicrosoftExtensionsStackForMinimalHost.<Context>Host.propsfor everything else. -
<Context>Host.propsexplicitly sets the canonical SaaS feature-flag matrix below. - No
build/CentralPackageVersions.MinimalHost.propsand noConnectSoftCentralPackageVersionOverridesinConnectSoft.TemplateRepositoryDirectory.Build.propsunless an ADR documents an exception; rely on ConnectSoft.BaseTemplate Option A (conditional satellitePackageReference/PackageVersionalignment). -
base-template/submodule pointer references a ConnectSoft.BaseTemplate commit that includes Option A CPM satellite fixes (or newer). -
nuget.configincludesnuget.org+ConnectSoft+ optionalCoreWCF(package source mapping respected). -
<Context>.slnxenumerates the subset ofbase-template/src/ConnectSoft.BaseTemplate.*projects this repo compiles plus the repo's own projects.
2. Canonical SaaS feature-flag matrix (<Context>Host.props)¶
Locked defaults for every SaaS repo (overridable per repo only via an ADR):
<Project>
<PropertyGroup Label="SaaS: messaging and actors">
<UseMassTransit>true</UseMassTransit>
<MessagingModelTypeNone>false</MessagingModelTypeNone>
<UseNServiceBus>false</UseNServiceBus>
<UseOrleans>true</UseOrleans>
<UseDapr>false</UseDapr>
<UseAkka>false</UseAkka>
</PropertyGroup>
<PropertyGroup Label="SaaS: service model (REST + gRPC only)">
<UseRestApi>true</UseRestApi>
<UseGrpc>true</UseGrpc>
<UseSignalR>false</UseSignalR>
<UseGraphQL>false</UseGraphQL>
<UseCoreWCF>false</UseCoreWCF>
<UseServiceFabric>false</UseServiceFabric>
<UseAzureFunction>false</UseAzureFunction>
</PropertyGroup>
<PropertyGroup Label="SaaS: persistence (NHibernate only, multi-dialect)">
<UseNHibernate>true</UseNHibernate>
<UseMongoDb>false</UseMongoDb>
<Migrations>true</Migrations>
</PropertyGroup>
<PropertyGroup Label="SaaS: scheduler / cache / telemetry">
<UseHangFire>true</UseHangFire>
<DistributedCache>Redis</DistributedCache>
<OpenTelemetry>true</OpenTelemetry>
<UseOtelCollector>true</UseOtelCollector>
<Serilog>true</Serilog>
<Log4Net>false</Log4Net>
</PropertyGroup>
<PropertyGroup Label="SaaS: AI / vector / agents OFF by default">
<UseMicrosoftExtensionsAI>false</UseMicrosoftExtensionsAI>
<UseMicrosoftAgentFramework>false</UseMicrosoftAgentFramework>
<UseVectorStore>false</UseVectorStore>
<UseAgentSkills>false</UseAgentSkills>
<UseModelContextProtocol>false</UseModelContextProtocol>
<UseBotModel>false</UseBotModel>
<UseAIModel>false</UseAIModel>
</PropertyGroup>
</Project>
- All of the above set exactly as shown (or overridden via ADR + changelog).
- The minimal-host props
DefineConstantsstrip removes all disabled preprocessor symbols sobase-template/srcprojects compile for this repo.
3. One aggregate root per repo¶
-
<Context>.jsondeclares exactly oneentityModel.aggregateRoot. -
<Context>.ArchitectureTestscontains a test that fails if more than one EntityModel aggregate-root interface extendsIAggregateRoot<>. -
docs/adr/0001-one-aggregate-root-per-repo.mdis present and up to date. -
docs/out-of-scope.mdlists deferred entities (if any) with a pointer to their future repo.
Current guardrail status (verified 2026-06-18):
| Repo | One aggregate root guardrail | Cross-repo published-language guardrail |
|---|---|---|
ConnectSoft.Saas.TenantsTemplate |
Implemented | Implemented |
ConnectSoft.Saas.ProductsCatalogTemplate |
Implemented | Implemented |
ConnectSoft.Saas.EntitlementsTemplate |
Implemented | Implemented |
ConnectSoft.Saas.BillingTemplate |
Implemented | Implemented |
ConnectSoft.Saas.MeteringTemplate |
Implemented | Implemented; Orleans grain partition test still pending/skipped |
4. Public contracts (ServiceModel + MessagingModel + ActorModel)¶
-
<Context>.ServiceModelcontains DTOs + typed client facades. -
<Context>.ServiceModel.RestApicontains controllers / minimal endpoints; published as a NuGet package. -
<Context>.ServiceModel.Grpccontains code-firstGrpc<Name>Serviceadapter classes implementing the C#[ServiceContract]interfaces from<Context>.ServiceModel(ServiceModel.Grpc — no.proto, noGrpc.Tools, noProtos/folder); published as a NuGet package. -
<Context>.MessagingModelcontains integration event contracts (tenantId,aggregateId,aggregateVersion,schemaVersion,correlationId,causationId) and is published as a NuGet package. -
<Context>.ActorModel(grain contracts) is published as a NuGet package whenUseOrleans=true. - No external repo may reference
<Context>.DomainModel,<Context>.EntityModel, or<Context>.PersistenceModel.*. Enforced inArchitectureTestsvia concreteCrossRepoPublishedLanguageTestsin all five repos. - Events version additively (
.v1topics); breaking changes require a.v2topic.
5. Persistence and outbox¶
-
<Context>.PersistenceModel.NHibernateis the only persistence implementation; multi-dialect viaPersistence:Dialectoption (sqlserver,postgres,mysql,oracle,sqlite). -
<Context>.DatabaseModel.Migrationsuses FluentMigrator expressions; dialect branches only viaIfDatabase(...)when an expression is impossible. - No custom outbox tables in migrations. MassTransit's built-in outbox (via BaseTemplate
AddMassTransitOutboxextension) owns the schema. -
Sample*Seed.csconstants align with saas-cross-repo-published-language.md sample seed IDs (7f4c2b9e-3d1a-4f8e-9c6b-5a0e183d42f1tenant; catalog product/edition GUIDs where applicable). -
docs/examples/sample-*-database.mddocuments seeded rows for acceptance tests. - Cross-repo reactions use MassTransit saga state machines (not standalone consumers).
- Consumers are idempotent; inbox dedupe key is
(sourceContext, eventId).
6. Multitenancy (hybrid default)¶
-
ConnectSoft.Extensions.Saas.*packages referenced (not ad hoc copies);MicroserviceRegistration/ SaaS registration seam callsAddConnectSoftSaas*(or equivalent) from BaseTemplate/SaaS template. - DedicatedSingleTenant +
appsettingspath documented for single installation; matches multitenancy-configuration-schema.md. -
ITenantContextpopulated from composite resolver (config → JWTtid→ headerX-Tenant-Id→ transport); rejects missing tenant on tenant-scoped endpoints when required. - gRPC: server/client use
ConnectSoft.Extensions.Saas.AspNetCore.Grpc—metadata keytenant-idper ADR-0100. - NHibernate:
EnableFilter+ parameter binding for shared-DB tenant filter in session scope (not onlyApplyFilteron mappings). - NServiceBus: when
UseNServiceBus=true, tenant propagation behaviors match MassTransit headertenant-idsemantics. - Background jobs and MassTransit / NServiceBus consumers propagate the same tenant context (envelope/header parity).
- Optional switches documented in
docs/configuration.md: -
Multitenancy:DeploymentKind = SharedDb(default row scoping) -
Multitenancy:DeploymentKind = DatabasePerTenant -
Multitenancy:DeploymentKind = ResidencySilo(perResidency/DataSiloId).
7. Actor / runtime coordination¶
- Orleans silo co-hosted in
<Context>.ApplicationwhenUseOrleans=true. - Grain keys include
tenantId(composite primary key). - Orleans streams used only for intra-silo fan-out. Cross-service integration goes through MassTransit.
8. Observability¶
- OTel traces + metrics + logs via
UseOtelCollector=true. -
observability/otel-collector/+observability/prometheus/+observability/grafana/+observability/loki/+observability/tempo/present and shared between DockerCompose and Deployment. - Grafana dashboards provisioned for: latency (p50/p95/p99), RED, errors, Orleans grain metrics (when on), MassTransit consume/publish, NHibernate query timings.
- Prometheus alerting rules for: p99 latency SLO breach, error-ratio breach, saga dead-letter growth, outbox delivery lag.
9. Deployment assets¶
-
DockerCompose/runs the service + chosen NHibernate DB + Redis + RabbitMQ + OTel Collector + Prometheus + Grafana + Loki + Tempo (or Jaeger). -
Deployment/kubernetes/contains raw manifests (Deployment/Service/HPA/PDB/NetworkPolicy/ServiceAccount/Ingress). -
Deployment/helm/contains a Helm chart with values for every supported dialect and a Values-driven observability block (ServiceMonitor/PodMonitor/PrometheusRule). -
<Context>.InfrastructureModelprovides Bicep/Terraform/Pulumi modules validated bybicep what-if/terraform plan/pulumi previewin the pipeline.
10. Template installer composition¶
-
.template.config/template.jsonis a minimal stub; real metadata is composed frombase-template/.template.config/template.json+template/<context>.template.extend.jsonduring CI. - Short name matches the canonical matrix in the table above.
-
azure-pipelines-template.ymlcalls the centralized submodule scriptsbase-template/build/Prepare-ExtendedTemplatePack.ps1 -PackProfile Simpleandbase-template/build/Invoke-TemplateCompose.ps1 -ComposeProfile Simple(no repo-rootbuild/prepare-*orbuild/template-compose*shims). - The CI compose step strips
base-template/.template.configfrom the final composed package after composition (single-installer model). - CI gate:
dotnet new install+dotnet new connectsoft-saas-<context> -n Smoke+dotnet buildon the generated solution.
11. Pipelines¶
-
azure-pipelines.yml— app build / test / docker. -
azure-pipelines-service-model.yml— REST + gRPC NuGet publish. -
azure-pipelines-messaging-model.yml— event contracts NuGet publish. -
azure-pipelines-actor-model.yml— grain contracts NuGet publish (Orleans). -
azure-pipelines-infrastructure-model.yml— IaC packaging + what-if. -
azure-pipelines-documentation.yml—mkdocs build --strict+ publish. -
azure-pipelines-template.yml— single-installer composition +dotnet newgate.
12. Documentation¶
-
docs/+mkdocs.yml(MkDocs Material + mermaid + plantuml). - Sections: Overview, Bounded Context, Aggregate Root, REST API, gRPC API, Events, Configuration, Multitenancy, Deployment (compose + k8s + IaC), Observability, ADRs, Getting Started, Out of Scope.
-
<Context>.DiagramAsCodeModelrenders C4 + sequence + deployment diagrams in CI and embeds them in the docs site. -
README.mdat repo root with clone +git submodule update --init --recursiveand a pointer to the docs site. -
AGENTS.mdat repo root with agent-oriented guidance aligned with the factory-generator.
13. AI Software Factory wiring¶
-
<Context>.jsonis the single source of truth for the aggregate. -
<Context>.schema.jsonand<Context>.entitymodel.schema.jsonvalidate the descriptor. -
build/regenerate.ps1invokes themicroservice-generator-agent(idempotent) to (re)generateEntityModel+PersistenceModel.NHibernate+DatabaseModel.Migrations+ServiceModelscaffolding. - Domain logic (invariants, policies, sagas) lives in
<Context>.DomainModel.Impland<Context>.FlowModel.MassTransitand is not touched by regeneration.
14. Gap traceability (wave 2)¶
Map baseline checklist sections to gap backlog Features in saas-gap-implementation-backlog.md (CompanyDocumentation).
| Checklist section | Gap Features (representative) |
|---|---|
| §3 Aggregate root / entity model | saas-TEN-F01, saas-ENT-F01, saas-MET-F01, saas-BIL-F05 |
| §4 ServiceModel surfaces | saas-BIL-F08, saas-CAT-F06 |
| §5 Actor / Orleans | saas-INTEG-F06, saas-TEN-F08, saas-CAT-F01, saas-ENT-F07, saas-BIL-F10, saas-MET-F05 |
| §6 Messaging / events | saas-INTEG-F01–F05, saas-TEN-F05, saas-BIL-F02, saas-MET-F03, saas-MET-F04 |
| §7 Persistence / outbox | saas-CAT-F05, saas-INTEG-F05 |
| §8 Architecture tests | saas-TEN-F06, saas-ENT-F06, saas-BIL-F09, saas-MET-F09 |
| §9 Tests / coverage | saas-TEN-F07, saas-ENT-F05, saas-CAT-F07, saas-MET-F07 |
| §12 Documentation | Per-repo docs/backlog/*-gap-analysis.md mirrors |
Per-repo gap analysis: docs/backlog/<context>-gap-analysis.md in each template repository.
15. Domain implementation patterns (wave 2)¶
Cross-repo patterns documented in SaaS Domain Implementation Patterns.
- Owned
GrpcRichErrorInterceptorper repo insrc/ConnectSoft.Saas.<Context>.ApplicationModel/(not base-template generic). - FluentValidation — one validator per processor/retriever input in
DomainModel.Impl/Validators/with matching*ValidatorUnitTests.cs. - Domain
<Context>Metrics—RegisterTemplateMetricssingleton, wired inDefault*ProcessorandDefault*Retrieverwith*MetricsUnitTests.cs. - Entitlements-style logging —
BeginScopeWithFlow,logger.Here().LogInformation, try/catch with error logs + metrics failure recorders in all domain handlers. - Per-repo
docs/domain-implementation.mdmirrors this checklist for the bounded context.
16. Acceptance¶
A repo is "SaaS-template baseline" only when every box above is checked. Gate both PR reviews and azure-pipelines-template.yml on this checklist.