Epic saas-EPIC-BIL — Billing gap implementation¶
Epic ID: saas-EPIC-BIL
Repository: ConnectSoft.Saas.BillingTemplate
Aggregate root: Subscription
Last verified: 2026-05-27
Source analysis: saas-gap-deep-analysis.md
Epic summary¶
Close evidence-based gaps between the Billing template and the canonical DDD blueprint, cross-repo published language, and E2E integration path. Priority P0 items (saas-BIL-F01, F02, F06) block cross-service demos until BillingConstants topic drift and DefaultSubscriptionsProcessor lifecycle semantics align with Tenants and Metering. Consumer-side quota contract (MeterKey vs Metering Dimension) is coordinated with saas-MET-F04 and saas-INTEG-F02.
Rollup (from gap analysis): 11 Implemented · 8 Partial · 7 Missing · 2 Deferred
Feature index¶
| Order | ID | Title |
|---|---|---|
| 040 | saas-BIL-F01 | Subscription Create-Draft lifecycle fix |
| 041 | saas-BIL-F02 | Inbound topic and quota payload harmonization |
| 042 | saas-BIL-F03 | Invoice read-model projection or defer ADR |
| 043 | saas-BIL-F04 | Payment provider ACL skeleton or defer ADR |
| 044 | saas-BIL-F05 | Promotion, proration, and seat policy domain |
| 045 | saas-BIL-F06 | Suspend-on-quota outbound integration event |
| 046 | saas-BIL-F07 | Rating-window outbound event to Metering |
| 047 | saas-BIL-F08 | Public ServiceModel operations for internal processor ops |
| 048 | saas-BIL-F09 | Architecture test enforcement |
| 049 | saas-BIL-F10 | Orleans grain write-path wiring |
| 050 | saas-BIL-F11 | SubscriptionSeatPolicy scaffold cleanup |
[040] saas-BIL-F01 — Subscription Create-Draft lifecycle fix¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 040
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, lifecycle, P0
Priority: P0
Effort: M
Dependencies: —
Blocks: saas-BIL-F08
Source gap analysis: billing-analysis · Finding B-001
Description (full):
The Billing template exposes a public "create draft" API surface (POST api/subscriptions/draft, gRPC CreateDraft) and documents a Draft → Active lifecycle in bounded-context docs. However, DefaultSubscriptionsProcessor.CreateSubscriptionAsync sets Status = SubscriptionLifecycleStatusEnumeration.Active immediately on create (line ~89). UpgradeAsync is documented as activating a draft but calls UpgradeSubscriptionAsync, which requires Active and performs edition uplift — there is no Draft→Active transition. ProvisionForTenantActivationAsync (tenant-activated saga reaction) also calls CreateSubscriptionAsync, so auto-provisioned subscriptions land in Active, bypassing draft semantics entirely.
The canonical blueprint expects Create to leave a subscription in Draft, with an explicit activate transition before billing-affecting operations. This mismatch breaks lifecycle invariants, confuses ServiceModel consumers, and prevents E2E scenarios where Entitlements assigns before activation.
Acceptance criteria (testable):
- AC-1:
CreateSubscriptionAsyncpersists new subscriptions withStatus = Draft; no code path sets Active on create unless explicitly activating. - AC-2: A distinct activate operation transitions Draft → Active;
UpgradeAsync/ edition change requires Active (or documents alternate path). - AC-3:
ProvisionForTenantActivationAsynceither creates Draft and activates in a second step, or is documented with ADR if auto-Active is intentional for saga path only. - AC-4: REST/gRPC responses and OpenAPI/gRPC proto reflect Draft status after create.
- AC-5: Unit tests in
SubscriptionAggregateTests(replacing placeholder) verify create→draft, activate→active, and reject upgrade on draft.
Implementation notes (full):
- Files to touch:
DefaultSubscriptionsProcessor.cs,ISubscriptionsProcessor.cs,ISubscriptionManagementService.cs,BillingController.cs/SubscriptionsController,GrpcSubscriptionManagementService.cs,SubscriptionLifecycleStatusEnumeration.cs - Code symbols:
CreateSubscriptionAsync,UpgradeSubscriptionAsync,ProvisionForTenantActivationAsync - ADRs: Consider note in
docs/adr/0001-one-aggregate-root-per-repo.mdif lifecycle VO changes - Tests: Replace placeholder
SubscriptionAggregateTests.cs; add processor unit tests for lifecycle transitions - Cross-reference: Tenant activation saga consumes
BillingConstants.InboundEventTopics.TenantActivated(see F02)
Out of scope:
- Invoice or payment side effects on activate (F03, F04)
- Promotion/proration on upgrade (F05)
Definition of done:
- All AC pass
- Tests added/updated and green
- Docs updated (
docs/bounded-context.md, JSON descriptor lifecycle if applicable) - Status updated in master + mirrors
saas-BIL-S01.1 — As a platform operator, I need subscriptions created in Draft so downstream contexts can assign entitlements before activation¶
Type: User Story
Parent: saas-BIL-F01
Implementation order: 040.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, lifecycle
Priority: P0
Effort: M
Dependencies: —
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Implement the Draft-first create path in DefaultSubscriptionsProcessor and align all entry points (REST, gRPC, tenant-activation saga) with the canonical subscription lifecycle. Today the processor contradicts its own ServiceModel contract by activating on create. The story delivers consistent state machine behavior: Create → Draft, Activate → Active, with validation guards on upgrade/change-edition/cancel.
Acceptance criteria (testable):
- AC-1: After
CreateAsync, persisted entity hasStatus == Draft. - AC-2:
ActivateAsync(new or renamed from Upgrade) transitions Draft → Active and publishes appropriate outbound event if applicable. - AC-3: Attempting
ChangeEditionAsyncorCancelAsyncon Draft follows documented rules (allow or reject with clear error). - AC-4: Tenant activation provisioning path documented and tested for expected final status.
Implementation notes (full):
- Primary file:
ConnectSoft.Saas.BillingTemplate/src/ConnectSoft.Saas.Billing.DomainModel.Impl/DefaultSubscriptionsProcessor.cs - Verify saga:
TenantActivatedBillingReactionStateMachine(or equivalent) after create semantics change - Update
ConnectSoft.Saas.Billing.jsondescriptor if lifecycle states listed
Out of scope: Public API rename debates beyond minimal clarity (e.g. keeping UpgradeAsync name with fixed semantics)
Definition of done:
- All AC pass
- Processor unit tests green
- Status updated
saas-BIL-T01.1.1 — Fix DefaultSubscriptionsProcessor.CreateSubscriptionAsync to persist Draft status¶
Type: Task
Parent: saas-BIL-S01.1
Implementation order: 040.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, processor
Priority: P0
Effort: S
Dependencies: —
Blocks: saas-BIL-T01.1.2
Source gap analysis: billing-analysis
Description (full):
Change CreateSubscriptionAsync so initial SubscriptionLifecycleStatusEnumeration is Draft, not Active. Audit all internal callers (ProvisionForTenantActivationAsync, sagas, tests) for assumptions of Active-on-create. Add explicit ActivateSubscriptionAsync if missing, or repurpose UpgradeSubscriptionAsync with Draft guard and rename for clarity.
Acceptance criteria (testable):
- AC-1: Single assignment to Draft on create in processor; no duplicate status logic in adapters.
- AC-2:
UpgradeSubscriptionAsyncthrows or returns domain error when status is Draft (until activate called). - AC-3: Existing Active-only tests updated or removed.
Implementation notes (full):
- Symbol:
DefaultSubscriptionsProcessor.CreateSubscriptionAsync - Enum:
SubscriptionLifecycleStatusEnumeration.Draft,.Active - Run full unit test suite after change
Out of scope: REST route renaming
Definition of done:
- All AC pass
- Tests green
saas-BIL-T01.1.2 — Add lifecycle unit tests and update ServiceModel documentation¶
Type: Task
Parent: saas-BIL-S01.1
Implementation order: 040.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, tests
Priority: P0
Effort: S
Dependencies: saas-BIL-T01.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Replace placeholder SubscriptionAggregateTests.cs (Assert.IsTrue(true)) with real tests covering create-draft, activate, and invalid transitions. Update XML docs on ISubscriptionManagementService and controller summaries to match processor behavior.
Acceptance criteria (testable):
- AC-1: At least three tests: create→draft, activate→active, upgrade-rejected-on-draft.
- AC-2: No placeholder assertions remain in
SubscriptionAggregateTests.cs. - AC-3: ServiceModel interface docs describe Draft-first lifecycle.
Implementation notes (full):
- Test project:
tests/ConnectSoft.Saas.Billing.UnitTests/SubscriptionAggregateTests.cs - Use in-memory or test doubles for
ISubscriptionsRepositoryper existing test patterns
Out of scope: Acceptance test host scenarios
Definition of done:
- All AC pass
- CI green
[041] saas-BIL-F02 — Inbound topic and quota payload harmonization¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 041
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, integration, P0
Priority: P0
Effort: M
Dependencies: saas-INTEG-F01, saas-INTEG-F02
Blocks: saas-BIL-F06
Source gap analysis: cross-topic-harmonization · cross-quota-payload
Description (full):
BillingConstants.InboundEventTopics drifts from canonical cross-repo published language and peer template constants. Evidence:
| Constant | Billing code | Canonical |
|---|---|---|
TenantActivated |
saas.tenants.v1.tenant-activated |
tenants.domain.v1.tenant-activated |
MeteringQuotaExceeded |
metering.quotas.v1.quota-exceeded |
metering.quota.v1.quota-exceeded |
EntitlementsEffectiveSnapshotChanged |
entitlements.effective.v1.effective-snapshot-changed |
entitlements.v1.effective-entitlements-updated |
CatalogProductUpdated |
catalog.products.v1.product-updated |
catalog.domain.v1.product-updated |
Additionally, consumer-side quota payload mismatch: Billing inbound DTO MeteringQuotaExceededInboundIntegrationEvent exposes MeterKey, while Metering publishes QuotaExceededIntegrationEvent.Dimension. MassTransit will not map across different property names without explicit mapping — E2E quota suspend path is broken even if topics align.
This feature aligns Billing as the consumer of Metering quota events: harmonize topic strings in BillingConstants and BillingMassTransitTopology, and resolve MeterKey vs Dimension (rename property, add alias, or configure explicit map) in coordination with saas-MET-F04.
Acceptance criteria (testable):
- AC-1: All four
BillingConstants.InboundEventTopicsvalues match canonical published language and peer repo constants. - AC-2:
BillingMassTransitTopologySetEntityNamebindings use updated constants. - AC-3:
MeteringQuotaExceededInboundIntegrationEventdeserializes quota events from Metering with meter/dimension identity populated (field name agreed with MET-F04). - AC-4:
MeteringQuotaExceededBillingReactionStateMachinecorrelates and processes test-published quota events end-to-end in integration test. - AC-5:
ConnectSoft.Saas.Billing.jsonconsumedEventssection lists inbound events with canonical topics.
Implementation notes (full):
- Files:
BillingConstants.cs,BillingMassTransitTopology.cs,MeteringQuotaExceededInboundIntegrationEvent.cs, saga state machines underFlowModel.MassTransit/ - Peer files:
TenantsConstants.EventTopics,MeteringConstants.EventTopics,EntitlementsConstants,ProductsCatalogConstants - Coordinate breaking change with
saas-INTEG-F01(platform-wide topic table) - Tests: enable
CrossRepoPublishedLanguageTests(F09) or add dedicated contract test
Out of scope:
- Outbound Billing topic changes (except F06/F07 additions)
- Event envelope fields (
schemaVersion,correlationId) —saas-INTEG-F03
Definition of done:
- All AC pass
- Cross-repo contract test or manual E2E verified with Metering publish
- Status updated
saas-BIL-S02.1 — As an integration engineer, I need Billing to consume canonical inbound topics so sagas receive peer events¶
Type: User Story
Parent: saas-BIL-F02
Implementation order: 041.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, masstransit
Priority: P0
Effort: M
Dependencies: saas-INTEG-F01
Blocks: saas-BIL-S02.2
Source gap analysis: cross-topic-harmonization
Description (full):
Update BillingConstants.InboundEventTopics and MassTransit topology so Billing sagas bind to the same entity names Tenants, Entitlements, Catalog, and Metering publish. Without this, tenant-activated and quota-exceeded messages never reach Billing consumers in a multi-service deployment.
Acceptance criteria (testable):
- AC-1: Constant values match published language doc table.
- AC-2: Each inbound saga has matching
SetEntityNamein topology configuration. - AC-3: JSON descriptor
consumedEventsupdated.
Implementation notes (full):
BillingConstants.cs— allInboundEventTopicsmembersBillingMassTransitTopology.cs—ConfigureInboundConsumedMessageTopology- Verify sagas: tenant activated, entitlements changed, catalog product updated, quota exceeded
Out of scope: Publisher-side changes in other repos (INTEG-F01)
Definition of done:
- All AC pass
- Topology configuration reviewed
saas-BIL-T02.1.1 — Update BillingConstants.InboundEventTopics to canonical values¶
Type: Task
Parent: saas-BIL-S02.1
Implementation order: 041.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, constants
Priority: P0
Effort: S
Dependencies: —
Blocks: saas-BIL-T02.1.2
Source gap analysis: cross-topic-harmonization
Description (full):
Replace drifted topic strings in BillingConstants.InboundEventTopics with canonical values. Search codebase for hard-coded legacy strings outside constants file and consolidate.
Acceptance criteria (testable):
- AC-1: Four topic constants updated per gap analysis table.
- AC-2: No remaining references to
saas.tenants.v1,metering.quotas.v1,entitlements.effective.v1,catalog.products.v1in Billing src.
Implementation notes (full):
- File:
src/ConnectSoft.Saas.Billing/BillingConstants.cs - Grep for legacy strings across
ConnectSoft.Saas.BillingTemplate
Out of scope: Metering publisher constants
Definition of done:
- All AC pass
saas-BIL-T02.1.2 — Align MeteringQuotaExceeded inbound DTO with Metering Dimension/MeterKey contract¶
Type: Task
Parent: saas-BIL-S02.1
Implementation order: 041.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, quota, acl
Priority: P0
Effort: S
Dependencies: saas-BIL-T02.1.1, saas-MET-F04
Blocks: —
Source gap analysis: cross-quota-payload
Description (full):
Fix consumer-side deserialization for quota exceeded events. Billing expects MeterKey on MeteringQuotaExceededInboundIntegrationEvent; Metering publishes Dimension on QuotaExceededIntegrationEvent. Choose canonical name (prefer Dimension per Metering aggregate language, or MeterKey per Billing bounded context) and apply consistently: rename property, add JSON alias, or explicit MassTransit/mapster map. Update MeteringQuotaExceededBillingReactionStateMachine and DefaultSubscriptionsProcessor.ReactToQuotaExceededAsync to use resolved property.
Acceptance criteria (testable):
- AC-1: Test message published from Metering-shaped DTO binds
Dimension/MeterKeycorrectly in Billing saga. - AC-2:
ReactToQuotaExceededAsyncreceives non-null meter identity in integration test. - AC-3: Decision recorded in code comment or ADR stub referencing INTEG-F02.
Implementation notes (full):
- Billing:
MeteringQuotaExceededInboundIntegrationEvent.cs, saga,ReactToQuotaExceededAsync - Metering peer:
QuotaExceededIntegrationEvent.cs(coordinate with MET-F04) - Consumer-side focus: Billing is the consumer; publisher may change in parallel
Out of scope: Threshold-crossed events (unless same mismatch exists)
Definition of done:
- All AC pass
- E2E quota path verified with Metering template
[042] saas-BIL-F03 — Invoice read-model projection or defer ADR¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 042
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, invoice, deferred
Priority: P1
Effort: L
Dependencies: —
Blocks: —
Source gap analysis: billing-analysis · ADR-BIL-001
Description (full):
Blueprint lists Invoice as a related aggregate; docs/out-of-scope.md defers Invoice entity to a future wave. Current implementation: DefaultSubscriptionsProcessor.RecordInvoiceIssuedAsync publishes InvoiceIssuedEvent only — no NHibernate entity, mapping, or read-model projector. JSON descriptor lists billing.invoices.v1.invoice-issued under publishedEvents without invoice entity in entityModel.
Strategic decision ADR-BIL-001 pending: (A) minimal read-model projection — invoice line items as read-only NHibernate entity fed by RecordInvoiceIssuedAsync; (B) explicit defer — events-only path with ADR and descriptor cleanup.
Acceptance criteria (testable):
- AC-1: ADR-BIL-001 authored under
docs/adr/with chosen option and rationale. - AC-2: If (A):
IInvoiceread entity + mapping + query endpoint or documented projector; if (B): out-of-scope doc updated, descriptor aligned, no phantom entity references. - AC-3:
RecordInvoiceIssuedAsyncbehavior documented for chosen path. - AC-4: Tests cover chosen path (projection mapping or event-only publish assertion).
Implementation notes (full):
- Files:
DefaultSubscriptionsProcessor.cs(RecordInvoiceIssuedAsync),InvoiceIssuedEvent.cs,docs/read-model-invoices.md,docs/out-of-scope.md,ConnectSoft.Saas.Billing.json - ADR:
docs/adr/0002-invoice-scope.md(suggested) - Tests: extend beyond
BillingLayerMappingProfileTestsinvoice enum-only coverage
Out of scope:
- Full Invoice aggregate with write lifecycle (separate bounded context)
- Payment capture (F04)
Definition of done:
- ADR merged
- All AC pass
- Docs and descriptor consistent
saas-BIL-S03.1 — As an architect, I need a decided invoice scope so implementers do not build the wrong abstraction¶
Type: User Story
Parent: saas-BIL-F03
Implementation order: 042.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, adr
Priority: P1
Effort: S
Dependencies: —
Blocks: saas-BIL-T03.1.2
Source gap analysis: decision-log
Description (full):
Facilitate and record ADR-BIL-001 decision between minimal read-model projection vs events-only deferral. Present tradeoffs: E2E demo needs vs DDD purity, NHibernate mapping cost, query API surface.
Acceptance criteria (testable):
- AC-1: ADR file exists with status Accepted.
- AC-2: Stakeholder sign-off noted in ADR or linked work item.
Implementation notes (full):
- Template: existing
docs/adr/0001-one-aggregate-root-per-repo.md - Link from
docs/out-of-scope.md
Out of scope: Implementation before ADR accepted
Definition of done:
- ADR accepted
saas-BIL-T03.1.1 — Draft ADR-BIL-001 with options A (projection) and B (events-only defer)¶
Type: Task
Parent: saas-BIL-S03.1
Implementation order: 042.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, adr
Priority: P1
Effort: S
Dependencies: —
Blocks: saas-BIL-T03.1.2
Source gap analysis: decision-log
Description (full):
Author ADR comparing minimal invoice read-model (NHibernate entity, no separate aggregate root) vs keeping InvoiceIssuedEvent as the only artifact with explicit deferral in out-of-scope.md and JSON descriptor trim.
Acceptance criteria (testable):
- AC-1: ADR documents consequences for
RecordInvoiceIssuedAsync, query APIs, and F08 public exposure. - AC-2: Both options have effort estimate (S/M/L).
Implementation notes (full):
- Path:
docs/adr/0002-invoice-scope.md
Out of scope: Code changes
Definition of done:
- ADR draft ready for review
saas-BIL-T03.1.2 — Implement ADR-BIL-001 outcome in processor and persistence layer¶
Type: Task
Parent: saas-BIL-S03.1
Implementation order: 042.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, invoice
Priority: P1
Effort: M
Dependencies: saas-BIL-T03.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Execute chosen ADR path. If projection: add read entity, NHibernate map, optional query method, wire RecordInvoiceIssuedAsync to persist projection row before/alongside event publish. If defer: remove misleading descriptor entries, strengthen out-of-scope doc, ensure event-only path tested.
Acceptance criteria (testable):
- AC-1: Code matches ADR decision; no contradictory docs.
- AC-2: At least one automated test validates invoice recording path.
Implementation notes (full):
- Processor:
RecordInvoiceIssuedAsync - Optional:
PersistenceModel.NHibernate/Mappings/
Out of scope: Payment provider integration
Definition of done:
- All AC pass
- Tests green
[043] saas-BIL-F04 — Payment provider ACL skeleton or defer ADR¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 043
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, payment, deferred
Priority: P1
Effort: L
Dependencies: —
Blocks: —
Source gap analysis: billing-analysis · ADR-BIL-002
Description (full):
Payment provider domain is explicitly out of scope in docs/out-of-scope.md ("Payment provider domain model (ACL only)"). DefaultSubscriptionsProcessor.RecordPaymentCapturedAsync publishes PaymentCapturedIntegrationEvent with no provider adapter, webhook handler, or anti-corruption layer. Strategic decision ADR-BIL-002: (A) skeleton ACL — IPaymentProviderAdapter interface, stub implementation, webhook ingress saga translating provider DTO → domain command; (B) defer with formal ADR and keep processor method as internal/test hook only.
Decommissioned vs Canceled naming drift (blueprint Decommissioned, code Canceled) may intersect cancel/payment flows — note for F01/F04 boundary.
Acceptance criteria (testable):
- AC-1: ADR-BIL-002 accepted documenting chosen path.
- AC-2: If (A): ACL interface + stub adapter + webhook handler skeleton registered in DI; if (B): out-of-scope strengthened, no public webhook surface.
- AC-3:
RecordPaymentCapturedAsyncdocumented as manual/internal vs provider-driven. - AC-4: Tests cover stub webhook → payment captured event path (A) or event-only unit test (B).
Implementation notes (full):
- Files:
DefaultSubscriptionsProcessor.cs,PaymentCapturedIntegrationEvent.cs,RecordPaymentCapturedRequest.cs,docs/out-of-scope.md - ADR:
docs/adr/0003-payment-acl.md(suggested) - InfrastructureModel for adapter registration pattern
Out of scope:
- Real Stripe/Adyen integration
- PCI compliance implementation
Definition of done:
- ADR merged
- All AC pass
saas-BIL-S04.1 — As a platform architect, I need a payment ACL decision before exposing payment webhooks¶
Type: User Story
Parent: saas-BIL-F04
Implementation order: 043.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, payment, adr
Priority: P1
Effort: S
Dependencies: —
Blocks: saas-BIL-T04.1.2
Source gap analysis: decision-log
Description (full):
Record ADR-BIL-002 choosing skeleton ACL vs defer. Define boundary: Billing publishes PaymentCapturedIntegrationEvent; provider specifics stay in InfrastructureModel adapter.
Acceptance criteria (testable):
- AC-1: ADR accepted with clear in/out of scope for wave-1.
- AC-2: Relationship to F08
RecordPaymentCapturedpublic API documented.
Implementation notes (full):
- Cross-link F03 invoice ADR for monetization story coherence
Out of scope: Production payment keys/secrets
Definition of done:
- ADR accepted
saas-BIL-T04.1.1 — Author ADR-BIL-002 payment provider ACL options¶
Type: Task
Parent: saas-BIL-S04.1
Implementation order: 043.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, adr
Priority: P1
Effort: S
Dependencies: —
Blocks: saas-BIL-T04.1.2
Source gap analysis: decision-log
Description (full):
Write ADR with options: skeleton adapter + webhook saga vs explicit defer. Include folder layout (InfrastructureModel/Payment/), saga naming, and test strategy.
Acceptance criteria (testable):
- AC-1: ADR file in
docs/adr/. - AC-2: References
RecordPaymentCapturedAsyncandPaymentCapturedIntegrationEvent.
Implementation notes (full):
- Suggested path:
docs/adr/0003-payment-acl.md
Out of scope: Implementation
Definition of done:
- ADR draft complete
saas-BIL-T04.1.2 — Implement ADR-BIL-002 skeleton ACL or document defer path¶
Type: Task
Parent: saas-BIL-S04.1
Implementation order: 043.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, payment
Priority: P1
Effort: M
Dependencies: saas-BIL-T04.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
If skeleton: add IPaymentProviderAdapter, StubPaymentProviderAdapter, optional PaymentWebhookController → saga → RecordPaymentCapturedAsync. If defer: update out-of-scope, add code comments on processor method, ensure no accidental public webhook routes.
Acceptance criteria (testable):
- AC-1: DI registration for adapter (A) or explicit "deferred" guard (B).
- AC-2: Unit test proves payment captured event publishes with expected payload.
Implementation notes (full):
- Processor:
RecordPaymentCapturedAsync - Event:
PaymentCapturedIntegrationEvent
Out of scope: Live provider credentials
Definition of done:
- All AC pass
[044] saas-BIL-F05 — Promotion, proration, and seat policy domain¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 044
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, domain
Priority: P1
Effort: L
Dependencies: saas-BIL-F11
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Blueprint expects PromotionApplication entity, ProrationPolicy VO, and SeatPolicy VO on Subscription. Current code: ChangeEditionAsync creates SubscriptionCycleEntity assignment rows only. No promotion or proration types. Seat-related child is a mis-copied Entitlements TenantFeatureOverride renamed to SubscriptionSeatPolicy with feature on/off semantics — not min/max/current seats (see F11).
Implement or explicitly scaffold blueprint children with processor behavior hooks for edition change and seat changes.
Acceptance criteria (testable):
- AC-1: Domain types exist:
PromotionApplication(or documented defer),ProrationPolicyVO,SeatPolicyVO with seat semantics. - AC-2:
ChangeEditionAsyncapplies proration policy when configured (or documents MVP without proration calc). - AC-3: Seat changes validate against
SeatPolicymin/max. - AC-4: NHibernate mappings and JSON descriptor updated.
- AC-5: Unit tests cover seat validation and edition change with cycle assignment.
Implementation notes (full):
- Files: EntityModel,
DefaultSubscriptionsProcessor.cs(ChangeEditionAsync), mappings - Coordinate with F11 cleanup of mis-copied
SubscriptionSeatPolicy - Docs:
docs/bounded-context.md,docs/aggregate-root.md
Out of scope:
- External promotion catalog service
- Tax calculation
Definition of done:
- All AC pass
- Tests green
saas-BIL-S05.1 — As a product owner, I need seat and proration policies modeled so edition changes respect commercial rules¶
Type: User Story
Parent: saas-BIL-F05
Implementation order: 044.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, domain
Priority: P1
Effort: L
Dependencies: saas-BIL-F11
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Introduce blueprint-aligned value objects and wire DefaultSubscriptionsProcessor edition/seat paths. Replace mis-copied feature-override semantics with true seat policy where F11 has cleaned naming.
Acceptance criteria (testable):
- AC-1:
SeatPolicyenforces min ≤ current ≤ max on seat update operations. - AC-2:
ProrationPolicyattached to subscription or cycle entity and referenced on edition change. - AC-3: Processor rejects invalid seat counts with domain exception.
Implementation notes (full):
- Symbols:
ChangeEditionAsync,SubscriptionCycleEntity, new VOs - Align with
ConnectSoft.Saas.Billing.jsonentityModel
Out of scope: Full promotion engine
Definition of done:
- All AC pass
saas-BIL-T05.1.1 — Add ProrationPolicy and SeatPolicy value objects to entity model¶
Type: Task
Parent: saas-BIL-S05.1
Implementation order: 044.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, entity
Priority: P1
Effort: M
Dependencies: saas-BIL-F11
Blocks: saas-BIL-T05.1.2
Source gap analysis: billing-analysis
Description (full):
Create IProrationPolicy / ProrationPolicyEntity (or embedded VO) and ISeatPolicy with MinSeats, MaxSeats, CurrentSeats. Add NHibernate mappings. Remove dependency on mis-copied SubscriptionSeatPolicy feature-override shape.
Acceptance criteria (testable):
- AC-1: Types in EntityModel with POCO implementations.
- AC-2: Mappings in PersistenceModel.NHibernate.
- AC-3: JSON descriptor lists new VOs/entities.
Implementation notes (full):
- Replace or refactor
SubscriptionSeatPolicyEntityper F11 - Reference canonical DDD entities doc
Out of scope: PromotionApplication full entity (optional stub)
Definition of done:
- All AC pass
saas-BIL-T05.1.2 — Wire DefaultSubscriptionsProcessor edition and seat validation¶
Type: Task
Parent: saas-BIL-S05.1
Implementation order: 044.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, processor
Priority: P1
Effort: M
Dependencies: saas-BIL-T05.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Update ChangeEditionAsync to reference ProrationPolicy and create cycle rows per policy. Add seat update method or extend change edition with seat policy checks via DefaultSubscriptionsProcessor.
Acceptance criteria (testable):
- AC-1: Edition change persists cycle assignment and policy snapshot.
- AC-2: Seat violation throws
BillingExceptionor domain equivalent. - AC-3: Unit tests for happy path and seat violation.
Implementation notes (full):
- Primary:
DefaultSubscriptionsProcessor.cs
Out of scope: Public REST for seat-only update unless needed
Definition of done:
- All AC pass
- Tests green
[045] saas-BIL-F06 — Suspend-on-quota outbound integration event¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 045
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, quota, P0
Priority: P0
Effort: M
Dependencies: saas-BIL-F02, saas-MET-F04
Blocks: —
Source gap analysis: billing-analysis
Description (full):
When Metering publishes quota exceeded (after F02/MET-F04 contract fix), MeteringQuotaExceededBillingReactionStateMachine invokes DefaultSubscriptionsProcessor.ReactToQuotaExceededAsync, which suspends Active subscriptions and persists — silently. No outbound integration event is published; saga ends with await Task.CompletedTask. Entitlements and other peers cannot react to billing-initiated suspension.
Add SubscriptionSuspendedIntegrationEvent (or equivalent) under BillingConstants.EventTopics, publish from processor on quota-driven suspend, register topology outbound binding, update JSON descriptor publishedEvents.
Acceptance criteria (testable):
- AC-1: Quota-triggered suspend publishes outbound event with tenantId, subscriptionId, reason=quota, meter/dimension identity.
- AC-2: Event topic follows published language pattern
billing.subscriptions.v1.subscription-suspended(or documented canonical name). - AC-3: Manual suspend path (if any) also publishes same event for consistency.
- AC-4: Integration test: Metering quota exceeded → Billing suspend → outbound event observable.
- AC-5:
DefaultSubscriptionsProcessorno longer ends quota reaction without event bus publish.
Implementation notes (full):
- Files:
DefaultSubscriptionsProcessor.cs(ReactToQuotaExceededAsync,ApplyTransition), new event in MessagingModel,BillingMassTransitTopology.cs,BillingConstants.EventTopics - Saga:
MeteringQuotaExceededBillingReactionStateMachine.cs - Depends on F02 for inbound quota delivery and Dimension/MeterKey resolution
Out of scope:
- Entitlements consumer implementation (Entitlements backlog)
- Un-suspend on quota recovery
Definition of done:
- All AC pass
- E2E with Metering verified
saas-BIL-S06.1 — As Entitlements service, I need notification when Billing suspends a subscription due to quota¶
Type: User Story
Parent: saas-BIL-F06
Implementation order: 045.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, events
Priority: P0
Effort: M
Dependencies: saas-BIL-F02
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Publish subscription suspended integration event when quota reaction transitions subscription to Suspended. Event must include correlation ids when envelope work (INTEG-F03) lands; minimum fields per published language.
Acceptance criteria (testable):
- AC-1: Event type exists and is published on quota suspend.
- AC-2: Topology registers outbound entity name.
- AC-3: Descriptor documents published event.
Implementation notes (full):
- Follow pattern of
SubscriptionCreatedIntegrationEventpublish in processor
Out of scope: Consumer sagas in other repos
Definition of done:
- All AC pass
saas-BIL-T06.1.1 — Add SubscriptionSuspendedIntegrationEvent and BillingConstants topic¶
Type: Task
Parent: saas-BIL-S06.1
Implementation order: 045.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, messaging
Priority: P0
Effort: S
Dependencies: saas-BIL-F02
Blocks: saas-BIL-T06.1.2
Source gap analysis: billing-analysis
Description (full):
Create event DTO in MessagingModel, add topic constant, configure outbound topology SetEntityName.
Acceptance criteria (testable):
- AC-1: Event class with required published-language fields (subset until INTEG-F03).
- AC-2: Constant in
BillingConstants.EventTopics. - AC-3: Topology outbound registration.
Implementation notes (full):
MessagingModel/Events/,BillingConstants.cs,BillingMassTransitTopology.cs
Out of scope: Envelope v2 fields
Definition of done:
- All AC pass
saas-BIL-T06.1.2 — Publish event from DefaultSubscriptionsProcessor.ReactToQuotaExceededAsync¶
Type: Task
Parent: saas-BIL-S06.1
Implementation order: 045.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, processor
Priority: P0
Effort: S
Dependencies: saas-BIL-T06.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
After ApplyTransition(..., Suspended) in quota reaction, invoke event bus to publish SubscriptionSuspendedIntegrationEvent. Include meter key/dimension from inbound quota event (post F02 fix). Add unit test with mock event bus.
Acceptance criteria (testable):
- AC-1:
IEventBus.PublishEvent(or equivalent) called once on successful suspend. - AC-2: No publish when subscription already suspended or not found.
- AC-3: Unit test verifies publish invocation.
Implementation notes (full):
- Symbol:
ReactToQuotaExceededAsync,ApplyTransition - Use meter identity field aligned with MET-F04
Out of scope: Saga test (optional)
Definition of done:
- All AC pass
- Tests green
[046] saas-BIL-F07 — Rating-window outbound event to Metering¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 046
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, metering, integration
Priority: P2
Effort: M
Dependencies: saas-BIL-F01
Blocks: saas-MET-F02
Source gap analysis: billing-analysis
Description (full):
Cross-service diagram (saas-aggregate-root-assignment.md) shows Billing → Metering edge via subscription.period.rating-window. Missing entirely in Billing: no event DTO, constant, topology binding, or processor publish path. Outbound topics limited to six in BillingConstants.EventTopics.
Implement rating-window signal so Metering can roll counters or open new billing windows at period boundaries. Coordinate topic name with Metering inbound consumption and MET-F02 Window VO semantics.
Acceptance criteria (testable):
- AC-1:
RatingWindowOpenedIntegrationEvent(or canonical name) defined with period start/end, subscriptionId, tenantId. - AC-2: Topic constant and outbound topology registration.
- AC-3: Processor or scheduled job publishes on billing period boundary (MVP: manual/API trigger documented if scheduler deferred).
- AC-4: JSON descriptor lists new published event.
- AC-5: Documented contract for Metering consumer (MET-F02 roll semantics).
Implementation notes (full):
- New MessagingModel event,
BillingConstants.EventTopics,BillingMassTransitTopology.cs - Trigger: Hangfire job, processor method on activate/renew, or explicit
OpenRatingWindowAsyncon processor - Cross-repo: Metering may add inbound saga in separate feature
Out of scope:
- Metering consumer implementation (Metering backlog)
- Full billing calendar engine
Definition of done:
- All AC pass
- Contract documented for Metering team
saas-BIL-S07.1 — As Metering service, I need billing period boundaries signaled so usage windows can roll¶
Type: User Story
Parent: saas-BIL-F07
Implementation order: 046.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, metering
Priority: P2
Effort: M
Dependencies: saas-BIL-F01
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Define and publish rating-window event from Billing when a subscription billing period starts or rolls. Enables Metering Window VO roll semantics (MET-F02).
Acceptance criteria (testable):
- AC-1: Event published with period boundaries.
- AC-2: At least one code path triggers publish (API or processor hook).
- AC-3: Published language doc cross-link or inline comment with canonical topic.
Implementation notes (full):
- Coordinate topic with platform published language maintainers
Out of scope: Automated cron unless Hangfire already configured
Definition of done:
- All AC pass
saas-BIL-T07.1.1 — Define RatingWindow integration event and outbound topology¶
Type: Task
Parent: saas-BIL-S07.1
Implementation order: 046.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, messaging
Priority: P2
Effort: S
Dependencies: —
Blocks: saas-BIL-T07.1.2
Source gap analysis: billing-analysis
Description (full):
Add event type, BillingConstants.EventTopics.RatingWindowOpened (or agreed name), topology outbound config, descriptor entry.
Acceptance criteria (testable):
- AC-1: Event and constant merged.
- AC-2: Topology test or configuration review checklist completed.
Implementation notes (full):
- Follow existing outbound event patterns in MessagingModel
Out of scope: Metering inbound
Definition of done:
- All AC pass
saas-BIL-T07.1.2 — Implement publish trigger in DefaultSubscriptionsProcessor¶
Type: Task
Parent: saas-BIL-S07.1
Implementation order: 046.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, processor
Priority: P2
Effort: S
Dependencies: saas-BIL-T07.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Add PublishRatingWindowOpenedAsync or hook into activate/renew paths in DefaultSubscriptionsProcessor to emit rating-window event with computed period from subscription cycle.
Acceptance criteria (testable):
- AC-1: Method callable from ServiceModel or internal scheduler.
- AC-2: Unit test asserts event publish with correct period dates.
Implementation notes (full):
DefaultSubscriptionsProcessor.cs, subscription cycle entities
Out of scope: Hangfire scheduling (document as follow-up)
Definition of done:
- All AC pass
[047] saas-BIL-F08 — Public ServiceModel operations for internal processor ops¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 047
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, servicemodel
Priority: P1
Effort: M
Dependencies: saas-BIL-F01, saas-BIL-F03, saas-BIL-F04
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Three operations exist on ISubscriptionsProcessor / DefaultSubscriptionsProcessor but are absent from public ISubscriptionManagementService and REST/gRPC adapters:
RecordInvoiceIssuedAsyncRecordPaymentCapturedAsyncRequestEntitlementsSyncAsync
Public write surface today: Create, Upgrade, ChangeEdition, Cancel only. Internal sagas and future webhooks need stable ServiceModel contracts for invoice recording, payment capture, and entitlements sync requests per published language consumer matrix.
Acceptance criteria (testable):
- AC-1: Three methods on
ISubscriptionManagementServicewith request/response DTOs. - AC-2: REST routes and gRPC RPCs implemented mirroring existing patterns in
BillingController/GrpcSubscriptionManagementService. - AC-3: Adapters delegate to processor only (no duplicated logic).
- AC-4: OpenAPI/gRPC proto regenerated or updated.
- AC-5: Architecture tests still pass (layering).
Implementation notes (full):
- Files:
ISubscriptionManagementService.cs,RecordInvoiceIssuedRequest.cs,RecordPaymentCapturedRequest.cs,RequestEntitlementsSyncRequest.cs, REST/gRPC adapters - Processor symbols already exist — expose only
- Depends on F01 lifecycle for sensible authorization rules
Out of scope:
- Payment webhook ingress (F04)
- Invoice projection persistence (F03) beyond existing processor behavior
Definition of done:
- All AC pass
- API docs updated
saas-BIL-S08.1 — As an external integrator, I need public APIs to record invoices and payments and request entitlements sync¶
Type: User Story
Parent: saas-BIL-F08
Implementation order: 047.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, api
Priority: P1
Effort: M
Dependencies: saas-BIL-F01
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Expose processor capabilities through ServiceModel package so gateways and automation can invoke them without referencing DomainModel.
Acceptance criteria (testable):
- AC-1: REST POST endpoints for three operations with validation.
- AC-2: gRPC methods with equivalent contracts.
- AC-3: ServiceModel NuGet consumers can call without Application layer reference.
Implementation notes (full):
- Pattern: existing
CreateAsync/CancelAsyncadapter flow
Out of scope: Authentication policy (platform concern)
Definition of done:
- All AC pass
saas-BIL-T08.1.1 — Extend ISubscriptionManagementService and request DTOs¶
Type: Task
Parent: saas-BIL-S08.1
Implementation order: 047.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, servicemodel
Priority: P1
Effort: S
Dependencies: saas-BIL-F01
Blocks: saas-BIL-T08.1.2
Source gap analysis: billing-analysis
Description (full):
Add interface methods mapping to RecordInvoiceIssuedAsync, RecordPaymentCapturedAsync, RequestEntitlementsSyncAsync. Ensure DTOs in ServiceModel project are complete for public consumption.
Acceptance criteria (testable):
- AC-1: Interface methods with XML documentation.
- AC-2: DTOs validated with data annotations or equivalent.
Implementation notes (full):
ServiceModel/ISubscriptionManagementService.cs, existing request types
Out of scope: Adapter implementation
Definition of done:
- All AC pass
saas-BIL-T08.1.2 — Implement REST and gRPC adapters delegating to DefaultSubscriptionsProcessor¶
Type: Task
Parent: saas-BIL-S08.1
Implementation order: 047.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, rest, grpc
Priority: P1
Effort: S
Dependencies: saas-BIL-T08.1.1
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Wire controllers and gRPC service to processor for three new operations. Add minimal integration or unit tests for adapter delegation.
Acceptance criteria (testable):
- AC-1: REST routes documented in OpenAPI.
- AC-2: gRPC service implements new RPCs.
- AC-3: No DomainModel types leak into ServiceModel responses.
Implementation notes (full):
ServiceModel.RestApi/BillingController.cs,ServiceModel.Grpc/GrpcSubscriptionManagementService.cs
Out of scope: E2E acceptance tests
Definition of done:
- All AC pass
- Layering tests green
[048] saas-BIL-F09 — Architecture test enforcement¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 048
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, architecture-tests
Priority: P1
Effort: M
Dependencies: saas-BIL-F02
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Billing ArchitectureTests project has real NetArch rules in ServiceModelLayeringNetArchTests and EntityIsolationNetArchTests, but OneAggregateRootPerRepoTests and CrossRepoPublishedLanguageTests are placeholders (Assert.IsTrue(true)). ADR-0001 claims CI enforcement via ArchitectureTests — placeholders undermine gap program exit criteria.
Replace placeholders with rules: single aggregate root (Subscription), public surface confinement, and validation that BillingConstants inbound/outbound topics match allowed set from published language (including post-F02 canonical values).
Acceptance criteria (testable):
- AC-1:
OneAggregateRootPerRepoTestsfails if secondIAggregateRootintroduced. - AC-2:
CrossRepoPublishedLanguageTestsvalidates topic constants against embedded canonical list or shared test data from INTEG program. - AC-3: No placeholder
Assert.IsTrue(true)in ArchitectureTests project. - AC-4: CI pipeline runs ArchitectureTests on every PR.
Implementation notes (full):
- Path:
tests/ConnectSoft.Saas.Billing.ArchitectureTests/ - Reference Products Catalog implementation as exemplar (real CrossRepo test)
- Include
BillingConstantsdrift regression after F02
Out of scope:
- Processor functional tests (unit test backlog)
Definition of done:
- All AC pass
- CI green
saas-BIL-S09.1 — As a platform maintainer, I need CI to block aggregate and published-language regressions¶
Type: User Story
Parent: saas-BIL-F09
Implementation order: 048.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, netarch
Priority: P1
Effort: M
Dependencies: saas-BIL-F02
Blocks: —
Source gap analysis: billing-analysis
Description (full):
Implement real NetArchTest rules so Billing template cannot regress to scaffold placeholders or reintroduce BillingConstants topic drift.
Acceptance criteria (testable):
- AC-1: Both placeholder test files contain real assertions.
- AC-2: Tests fail on intentional violation (verified locally once).
Implementation notes (full):
- NetArchTest.Rules patterns from Catalog template
Out of scope: Cross-repo NuGet shared test library (INTEG-F07)
Definition of done:
- All AC pass
saas-BIL-T09.1.1 — Implement OneAggregateRootPerRepoTests with NetArchTest¶
Type: Task
Parent: saas-BIL-S09.1
Implementation order: 048.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, netarch
Priority: P1
Effort: S
Dependencies: —
Blocks: saas-BIL-T09.1.2
Source gap analysis: billing-analysis
Description (full):
Replace placeholder with rule: exactly one type implementing aggregate root marker in EntityModel, matching Subscription.
Acceptance criteria (testable):
- AC-1: Test enumerates aggregate roots and asserts count == 1.
- AC-2: Removes
Assert.IsTrue(true).
Implementation notes (full):
- File:
OneAggregateRootPerRepoTests.cs - ADR:
docs/adr/0001-one-aggregate-root-per-repo.md
Out of scope: Invoice as second root (deferred per F03)
Definition of done:
- All AC pass
saas-BIL-T09.1.2 — Implement CrossRepoPublishedLanguageTests for BillingConstants topics¶
Type: Task
Parent: saas-BIL-S09.1
Implementation order: 048.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, netarch
Priority: P1
Effort: S
Dependencies: saas-BIL-F02, saas-BIL-T09.1.1
Blocks: —
Source gap analysis: cross-topic-harmonization
Description (full):
Assert BillingConstants.InboundEventTopics and EventTopics values match canonical table (tenants.domain.v1, metering.quota.v1, etc.). Optionally scan MessagingModel public types for surface rules.
Acceptance criteria (testable):
- AC-1: Test fails if TenantActivated reverts to
saas.tenants.v1. - AC-2: Test fails if MeteringQuotaExceeded reverts to
metering.quotas.v1. - AC-3: Removes placeholder assertion.
Implementation notes (full):
- File:
CrossRepoPublishedLanguageTests.cs - Canonical list from published language doc or shared constants
Out of scope: Envelope field validation
Definition of done:
- All AC pass
[049] saas-BIL-F10 — Orleans grain write-path wiring¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 049
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, orleans
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06, saas-BIL-F01
Blocks: —
Source gap analysis: cross-orleans
Description (full):
SubscriptionEditorGrain implements ISubscriptionEditorGrain / ISubscriptionEditorActor and delegates to DefaultSubscriptionsProcessor for Create, Upgrade, ChangeEdition, Cancel. Orleans registered via OrleansExtensions.UseMicroserviceOrleans. No GetGrain<ISubscriptionEditorGrain> in Billing src/ — REST/gRPC adapters inject ISubscriptionsProcessor directly, bypassing actor model.
Per platform decision INTEG-F06 / ADR-INTEG-001, wire write commands through grain for single-writer semantics and partition affinity, or document processor-only ADR if grains remain test-only.
Acceptance criteria (testable):
- AC-1: REST/gRPC write operations resolve grain by subscription id and invoke grain methods (if Option A).
- AC-2: Grain methods still delegate to processor (no duplicated domain logic).
- AC-3: Orleans silo tests or unit tests with test cluster for at least Create path.
- AC-4: ADR-INTEG-001 referenced; Billing-specific notes in
docs/adr/if needed.
Implementation notes (full):
- Files:
SubscriptionEditorGrain.cs,BillingController.cs,GrpcSubscriptionManagementService.cs,OrleansExtensions.cs - Pattern: Tenants
TenantEditorGrainwiring when INTEG-F06 lands - Grain key: subscription id string
Out of scope:
- Read path caching via grain
- Cross-grain transactions
Definition of done:
- All AC pass
- Grain test green
saas-BIL-S10.1 — As a platform architect, I need Billing writes routed through Orleans grains per actor model¶
Type: User Story
Parent: saas-BIL-F10
Implementation order: 049.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, orleans
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06
Blocks: —
Source gap analysis: cross-orleans
Description (full):
Refactor ServiceModel adapters to call GetGrain<ISubscriptionEditorGrain>(subscriptionId) for writes instead of injecting processor directly. Maintains consistency with SaaS platform actor model.
Acceptance criteria (testable):
- AC-1: No direct processor injection in write methods of REST/gRPC adapters (processor only inside grain).
- AC-2: Create/Upgrade/ChangeEdition/Cancel go through grain.
- AC-3: DI registration unchanged for processor (grain dependency).
Implementation notes (full):
- Inject
IGrainFactoryinto adapters - Follow Catalog
ProductEditorGrainpattern when available
Out of scope: Saga grain routing
Definition of done:
- All AC pass
saas-BIL-T10.1.1 — Refactor REST adapter to use ISubscriptionEditorGrain for writes¶
Type: Task
Parent: saas-BIL-S10.1
Implementation order: 049.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, rest, orleans
Priority: P1
Effort: S
Dependencies: saas-INTEG-F06
Blocks: saas-BIL-T10.1.2
Source gap analysis: cross-orleans
Description (full):
Update BillingController / SubscriptionsController write actions to resolve grain and await grain methods.
Acceptance criteria (testable):
- AC-1: All write endpoints use grain.
- AC-2: Layering tests pass (REST may reference ActorModel.Orleans via factory abstraction if needed).
Implementation notes (full):
ServiceModel.RestApi/BillingController.cs
Out of scope: gRPC (separate task)
Definition of done:
- All AC pass
saas-BIL-T10.1.2 — Refactor gRPC adapter and add SubscriptionEditorGrain silo test¶
Type: Task
Parent: saas-BIL-S10.1
Implementation order: 049.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, grpc, orleans
Priority: P1
Effort: M
Dependencies: saas-BIL-T10.1.1
Blocks: —
Source gap analysis: cross-orleans
Description (full):
Update GrpcSubscriptionManagementService for grain routing. Add silo test project test for Create via grain (pattern from Catalog ProductEditorGrainSiloTests).
Acceptance criteria (testable):
- AC-1: gRPC write RPCs use grain.
- AC-2: At least one silo test activates grain and verifies processor delegation.
Implementation notes (full):
ServiceModel.Grpc/GrpcSubscriptionManagementService.cs- New or extended test in ArchitectureTests or dedicated Orleans test project
Out of scope: Full grain coverage for all ops
Definition of done:
- All AC pass
- Silo test green
[050] saas-BIL-F11 — SubscriptionSeatPolicy scaffold cleanup¶
Type: Feature
Parent: saas-EPIC-BIL
Implementation order: 050
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, cleanup
Priority: P1
Effort: M
Dependencies: —
Blocks: saas-BIL-F05
Source gap analysis: billing-analysis
Description (full):
Partial scaffold rename from Entitlements TenantFeatureOverride: filenames still TenantFeatureOverride*, types renamed to SubscriptionSeatPolicy*. Semantics are feature overrides (FeatureKey, IsEnabled, Reason) — not seat policy (min/max/current). Collection on ISubscription still named FeatureOverrides.
Evidence:
- ITenantFeatureOverride.cs defines ISubscriptionSeatPolicy
- TenantFeatureOverrideEntity.cs → class SubscriptionSeatPolicyEntity
- TenantFeatureOverrideEntityMap.cs → SubscriptionSeatPolicyEntityMap
- TenantFeatureOverrideDto.cs → SubscriptionSeatPolicyDto
- SubscriptionEntity.FeatureOverrides collection
- build/scaffold-replacements-billing.ps1 rename script
Complete rename (files + properties), reshape to blueprint SeatPolicy VO or remove mis-copied entity; align with F05.
Acceptance criteria (testable):
- AC-1: No
TenantFeatureOverride*filenames remain unless ADR documents intentional retention. - AC-2: Type names and collection properties reflect
SeatPolicyor entity removed in favor of F05 VO. - AC-3: NHibernate mappings and DTOs consistent with renamed types.
- AC-4:
scaffold-replacements-billing.ps1updated or removed if obsolete. - AC-5: No feature-key/on-off semantics on seat policy types unless explicitly dual-purpose documented.
Implementation notes (full):
- EntityModel, PersistenceModel.NHibernate, ServiceModel DTOs, AutoMapper profiles
- Database migration if table/column rename required
- Coordinate with F05 for final SeatPolicy shape
Out of scope:
- PromotionApplication entity
Definition of done:
- All AC pass
- Migration tested
- F05 unblocked
saas-BIL-S11.1 — As a developer, I need consistent seat policy naming without Entitlements scaffold drift¶
Type: User Story
Parent: saas-BIL-F11
Implementation order: 050.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, cleanup
Priority: P1
Effort: M
Dependencies: —
Blocks: saas-BIL-F05
Source gap analysis: billing-analysis
Description (full):
Remove copy-paste artifact from Entitlements template. Either delete mis-copied entity and prepare for F05 SeatPolicy VO, or rename comprehensively and reshape properties to seat counts.
Acceptance criteria (testable):
- AC-1: Codebase search shows no
TenantFeatureOverridein Billing (except git history). - AC-2: Subscription aggregate child collection named consistently (
SeatPoliciesor embedded VO).
Implementation notes (full):
- Run
scaffold-replacements-billing.ps1audit - Update
BillingLayerMappingProfileTestsif affected
Out of scope: Data migration production rollout
Definition of done:
- All AC pass
saas-BIL-T11.1.1 — Rename files and types from TenantFeatureOverride to SeatPolicy¶
Type: Task
Parent: saas-BIL-S11.1
Implementation order: 050.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, refactor
Priority: P1
Effort: M
Dependencies: —
Blocks: saas-BIL-T11.1.2
Source gap analysis: billing-analysis
Description (full):
Rename source files, interfaces, entities, maps, DTOs to SeatPolicy* naming. Update ISubscription collection property. Add DB migration script for table rename if applicable.
Acceptance criteria (testable):
- AC-1: All references compile.
- AC-2: Filenames match type names.
Implementation notes (full):
- EntityModel, PersistenceModel, ServiceModel, Mapping profiles
Out of scope: Seat count semantics (F05)
Definition of done:
- All AC pass
- Build green
saas-BIL-T11.1.2 — Remove feature-override semantics and update tests/docs¶
Type: Task
Parent: saas-BIL-S11.1
Implementation order: 050.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Billing
Iteration: TBD
Tags: saas-platform, gap, billing, cleanup
Priority: P1
Effort: S
Dependencies: saas-BIL-T11.1.1
Blocks: saas-BIL-F05
Source gap analysis: billing-analysis
Description (full):
Remove or replace FeatureKey, IsEnabled, Reason properties with seat placeholders (Min/Max/Current) stubbed for F05, or delete entity if switching to embedded VO only. Update bounded-context docs and mapping tests.
Acceptance criteria (testable):
- AC-1: No Entitlements-style feature override properties on seat types unless documented.
- AC-2: Docs describe seat policy accurately.
- AC-3: Mapping tests updated and passing.
Implementation notes (full):
docs/bounded-context.md,docs/aggregate-root.mdBillingLayerMappingProfileTests.cs
Out of scope: Full F05 processor wiring
Definition of done:
- All AC pass
- F05 ready to start