Epic saas-EPIC-MET — Metering gap implementation¶
Epic ID: saas-EPIC-MET
Repository: ConnectSoft.Saas.MeteringTemplate
Aggregate root: UsageMeter
Last verified: 2026-05-27
Source analysis: saas-gap-deep-analysis.md
Epic summary¶
Close evidence-based gaps between the Metering template and the canonical DDD blueprint, cross-repo published language, and E2E integration path. Priority P0 items (saas-MET-F03, F04) block quota enforcement and Billing consumer wiring until inbound topology and publisher-side Dimension field align with Billing's consumer-side MeterKey expectation (resolved jointly with saas-BIL-F02 and saas-INTEG-F02). DefaultUsageMetersProcessor currently uses flat counter semantics without UsageRecord idempotency or Window VO roll model.
Rollup (from gap analysis): 9 Implemented · 8 Partial · 8 Missing · 1 Deferred
Feature index¶
| Order | ID | Title |
|---|---|---|
| 060 | saas-MET-F01 | UsageRecord value object and idempotency |
| 061 | saas-MET-F02 | Window value object and roll semantics |
| 062 | saas-MET-F03 | UsageReportedForQuota inbound topology |
| 063 | saas-MET-F04 | Quota contract alignment (Dimension vs MeterKey) |
| 064 | saas-MET-F05 | Orleans grain write-path wiring |
| 065 | saas-MET-F06 | Dead code and misnamed surrogate removal |
| 066 | saas-MET-F07 | Processor, saga, and aggregate tests |
| 067 | saas-MET-F08 | Optional Entitlements reaction registration |
| 068 | saas-MET-F09 | Architecture test enforcement |
[060] saas-MET-F01 — UsageRecord value object and idempotency¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 060
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, idempotency, P1
Priority: P1
Effort: L
Dependencies: —
Blocks: saas-MET-F07
Source gap analysis: metering-analysis · Finding M-001
Description (full):
Blueprint requires UsageRecord VO with fields such as OccurredAtUtc, Quantity, Source, IdempotencyKey, and append-only usage log semantics. Current ingest path: RecordUsageInput / RecordUsageRequest with TenantId + Dimension + Delta only. DefaultUsageMetersProcessor.RecordUsageAsync always increments CounterValue, bumps AggregateVersion, publishes UsageRecordedIntegrationEvent — no idempotency key, no dedupe store, no usage record history.
Published language rule #6: at-least-once delivery requires (tenantId, dimension, idempotencyKey) dedupe so replays do not double-count. JSON descriptor ConnectSoft.Saas.Metering.json has "valueObjects": [].
Acceptance criteria (testable):
- AC-1:
UsageRecordVO defined in EntityModel with idempotency key and metadata fields. - AC-2:
RecordUsageAsyncaccepts idempotency key on input and request DTOs. - AC-3: Duplicate
(tenantId, dimension, idempotencyKey)returns success without double increment or duplicate event publish. - AC-4: Dedupe store implemented (NHibernate table, Redis, or aggregate collection with documented limits).
- AC-5: JSON descriptor lists
UsageRecordin valueObjects. - AC-6: Unit tests prove replay safety.
Implementation notes (full):
- Files:
RecordUsageInput.cs,UsageMeterContracts.cs(RecordUsageRequest),DefaultUsageMetersProcessor.cs,UsageRecordedIntegrationEvent.cs, EntityModel, mappings - ADR:
docs/adr/0001-one-aggregate-root-per-repo.mdmay note VO addition - Tests: replace placeholder
UsageMeterAggregateTests.cs
Out of scope:
- External usage ingestion API gateway (platform)
- Billing rating-window trigger (BIL-F07)
Definition of done:
- All AC pass
- Tests green
- Descriptor updated
saas-MET-S01.1 — As a metering operator, I need idempotent usage ingestion so at-least-once delivery does not inflate counters¶
Type: User Story
Parent: saas-MET-F01
Implementation order: 060.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, idempotency
Priority: P1
Effort: L
Dependencies: —
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Implement UsageRecord-backed idempotent record path in DefaultUsageMetersProcessor and expose idempotency key on public ServiceModel contract.
Acceptance criteria (testable):
- AC-1: REST/gRPC record usage accepts optional/required idempotency key per API design.
- AC-2: Second call with same key is no-op for counter and events.
- AC-3: Different keys same dimension accumulate correctly.
Implementation notes (full):
- Processor:
RecordUsageAsync - Public API:
UsageMetersController,GrpcUsageMeterManagementService
Out of scope: Cross-dimension idempotency
Definition of done:
- All AC pass
saas-MET-T01.1.1 — Add UsageRecord VO and extend RecordUsageInput/Request¶
Type: Task
Parent: saas-MET-S01.1
Implementation order: 060.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, entity
Priority: P1
Effort: M
Dependencies: —
Blocks: saas-MET-T01.1.2
Source gap analysis: metering-analysis
Description (full):
Create UsageRecord value object and embed or associate with UsageMeterEntity. Add IdempotencyKey to domain input and ServiceModel request. Update NHibernate mapping and JSON descriptor.
Acceptance criteria (testable):
- AC-1: VO types compile and map to persistence.
- AC-2: API contracts include idempotency key field.
Implementation notes (full):
- EntityModel, PersistenceModel.NHibernate, ServiceModel
- Reference canonical DDD entities doc for field list
Out of scope: Processor dedupe logic
Definition of done:
- All AC pass
saas-MET-T01.1.2 — Implement dedupe in DefaultUsageMetersProcessor.RecordUsageAsync¶
Type: Task
Parent: saas-MET-S01.1
Implementation order: 060.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, processor
Priority: P1
Effort: M
Dependencies: saas-MET-T01.1.1
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Before incrementing counter, check dedupe index for (tenantId, dimension, idempotencyKey). On duplicate, return existing result without publish. On new key, append UsageRecord, increment, publish UsageRecordedIntegrationEvent.
Acceptance criteria (testable):
- AC-1: Unit test: two identical keys → single increment.
- AC-2: Unit test: two different keys → counter sum correct.
- AC-3: Event published once per unique key.
Implementation notes (full):
- Symbol:
DefaultUsageMetersProcessor.RecordUsageAsync - Consider saga replay calling same processor
Out of scope: Redis dedupe (unless chosen over DB)
Definition of done:
- All AC pass
- Tests green
[061] saas-MET-F02 — Window value object and roll semantics¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 061
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, window, P1
Priority: P1
Effort: L
Dependencies: saas-BIL-F07
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Blueprint defines Window VO (Fixed/Rolling, size, anchor) and per-window counters. Current code: single scalar CounterValue per tenant+dimension; "window" approximated by boolean ThresholdCrossedEmitted / QuotaExceededEmitted flags reset on roll in RollUsageCounterInnerAsync. Roll is manual/API-driven, not tied to billing period boundaries. Events lack windowId.
Replace flat counter model with Window VO semantics: non-decreasing counters within open window, ResetWindow, CounterRolledIntegrationEvent tied to window boundaries. Optional inbound reaction to Billing rating-window event (BIL-F07).
Acceptance criteria (testable):
- AC-1:
WindowVO in EntityModel with kind, size, anchor, windowId. - AC-2: Counter scoped to active window; roll closes window and opens new.
- AC-3:
RollUsageCounterAsyncuses Window semantics not bare flag reset. - AC-4: Outbound events include windowId where applicable.
- AC-5: Unit tests for roll invariants and non-decreasing counter within window.
Implementation notes (full):
- Files:
IUsageMeter.cs,UsageMeterEntity.cs,DefaultUsageMetersProcessor.cs(RollUsageCounterInnerAsync,PrepareQuotaTransitions) - Cross-service: BIL-F07 rating-window event as optional roll trigger
- JSON descriptor valueObjects
Out of scope:
- Multi-dimensional window aggregation across tenants
Definition of done:
- All AC pass
- Tests green
saas-MET-S02.1 — As a billing-period consumer, I need usage counters scoped to windows so quota resets align with subscription periods¶
Type: User Story
Parent: saas-MET-F02
Implementation order: 061.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, window
Priority: P1
Effort: L
Dependencies: —
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Model Window on UsageMeter aggregate and refactor roll/quota logic to reference active window instead of boolean emission flags.
Acceptance criteria (testable):
- AC-1: Active window identifiable on aggregate.
- AC-2: Roll produces new windowId and zeros counter for new window only.
- AC-3: Quota flags scoped per window cycle.
Implementation notes (full):
- Processor methods:
RollUsageCounterInnerAsync,PrepareQuotaTransitions
Out of scope: Billing event consumer (optional hook)
Definition of done:
- All AC pass
saas-MET-T02.1.1 — Introduce Window VO and persist on UsageMeterEntity¶
Type: Task
Parent: saas-MET-S02.1
Implementation order: 061.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, entity
Priority: P1
Effort: M
Dependencies: —
Blocks: saas-MET-T02.1.2
Source gap analysis: metering-analysis
Description (full):
Add Window value object (or embedded columns) to usage meter entity with NHibernate mapping. Migrate existing counter data to initial window on upgrade script.
Acceptance criteria (testable):
- AC-1: Window fields persisted.
- AC-2: Migration or default window for existing rows documented.
Implementation notes (full):
- EntityModel, PersistenceModel.NHibernate, DatabaseModel.Migrations
Out of scope: Roll processor refactor
Definition of done:
- All AC pass
saas-MET-T02.1.2 — Refactor DefaultUsageMetersProcessor roll and quota for Window semantics¶
Type: Task
Parent: saas-MET-S02.1
Implementation order: 061.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, processor
Priority: P1
Effort: M
Dependencies: saas-MET-T02.1.1
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Replace boolean flag reset pattern with window close/open in RollUsageCounterInnerAsync. Update PrepareQuotaTransitions to emit threshold/quota events with windowId. Publish CounterRolledIntegrationEvent with window metadata.
Acceptance criteria (testable):
- AC-1: Roll changes windowId.
- AC-2: Quota emission flags reset on new window only.
- AC-3: Unit tests for roll + quota sequence.
Implementation notes (full):
DefaultUsageMetersProcessor.cs
Out of scope: Rating-window inbound saga
Definition of done:
- All AC pass
- Tests green
[062] saas-MET-F03 — UsageReportedForQuota inbound topology¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 062
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, masstransit, P0
Priority: P0
Effort: M
Dependencies: saas-INTEG-F01
Blocks: —
Source gap analysis: metering-analysis · Finding M-002
Description (full):
UsageReportedForQuotaInboundEvent and UsageMeterQuotaEnforcementStateMachine exist — saga handles UsageReported and calls RecordUsageAsync. Inbound topology gap: MeteringMassTransitTopology.ConfigureInboundConsumedMessageTopology registers only TenantActivatedMeteringInboundEvent, SubscriptionCreatedMeteringInboundEvent, EntitlementsChangedQuotaInboundEvent — not UsageReportedForQuotaInboundEvent. No topic constant in UsageMetersConstants.InboundEventTopics / MeteringConstants.
Cross-repo publishers cannot bind to the saga consumer without matching SetEntityName. Gap analysis: "Event + saga exist, no SetEntityName."
Acceptance criteria (testable):
- AC-1: Inbound topic constant added to MeteringConstants/UsageMetersConstants.
- AC-2: Topology registers
SetEntityNamefor usage-reported message matching saga consumer. - AC-3: JSON descriptor
consumedEventsincludes usage-reported inbound event. - AC-4: Integration test: publish usage-reported message → saga → processor record invoked.
- AC-5: Topic aligns with published language canonical name.
Implementation notes (full):
- Files:
UsageReportedForQuotaInboundEvent.cs,UsageMeterQuotaEnforcementStateMachine.cs,MeteringMassTransitTopology.cs,MassTransitExtensions.cs, constants - Coordinate topic name with platform published language
Out of scope:
- External publisher implementation (other services)
Definition of done:
- All AC pass
- Topology verified
saas-MET-S03.1 — As an upstream usage reporter, I need a registered inbound topic so usage-reported messages reach the quota enforcement saga¶
Type: User Story
Parent: saas-MET-F03
Implementation order: 062.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, masstransit
Priority: P0
Effort: M
Dependencies: saas-INTEG-F01
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Wire missing inbound topology so existing saga is reachable on the bus.
Acceptance criteria (testable):
- AC-1: Saga consumer endpoint binds to canonical entity name.
- AC-2: Manual test message consumed successfully.
Implementation notes (full):
- Pattern: other three inbound events in same topology class
Out of scope: Saga logic changes
Definition of done:
- All AC pass
saas-MET-T03.1.1 — Add UsageReported inbound topic constant and descriptor entry¶
Type: Task
Parent: saas-MET-S03.1
Implementation order: 062.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, constants
Priority: P0
Effort: S
Dependencies: —
Blocks: saas-MET-T03.1.2
Source gap analysis: metering-analysis
Description (full):
Add constant to MeteringConstants.InboundEventTopics (or UsageMetersConstants). Update ConnectSoft.Saas.Metering.json consumedEvents.
Acceptance criteria (testable):
- AC-1: Constant value matches published language.
- AC-2: Descriptor synchronized.
Implementation notes (full):
- Constants file, JSON descriptor
Out of scope: Topology code
Definition of done:
- All AC pass
saas-MET-T03.1.2 — Register SetEntityName for UsageReportedForQuota in MeteringMassTransitTopology¶
Type: Task
Parent: saas-MET-S03.1
Implementation order: 062.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, topology
Priority: P0
Effort: S
Dependencies: saas-MET-T03.1.1
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Extend ConfigureInboundConsumedMessageTopology to include UsageReportedForQuotaInboundEvent with entity name from constant. Verify saga state machine consumer configuration matches.
Acceptance criteria (testable):
- AC-1: Topology configuration includes fourth inbound event.
- AC-2: Saga test or harness receives message type.
Implementation notes (full):
MeteringMassTransitTopology.cs,UsageMeterQuotaEnforcementStateMachine.cs
Out of scope: F01 idempotency in saga path (follow-up)
Definition of done:
- All AC pass
[063] saas-MET-F04 — Quota contract alignment (Dimension vs MeterKey)¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 063
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, quota, P0
Priority: P0
Effort: M
Dependencies: saas-INTEG-F02
Blocks: saas-BIL-F06
Source gap analysis: cross-quota-payload
Description (full):
Publisher-side (Metering) vs consumer-side (Billing) contract mismatch. Metering publishes QuotaExceededIntegrationEvent and QuotaThresholdCrossedIntegrationEvent with property Dimension. Billing inbound DTO MeteringQuotaExceededInboundIntegrationEvent expects MeterKey. MassTransit JSON deserialization will not map across different property names without explicit mapping.
Metering topic string on publish side is aligned: MeteringConstants.EventTopics.QuotaExceeded = metering.quota.v1.quota-exceeded. Billing topic drift is separate (BIL-F02). This feature fixes payload field name on publisher side (or dual-publish alias) in coordination with Billing consumer fix.
Acceptance criteria (testable):
- AC-1: Canonical field name agreed and documented (Dimension or MeterKey per INTEG-F02).
- AC-2:
QuotaExceededIntegrationEventand threshold event use canonical property; obsolete name removed or aliased with deprecation. - AC-3:
DefaultUsageMetersProcessor.PrepareQuotaTransitionspopulates canonical field. - AC-4: Cross-repo contract test: Metering publish → Billing saga deserializes meter identity (with BIL-F02).
- AC-5:
CrossRepoPublishedLanguageTests(F09) includes quota payload rule when implemented.
Implementation notes (full):
- Files:
QuotaExceededIntegrationEvent.cs,QuotaThresholdCrossedIntegrationEvent.cs,DefaultUsageMetersProcessor.cs - Billing peer:
MeteringQuotaExceededInboundIntegrationEvent.cs(saas-BIL-F02) - Prefer single name in published language glossary
Out of scope:
- Billing inbound topic strings (BIL-F02)
Definition of done:
- All AC pass
- E2E quota path with Billing verified
saas-MET-S04.1 — As Billing service (consumer), I need quota events with a field name I can deserialize so suspend-on-quota works E2E¶
Type: User Story
Parent: saas-MET-F04
Implementation order: 063.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, billing, quota
Priority: P0
Effort: M
Dependencies: saas-INTEG-F02
Blocks: saas-BIL-F06
Source gap analysis: cross-quota-payload
Description (full):
Align Metering outbound quota event payload with Billing consumer DTO. Metering is the publisher; Billing is the consumer — both must agree on Dimension vs MeterKey.
Acceptance criteria (testable):
- AC-1: Published JSON sample matches Billing inbound DTO shape.
- AC-2: Billing
ReactToQuotaExceededAsyncreceives non-null meter identity in E2E test.
Implementation notes (full):
- Coordinate merge order with BIL-F02 T02.1.2
- Update published language doc table if needed
Out of scope: Entitlements quota reactions
Definition of done:
- All AC pass
saas-MET-T04.1.1 — Rename or alias Dimension to canonical MeterKey/Dimension on quota events¶
Type: Task
Parent: saas-MET-S04.1
Implementation order: 063.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, messaging
Priority: P0
Effort: S
Dependencies: saas-INTEG-F02
Blocks: saas-MET-T04.1.2
Source gap analysis: cross-quota-payload
Description (full):
Apply agreed field name to QuotaExceededIntegrationEvent and threshold event. Update processor mapping in PrepareQuotaTransitions. If renaming to MeterKey, update internal Dimension references for outbound-only clarity or keep Dimension as domain term with JSON property alias.
Acceptance criteria (testable):
- AC-1: Event serialization sample reviewed against Billing DTO.
- AC-2: No breaking unpublished consumers within template solution.
Implementation notes (full):
- MessagingModel/Events/
- System.Text.Json or Newtonsoft attributes if alias approach
Out of scope: Billing changes
Definition of done:
- All AC pass
saas-MET-T04.1.2 — Add cross-repo quota payload contract test¶
Type: Task
Parent: saas-MET-S04.1
Implementation order: 063.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, tests
Priority: P0
Effort: S
Dependencies: saas-MET-T04.1.1, saas-BIL-F02
Blocks: —
Source gap analysis: cross-quota-payload
Description (full):
Add integration or contract test that serializes Metering quota event and deserializes into Billing inbound type (test project reference or shared test package). Prevents Dimension/MeterKey regression.
Acceptance criteria (testable):
- AC-1: Test fails if property names diverge.
- AC-2: Test runs in CI.
Implementation notes (full):
- ArchitectureTests or new ContractTests project
- May reference Billing MessagingModel as test-only dependency
Out of scope: Full MassTransit bus test
Definition of done:
- All AC pass
- CI green
[064] saas-MET-F05 — Orleans grain write-path wiring¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 064
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, orleans, P1
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06
Blocks: saas-MET-F09
Source gap analysis: cross-orleans
Description (full):
UsageMeterGrain implements grain interface, enforces composite key tenantId:dimension via UsageMeterGrainKey, delegates to DefaultUsageMetersProcessor. All write APIs (RecordUsage, RollCounter, EnsureProvisioned, ApplyQuota) in UsageMetersController and GrpcUsageMeterManagementService call IUsageMetersProcessor directly — no GetGrain<IUsageMeterGrain>. Sagas also use processor directly. Orleans host wired; grain unused on hot path.
Per INTEG-F06 / ADR-INTEG-001, route writes through grain for partition affinity and single-writer semantics.
Acceptance criteria (testable):
- AC-1: REST/gRPC writes resolve
IUsageMeterGrainwith composite key. - AC-2: Sagas invoke grain or documented exception with ADR if sagas stay processor-direct.
- AC-3: Grain still delegates to processor (no duplicated logic).
- AC-4: Enable
MeteringOrleansGrainPartitionTests(F09/F07) with real assertions.
Implementation notes (full):
- Files:
UsageMeterGrain.cs,UsageMetersController.cs,GrpcUsageMeterManagementService.cs,OrleansExtensions.cs, saga classes in FlowModel.MassTransit - Key:
UsageMeterGrainKey.Format/EnsureMatchesGrainKey
Out of scope:
- Read-only query grain caching
Definition of done:
- All AC pass
- Grain partition test enabled
saas-MET-S05.1 — As a platform architect, I need Metering writes routed through UsageMeterGrain per actor model¶
Type: User Story
Parent: saas-MET-F05
Implementation order: 064.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, orleans
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06
Blocks: —
Source gap analysis: cross-orleans
Description (full):
Refactor adapters to use IGrainFactory and composite grain key for all mutating operations.
Acceptance criteria (testable):
- AC-1: No direct processor injection in write controller methods.
- AC-2: Grain key validation enforced on record usage.
Implementation notes (full):
- Pattern: Billing F10, Tenants F08
Out of scope: Processor internal refactor
Definition of done:
- All AC pass
saas-MET-T05.1.1 — Refactor UsageMetersController and gRPC service to use IUsageMeterGrain¶
Type: Task
Parent: saas-MET-S05.1
Implementation order: 064.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, api, orleans
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06
Blocks: saas-MET-T05.1.2
Source gap analysis: cross-orleans
Description (full):
Replace processor calls with grain factory resolution using tenantId:dimension key on RecordUsage, RollCounter, EnsureProvisioned, ApplyQuota endpoints.
Acceptance criteria (testable):
- AC-1: All four write operations use grain.
- AC-2: Layering tests pass.
Implementation notes (full):
ServiceModel.RestApi/UsageMetersController.csServiceModel.Grpc/GrpcUsageMeterManagementService.cs
Out of scope: Saga grain routing
Definition of done:
- All AC pass
saas-MET-T05.1.2 — Route saga write calls through grain or document processor-direct ADR exception¶
Type: Task
Parent: saas-MET-S05.1
Implementation order: 064.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, saga, orleans
Priority: P1
Effort: S
Dependencies: saas-MET-T05.1.1
Blocks: —
Source gap analysis: cross-orleans
Description (full):
Update MassTransit sagas (UsageMeterQuotaEnforcementStateMachine, entitlement/quota sagas) to resolve grain before record/quota operations, OR add ADR note if sagas intentionally bypass grain for durability reasons (must align with INTEG-F06 decision).
Acceptance criteria (testable):
- AC-1: Decision documented in code or ADR.
- AC-2: Saga integration test passes with chosen path.
Implementation notes (full):
- FlowModel.MassTransit state machines
- Inject
IGrainFactoryinto saga definition if needed
Out of scope: In-memory saga repo → durable outbox (INTEG-F05)
Definition of done:
- All AC pass
[065] saas-MET-F06 — Dead code and misnamed surrogate removal¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 065
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, cleanup, P1
Priority: P1
Effort: M
Dependencies: —
Blocks: —
Source gap analysis: metering-analysis · Finding M-003
Description (full):
Orphan domain inputs from Entitlements scaffold not on IUsageMetersProcessor:
| File | Actual type | Status |
|---|---|---|
AssignEditionInput.cs |
SetQuotaInput |
Orphan |
ActivateUsageMeterInput.cs |
RollCounterInput |
Orphan |
OverrideTenantFeatureInput.cs |
ResetCounterInput |
Orphan |
CatalogProductRetiredReactionInput.cs |
same | Orphan |
Live API uses RollUsageCounterInput, ApplyUsageQuotaInput. Orleans surrogates misnamed: e.g. AssignEditionInputSurrogate.cs → SetQuotaInputSurrogate. MeteringMetrics.cs has RecordCatalogProductRetiredReaction* for unused input. build/scaffold-replacements-metering.ps1 documents copy-paste origin.
Remove dead surface or wire to processor with saga if product-required.
Acceptance criteria (testable):
- AC-1: No orphan input types without processor method or explicit removal.
- AC-2: Surrogate filenames match domain type names.
- AC-3: Unused metrics helpers removed or wired.
- AC-4:
scaffold-replacements-metering.ps1updated or removed. - AC-5: Build and architecture tests pass after deletion.
Implementation notes (full):
- DomainModel inputs, ActorModel.Orleans/Surrogates, Metrics/MeteringMetrics.cs
- Grep for references before delete
Out of scope:
- Catalog product retired saga (unless explicitly in scope)
Definition of done:
- All AC pass
- No dead public API surface
saas-MET-S06.1 — As a maintainer, I need Metering free of Entitlements scaffold orphans so the domain surface matches IUsageMetersProcessor¶
Type: User Story
Parent: saas-MET-F06
Implementation order: 065.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, cleanup
Priority: P1
Effort: M
Dependencies: —
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Audit and remove or implement orphan inputs and misnamed surrogates.
Acceptance criteria (testable):
- AC-1: DomainModel input count matches processor public methods.
- AC-2: Surrogate folder naming consistent.
Implementation notes (full):
- Compare with
IUsageMetersProcessorinterface
Out of scope: New catalog retirement feature
Definition of done:
- All AC pass
saas-MET-T06.1.1 — Delete orphan domain inputs and unused metrics¶
Type: Task
Parent: saas-MET-S06.1
Implementation order: 065.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, cleanup
Priority: P1
Effort: S
Dependencies: —
Blocks: saas-MET-T06.1.2
Source gap analysis: metering-analysis
Description (full):
Remove SetQuotaInput, misnamed roll/reset inputs, CatalogProductRetiredReactionInput if no saga planned. Remove RecordCatalogProductRetiredReaction* from MeteringMetrics.
Acceptance criteria (testable):
- AC-1: Solution builds with no references to removed types.
- AC-2: Grep confirms removal.
Implementation notes (full):
- DomainModel/, Metrics/MeteringMetrics.cs
Out of scope: Surrogate rename
Definition of done:
- All AC pass
saas-MET-T06.1.2 — Rename Orleans surrogates to match live domain types¶
Type: Task
Parent: saas-MET-S06.1
Implementation order: 065.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, orleans
Priority: P1
Effort: S
Dependencies: saas-MET-T06.1.1
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Rename surrogate files and types to match RecordUsageInput, RollUsageCounterInput, ApplyUsageQuotaInput, etc. Update surrogate registration. Refresh scaffold script.
Acceptance criteria (testable):
- AC-1: Filename/type alignment for all surrogates.
- AC-2: Orleans serialization tests or smoke test pass.
Implementation notes (full):
- ActorModel.Orleans/Surrogates/
build/scaffold-replacements-metering.ps1
Out of scope: New surrogates for removed types
Definition of done:
- All AC pass
[066] saas-MET-F07 — Processor, saga, and aggregate tests¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 066
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, tests, P1
Priority: P1
Effort: L
Dependencies: saas-MET-F01, saas-MET-F02, saas-MET-F03
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Test gap across Metering template:
| Test | Status |
|---|---|
UsageMeterAggregateTests.cs |
Placeholder Assert.IsTrue(true) |
MeteringDomainBoundaryTests.cs |
MeteringException only |
MeteringOrleansGrainPartitionTests.cs |
[Ignore], empty |
| AcceptanceTests | BaseTemplate welcome scaffold only |
No processor, saga, idempotency, or quota transition tests. Domain aggregate invariants not verified.
Acceptance criteria (testable):
- AC-1:
UsageMeterAggregateTestscovers record, roll, quota threshold/exceeded transitions. - AC-2: Idempotency tests after F01.
- AC-3: Saga topology or handler test for UsageReported path after F03.
- AC-4:
MeteringOrleansGrainPartitionTestsenabled with composite key assertions when F05 lands. - AC-5: No placeholder
Assert.IsTrue(true)in UnitTests processor coverage.
Implementation notes (full):
- UnitTests, ArchitectureTests
- Use test doubles for repository and event bus
Out of scope:
- Full Playwright acceptance scenarios
Definition of done:
- All AC pass
- CI green
saas-MET-S07.1 — As a developer, I need automated tests for DefaultUsageMetersProcessor so quota and idempotency behavior cannot regress¶
Type: User Story
Parent: saas-MET-F07
Implementation order: 066.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, tests
Priority: P1
Effort: L
Dependencies: saas-MET-F01
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Build real unit test suite for processor invariants replacing placeholders.
Acceptance criteria (testable):
- AC-1: Minimum 8 processor tests covering happy paths and edge cases.
- AC-2: Quota exceeded publishes event with correct Dimension/MeterKey field (F04).
Implementation notes (full):
DefaultUsageMetersProcessor.cstest harness
Out of scope: Silo cluster tests (F05/F09)
Definition of done:
- All AC pass
saas-MET-T07.1.1 — Replace UsageMeterAggregateTests placeholder with processor unit tests¶
Type: Task
Parent: saas-MET-S07.1
Implementation order: 066.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, unit-tests
Priority: P1
Effort: M
Dependencies: saas-MET-F01
Blocks: saas-MET-T07.1.2
Source gap analysis: metering-analysis
Description (full):
Implement tests: record usage, roll counter, apply quota, threshold cross, quota exceed, idempotent replay.
Acceptance criteria (testable):
- AC-1: At least 6 meaningful tests.
- AC-2: No
Assert.IsTrue(true).
Implementation notes (full):
tests/ConnectSoft.Saas.Metering.UnitTests/UsageMeterAggregateTests.cs
Out of scope: Saga tests
Definition of done:
- All AC pass
saas-MET-T07.1.2 — Add saga/topology test for UsageReported quota enforcement path¶
Type: Task
Parent: saas-MET-S07.1
Implementation order: 066.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, saga-tests
Priority: P1
Effort: M
Dependencies: saas-MET-F03, saas-MET-T07.1.1
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Add test harness verifying UsageMeterQuotaEnforcementStateMachine consumes usage-reported message and invokes record usage (in-memory MassTransit test or focused saga unit test).
Acceptance criteria (testable):
- AC-1: Test proves saga → processor call chain.
- AC-2: Fails if topology registration removed (F03 regression).
Implementation notes (full):
UsageMeterQuotaEnforcementStateMachine.cs- MassTransit test harness pattern from BaseTemplate if available
Out of scope: Full bus integration test
Definition of done:
- All AC pass
[067] saas-MET-F08 — Optional Entitlements reaction registration¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 067
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, entitlements, P2
Priority: P2
Effort: M
Dependencies: saas-BIL-F02
Blocks: —
Source gap analysis: priority-rationale
Description (full):
EntitlementsChangedQuotaStateMachine and EntitlementsChangedQuotaInboundEvent are always registered in MassTransitExtensions.AddMicroserviceMassTransit with in-memory saga repository. Topology binds MeteringConstants.InboundEventTopics.EntitlementsEffectiveSnapshotChanged → entitlements.v1.effective-entitlements-updated. No feature flag for deployments without Entitlements bounded context.
Gap priority table lists F08 as P2 optional cross-repo edge: make entitlements consumption configurable — skip saga/topology when Entitlements BC absent.
Acceptance criteria (testable):
- AC-1: Configuration option (e.g.
MeteringOptions:EnableEntitlementsQuotaSync) defaults to true for full stack, false documented for metering-only. - AC-2: When disabled, saga not registered and topology skips entitlements inbound binding.
- AC-3: When enabled, behavior unchanged from current.
- AC-4: README or docs describe deployment modes.
- AC-5: Test proves conditional registration.
Implementation notes (full):
- Files:
EntitlementsChangedQuotaStateMachine.cs,MassTransitExtensions.cs,MeteringMassTransitTopology.cs, options class - Topic already canonical vs Billing drift — verify constant value
Out of scope:
- Entitlements publisher changes
Definition of done:
- All AC pass
- Docs updated
saas-MET-S08.1 — As a deployer running Metering-only, I need to disable Entitlements inbound sagas so the service starts without missing publishers¶
Type: User Story
Parent: saas-MET-F08
Implementation order: 067.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, configuration
Priority: P2
Effort: M
Dependencies: —
Blocks: —
Source gap analysis: priority-rationale
Description (full):
Introduce feature flag controlling entitlements quota sync saga and topology.
Acceptance criteria (testable):
- AC-1: Metering-only docker-compose profile documented with flag false.
- AC-2: No startup errors when entitlements topic unused.
Implementation notes (full):
- appsettings.json, Docker compose samples
Out of scope: Other optional inbound sagas
Definition of done:
- All AC pass
saas-MET-T08.1.1 — Add MeteringOptions flag and conditional saga registration¶
Type: Task
Parent: saas-MET-S08.1
Implementation order: 067.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, options
Priority: P2
Effort: S
Dependencies: —
Blocks: saas-MET-T08.1.2
Source gap analysis: priority-rationale
Description (full):
Add options binding and wrap EntitlementsChangedQuotaStateMachine registration in MassTransitExtensions with flag check.
Acceptance criteria (testable):
- AC-1: Flag false → saga type not in registration list.
- AC-2: Flag true → current behavior.
Implementation notes (full):
MassTransitExtensions.cs, new options in ApplicationModel or InfrastructureModel
Out of scope: Topology skip
Definition of done:
- All AC pass
saas-MET-T08.1.2 — Conditionally skip entitlements inbound topology and add registration test¶
Type: Task
Parent: saas-MET-S08.1
Implementation order: 067.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, topology
Priority: P2
Effort: S
Dependencies: saas-MET-T08.1.1
Blocks: —
Source gap analysis: priority-rationale
Description (full):
Update MeteringMassTransitTopology to skip entitlements inbound when flag false. Add unit test asserting registration count differs by configuration.
Acceptance criteria (testable):
- AC-1: Topology method respects flag.
- AC-2: Automated test covers both modes.
Implementation notes (full):
MeteringMassTransitTopology.cs
Out of scope: Tenant/subscription optional flags
Definition of done:
- All AC pass
[068] saas-MET-F09 — Architecture test enforcement¶
Type: Feature
Parent: saas-EPIC-MET
Implementation order: 068
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, architecture-tests, P1
Priority: P1
Effort: M
Dependencies: saas-MET-F04, saas-MET-F05
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Metering ArchitectureTests status:
| File | Status |
|---|---|
EntityIsolationNetArchTests.cs |
Implemented |
ServiceModelLayeringNetArchTests.cs |
Implemented |
OneAggregateRootPerRepoTests.cs |
Placeholder |
CrossRepoPublishedLanguageTests.cs |
Placeholder |
MeteringOrleansGrainPartitionTests.cs |
Ignored, empty |
ADR-0001 claims CI enforcement; placeholders and ignored tests undermine exit criteria. Implement rules for single UsageMeter aggregate root, published language topic validation (including quota Dimension/MeterKey and inbound topology constants post F03/F04), and composite grain key partition test when F05 completes.
Acceptance criteria (testable):
- AC-1:
OneAggregateRootPerRepoTestsenforces single aggregate root. - AC-2:
CrossRepoPublishedLanguageTestsvalidates MeteringConstants topics and quota event public surface. - AC-3:
MeteringOrleansGrainPartitionTestsenabled withtenantId:dimensionkey assertions. - AC-4: No placeholder or empty ignored tests without linked work item.
- AC-5: CI runs ArchitectureTests on PR.
Implementation notes (full):
- Path:
tests/ConnectSoft.Saas.Metering.ArchitectureTests/ - Exemplar: Products Catalog CrossRepo and OneAggregate tests
- Include regression for F04 payload property name
Out of scope:
- Functional processor tests (F07)
Definition of done:
- All AC pass
- CI green
saas-MET-S09.1 — As a platform maintainer, I need Metering architecture tests to enforce aggregate and cross-repo contract rules¶
Type: User Story
Parent: saas-MET-F09
Implementation order: 068.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, netarch
Priority: P1
Effort: M
Dependencies: saas-MET-F04
Blocks: —
Source gap analysis: metering-analysis
Description (full):
Replace placeholders and enable ignored grain test with real assertions.
Acceptance criteria (testable):
- AC-1: All five arch test files have meaningful content or are merged intentionally.
- AC-2: Intentional violation fails tests locally once.
Implementation notes (full):
- NetArchTest.Rules
Out of scope: Shared cross-repo test NuGet (INTEG-F07)
Definition of done:
- All AC pass
saas-MET-T09.1.1 — Implement OneAggregateRootPerRepoTests and CrossRepoPublishedLanguageTests¶
Type: Task
Parent: saas-MET-S09.1
Implementation order: 068.1.1
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, netarch
Priority: P1
Effort: S
Dependencies: saas-MET-F04
Blocks: saas-MET-T09.1.2
Source gap analysis: cross-topic-harmonization
Description (full):
Replace placeholders. CrossRepo test validates outbound QuotaExceeded topic, inbound entitlements/tenant/subscription/usage-reported constants, and quota event property name matches Billing consumer contract (post F04).
Acceptance criteria (testable):
- AC-1: Single aggregate root asserted.
- AC-2: Topic constants match canonical list.
- AC-3: Quota event exposes agreed meter identity property.
Implementation notes (full):
OneAggregateRootPerRepoTests.cs,CrossRepoPublishedLanguageTests.cs- Reference Billing inbound type in test if needed for F04
Out of scope: Envelope fields
Definition of done:
- All AC pass
saas-MET-T09.1.2 — Enable MeteringOrleansGrainPartitionTests for composite tenantId:dimension key¶
Type: Task
Parent: saas-MET-S09.1
Implementation order: 068.1.2
Status: Not Started
Area path: ConnectSoft\SaaS\Metering
Iteration: TBD
Tags: saas-platform, gap, metering, orleans, netarch
Priority: P1
Effort: S
Dependencies: saas-MET-F05, saas-MET-T09.1.1
Blocks: —
Source gap analysis: cross-orleans
Description (full):
Remove [Ignore], implement tests verifying UsageMeterGrainKey.Format and EnsureMatchesGrainKey reject malformed keys and accept valid composite keys. Optional: silo test grain activation per partition.
Acceptance criteria (testable):
- AC-1: Test not ignored in CI.
- AC-2: Invalid key patterns fail validation test cases.
Implementation notes (full):
MeteringOrleansGrainPartitionTests.cs,UsageMeterGrainKey.cs
Out of scope: Full silo cluster load test
Definition of done:
- All AC pass
- CI green