Skip to content

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: CreateSubscriptionAsync persists new subscriptions with Status = 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: ProvisionForTenantActivationAsync either 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.md if 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 has Status == Draft.
  • AC-2: ActivateAsync (new or renamed from Upgrade) transitions Draft → Active and publishes appropriate outbound event if applicable.
  • AC-3: Attempting ChangeEditionAsync or CancelAsync on 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.json descriptor 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: UpgradeSubscriptionAsync throws 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 ISubscriptionsRepository per 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.InboundEventTopics values match canonical published language and peer repo constants.
  • AC-2: BillingMassTransitTopology SetEntityName bindings use updated constants.
  • AC-3: MeteringQuotaExceededInboundIntegrationEvent deserializes quota events from Metering with meter/dimension identity populated (field name agreed with MET-F04).
  • AC-4: MeteringQuotaExceededBillingReactionStateMachine correlates and processes test-published quota events end-to-end in integration test.
  • AC-5: ConnectSoft.Saas.Billing.json consumedEvents section lists inbound events with canonical topics.

Implementation notes (full):

  • Files: BillingConstants.cs, BillingMassTransitTopology.cs, MeteringQuotaExceededInboundIntegrationEvent.cs, saga state machines under FlowModel.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 SetEntityName in topology configuration.
  • AC-3: JSON descriptor consumedEvents updated.

Implementation notes (full):

  • BillingConstants.cs — all InboundEventTopics members
  • BillingMassTransitTopology.csConfigureInboundConsumedMessageTopology
  • 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.v1 in 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/MeterKey correctly in Billing saga.
  • AC-2: ReactToQuotaExceededAsync receives 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): IInvoice read entity + mapping + query endpoint or documented projector; if (B): out-of-scope doc updated, descriptor aligned, no phantom entity references.
  • AC-3: RecordInvoiceIssuedAsync behavior 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 BillingLayerMappingProfileTests invoice 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: RecordPaymentCapturedAsync documented 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 RecordPaymentCaptured public 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 RecordPaymentCapturedAsync and PaymentCapturedIntegrationEvent.

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), ProrationPolicy VO, SeatPolicy VO with seat semantics.
  • AC-2: ChangeEditionAsync applies proration policy when configured (or documents MVP without proration calc).
  • AC-3: Seat changes validate against SeatPolicy min/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: SeatPolicy enforces min ≤ current ≤ max on seat update operations.
  • AC-2: ProrationPolicy attached 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.json entityModel

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 SubscriptionSeatPolicyEntity per 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 BillingException or 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: DefaultSubscriptionsProcessor no 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 SubscriptionCreatedIntegrationEvent publish 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 OpenRatingWindowAsync on 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:

  • RecordInvoiceIssuedAsync
  • RecordPaymentCapturedAsync
  • RequestEntitlementsSyncAsync

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 ISubscriptionManagementService with 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 / CancelAsync adapter 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: OneAggregateRootPerRepoTests fails if second IAggregateRoot introduced.
  • AC-2: CrossRepoPublishedLanguageTests validates 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 BillingConstants drift 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 TenantEditorGrain wiring 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 IGrainFactory into adapters
  • Follow Catalog ProductEditorGrain pattern 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.csSubscriptionSeatPolicyEntityMap - TenantFeatureOverrideDto.csSubscriptionSeatPolicyDto - 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 SeatPolicy or entity removed in favor of F05 VO.
  • AC-3: NHibernate mappings and DTOs consistent with renamed types.
  • AC-4: scaffold-replacements-billing.ps1 updated 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 TenantFeatureOverride in Billing (except git history).
  • AC-2: Subscription aggregate child collection named consistently (SeatPolicies or embedded VO).

Implementation notes (full):

  • Run scaffold-replacements-billing.ps1 audit
  • Update BillingLayerMappingProfileTests if 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.md
  • BillingLayerMappingProfileTests.cs

Out of scope: Full F05 processor wiring

Definition of done:

  • All AC pass
  • F05 ready to start